forked from M-Labs/nac3
Compare commits
713 Commits
Author | SHA1 | Date | |
---|---|---|---|
1531b6cc98 | |||
9bbc40bbfa | |||
790e56d106 | |||
a00eb7969e | |||
27a6f47330 | |||
061747c67b | |||
dc91d9e35a | |||
438943ac6f | |||
678e56c95d | |||
fdfc80ca5f | |||
8b3429d62a | |||
f4c5038b95 | |||
ddd16738a6 | |||
44c49dc102 | |||
e4bd376587 | |||
44498f22f6 | |||
110416d07a | |||
08a7d01a13 | |||
3cd36fddc3 | |||
56a7a9e03d | |||
574ae40f97 | |||
aa293b6bea | |||
eb4b881690 | |||
3d0a1d281c | |||
ad67a99c8f | |||
8e2b50df21 | |||
06092ad29b | |||
d62c6b95fd | |||
95e29d9997 | |||
536ed2146c | |||
d484d44d95 | |||
ac978864f2 | |||
95254f8464 | |||
964945d244 | |||
ae09a0d444 | |||
01edd5af67 | |||
015714eee1 | |||
71dec251e3 | |||
fce61f7b8c | |||
babc081dbd | |||
5337dbe23b | |||
f862c01412 | |||
0c9705f5f1 | |||
5f940f86d9 | |||
5651e00688 | |||
f6745b987f | |||
e0dedc6580 | |||
28f574282c | |||
144f0922db | |||
c58ce9c3a9 | |||
f7e296da53 | |||
b58c99369e | |||
1a535db558 | |||
1ba2e287a6 | |||
f95f979ad3 | |||
48e2148c0f | |||
88e57f7120 | |||
d7633c42bc | |||
a4f53b6e6b | |||
9d9ead211e | |||
26a1b85206 | |||
2822074b2d | |||
fe67ed076c | |||
94e2414df0 | |||
2cee760404 | |||
230982dc84 | |||
2bd3f63991 | |||
b53266e9e6 | |||
86eb22bbf3 | |||
beaa38047d | |||
705dc4ff1c | |||
979209a526 | |||
c3927d0ef6 | |||
202a902cd0 | |||
b6e2644391 | |||
45cd01556b | |||
b6cd2a6993 | |||
a98f33e6d1 | |||
5839badadd | |||
56c845aac4 | |||
65a12d9ab3 | |||
9c6685fa8f | |||
2bb788e4bb | |||
42a2f243b5 | |||
3ce2eddcdc | |||
51bf126a32 | |||
1a197c67f6 | |||
581b2f7bb2 | |||
746329ec5d | |||
e60e8e837f | |||
9fdbe9695d | |||
8065e73598 | |||
192290889b | |||
1407553a2f | |||
c7697606e1 | |||
88d0ccbf69 | |||
a43b59539c | |||
fe06b2806f | |||
7f6c9a25ac | |||
6c8382219f | |||
9274a7b96b | |||
d1c0fe2900 | |||
f2c047ba57 | |||
5e2e77a500 | |||
f3cc4702b9 | |||
3e92c491f5 | |||
7f629f1579 | |||
5640a793e2 | |||
abbaa506ad | |||
f3dc02d646 | |||
ea217eaea1 | |||
5a34551905 | |||
6098b1b853 | |||
668ccb1c95 | |||
a3c624d69d | |||
bd06155f34 | |||
9c33c4209c | |||
122983f11c | |||
71c3a65a31 | |||
8c540d1033 | |||
0cc60a3d33 | |||
a59c26aa99 | |||
02d93b11d1 | |||
59cad5bfe1 | |||
4318f8de84 | |||
15ac00708a | |||
c8dfdcfdea | |||
600a5c8679 | |||
22c4d25802 | |||
308edb8237 | |||
9848795dcc | |||
58222feed4 | |||
518f21d174 | |||
e8e49684bf | |||
b2900b4883 | |||
c6dade1394 | |||
7e3fcc0845 | |||
d3b4c60d7f | |||
5b2b6db7ed | |||
15e62f467e | |||
2c88924ff7 | |||
a744b139ba | |||
2b2b2dbf8f | |||
d9f96dab33 | |||
c5ae0e7c36 | |||
b8dab6cf7c | |||
4d80ba38b7 | |||
33929bda24 | |||
a8e92212c0 | |||
908271014a | |||
c407622f5c | |||
d7952d0629 | |||
ca1395aed6 | |||
7799aa4987 | |||
76016a26ad | |||
8532bf5206 | |||
2cf64d8608 | |||
706759adb2 | |||
b90cf2300b | |||
0fc26df29e | |||
0b074c2cf2 | |||
a0f6961e0e | |||
b1c5c2e1d4 | |||
69320a6cf1 | |||
9e0601837a | |||
432c81a500 | |||
6beff7a268 | |||
6ca7aecd4a | |||
8fd7216243 | |||
4f5e417012 | |||
a0614bad83 | |||
5539d144ed | |||
b3891b9a0d | |||
6fb8939179 | |||
973dc5041a | |||
d0da688aa7 | |||
12c4e1cf48 | |||
9b988647ed | |||
35a7cecc12 | |||
7e3d87f841 | |||
ac0d83ef98 | |||
3ff6db1a29 | |||
d7b806afb4 | |||
fac60c3974 | |||
f5fb504a15 | |||
faa3bb97ad | |||
6a64c9d1de | |||
3dc8498202 | |||
cbf79c5e9c | |||
b8aa17bf8c | |||
f5b998cd9c | |||
c36f85ecb9 | |||
3a8c385e01 | |||
221de4d06a | |||
fb9fe8edf2 | |||
894083c6a3 | |||
669c6aca6b | |||
63d2b49b09 | |||
bf709889c4 | |||
1c72698d02 | |||
54f883f0a5 | |||
4a6845dac6 | |||
00236f48bc | |||
a3e6bb2292 | |||
17171065b1 | |||
540b35ec84 | |||
4bb00c52e3 | |||
faf07527cb | |||
d6a4d0a634 | |||
2242c5af43 | |||
318a675ea6 | |||
32e52ce198 | |||
665ca8e32d | |||
12c12b1d80 | |||
72972fa909 | |||
142cd48594 | |||
8adfe781c5 | |||
339b74161b | |||
8c5ba37d09 | |||
05a8948ff2 | |||
6d171ec284 | |||
0ba68f6657 | |||
693b2a8863 | |||
5faeede0e5 | |||
266707df9d | |||
3d3c258756 | |||
ed1182cb24 | |||
fd025c1137 | |||
f139db9af9 | |||
44487b76ae | |||
1332f113e8 | |||
7632d6f72a | |||
4948395ca2 | |||
3db3061d99 | |||
51c2175c80 | |||
1a31a50b8a | |||
6c10e3d056 | |||
2dbc1ec659 | |||
c80378063a | |||
513d30152b | |||
45e9360c4d | |||
2e01b77fc8 | |||
cea7cade51 | |||
d658d9b00e | |||
eeb474f9e6 | |||
88b72af2d1 | |||
b73f6c4d68 | |||
f47cdec650 | |||
d656880e44 | |||
a91602915a | |||
1c56005a01 | |||
bc40a32524 | |||
c820daf5f8 | |||
25d2de67f7 | |||
2cfb7a7e10 | |||
9238a5e86e | |||
76defac462 | |||
650f354b74 | |||
f062ef5f59 | |||
f52086b706 | |||
0a732691c9 | |||
cbff356d50 | |||
24ac3820b2 | |||
ba32fab374 | |||
c4052b6342 | |||
66c205275f | |||
c85e412206 | |||
075536d7bd | |||
13beeaa2bf | |||
2194dbddd5 | |||
94a1d547d6 | |||
d6565feed3 | |||
83154ef8e1 | |||
0744b938b8 | |||
56fa2b6803 | |||
d06c13f936 | |||
9808923258 | |||
5b11a1dbdd | |||
b21df53e0d | |||
0ec967a468 | |||
ca8459dc7b | |||
b0b804051a | |||
134af79fd6 | |||
7fe2c3496c | |||
144a3fc426 | |||
74096eb9f6 | |||
06e9d90d57 | |||
d89146aa02 | |||
5bade81ddb | |||
0452e6de78 | |||
635c944c90 | |||
e36af3b0a3 | |||
5b1aa812ed | |||
d3cd2a8d99 | |||
202a63274d | |||
76dd5191f5 | |||
8d9df0a615 | |||
07adfb2a18 | |||
f00e458f60 | |||
1bc95a7ba6 | |||
e85f4f9bd2 | |||
ce3e9bf4fe | |||
82091b1be8 | |||
32919949e2 | |||
2abe75d1f4 | |||
676412fe6d | |||
8b9df7252f | |||
6979843431 | |||
fed1361c6a | |||
aa94e0c8a4 | |||
f523e26227 | |||
f026b48e2a | |||
dc874f2994 | |||
95de0800b4 | |||
3d71c6a850 | |||
be55e2ac80 | |||
79c8b759ad | |||
4798c53a21 | |||
23974feae7 | |||
40a3bded36 | |||
c4420e6ab9 | |||
fd36f78005 | |||
8168692cc3 | |||
53d44b9595 | |||
6153f94b05 | |||
4730b595f3 | |||
c2fdb12397 | |||
82bf14785b | |||
2d4329e23c | |||
679656f9e1 | |||
210d9e2334 | |||
181ac3ec1a | |||
3acdfb304d | |||
6e24da9cc5 | |||
f0ab1b858a | |||
08129cc635 | |||
ad4832dcf4 | |||
520bbb246b | |||
b857f1e403 | |||
fa8af37e84 | |||
23b2fee4e7 | |||
ed79d5bb9e | |||
c35ad06949 | |||
135ef557f9 | |||
a176c3eb70 | |||
2cf79510c2 | |||
b6ff75dcaf | |||
588c15f80d | |||
82cc693b11 | |||
520e1adc56 | |||
73e81259f3 | |||
7627acea41 | |||
a777099ea8 | |||
876e6ea7b8 | |||
30c6cffbad | |||
51671800b6 | |||
7195476edb | |||
eecba0b71d | |||
7b4253ccd8 | |||
f58c3a11f8 | |||
d0766a116f | |||
64a3751fc2 | |||
9566047241 | |||
062e318dd5 | |||
c4dc36ae99 | |||
baac348ee6 | |||
847615fc2f | |||
5dfcc63978 | |||
025b3cd02f | |||
e0f440040c | |||
f0715e2b6d | |||
e7fca67786 | |||
52c731c312 | |||
00d1b9be9b | |||
8404d4c4dc | |||
e614dd4257 | |||
937a8b9698 | |||
876ad6c59c | |||
a920fe0501 | |||
727a1886b3 | |||
6af13a8261 | |||
3540d0ab29 | |||
3a6c53d760 | |||
87bc34f7ec | |||
f50a5f0345 | |||
a77fd213e0 | |||
8f1497df83 | |||
5ca2dbeec8 | |||
9a98cde595 | |||
5ba8601b39 | |||
26a01b14d5 | |||
d5f4817134 | |||
789bfb5a26 | |||
4bb0e60981 | |||
623fcf85af | |||
13f06f3e29 | |||
f0da9c0283 | |||
2c4bf3ce59 | |||
e980f19c93 | |||
cfbc37c1ed | |||
50264e8750 | |||
1b77e62901 | |||
fd44ee6887 | |||
c8866b1534 | |||
84a888758a | |||
9d550725b7 | |||
2edc1de0b6 | |||
c3b122acfc | |||
a94927a11d | |||
ebf86cd134 | |||
cccd8f2d00 | |||
3292aed099 | |||
96b7f29679 | |||
3d2abf73c8 | |||
f682e9bf7a | |||
b26cb2b360 | |||
2317516cf6 | |||
77de24ef74 | |||
234a6bde2a | |||
c3db6297d9 | |||
82fdb02d13 | |||
4efdd17513 | |||
49de81ef1e | |||
8492503af2 | |||
e1dbe2526a | |||
f37de381ce | |||
4452c8986a | |||
22e831cb76 | |||
cc538d221a | |||
0d5c53e60c | |||
976a9512c1 | |||
1eacaf9afa | |||
8c7e44098a | |||
282a3e1911 | |||
5cecb2bb74 | |||
1963c30744 | |||
27011f385b | |||
d6302b6ec8 | |||
fef4b2a5ce | |||
b3736c3e99 | |||
e328e44c9a | |||
9e4e90f8a0 | |||
8470915809 | |||
148900302e | |||
5ee08b585f | |||
f1581299fc | |||
af95ba5012 | |||
9c9756be33 | |||
2a922c7480 | |||
e3e2c36ef4 | |||
4f9a0110c4 | |||
12c0eed0a3 | |||
c679474f5c | |||
ab3fa05996 | |||
140f8f8a08 | |||
27fcf8926e | |||
afa7d9b100 | |||
c395472094 | |||
03870f222d | |||
e435b25756 | |||
bd792904f9 | |||
1c3a823670 | |||
f01d833d48 | |||
9d64e606f4 | |||
6dccb343bb | |||
d47534e2ad | |||
8886964776 | |||
f09f3c27a5 | |||
0bbc9ce6f5 | |||
457d3b6cd7 | |||
5f692debd8 | |||
c7735d935b | |||
b47ac1b89b | |||
a19f1065e3 | |||
5bf05c6a69 | |||
32746c37be | |||
1d6291b9ba | |||
16655959f2 | |||
beee3e1f7e | |||
d4c109b6ef | |||
5ffd06dd61 | |||
95d0c3c93c | |||
bd3d67f3d6 | |||
ddfb532b80 | |||
02933753ca | |||
a1f244834f | |||
d304afd333 | |||
ef04696b02 | |||
4dc5dbb856 | |||
fd9f66b8d9 | |||
5182453bd9 | |||
68556da5fd | |||
983f080ea7 | |||
031e660f18 | |||
b6dfcfcc38 | |||
c93ad152d7 | |||
68b97347b1 | |||
875d534de4 | |||
adadf56e2b | |||
9f610745b7 | |||
98199768e3 | |||
bfa9ceaae3 | |||
120f8da5c7 | |||
cee62aa6c5 | |||
fcda360ad6 | |||
87c20ada48 | |||
38e968cff6 | |||
5c5620692f | |||
0af1e37e99 | |||
854e33ed48 | |||
f020d61cbb | |||
10538b5296 | |||
d322c91697 | |||
3231eb0d78 | |||
1ca4de99b9 | |||
bf4b1aae47 | |||
08a5050f9a | |||
c2ab6b58ff | |||
0a84f7ac31 | |||
fd787ca3f5 | |||
4dbe07a0c0 | |||
2e055e8ab1 | |||
9d737743c1 | |||
c6b9aefe00 | |||
8ad09748d0 | |||
7a5a2db842 | |||
447eb9c387 | |||
92d6f0a5d3 | |||
7e4dab15ae | |||
ff1fed112c | |||
36a6a7b8cd | |||
2b635a0b97 | |||
60ad100fbb | |||
316f0824d8 | |||
7cf7634985 | |||
068f0d9faf | |||
95810d4229 | |||
630897b779 | |||
e546535df0 | |||
352f70b885 | |||
e95586f61e | |||
bb27e3d400 | |||
bb5147521f | |||
9518d3fe14 | |||
cbd333ab10 | |||
65d6104d00 | |||
8373a6cb0f | |||
f75ae78677 | |||
ea2ab0ef7c | |||
e49b760e34 | |||
aa92778363 | |||
e1487ed335 | |||
73500c9081 | |||
9ca34c714e | |||
7fc2a30c14 | |||
950f431483 | |||
a50c690428 | |||
48eb64403f | |||
2c44b58bb8 | |||
50230e61f3 | |||
0205161e35 | |||
a2fce49b26 | |||
60a503a791 | |||
0c49b30a90 | |||
c7de22287e | |||
1a54aaa1c0 | |||
c5629d4eb5 | |||
a79286113e | |||
901e921e00 | |||
45a323e969 | |||
11759a722f | |||
480a4bc0ad | |||
a1d3093196 | |||
85c5f2c044 | |||
f34c6053d6 | |||
e8a5f0dfef | |||
7140901261 | |||
2a775d822e | |||
1659c3e724 | |||
f53cb804ec | |||
279376a373 | |||
b6afd1bfda | |||
be3e8f50a2 | |||
059d3da58b | |||
9b28f23d8c | |||
119f4d63e9 | |||
458fa12788 | |||
48c6498d1f | |||
2a38d5160e | |||
b39831b388 | |||
cb39f61e79 | |||
176f250bdb | |||
acdb1de6fe | |||
31dcd2dde9 | |||
fc93fc2f0e | |||
dd42022633 | |||
6dfc43c8b0 | |||
ab2360d7a0 | |||
ee1ee4ab3b | |||
3e430b9b40 | |||
9e57498958 | |||
769fd01df8 | |||
411837cacd | |||
f59d45805f | |||
048fcb0a69 | |||
676d07657a | |||
2482a1ef9b | |||
eb63f2ad48 | |||
ff27e22ee6 | |||
d672ef094b | |||
d25921230e | |||
66f07b5bf4 | |||
008d50995c | |||
474f9050ce | |||
3993a5cf3f | |||
39724de598 | |||
e4940247f3 | |||
4481d48709 | |||
b4983526bd | |||
b4a9616648 | |||
e0de82993f | |||
6805253515 | |||
19915bac79 | |||
17b4686260 | |||
6de0884dc1 | |||
f1b0e05b3d | |||
ff23968544 | |||
049908044a | |||
d37287a33d | |||
283bd7c69a | |||
3d73f5c129 | |||
d824c5d8b5 | |||
b8d637f5c4 | |||
3af287d1c4 | |||
5b53be0311 | |||
aead36f0fd | |||
c269444c0b | |||
52cec3c12f | |||
2927f2a1d0 | |||
c1c45373a6 | |||
946ea155b8 | |||
085c6ee738 | |||
cfa67c418a | |||
813bfa92a7 | |||
fff4b65169 | |||
c891fffd75 | |||
12acd35e15 | |||
f66ca02b2d | |||
b514f91441 | |||
8f95b79257 | |||
ebd25af38b | |||
96b3a3bf5c | |||
a18d095245 | |||
b242463548 | |||
8e6e4d6715 | |||
73c2aefe4b | |||
892597cda4 | |||
33321c5e9c | |||
50ed04b787 | |||
7cb9be0f81 | |||
ac560ba985 | |||
a96371145d | |||
8addf2b55e | |||
5d5e9a5e02 | |||
4c39dd240f | |||
48fc5ceb8e | |||
c4ab2855e5 | |||
ffac37dc48 | |||
76473152e8 | |||
b04631e935 | |||
09820e5aed | |||
0ec2ed4d91 | |||
2cb725b7ac | |||
b9259b1907 | |||
096f4b03c0 | |||
a022005183 | |||
325ba0a408 | |||
ae6434696c | |||
3f327113b2 | |||
27d509d70e | |||
a321b13bec | |||
48cb485b89 | |||
837aaa95f1 | |||
a19e9c0bec | |||
5dbe1d3d7d | |||
e9bca3c822 | |||
42d1aad507 | |||
2777a6e05f | |||
05be5e93c4 | |||
85f21060e4 | |||
a308d24caa | |||
1eac111d4c | |||
44199781dc | |||
711c3d3303 | |||
0975264482 | |||
087aded3a3 | |||
f14b32be67 | |||
|
879c66cccf | ||
|
35b6459c58 | ||
e94b25f544 | |||
6972689469 | |||
3fb22c9182 | |||
1e7abf0268 | |||
f5a6d29106 | |||
ca07cb66cd | |||
93e9a6a38a | |||
722e3df086 | |||
ad9ad22cb8 | |||
f66f66b3a4 | |||
6c485bc9dc | |||
089bba96a3 | |||
0e0871bc38 | |||
26187bff0b |
32
.clang-format
Normal file
32
.clang-format
Normal file
@ -0,0 +1,32 @@
|
||||
BasedOnStyle: LLVM
|
||||
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
AccessModifierOffset: -1
|
||||
AlignEscapedNewlines: Left
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
BinPackParameters: false
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: AfterColon
|
||||
BreakInheritanceList: AfterColon
|
||||
ColumnLimit: 120
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ContinuationIndentWidth: 4
|
||||
DerivePointerAlignment: false
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
MaxEmptyLinesToKeep: 1
|
||||
PointerAlignment: Left
|
||||
ReflowComments: true
|
||||
SortIncludes: false
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
1
.clippy.toml
Normal file
1
.clippy.toml
Normal file
@ -0,0 +1 @@
|
||||
doc-valid-idents = ["CPython", "NumPy", ".."]
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
__pycache__
|
||||
/target
|
||||
windows/msys2
|
||||
/nac3standalone/demo/linalg/target
|
||||
nix/windows/msys2
|
||||
|
24
.pre-commit-config.yaml
Normal file
24
.pre-commit-config.yaml
Normal file
@ -0,0 +1,24 @@
|
||||
# See https://pre-commit.com for more information
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
|
||||
default_stages: [pre-commit]
|
||||
|
||||
repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: nac3-cargo-fmt
|
||||
name: nac3 cargo format
|
||||
entry: nix
|
||||
language: system
|
||||
types: [file, rust]
|
||||
pass_filenames: false
|
||||
description: Runs cargo fmt on the codebase.
|
||||
args: [develop, -c, cargo, fmt, --all]
|
||||
- id: nac3-cargo-clippy
|
||||
name: nac3 cargo clippy
|
||||
entry: nix
|
||||
language: system
|
||||
types: [file, rust]
|
||||
pass_filenames: false
|
||||
description: Runs cargo clippy on the codebase.
|
||||
args: [develop, -c, cargo, clippy, --tests]
|
1313
Cargo.lock
generated
1313
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,15 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"nac3ld",
|
||||
"nac3ast",
|
||||
"nac3parser",
|
||||
"nac3core",
|
||||
"nac3core/nac3core_derive",
|
||||
"nac3standalone",
|
||||
"nac3artiq",
|
||||
"runkernel",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
[profile.release]
|
||||
debug = true
|
||||
|
33
README.md
33
README.md
@ -1,5 +1,10 @@
|
||||
# NAC3
|
||||
<div align="center">
|
||||
|
||||
![icon](https://git.m-labs.hk/M-Labs/nac3/raw/branch/master/nac3.svg)
|
||||
|
||||
</div>
|
||||
|
||||
# NAC3
|
||||
NAC3 is a major, backward-incompatible rewrite of the compiler for the [ARTIQ](https://m-labs.hk/artiq) physics experiment control and data acquisition system. It features greatly improved compilation speeds, a much better type system, and more predictable and transparent operation.
|
||||
|
||||
NAC3 has a modular design and its applicability reaches beyond ARTIQ. The ``nac3core`` module does not contain anything specific to ARTIQ, and can be used in any project that requires compiling Python to machine code.
|
||||
@ -8,7 +13,7 @@ NAC3 has a modular design and its applicability reaches beyond ARTIQ. The ``nac3
|
||||
|
||||
## Packaging
|
||||
|
||||
NAC3 is packaged using the [Nix](https://nixos.org) Flakes system. Install Nix 2.4+ and enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
|
||||
NAC3 is packaged using the [Nix](https://nixos.org) Flakes system. Install Nix 2.8+ and enable flakes by adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (e.g. ``~/.config/nix/nix.conf``).
|
||||
|
||||
## Try NAC3
|
||||
|
||||
@ -18,27 +23,19 @@ After setting up Nix as above, use ``nix shell git+https://github.com/m-labs/art
|
||||
|
||||
### Windows
|
||||
|
||||
Install [MSYS2](https://www.msys2.org/), and open "MSYS2 MinGW x64". Edit ``/etc/pacman.conf`` to add:
|
||||
Install [MSYS2](https://www.msys2.org/), and open "MSYS2 CLANG64". Edit ``/etc/pacman.conf`` to add:
|
||||
```
|
||||
[artiq]
|
||||
SigLevel = Optional TrustAll
|
||||
Server = https://lab.m-labs.hk/msys2
|
||||
Server = https://msys2.m-labs.hk/artiq-nac3
|
||||
```
|
||||
|
||||
Then run the following commands:
|
||||
```
|
||||
pacman -Syu
|
||||
pacman -S mingw-w64-x86_64-artiq
|
||||
pacman -S mingw-w64-clang-x86_64-artiq
|
||||
```
|
||||
|
||||
Install ``lld-msys2`` manually:
|
||||
```
|
||||
wget https://nixbld.m-labs.hk/build/115527/download/1/ld.lld.exe
|
||||
mv ld.lld.exe C:/msys64/mingw64/bin
|
||||
```
|
||||
|
||||
Note: This build of NAC3 cannot be used with Anaconda Python nor the python.org binaries for Windows. Those Python versions are compiled with Visual Studio (MSVC) and their ABI is incompatible with the GNU ABI used in this build. We have no plans to support Visual Studio nor the MSVC ABI. If you need a MSVC build, please install the requisite bloated spyware from Microsoft and compile NAC3 yourself.
|
||||
|
||||
## For developers
|
||||
|
||||
This repository contains:
|
||||
@ -46,6 +43,7 @@ This repository contains:
|
||||
- ``nac3parser``: Python parser (based on RustPython).
|
||||
- ``nac3core``: Core compiler library, containing type-checking and code generation.
|
||||
- ``nac3standalone``: Standalone compiler tool (core language only).
|
||||
- ``nac3ld``: Minimalist RISC-V and ARM linker.
|
||||
- ``nac3artiq``: Integration with ARTIQ and implementation of ARTIQ-specific extensions to the core language.
|
||||
- ``runkernel``: Simple program that runs compiled ARTIQ kernels on the host and displays RTIO operations. Useful for testing without hardware.
|
||||
|
||||
@ -53,3 +51,12 @@ Use ``nix develop`` in this repository to enter a development shell.
|
||||
If you are using a different shell than bash you can use e.g. ``nix develop --command fish``.
|
||||
|
||||
Build NAC3 with ``cargo build --release``. See the demonstrations in ``nac3artiq`` and ``nac3standalone``.
|
||||
|
||||
### Pre-Commit Hooks
|
||||
|
||||
You are strongly recommended to use the provided pre-commit hooks to automatically reformat files and check for non-optimal Rust practices using Clippy. Run `pre-commit install` to install the hook and `pre-commit` will automatically run `cargo fmt` and `cargo clippy` for you.
|
||||
|
||||
Several things to note:
|
||||
|
||||
- If `cargo fmt` or `cargo clippy` returns an error, the pre-commit hook will fail. You should fix all errors before trying to commit again.
|
||||
- If `cargo fmt` reformats some files, the pre-commit hook will also fail. You should review the changes and, if satisfied, try to commit again.
|
||||
|
8
flake.lock
generated
8
flake.lock
generated
@ -2,16 +2,16 @@
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1649024309,
|
||||
"narHash": "sha256-AWbvj/NHZXVwAnHaVOFlxg7tcNerEKrKBmgGfztSHWM=",
|
||||
"lastModified": 1733940404,
|
||||
"narHash": "sha256-Pj39hSoUA86ZePPF/UXiYHHM7hMIkios8TYG29kQT4g=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "af0a9bc0e5341855518e9c1734d7ef913e5138b9",
|
||||
"rev": "5d67ea6b4b63378b9c13be21e2ec9d1afc921713",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-21.11",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
108
flake.nix
108
flake.nix
@ -1,29 +1,57 @@
|
||||
{
|
||||
description = "The third-generation ARTIQ compiler";
|
||||
|
||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
|
||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-unstable;
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
pkgs32 = import nixpkgs { system = "i686-linux"; };
|
||||
in rec {
|
||||
packages.x86_64-linux = rec {
|
||||
llvm-nac3 = pkgs.callPackage ./nix/llvm {};
|
||||
llvm-tools-irrt = pkgs.runCommandNoCC "llvm-tools-irrt" {}
|
||||
''
|
||||
mkdir -p $out/bin
|
||||
ln -s ${pkgs.llvmPackages_14.clang-unwrapped}/bin/clang $out/bin/clang-irrt
|
||||
ln -s ${pkgs.llvmPackages_14.llvm.out}/bin/llvm-as $out/bin/llvm-as-irrt
|
||||
'';
|
||||
demo-linalg-stub = pkgs.rustPlatform.buildRustPackage {
|
||||
name = "demo-linalg-stub";
|
||||
src = ./nac3standalone/demo/linalg;
|
||||
cargoLock = {
|
||||
lockFile = ./nac3standalone/demo/linalg/Cargo.lock;
|
||||
};
|
||||
doCheck = false;
|
||||
};
|
||||
demo-linalg-stub32 = pkgs32.rustPlatform.buildRustPackage {
|
||||
name = "demo-linalg-stub32";
|
||||
src = ./nac3standalone/demo/linalg;
|
||||
cargoLock = {
|
||||
lockFile = ./nac3standalone/demo/linalg/Cargo.lock;
|
||||
};
|
||||
doCheck = false;
|
||||
};
|
||||
nac3artiq = pkgs.python3Packages.toPythonModule (
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
pkgs.rustPlatform.buildRustPackage rec {
|
||||
name = "nac3artiq";
|
||||
outputs = [ "out" "runkernel" "standalone" ];
|
||||
src = self;
|
||||
cargoLock = { lockFile = ./Cargo.lock; };
|
||||
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_13.clang-unwrapped llvm-nac3 ];
|
||||
cargoLock = {
|
||||
lockFile = ./Cargo.lock;
|
||||
};
|
||||
passthru.cargoLock = cargoLock;
|
||||
nativeBuildInputs = [ pkgs.python3 (pkgs.wrapClangMulti pkgs.llvmPackages_14.clang) llvm-tools-irrt pkgs.llvmPackages_14.llvm.out llvm-nac3 ];
|
||||
buildInputs = [ pkgs.python3 llvm-nac3 ];
|
||||
checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ])) ];
|
||||
checkInputs = [ (pkgs.python3.withPackages(ps: [ ps.numpy ps.scipy ])) ];
|
||||
checkPhase =
|
||||
''
|
||||
echo "Checking nac3standalone demos..."
|
||||
pushd nac3standalone/demo
|
||||
patchShebangs .
|
||||
./check_demos.sh
|
||||
export DEMO_LINALG_STUB=${demo-linalg-stub}/lib/liblinalg.a
|
||||
export DEMO_LINALG_STUB32=${demo-linalg-stub32}/lib/liblinalg.a
|
||||
./check_demos.sh -i686
|
||||
popd
|
||||
echo "Running Cargo tests..."
|
||||
cargoCheckHook
|
||||
@ -49,21 +77,21 @@
|
||||
|
||||
# LLVM PGO support
|
||||
llvm-nac3-instrumented = pkgs.callPackage ./nix/llvm {
|
||||
stdenv = pkgs.llvmPackages_13.stdenv;
|
||||
stdenv = pkgs.llvmPackages_14.stdenv;
|
||||
extraCmakeFlags = [ "-DLLVM_BUILD_INSTRUMENTED=IR" ];
|
||||
};
|
||||
nac3artiq-instrumented = pkgs.python3Packages.toPythonModule (
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
name = "nac3artiq-instrumented";
|
||||
src = self;
|
||||
cargoLock = { lockFile = ./Cargo.lock; };
|
||||
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_13.clang-unwrapped llvm-nac3-instrumented ];
|
||||
inherit (nac3artiq) cargoLock;
|
||||
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt llvm-nac3-instrumented ];
|
||||
buildInputs = [ pkgs.python3 llvm-nac3-instrumented ];
|
||||
cargoBuildFlags = [ "--package" "nac3artiq" "--features" "init-llvm-profile" ];
|
||||
doCheck = false;
|
||||
configurePhase =
|
||||
''
|
||||
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-L${pkgs.llvmPackages_13.compiler-rt}/lib/linux -C link-arg=-lclang_rt.profile-x86_64"
|
||||
export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C link-arg=-L${pkgs.llvmPackages_14.compiler-rt}/lib/linux -C link-arg=-lclang_rt.profile-x86_64"
|
||||
'';
|
||||
installPhase =
|
||||
''
|
||||
@ -75,11 +103,35 @@
|
||||
);
|
||||
nac3artiq-profile = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "nac3artiq-profile";
|
||||
src = self;
|
||||
buildInputs = [ (python3-mimalloc.withPackages(ps: [ ps.numpy nac3artiq-instrumented ])) pkgs.lld_13 pkgs.llvmPackages_13.libllvm ];
|
||||
srcs = [
|
||||
(pkgs.fetchFromGitHub {
|
||||
owner = "m-labs";
|
||||
repo = "sipyco";
|
||||
rev = "094a6cd63ffa980ef63698920170e50dc9ba77fd";
|
||||
sha256 = "sha256-PPnAyDedUQ7Og/Cby9x5OT9wMkNGTP8GS53V6N/dk4w=";
|
||||
})
|
||||
(pkgs.fetchFromGitHub {
|
||||
owner = "m-labs";
|
||||
repo = "artiq";
|
||||
rev = "28c9de3e251daa89a8c9fd79d5ab64a3ec03bac6";
|
||||
sha256 = "sha256-vAvpbHc5B+1wtG8zqN7j9dQE1ON+i22v+uqA+tw6Gak=";
|
||||
})
|
||||
];
|
||||
buildInputs = [
|
||||
(python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ps.jsonschema ps.lmdb ps.platformdirs nac3artiq-instrumented ]))
|
||||
pkgs.llvmPackages_14.llvm.out
|
||||
];
|
||||
phases = [ "buildPhase" "installPhase" ];
|
||||
# TODO: get more representative code.
|
||||
buildPhase = "python $src/nac3artiq/demo/demo.py";
|
||||
buildPhase =
|
||||
''
|
||||
srcs=($srcs)
|
||||
sipyco=''${srcs[0]}
|
||||
artiq=''${srcs[1]}
|
||||
export PYTHONPATH=$sipyco:$artiq
|
||||
python -m artiq.frontend.artiq_ddb_template $artiq/artiq/examples/nac3devices/nac3devices.json > device_db.py
|
||||
cp $artiq/artiq/examples/nac3devices/nac3devices.py .
|
||||
python -m artiq.frontend.artiq_compile nac3devices.py
|
||||
'';
|
||||
installPhase =
|
||||
''
|
||||
mkdir $out
|
||||
@ -87,15 +139,15 @@
|
||||
'';
|
||||
};
|
||||
llvm-nac3-pgo = pkgs.callPackage ./nix/llvm {
|
||||
stdenv = pkgs.llvmPackages_13.stdenv;
|
||||
stdenv = pkgs.llvmPackages_14.stdenv;
|
||||
extraCmakeFlags = [ "-DLLVM_PROFDATA_FILE=${nac3artiq-profile}/llvm.profdata" ];
|
||||
};
|
||||
nac3artiq-pgo = pkgs.python3Packages.toPythonModule (
|
||||
pkgs.rustPlatform.buildRustPackage {
|
||||
name = "nac3artiq-pgo";
|
||||
src = self;
|
||||
cargoLock = { lockFile = ./Cargo.lock; };
|
||||
nativeBuildInputs = [ pkgs.python3 pkgs.llvmPackages_13.clang-unwrapped llvm-nac3-pgo ];
|
||||
inherit (nac3artiq) cargoLock;
|
||||
nativeBuildInputs = [ pkgs.python3 packages.x86_64-linux.llvm-tools-irrt llvm-nac3-pgo ];
|
||||
buildInputs = [ pkgs.python3 llvm-nac3-pgo ];
|
||||
cargoBuildFlags = [ "--package" "nac3artiq" ];
|
||||
cargoTestFlags = [ "--package" "nac3ast" "--package" "nac3parser" "--package" "nac3core" "--package" "nac3artiq" ];
|
||||
@ -111,22 +163,29 @@
|
||||
|
||||
packages.x86_64-w64-mingw32 = import ./nix/windows { inherit pkgs; };
|
||||
|
||||
devShell.x86_64-linux = pkgs.mkShell {
|
||||
devShells.x86_64-linux.default = pkgs.mkShell {
|
||||
name = "nac3-dev-shell";
|
||||
buildInputs = with pkgs; [
|
||||
# build dependencies
|
||||
packages.x86_64-linux.llvm-nac3
|
||||
llvmPackages_13.clang-unwrapped # IRRT
|
||||
(pkgs.wrapClangMulti llvmPackages_14.clang) llvmPackages_14.llvm.out # for running nac3standalone demos
|
||||
packages.x86_64-linux.llvm-tools-irrt
|
||||
cargo
|
||||
rustc
|
||||
# runtime dependencies
|
||||
lld_13
|
||||
(packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ]))
|
||||
lld_14 # for running kernels on the host
|
||||
(packages.x86_64-linux.python3-mimalloc.withPackages(ps: [ ps.numpy ps.scipy ]))
|
||||
# development tools
|
||||
cargo-insta
|
||||
clippy
|
||||
pre-commit
|
||||
rustfmt
|
||||
];
|
||||
shellHook =
|
||||
''
|
||||
export DEMO_LINALG_STUB=${packages.x86_64-linux.demo-linalg-stub}/lib/liblinalg.a
|
||||
export DEMO_LINALG_STUB32=${packages.x86_64-linux.demo-linalg-stub32}/lib/liblinalg.a
|
||||
'';
|
||||
};
|
||||
devShells.x86_64-linux.msys2 = pkgs.mkShell {
|
||||
name = "nac3-dev-shell-msys2";
|
||||
@ -139,16 +198,15 @@
|
||||
};
|
||||
|
||||
hydraJobs = {
|
||||
inherit (packages.x86_64-linux) llvm-nac3 nac3artiq;
|
||||
inherit (packages.x86_64-linux) llvm-nac3 nac3artiq nac3artiq-pgo;
|
||||
llvm-nac3-msys2 = packages.x86_64-w64-mingw32.llvm-nac3;
|
||||
nac3artiq-msys2 = packages.x86_64-w64-mingw32.nac3artiq;
|
||||
nac3artiq-msys2-pkg = packages.x86_64-w64-mingw32.nac3artiq-pkg;
|
||||
lld-msys2 = packages.x86_64-w64-mingw32.lld;
|
||||
};
|
||||
};
|
||||
|
||||
nixConfig = {
|
||||
binaryCachePublicKeys = ["nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc="];
|
||||
binaryCaches = ["https://nixbld.m-labs.hk" "https://cache.nixos.org"];
|
||||
extra-trusted-public-keys = "nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc=";
|
||||
extra-substituters = "https://nixbld.m-labs.hk";
|
||||
};
|
||||
}
|
||||
|
56
nac3.svg
Normal file
56
nac3.svg
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="a"
|
||||
width="128"
|
||||
height="128"
|
||||
viewBox="0 0 95.99999 95.99999"
|
||||
version="1.1"
|
||||
sodipodi:docname="nac3.svg"
|
||||
inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs11" />
|
||||
<sodipodi:namedview
|
||||
id="namedview9"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
width="128px"
|
||||
inkscape:zoom="5.9448568"
|
||||
inkscape:cx="60.472441"
|
||||
inkscape:cy="60.556547"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1371"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="a" />
|
||||
<rect
|
||||
x="40.072601"
|
||||
y="-26.776209"
|
||||
width="55.668747"
|
||||
height="55.668747"
|
||||
transform="matrix(0.71803815,0.69600374,-0.71803815,0.69600374,0,0)"
|
||||
style="fill:#be211e;stroke:#000000;stroke-width:4.37375px;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="rect2" />
|
||||
<line
|
||||
x1="38.00692"
|
||||
y1="63.457153"
|
||||
x2="57.993061"
|
||||
y2="63.457153"
|
||||
style="fill:none;stroke:#000000;stroke-width:4.37269px;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="line4" />
|
||||
<path
|
||||
d="m 48.007301,57.843329 c -1.943097,0 -3.877522,-0.41727 -5.686157,-1.246007 -3.218257,-1.474616 -5.650382,-4.075418 -6.849639,-7.323671 -2.065624,-5.588921 -1.192751,-10.226647 2.575258,-13.827 0.611554,-0.584909 1.518048,-0.773041 2.323689,-0.488206 0.80673,0.286405 1.369495,0.998486 1.447563,1.827234 0.237469,2.549302 2.439719,5.917376 4.28414,6.55273 0.396859,0.13506 0.820953,-0.05859 1.097084,-0.35222 0.339254,-0.360754 0.451065,-0.961893 -1.013597,-3.191372 -2.089851,-3.181137 -4.638728,-8.754903 -0.262407,-15.069853 0.494457,-0.713491 1.384673,-1.068907 2.256469,-0.909156 0.871795,0.161332 1.583757,0.806404 1.752251,1.651189 0.716448,3.591862 2.962357,6.151755 5.199306,8.023138 1.935503,1.61861 4.344688,3.867387 5.435687,7.096643 2.283183,6.758017 -1.202511,14.114988 -8.060822,16.494025 -1.467083,0.509226 -2.98513,0.762536 -4.498836,0.762536 z M 39.358865,40.002192 c -0.304711,0.696206 -0.541636,2.080524 -0.56865,2.237454 -0.330316,1.918771 0.168305,3.803963 0.846157,5.539951 0.856828,2.19436 2.437543,3.942467 4.583411,4.925713 2.143691,0.981675 4.554131,1.097816 6.789992,0.322666 4.571485,-1.586549 6.977584,-6.532238 5.363036,-11.02597 v -5.27e-4 C 55.455481,39.447968 54.023463,38.162043 52.221335,36.65432 50.876945,35.529534 49.409662,33.987726 48.417983,32.135555 48.01343,31.37996 47.79547,30.34303 47.76669,29.413263 c -0.187481,0.669514 -0.212441,2.325923 -0.150396,2.93691 0.179209,1.764456 1.333476,3.644546 2.340611,5.171243 1.311568,1.988179 2.72058,6.037272 0.459681,8.367985 -1.54192,1.58953 -4.038511,2.052034 -5.839973,1.38492 -2.398314,-0.888147 -3.942744,-2.690627 -4.941118,-4.768029 -0.121194,-0.25217 -0.532464,-1.174187 -0.276619,-2.5041 z"
|
||||
id="path6"
|
||||
style="stroke-width:1.09317" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
@ -2,23 +2,20 @@
|
||||
name = "nac3artiq"
|
||||
version = "0.1.0"
|
||||
authors = ["M-Labs"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "nac3artiq"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
pyo3 = { version = "0.14", features = ["extension-module"] }
|
||||
parking_lot = "0.11"
|
||||
tempfile = "3"
|
||||
nac3parser = { path = "../nac3parser" }
|
||||
itertools = "0.13"
|
||||
pyo3 = { version = "0.21", features = ["extension-module", "gil-refs"] }
|
||||
parking_lot = "0.12"
|
||||
tempfile = "3.13"
|
||||
nac3core = { path = "../nac3core" }
|
||||
|
||||
[dependencies.inkwell]
|
||||
version = "0.1.0-beta.4"
|
||||
default-features = false
|
||||
features = ["llvm13-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]
|
||||
nac3ld = { path = "../nac3ld" }
|
||||
|
||||
[features]
|
||||
init-llvm-profile = []
|
||||
no-escape-analysis = ["nac3core/no-escape-analysis"]
|
||||
|
@ -1,10 +1,4 @@
|
||||
from min_artiq import *
|
||||
from numpy import int32, int64
|
||||
|
||||
|
||||
@extern
|
||||
def output_int(x: int32):
|
||||
...
|
||||
|
||||
|
||||
@nac3
|
||||
|
@ -7,26 +7,6 @@ class EmbeddingMap:
|
||||
self.function_map = {}
|
||||
self.attributes_writeback = []
|
||||
|
||||
# preallocate exception names
|
||||
self.preallocate_runtime_exception_names(["RuntimeError",
|
||||
"RTIOUnderflow",
|
||||
"RTIOOverflow",
|
||||
"RTIODestinationUnreachable",
|
||||
"DMAError",
|
||||
"I2CError",
|
||||
"CacheError",
|
||||
"SPIError",
|
||||
"0:ZeroDivisionError",
|
||||
"0:IndexError",
|
||||
"0:UnwrapNoneError"])
|
||||
|
||||
def preallocate_runtime_exception_names(self, names):
|
||||
for i, name in enumerate(names):
|
||||
if ":" not in name:
|
||||
name = "0:artiq.coredevice.exceptions." + name
|
||||
exn_id = self.store_str(name)
|
||||
assert exn_id == i
|
||||
|
||||
def store_function(self, key, fun):
|
||||
self.function_map[key] = fun
|
||||
return key
|
||||
|
24
nac3artiq/demo/list_cmp.py
Normal file
24
nac3artiq/demo/list_cmp.py
Normal file
@ -0,0 +1,24 @@
|
||||
from min_artiq import *
|
||||
from numpy import int32
|
||||
|
||||
|
||||
@nac3
|
||||
class EmptyList:
|
||||
core: KernelInvariant[Core]
|
||||
|
||||
def __init__(self):
|
||||
self.core = Core()
|
||||
|
||||
@rpc
|
||||
def get_empty(self) -> list[int32]:
|
||||
return []
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
a: list[int32] = self.get_empty()
|
||||
if a != []:
|
||||
raise ValueError
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
EmptyList().run()
|
@ -10,7 +10,7 @@ from embedding_map import EmbeddingMap
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Kernel", "KernelInvariant", "virtual",
|
||||
"Kernel", "KernelInvariant", "virtual", "ConstGeneric",
|
||||
"Option", "Some", "none", "UnwrapNoneError",
|
||||
"round64", "floor64", "ceil64",
|
||||
"extern", "kernel", "portable", "nac3",
|
||||
@ -67,6 +67,12 @@ def Some(v: T) -> Option[T]:
|
||||
|
||||
none = Option(None)
|
||||
|
||||
class _ConstGenericMarker:
|
||||
pass
|
||||
|
||||
def ConstGeneric(name, constraint):
|
||||
return TypeVar(name, _ConstGenericMarker, constraint)
|
||||
|
||||
def round64(x):
|
||||
return round(x)
|
||||
|
||||
@ -80,7 +86,13 @@ def ceil64(x):
|
||||
import device_db
|
||||
core_arguments = device_db.device_db["core"]["arguments"]
|
||||
|
||||
compiler = nac3artiq.NAC3(core_arguments["target"])
|
||||
artiq_builtins = {
|
||||
"none": none,
|
||||
"virtual": virtual,
|
||||
"_ConstGenericMarker": _ConstGenericMarker,
|
||||
"Option": Option,
|
||||
}
|
||||
compiler = nac3artiq.NAC3(core_arguments["target"], artiq_builtins)
|
||||
allow_registration = True
|
||||
# Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side.
|
||||
registered_functions = set()
|
||||
@ -100,10 +112,15 @@ def extern(function):
|
||||
register_function(function)
|
||||
return function
|
||||
|
||||
def rpc(function):
|
||||
"""Decorates a function declaration defined by the core device runtime."""
|
||||
register_function(function)
|
||||
return function
|
||||
|
||||
def rpc(arg=None, flags={}):
|
||||
"""Decorates a function or method to be executed on the host interpreter."""
|
||||
if arg is None:
|
||||
def inner_decorator(function):
|
||||
return rpc(function, flags)
|
||||
return inner_decorator
|
||||
register_function(arg)
|
||||
return arg
|
||||
|
||||
def kernel(function_or_method):
|
||||
"""Decorates a function or method to be executed on the core device."""
|
||||
@ -189,7 +206,7 @@ class Core:
|
||||
embedding = EmbeddingMap()
|
||||
|
||||
if allow_registration:
|
||||
compiler.analyze(registered_functions, registered_classes)
|
||||
compiler.analyze(registered_functions, registered_classes, set())
|
||||
allow_registration = False
|
||||
|
||||
if hasattr(method, "__self__"):
|
||||
|
26
nac3artiq/demo/str_abi.py
Normal file
26
nac3artiq/demo/str_abi.py
Normal file
@ -0,0 +1,26 @@
|
||||
from min_artiq import *
|
||||
from numpy import ndarray, zeros as np_zeros
|
||||
|
||||
|
||||
@nac3
|
||||
class StrFail:
|
||||
core: KernelInvariant[Core]
|
||||
|
||||
def __init__(self):
|
||||
self.core = Core()
|
||||
|
||||
@kernel
|
||||
def hello(self, arg: str):
|
||||
pass
|
||||
|
||||
@kernel
|
||||
def consume_ndarray(self, arg: ndarray[str, 1]):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
self.hello("world")
|
||||
self.consume_ndarray(np_zeros([10], dtype=str))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
StrFail().run()
|
24
nac3artiq/demo/string_attribute_issue337.py
Normal file
24
nac3artiq/demo/string_attribute_issue337.py
Normal file
@ -0,0 +1,24 @@
|
||||
from min_artiq import *
|
||||
from numpy import int32
|
||||
|
||||
|
||||
@nac3
|
||||
class Demo:
|
||||
core: KernelInvariant[Core]
|
||||
attr1: KernelInvariant[str]
|
||||
attr2: KernelInvariant[int32]
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.core = Core()
|
||||
self.attr2 = 32
|
||||
self.attr1 = "SAMPLE"
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
print_int32(self.attr2)
|
||||
self.attr1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
Demo().run()
|
40
nac3artiq/demo/support_class_attr_issue102.py
Normal file
40
nac3artiq/demo/support_class_attr_issue102.py
Normal file
@ -0,0 +1,40 @@
|
||||
from min_artiq import *
|
||||
from numpy import int32
|
||||
|
||||
|
||||
@nac3
|
||||
class Demo:
|
||||
attr1: KernelInvariant[int32] = 2
|
||||
attr2: int32 = 4
|
||||
attr3: Kernel[int32]
|
||||
|
||||
@kernel
|
||||
def __init__(self):
|
||||
self.attr3 = 8
|
||||
|
||||
|
||||
@nac3
|
||||
class NAC3Devices:
|
||||
core: KernelInvariant[Core]
|
||||
attr4: KernelInvariant[int32] = 16
|
||||
|
||||
def __init__(self):
|
||||
self.core = Core()
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
Demo.attr1 # Supported
|
||||
# Demo.attr2 # Field not accessible on Kernel
|
||||
# Demo.attr3 # Only attributes can be accessed in this way
|
||||
# Demo.attr1 = 2 # Attributes are immutable
|
||||
|
||||
self.attr4 # Attributes can be accessed within class
|
||||
|
||||
obj = Demo()
|
||||
obj.attr1 # Attributes can be accessed by class objects
|
||||
|
||||
NAC3Devices.attr4 # Attributes accessible for classes without __init__
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
NAC3Devices().run()
|
File diff suppressed because it is too large
Load Diff
1252
nac3artiq/src/lib.rs
1252
nac3artiq/src/lib.rs
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,23 @@
|
||||
use inkwell::{values::BasicValueEnum, AddressSpace, AtomicOrdering};
|
||||
use nac3core::codegen::CodeGenContext;
|
||||
use itertools::Either;
|
||||
|
||||
use nac3core::{
|
||||
codegen::CodeGenContext,
|
||||
inkwell::{
|
||||
values::{BasicValueEnum, CallSiteValue},
|
||||
AddressSpace, AtomicOrdering,
|
||||
},
|
||||
};
|
||||
|
||||
/// Functions for manipulating the timeline.
|
||||
pub trait TimeFns {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx>;
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>);
|
||||
fn emit_delay_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, dt: BasicValueEnum<'ctx>);
|
||||
/// Emits LLVM IR for `now_mu`.
|
||||
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx>;
|
||||
|
||||
/// Emits LLVM IR for `at_mu`.
|
||||
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>);
|
||||
|
||||
/// Emits LLVM IR for `delay_mu`.
|
||||
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>);
|
||||
}
|
||||
|
||||
pub struct NowPinningTimeFns64 {}
|
||||
@ -12,141 +25,143 @@ pub struct NowPinningTimeFns64 {}
|
||||
// For FPGA design reasons, on VexRiscv with 64-bit data bus, the "now" CSR is split into two 32-bit
|
||||
// values that are each padded to 64-bits.
|
||||
impl TimeFns for NowPinningTimeFns64 {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
|
||||
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> {
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr =
|
||||
ctx.builder.build_bitcast(now, i32_type.ptr_type(AddressSpace::Generic), "now_hiptr");
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_gep")
|
||||
};
|
||||
if let (BasicValueEnum::IntValue(now_hi), BasicValueEnum::IntValue(now_lo)) = (
|
||||
ctx.builder.build_load(now_hiptr, "now_hi"),
|
||||
ctx.builder.build_load(now_loptr, "now_lo"),
|
||||
) {
|
||||
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "now_zext_hi");
|
||||
let shifted_hi = ctx.builder.build_left_shift(
|
||||
zext_hi,
|
||||
i64_type.const_int(32, false),
|
||||
"now_shifted_zext_hi",
|
||||
);
|
||||
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "now_zext_lo");
|
||||
ctx.builder.build_or(shifted_hi, zext_lo, "now_or").into()
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
let now_hiptr = ctx
|
||||
.builder
|
||||
.build_bit_cast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap();
|
||||
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now.lo.addr")
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
let now_hi = ctx
|
||||
.builder
|
||||
.build_load(now_hiptr, "now.hi")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
let now_lo = ctx
|
||||
.builder
|
||||
.build_load(now_loptr, "now.lo")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
|
||||
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "").unwrap();
|
||||
let shifted_hi =
|
||||
ctx.builder.build_left_shift(zext_hi, i64_type.const_int(32, false), "").unwrap();
|
||||
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "").unwrap();
|
||||
ctx.builder.build_or(shifted_hi, zext_lo, "now_mu").map(Into::into).unwrap()
|
||||
}
|
||||
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
|
||||
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) {
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
if let BasicValueEnum::IntValue(time) = t {
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
|
||||
let time = t.into_int_value();
|
||||
|
||||
let time_hi = ctx
|
||||
.builder
|
||||
.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "time.hi").unwrap(),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr = ctx.builder.build_bitcast(
|
||||
now,
|
||||
i32_type.ptr_type(AddressSpace::Generic),
|
||||
"now_bitcast",
|
||||
);
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_gep")
|
||||
};
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
"",
|
||||
)
|
||||
.unwrap();
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "time.lo").unwrap();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr = ctx
|
||||
.builder
|
||||
.build_bit_cast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap();
|
||||
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now.lo.addr")
|
||||
}
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn emit_delay_mu<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
dt: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) {
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr =
|
||||
ctx.builder.build_bitcast(now, i32_type.ptr_type(AddressSpace::Generic), "now_hiptr");
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now_loptr")
|
||||
};
|
||||
if let (
|
||||
BasicValueEnum::IntValue(now_hi),
|
||||
BasicValueEnum::IntValue(now_lo),
|
||||
BasicValueEnum::IntValue(dt),
|
||||
) = (
|
||||
ctx.builder.build_load(now_hiptr, "now_hi"),
|
||||
ctx.builder.build_load(now_loptr, "now_lo"),
|
||||
dt,
|
||||
) {
|
||||
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "now_zext_hi");
|
||||
let shifted_hi = ctx.builder.build_left_shift(
|
||||
zext_hi,
|
||||
i64_type.const_int(32, false),
|
||||
"now_shifted_zext_hi",
|
||||
);
|
||||
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "now_zext_lo");
|
||||
let now_val = ctx.builder.build_or(shifted_hi, zext_lo, "now_or");
|
||||
let now_hiptr = ctx
|
||||
.builder
|
||||
.build_bit_cast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap();
|
||||
|
||||
let time = ctx.builder.build_int_add(now_val, dt, "now_add");
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(
|
||||
time,
|
||||
i64_type.const_int(32, false),
|
||||
false,
|
||||
"now_lshr",
|
||||
),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(2, false)], "now.lo.addr")
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
let now_hi = ctx
|
||||
.builder
|
||||
.build_load(now_hiptr, "now.hi")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
let now_lo = ctx
|
||||
.builder
|
||||
.build_load(now_loptr, "now.lo")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
let dt = dt.into_int_value();
|
||||
|
||||
let zext_hi = ctx.builder.build_int_z_extend(now_hi, i64_type, "").unwrap();
|
||||
let shifted_hi =
|
||||
ctx.builder.build_left_shift(zext_hi, i64_type.const_int(32, false), "").unwrap();
|
||||
let zext_lo = ctx.builder.build_int_z_extend(now_lo, i64_type, "").unwrap();
|
||||
let now_val = ctx.builder.build_or(shifted_hi, zext_lo, "now").unwrap();
|
||||
|
||||
let time = ctx.builder.build_int_add(now_val, dt, "time").unwrap();
|
||||
let time_hi = ctx
|
||||
.builder
|
||||
.build_int_truncate(
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
};
|
||||
.build_right_shift(time, i64_type.const_int(32, false), false, "")
|
||||
.unwrap(),
|
||||
i32_type,
|
||||
"time.hi",
|
||||
)
|
||||
.unwrap();
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "time.lo").unwrap();
|
||||
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -155,68 +170,67 @@ pub static NOW_PINNING_TIME_FNS_64: NowPinningTimeFns64 = NowPinningTimeFns64 {}
|
||||
pub struct NowPinningTimeFns {}
|
||||
|
||||
impl TimeFns for NowPinningTimeFns {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
|
||||
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> {
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_raw = ctx.builder.build_load(now.as_pointer_value(), "now");
|
||||
if let BasicValueEnum::IntValue(now_raw) = now_raw {
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now_shl");
|
||||
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now_lshr");
|
||||
ctx.builder.build_or(now_lo, now_hi, "now_or").into()
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
let now_raw = ctx
|
||||
.builder
|
||||
.build_load(now.as_pointer_value(), "now")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now.lo").unwrap();
|
||||
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now.hi").unwrap();
|
||||
ctx.builder.build_or(now_lo, now_hi, "now_mu").map(Into::into).unwrap()
|
||||
}
|
||||
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
|
||||
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) {
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
if let BasicValueEnum::IntValue(time) = t {
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
|
||||
|
||||
let time = t.into_int_value();
|
||||
|
||||
let time_hi = ctx
|
||||
.builder
|
||||
.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "").unwrap(),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr = ctx.builder.build_bitcast(
|
||||
now,
|
||||
i32_type.ptr_type(AddressSpace::Generic),
|
||||
"now_bitcast",
|
||||
);
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now_gep")
|
||||
};
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
"time.hi",
|
||||
)
|
||||
.unwrap();
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc").unwrap();
|
||||
let now = ctx
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_hiptr = ctx
|
||||
.builder
|
||||
.build_bit_cast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap();
|
||||
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now.lo.addr")
|
||||
}
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn emit_delay_mu<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
dt: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) {
|
||||
let i32_type = ctx.ctx.i32_type();
|
||||
let i64_type = ctx.ctx.i64_type();
|
||||
let i64_32 = i64_type.const_int(32, false);
|
||||
@ -224,41 +238,47 @@ impl TimeFns for NowPinningTimeFns {
|
||||
.module
|
||||
.get_global("now")
|
||||
.unwrap_or_else(|| ctx.module.add_global(i64_type, None, "now"));
|
||||
let now_raw = ctx.builder.build_load(now.as_pointer_value(), "now");
|
||||
if let (BasicValueEnum::IntValue(now_raw), BasicValueEnum::IntValue(dt)) = (now_raw, dt) {
|
||||
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now_shl");
|
||||
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now_lshr");
|
||||
let now_val = ctx.builder.build_or(now_lo, now_hi, "now_or");
|
||||
let time = ctx.builder.build_int_add(now_val, dt, "now_add");
|
||||
let time_hi = ctx.builder.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "now_lshr"),
|
||||
let now_raw = ctx
|
||||
.builder
|
||||
.build_load(now.as_pointer_value(), "")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
|
||||
let dt = dt.into_int_value();
|
||||
|
||||
let now_lo = ctx.builder.build_left_shift(now_raw, i64_32, "now.lo").unwrap();
|
||||
let now_hi = ctx.builder.build_right_shift(now_raw, i64_32, false, "now.hi").unwrap();
|
||||
let now_val = ctx.builder.build_or(now_lo, now_hi, "now_val").unwrap();
|
||||
let time = ctx.builder.build_int_add(now_val, dt, "time").unwrap();
|
||||
let time_hi = ctx
|
||||
.builder
|
||||
.build_int_truncate(
|
||||
ctx.builder.build_right_shift(time, i64_32, false, "time.hi").unwrap(),
|
||||
i32_type,
|
||||
"now_trunc",
|
||||
);
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "now_trunc");
|
||||
let now_hiptr = ctx.builder.build_bitcast(
|
||||
now,
|
||||
i32_type.ptr_type(AddressSpace::Generic),
|
||||
"now_bitcast",
|
||||
);
|
||||
if let BasicValueEnum::PointerValue(now_hiptr) = now_hiptr {
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now_gep")
|
||||
};
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
)
|
||||
.unwrap();
|
||||
let time_lo = ctx.builder.build_int_truncate(time, i32_type, "time.lo").unwrap();
|
||||
let now_hiptr = ctx
|
||||
.builder
|
||||
.build_bit_cast(now, i32_type.ptr_type(AddressSpace::default()), "now.hi.addr")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap();
|
||||
|
||||
let now_loptr = unsafe {
|
||||
ctx.builder.build_gep(now_hiptr, &[i32_type.const_int(1, false)], "now.lo.addr")
|
||||
}
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_hiptr, time_hi)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
ctx.builder
|
||||
.build_store(now_loptr, time_lo)
|
||||
.unwrap()
|
||||
.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -267,14 +287,18 @@ pub static NOW_PINNING_TIME_FNS: NowPinningTimeFns = NowPinningTimeFns {};
|
||||
pub struct ExternTimeFns {}
|
||||
|
||||
impl TimeFns for ExternTimeFns {
|
||||
fn emit_now_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>) -> BasicValueEnum<'ctx> {
|
||||
fn emit_now_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> {
|
||||
let now_mu = ctx.module.get_function("now_mu").unwrap_or_else(|| {
|
||||
ctx.module.add_function("now_mu", ctx.ctx.i64_type().fn_type(&[], false), None)
|
||||
});
|
||||
ctx.builder.build_call(now_mu, &[], "now_mu").try_as_basic_value().left().unwrap()
|
||||
ctx.builder
|
||||
.build_call(now_mu, &[], "now_mu")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn emit_at_mu<'ctx, 'a>(&self, ctx: &mut CodeGenContext<'ctx, 'a>, t: BasicValueEnum<'ctx>) {
|
||||
fn emit_at_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, t: BasicValueEnum<'ctx>) {
|
||||
let at_mu = ctx.module.get_function("at_mu").unwrap_or_else(|| {
|
||||
ctx.module.add_function(
|
||||
"at_mu",
|
||||
@ -282,14 +306,10 @@ impl TimeFns for ExternTimeFns {
|
||||
None,
|
||||
)
|
||||
});
|
||||
ctx.builder.build_call(at_mu, &[t.into()], "at_mu");
|
||||
ctx.builder.build_call(at_mu, &[t.into()], "at_mu").unwrap();
|
||||
}
|
||||
|
||||
fn emit_delay_mu<'ctx, 'a>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
dt: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
fn emit_delay_mu<'ctx>(&self, ctx: &mut CodeGenContext<'ctx, '_>, dt: BasicValueEnum<'ctx>) {
|
||||
let delay_mu = ctx.module.get_function("delay_mu").unwrap_or_else(|| {
|
||||
ctx.module.add_function(
|
||||
"delay_mu",
|
||||
@ -297,7 +317,7 @@ impl TimeFns for ExternTimeFns {
|
||||
None,
|
||||
)
|
||||
});
|
||||
ctx.builder.build_call(delay_mu, &[dt.into()], "delay_mu");
|
||||
ctx.builder.build_call(delay_mu, &[dt.into()], "delay_mu").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "nac3ast"
|
||||
version = "0.1.0"
|
||||
authors = ["RustPython Team", "M-Labs"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["constant-optimization", "fold"]
|
||||
@ -10,7 +10,6 @@ constant-optimization = ["fold"]
|
||||
fold = []
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.4.0"
|
||||
parking_lot = "0.11.1"
|
||||
string-interner = "0.13.0"
|
||||
fxhash = "0.2.1"
|
||||
parking_lot = "0.12"
|
||||
string-interner = "0.17"
|
||||
fxhash = "0.2"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -28,12 +28,12 @@ impl From<bool> for Constant {
|
||||
}
|
||||
impl From<i32> for Constant {
|
||||
fn from(i: i32) -> Constant {
|
||||
Self::Int(i as i128)
|
||||
Self::Int(i128::from(i))
|
||||
}
|
||||
}
|
||||
impl From<i64> for Constant {
|
||||
fn from(i: i64) -> Constant {
|
||||
Self::Int(i as i128)
|
||||
Self::Int(i128::from(i))
|
||||
}
|
||||
}
|
||||
|
||||
@ -50,6 +50,7 @@ pub enum ConversionFlag {
|
||||
}
|
||||
|
||||
impl ConversionFlag {
|
||||
#[must_use]
|
||||
pub fn try_from_byte(b: u8) -> Option<Self> {
|
||||
match b {
|
||||
b's' => Some(Self::Str),
|
||||
@ -69,6 +70,7 @@ pub struct ConstantOptimizer {
|
||||
#[cfg(feature = "constant-optimization")]
|
||||
impl ConstantOptimizer {
|
||||
#[inline]
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self { _priv: () }
|
||||
}
|
||||
@ -85,33 +87,22 @@ impl<U> crate::fold::Fold<U> for ConstantOptimizer {
|
||||
fn fold_expr(&mut self, node: crate::Expr<U>) -> Result<crate::Expr<U>, Self::Error> {
|
||||
match node.node {
|
||||
crate::ExprKind::Tuple { elts, ctx } => {
|
||||
let elts = elts
|
||||
.into_iter()
|
||||
.map(|x| self.fold_expr(x))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let expr = if elts
|
||||
.iter()
|
||||
.all(|e| matches!(e.node, crate::ExprKind::Constant { .. }))
|
||||
{
|
||||
let tuple = elts
|
||||
.into_iter()
|
||||
.map(|e| match e.node {
|
||||
crate::ExprKind::Constant { value, .. } => value,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
crate::ExprKind::Constant {
|
||||
value: Constant::Tuple(tuple),
|
||||
kind: None,
|
||||
}
|
||||
} else {
|
||||
crate::ExprKind::Tuple { elts, ctx }
|
||||
};
|
||||
Ok(crate::Expr {
|
||||
node: expr,
|
||||
custom: node.custom,
|
||||
location: node.location,
|
||||
})
|
||||
let elts =
|
||||
elts.into_iter().map(|x| self.fold_expr(x)).collect::<Result<Vec<_>, _>>()?;
|
||||
let expr =
|
||||
if elts.iter().all(|e| matches!(e.node, crate::ExprKind::Constant { .. })) {
|
||||
let tuple = elts
|
||||
.into_iter()
|
||||
.map(|e| match e.node {
|
||||
crate::ExprKind::Constant { value, .. } => value,
|
||||
_ => unreachable!(),
|
||||
})
|
||||
.collect();
|
||||
crate::ExprKind::Constant { value: Constant::Tuple(tuple), kind: None }
|
||||
} else {
|
||||
crate::ExprKind::Tuple { elts, ctx }
|
||||
};
|
||||
Ok(crate::Expr { node: expr, custom: node.custom, location: node.location })
|
||||
}
|
||||
_ => crate::fold::fold_expr(self, node),
|
||||
}
|
||||
@ -127,7 +118,7 @@ mod tests {
|
||||
use crate::fold::Fold;
|
||||
use crate::*;
|
||||
|
||||
let location = Location::new(0, 0, Default::default());
|
||||
let location = Location::new(0, 0, FileName::default());
|
||||
let custom = ();
|
||||
let ast = Located {
|
||||
location,
|
||||
@ -138,18 +129,12 @@ mod tests {
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 1.into(),
|
||||
kind: None,
|
||||
},
|
||||
node: ExprKind::Constant { value: 1.into(), kind: None },
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 2.into(),
|
||||
kind: None,
|
||||
},
|
||||
node: ExprKind::Constant { value: 2.into(), kind: None },
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
@ -160,26 +145,17 @@ mod tests {
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 3.into(),
|
||||
kind: None,
|
||||
},
|
||||
node: ExprKind::Constant { value: 3.into(), kind: None },
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 4.into(),
|
||||
kind: None,
|
||||
},
|
||||
node: ExprKind::Constant { value: 4.into(), kind: None },
|
||||
},
|
||||
Located {
|
||||
location,
|
||||
custom,
|
||||
node: ExprKind::Constant {
|
||||
value: 5.into(),
|
||||
kind: None,
|
||||
},
|
||||
node: ExprKind::Constant { value: 5.into(), kind: None },
|
||||
},
|
||||
],
|
||||
},
|
||||
@ -187,9 +163,7 @@ mod tests {
|
||||
],
|
||||
},
|
||||
};
|
||||
let new_ast = ConstantOptimizer::new()
|
||||
.fold_expr(ast)
|
||||
.unwrap_or_else(|e| match e {});
|
||||
let new_ast = ConstantOptimizer::new().fold_expr(ast).unwrap_or_else(|e| match e {});
|
||||
assert_eq!(
|
||||
new_ast,
|
||||
Located {
|
||||
@ -199,11 +173,7 @@ mod tests {
|
||||
value: Constant::Tuple(vec![
|
||||
1.into(),
|
||||
2.into(),
|
||||
Constant::Tuple(vec![
|
||||
3.into(),
|
||||
4.into(),
|
||||
5.into(),
|
||||
])
|
||||
Constant::Tuple(vec![3.into(), 4.into(), 5.into(),])
|
||||
]),
|
||||
kind: None
|
||||
},
|
||||
|
@ -64,11 +64,4 @@ macro_rules! simple_fold {
|
||||
};
|
||||
}
|
||||
|
||||
simple_fold!(
|
||||
usize,
|
||||
String,
|
||||
bool,
|
||||
StrRef,
|
||||
constant::Constant,
|
||||
constant::ConversionFlag
|
||||
);
|
||||
simple_fold!(usize, String, bool, StrRef, constant::Constant, constant::ConversionFlag);
|
||||
|
@ -2,6 +2,7 @@ use crate::{Constant, ExprKind};
|
||||
|
||||
impl<U> ExprKind<U> {
|
||||
/// Returns a short name for the node suitable for use in error messages.
|
||||
#[must_use]
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
ExprKind::BoolOp { .. } | ExprKind::BinOp { .. } | ExprKind::UnaryOp { .. } => {
|
||||
@ -34,10 +35,7 @@ impl<U> ExprKind<U> {
|
||||
ExprKind::Starred { .. } => "starred",
|
||||
ExprKind::Slice { .. } => "slice",
|
||||
ExprKind::JoinedStr { values } => {
|
||||
if values
|
||||
.iter()
|
||||
.any(|e| matches!(e.node, ExprKind::JoinedStr { .. }))
|
||||
{
|
||||
if values.iter().any(|e| matches!(e.node, ExprKind::JoinedStr { .. })) {
|
||||
"f-string expression"
|
||||
} else {
|
||||
"literal"
|
||||
|
@ -1,5 +1,12 @@
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::too_many_lines,
|
||||
clippy::wildcard_imports
|
||||
)]
|
||||
|
||||
mod ast_gen;
|
||||
mod constant;
|
||||
@ -9,6 +16,6 @@ mod impls;
|
||||
mod location;
|
||||
|
||||
pub use ast_gen::*;
|
||||
pub use location::{Location, FileName};
|
||||
pub use location::{FileName, Location};
|
||||
|
||||
pub type Suite<U = ()> = Vec<Stmt<U>>;
|
||||
|
@ -1,8 +1,9 @@
|
||||
//! Datatypes to support source location information.
|
||||
use crate::ast_gen::StrRef;
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct FileName(pub StrRef);
|
||||
impl Default for FileName {
|
||||
fn default() -> Self {
|
||||
@ -17,16 +18,38 @@ impl From<String> for FileName {
|
||||
}
|
||||
|
||||
/// A location somewhere in the sourcecode.
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq)]
|
||||
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
|
||||
pub struct Location {
|
||||
pub row: usize,
|
||||
pub column: usize,
|
||||
pub file: FileName
|
||||
pub file: FileName,
|
||||
}
|
||||
|
||||
impl fmt::Display for Location {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}: line {} column {}", self.file.0, self.row, self.column)
|
||||
write!(f, "{}:{}:{}", self.file.0, self.row, self.column)
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Location {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
let file_cmp = self.file.0.to_string().cmp(&other.file.0.to_string());
|
||||
if file_cmp != Ordering::Equal {
|
||||
return file_cmp;
|
||||
}
|
||||
|
||||
let row_cmp = self.row.cmp(&other.row);
|
||||
if row_cmp != Ordering::Equal {
|
||||
return row_cmp;
|
||||
}
|
||||
|
||||
self.column.cmp(&other.column)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for Location {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,23 +76,22 @@ impl Location {
|
||||
)
|
||||
}
|
||||
}
|
||||
Visualize {
|
||||
loc: *self,
|
||||
line,
|
||||
desc,
|
||||
}
|
||||
Visualize { loc: *self, line, desc }
|
||||
}
|
||||
}
|
||||
|
||||
impl Location {
|
||||
#[must_use]
|
||||
pub fn new(row: usize, column: usize, file: FileName) -> Self {
|
||||
Location { row, column, file }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn row(&self) -> usize {
|
||||
self.row
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn column(&self) -> usize {
|
||||
self.column
|
||||
}
|
||||
|
@ -2,24 +2,33 @@
|
||||
name = "nac3core"
|
||||
version = "0.1.0"
|
||||
authors = ["M-Labs"]
|
||||
edition = "2018"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["derive"]
|
||||
derive = ["dep:nac3core_derive"]
|
||||
no-escape-analysis = []
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.10.1"
|
||||
crossbeam = "0.8.1"
|
||||
parking_lot = "0.11.1"
|
||||
rayon = "1.5.1"
|
||||
itertools = "0.13"
|
||||
crossbeam = "0.8"
|
||||
indexmap = "2.6"
|
||||
parking_lot = "0.12"
|
||||
rayon = "1.10"
|
||||
nac3core_derive = { path = "nac3core_derive", optional = true }
|
||||
nac3parser = { path = "../nac3parser" }
|
||||
strum = "0.26"
|
||||
strum_macros = "0.26"
|
||||
|
||||
[dependencies.inkwell]
|
||||
version = "0.1.0-beta.4"
|
||||
version = "0.5"
|
||||
default-features = false
|
||||
features = ["llvm13-0", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]
|
||||
features = ["llvm14-0-prefer-dynamic", "target-x86", "target-arm", "target-riscv", "no-libffi-linking"]
|
||||
|
||||
[dev-dependencies]
|
||||
test-case = "1.2.0"
|
||||
indoc = "1.0"
|
||||
indoc = "2.0"
|
||||
insta = "=1.11.0"
|
||||
|
||||
[build-dependencies]
|
||||
regex = "1"
|
||||
regex = "1.10"
|
||||
|
@ -1,4 +1,3 @@
|
||||
use regex::Regex;
|
||||
use std::{
|
||||
env,
|
||||
fs::File,
|
||||
@ -7,34 +6,58 @@ use std::{
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
fn main() {
|
||||
const FILE: &str = "src/codegen/irrt/irrt.c";
|
||||
println!("cargo:rerun-if-changed={}", FILE);
|
||||
let out_dir = env::var("OUT_DIR").unwrap();
|
||||
let out_path = Path::new(&out_dir);
|
||||
let out_dir = Path::new(&out_dir);
|
||||
let irrt_dir = Path::new("irrt");
|
||||
|
||||
let irrt_cpp_path = irrt_dir.join("irrt.cpp");
|
||||
|
||||
/*
|
||||
* HACK: Sadly, clang doesn't let us emit generic LLVM bitcode.
|
||||
* Compiling for WASM32 and filtering the output with regex is the closest we can get.
|
||||
*/
|
||||
|
||||
const FLAG: &[&str] = &[
|
||||
let mut flags: Vec<&str> = vec![
|
||||
"--target=wasm32",
|
||||
FILE,
|
||||
"-O3",
|
||||
"-x",
|
||||
"c++",
|
||||
"-std=c++20",
|
||||
"-fno-discard-value-names",
|
||||
"-fno-exceptions",
|
||||
"-fno-rtti",
|
||||
"-emit-llvm",
|
||||
"-S",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
"-o",
|
||||
"-",
|
||||
"-I",
|
||||
irrt_dir.to_str().unwrap(),
|
||||
irrt_cpp_path.to_str().unwrap(),
|
||||
];
|
||||
let output = Command::new("clang")
|
||||
.args(FLAG)
|
||||
|
||||
match env::var("PROFILE").as_deref() {
|
||||
Ok("debug") => {
|
||||
flags.push("-O0");
|
||||
flags.push("-DIRRT_DEBUG_ASSERT");
|
||||
}
|
||||
Ok("release") => {
|
||||
flags.push("-O3");
|
||||
}
|
||||
flavor => panic!("Unknown or missing build flavor {flavor:?}"),
|
||||
}
|
||||
|
||||
// Tell Cargo to rerun if any file under `irrt_dir` (recursive) changes
|
||||
println!("cargo:rerun-if-changed={}", irrt_dir.to_str().unwrap());
|
||||
|
||||
// Compile IRRT and capture the LLVM IR output
|
||||
let output = Command::new("clang-irrt")
|
||||
.args(flags)
|
||||
.output()
|
||||
.map(|o| {
|
||||
.inspect(|o| {
|
||||
assert!(o.status.success(), "{}", std::str::from_utf8(&o.stderr).unwrap());
|
||||
o
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@ -42,9 +65,19 @@ fn main() {
|
||||
let output = std::str::from_utf8(&output.stdout).unwrap().replace("\r\n", "\n");
|
||||
let mut filtered_output = String::with_capacity(output.len());
|
||||
|
||||
let regex_filter = regex::Regex::new(r"(?ms:^define.*?\}$)|(?m:^declare.*?$)").unwrap();
|
||||
// Filter out irrelevant IR
|
||||
//
|
||||
// Regex:
|
||||
// - `(?ms:^define.*?\}$)` captures LLVM `define` blocks
|
||||
// - `(?m:^declare.*?$)` captures LLVM `declare` lines
|
||||
// - `(?m:^%.+?=\s*type\s*\{.+?\}$)` captures LLVM `type` declarations
|
||||
// - `(?m:^@.+?=.+$)` captures global constants
|
||||
let regex_filter = Regex::new(
|
||||
r"(?ms:^define.*?\}$)|(?m:^declare.*?$)|(?m:^%.+?=\s*type\s*\{.+?\}$)|(?m:^@.+?=.+$)",
|
||||
)
|
||||
.unwrap();
|
||||
for f in regex_filter.captures_iter(&output) {
|
||||
assert!(f.len() == 1);
|
||||
assert_eq!(f.len(), 1);
|
||||
filtered_output.push_str(&f[0]);
|
||||
filtered_output.push('\n');
|
||||
}
|
||||
@ -53,20 +86,24 @@ fn main() {
|
||||
.unwrap()
|
||||
.replace_all(&filtered_output, "");
|
||||
|
||||
println!("cargo:rerun-if-env-changed=DEBUG_DUMP_IRRT");
|
||||
if env::var("DEBUG_DUMP_IRRT").is_ok() {
|
||||
let mut file = File::create(out_path.join("irrt.ll")).unwrap();
|
||||
// For debugging
|
||||
// Doing `DEBUG_DUMP_IRRT=1 cargo build -p nac3core` dumps the LLVM IR generated
|
||||
const DEBUG_DUMP_IRRT: &str = "DEBUG_DUMP_IRRT";
|
||||
println!("cargo:rerun-if-env-changed={DEBUG_DUMP_IRRT}");
|
||||
if env::var(DEBUG_DUMP_IRRT).is_ok() {
|
||||
let mut file = File::create(out_dir.join("irrt.ll")).unwrap();
|
||||
file.write_all(output.as_bytes()).unwrap();
|
||||
let mut file = File::create(out_path.join("irrt-filtered.ll")).unwrap();
|
||||
|
||||
let mut file = File::create(out_dir.join("irrt-filtered.ll")).unwrap();
|
||||
file.write_all(filtered_output.as_bytes()).unwrap();
|
||||
}
|
||||
|
||||
let mut llvm_as = Command::new("llvm-as")
|
||||
let mut llvm_as = Command::new("llvm-as-irrt")
|
||||
.stdin(Stdio::piped())
|
||||
.arg("-o")
|
||||
.arg(out_path.join("irrt.bc"))
|
||||
.arg(out_dir.join("irrt.bc"))
|
||||
.spawn()
|
||||
.unwrap();
|
||||
llvm_as.stdin.as_mut().unwrap().write_all(filtered_output.as_bytes()).unwrap();
|
||||
assert!(llvm_as.wait().unwrap().success())
|
||||
assert!(llvm_as.wait().unwrap().success());
|
||||
}
|
||||
|
10
nac3core/irrt/irrt.cpp
Normal file
10
nac3core/irrt/irrt.cpp
Normal file
@ -0,0 +1,10 @@
|
||||
#include "irrt/exception.hpp"
|
||||
#include "irrt/list.hpp"
|
||||
#include "irrt/math.hpp"
|
||||
#include "irrt/ndarray.hpp"
|
||||
#include "irrt/range.hpp"
|
||||
#include "irrt/slice.hpp"
|
||||
#include "irrt/ndarray/basic.hpp"
|
||||
#include "irrt/ndarray/def.hpp"
|
||||
#include "irrt/ndarray/iter.hpp"
|
||||
#include "irrt/ndarray/indexing.hpp"
|
9
nac3core/irrt/irrt/cslice.hpp
Normal file
9
nac3core/irrt/irrt/cslice.hpp
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/int_types.hpp"
|
||||
|
||||
template<typename SizeT>
|
||||
struct CSlice {
|
||||
void* base;
|
||||
SizeT len;
|
||||
};
|
25
nac3core/irrt/irrt/debug.hpp
Normal file
25
nac3core/irrt/irrt/debug.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
// Set in nac3core/build.rs
|
||||
#ifdef IRRT_DEBUG_ASSERT
|
||||
#define IRRT_DEBUG_ASSERT_BOOL true
|
||||
#else
|
||||
#define IRRT_DEBUG_ASSERT_BOOL false
|
||||
#endif
|
||||
|
||||
#define raise_debug_assert(SizeT, msg, param1, param2, param3) \
|
||||
raise_exception(SizeT, EXN_ASSERTION_ERROR, "IRRT debug assert failed: " msg, param1, param2, param3)
|
||||
|
||||
#define debug_assert_eq(SizeT, lhs, rhs) \
|
||||
if constexpr (IRRT_DEBUG_ASSERT_BOOL) { \
|
||||
if ((lhs) != (rhs)) { \
|
||||
raise_debug_assert(SizeT, "LHS = {0}. RHS = {1}", lhs, rhs, NO_PARAM); \
|
||||
} \
|
||||
}
|
||||
|
||||
#define debug_assert(SizeT, expr) \
|
||||
if constexpr (IRRT_DEBUG_ASSERT_BOOL) { \
|
||||
if (!(expr)) { \
|
||||
raise_debug_assert(SizeT, "Got false.", NO_PARAM, NO_PARAM, NO_PARAM); \
|
||||
} \
|
||||
}
|
85
nac3core/irrt/irrt/exception.hpp
Normal file
85
nac3core/irrt/irrt/exception.hpp
Normal file
@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/cslice.hpp"
|
||||
#include "irrt/int_types.hpp"
|
||||
|
||||
/**
|
||||
* @brief The int type of ARTIQ exception IDs.
|
||||
*/
|
||||
using ExceptionId = int32_t;
|
||||
|
||||
/*
|
||||
* Set of exceptions C++ IRRT can use.
|
||||
* Must be synchronized with `setup_irrt_exceptions` in `nac3core/src/codegen/irrt/mod.rs`.
|
||||
*/
|
||||
extern "C" {
|
||||
ExceptionId EXN_INDEX_ERROR;
|
||||
ExceptionId EXN_VALUE_ERROR;
|
||||
ExceptionId EXN_ASSERTION_ERROR;
|
||||
ExceptionId EXN_TYPE_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Extern function to `__nac3_raise`
|
||||
*
|
||||
* The parameter `err` could be `Exception<int32_t>` or `Exception<int64_t>`. The caller
|
||||
* must make sure to pass `Exception`s with the correct `SizeT` depending on the `size_t` of the runtime.
|
||||
*/
|
||||
extern "C" void __nac3_raise(void* err);
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* @brief NAC3's Exception struct
|
||||
*/
|
||||
template<typename SizeT>
|
||||
struct Exception {
|
||||
ExceptionId id;
|
||||
CSlice<SizeT> filename;
|
||||
int32_t line;
|
||||
int32_t column;
|
||||
CSlice<SizeT> function;
|
||||
CSlice<SizeT> msg;
|
||||
int64_t params[3];
|
||||
};
|
||||
|
||||
constexpr int64_t NO_PARAM = 0;
|
||||
|
||||
template<typename SizeT>
|
||||
void _raise_exception_helper(ExceptionId id,
|
||||
const char* filename,
|
||||
int32_t line,
|
||||
const char* function,
|
||||
const char* msg,
|
||||
int64_t param0,
|
||||
int64_t param1,
|
||||
int64_t param2) {
|
||||
Exception<SizeT> e = {
|
||||
.id = id,
|
||||
.filename = {.base = reinterpret_cast<void*>(const_cast<char*>(filename)),
|
||||
.len = static_cast<SizeT>(__builtin_strlen(filename))},
|
||||
.line = line,
|
||||
.column = 0,
|
||||
.function = {.base = reinterpret_cast<void*>(const_cast<char*>(function)),
|
||||
.len = static_cast<SizeT>(__builtin_strlen(function))},
|
||||
.msg = {.base = reinterpret_cast<void*>(const_cast<char*>(msg)),
|
||||
.len = static_cast<SizeT>(__builtin_strlen(msg))},
|
||||
};
|
||||
e.params[0] = param0;
|
||||
e.params[1] = param1;
|
||||
e.params[2] = param2;
|
||||
__nac3_raise(reinterpret_cast<void*>(&e));
|
||||
__builtin_unreachable();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief Raise an exception with location details (location in the IRRT source files).
|
||||
* @param SizeT The runtime `size_t` type.
|
||||
* @param id The ID of the exception to raise.
|
||||
* @param msg A global constant C-string of the error message.
|
||||
*
|
||||
* `param0` to `param2` are optional format arguments of `msg`. They should be set to
|
||||
* `NO_PARAM` to indicate they are unused.
|
||||
*/
|
||||
#define raise_exception(SizeT, id, msg, param0, param1, param2) \
|
||||
_raise_exception_helper<SizeT>(id, __FILE__, __LINE__, __FUNCTION__, msg, param0, param1, param2)
|
27
nac3core/irrt/irrt/int_types.hpp
Normal file
27
nac3core/irrt/irrt/int_types.hpp
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#if __STDC_VERSION__ >= 202000
|
||||
using int8_t = _BitInt(8);
|
||||
using uint8_t = unsigned _BitInt(8);
|
||||
using int32_t = _BitInt(32);
|
||||
using uint32_t = unsigned _BitInt(32);
|
||||
using int64_t = _BitInt(64);
|
||||
using uint64_t = unsigned _BitInt(64);
|
||||
#else
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-type"
|
||||
using int8_t = _ExtInt(8);
|
||||
using uint8_t = unsigned _ExtInt(8);
|
||||
using int32_t = _ExtInt(32);
|
||||
using uint32_t = unsigned _ExtInt(32);
|
||||
using int64_t = _ExtInt(64);
|
||||
using uint64_t = unsigned _ExtInt(64);
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
#endif
|
||||
|
||||
// NDArray indices are always `uint32_t`.
|
||||
using NDIndexInt = uint32_t;
|
||||
// The type of an index or a value describing the length of a range/slice is always `int32_t`.
|
||||
using SliceIndex = int32_t;
|
81
nac3core/irrt/irrt/list.hpp
Normal file
81
nac3core/irrt/irrt/list.hpp
Normal file
@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/int_types.hpp"
|
||||
#include "irrt/math_util.hpp"
|
||||
|
||||
extern "C" {
|
||||
// Handle list assignment and dropping part of the list when
|
||||
// both dest_step and src_step are +1.
|
||||
// - All the index must *not* be out-of-bound or negative,
|
||||
// - The end index is *inclusive*,
|
||||
// - The length of src and dest slice size should already
|
||||
// be checked: if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest)
|
||||
SliceIndex __nac3_list_slice_assign_var_size(SliceIndex dest_start,
|
||||
SliceIndex dest_end,
|
||||
SliceIndex dest_step,
|
||||
void* dest_arr,
|
||||
SliceIndex dest_arr_len,
|
||||
SliceIndex src_start,
|
||||
SliceIndex src_end,
|
||||
SliceIndex src_step,
|
||||
void* src_arr,
|
||||
SliceIndex src_arr_len,
|
||||
const SliceIndex size) {
|
||||
/* if dest_arr_len == 0, do nothing since we do not support extending list */
|
||||
if (dest_arr_len == 0)
|
||||
return dest_arr_len;
|
||||
/* if both step is 1, memmove directly, handle the dropping of the list, and shrink size */
|
||||
if (src_step == dest_step && dest_step == 1) {
|
||||
const SliceIndex src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
|
||||
const SliceIndex dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
|
||||
if (src_len > 0) {
|
||||
__builtin_memmove(static_cast<uint8_t*>(dest_arr) + dest_start * size,
|
||||
static_cast<uint8_t*>(src_arr) + src_start * size, src_len * size);
|
||||
}
|
||||
if (dest_len > 0) {
|
||||
/* dropping */
|
||||
__builtin_memmove(static_cast<uint8_t*>(dest_arr) + (dest_start + src_len) * size,
|
||||
static_cast<uint8_t*>(dest_arr) + (dest_end + 1) * size,
|
||||
(dest_arr_len - dest_end - 1) * size);
|
||||
}
|
||||
/* shrink size */
|
||||
return dest_arr_len - (dest_len - src_len);
|
||||
}
|
||||
/* if two range overlaps, need alloca */
|
||||
uint8_t need_alloca = (dest_arr == src_arr)
|
||||
&& !(max(dest_start, dest_end) < min(src_start, src_end)
|
||||
|| max(src_start, src_end) < min(dest_start, dest_end));
|
||||
if (need_alloca) {
|
||||
void* tmp = __builtin_alloca(src_arr_len * size);
|
||||
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
|
||||
src_arr = tmp;
|
||||
}
|
||||
SliceIndex src_ind = src_start;
|
||||
SliceIndex dest_ind = dest_start;
|
||||
for (; (src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end); src_ind += src_step, dest_ind += dest_step) {
|
||||
/* for constant optimization */
|
||||
if (size == 1) {
|
||||
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind, static_cast<uint8_t*>(src_arr) + src_ind, 1);
|
||||
} else if (size == 4) {
|
||||
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind * 4,
|
||||
static_cast<uint8_t*>(src_arr) + src_ind * 4, 4);
|
||||
} else if (size == 8) {
|
||||
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind * 8,
|
||||
static_cast<uint8_t*>(src_arr) + src_ind * 8, 8);
|
||||
} else {
|
||||
/* memcpy for var size, cannot overlap after previous alloca */
|
||||
__builtin_memcpy(static_cast<uint8_t*>(dest_arr) + dest_ind * size,
|
||||
static_cast<uint8_t*>(src_arr) + src_ind * size, size);
|
||||
}
|
||||
}
|
||||
/* only dest_step == 1 can we shrink the dest list. */
|
||||
/* size should be ensured prior to calling this function */
|
||||
if (dest_step == 1 && dest_end >= dest_start) {
|
||||
__builtin_memmove(static_cast<uint8_t*>(dest_arr) + dest_ind * size,
|
||||
static_cast<uint8_t*>(dest_arr) + (dest_end + 1) * size,
|
||||
(dest_arr_len - dest_end - 1) * size);
|
||||
return dest_arr_len - (dest_end - dest_ind) - 1;
|
||||
}
|
||||
return dest_arr_len;
|
||||
}
|
||||
} // extern "C"
|
93
nac3core/irrt/irrt/math.hpp
Normal file
93
nac3core/irrt/irrt/math.hpp
Normal file
@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
namespace {
|
||||
// adapted from GNU Scientific Library: https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
// need to make sure `exp >= 0` before calling this function
|
||||
template<typename T>
|
||||
T __nac3_int_exp_impl(T base, T exp) {
|
||||
T res = 1;
|
||||
/* repeated squaring method */
|
||||
do {
|
||||
if (exp & 1) {
|
||||
res *= base; /* for n odd */
|
||||
}
|
||||
exp >>= 1;
|
||||
base *= base;
|
||||
} while (exp);
|
||||
return res;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#define DEF_nac3_int_exp_(T) \
|
||||
T __nac3_int_exp_##T(T base, T exp) { \
|
||||
return __nac3_int_exp_impl(base, exp); \
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
// Putting semicolons here to make clang-format not reformat this into
|
||||
// a stair shape.
|
||||
DEF_nac3_int_exp_(int32_t);
|
||||
DEF_nac3_int_exp_(int64_t);
|
||||
DEF_nac3_int_exp_(uint32_t);
|
||||
DEF_nac3_int_exp_(uint64_t);
|
||||
|
||||
int32_t __nac3_isinf(double x) {
|
||||
return __builtin_isinf(x);
|
||||
}
|
||||
|
||||
int32_t __nac3_isnan(double x) {
|
||||
return __builtin_isnan(x);
|
||||
}
|
||||
|
||||
double tgamma(double arg);
|
||||
|
||||
double __nac3_gamma(double z) {
|
||||
// Handling for denormals
|
||||
// | x | Python gamma(x) | C tgamma(x) |
|
||||
// --- | ----------------- | --------------- | ----------- |
|
||||
// (1) | nan | nan | nan |
|
||||
// (2) | -inf | -inf | inf |
|
||||
// (3) | inf | inf | inf |
|
||||
// (4) | 0.0 | inf | inf |
|
||||
// (5) | {-1.0, -2.0, ...} | inf | nan |
|
||||
|
||||
// (1)-(3)
|
||||
if (__builtin_isinf(z) || __builtin_isnan(z)) {
|
||||
return z;
|
||||
}
|
||||
|
||||
double v = tgamma(z);
|
||||
|
||||
// (4)-(5)
|
||||
return __builtin_isinf(v) || __builtin_isnan(v) ? __builtin_inf() : v;
|
||||
}
|
||||
|
||||
double lgamma(double arg);
|
||||
|
||||
double __nac3_gammaln(double x) {
|
||||
// libm's handling of value overflows differs from scipy:
|
||||
// - scipy: gammaln(-inf) -> -inf
|
||||
// - libm : lgamma(-inf) -> inf
|
||||
|
||||
if (__builtin_isinf(x)) {
|
||||
return x;
|
||||
}
|
||||
|
||||
return lgamma(x);
|
||||
}
|
||||
|
||||
double j0(double x);
|
||||
|
||||
double __nac3_j0(double x) {
|
||||
// libm's handling of value overflows differs from scipy:
|
||||
// - scipy: j0(inf) -> nan
|
||||
// - libm : j0(inf) -> 0.0
|
||||
|
||||
if (__builtin_isinf(x)) {
|
||||
return __builtin_nan("");
|
||||
}
|
||||
|
||||
return j0(x);
|
||||
}
|
||||
} // namespace
|
13
nac3core/irrt/irrt/math_util.hpp
Normal file
13
nac3core/irrt/irrt/math_util.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace {
|
||||
template<typename T>
|
||||
const T& max(const T& a, const T& b) {
|
||||
return a > b ? a : b;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
const T& min(const T& a, const T& b) {
|
||||
return a > b ? b : a;
|
||||
}
|
||||
} // namespace
|
151
nac3core/irrt/irrt/ndarray.hpp
Normal file
151
nac3core/irrt/irrt/ndarray.hpp
Normal file
@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/int_types.hpp"
|
||||
|
||||
// TODO: To be deleted since NDArray with strides is done.
|
||||
|
||||
namespace {
|
||||
template<typename SizeT>
|
||||
SizeT __nac3_ndarray_calc_size_impl(const SizeT* list_data, SizeT list_len, SizeT begin_idx, SizeT end_idx) {
|
||||
__builtin_assume(end_idx <= list_len);
|
||||
|
||||
SizeT num_elems = 1;
|
||||
for (SizeT i = begin_idx; i < end_idx; ++i) {
|
||||
SizeT val = list_data[i];
|
||||
__builtin_assume(val > 0);
|
||||
num_elems *= val;
|
||||
}
|
||||
return num_elems;
|
||||
}
|
||||
|
||||
template<typename SizeT>
|
||||
void __nac3_ndarray_calc_nd_indices_impl(SizeT index, const SizeT* dims, SizeT num_dims, NDIndexInt* idxs) {
|
||||
SizeT stride = 1;
|
||||
for (SizeT dim = 0; dim < num_dims; dim++) {
|
||||
SizeT i = num_dims - dim - 1;
|
||||
__builtin_assume(dims[i] > 0);
|
||||
idxs[i] = (index / stride) % dims[i];
|
||||
stride *= dims[i];
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SizeT>
|
||||
SizeT __nac3_ndarray_flatten_index_impl(const SizeT* dims,
|
||||
SizeT num_dims,
|
||||
const NDIndexInt* indices,
|
||||
SizeT num_indices) {
|
||||
SizeT idx = 0;
|
||||
SizeT stride = 1;
|
||||
for (SizeT i = 0; i < num_dims; ++i) {
|
||||
SizeT ri = num_dims - i - 1;
|
||||
if (ri < num_indices) {
|
||||
idx += stride * indices[ri];
|
||||
}
|
||||
|
||||
__builtin_assume(dims[i] > 0);
|
||||
stride *= dims[ri];
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
template<typename SizeT>
|
||||
void __nac3_ndarray_calc_broadcast_impl(const SizeT* lhs_dims,
|
||||
SizeT lhs_ndims,
|
||||
const SizeT* rhs_dims,
|
||||
SizeT rhs_ndims,
|
||||
SizeT* out_dims) {
|
||||
SizeT max_ndims = lhs_ndims > rhs_ndims ? lhs_ndims : rhs_ndims;
|
||||
|
||||
for (SizeT i = 0; i < max_ndims; ++i) {
|
||||
const SizeT* lhs_dim_sz = i < lhs_ndims ? &lhs_dims[lhs_ndims - i - 1] : nullptr;
|
||||
const SizeT* rhs_dim_sz = i < rhs_ndims ? &rhs_dims[rhs_ndims - i - 1] : nullptr;
|
||||
SizeT* out_dim = &out_dims[max_ndims - i - 1];
|
||||
|
||||
if (lhs_dim_sz == nullptr) {
|
||||
*out_dim = *rhs_dim_sz;
|
||||
} else if (rhs_dim_sz == nullptr) {
|
||||
*out_dim = *lhs_dim_sz;
|
||||
} else if (*lhs_dim_sz == 1) {
|
||||
*out_dim = *rhs_dim_sz;
|
||||
} else if (*rhs_dim_sz == 1) {
|
||||
*out_dim = *lhs_dim_sz;
|
||||
} else if (*lhs_dim_sz == *rhs_dim_sz) {
|
||||
*out_dim = *lhs_dim_sz;
|
||||
} else {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename SizeT>
|
||||
void __nac3_ndarray_calc_broadcast_idx_impl(const SizeT* src_dims,
|
||||
SizeT src_ndims,
|
||||
const NDIndexInt* in_idx,
|
||||
NDIndexInt* out_idx) {
|
||||
for (SizeT i = 0; i < src_ndims; ++i) {
|
||||
SizeT src_i = src_ndims - i - 1;
|
||||
out_idx[src_i] = src_dims[src_i] == 1 ? 0 : in_idx[src_i];
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
uint32_t __nac3_ndarray_calc_size(const uint32_t* list_data, uint32_t list_len, uint32_t begin_idx, uint32_t end_idx) {
|
||||
return __nac3_ndarray_calc_size_impl(list_data, list_len, begin_idx, end_idx);
|
||||
}
|
||||
|
||||
uint64_t
|
||||
__nac3_ndarray_calc_size64(const uint64_t* list_data, uint64_t list_len, uint64_t begin_idx, uint64_t end_idx) {
|
||||
return __nac3_ndarray_calc_size_impl(list_data, list_len, begin_idx, end_idx);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_nd_indices(uint32_t index, const uint32_t* dims, uint32_t num_dims, NDIndexInt* idxs) {
|
||||
__nac3_ndarray_calc_nd_indices_impl(index, dims, num_dims, idxs);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_nd_indices64(uint64_t index, const uint64_t* dims, uint64_t num_dims, NDIndexInt* idxs) {
|
||||
__nac3_ndarray_calc_nd_indices_impl(index, dims, num_dims, idxs);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
__nac3_ndarray_flatten_index(const uint32_t* dims, uint32_t num_dims, const NDIndexInt* indices, uint32_t num_indices) {
|
||||
return __nac3_ndarray_flatten_index_impl(dims, num_dims, indices, num_indices);
|
||||
}
|
||||
|
||||
uint64_t __nac3_ndarray_flatten_index64(const uint64_t* dims,
|
||||
uint64_t num_dims,
|
||||
const NDIndexInt* indices,
|
||||
uint64_t num_indices) {
|
||||
return __nac3_ndarray_flatten_index_impl(dims, num_dims, indices, num_indices);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast(const uint32_t* lhs_dims,
|
||||
uint32_t lhs_ndims,
|
||||
const uint32_t* rhs_dims,
|
||||
uint32_t rhs_ndims,
|
||||
uint32_t* out_dims) {
|
||||
return __nac3_ndarray_calc_broadcast_impl(lhs_dims, lhs_ndims, rhs_dims, rhs_ndims, out_dims);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast64(const uint64_t* lhs_dims,
|
||||
uint64_t lhs_ndims,
|
||||
const uint64_t* rhs_dims,
|
||||
uint64_t rhs_ndims,
|
||||
uint64_t* out_dims) {
|
||||
return __nac3_ndarray_calc_broadcast_impl(lhs_dims, lhs_ndims, rhs_dims, rhs_ndims, out_dims);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast_idx(const uint32_t* src_dims,
|
||||
uint32_t src_ndims,
|
||||
const NDIndexInt* in_idx,
|
||||
NDIndexInt* out_idx) {
|
||||
__nac3_ndarray_calc_broadcast_idx_impl(src_dims, src_ndims, in_idx, out_idx);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_calc_broadcast_idx64(const uint64_t* src_dims,
|
||||
uint64_t src_ndims,
|
||||
const NDIndexInt* in_idx,
|
||||
NDIndexInt* out_idx) {
|
||||
__nac3_ndarray_calc_broadcast_idx_impl(src_dims, src_ndims, in_idx, out_idx);
|
||||
}
|
||||
} // namespace
|
342
nac3core/irrt/irrt/ndarray/basic.hpp
Normal file
342
nac3core/irrt/irrt/ndarray/basic.hpp
Normal file
@ -0,0 +1,342 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/debug.hpp"
|
||||
#include "irrt/exception.hpp"
|
||||
#include "irrt/int_types.hpp"
|
||||
#include "irrt/ndarray/def.hpp"
|
||||
|
||||
namespace {
|
||||
namespace ndarray {
|
||||
namespace basic {
|
||||
/**
|
||||
* @brief Assert that `shape` does not contain negative dimensions.
|
||||
*
|
||||
* @param ndims Number of dimensions in `shape`
|
||||
* @param shape The shape to check on
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void assert_shape_no_negative(SizeT ndims, const SizeT* shape) {
|
||||
for (SizeT axis = 0; axis < ndims; axis++) {
|
||||
if (shape[axis] < 0) {
|
||||
raise_exception(SizeT, EXN_VALUE_ERROR,
|
||||
"negative dimensions are not allowed; axis {0} "
|
||||
"has dimension {1}",
|
||||
axis, shape[axis], NO_PARAM);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Assert that two shapes are the same in the context of writing output to an ndarray.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void assert_output_shape_same(SizeT ndarray_ndims,
|
||||
const SizeT* ndarray_shape,
|
||||
SizeT output_ndims,
|
||||
const SizeT* output_shape) {
|
||||
if (ndarray_ndims != output_ndims) {
|
||||
// There is no corresponding NumPy error message like this.
|
||||
raise_exception(SizeT, EXN_VALUE_ERROR, "Cannot write output of ndims {0} to an ndarray with ndims {1}",
|
||||
output_ndims, ndarray_ndims, NO_PARAM);
|
||||
}
|
||||
|
||||
for (SizeT axis = 0; axis < ndarray_ndims; axis++) {
|
||||
if (ndarray_shape[axis] != output_shape[axis]) {
|
||||
// There is no corresponding NumPy error message like this.
|
||||
raise_exception(SizeT, EXN_VALUE_ERROR,
|
||||
"Mismatched dimensions on axis {0}, output has "
|
||||
"dimension {1}, but destination ndarray has dimension {2}.",
|
||||
axis, output_shape[axis], ndarray_shape[axis]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the number of elements of an ndarray given its shape.
|
||||
*
|
||||
* @param ndims Number of dimensions in `shape`
|
||||
* @param shape The shape of the ndarray
|
||||
*/
|
||||
template<typename SizeT>
|
||||
SizeT calc_size_from_shape(SizeT ndims, const SizeT* shape) {
|
||||
SizeT size = 1;
|
||||
for (SizeT axis = 0; axis < ndims; axis++)
|
||||
size *= shape[axis];
|
||||
return size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute the array indices of the `nth` (0-based) element of an ndarray given only its shape.
|
||||
*
|
||||
* @param ndims Number of elements in `shape` and `indices`
|
||||
* @param shape The shape of the ndarray
|
||||
* @param indices The returned indices indexing the ndarray with shape `shape`.
|
||||
* @param nth The index of the element of interest.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void set_indices_by_nth(SizeT ndims, const SizeT* shape, SizeT* indices, SizeT nth) {
|
||||
for (SizeT i = 0; i < ndims; i++) {
|
||||
SizeT axis = ndims - i - 1;
|
||||
SizeT dim = shape[axis];
|
||||
|
||||
indices[axis] = nth % dim;
|
||||
nth /= dim;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the number of elements of an `ndarray`
|
||||
*
|
||||
* This function corresponds to `<an_ndarray>.size`
|
||||
*/
|
||||
template<typename SizeT>
|
||||
SizeT size(const NDArray<SizeT>* ndarray) {
|
||||
return calc_size_from_shape(ndarray->ndims, ndarray->shape);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return of the number of its content of an `ndarray`.
|
||||
*
|
||||
* This function corresponds to `<an_ndarray>.nbytes`.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
SizeT nbytes(const NDArray<SizeT>* ndarray) {
|
||||
return size(ndarray) * ndarray->itemsize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the `len()` of an ndarray, and asserts that `ndarray` is a sized object.
|
||||
*
|
||||
* This function corresponds to `<an_ndarray>.__len__`.
|
||||
*
|
||||
* @param dst_length The length.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
SizeT len(const NDArray<SizeT>* ndarray) {
|
||||
if (ndarray->ndims != 0) {
|
||||
return ndarray->shape[0];
|
||||
}
|
||||
|
||||
// numpy prohibits `__len__` on unsized objects
|
||||
raise_exception(SizeT, EXN_TYPE_ERROR, "len() of unsized object", NO_PARAM, NO_PARAM, NO_PARAM);
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a boolean indicating if `ndarray` is (C-)contiguous.
|
||||
*
|
||||
* You may want to see ndarray's rules for C-contiguity:
|
||||
* https://github.com/numpy/numpy/blob/df256d0d2f3bc6833699529824781c58f9c6e697/numpy/core/src/multiarray/flagsobject.c#L95C1-L99C45
|
||||
*/
|
||||
template<typename SizeT>
|
||||
bool is_c_contiguous(const NDArray<SizeT>* ndarray) {
|
||||
// References:
|
||||
// - tinynumpy's implementation:
|
||||
// https://github.com/wadetb/tinynumpy/blob/0d23d22e07062ffab2afa287374c7b366eebdda1/tinynumpy/tinynumpy.py#L102
|
||||
// - ndarray's flags["C_CONTIGUOUS"]:
|
||||
// https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html#numpy.ndarray.flags
|
||||
// - ndarray's rules for C-contiguity:
|
||||
// https://github.com/numpy/numpy/blob/df256d0d2f3bc6833699529824781c58f9c6e697/numpy/core/src/multiarray/flagsobject.c#L95C1-L99C45
|
||||
|
||||
// From
|
||||
// https://github.com/numpy/numpy/blob/df256d0d2f3bc6833699529824781c58f9c6e697/numpy/core/src/multiarray/flagsobject.c#L95C1-L99C45:
|
||||
//
|
||||
// The traditional rule is that for an array to be flagged as C contiguous,
|
||||
// the following must hold:
|
||||
//
|
||||
// strides[-1] == itemsize
|
||||
// strides[i] == shape[i+1] * strides[i + 1]
|
||||
// [...]
|
||||
// According to these rules, a 0- or 1-dimensional array is either both
|
||||
// C- and F-contiguous, or neither; and an array with 2+ dimensions
|
||||
// can be C- or F- contiguous, or neither, but not both. Though there
|
||||
// there are exceptions for arrays with zero or one item, in the first
|
||||
// case the check is relaxed up to and including the first dimension
|
||||
// with shape[i] == 0. In the second case `strides == itemsize` will
|
||||
// can be true for all dimensions and both flags are set.
|
||||
|
||||
if (ndarray->ndims == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ndarray->strides[ndarray->ndims - 1] != ndarray->itemsize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (SizeT i = 1; i < ndarray->ndims; i++) {
|
||||
SizeT axis_i = ndarray->ndims - i - 1;
|
||||
if (ndarray->strides[axis_i] != ndarray->shape[axis_i + 1] * ndarray->strides[axis_i + 1]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the pointer to the element indexed by `indices` along the ndarray's axes.
|
||||
*
|
||||
* This function does no bound check.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void* get_pelement_by_indices(const NDArray<SizeT>* ndarray, const SizeT* indices) {
|
||||
void* element = ndarray->data;
|
||||
for (SizeT dim_i = 0; dim_i < ndarray->ndims; dim_i++)
|
||||
element = static_cast<uint8_t*>(element) + indices[dim_i] * ndarray->strides[dim_i];
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return the pointer to the nth (0-based) element of `ndarray` in flattened view.
|
||||
*
|
||||
* This function does no bound check.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void* get_nth_pelement(const NDArray<SizeT>* ndarray, SizeT nth) {
|
||||
void* element = ndarray->data;
|
||||
for (SizeT i = 0; i < ndarray->ndims; i++) {
|
||||
SizeT axis = ndarray->ndims - i - 1;
|
||||
SizeT dim = ndarray->shape[axis];
|
||||
element = static_cast<uint8_t*>(element) + ndarray->strides[axis] * (nth % dim);
|
||||
nth /= dim;
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the strides of an ndarray given an ndarray `shape` to be contiguous.
|
||||
*
|
||||
* You might want to read https://ajcr.net/stride-guide-part-1/.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void set_strides_by_shape(NDArray<SizeT>* ndarray) {
|
||||
SizeT stride_product = 1;
|
||||
for (SizeT i = 0; i < ndarray->ndims; i++) {
|
||||
SizeT axis = ndarray->ndims - i - 1;
|
||||
ndarray->strides[axis] = stride_product * ndarray->itemsize;
|
||||
stride_product *= ndarray->shape[axis];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set an element in `ndarray`.
|
||||
*
|
||||
* @param pelement Pointer to the element in `ndarray` to be set.
|
||||
* @param pvalue Pointer to the value `pelement` will be set to.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void set_pelement_value(NDArray<SizeT>* ndarray, void* pelement, const void* pvalue) {
|
||||
__builtin_memcpy(pelement, pvalue, ndarray->itemsize);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Copy data from one ndarray to another of the exact same size and itemsize.
|
||||
*
|
||||
* Both ndarrays will be viewed in their flatten views when copying the elements.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void copy_data(const NDArray<SizeT>* src_ndarray, NDArray<SizeT>* dst_ndarray) {
|
||||
// TODO: Make this faster with memcpy when we see a contiguous segment.
|
||||
// TODO: Handle overlapping.
|
||||
|
||||
debug_assert_eq(SizeT, src_ndarray->itemsize, dst_ndarray->itemsize);
|
||||
|
||||
for (SizeT i = 0; i < size(src_ndarray); i++) {
|
||||
auto src_element = ndarray::basic::get_nth_pelement(src_ndarray, i);
|
||||
auto dst_element = ndarray::basic::get_nth_pelement(dst_ndarray, i);
|
||||
ndarray::basic::set_pelement_value(dst_ndarray, dst_element, src_element);
|
||||
}
|
||||
}
|
||||
} // namespace basic
|
||||
} // namespace ndarray
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
using namespace ndarray::basic;
|
||||
|
||||
void __nac3_ndarray_util_assert_shape_no_negative(int32_t ndims, int32_t* shape) {
|
||||
assert_shape_no_negative(ndims, shape);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_util_assert_shape_no_negative64(int64_t ndims, int64_t* shape) {
|
||||
assert_shape_no_negative(ndims, shape);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_util_assert_output_shape_same(int32_t ndarray_ndims,
|
||||
const int32_t* ndarray_shape,
|
||||
int32_t output_ndims,
|
||||
const int32_t* output_shape) {
|
||||
assert_output_shape_same(ndarray_ndims, ndarray_shape, output_ndims, output_shape);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_util_assert_output_shape_same64(int64_t ndarray_ndims,
|
||||
const int64_t* ndarray_shape,
|
||||
int64_t output_ndims,
|
||||
const int64_t* output_shape) {
|
||||
assert_output_shape_same(ndarray_ndims, ndarray_shape, output_ndims, output_shape);
|
||||
}
|
||||
|
||||
uint32_t __nac3_ndarray_size(NDArray<int32_t>* ndarray) {
|
||||
return size(ndarray);
|
||||
}
|
||||
|
||||
uint64_t __nac3_ndarray_size64(NDArray<int64_t>* ndarray) {
|
||||
return size(ndarray);
|
||||
}
|
||||
|
||||
uint32_t __nac3_ndarray_nbytes(NDArray<int32_t>* ndarray) {
|
||||
return nbytes(ndarray);
|
||||
}
|
||||
|
||||
uint64_t __nac3_ndarray_nbytes64(NDArray<int64_t>* ndarray) {
|
||||
return nbytes(ndarray);
|
||||
}
|
||||
|
||||
int32_t __nac3_ndarray_len(NDArray<int32_t>* ndarray) {
|
||||
return len(ndarray);
|
||||
}
|
||||
|
||||
int64_t __nac3_ndarray_len64(NDArray<int64_t>* ndarray) {
|
||||
return len(ndarray);
|
||||
}
|
||||
|
||||
bool __nac3_ndarray_is_c_contiguous(NDArray<int32_t>* ndarray) {
|
||||
return is_c_contiguous(ndarray);
|
||||
}
|
||||
|
||||
bool __nac3_ndarray_is_c_contiguous64(NDArray<int64_t>* ndarray) {
|
||||
return is_c_contiguous(ndarray);
|
||||
}
|
||||
|
||||
void* __nac3_ndarray_get_nth_pelement(const NDArray<int32_t>* ndarray, int32_t nth) {
|
||||
return get_nth_pelement(ndarray, nth);
|
||||
}
|
||||
|
||||
void* __nac3_ndarray_get_nth_pelement64(const NDArray<int64_t>* ndarray, int64_t nth) {
|
||||
return get_nth_pelement(ndarray, nth);
|
||||
}
|
||||
|
||||
void* __nac3_ndarray_get_pelement_by_indices(const NDArray<int32_t>* ndarray, int32_t* indices) {
|
||||
return get_pelement_by_indices(ndarray, indices);
|
||||
}
|
||||
|
||||
void* __nac3_ndarray_get_pelement_by_indices64(const NDArray<int64_t>* ndarray, int64_t* indices) {
|
||||
return get_pelement_by_indices(ndarray, indices);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_set_strides_by_shape(NDArray<int32_t>* ndarray) {
|
||||
set_strides_by_shape(ndarray);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_set_strides_by_shape64(NDArray<int64_t>* ndarray) {
|
||||
set_strides_by_shape(ndarray);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_copy_data(NDArray<int32_t>* src_ndarray, NDArray<int32_t>* dst_ndarray) {
|
||||
copy_data(src_ndarray, dst_ndarray);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_copy_data64(NDArray<int64_t>* src_ndarray, NDArray<int64_t>* dst_ndarray) {
|
||||
copy_data(src_ndarray, dst_ndarray);
|
||||
}
|
||||
}
|
51
nac3core/irrt/irrt/ndarray/def.hpp
Normal file
51
nac3core/irrt/irrt/ndarray/def.hpp
Normal file
@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/int_types.hpp"
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* @brief The NDArray object
|
||||
*
|
||||
* Official numpy implementation:
|
||||
* https://github.com/numpy/numpy/blob/735a477f0bc2b5b84d0e72d92f224bde78d4e069/doc/source/reference/c-api/types-and-structures.rst#pyarrayinterface
|
||||
*
|
||||
* Note that this implementation is based on `PyArrayInterface` rather of `PyArrayObject`. The
|
||||
* difference between `PyArrayInterface` and `PyArrayObject` (relevant to our implementation) is
|
||||
* that `PyArrayInterface` *has* `itemsize` and uses `void*` for its `data`, whereas `PyArrayObject`
|
||||
* does not require `itemsize` (probably using `strides[-1]` instead) and uses `char*` for its
|
||||
* `data`. There are also minor differences in the struct layout.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
struct NDArray {
|
||||
/**
|
||||
* @brief The number of bytes of a single element in `data`.
|
||||
*/
|
||||
SizeT itemsize;
|
||||
|
||||
/**
|
||||
* @brief The number of dimensions of this shape.
|
||||
*/
|
||||
SizeT ndims;
|
||||
|
||||
/**
|
||||
* @brief The NDArray shape, with length equal to `ndims`.
|
||||
*
|
||||
* Note that it may contain 0.
|
||||
*/
|
||||
SizeT* shape;
|
||||
|
||||
/**
|
||||
* @brief Array strides, with length equal to `ndims`
|
||||
*
|
||||
* The stride values are in units of bytes, not number of elements.
|
||||
*
|
||||
* Note that `strides` can have negative values or contain 0.
|
||||
*/
|
||||
SizeT* strides;
|
||||
|
||||
/**
|
||||
* @brief The underlying data this `ndarray` is pointing to.
|
||||
*/
|
||||
void* data;
|
||||
};
|
||||
} // namespace
|
220
nac3core/irrt/irrt/ndarray/indexing.hpp
Normal file
220
nac3core/irrt/irrt/ndarray/indexing.hpp
Normal file
@ -0,0 +1,220 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/exception.hpp"
|
||||
#include "irrt/int_types.hpp"
|
||||
#include "irrt/ndarray/basic.hpp"
|
||||
#include "irrt/ndarray/def.hpp"
|
||||
#include "irrt/range.hpp"
|
||||
#include "irrt/slice.hpp"
|
||||
|
||||
namespace {
|
||||
typedef uint8_t NDIndexType;
|
||||
|
||||
/**
|
||||
* @brief A single element index
|
||||
*
|
||||
* `data` points to a `int32_t`.
|
||||
*/
|
||||
const NDIndexType ND_INDEX_TYPE_SINGLE_ELEMENT = 0;
|
||||
|
||||
/**
|
||||
* @brief A slice index
|
||||
*
|
||||
* `data` points to a `Slice<int32_t>`.
|
||||
*/
|
||||
const NDIndexType ND_INDEX_TYPE_SLICE = 1;
|
||||
|
||||
/**
|
||||
* @brief `np.newaxis` / `None`
|
||||
*
|
||||
* `data` is unused.
|
||||
*/
|
||||
const NDIndexType ND_INDEX_TYPE_NEWAXIS = 2;
|
||||
|
||||
/**
|
||||
* @brief `Ellipsis` / `...`
|
||||
*
|
||||
* `data` is unused.
|
||||
*/
|
||||
const NDIndexType ND_INDEX_TYPE_ELLIPSIS = 3;
|
||||
|
||||
/**
|
||||
* @brief An index used in ndarray indexing
|
||||
*
|
||||
* That is:
|
||||
* ```
|
||||
* my_ndarray[::-1, 3, ..., np.newaxis]
|
||||
* ^^^^ ^ ^^^ ^^^^^^^^^^ each of these is represented by an NDIndex.
|
||||
* ```
|
||||
*/
|
||||
struct NDIndex {
|
||||
/**
|
||||
* @brief Enum tag to specify the type of index.
|
||||
*
|
||||
* Please see the comment of each enum constant.
|
||||
*/
|
||||
NDIndexType type;
|
||||
|
||||
/**
|
||||
* @brief The accompanying data associated with `type`.
|
||||
*
|
||||
* Please see the comment of each enum constant.
|
||||
*/
|
||||
uint8_t* data;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace {
|
||||
namespace ndarray {
|
||||
namespace indexing {
|
||||
/**
|
||||
* @brief Perform ndarray "basic indexing" (https://numpy.org/doc/stable/user/basics.indexing.html#basic-indexing)
|
||||
*
|
||||
* This function is very similar to performing `dst_ndarray = src_ndarray[indices]` in Python.
|
||||
*
|
||||
* This function also does proper assertions on `indices` to check for out of bounds access and more.
|
||||
*
|
||||
* # Notes on `dst_ndarray`
|
||||
* The caller is responsible for allocating space for the resulting ndarray.
|
||||
* Here is what this function expects from `dst_ndarray` when called:
|
||||
* - `dst_ndarray->data` does not have to be initialized.
|
||||
* - `dst_ndarray->itemsize` does not have to be initialized.
|
||||
* - `dst_ndarray->ndims` must be initialized, and it must be equal to the expected `ndims` of the `dst_ndarray` after
|
||||
* indexing `src_ndarray` with `indices`.
|
||||
* - `dst_ndarray->shape` must be allocated, through it can contain uninitialized values.
|
||||
* - `dst_ndarray->strides` must be allocated, through it can contain uninitialized values.
|
||||
* When this function call ends:
|
||||
* - `dst_ndarray->data` is set to `src_ndarray->data`.
|
||||
* - `dst_ndarray->itemsize` is set to `src_ndarray->itemsize`.
|
||||
* - `dst_ndarray->ndims` is unchanged.
|
||||
* - `dst_ndarray->shape` is updated according to how `src_ndarray` is indexed.
|
||||
* - `dst_ndarray->strides` is updated accordingly by how ndarray indexing works.
|
||||
*
|
||||
* @param indices indices to index `src_ndarray`, ordered in the same way you would write them in Python.
|
||||
* @param src_ndarray The NDArray to be indexed.
|
||||
* @param dst_ndarray The resulting NDArray after indexing. Further details in the comments above,
|
||||
*/
|
||||
template<typename SizeT>
|
||||
void index(SizeT num_indices, const NDIndex* indices, const NDArray<SizeT>* src_ndarray, NDArray<SizeT>* dst_ndarray) {
|
||||
// Validate `indices`.
|
||||
|
||||
// Expected value of `dst_ndarray->ndims`.
|
||||
SizeT expected_dst_ndims = src_ndarray->ndims;
|
||||
// To check for "too many indices for array: array is ?-dimensional, but ? were indexed"
|
||||
SizeT num_indexed = 0;
|
||||
// There may be ellipsis `...` in `indices`. There can only be 0 or 1 ellipsis.
|
||||
SizeT num_ellipsis = 0;
|
||||
|
||||
for (SizeT i = 0; i < num_indices; i++) {
|
||||
if (indices[i].type == ND_INDEX_TYPE_SINGLE_ELEMENT) {
|
||||
expected_dst_ndims--;
|
||||
num_indexed++;
|
||||
} else if (indices[i].type == ND_INDEX_TYPE_SLICE) {
|
||||
num_indexed++;
|
||||
} else if (indices[i].type == ND_INDEX_TYPE_NEWAXIS) {
|
||||
expected_dst_ndims++;
|
||||
} else if (indices[i].type == ND_INDEX_TYPE_ELLIPSIS) {
|
||||
num_ellipsis++;
|
||||
if (num_ellipsis > 1) {
|
||||
raise_exception(SizeT, EXN_INDEX_ERROR, "an index can only have a single ellipsis ('...')", NO_PARAM,
|
||||
NO_PARAM, NO_PARAM);
|
||||
}
|
||||
} else {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert_eq(SizeT, expected_dst_ndims, dst_ndarray->ndims);
|
||||
|
||||
if (src_ndarray->ndims - num_indexed < 0) {
|
||||
raise_exception(SizeT, EXN_INDEX_ERROR,
|
||||
"too many indices for array: array is {0}-dimensional, "
|
||||
"but {1} were indexed",
|
||||
src_ndarray->ndims, num_indices, NO_PARAM);
|
||||
}
|
||||
|
||||
dst_ndarray->data = src_ndarray->data;
|
||||
dst_ndarray->itemsize = src_ndarray->itemsize;
|
||||
|
||||
// Reference code:
|
||||
// https://github.com/wadetb/tinynumpy/blob/0d23d22e07062ffab2afa287374c7b366eebdda1/tinynumpy/tinynumpy.py#L652
|
||||
SizeT src_axis = 0;
|
||||
SizeT dst_axis = 0;
|
||||
|
||||
for (int32_t i = 0; i < num_indices; i++) {
|
||||
const NDIndex* index = &indices[i];
|
||||
if (index->type == ND_INDEX_TYPE_SINGLE_ELEMENT) {
|
||||
SizeT input = (SizeT) * ((int32_t*)index->data);
|
||||
|
||||
SizeT k = slice::resolve_index_in_length(src_ndarray->shape[src_axis], input);
|
||||
if (k == -1) {
|
||||
raise_exception(SizeT, EXN_INDEX_ERROR,
|
||||
"index {0} is out of bounds for axis {1} "
|
||||
"with size {2}",
|
||||
input, src_axis, src_ndarray->shape[src_axis]);
|
||||
}
|
||||
|
||||
dst_ndarray->data = static_cast<uint8_t*>(dst_ndarray->data) + k * src_ndarray->strides[src_axis];
|
||||
|
||||
src_axis++;
|
||||
} else if (index->type == ND_INDEX_TYPE_SLICE) {
|
||||
Slice<int32_t>* slice = (Slice<int32_t>*)index->data;
|
||||
|
||||
Range<int32_t> range = slice->indices_checked<SizeT>(src_ndarray->shape[src_axis]);
|
||||
|
||||
dst_ndarray->data = static_cast<uint8_t*>(dst_ndarray->data) + (SizeT)range.start * src_ndarray->strides[src_axis];
|
||||
dst_ndarray->strides[dst_axis] = ((SizeT)range.step) * src_ndarray->strides[src_axis];
|
||||
dst_ndarray->shape[dst_axis] = (SizeT)range.len<SizeT>();
|
||||
|
||||
dst_axis++;
|
||||
src_axis++;
|
||||
} else if (index->type == ND_INDEX_TYPE_NEWAXIS) {
|
||||
dst_ndarray->strides[dst_axis] = 0;
|
||||
dst_ndarray->shape[dst_axis] = 1;
|
||||
|
||||
dst_axis++;
|
||||
} else if (index->type == ND_INDEX_TYPE_ELLIPSIS) {
|
||||
// The number of ':' entries this '...' implies.
|
||||
SizeT ellipsis_size = src_ndarray->ndims - num_indexed;
|
||||
|
||||
for (SizeT j = 0; j < ellipsis_size; j++) {
|
||||
dst_ndarray->strides[dst_axis] = src_ndarray->strides[src_axis];
|
||||
dst_ndarray->shape[dst_axis] = src_ndarray->shape[src_axis];
|
||||
|
||||
dst_axis++;
|
||||
src_axis++;
|
||||
}
|
||||
} else {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
for (; dst_axis < dst_ndarray->ndims; dst_axis++, src_axis++) {
|
||||
dst_ndarray->shape[dst_axis] = src_ndarray->shape[src_axis];
|
||||
dst_ndarray->strides[dst_axis] = src_ndarray->strides[src_axis];
|
||||
}
|
||||
|
||||
debug_assert_eq(SizeT, src_ndarray->ndims, src_axis);
|
||||
debug_assert_eq(SizeT, dst_ndarray->ndims, dst_axis);
|
||||
}
|
||||
} // namespace indexing
|
||||
} // namespace ndarray
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
using namespace ndarray::indexing;
|
||||
|
||||
void __nac3_ndarray_index(int32_t num_indices,
|
||||
NDIndex* indices,
|
||||
NDArray<int32_t>* src_ndarray,
|
||||
NDArray<int32_t>* dst_ndarray) {
|
||||
index(num_indices, indices, src_ndarray, dst_ndarray);
|
||||
}
|
||||
|
||||
void __nac3_ndarray_index64(int64_t num_indices,
|
||||
NDIndex* indices,
|
||||
NDArray<int64_t>* src_ndarray,
|
||||
NDArray<int64_t>* dst_ndarray) {
|
||||
index(num_indices, indices, src_ndarray, dst_ndarray);
|
||||
}
|
||||
}
|
146
nac3core/irrt/irrt/ndarray/iter.hpp
Normal file
146
nac3core/irrt/irrt/ndarray/iter.hpp
Normal file
@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/int_types.hpp"
|
||||
#include "irrt/ndarray/def.hpp"
|
||||
|
||||
namespace {
|
||||
/**
|
||||
* @brief Helper struct to enumerate through an ndarray *efficiently*.
|
||||
*
|
||||
* Example usage (in pseudo-code):
|
||||
* ```
|
||||
* // Suppose my_ndarray has been initialized, with shape [2, 3] and dtype `double`
|
||||
* NDIter nditer;
|
||||
* nditer.initialize(my_ndarray);
|
||||
* while (nditer.has_element()) {
|
||||
* // This body is run 6 (= my_ndarray.size) times.
|
||||
*
|
||||
* // [0, 0] -> [0, 1] -> [0, 2] -> [1, 0] -> [1, 1] -> [1, 2] -> end
|
||||
* print(nditer.indices);
|
||||
*
|
||||
* // 0 -> 1 -> 2 -> 3 -> 4 -> 5
|
||||
* print(nditer.nth);
|
||||
*
|
||||
* // <1st element> -> <2nd element> -> ... -> <6th element> -> end
|
||||
* print(*((double *) nditer.element))
|
||||
*
|
||||
* nditer.next(); // Go to next element.
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* Interesting cases:
|
||||
* - If `my_ndarray.ndims` == 0, there is one iteration.
|
||||
* - If `my_ndarray.shape` contains zeroes, there are no iterations.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
struct NDIter {
|
||||
// Information about the ndarray being iterated over.
|
||||
SizeT ndims;
|
||||
SizeT* shape;
|
||||
SizeT* strides;
|
||||
|
||||
/**
|
||||
* @brief The current indices.
|
||||
*
|
||||
* Must be allocated by the caller.
|
||||
*/
|
||||
SizeT* indices;
|
||||
|
||||
/**
|
||||
* @brief The nth (0-based) index of the current indices.
|
||||
*
|
||||
* Initially this is 0.
|
||||
*/
|
||||
SizeT nth;
|
||||
|
||||
/**
|
||||
* @brief Pointer to the current element.
|
||||
*
|
||||
* Initially this points to first element of the ndarray.
|
||||
*/
|
||||
void* element;
|
||||
|
||||
/**
|
||||
* @brief Cache for the product of shape.
|
||||
*
|
||||
* Could be 0 if `shape` has 0s in it.
|
||||
*/
|
||||
SizeT size;
|
||||
|
||||
void initialize(SizeT ndims, SizeT* shape, SizeT* strides, void* element, SizeT* indices) {
|
||||
this->ndims = ndims;
|
||||
this->shape = shape;
|
||||
this->strides = strides;
|
||||
|
||||
this->indices = indices;
|
||||
this->element = element;
|
||||
|
||||
// Compute size
|
||||
this->size = 1;
|
||||
for (SizeT i = 0; i < ndims; i++) {
|
||||
this->size *= shape[i];
|
||||
}
|
||||
|
||||
// `indices` starts on all 0s.
|
||||
for (SizeT axis = 0; axis < ndims; axis++)
|
||||
indices[axis] = 0;
|
||||
nth = 0;
|
||||
}
|
||||
|
||||
void initialize_by_ndarray(NDArray<SizeT>* ndarray, SizeT* indices) {
|
||||
// NOTE: ndarray->data is pointing to the first element, and `NDIter`'s `element` should also point to the first
|
||||
// element as well.
|
||||
this->initialize(ndarray->ndims, ndarray->shape, ndarray->strides, ndarray->data, indices);
|
||||
}
|
||||
|
||||
// Is the current iteration valid?
|
||||
// If true, then `element`, `indices` and `nth` contain details about the current element.
|
||||
bool has_element() { return nth < size; }
|
||||
|
||||
// Go to the next element.
|
||||
void next() {
|
||||
for (SizeT i = 0; i < ndims; i++) {
|
||||
SizeT axis = ndims - i - 1;
|
||||
indices[axis]++;
|
||||
if (indices[axis] >= shape[axis]) {
|
||||
indices[axis] = 0;
|
||||
|
||||
// TODO: There is something called backstrides to speedup iteration.
|
||||
// See https://ajcr.net/stride-guide-part-1/, and
|
||||
// https://docs.scipy.org/doc/numpy-1.13.0/reference/c-api.types-and-structures.html#c.PyArrayIterObject.PyArrayIterObject.backstrides.
|
||||
element = static_cast<void*>(reinterpret_cast<uint8_t*>(element) - strides[axis] * (shape[axis] - 1));
|
||||
} else {
|
||||
element = static_cast<void*>(reinterpret_cast<uint8_t*>(element) + strides[axis]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
nth++;
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
void __nac3_nditer_initialize(NDIter<int32_t>* iter, NDArray<int32_t>* ndarray, int32_t* indices) {
|
||||
iter->initialize_by_ndarray(ndarray, indices);
|
||||
}
|
||||
|
||||
void __nac3_nditer_initialize64(NDIter<int64_t>* iter, NDArray<int64_t>* ndarray, int64_t* indices) {
|
||||
iter->initialize_by_ndarray(ndarray, indices);
|
||||
}
|
||||
|
||||
bool __nac3_nditer_has_element(NDIter<int32_t>* iter) {
|
||||
return iter->has_element();
|
||||
}
|
||||
|
||||
bool __nac3_nditer_has_element64(NDIter<int64_t>* iter) {
|
||||
return iter->has_element();
|
||||
}
|
||||
|
||||
void __nac3_nditer_next(NDIter<int32_t>* iter) {
|
||||
iter->next();
|
||||
}
|
||||
|
||||
void __nac3_nditer_next64(NDIter<int64_t>* iter) {
|
||||
iter->next();
|
||||
}
|
||||
}
|
47
nac3core/irrt/irrt/range.hpp
Normal file
47
nac3core/irrt/irrt/range.hpp
Normal file
@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/debug.hpp"
|
||||
#include "irrt/int_types.hpp"
|
||||
|
||||
namespace {
|
||||
namespace range {
|
||||
template<typename T>
|
||||
T len(T start, T stop, T step) {
|
||||
// Reference:
|
||||
// https://github.com/python/cpython/blob/9dbd12375561a393eaec4b21ee4ac568a407cdb0/Objects/rangeobject.c#L933
|
||||
if (step > 0 && start < stop)
|
||||
return 1 + (stop - 1 - start) / step;
|
||||
else if (step < 0 && start > stop)
|
||||
return 1 + (start - 1 - stop) / (-step);
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
} // namespace range
|
||||
|
||||
/**
|
||||
* @brief A Python range.
|
||||
*/
|
||||
template<typename T>
|
||||
struct Range {
|
||||
T start;
|
||||
T stop;
|
||||
T step;
|
||||
|
||||
/**
|
||||
* @brief Calculate the `len()` of this range.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
T len() {
|
||||
debug_assert(SizeT, step != 0);
|
||||
return range::len(start, stop, step);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
using namespace range;
|
||||
|
||||
SliceIndex __nac3_range_slice_len(const SliceIndex start, const SliceIndex end, const SliceIndex step) {
|
||||
return len(start, end, step);
|
||||
}
|
||||
}
|
156
nac3core/irrt/irrt/slice.hpp
Normal file
156
nac3core/irrt/irrt/slice.hpp
Normal file
@ -0,0 +1,156 @@
|
||||
#pragma once
|
||||
|
||||
#include "irrt/debug.hpp"
|
||||
#include "irrt/exception.hpp"
|
||||
#include "irrt/int_types.hpp"
|
||||
#include "irrt/math_util.hpp"
|
||||
#include "irrt/range.hpp"
|
||||
|
||||
namespace {
|
||||
namespace slice {
|
||||
/**
|
||||
* @brief Resolve a possibly negative index in a list of a known length.
|
||||
*
|
||||
* Returns -1 if the resolved index is out of the list's bounds.
|
||||
*/
|
||||
template<typename T>
|
||||
T resolve_index_in_length(T length, T index) {
|
||||
T resolved = index < 0 ? length + index : index;
|
||||
if (0 <= resolved && resolved < length) {
|
||||
return resolved;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resolve a slice as a range.
|
||||
*
|
||||
* This is equivalent to `range(*slice(start, stop, step).indices(length))` in Python.
|
||||
*/
|
||||
template<typename T>
|
||||
void indices(bool start_defined,
|
||||
T start,
|
||||
bool stop_defined,
|
||||
T stop,
|
||||
bool step_defined,
|
||||
T step,
|
||||
T length,
|
||||
T* range_start,
|
||||
T* range_stop,
|
||||
T* range_step) {
|
||||
// Reference: https://github.com/python/cpython/blob/main/Objects/sliceobject.c#L388
|
||||
*range_step = step_defined ? step : 1;
|
||||
bool step_is_negative = *range_step < 0;
|
||||
|
||||
T lower, upper;
|
||||
if (step_is_negative) {
|
||||
lower = -1;
|
||||
upper = length - 1;
|
||||
} else {
|
||||
lower = 0;
|
||||
upper = length;
|
||||
}
|
||||
|
||||
if (start_defined) {
|
||||
*range_start = start < 0 ? max(lower, start + length) : min(upper, start);
|
||||
} else {
|
||||
*range_start = step_is_negative ? upper : lower;
|
||||
}
|
||||
|
||||
if (stop_defined) {
|
||||
*range_stop = stop < 0 ? max(lower, stop + length) : min(upper, stop);
|
||||
} else {
|
||||
*range_stop = step_is_negative ? lower : upper;
|
||||
}
|
||||
}
|
||||
} // namespace slice
|
||||
|
||||
/**
|
||||
* @brief A Python-like slice with **unresolved** indices.
|
||||
*/
|
||||
template<typename T>
|
||||
struct Slice {
|
||||
bool start_defined;
|
||||
T start;
|
||||
|
||||
bool stop_defined;
|
||||
T stop;
|
||||
|
||||
bool step_defined;
|
||||
T step;
|
||||
|
||||
Slice() { this->reset(); }
|
||||
|
||||
void reset() {
|
||||
this->start_defined = false;
|
||||
this->stop_defined = false;
|
||||
this->step_defined = false;
|
||||
}
|
||||
|
||||
void set_start(T start) {
|
||||
this->start_defined = true;
|
||||
this->start = start;
|
||||
}
|
||||
|
||||
void set_stop(T stop) {
|
||||
this->stop_defined = true;
|
||||
this->stop = stop;
|
||||
}
|
||||
|
||||
void set_step(T step) {
|
||||
this->step_defined = true;
|
||||
this->step = step;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Resolve this slice as a range.
|
||||
*
|
||||
* In Python, this would be `range(*slice(start, stop, step).indices(length))`.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
Range<T> indices(T length) {
|
||||
// Reference:
|
||||
// https://github.com/python/cpython/blob/main/Objects/sliceobject.c#L388
|
||||
debug_assert(SizeT, length >= 0);
|
||||
|
||||
Range<T> result;
|
||||
slice::indices(start_defined, start, stop_defined, stop, step_defined, step, length, &result.start,
|
||||
&result.stop, &result.step);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Like `.indices()` but with assertions.
|
||||
*/
|
||||
template<typename SizeT>
|
||||
Range<T> indices_checked(T length) {
|
||||
// TODO: Switch to `SizeT length`
|
||||
|
||||
if (length < 0) {
|
||||
raise_exception(SizeT, EXN_VALUE_ERROR, "length should not be negative, got {0}", length, NO_PARAM,
|
||||
NO_PARAM);
|
||||
}
|
||||
|
||||
if (this->step_defined && this->step == 0) {
|
||||
raise_exception(SizeT, EXN_VALUE_ERROR, "slice step cannot be zero", NO_PARAM, NO_PARAM, NO_PARAM);
|
||||
}
|
||||
|
||||
return this->indices<SizeT>(length);
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
SliceIndex __nac3_slice_index_bound(SliceIndex i, const SliceIndex len) {
|
||||
if (i < 0) {
|
||||
i = len + i;
|
||||
}
|
||||
if (i < 0) {
|
||||
return 0;
|
||||
} else if (i > len) {
|
||||
return len;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
21
nac3core/nac3core_derive/Cargo.toml
Normal file
21
nac3core/nac3core_derive/Cargo.toml
Normal file
@ -0,0 +1,21 @@
|
||||
[package]
|
||||
name = "nac3core_derive"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[[test]]
|
||||
name = "structfields_tests"
|
||||
path = "tests/structfields_test.rs"
|
||||
|
||||
[dev-dependencies]
|
||||
nac3core = { path = ".." }
|
||||
trybuild = { version = "1.0", features = ["diff"] }
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = "1.0"
|
||||
proc-macro-error = "1.0"
|
||||
syn = "2.0"
|
||||
quote = "1.0"
|
320
nac3core/nac3core_derive/src/lib.rs
Normal file
320
nac3core/nac3core_derive/src/lib.rs
Normal file
@ -0,0 +1,320 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro_error::{abort, proc_macro_error};
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse_macro_input, spanned::Spanned, Data, DataStruct, Expr, ExprField, ExprMethodCall,
|
||||
ExprPath, GenericArgument, Ident, LitStr, Path, PathArguments, Type, TypePath,
|
||||
};
|
||||
|
||||
/// Extracts all generic arguments of a [`Type`] into a [`Vec`].
|
||||
///
|
||||
/// Returns [`Some`] of a possibly-empty [`Vec`] if the path of `ty` matches with
|
||||
/// `expected_ty_name`, otherwise returns [`None`].
|
||||
fn extract_generic_args(expected_ty_name: &'static str, ty: &Type) -> Option<Vec<GenericArgument>> {
|
||||
let Type::Path(TypePath { qself: None, path, .. }) = ty else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let segments = &path.segments;
|
||||
if segments.len() != 1 {
|
||||
return None;
|
||||
};
|
||||
|
||||
let segment = segments.iter().next().unwrap();
|
||||
if segment.ident != expected_ty_name {
|
||||
return None;
|
||||
}
|
||||
|
||||
let PathArguments::AngleBracketed(path_args) = &segment.arguments else {
|
||||
return Some(Vec::new());
|
||||
};
|
||||
let args = &path_args.args;
|
||||
|
||||
Some(args.iter().cloned().collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
/// Maps a `path` matching one of the `target_idents` into the `replacement` [`Ident`].
|
||||
fn map_path_to_ident(path: &Path, target_idents: &[&str], replacement: &str) -> Option<Ident> {
|
||||
path.require_ident()
|
||||
.ok()
|
||||
.filter(|ident| target_idents.iter().any(|target| ident == target))
|
||||
.map(|ident| Ident::new(replacement, ident.span()))
|
||||
}
|
||||
|
||||
/// Extracts the left-hand side of a dot-expression.
|
||||
fn extract_dot_operand(expr: &Expr) -> Option<&Expr> {
|
||||
match expr {
|
||||
Expr::MethodCall(ExprMethodCall { receiver: operand, .. })
|
||||
| Expr::Field(ExprField { base: operand, .. }) => Some(operand),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Replaces the top-level receiver of a dot-expression with an [`Ident`], returning `Some(&mut expr)` if the
|
||||
/// replacement is performed.
|
||||
///
|
||||
/// The top-level receiver is the left-most receiver expression, e.g. the top-level receiver of `a.b.c.foo()` is `a`.
|
||||
fn replace_top_level_receiver(expr: &mut Expr, ident: Ident) -> Option<&mut Expr> {
|
||||
if let Expr::MethodCall(ExprMethodCall { receiver: operand, .. })
|
||||
| Expr::Field(ExprField { base: operand, .. }) = expr
|
||||
{
|
||||
return if extract_dot_operand(operand).is_some() {
|
||||
if replace_top_level_receiver(operand, ident).is_some() {
|
||||
Some(expr)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
*operand = Box::new(Expr::Path(ExprPath {
|
||||
attrs: Vec::default(),
|
||||
qself: None,
|
||||
path: ident.into(),
|
||||
}));
|
||||
|
||||
Some(expr)
|
||||
};
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Iterates all operands to the left-hand side of the `.` of an [expression][`Expr`], i.e. the container operand of all
|
||||
/// [`Expr::Field`] and the receiver operand of all [`Expr::MethodCall`].
|
||||
///
|
||||
/// The iterator will return the operand expressions in reverse order of appearance. For example, `a.b.c.func()` will
|
||||
/// return `vec![c, b, a]`.
|
||||
fn iter_dot_operands(expr: &Expr) -> impl Iterator<Item = &Expr> {
|
||||
let mut o = extract_dot_operand(expr);
|
||||
|
||||
std::iter::from_fn(move || {
|
||||
let this = o;
|
||||
o = o.as_ref().and_then(|o| extract_dot_operand(o));
|
||||
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
/// Normalizes a value expression for use when creating an instance of this structure, returning a
|
||||
/// [`proc_macro2::TokenStream`] of tokens representing the normalized expression.
|
||||
fn normalize_value_expr(expr: &Expr) -> proc_macro2::TokenStream {
|
||||
match &expr {
|
||||
Expr::Path(ExprPath { qself: None, path, .. }) => {
|
||||
if let Some(ident) = map_path_to_ident(path, &["usize", "size_t"], "llvm_usize") {
|
||||
quote! { #ident }
|
||||
} else {
|
||||
abort!(
|
||||
path,
|
||||
format!(
|
||||
"Expected one of `size_t`, `usize`, or an implicit call expression in #[value_type(...)], found {}",
|
||||
quote!(#expr).to_string(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Expr::Call(_) => {
|
||||
quote! { ctx.#expr }
|
||||
}
|
||||
|
||||
Expr::MethodCall(_) => {
|
||||
let base_receiver = iter_dot_operands(expr).last();
|
||||
|
||||
match base_receiver {
|
||||
// `usize.{...}`, `size_t.{...}` -> Rewrite the identifiers to `llvm_usize`
|
||||
Some(Expr::Path(ExprPath { qself: None, path, .. }))
|
||||
if map_path_to_ident(path, &["usize", "size_t"], "llvm_usize").is_some() =>
|
||||
{
|
||||
let ident =
|
||||
map_path_to_ident(path, &["usize", "size_t"], "llvm_usize").unwrap();
|
||||
|
||||
let mut expr = expr.clone();
|
||||
let expr = replace_top_level_receiver(&mut expr, ident).unwrap();
|
||||
|
||||
quote!(#expr)
|
||||
}
|
||||
|
||||
// `ctx.{...}`, `context.{...}` -> Rewrite the identifiers to `ctx`
|
||||
Some(Expr::Path(ExprPath { qself: None, path, .. }))
|
||||
if map_path_to_ident(path, &["ctx", "context"], "ctx").is_some() =>
|
||||
{
|
||||
let ident = map_path_to_ident(path, &["ctx", "context"], "ctx").unwrap();
|
||||
|
||||
let mut expr = expr.clone();
|
||||
let expr = replace_top_level_receiver(&mut expr, ident).unwrap();
|
||||
|
||||
quote!(#expr)
|
||||
}
|
||||
|
||||
// No reserved identifier prefix -> Prepend `ctx.` to the entire expression
|
||||
_ => quote! { ctx.#expr },
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
abort!(
|
||||
expr,
|
||||
format!(
|
||||
"Expected one of `size_t`, `usize`, or an implicit call expression in #[value_type(...)], found {}",
|
||||
quote!(#expr).to_string(),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Derives an implementation of `codegen::types::structure::StructFields`.
|
||||
///
|
||||
/// The benefit of using `#[derive(StructFields)]` is that all index- or order-dependent logic required by
|
||||
/// `impl StructFields` is automatically generated by this implementation, including the field index as required by
|
||||
/// `StructField::new` and the fields as returned by `StructFields::to_vec`.
|
||||
///
|
||||
/// # Prerequisites
|
||||
///
|
||||
/// In order to derive from [`StructFields`], you must implement (or derive) [`Eq`] and [`Copy`] as required by
|
||||
/// `StructFields`.
|
||||
///
|
||||
/// Moreover, `#[derive(StructFields)]` can only be used for `struct`s with named fields, and may only contain fields
|
||||
/// with either `StructField` or [`PhantomData`] types.
|
||||
///
|
||||
/// # Attributes for [`StructFields`]
|
||||
///
|
||||
/// Each `StructField` field must be declared with the `#[value_type(...)]` attribute. The argument of `value_type`
|
||||
/// accepts one of the following:
|
||||
///
|
||||
/// - An expression returning an instance of `inkwell::types::BasicType` (with or without the receiver `ctx`/`context`).
|
||||
/// For example, `context.i8_type()`, `ctx.i8_type()`, and `i8_type()` all refer to `i8`.
|
||||
/// - The reserved identifiers `usize` and `size_t` referring to an `inkwell::types::IntType` of the platform-dependent
|
||||
/// integer size. `usize` and `size_t` can also be used as the receiver to other method calls, e.g.
|
||||
/// `usize.array_type(3)`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// The following is an example of an LLVM slice implemented using `#[derive(StructFields)]`.
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// use nac3core::{
|
||||
/// codegen::types::structure::StructField,
|
||||
/// inkwell::{
|
||||
/// values::{IntValue, PointerValue},
|
||||
/// AddressSpace,
|
||||
/// },
|
||||
/// };
|
||||
/// use nac3core_derive::StructFields;
|
||||
///
|
||||
/// // All classes that implement StructFields must also implement Eq and Copy
|
||||
/// #[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
/// pub struct SliceValue<'ctx> {
|
||||
/// // Declares ptr have a value type of i8*
|
||||
/// //
|
||||
/// // Can also be written as `ctx.i8_type().ptr_type(...)` or `context.i8_type().ptr_type(...)`
|
||||
/// #[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
/// ptr: StructField<'ctx, PointerValue<'ctx>>,
|
||||
///
|
||||
/// // Declares len have a value type of usize, depending on the target compilation platform
|
||||
/// #[value_type(usize)]
|
||||
/// len: StructField<'ctx, IntValue<'ctx>>,
|
||||
/// }
|
||||
/// ```
|
||||
#[proc_macro_derive(StructFields, attributes(value_type))]
|
||||
#[proc_macro_error]
|
||||
pub fn derive(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as syn::DeriveInput);
|
||||
let ident = &input.ident;
|
||||
|
||||
let Data::Struct(DataStruct { fields, .. }) = &input.data else {
|
||||
abort!(input, "Only structs with named fields are supported");
|
||||
};
|
||||
if let Err(err_span) =
|
||||
fields
|
||||
.iter()
|
||||
.try_for_each(|field| if field.ident.is_some() { Ok(()) } else { Err(field.span()) })
|
||||
{
|
||||
abort!(err_span, "Only structs with named fields are supported");
|
||||
};
|
||||
|
||||
// Check if struct<'ctx>
|
||||
if input.generics.params.len() != 1 {
|
||||
abort!(input.generics, "Expected exactly 1 generic parameter")
|
||||
}
|
||||
|
||||
let phantom_info = fields
|
||||
.iter()
|
||||
.filter(|field| extract_generic_args("PhantomData", &field.ty).is_some())
|
||||
.map(|field| field.ident.as_ref().unwrap())
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let field_info = fields
|
||||
.iter()
|
||||
.filter(|field| extract_generic_args("PhantomData", &field.ty).is_none())
|
||||
.map(|field| {
|
||||
let ident = field.ident.as_ref().unwrap();
|
||||
let ty = &field.ty;
|
||||
|
||||
let Some(_) = extract_generic_args("StructField", ty) else {
|
||||
abort!(field, "Only StructField and PhantomData are allowed")
|
||||
};
|
||||
|
||||
let attrs = &field.attrs;
|
||||
let Some(value_type_attr) =
|
||||
attrs.iter().find(|attr| attr.path().is_ident("value_type"))
|
||||
else {
|
||||
abort!(field, "Expected #[value_type(...)] attribute for field");
|
||||
};
|
||||
|
||||
let Ok(value_type_expr) = value_type_attr.parse_args::<Expr>() else {
|
||||
abort!(value_type_attr, "Expected expression in #[value_type(...)]");
|
||||
};
|
||||
|
||||
let value_expr_toks = normalize_value_expr(&value_type_expr);
|
||||
|
||||
(ident.clone(), value_expr_toks)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// `<*>::new` impl of `StructField` and `PhantomData` for `StructFields::new`
|
||||
let phantoms_create = phantom_info
|
||||
.iter()
|
||||
.map(|id| quote! { #id: ::std::marker::PhantomData })
|
||||
.collect::<Vec<_>>();
|
||||
let fields_create = field_info
|
||||
.iter()
|
||||
.map(|(id, ty)| {
|
||||
let id_lit = LitStr::new(&id.to_string(), id.span());
|
||||
quote! {
|
||||
#id: ::nac3core::codegen::types::structure::StructField::create(
|
||||
&mut counter,
|
||||
#id_lit,
|
||||
#ty,
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// `.into()` impl of `StructField` for `StructFields::to_vec`
|
||||
let fields_into =
|
||||
field_info.iter().map(|(id, _)| quote! { self.#id.into() }).collect::<Vec<_>>();
|
||||
|
||||
let impl_block = quote! {
|
||||
impl<'ctx> ::nac3core::codegen::types::structure::StructFields<'ctx> for #ident<'ctx> {
|
||||
fn new(ctx: impl ::nac3core::inkwell::context::AsContextRef<'ctx>, llvm_usize: ::nac3core::inkwell::types::IntType<'ctx>) -> Self {
|
||||
let ctx = unsafe { ::nac3core::inkwell::context::ContextRef::new(ctx.as_ctx_ref()) };
|
||||
|
||||
let mut counter = ::nac3core::codegen::types::structure::FieldIndexCounter::default();
|
||||
|
||||
#ident {
|
||||
#(#fields_create),*
|
||||
#(#phantoms_create),*
|
||||
}
|
||||
}
|
||||
|
||||
fn to_vec(&self) -> ::std::vec::Vec<(&'static str, ::nac3core::inkwell::types::BasicTypeEnum<'ctx>)> {
|
||||
vec![
|
||||
#(#fields_into),*
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
impl_block.into()
|
||||
}
|
9
nac3core/nac3core_derive/tests/structfields_empty.rs
Normal file
9
nac3core/nac3core_derive/tests/structfields_empty.rs
Normal file
@ -0,0 +1,9 @@
|
||||
use nac3core_derive::StructFields;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct EmptyValue<'ctx> {
|
||||
_phantom: PhantomData<&'ctx ()>,
|
||||
}
|
||||
|
||||
fn main() {}
|
20
nac3core/nac3core_derive/tests/structfields_ndarray.rs
Normal file
20
nac3core/nac3core_derive/tests/structfields_ndarray.rs
Normal file
@ -0,0 +1,20 @@
|
||||
use nac3core::{
|
||||
codegen::types::structure::StructField,
|
||||
inkwell::{
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
},
|
||||
};
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct NDArrayValue<'ctx> {
|
||||
#[value_type(usize)]
|
||||
ndims: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(usize.ptr_type(AddressSpace::default()))]
|
||||
shape: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
data: StructField<'ctx, PointerValue<'ctx>>,
|
||||
}
|
||||
|
||||
fn main() {}
|
18
nac3core/nac3core_derive/tests/structfields_slice.rs
Normal file
18
nac3core/nac3core_derive/tests/structfields_slice.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use nac3core::{
|
||||
codegen::types::structure::StructField,
|
||||
inkwell::{
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
},
|
||||
};
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct SliceValue<'ctx> {
|
||||
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
ptr: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
len: StructField<'ctx, IntValue<'ctx>>,
|
||||
}
|
||||
|
||||
fn main() {}
|
18
nac3core/nac3core_derive/tests/structfields_slice_context.rs
Normal file
18
nac3core/nac3core_derive/tests/structfields_slice_context.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use nac3core::{
|
||||
codegen::types::structure::StructField,
|
||||
inkwell::{
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
},
|
||||
};
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct SliceValue<'ctx> {
|
||||
#[value_type(context.i8_type().ptr_type(AddressSpace::default()))]
|
||||
ptr: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
len: StructField<'ctx, IntValue<'ctx>>,
|
||||
}
|
||||
|
||||
fn main() {}
|
18
nac3core/nac3core_derive/tests/structfields_slice_ctx.rs
Normal file
18
nac3core/nac3core_derive/tests/structfields_slice_ctx.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use nac3core::{
|
||||
codegen::types::structure::StructField,
|
||||
inkwell::{
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
},
|
||||
};
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct SliceValue<'ctx> {
|
||||
#[value_type(ctx.i8_type().ptr_type(AddressSpace::default()))]
|
||||
ptr: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
len: StructField<'ctx, IntValue<'ctx>>,
|
||||
}
|
||||
|
||||
fn main() {}
|
18
nac3core/nac3core_derive/tests/structfields_slice_sizet.rs
Normal file
18
nac3core/nac3core_derive/tests/structfields_slice_sizet.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use nac3core::{
|
||||
codegen::types::structure::StructField,
|
||||
inkwell::{
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
},
|
||||
};
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct SliceValue<'ctx> {
|
||||
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
ptr: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(size_t)]
|
||||
len: StructField<'ctx, IntValue<'ctx>>,
|
||||
}
|
||||
|
||||
fn main() {}
|
10
nac3core/nac3core_derive/tests/structfields_test.rs
Normal file
10
nac3core/nac3core_derive/tests/structfields_test.rs
Normal file
@ -0,0 +1,10 @@
|
||||
#[test]
|
||||
fn test_parse_empty() {
|
||||
let t = trybuild::TestCases::new();
|
||||
t.pass("tests/structfields_empty.rs");
|
||||
t.pass("tests/structfields_slice.rs");
|
||||
t.pass("tests/structfields_slice_ctx.rs");
|
||||
t.pass("tests/structfields_slice_context.rs");
|
||||
t.pass("tests/structfields_slice_sizet.rs");
|
||||
t.pass("tests/structfields_ndarray.rs");
|
||||
}
|
2336
nac3core/src/codegen/builtin_fns.rs
Normal file
2336
nac3core/src/codegen/builtin_fns.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,15 +1,20 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use nac3parser::ast::StrRef;
|
||||
|
||||
use crate::{
|
||||
symbol_resolver::SymbolValue,
|
||||
toplevel::DefinitionId,
|
||||
typecheck::{
|
||||
type_inferencer::PrimitiveStore,
|
||||
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
|
||||
typedef::{
|
||||
into_var_map, FunSignature, FuncArg, Type, TypeEnum, TypeVar, TypeVarId, Unifier,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use nac3parser::ast::StrRef;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub struct ConcreteTypeStore {
|
||||
store: Vec<ConcreteTypeEnum>,
|
||||
}
|
||||
@ -22,6 +27,7 @@ pub struct ConcreteFuncArg {
|
||||
pub name: StrRef,
|
||||
pub ty: ConcreteType,
|
||||
pub default_value: Option<SymbolValue>,
|
||||
pub is_vararg: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@ -43,14 +49,12 @@ pub enum ConcreteTypeEnum {
|
||||
TPrimitive(Primitive),
|
||||
TTuple {
|
||||
ty: Vec<ConcreteType>,
|
||||
},
|
||||
TList {
|
||||
ty: ConcreteType,
|
||||
is_vararg_ctx: bool,
|
||||
},
|
||||
TObj {
|
||||
obj_id: DefinitionId,
|
||||
fields: HashMap<StrRef, (ConcreteType, bool)>,
|
||||
params: HashMap<u32, ConcreteType>,
|
||||
params: IndexMap<TypeVarId, ConcreteType>,
|
||||
},
|
||||
TVirtual {
|
||||
ty: ConcreteType,
|
||||
@ -58,11 +62,15 @@ pub enum ConcreteTypeEnum {
|
||||
TFunc {
|
||||
args: Vec<ConcreteFuncArg>,
|
||||
ret: ConcreteType,
|
||||
vars: HashMap<u32, ConcreteType>,
|
||||
vars: HashMap<TypeVarId, ConcreteType>,
|
||||
},
|
||||
TLiteral {
|
||||
values: Vec<SymbolValue>,
|
||||
},
|
||||
}
|
||||
|
||||
impl ConcreteTypeStore {
|
||||
#[must_use]
|
||||
pub fn new() -> ConcreteTypeStore {
|
||||
ConcreteTypeStore {
|
||||
store: vec![
|
||||
@ -80,6 +88,7 @@ impl ConcreteTypeStore {
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get(&self, cty: ConcreteType) -> &ConcreteTypeEnum {
|
||||
&self.store[cty.0]
|
||||
}
|
||||
@ -97,8 +106,16 @@ impl ConcreteTypeStore {
|
||||
.iter()
|
||||
.map(|arg| ConcreteFuncArg {
|
||||
name: arg.name,
|
||||
ty: self.from_unifier_type(unifier, primitives, arg.ty, cache),
|
||||
ty: if arg.is_vararg {
|
||||
let tuple_ty = unifier
|
||||
.add_ty(TypeEnum::TTuple { ty: vec![arg.ty], is_vararg_ctx: true });
|
||||
|
||||
self.from_unifier_type(unifier, primitives, tuple_ty, cache)
|
||||
} else {
|
||||
self.from_unifier_type(unifier, primitives, arg.ty, cache)
|
||||
},
|
||||
default_value: arg.default_value.clone(),
|
||||
is_vararg: arg.is_vararg,
|
||||
})
|
||||
.collect(),
|
||||
ret: self.from_unifier_type(unifier, primitives, signature.ret, cache),
|
||||
@ -153,14 +170,12 @@ impl ConcreteTypeStore {
|
||||
cache.insert(ty, None);
|
||||
let ty_enum = unifier.get_ty(ty);
|
||||
let result = match &*ty_enum {
|
||||
TypeEnum::TTuple { ty } => ConcreteTypeEnum::TTuple {
|
||||
TypeEnum::TTuple { ty, is_vararg_ctx } => ConcreteTypeEnum::TTuple {
|
||||
ty: ty
|
||||
.iter()
|
||||
.map(|t| self.from_unifier_type(unifier, primitives, *t, cache))
|
||||
.collect(),
|
||||
},
|
||||
TypeEnum::TList { ty } => ConcreteTypeEnum::TList {
|
||||
ty: self.from_unifier_type(unifier, primitives, *ty, cache),
|
||||
is_vararg_ctx: *is_vararg_ctx,
|
||||
},
|
||||
TypeEnum::TObj { obj_id, fields, params } => ConcreteTypeEnum::TObj {
|
||||
obj_id: *obj_id,
|
||||
@ -194,9 +209,12 @@ impl ConcreteTypeStore {
|
||||
ty: self.from_unifier_type(unifier, primitives, *ty, cache),
|
||||
},
|
||||
TypeEnum::TFunc(signature) => {
|
||||
self.from_signature(unifier, primitives, &*signature, cache)
|
||||
self.from_signature(unifier, primitives, signature, cache)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
TypeEnum::TLiteral { values, .. } => {
|
||||
ConcreteTypeEnum::TLiteral { values: values.clone() }
|
||||
}
|
||||
_ => unreachable!("{:?}", ty_enum.get_type_name()),
|
||||
};
|
||||
let index = if let Some(ConcreteType(index)) = cache.get(&ty).unwrap() {
|
||||
self.store[*index] = result;
|
||||
@ -221,7 +239,7 @@ impl ConcreteTypeStore {
|
||||
return if let Some(ty) = ty {
|
||||
*ty
|
||||
} else {
|
||||
*ty = Some(unifier.get_dummy_var().0);
|
||||
*ty = Some(unifier.get_dummy_var().ty);
|
||||
ty.unwrap()
|
||||
};
|
||||
}
|
||||
@ -243,15 +261,13 @@ impl ConcreteTypeStore {
|
||||
*cache.get_mut(&cty).unwrap() = Some(ty);
|
||||
return ty;
|
||||
}
|
||||
ConcreteTypeEnum::TTuple { ty } => TypeEnum::TTuple {
|
||||
ConcreteTypeEnum::TTuple { ty, is_vararg_ctx } => TypeEnum::TTuple {
|
||||
ty: ty
|
||||
.iter()
|
||||
.map(|cty| self.to_unifier_type(unifier, primitives, *cty, cache))
|
||||
.collect(),
|
||||
is_vararg_ctx: *is_vararg_ctx,
|
||||
},
|
||||
ConcreteTypeEnum::TList { ty } => {
|
||||
TypeEnum::TList { ty: self.to_unifier_type(unifier, primitives, *ty, cache) }
|
||||
}
|
||||
ConcreteTypeEnum::TVirtual { ty } => {
|
||||
TypeEnum::TVirtual { ty: self.to_unifier_type(unifier, primitives, *ty, cache) }
|
||||
}
|
||||
@ -263,10 +279,10 @@ impl ConcreteTypeStore {
|
||||
(*name, (self.to_unifier_type(unifier, primitives, cty.0, cache), cty.1))
|
||||
})
|
||||
.collect::<HashMap<_, _>>(),
|
||||
params: params
|
||||
.iter()
|
||||
.map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache)))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
params: into_var_map(params.iter().map(|(&id, cty)| {
|
||||
let ty = self.to_unifier_type(unifier, primitives, *cty, cache);
|
||||
TypeVar { id, ty }
|
||||
})),
|
||||
},
|
||||
ConcreteTypeEnum::TFunc { args, ret, vars } => TypeEnum::TFunc(FunSignature {
|
||||
args: args
|
||||
@ -275,14 +291,18 @@ impl ConcreteTypeStore {
|
||||
name: arg.name,
|
||||
ty: self.to_unifier_type(unifier, primitives, arg.ty, cache),
|
||||
default_value: arg.default_value.clone(),
|
||||
is_vararg: false,
|
||||
})
|
||||
.collect(),
|
||||
ret: self.to_unifier_type(unifier, primitives, *ret, cache),
|
||||
vars: vars
|
||||
.iter()
|
||||
.map(|(id, cty)| (*id, self.to_unifier_type(unifier, primitives, *cty, cache)))
|
||||
.collect::<HashMap<_, _>>(),
|
||||
vars: into_var_map(vars.iter().map(|(&id, cty)| {
|
||||
let ty = self.to_unifier_type(unifier, primitives, *cty, cache);
|
||||
TypeVar { id, ty }
|
||||
})),
|
||||
}),
|
||||
ConcreteTypeEnum::TLiteral { values, .. } => {
|
||||
TypeEnum::TLiteral { values: values.clone(), loc: None }
|
||||
}
|
||||
};
|
||||
let result = unifier.add_ty(result);
|
||||
if let Some(ty) = cache.get(&cty).unwrap() {
|
||||
|
File diff suppressed because it is too large
Load Diff
193
nac3core/src/codegen/extern_fns.rs
Normal file
193
nac3core/src/codegen/extern_fns.rs
Normal file
@ -0,0 +1,193 @@
|
||||
use inkwell::{
|
||||
attributes::{Attribute, AttributeLoc},
|
||||
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue},
|
||||
};
|
||||
use itertools::Either;
|
||||
|
||||
use super::CodeGenContext;
|
||||
|
||||
/// Macro to generate extern function
|
||||
/// Both function return type and function parameter type are `FloatValue`
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `unary/binary`: Whether the extern function requires one (unary) or two (binary) operands
|
||||
/// * `$fn_name:ident`: The identifier of the rust function to be generated
|
||||
/// * `$extern_fn:literal`: Name of underlying extern function
|
||||
///
|
||||
/// Optional Arguments:
|
||||
/// * `$(,$attributes:literal)*)`: Attributes linked with the extern function.
|
||||
/// The default attributes are "mustprogress", "nofree", "nounwind", "willreturn", and "writeonly".
|
||||
/// These will be used unless other attributes are specified
|
||||
/// * `$(,$args:ident)*`: Operands of the extern function
|
||||
/// The data type of these operands will be set to `FloatValue`
|
||||
///
|
||||
macro_rules! generate_extern_fn {
|
||||
("unary", $fn_name:ident, $extern_fn:literal) => {
|
||||
generate_extern_fn!($fn_name, $extern_fn, arg, "mustprogress", "nofree", "nounwind", "willreturn", "writeonly");
|
||||
};
|
||||
("unary", $fn_name:ident, $extern_fn:literal $(,$attributes:literal)*) => {
|
||||
generate_extern_fn!($fn_name, $extern_fn, arg $(,$attributes)*);
|
||||
};
|
||||
("binary", $fn_name:ident, $extern_fn:literal) => {
|
||||
generate_extern_fn!($fn_name, $extern_fn, arg1, arg2, "mustprogress", "nofree", "nounwind", "willreturn", "writeonly");
|
||||
};
|
||||
("binary", $fn_name:ident, $extern_fn:literal $(,$attributes:literal)*) => {
|
||||
generate_extern_fn!($fn_name, $extern_fn, arg1, arg2 $(,$attributes)*);
|
||||
};
|
||||
($fn_name:ident, $extern_fn:literal $(,$args:ident)* $(,$attributes:literal)*) => {
|
||||
#[doc = concat!("Invokes the [`", stringify!($extern_fn), "`](https://en.cppreference.com/w/c/numeric/math/", stringify!($llvm_name), ") function." )]
|
||||
pub fn $fn_name<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>
|
||||
$(,$args: FloatValue<'ctx>)*,
|
||||
name: Option<&str>,
|
||||
) -> FloatValue<'ctx> {
|
||||
const FN_NAME: &str = $extern_fn;
|
||||
|
||||
let llvm_f64 = ctx.ctx.f64_type();
|
||||
$(debug_assert_eq!($args.get_type(), llvm_f64);)*
|
||||
|
||||
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
|
||||
let fn_type = llvm_f64.fn_type(&[$($args.get_type().into()),*], false);
|
||||
let func = ctx.module.add_function(FN_NAME, fn_type, None);
|
||||
for attr in [$($attributes),*] {
|
||||
func.add_attribute(
|
||||
AttributeLoc::Function,
|
||||
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
|
||||
);
|
||||
}
|
||||
func
|
||||
});
|
||||
|
||||
ctx.builder
|
||||
.build_call(extern_fn, &[$($args.into()),*], name.unwrap_or_default())
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_float_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_extern_fn!("unary", call_tan, "tan");
|
||||
generate_extern_fn!("unary", call_asin, "asin");
|
||||
generate_extern_fn!("unary", call_acos, "acos");
|
||||
generate_extern_fn!("unary", call_atan, "atan");
|
||||
generate_extern_fn!("unary", call_sinh, "sinh");
|
||||
generate_extern_fn!("unary", call_cosh, "cosh");
|
||||
generate_extern_fn!("unary", call_tanh, "tanh");
|
||||
generate_extern_fn!("unary", call_asinh, "asinh");
|
||||
generate_extern_fn!("unary", call_acosh, "acosh");
|
||||
generate_extern_fn!("unary", call_atanh, "atanh");
|
||||
generate_extern_fn!("unary", call_expm1, "expm1");
|
||||
generate_extern_fn!(
|
||||
"unary",
|
||||
call_cbrt,
|
||||
"cbrt",
|
||||
"mustprogress",
|
||||
"nofree",
|
||||
"nosync",
|
||||
"nounwind",
|
||||
"readonly",
|
||||
"willreturn"
|
||||
);
|
||||
generate_extern_fn!("unary", call_erf, "erf", "nounwind");
|
||||
generate_extern_fn!("unary", call_erfc, "erfc", "nounwind");
|
||||
generate_extern_fn!("unary", call_j1, "j1", "nounwind");
|
||||
|
||||
generate_extern_fn!("binary", call_atan2, "atan2");
|
||||
generate_extern_fn!("binary", call_hypot, "hypot", "nounwind");
|
||||
generate_extern_fn!("binary", call_nextafter, "nextafter", "nounwind");
|
||||
|
||||
/// Invokes the [`ldexp`](https://en.cppreference.com/w/c/numeric/math/ldexp) function.
|
||||
pub fn call_ldexp<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
arg: FloatValue<'ctx>,
|
||||
exp: IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> FloatValue<'ctx> {
|
||||
const FN_NAME: &str = "ldexp";
|
||||
|
||||
let llvm_f64 = ctx.ctx.f64_type();
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
debug_assert_eq!(arg.get_type(), llvm_f64);
|
||||
debug_assert_eq!(exp.get_type(), llvm_i32);
|
||||
|
||||
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
|
||||
let fn_type = llvm_f64.fn_type(&[llvm_f64.into(), llvm_i32.into()], false);
|
||||
let func = ctx.module.add_function(FN_NAME, fn_type, None);
|
||||
for attr in ["mustprogress", "nofree", "nounwind", "willreturn"] {
|
||||
func.add_attribute(
|
||||
AttributeLoc::Function,
|
||||
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
|
||||
);
|
||||
}
|
||||
|
||||
func
|
||||
});
|
||||
|
||||
ctx.builder
|
||||
.build_call(extern_fn, &[arg.into(), exp.into()], name.unwrap_or_default())
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_float_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Macro to generate `np_linalg` and `sp_linalg` functions
|
||||
/// The function takes as input `NDArray` and returns ()
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `$fn_name:ident`: The identifier of the rust function to be generated
|
||||
/// * `$extern_fn:literal`: Name of underlying extern function
|
||||
/// * (2/3/4): Number of `NDArray` that function takes as input
|
||||
///
|
||||
/// Note:
|
||||
/// The operands and resulting `NDArray` are both passed as input to the funcion
|
||||
/// It is the responsibility of caller to ensure that output `NDArray` is properly allocated on stack
|
||||
/// The function changes the content of the output `NDArray` in-place
|
||||
macro_rules! generate_linalg_extern_fn {
|
||||
($fn_name:ident, $extern_fn:literal, 2) => {
|
||||
generate_linalg_extern_fn!($fn_name, $extern_fn, mat1, mat2);
|
||||
};
|
||||
($fn_name:ident, $extern_fn:literal, 3) => {
|
||||
generate_linalg_extern_fn!($fn_name, $extern_fn, mat1, mat2, mat3);
|
||||
};
|
||||
($fn_name:ident, $extern_fn:literal, 4) => {
|
||||
generate_linalg_extern_fn!($fn_name, $extern_fn, mat1, mat2, mat3, mat4);
|
||||
};
|
||||
($fn_name:ident, $extern_fn:literal $(,$input_matrix:ident)*) => {
|
||||
#[doc = concat!("Invokes the linalg `", stringify!($extern_fn), " function." )]
|
||||
pub fn $fn_name<'ctx>(
|
||||
ctx: &mut CodeGenContext<'ctx, '_>
|
||||
$(,$input_matrix: BasicValueEnum<'ctx>)*,
|
||||
name: Option<&str>,
|
||||
){
|
||||
const FN_NAME: &str = $extern_fn;
|
||||
let extern_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
|
||||
let fn_type = ctx.ctx.void_type().fn_type(&[$($input_matrix.get_type().into()),*], false);
|
||||
|
||||
let func = ctx.module.add_function(FN_NAME, fn_type, None);
|
||||
for attr in ["mustprogress", "nofree", "nounwind", "willreturn", "writeonly"] {
|
||||
func.add_attribute(
|
||||
AttributeLoc::Function,
|
||||
ctx.ctx.create_enum_attribute(Attribute::get_named_enum_kind_id(attr), 0),
|
||||
);
|
||||
}
|
||||
func
|
||||
});
|
||||
|
||||
ctx.builder.build_call(extern_fn, &[$($input_matrix.into(),)*], name.unwrap_or_default()).unwrap();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_linalg_extern_fn!(call_np_linalg_cholesky, "np_linalg_cholesky", 2);
|
||||
generate_linalg_extern_fn!(call_np_linalg_qr, "np_linalg_qr", 3);
|
||||
generate_linalg_extern_fn!(call_np_linalg_svd, "np_linalg_svd", 4);
|
||||
generate_linalg_extern_fn!(call_np_linalg_inv, "np_linalg_inv", 2);
|
||||
generate_linalg_extern_fn!(call_np_linalg_pinv, "np_linalg_pinv", 2);
|
||||
generate_linalg_extern_fn!(call_np_linalg_matrix_power, "np_linalg_matrix_power", 3);
|
||||
generate_linalg_extern_fn!(call_np_linalg_det, "np_linalg_det", 2);
|
||||
generate_linalg_extern_fn!(call_sp_linalg_lu, "sp_linalg_lu", 3);
|
||||
generate_linalg_extern_fn!(call_sp_linalg_schur, "sp_linalg_schur", 3);
|
||||
generate_linalg_extern_fn!(call_sp_linalg_hessenberg, "sp_linalg_hessenberg", 3);
|
@ -1,15 +1,17 @@
|
||||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicTypeEnum, IntType},
|
||||
values::{BasicValueEnum, IntValue, PointerValue},
|
||||
};
|
||||
|
||||
use nac3parser::ast::{Expr, Stmt, StrRef};
|
||||
|
||||
use super::{bool_to_i1, bool_to_i8, expr::*, stmt::*, values::ArraySliceValue, CodeGenContext};
|
||||
use crate::{
|
||||
codegen::{expr::*, stmt::*, CodeGenContext},
|
||||
symbol_resolver::ValueEnum,
|
||||
toplevel::{DefinitionId, TopLevelDef},
|
||||
typecheck::typedef::{FunSignature, Type},
|
||||
};
|
||||
use inkwell::{
|
||||
context::Context,
|
||||
types::{BasicTypeEnum, IntType},
|
||||
values::{BasicValueEnum, PointerValue},
|
||||
};
|
||||
use nac3parser::ast::{Expr, Stmt, StrRef};
|
||||
|
||||
pub trait CodeGenerator {
|
||||
/// Return the module name for the code generator.
|
||||
@ -22,9 +24,9 @@ pub trait CodeGenerator {
|
||||
/// - fun: Function signature and definition ID.
|
||||
/// - params: Function parameters. Note that this does not include the object even if the
|
||||
/// function is a class method.
|
||||
fn gen_call<'ctx, 'a>(
|
||||
fn gen_call<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
fun: (&FunSignature, DefinitionId),
|
||||
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
@ -36,12 +38,12 @@ pub trait CodeGenerator {
|
||||
}
|
||||
|
||||
/// Generate object constructor and returns the constructed object.
|
||||
/// - signature: Function signature of the contructor.
|
||||
/// - signature: Function signature of the constructor.
|
||||
/// - def: Class definition for the constructor class.
|
||||
/// - params: Function parameters.
|
||||
fn gen_constructor<'ctx, 'a>(
|
||||
fn gen_constructor<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
signature: &FunSignature,
|
||||
def: &TopLevelDef,
|
||||
params: Vec<(Option<StrRef>, ValueEnum<'ctx>)>,
|
||||
@ -57,22 +59,23 @@ pub trait CodeGenerator {
|
||||
/// - fun: Function signature, definition ID and the substitution key.
|
||||
/// - params: Function parameters. Note that this does not include the object even if the
|
||||
/// function is a class method.
|
||||
///
|
||||
/// Note that this function should check if the function is generated in another thread (due to
|
||||
/// possible race condition), see the default implementation for an example.
|
||||
fn gen_func_instance<'ctx, 'a>(
|
||||
fn gen_func_instance<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
obj: Option<(Type, ValueEnum<'ctx>)>,
|
||||
fun: (&FunSignature, &mut TopLevelDef, String),
|
||||
id: usize,
|
||||
) -> Result<String, String> {
|
||||
gen_func_instance(ctx, obj, fun, id)
|
||||
gen_func_instance(ctx, &obj, fun, id)
|
||||
}
|
||||
|
||||
/// Generate the code for an expression.
|
||||
fn gen_expr<'ctx, 'a>(
|
||||
fn gen_expr<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
expr: &Expr<Option<Type>>,
|
||||
) -> Result<Option<ValueEnum<'ctx>>, String>
|
||||
where
|
||||
@ -83,44 +86,92 @@ pub trait CodeGenerator {
|
||||
|
||||
/// Allocate memory for a variable and return a pointer pointing to it.
|
||||
/// The default implementation places the allocations at the start of the function.
|
||||
fn gen_var_alloc<'ctx, 'a>(
|
||||
fn gen_var_alloc<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> Result<PointerValue<'ctx>, String> {
|
||||
gen_var(ctx, ty)
|
||||
gen_var(ctx, ty, name)
|
||||
}
|
||||
|
||||
/// Allocate memory for a variable and return a pointer pointing to it.
|
||||
/// The default implementation places the allocations at the start of the function.
|
||||
fn gen_array_var_alloc<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Result<ArraySliceValue<'ctx>, String> {
|
||||
gen_array_var(ctx, ty, size, name)
|
||||
}
|
||||
|
||||
/// Return a pointer pointing to the target of the expression.
|
||||
fn gen_store_target<'ctx, 'a>(
|
||||
fn gen_store_target<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
pattern: &Expr<Option<Type>>,
|
||||
) -> Result<PointerValue<'ctx>, String>
|
||||
name: Option<&str>,
|
||||
) -> Result<Option<PointerValue<'ctx>>, String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_store_target(self, ctx, pattern)
|
||||
gen_store_target(self, ctx, pattern, name)
|
||||
}
|
||||
|
||||
/// Generate code for an assignment expression.
|
||||
fn gen_assign<'ctx, 'a>(
|
||||
fn gen_assign<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
target: &Expr<Option<Type>>,
|
||||
value: ValueEnum<'ctx>,
|
||||
value_ty: Type,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_assign(self, ctx, target, value)
|
||||
gen_assign(self, ctx, target, value, value_ty)
|
||||
}
|
||||
|
||||
/// Generate code for an assignment expression where LHS is a `"target_list"`.
|
||||
///
|
||||
/// See <https://docs.python.org/3/reference/simple_stmts.html#assignment-statements>.
|
||||
fn gen_assign_target_list<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
targets: &Vec<Expr<Option<Type>>>,
|
||||
value: ValueEnum<'ctx>,
|
||||
value_ty: Type,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_assign_target_list(self, ctx, targets, value, value_ty)
|
||||
}
|
||||
|
||||
/// Generate code for an item assignment.
|
||||
///
|
||||
/// i.e., `target[key] = value`
|
||||
fn gen_setitem<'ctx>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
target: &Expr<Option<Type>>,
|
||||
key: &Expr<Option<Type>>,
|
||||
value: ValueEnum<'ctx>,
|
||||
value_ty: Type,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_setitem(self, ctx, target, key, value, value_ty)
|
||||
}
|
||||
|
||||
/// Generate code for a while expression.
|
||||
/// Return true if the while loop must early return
|
||||
fn gen_while<'ctx, 'a>(
|
||||
fn gen_while(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'_, '_>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
@ -129,11 +180,11 @@ pub trait CodeGenerator {
|
||||
gen_while(self, ctx, stmt)
|
||||
}
|
||||
|
||||
/// Generate code for a while expression.
|
||||
/// Return true if the while loop must early return
|
||||
fn gen_for<'ctx, 'a>(
|
||||
/// Generate code for a for expression.
|
||||
/// Return true if the for loop must early return
|
||||
fn gen_for(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'_, '_>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
@ -144,9 +195,9 @@ pub trait CodeGenerator {
|
||||
|
||||
/// Generate code for an if expression.
|
||||
/// Return true if the statement must early return
|
||||
fn gen_if<'ctx, 'a>(
|
||||
fn gen_if(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'_, '_>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
@ -155,9 +206,9 @@ pub trait CodeGenerator {
|
||||
gen_if(self, ctx, stmt)
|
||||
}
|
||||
|
||||
fn gen_with<'ctx, 'a>(
|
||||
fn gen_with(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'_, '_>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
@ -167,10 +218,11 @@ pub trait CodeGenerator {
|
||||
}
|
||||
|
||||
/// Generate code for a statement
|
||||
///
|
||||
/// Return true if the statement must early return
|
||||
fn gen_stmt<'ctx, 'a>(
|
||||
fn gen_stmt(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'_, '_>,
|
||||
stmt: &Stmt<Option<Type>>,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
@ -178,6 +230,36 @@ pub trait CodeGenerator {
|
||||
{
|
||||
gen_stmt(self, ctx, stmt)
|
||||
}
|
||||
|
||||
/// Generates code for a block statement.
|
||||
fn gen_block<'a, I: Iterator<Item = &'a Stmt<Option<Type>>>>(
|
||||
&mut self,
|
||||
ctx: &mut CodeGenContext<'_, '_>,
|
||||
stmts: I,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
gen_block(self, ctx, stmts)
|
||||
}
|
||||
|
||||
/// See [`bool_to_i1`].
|
||||
fn bool_to_i1<'ctx>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
bool_value: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
bool_to_i1(&ctx.builder, bool_value)
|
||||
}
|
||||
|
||||
/// See [`bool_to_i8`].
|
||||
fn bool_to_i8<'ctx>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
bool_value: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
bool_to_i8(&ctx.builder, ctx.ctx, bool_value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DefaultCodeGenerator {
|
||||
@ -186,17 +268,20 @@ pub struct DefaultCodeGenerator {
|
||||
}
|
||||
|
||||
impl DefaultCodeGenerator {
|
||||
#[must_use]
|
||||
pub fn new(name: String, size_t: u32) -> DefaultCodeGenerator {
|
||||
assert!(size_t == 32 || size_t == 64);
|
||||
assert!(matches!(size_t, 32 | 64));
|
||||
DefaultCodeGenerator { name, size_t }
|
||||
}
|
||||
}
|
||||
|
||||
impl CodeGenerator for DefaultCodeGenerator {
|
||||
/// Returns the name for this [`CodeGenerator`].
|
||||
fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns an LLVM integer type representing `size_t`.
|
||||
fn get_size_type<'ctx>(&self, ctx: &'ctx Context) -> IntType<'ctx> {
|
||||
// it should be unsigned, but we don't really need unsigned and this could save us from
|
||||
// having to do a bit cast...
|
||||
|
@ -1,140 +0,0 @@
|
||||
typedef _ExtInt(8) int8_t;
|
||||
typedef unsigned _ExtInt(8) uint8_t;
|
||||
typedef _ExtInt(32) int32_t;
|
||||
typedef unsigned _ExtInt(32) uint32_t;
|
||||
typedef _ExtInt(64) int64_t;
|
||||
typedef unsigned _ExtInt(64) uint64_t;
|
||||
|
||||
# define MAX(a, b) (a > b ? a : b)
|
||||
# define MIN(a, b) (a > b ? b : a)
|
||||
|
||||
// adapted from GNU Scientific Library: https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
// need to make sure `exp >= 0` before calling this function
|
||||
#define DEF_INT_EXP(T) T __nac3_int_exp_##T( \
|
||||
T base, \
|
||||
T exp \
|
||||
) { \
|
||||
T res = (T)1; \
|
||||
/* repeated squaring method */ \
|
||||
do { \
|
||||
if (exp & 1) res *= base; /* for n odd */ \
|
||||
exp >>= 1; \
|
||||
base *= base; \
|
||||
} while (exp); \
|
||||
return res; \
|
||||
} \
|
||||
|
||||
DEF_INT_EXP(int32_t)
|
||||
DEF_INT_EXP(int64_t)
|
||||
DEF_INT_EXP(uint32_t)
|
||||
DEF_INT_EXP(uint64_t)
|
||||
|
||||
|
||||
int32_t __nac3_slice_index_bound(int32_t i, const int32_t len) {
|
||||
if (i < 0) {
|
||||
i = len + i;
|
||||
}
|
||||
if (i < 0) {
|
||||
return 0;
|
||||
} else if (i > len) {
|
||||
return len;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
int32_t __nac3_range_slice_len(const int32_t start, const int32_t end, const int32_t step) {
|
||||
int32_t diff = end - start;
|
||||
if (diff > 0 && step > 0) {
|
||||
return ((diff - 1) / step) + 1;
|
||||
} else if (diff < 0 && step < 0) {
|
||||
return ((diff + 1) / step) + 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle list assignment and dropping part of the list when
|
||||
// both dest_step and src_step are +1.
|
||||
// - All the index must *not* be out-of-bound or negative,
|
||||
// - The end index is *inclusive*,
|
||||
// - The length of src and dest slice size should already
|
||||
// be checked: if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest)
|
||||
int32_t __nac3_list_slice_assign_var_size(
|
||||
int32_t dest_start,
|
||||
int32_t dest_end,
|
||||
int32_t dest_step,
|
||||
uint8_t *dest_arr,
|
||||
int32_t dest_arr_len,
|
||||
int32_t src_start,
|
||||
int32_t src_end,
|
||||
int32_t src_step,
|
||||
uint8_t *src_arr,
|
||||
int32_t src_arr_len,
|
||||
const int32_t size
|
||||
) {
|
||||
/* if dest_arr_len == 0, do nothing since we do not support extending list */
|
||||
if (dest_arr_len == 0) return dest_arr_len;
|
||||
/* if both step is 1, memmove directly, handle the dropping of the list, and shrink size */
|
||||
if (src_step == dest_step && dest_step == 1) {
|
||||
const int32_t src_len = (src_end >= src_start) ? (src_end - src_start + 1) : 0;
|
||||
const int32_t dest_len = (dest_end >= dest_start) ? (dest_end - dest_start + 1) : 0;
|
||||
if (src_len > 0) {
|
||||
__builtin_memmove(
|
||||
dest_arr + dest_start * size,
|
||||
src_arr + src_start * size,
|
||||
src_len * size
|
||||
);
|
||||
}
|
||||
if (dest_len > 0) {
|
||||
/* dropping */
|
||||
__builtin_memmove(
|
||||
dest_arr + (dest_start + src_len) * size,
|
||||
dest_arr + (dest_end + 1) * size,
|
||||
(dest_arr_len - dest_end - 1) * size
|
||||
);
|
||||
}
|
||||
/* shrink size */
|
||||
return dest_arr_len - (dest_len - src_len);
|
||||
}
|
||||
/* if two range overlaps, need alloca */
|
||||
uint8_t need_alloca =
|
||||
(dest_arr == src_arr)
|
||||
&& !(
|
||||
MAX(dest_start, dest_end) < MIN(src_start, src_end)
|
||||
|| MAX(src_start, src_end) < MIN(dest_start, dest_end)
|
||||
);
|
||||
if (need_alloca) {
|
||||
uint8_t *tmp = __builtin_alloca(src_arr_len * size);
|
||||
__builtin_memcpy(tmp, src_arr, src_arr_len * size);
|
||||
src_arr = tmp;
|
||||
}
|
||||
int32_t src_ind = src_start;
|
||||
int32_t dest_ind = dest_start;
|
||||
for (;
|
||||
(src_step > 0) ? (src_ind <= src_end) : (src_ind >= src_end);
|
||||
src_ind += src_step, dest_ind += dest_step
|
||||
) {
|
||||
/* for constant optimization */
|
||||
if (size == 1) {
|
||||
__builtin_memcpy(dest_arr + dest_ind, src_arr + src_ind, 1);
|
||||
} else if (size == 4) {
|
||||
__builtin_memcpy(dest_arr + dest_ind * 4, src_arr + src_ind * 4, 4);
|
||||
} else if (size == 8) {
|
||||
__builtin_memcpy(dest_arr + dest_ind * 8, src_arr + src_ind * 8, 8);
|
||||
} else {
|
||||
/* memcpy for var size, cannot overlap after previous alloca */
|
||||
__builtin_memcpy(dest_arr + dest_ind * size, src_arr + src_ind * size, size);
|
||||
}
|
||||
}
|
||||
/* only dest_step == 1 can we shrink the dest list. */
|
||||
/* size should be ensured prior to calling this function */
|
||||
if (dest_step == 1 && dest_end >= dest_start) {
|
||||
__builtin_memmove(
|
||||
dest_arr + dest_ind * size,
|
||||
dest_arr + (dest_end + 1) * size,
|
||||
(dest_arr_len - dest_end - 1) * size
|
||||
);
|
||||
return dest_arr_len - (dest_end - dest_ind) - 1;
|
||||
}
|
||||
return dest_arr_len;
|
||||
}
|
162
nac3core/src/codegen/irrt/list.rs
Normal file
162
nac3core/src/codegen/irrt/list.rs
Normal file
@ -0,0 +1,162 @@
|
||||
use inkwell::{
|
||||
types::BasicTypeEnum,
|
||||
values::{BasicValueEnum, CallSiteValue, IntValue},
|
||||
AddressSpace, IntPredicate,
|
||||
};
|
||||
use itertools::Either;
|
||||
|
||||
use super::calculate_len_for_slice_range;
|
||||
use crate::codegen::{
|
||||
macros::codegen_unreachable,
|
||||
values::{ArrayLikeValue, ListValue},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
/// This function handles 'end' **inclusively**.
|
||||
/// Order of tuples `assign_idx` and `value_idx` is ('start', 'end', 'step').
|
||||
/// Negative index should be handled before entering this function
|
||||
pub fn list_slice_assignment<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
dest_arr: ListValue<'ctx>,
|
||||
dest_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
|
||||
src_arr: ListValue<'ctx>,
|
||||
src_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
|
||||
) {
|
||||
let size_ty = generator.get_size_type(ctx.ctx);
|
||||
let int8_ptr = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let (fun_symbol, elem_ptr_type) = ("__nac3_list_slice_assign_var_size", int8_ptr);
|
||||
let slice_assign_fun = {
|
||||
let ty_vec = vec![
|
||||
int32.into(), // dest start idx
|
||||
int32.into(), // dest end idx
|
||||
int32.into(), // dest step
|
||||
elem_ptr_type.into(), // dest arr ptr
|
||||
int32.into(), // dest arr len
|
||||
int32.into(), // src start idx
|
||||
int32.into(), // src end idx
|
||||
int32.into(), // src step
|
||||
elem_ptr_type.into(), // src arr ptr
|
||||
int32.into(), // src arr len
|
||||
int32.into(), // size
|
||||
];
|
||||
ctx.module.get_function(fun_symbol).unwrap_or_else(|| {
|
||||
let fn_t = int32.fn_type(ty_vec.as_slice(), false);
|
||||
ctx.module.add_function(fun_symbol, fn_t, None)
|
||||
})
|
||||
};
|
||||
|
||||
let zero = int32.const_zero();
|
||||
let one = int32.const_int(1, false);
|
||||
let dest_arr_ptr = dest_arr.data().base_ptr(ctx, generator);
|
||||
let dest_arr_ptr =
|
||||
ctx.builder.build_pointer_cast(dest_arr_ptr, elem_ptr_type, "dest_arr_ptr_cast").unwrap();
|
||||
let dest_len = dest_arr.load_size(ctx, Some("dest.len"));
|
||||
let dest_len = ctx.builder.build_int_truncate_or_bit_cast(dest_len, int32, "srclen32").unwrap();
|
||||
let src_arr_ptr = src_arr.data().base_ptr(ctx, generator);
|
||||
let src_arr_ptr =
|
||||
ctx.builder.build_pointer_cast(src_arr_ptr, elem_ptr_type, "src_arr_ptr_cast").unwrap();
|
||||
let src_len = src_arr.load_size(ctx, Some("src.len"));
|
||||
let src_len = ctx.builder.build_int_truncate_or_bit_cast(src_len, int32, "srclen32").unwrap();
|
||||
|
||||
// index in bound and positive should be done
|
||||
// assert if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest), and
|
||||
// throw exception if not satisfied
|
||||
let src_end = ctx
|
||||
.builder
|
||||
.build_select(
|
||||
ctx.builder.build_int_compare(IntPredicate::SLT, src_idx.2, zero, "is_neg").unwrap(),
|
||||
ctx.builder.build_int_sub(src_idx.1, one, "e_min_one").unwrap(),
|
||||
ctx.builder.build_int_add(src_idx.1, one, "e_add_one").unwrap(),
|
||||
"final_e",
|
||||
)
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
let dest_end = ctx
|
||||
.builder
|
||||
.build_select(
|
||||
ctx.builder.build_int_compare(IntPredicate::SLT, dest_idx.2, zero, "is_neg").unwrap(),
|
||||
ctx.builder.build_int_sub(dest_idx.1, one, "e_min_one").unwrap(),
|
||||
ctx.builder.build_int_add(dest_idx.1, one, "e_add_one").unwrap(),
|
||||
"final_e",
|
||||
)
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap();
|
||||
let src_slice_len =
|
||||
calculate_len_for_slice_range(generator, ctx, src_idx.0, src_end, src_idx.2);
|
||||
let dest_slice_len =
|
||||
calculate_len_for_slice_range(generator, ctx, dest_idx.0, dest_end, dest_idx.2);
|
||||
let src_eq_dest = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::EQ, src_slice_len, dest_slice_len, "slice_src_eq_dest")
|
||||
.unwrap();
|
||||
let src_slt_dest = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::SLT, src_slice_len, dest_slice_len, "slice_src_slt_dest")
|
||||
.unwrap();
|
||||
let dest_step_eq_one = ctx
|
||||
.builder
|
||||
.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
dest_idx.2,
|
||||
dest_idx.2.get_type().const_int(1, false),
|
||||
"slice_dest_step_eq_one",
|
||||
)
|
||||
.unwrap();
|
||||
let cond_1 = ctx.builder.build_and(dest_step_eq_one, src_slt_dest, "slice_cond_1").unwrap();
|
||||
let cond = ctx.builder.build_or(src_eq_dest, cond_1, "slice_cond").unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
cond,
|
||||
"0:ValueError",
|
||||
"attempt to assign sequence of size {0} to slice of size {1} with step size {2}",
|
||||
[Some(src_slice_len), Some(dest_slice_len), Some(dest_idx.2)],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
let new_len = {
|
||||
let args = vec![
|
||||
dest_idx.0.into(), // dest start idx
|
||||
dest_idx.1.into(), // dest end idx
|
||||
dest_idx.2.into(), // dest step
|
||||
dest_arr_ptr.into(), // dest arr ptr
|
||||
dest_len.into(), // dest arr len
|
||||
src_idx.0.into(), // src start idx
|
||||
src_idx.1.into(), // src end idx
|
||||
src_idx.2.into(), // src step
|
||||
src_arr_ptr.into(), // src arr ptr
|
||||
src_len.into(), // src arr len
|
||||
{
|
||||
let s = match ty {
|
||||
BasicTypeEnum::FloatType(t) => t.size_of(),
|
||||
BasicTypeEnum::IntType(t) => t.size_of(),
|
||||
BasicTypeEnum::PointerType(t) => t.size_of(),
|
||||
BasicTypeEnum::StructType(t) => t.size_of().unwrap(),
|
||||
_ => codegen_unreachable!(ctx),
|
||||
};
|
||||
ctx.builder.build_int_truncate_or_bit_cast(s, int32, "size").unwrap()
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
ctx.builder
|
||||
.build_call(slice_assign_fun, args.as_slice(), "slice_assign")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
};
|
||||
// update length
|
||||
let need_update =
|
||||
ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update").unwrap();
|
||||
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let update_bb = ctx.ctx.append_basic_block(current, "update");
|
||||
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
|
||||
ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb).unwrap();
|
||||
ctx.builder.position_at_end(update_bb);
|
||||
let new_len = ctx.builder.build_int_z_extend_or_bit_cast(new_len, size_ty, "new_len").unwrap();
|
||||
dest_arr.store_size(ctx, generator, new_len);
|
||||
ctx.builder.build_unconditional_branch(cont_bb).unwrap();
|
||||
ctx.builder.position_at_end(cont_bb);
|
||||
}
|
152
nac3core/src/codegen/irrt/math.rs
Normal file
152
nac3core/src/codegen/irrt/math.rs
Normal file
@ -0,0 +1,152 @@
|
||||
use inkwell::{
|
||||
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue},
|
||||
IntPredicate,
|
||||
};
|
||||
use itertools::Either;
|
||||
|
||||
use crate::codegen::{
|
||||
macros::codegen_unreachable,
|
||||
{CodeGenContext, CodeGenerator},
|
||||
};
|
||||
|
||||
// repeated squaring method adapted from GNU Scientific Library:
|
||||
// https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
pub fn integer_power<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
base: IntValue<'ctx>,
|
||||
exp: IntValue<'ctx>,
|
||||
signed: bool,
|
||||
) -> IntValue<'ctx> {
|
||||
let symbol = match (base.get_type().get_bit_width(), exp.get_type().get_bit_width(), signed) {
|
||||
(32, 32, true) => "__nac3_int_exp_int32_t",
|
||||
(64, 64, true) => "__nac3_int_exp_int64_t",
|
||||
(32, 32, false) => "__nac3_int_exp_uint32_t",
|
||||
(64, 64, false) => "__nac3_int_exp_uint64_t",
|
||||
_ => codegen_unreachable!(ctx),
|
||||
};
|
||||
let base_type = base.get_type();
|
||||
let pow_fun = ctx.module.get_function(symbol).unwrap_or_else(|| {
|
||||
let fn_type = base_type.fn_type(&[base_type.into(), base_type.into()], false);
|
||||
ctx.module.add_function(symbol, fn_type, None)
|
||||
});
|
||||
// throw exception when exp < 0
|
||||
let ge_zero = ctx
|
||||
.builder
|
||||
.build_int_compare(
|
||||
IntPredicate::SGE,
|
||||
exp,
|
||||
exp.get_type().const_zero(),
|
||||
"assert_int_pow_ge_0",
|
||||
)
|
||||
.unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
ge_zero,
|
||||
"0:ValueError",
|
||||
"integer power must be positive or zero",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
ctx.builder
|
||||
.build_call(pow_fun, &[base.into(), exp.into()], "call_int_pow")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Generates a call to `isinf` in IR. Returns an `i1` representing the result.
|
||||
pub fn call_isinf<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &mut G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
v: FloatValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let intrinsic_fn = ctx.module.get_function("__nac3_isinf").unwrap_or_else(|| {
|
||||
let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false);
|
||||
ctx.module.add_function("__nac3_isinf", fn_type, None)
|
||||
});
|
||||
|
||||
let ret = ctx
|
||||
.builder
|
||||
.build_call(intrinsic_fn, &[v.into()], "isinf")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap();
|
||||
|
||||
generator.bool_to_i1(ctx, ret)
|
||||
}
|
||||
|
||||
/// Generates a call to `isnan` in IR. Returns an `i1` representing the result.
|
||||
pub fn call_isnan<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &mut G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
v: FloatValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let intrinsic_fn = ctx.module.get_function("__nac3_isnan").unwrap_or_else(|| {
|
||||
let fn_type = ctx.ctx.i32_type().fn_type(&[ctx.ctx.f64_type().into()], false);
|
||||
ctx.module.add_function("__nac3_isnan", fn_type, None)
|
||||
});
|
||||
|
||||
let ret = ctx
|
||||
.builder
|
||||
.build_call(intrinsic_fn, &[v.into()], "isnan")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap();
|
||||
|
||||
generator.bool_to_i1(ctx, ret)
|
||||
}
|
||||
|
||||
/// Generates a call to `gamma` in IR. Returns an `f64` representing the result.
|
||||
pub fn call_gamma<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
|
||||
let llvm_f64 = ctx.ctx.f64_type();
|
||||
|
||||
let intrinsic_fn = ctx.module.get_function("__nac3_gamma").unwrap_or_else(|| {
|
||||
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
|
||||
ctx.module.add_function("__nac3_gamma", fn_type, None)
|
||||
});
|
||||
|
||||
ctx.builder
|
||||
.build_call(intrinsic_fn, &[v.into()], "gamma")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_float_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Generates a call to `gammaln` in IR. Returns an `f64` representing the result.
|
||||
pub fn call_gammaln<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
|
||||
let llvm_f64 = ctx.ctx.f64_type();
|
||||
|
||||
let intrinsic_fn = ctx.module.get_function("__nac3_gammaln").unwrap_or_else(|| {
|
||||
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
|
||||
ctx.module.add_function("__nac3_gammaln", fn_type, None)
|
||||
});
|
||||
|
||||
ctx.builder
|
||||
.build_call(intrinsic_fn, &[v.into()], "gammaln")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_float_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Generates a call to `j0` in IR. Returns an `f64` representing the result.
|
||||
pub fn call_j0<'ctx>(ctx: &CodeGenContext<'ctx, '_>, v: FloatValue<'ctx>) -> FloatValue<'ctx> {
|
||||
let llvm_f64 = ctx.ctx.f64_type();
|
||||
|
||||
let intrinsic_fn = ctx.module.get_function("__nac3_j0").unwrap_or_else(|| {
|
||||
let fn_type = llvm_f64.fn_type(&[llvm_f64.into()], false);
|
||||
ctx.module.add_function("__nac3_j0", fn_type, None)
|
||||
});
|
||||
|
||||
ctx.builder
|
||||
.build_call(intrinsic_fn, &[v.into()], "j0")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_float_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
@ -1,18 +1,29 @@
|
||||
use crate::typecheck::typedef::Type;
|
||||
|
||||
use super::{CodeGenContext, CodeGenerator};
|
||||
use inkwell::{
|
||||
attributes::{Attribute, AttributeLoc},
|
||||
context::Context,
|
||||
memory_buffer::MemoryBuffer,
|
||||
module::Module,
|
||||
types::BasicTypeEnum,
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace, IntPredicate,
|
||||
values::{BasicValue, BasicValueEnum, IntValue},
|
||||
IntPredicate,
|
||||
};
|
||||
|
||||
use nac3parser::ast::Expr;
|
||||
|
||||
pub fn load_irrt(ctx: &Context) -> Module {
|
||||
use super::{CodeGenContext, CodeGenerator};
|
||||
use crate::{symbol_resolver::SymbolResolver, typecheck::typedef::Type};
|
||||
pub use list::*;
|
||||
pub use math::*;
|
||||
pub use range::*;
|
||||
pub use slice::*;
|
||||
|
||||
mod list;
|
||||
mod math;
|
||||
pub mod ndarray;
|
||||
mod range;
|
||||
mod slice;
|
||||
|
||||
#[must_use]
|
||||
pub fn load_irrt<'ctx>(ctx: &'ctx Context, symbol_resolver: &dyn SymbolResolver) -> Module<'ctx> {
|
||||
let bitcode_buf = MemoryBuffer::create_from_memory_range(
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/irrt.bc")),
|
||||
"irrt_bitcode_buffer",
|
||||
@ -28,87 +39,47 @@ pub fn load_irrt(ctx: &Context) -> Module {
|
||||
let function = irrt_mod.get_function(symbol).unwrap();
|
||||
function.add_attribute(AttributeLoc::Function, ctx.create_enum_attribute(inline_attr, 0));
|
||||
}
|
||||
|
||||
// Initialize all global `EXN_*` exception IDs in IRRT with the [`SymbolResolver`].
|
||||
let exn_id_type = ctx.i32_type();
|
||||
let errors = &[
|
||||
("EXN_INDEX_ERROR", "0:IndexError"),
|
||||
("EXN_VALUE_ERROR", "0:ValueError"),
|
||||
("EXN_ASSERTION_ERROR", "0:AssertionError"),
|
||||
("EXN_TYPE_ERROR", "0:TypeError"),
|
||||
];
|
||||
for (irrt_name, symbol_name) in errors {
|
||||
let exn_id = symbol_resolver.get_string_id(symbol_name);
|
||||
let exn_id = exn_id_type.const_int(exn_id as u64, false).as_basic_value_enum();
|
||||
|
||||
let global = irrt_mod.get_global(irrt_name).unwrap_or_else(|| {
|
||||
panic!("Exception symbol name '{irrt_name}' should exist in the IRRT LLVM module")
|
||||
});
|
||||
global.set_initializer(&exn_id);
|
||||
}
|
||||
|
||||
irrt_mod
|
||||
}
|
||||
|
||||
// repeated squaring method adapted from GNU Scientific Library:
|
||||
// https://git.savannah.gnu.org/cgit/gsl.git/tree/sys/pow_int.c
|
||||
pub fn integer_power<'ctx, 'a>(
|
||||
generator: &mut dyn CodeGenerator,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
base: IntValue<'ctx>,
|
||||
exp: IntValue<'ctx>,
|
||||
signed: bool,
|
||||
) -> IntValue<'ctx> {
|
||||
let symbol = match (base.get_type().get_bit_width(), exp.get_type().get_bit_width(), signed) {
|
||||
(32, 32, true) => "__nac3_int_exp_int32_t",
|
||||
(64, 64, true) => "__nac3_int_exp_int64_t",
|
||||
(32, 32, false) => "__nac3_int_exp_uint32_t",
|
||||
(64, 64, false) => "__nac3_int_exp_uint64_t",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let base_type = base.get_type();
|
||||
let pow_fun = ctx.module.get_function(symbol).unwrap_or_else(|| {
|
||||
let fn_type = base_type.fn_type(&[base_type.into(), base_type.into()], false);
|
||||
ctx.module.add_function(symbol, fn_type, None)
|
||||
});
|
||||
// throw exception when exp < 0
|
||||
let ge_zero = ctx.builder.build_int_compare(
|
||||
IntPredicate::SGE,
|
||||
exp,
|
||||
exp.get_type().const_zero(),
|
||||
"assert_int_pow_ge_0",
|
||||
);
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
ge_zero,
|
||||
"0:ValueError",
|
||||
"integer power must be positive or zero",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
ctx.builder
|
||||
.build_call(pow_fun, &[base.into(), exp.into()], "call_int_pow")
|
||||
.try_as_basic_value()
|
||||
.unwrap_left()
|
||||
.into_int_value()
|
||||
}
|
||||
|
||||
pub fn calculate_len_for_slice_range<'ctx, 'a>(
|
||||
generator: &mut dyn CodeGenerator,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
start: IntValue<'ctx>,
|
||||
end: IntValue<'ctx>,
|
||||
step: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
const SYMBOL: &str = "__nac3_range_slice_len";
|
||||
let len_func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
|
||||
let i32_t = ctx.ctx.i32_type();
|
||||
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into(), i32_t.into()], false);
|
||||
ctx.module.add_function(SYMBOL, fn_t, None)
|
||||
});
|
||||
|
||||
// assert step != 0, throw exception if not
|
||||
let not_zero = ctx.builder.build_int_compare(
|
||||
IntPredicate::NE,
|
||||
step,
|
||||
step.get_type().const_zero(),
|
||||
"range_step_ne",
|
||||
);
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
not_zero,
|
||||
"0:ValueError",
|
||||
"step must not be zero",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
ctx.builder
|
||||
.build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len")
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap()
|
||||
.into_int_value()
|
||||
/// Returns the name of a function which contains variants for 32-bit and 64-bit `size_t`.
|
||||
///
|
||||
/// - When [`TypeContext::size_type`] is 32-bits, the function name is `fn_name}`.
|
||||
/// - When [`TypeContext::size_type`] is 64-bits, the function name is `{fn_name}64`.
|
||||
#[must_use]
|
||||
pub fn get_usize_dependent_function_name<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'_, '_>,
|
||||
name: &str,
|
||||
) -> String {
|
||||
let mut name = name.to_owned();
|
||||
match generator.get_size_type(ctx.ctx).get_bit_width() {
|
||||
32 => {}
|
||||
64 => name.push_str("64"),
|
||||
bit_width => {
|
||||
panic!("Unsupported int type bit width {bit_width}, must be either 32-bits or 64-bits")
|
||||
}
|
||||
}
|
||||
name
|
||||
}
|
||||
|
||||
/// NOTE: the output value of the end index of this function should be compared ***inclusively***,
|
||||
@ -151,47 +122,57 @@ pub fn calculate_len_for_slice_range<'ctx, 'a>(
|
||||
/// ,step
|
||||
/// )
|
||||
/// ```
|
||||
pub fn handle_slice_indices<'a, 'ctx, G: CodeGenerator>(
|
||||
pub fn handle_slice_indices<'ctx, G: CodeGenerator>(
|
||||
start: &Option<Box<Expr<Option<Type>>>>,
|
||||
end: &Option<Box<Expr<Option<Type>>>>,
|
||||
step: &Option<Box<Expr<Option<Type>>>>,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
list: PointerValue<'ctx>,
|
||||
) -> Result<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>), String> {
|
||||
length: IntValue<'ctx>,
|
||||
) -> Result<Option<(IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>)>, String> {
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let zero = int32.const_zero();
|
||||
let one = int32.const_int(1, false);
|
||||
let length = ctx.build_gep_and_load(list, &[zero, one]).into_int_value();
|
||||
let length = ctx.builder.build_int_truncate_or_bit_cast(length, int32, "leni32");
|
||||
Ok(match (start, end, step) {
|
||||
let length = ctx.builder.build_int_truncate_or_bit_cast(length, int32, "leni32").unwrap();
|
||||
Ok(Some(match (start, end, step) {
|
||||
(s, e, None) => (
|
||||
s.as_ref().map_or_else(
|
||||
|| Ok(int32.const_zero()),
|
||||
|s| handle_slice_index_bound(s, ctx, generator, length),
|
||||
)?,
|
||||
if let Some(s) = s.as_ref() {
|
||||
match handle_slice_index_bound(s, ctx, generator, length)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
}
|
||||
} else {
|
||||
int32.const_zero()
|
||||
},
|
||||
{
|
||||
let e = e.as_ref().map_or_else(
|
||||
|| Ok(length),
|
||||
|e| handle_slice_index_bound(e, ctx, generator, length),
|
||||
)?;
|
||||
ctx.builder.build_int_sub(e, one, "final_end")
|
||||
let e = if let Some(s) = e.as_ref() {
|
||||
match handle_slice_index_bound(s, ctx, generator, length)? {
|
||||
Some(v) => v,
|
||||
None => return Ok(None),
|
||||
}
|
||||
} else {
|
||||
length
|
||||
};
|
||||
ctx.builder.build_int_sub(e, one, "final_end").unwrap()
|
||||
},
|
||||
one,
|
||||
),
|
||||
(s, e, Some(step)) => {
|
||||
let step = generator
|
||||
.gen_expr(ctx, step)?
|
||||
.unwrap()
|
||||
.to_basic_value_enum(ctx, generator)?
|
||||
.into_int_value();
|
||||
let step = if let Some(v) = generator.gen_expr(ctx, step)? {
|
||||
v.to_basic_value_enum(ctx, generator, ctx.primitives.int32)?.into_int_value()
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
// assert step != 0, throw exception if not
|
||||
let not_zero = ctx.builder.build_int_compare(
|
||||
IntPredicate::NE,
|
||||
step,
|
||||
step.get_type().const_zero(),
|
||||
"range_step_ne",
|
||||
);
|
||||
let not_zero = ctx
|
||||
.builder
|
||||
.build_int_compare(
|
||||
IntPredicate::NE,
|
||||
step,
|
||||
step.get_type().const_zero(),
|
||||
"range_step_ne",
|
||||
)
|
||||
.unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
not_zero,
|
||||
@ -200,235 +181,69 @@ pub fn handle_slice_indices<'a, 'ctx, G: CodeGenerator>(
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
let len_id = ctx.builder.build_int_sub(length, one, "lenmin1");
|
||||
let neg = ctx.builder.build_int_compare(IntPredicate::SLT, step, zero, "step_is_neg");
|
||||
let len_id = ctx.builder.build_int_sub(length, one, "lenmin1").unwrap();
|
||||
let neg = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::SLT, step, zero, "step_is_neg")
|
||||
.unwrap();
|
||||
(
|
||||
match s {
|
||||
Some(s) => {
|
||||
let s = handle_slice_index_bound(s, ctx, generator, length)?;
|
||||
let Some(s) = handle_slice_index_bound(s, ctx, generator, length)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
ctx.builder
|
||||
.build_select(
|
||||
ctx.builder.build_and(
|
||||
ctx.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
s,
|
||||
length,
|
||||
"s_eq_len",
|
||||
),
|
||||
neg,
|
||||
"should_minus_one",
|
||||
),
|
||||
ctx.builder.build_int_sub(s, one, "s_min"),
|
||||
ctx.builder
|
||||
.build_and(
|
||||
ctx.builder
|
||||
.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
s,
|
||||
length,
|
||||
"s_eq_len",
|
||||
)
|
||||
.unwrap(),
|
||||
neg,
|
||||
"should_minus_one",
|
||||
)
|
||||
.unwrap(),
|
||||
ctx.builder.build_int_sub(s, one, "s_min").unwrap(),
|
||||
s,
|
||||
"final_start",
|
||||
)
|
||||
.into_int_value()
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
None => ctx.builder.build_select(neg, len_id, zero, "stt").into_int_value(),
|
||||
None => ctx
|
||||
.builder
|
||||
.build_select(neg, len_id, zero, "stt")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap(),
|
||||
},
|
||||
match e {
|
||||
Some(e) => {
|
||||
let e = handle_slice_index_bound(e, ctx, generator, length)?;
|
||||
let Some(e) = handle_slice_index_bound(e, ctx, generator, length)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
ctx.builder
|
||||
.build_select(
|
||||
neg,
|
||||
ctx.builder.build_int_add(e, one, "end_add_one"),
|
||||
ctx.builder.build_int_sub(e, one, "end_sub_one"),
|
||||
ctx.builder.build_int_add(e, one, "end_add_one").unwrap(),
|
||||
ctx.builder.build_int_sub(e, one, "end_sub_one").unwrap(),
|
||||
"final_end",
|
||||
)
|
||||
.into_int_value()
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
None => ctx.builder.build_select(neg, zero, len_id, "end").into_int_value(),
|
||||
None => ctx
|
||||
.builder
|
||||
.build_select(neg, zero, len_id, "end")
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap(),
|
||||
},
|
||||
step,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// this function allows index out of range, since python
|
||||
/// allows index out of range in slice (`a = [1,2,3]; a[1:10] == [2,3]`).
|
||||
pub fn handle_slice_index_bound<'a, 'ctx, G: CodeGenerator>(
|
||||
i: &Expr<Option<Type>>,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
generator: &mut G,
|
||||
length: IntValue<'ctx>,
|
||||
) -> Result<IntValue<'ctx>, String> {
|
||||
const SYMBOL: &str = "__nac3_slice_index_bound";
|
||||
let func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
|
||||
let i32_t = ctx.ctx.i32_type();
|
||||
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into()], false);
|
||||
ctx.module.add_function(SYMBOL, fn_t, None)
|
||||
});
|
||||
|
||||
let i = generator.gen_expr(ctx, i)?.unwrap().to_basic_value_enum(ctx, generator)?;
|
||||
Ok(ctx
|
||||
.builder
|
||||
.build_call(func, &[i.into(), length.into()], "bounded_ind")
|
||||
.try_as_basic_value()
|
||||
.left()
|
||||
.unwrap()
|
||||
.into_int_value())
|
||||
}
|
||||
|
||||
/// This function handles 'end' **inclusively**.
|
||||
/// Order of tuples assign_idx and value_idx is ('start', 'end', 'step').
|
||||
/// Negative index should be handled before entering this function
|
||||
pub fn list_slice_assignment<'ctx, 'a>(
|
||||
generator: &mut dyn CodeGenerator,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
dest_arr: PointerValue<'ctx>,
|
||||
dest_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
|
||||
src_arr: PointerValue<'ctx>,
|
||||
src_idx: (IntValue<'ctx>, IntValue<'ctx>, IntValue<'ctx>),
|
||||
) {
|
||||
let size_ty = generator.get_size_type(ctx.ctx);
|
||||
let int8_ptr = ctx.ctx.i8_type().ptr_type(AddressSpace::Generic);
|
||||
let int32 = ctx.ctx.i32_type();
|
||||
let (fun_symbol, elem_ptr_type) = ("__nac3_list_slice_assign_var_size", int8_ptr);
|
||||
let slice_assign_fun = {
|
||||
let ty_vec = vec![
|
||||
int32.into(), // dest start idx
|
||||
int32.into(), // dest end idx
|
||||
int32.into(), // dest step
|
||||
elem_ptr_type.into(), // dest arr ptr
|
||||
int32.into(), // dest arr len
|
||||
int32.into(), // src start idx
|
||||
int32.into(), // src end idx
|
||||
int32.into(), // src step
|
||||
elem_ptr_type.into(), // src arr ptr
|
||||
int32.into(), // src arr len
|
||||
int32.into(), // size
|
||||
];
|
||||
ctx.module.get_function(fun_symbol).unwrap_or_else(|| {
|
||||
let fn_t = int32.fn_type(ty_vec.as_slice(), false);
|
||||
ctx.module.add_function(fun_symbol, fn_t, None)
|
||||
})
|
||||
};
|
||||
|
||||
let zero = int32.const_zero();
|
||||
let one = int32.const_int(1, false);
|
||||
let dest_arr_ptr = ctx.build_gep_and_load(dest_arr, &[zero, zero]);
|
||||
let dest_arr_ptr = ctx.builder.build_pointer_cast(
|
||||
dest_arr_ptr.into_pointer_value(),
|
||||
elem_ptr_type,
|
||||
"dest_arr_ptr_cast",
|
||||
);
|
||||
let dest_len = ctx.build_gep_and_load(dest_arr, &[zero, one]).into_int_value();
|
||||
let dest_len = ctx.builder.build_int_truncate_or_bit_cast(dest_len, int32, "srclen32");
|
||||
let src_arr_ptr = ctx.build_gep_and_load(src_arr, &[zero, zero]);
|
||||
let src_arr_ptr = ctx.builder.build_pointer_cast(
|
||||
src_arr_ptr.into_pointer_value(),
|
||||
elem_ptr_type,
|
||||
"src_arr_ptr_cast",
|
||||
);
|
||||
let src_len = ctx.build_gep_and_load(src_arr, &[zero, one]).into_int_value();
|
||||
let src_len = ctx.builder.build_int_truncate_or_bit_cast(src_len, int32, "srclen32");
|
||||
|
||||
// index in bound and positive should be done
|
||||
// assert if dest.step == 1 then len(src) <= len(dest) else len(src) == len(dest), and
|
||||
// throw exception if not satisfied
|
||||
let src_end = ctx.builder
|
||||
.build_select(
|
||||
ctx.builder.build_int_compare(
|
||||
inkwell::IntPredicate::SLT,
|
||||
src_idx.2,
|
||||
zero,
|
||||
"is_neg",
|
||||
),
|
||||
ctx.builder.build_int_sub(src_idx.1, one, "e_min_one"),
|
||||
ctx.builder.build_int_add(src_idx.1, one, "e_add_one"),
|
||||
"final_e",
|
||||
)
|
||||
.into_int_value();
|
||||
let dest_end = ctx.builder
|
||||
.build_select(
|
||||
ctx.builder.build_int_compare(
|
||||
inkwell::IntPredicate::SLT,
|
||||
dest_idx.2,
|
||||
zero,
|
||||
"is_neg",
|
||||
),
|
||||
ctx.builder.build_int_sub(dest_idx.1, one, "e_min_one"),
|
||||
ctx.builder.build_int_add(dest_idx.1, one, "e_add_one"),
|
||||
"final_e",
|
||||
)
|
||||
.into_int_value();
|
||||
let src_slice_len =
|
||||
calculate_len_for_slice_range(generator, ctx, src_idx.0, src_end, src_idx.2);
|
||||
let dest_slice_len =
|
||||
calculate_len_for_slice_range(generator, ctx, dest_idx.0, dest_end, dest_idx.2);
|
||||
let src_eq_dest = ctx.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
src_slice_len,
|
||||
dest_slice_len,
|
||||
"slice_src_eq_dest",
|
||||
);
|
||||
let src_slt_dest = ctx.builder.build_int_compare(
|
||||
IntPredicate::SLT,
|
||||
src_slice_len,
|
||||
dest_slice_len,
|
||||
"slice_src_slt_dest",
|
||||
);
|
||||
let dest_step_eq_one = ctx.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
dest_idx.2,
|
||||
dest_idx.2.get_type().const_int(1, false),
|
||||
"slice_dest_step_eq_one",
|
||||
);
|
||||
let cond_1 = ctx.builder.build_and(dest_step_eq_one, src_slt_dest, "slice_cond_1");
|
||||
let cond = ctx.builder.build_or(src_eq_dest, cond_1, "slice_cond");
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
cond,
|
||||
"0:ValueError",
|
||||
"attempt to assign sequence of size {0} to slice of size {1} with step size {2}",
|
||||
[Some(src_slice_len), Some(dest_slice_len), Some(dest_idx.2)],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
let new_len = {
|
||||
let args = vec![
|
||||
dest_idx.0.into(), // dest start idx
|
||||
dest_idx.1.into(), // dest end idx
|
||||
dest_idx.2.into(), // dest step
|
||||
dest_arr_ptr.into(), // dest arr ptr
|
||||
dest_len.into(), // dest arr len
|
||||
src_idx.0.into(), // src start idx
|
||||
src_idx.1.into(), // src end idx
|
||||
src_idx.2.into(), // src step
|
||||
src_arr_ptr.into(), // src arr ptr
|
||||
src_len.into(), // src arr len
|
||||
{
|
||||
let s = match ty {
|
||||
BasicTypeEnum::FloatType(t) => t.size_of(),
|
||||
BasicTypeEnum::IntType(t) => t.size_of(),
|
||||
BasicTypeEnum::PointerType(t) => t.size_of(),
|
||||
BasicTypeEnum::StructType(t) => t.size_of().unwrap(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ctx.builder.build_int_truncate_or_bit_cast(s, int32, "size")
|
||||
}
|
||||
.into(),
|
||||
];
|
||||
ctx.builder
|
||||
.build_call(slice_assign_fun, args.as_slice(), "slice_assign")
|
||||
.try_as_basic_value()
|
||||
.unwrap_left()
|
||||
.into_int_value()
|
||||
};
|
||||
// update length
|
||||
let need_update =
|
||||
ctx.builder.build_int_compare(IntPredicate::NE, new_len, dest_len, "need_update");
|
||||
let current = ctx.builder.get_insert_block().unwrap().get_parent().unwrap();
|
||||
let update_bb = ctx.ctx.append_basic_block(current, "update");
|
||||
let cont_bb = ctx.ctx.append_basic_block(current, "cont");
|
||||
ctx.builder.build_conditional_branch(need_update, update_bb, cont_bb);
|
||||
ctx.builder.position_at_end(update_bb);
|
||||
let dest_len_ptr = unsafe { ctx.builder.build_gep(dest_arr, &[zero, one], "dest_len_ptr") };
|
||||
let new_len = ctx.builder.build_int_z_extend_or_bit_cast(new_len, size_ty, "new_len");
|
||||
ctx.builder.build_store(dest_len_ptr, new_len);
|
||||
ctx.builder.build_unconditional_branch(cont_bb);
|
||||
ctx.builder.position_at_end(cont_bb);
|
||||
}))
|
||||
}
|
||||
|
250
nac3core/src/codegen/irrt/ndarray/basic.rs
Normal file
250
nac3core/src/codegen/irrt/ndarray/basic.rs
Normal file
@ -0,0 +1,250 @@
|
||||
use inkwell::{
|
||||
values::{BasicValueEnum, IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
|
||||
use crate::codegen::{
|
||||
expr::{create_and_call_function, infer_and_call_function},
|
||||
irrt::get_usize_dependent_function_name,
|
||||
types::ProxyType,
|
||||
values::{ndarray::NDArrayValue, ProxyValue},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
pub fn call_nac3_ndarray_util_assert_shape_no_negative<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndims: IntValue<'ctx>,
|
||||
shape: PointerValue<'ctx>,
|
||||
) {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
let name = get_usize_dependent_function_name(
|
||||
generator,
|
||||
ctx,
|
||||
"__nac3_ndarray_util_assert_shape_no_negative",
|
||||
);
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_usize.into()),
|
||||
&[(llvm_usize.into(), ndims.into()), (llvm_pusize.into(), shape.into())],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_util_assert_output_shape_same<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray_ndims: IntValue<'ctx>,
|
||||
ndarray_shape: PointerValue<'ctx>,
|
||||
output_ndims: IntValue<'ctx>,
|
||||
output_shape: IntValue<'ctx>,
|
||||
) {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
let name = get_usize_dependent_function_name(
|
||||
generator,
|
||||
ctx,
|
||||
"__nac3_ndarray_util_assert_output_shape_same",
|
||||
);
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_usize.into()),
|
||||
&[
|
||||
(llvm_usize.into(), ndarray_ndims.into()),
|
||||
(llvm_pusize.into(), ndarray_shape.into()),
|
||||
(llvm_usize.into(), output_ndims.into()),
|
||||
(llvm_pusize.into(), output_shape.into()),
|
||||
],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_size<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_ndarray = ndarray.get_type().as_base_type();
|
||||
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_size");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_usize.into()),
|
||||
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
|
||||
Some("size"),
|
||||
None,
|
||||
)
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_nbytes<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_ndarray = ndarray.get_type().as_base_type();
|
||||
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_nbytes");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_usize.into()),
|
||||
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
|
||||
Some("nbytes"),
|
||||
None,
|
||||
)
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_len<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_ndarray = ndarray.get_type().as_base_type();
|
||||
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_len");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_usize.into()),
|
||||
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
|
||||
Some("len"),
|
||||
None,
|
||||
)
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_is_c_contiguous<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let llvm_i1 = ctx.ctx.bool_type();
|
||||
let llvm_ndarray = ndarray.get_type().as_base_type();
|
||||
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_is_c_contiguous");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_i1.into()),
|
||||
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
|
||||
Some("is_c_contiguous"),
|
||||
None,
|
||||
)
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_get_nth_pelement<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
index: IntValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_ndarray = ndarray.get_type().as_base_type();
|
||||
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_nth_pelement");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_pi8.into()),
|
||||
&[(llvm_ndarray.into(), ndarray.as_base_value().into()), (llvm_usize.into(), index.into())],
|
||||
Some("pelement"),
|
||||
None,
|
||||
)
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_get_pelement_by_indices<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
indices: PointerValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_pi8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
let llvm_ndarray = ndarray.get_type().as_base_type();
|
||||
|
||||
let name =
|
||||
get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_get_pelement_by_indices");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(llvm_pi8.into()),
|
||||
&[
|
||||
(llvm_ndarray.into(), ndarray.as_base_value().into()),
|
||||
(llvm_pusize.into(), indices.into()),
|
||||
],
|
||||
Some("pelement"),
|
||||
None,
|
||||
)
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_set_strides_by_shape<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
) {
|
||||
let llvm_ndarray = ndarray.get_type().as_base_type();
|
||||
|
||||
let name =
|
||||
get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_set_strides_by_shape");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
None,
|
||||
&[(llvm_ndarray.into(), ndarray.as_base_value().into())],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn call_nac3_ndarray_copy_data<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
src_ndarray: NDArrayValue<'ctx>,
|
||||
dst_ndarray: NDArrayValue<'ctx>,
|
||||
) {
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_copy_data");
|
||||
|
||||
infer_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
None,
|
||||
&[src_ndarray.as_base_value().into(), dst_ndarray.as_base_value().into()],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
29
nac3core/src/codegen/irrt/ndarray/indexing.rs
Normal file
29
nac3core/src/codegen/irrt/ndarray/indexing.rs
Normal file
@ -0,0 +1,29 @@
|
||||
use crate::codegen::{
|
||||
expr::infer_and_call_function,
|
||||
irrt::get_usize_dependent_function_name,
|
||||
values::{ndarray::NDArrayValue, ArrayLikeValue, ArraySliceValue, ProxyValue},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
pub fn call_nac3_ndarray_index<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
indices: ArraySliceValue<'ctx>,
|
||||
src_ndarray: NDArrayValue<'ctx>,
|
||||
dst_ndarray: NDArrayValue<'ctx>,
|
||||
) {
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_ndarray_index");
|
||||
infer_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
None,
|
||||
&[
|
||||
indices.size(ctx, generator).into(),
|
||||
indices.base_ptr(ctx, generator).into(),
|
||||
src_ndarray.as_base_value().into(),
|
||||
dst_ndarray.as_base_value().into(),
|
||||
],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
70
nac3core/src/codegen/irrt/ndarray/iter.rs
Normal file
70
nac3core/src/codegen/irrt/ndarray/iter.rs
Normal file
@ -0,0 +1,70 @@
|
||||
use inkwell::{
|
||||
values::{BasicValueEnum, IntValue},
|
||||
AddressSpace,
|
||||
};
|
||||
|
||||
use crate::codegen::{
|
||||
expr::{create_and_call_function, infer_and_call_function},
|
||||
irrt::get_usize_dependent_function_name,
|
||||
types::ProxyType,
|
||||
values::{
|
||||
ndarray::{NDArrayValue, NDIterValue},
|
||||
ArrayLikeValue, ArraySliceValue, ProxyValue,
|
||||
},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
pub fn call_nac3_nditer_initialize<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
iter: NDIterValue<'ctx>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
indices: ArraySliceValue<'ctx>,
|
||||
) {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_initialize");
|
||||
|
||||
create_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
None,
|
||||
&[
|
||||
(iter.get_type().as_base_type().into(), iter.as_base_value().into()),
|
||||
(ndarray.get_type().as_base_type().into(), ndarray.as_base_value().into()),
|
||||
(llvm_pusize.into(), indices.base_ptr(ctx, generator).into()),
|
||||
],
|
||||
None,
|
||||
None,
|
||||
);
|
||||
}
|
||||
|
||||
pub fn call_nac3_nditer_has_element<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
iter: NDIterValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_has_element");
|
||||
|
||||
infer_and_call_function(
|
||||
ctx,
|
||||
&name,
|
||||
Some(ctx.ctx.bool_type().into()),
|
||||
&[iter.as_base_value().into()],
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn call_nac3_nditer_next<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
iter: NDIterValue<'ctx>,
|
||||
) {
|
||||
let name = get_usize_dependent_function_name(generator, ctx, "__nac3_nditer_next");
|
||||
|
||||
infer_and_call_function(ctx, &name, None, &[iter.as_base_value().into()], None, None);
|
||||
}
|
391
nac3core/src/codegen/irrt/ndarray/mod.rs
Normal file
391
nac3core/src/codegen/irrt/ndarray/mod.rs
Normal file
@ -0,0 +1,391 @@
|
||||
use inkwell::{
|
||||
types::IntType,
|
||||
values::{BasicValueEnum, CallSiteValue, IntValue},
|
||||
AddressSpace, IntPredicate,
|
||||
};
|
||||
use itertools::Either;
|
||||
|
||||
use crate::codegen::{
|
||||
llvm_intrinsics,
|
||||
macros::codegen_unreachable,
|
||||
stmt::gen_for_callback_incrementing,
|
||||
values::{
|
||||
ndarray::NDArrayValue, ArrayLikeIndexer, ArrayLikeValue, ArraySliceValue,
|
||||
TypedArrayLikeAccessor, TypedArrayLikeAdapter, UntypedArrayLikeAccessor,
|
||||
},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
pub use basic::*;
|
||||
pub use indexing::*;
|
||||
pub use iter::*;
|
||||
|
||||
mod basic;
|
||||
mod indexing;
|
||||
mod iter;
|
||||
|
||||
/// Generates a call to `__nac3_ndarray_calc_size`. Returns an [`IntValue`] representing the
|
||||
/// calculated total size.
|
||||
///
|
||||
/// * `dims` - An [`ArrayLikeIndexer`] containing the size of each dimension.
|
||||
/// * `range` - The dimension index to begin and end (exclusively) calculating the dimensions for,
|
||||
/// or [`None`] if starting from the first dimension and ending at the last dimension
|
||||
/// respectively.
|
||||
pub fn call_ndarray_calc_size<'ctx, G, Dims>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
dims: &Dims,
|
||||
(begin, end): (Option<IntValue<'ctx>>, Option<IntValue<'ctx>>),
|
||||
) -> IntValue<'ctx>
|
||||
where
|
||||
G: CodeGenerator + ?Sized,
|
||||
Dims: ArrayLikeIndexer<'ctx>,
|
||||
{
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
let ndarray_calc_size_fn_name = match llvm_usize.get_bit_width() {
|
||||
32 => "__nac3_ndarray_calc_size",
|
||||
64 => "__nac3_ndarray_calc_size64",
|
||||
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
|
||||
};
|
||||
let ndarray_calc_size_fn_t = llvm_usize.fn_type(
|
||||
&[llvm_pusize.into(), llvm_usize.into(), llvm_usize.into(), llvm_usize.into()],
|
||||
false,
|
||||
);
|
||||
let ndarray_calc_size_fn =
|
||||
ctx.module.get_function(ndarray_calc_size_fn_name).unwrap_or_else(|| {
|
||||
ctx.module.add_function(ndarray_calc_size_fn_name, ndarray_calc_size_fn_t, None)
|
||||
});
|
||||
|
||||
let begin = begin.unwrap_or_else(|| llvm_usize.const_zero());
|
||||
let end = end.unwrap_or_else(|| dims.size(ctx, generator));
|
||||
ctx.builder
|
||||
.build_call(
|
||||
ndarray_calc_size_fn,
|
||||
&[
|
||||
dims.base_ptr(ctx, generator).into(),
|
||||
dims.size(ctx, generator).into(),
|
||||
begin.into(),
|
||||
end.into(),
|
||||
],
|
||||
"",
|
||||
)
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Generates a call to `__nac3_ndarray_calc_nd_indices`. Returns a [`TypeArrayLikeAdpater`]
|
||||
/// containing `i32` indices of the flattened index.
|
||||
///
|
||||
/// * `index` - The index to compute the multidimensional index for.
|
||||
/// * `ndarray` - LLVM pointer to the `NDArray`. This value must be the LLVM representation of an
|
||||
/// `NDArray`.
|
||||
pub fn call_ndarray_calc_nd_indices<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
index: IntValue<'ctx>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
) -> TypedArrayLikeAdapter<'ctx, IntValue<'ctx>> {
|
||||
let llvm_void = ctx.ctx.void_type();
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pi32 = llvm_i32.ptr_type(AddressSpace::default());
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
let ndarray_calc_nd_indices_fn_name = match llvm_usize.get_bit_width() {
|
||||
32 => "__nac3_ndarray_calc_nd_indices",
|
||||
64 => "__nac3_ndarray_calc_nd_indices64",
|
||||
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
|
||||
};
|
||||
let ndarray_calc_nd_indices_fn =
|
||||
ctx.module.get_function(ndarray_calc_nd_indices_fn_name).unwrap_or_else(|| {
|
||||
let fn_type = llvm_void.fn_type(
|
||||
&[llvm_usize.into(), llvm_pusize.into(), llvm_usize.into(), llvm_pi32.into()],
|
||||
false,
|
||||
);
|
||||
|
||||
ctx.module.add_function(ndarray_calc_nd_indices_fn_name, fn_type, None)
|
||||
});
|
||||
|
||||
let ndarray_num_dims = ndarray.load_ndims(ctx);
|
||||
let ndarray_dims = ndarray.shape();
|
||||
|
||||
let indices = ctx.builder.build_array_alloca(llvm_i32, ndarray_num_dims, "").unwrap();
|
||||
|
||||
ctx.builder
|
||||
.build_call(
|
||||
ndarray_calc_nd_indices_fn,
|
||||
&[
|
||||
index.into(),
|
||||
ndarray_dims.base_ptr(ctx, generator).into(),
|
||||
ndarray_num_dims.into(),
|
||||
indices.into(),
|
||||
],
|
||||
"",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
TypedArrayLikeAdapter::from(
|
||||
ArraySliceValue::from_ptr_val(indices, ndarray_num_dims, None),
|
||||
Box::new(|_, v| v.into_int_value()),
|
||||
Box::new(|_, v| v.into()),
|
||||
)
|
||||
}
|
||||
|
||||
fn call_ndarray_flatten_index_impl<'ctx, G, Indices>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
indices: &Indices,
|
||||
) -> IntValue<'ctx>
|
||||
where
|
||||
G: CodeGenerator + ?Sized,
|
||||
Indices: ArrayLikeIndexer<'ctx>,
|
||||
{
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
|
||||
let llvm_pi32 = llvm_i32.ptr_type(AddressSpace::default());
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
debug_assert_eq!(
|
||||
IntType::try_from(indices.element_type(ctx, generator))
|
||||
.map(IntType::get_bit_width)
|
||||
.unwrap_or_default(),
|
||||
llvm_i32.get_bit_width(),
|
||||
"Expected i32 value for argument `indices` to `call_ndarray_flatten_index_impl`"
|
||||
);
|
||||
debug_assert_eq!(
|
||||
indices.size(ctx, generator).get_type().get_bit_width(),
|
||||
llvm_usize.get_bit_width(),
|
||||
"Expected usize integer value for argument `indices_size` to `call_ndarray_flatten_index_impl`"
|
||||
);
|
||||
|
||||
let ndarray_flatten_index_fn_name = match llvm_usize.get_bit_width() {
|
||||
32 => "__nac3_ndarray_flatten_index",
|
||||
64 => "__nac3_ndarray_flatten_index64",
|
||||
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
|
||||
};
|
||||
let ndarray_flatten_index_fn =
|
||||
ctx.module.get_function(ndarray_flatten_index_fn_name).unwrap_or_else(|| {
|
||||
let fn_type = llvm_usize.fn_type(
|
||||
&[llvm_pusize.into(), llvm_usize.into(), llvm_pi32.into(), llvm_usize.into()],
|
||||
false,
|
||||
);
|
||||
|
||||
ctx.module.add_function(ndarray_flatten_index_fn_name, fn_type, None)
|
||||
});
|
||||
|
||||
let ndarray_num_dims = ndarray.load_ndims(ctx);
|
||||
let ndarray_dims = ndarray.shape();
|
||||
|
||||
let index = ctx
|
||||
.builder
|
||||
.build_call(
|
||||
ndarray_flatten_index_fn,
|
||||
&[
|
||||
ndarray_dims.base_ptr(ctx, generator).into(),
|
||||
ndarray_num_dims.into(),
|
||||
indices.base_ptr(ctx, generator).into(),
|
||||
indices.size(ctx, generator).into(),
|
||||
],
|
||||
"",
|
||||
)
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap();
|
||||
|
||||
index
|
||||
}
|
||||
|
||||
/// Generates a call to `__nac3_ndarray_flatten_index`. Returns the flattened index for the
|
||||
/// multidimensional index.
|
||||
///
|
||||
/// * `ndarray` - LLVM pointer to the `NDArray`. This value must be the LLVM representation of an
|
||||
/// `NDArray`.
|
||||
/// * `indices` - The multidimensional index to compute the flattened index for.
|
||||
pub fn call_ndarray_flatten_index<'ctx, G, Index>(
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
indices: &Index,
|
||||
) -> IntValue<'ctx>
|
||||
where
|
||||
G: CodeGenerator + ?Sized,
|
||||
Index: ArrayLikeIndexer<'ctx>,
|
||||
{
|
||||
call_ndarray_flatten_index_impl(generator, ctx, ndarray, indices)
|
||||
}
|
||||
|
||||
/// Generates a call to `__nac3_ndarray_calc_broadcast`. Returns a tuple containing the number of
|
||||
/// dimension and size of each dimension of the resultant `ndarray`.
|
||||
pub fn call_ndarray_calc_broadcast<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
lhs: NDArrayValue<'ctx>,
|
||||
rhs: NDArrayValue<'ctx>,
|
||||
) -> TypedArrayLikeAdapter<'ctx, IntValue<'ctx>> {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
let ndarray_calc_broadcast_fn_name = match llvm_usize.get_bit_width() {
|
||||
32 => "__nac3_ndarray_calc_broadcast",
|
||||
64 => "__nac3_ndarray_calc_broadcast64",
|
||||
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
|
||||
};
|
||||
let ndarray_calc_broadcast_fn =
|
||||
ctx.module.get_function(ndarray_calc_broadcast_fn_name).unwrap_or_else(|| {
|
||||
let fn_type = llvm_usize.fn_type(
|
||||
&[
|
||||
llvm_pusize.into(),
|
||||
llvm_usize.into(),
|
||||
llvm_pusize.into(),
|
||||
llvm_usize.into(),
|
||||
llvm_pusize.into(),
|
||||
],
|
||||
false,
|
||||
);
|
||||
|
||||
ctx.module.add_function(ndarray_calc_broadcast_fn_name, fn_type, None)
|
||||
});
|
||||
|
||||
let lhs_ndims = lhs.load_ndims(ctx);
|
||||
let rhs_ndims = rhs.load_ndims(ctx);
|
||||
let min_ndims = llvm_intrinsics::call_int_umin(ctx, lhs_ndims, rhs_ndims, None);
|
||||
|
||||
gen_for_callback_incrementing(
|
||||
generator,
|
||||
ctx,
|
||||
None,
|
||||
llvm_usize.const_zero(),
|
||||
(min_ndims, false),
|
||||
|generator, ctx, _, idx| {
|
||||
let idx = ctx.builder.build_int_sub(min_ndims, idx, "").unwrap();
|
||||
let (lhs_dim_sz, rhs_dim_sz) = unsafe {
|
||||
(
|
||||
lhs.shape().get_typed_unchecked(ctx, generator, &idx, None),
|
||||
rhs.shape().get_typed_unchecked(ctx, generator, &idx, None),
|
||||
)
|
||||
};
|
||||
|
||||
let llvm_usize_const_one = llvm_usize.const_int(1, false);
|
||||
let lhs_eqz = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::EQ, lhs_dim_sz, llvm_usize_const_one, "")
|
||||
.unwrap();
|
||||
let rhs_eqz = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::EQ, rhs_dim_sz, llvm_usize_const_one, "")
|
||||
.unwrap();
|
||||
let lhs_or_rhs_eqz = ctx.builder.build_or(lhs_eqz, rhs_eqz, "").unwrap();
|
||||
|
||||
let lhs_eq_rhs = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::EQ, lhs_dim_sz, rhs_dim_sz, "")
|
||||
.unwrap();
|
||||
|
||||
let is_compatible = ctx.builder.build_or(lhs_or_rhs_eqz, lhs_eq_rhs, "").unwrap();
|
||||
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
is_compatible,
|
||||
"0:ValueError",
|
||||
"operands could not be broadcast together",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
llvm_usize.const_int(1, false),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let max_ndims = llvm_intrinsics::call_int_umax(ctx, lhs_ndims, rhs_ndims, None);
|
||||
let lhs_dims = lhs.shape().base_ptr(ctx, generator);
|
||||
let lhs_ndims = lhs.load_ndims(ctx);
|
||||
let rhs_dims = rhs.shape().base_ptr(ctx, generator);
|
||||
let rhs_ndims = rhs.load_ndims(ctx);
|
||||
let out_dims = ctx.builder.build_array_alloca(llvm_usize, max_ndims, "").unwrap();
|
||||
let out_dims = ArraySliceValue::from_ptr_val(out_dims, max_ndims, None);
|
||||
|
||||
ctx.builder
|
||||
.build_call(
|
||||
ndarray_calc_broadcast_fn,
|
||||
&[
|
||||
lhs_dims.into(),
|
||||
lhs_ndims.into(),
|
||||
rhs_dims.into(),
|
||||
rhs_ndims.into(),
|
||||
out_dims.base_ptr(ctx, generator).into(),
|
||||
],
|
||||
"",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
TypedArrayLikeAdapter::from(
|
||||
out_dims,
|
||||
Box::new(|_, v| v.into_int_value()),
|
||||
Box::new(|_, v| v.into()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Generates a call to `__nac3_ndarray_calc_broadcast_idx`. Returns an [`ArrayAllocaValue`]
|
||||
/// containing the indices used for accessing `array` corresponding to the index of the broadcasted
|
||||
/// array `broadcast_idx`.
|
||||
pub fn call_ndarray_calc_broadcast_index<
|
||||
'ctx,
|
||||
G: CodeGenerator + ?Sized,
|
||||
BroadcastIdx: UntypedArrayLikeAccessor<'ctx>,
|
||||
>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
array: NDArrayValue<'ctx>,
|
||||
broadcast_idx: &BroadcastIdx,
|
||||
) -> TypedArrayLikeAdapter<'ctx, IntValue<'ctx>> {
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let llvm_pi32 = llvm_i32.ptr_type(AddressSpace::default());
|
||||
let llvm_pusize = llvm_usize.ptr_type(AddressSpace::default());
|
||||
|
||||
let ndarray_calc_broadcast_fn_name = match llvm_usize.get_bit_width() {
|
||||
32 => "__nac3_ndarray_calc_broadcast_idx",
|
||||
64 => "__nac3_ndarray_calc_broadcast_idx64",
|
||||
bw => codegen_unreachable!(ctx, "Unsupported size type bit width: {}", bw),
|
||||
};
|
||||
let ndarray_calc_broadcast_fn =
|
||||
ctx.module.get_function(ndarray_calc_broadcast_fn_name).unwrap_or_else(|| {
|
||||
let fn_type = llvm_usize.fn_type(
|
||||
&[llvm_pusize.into(), llvm_usize.into(), llvm_pi32.into(), llvm_pi32.into()],
|
||||
false,
|
||||
);
|
||||
|
||||
ctx.module.add_function(ndarray_calc_broadcast_fn_name, fn_type, None)
|
||||
});
|
||||
|
||||
let broadcast_size = broadcast_idx.size(ctx, generator);
|
||||
let out_idx = ctx.builder.build_array_alloca(llvm_i32, broadcast_size, "").unwrap();
|
||||
|
||||
let array_dims = array.shape().base_ptr(ctx, generator);
|
||||
let array_ndims = array.load_ndims(ctx);
|
||||
let broadcast_idx_ptr = unsafe {
|
||||
broadcast_idx.ptr_offset_unchecked(ctx, generator, &llvm_usize.const_zero(), None)
|
||||
};
|
||||
|
||||
ctx.builder
|
||||
.build_call(
|
||||
ndarray_calc_broadcast_fn,
|
||||
&[array_dims.into(), array_ndims.into(), broadcast_idx_ptr.into(), out_idx.into()],
|
||||
"",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
TypedArrayLikeAdapter::from(
|
||||
ArraySliceValue::from_ptr_val(out_idx, broadcast_size, None),
|
||||
Box::new(|_, v| v.into_int_value()),
|
||||
Box::new(|_, v| v.into()),
|
||||
)
|
||||
}
|
42
nac3core/src/codegen/irrt/range.rs
Normal file
42
nac3core/src/codegen/irrt/range.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use inkwell::{
|
||||
values::{BasicValueEnum, CallSiteValue, IntValue},
|
||||
IntPredicate,
|
||||
};
|
||||
use itertools::Either;
|
||||
|
||||
use crate::codegen::{CodeGenContext, CodeGenerator};
|
||||
|
||||
pub fn calculate_len_for_slice_range<'ctx, G: CodeGenerator + ?Sized>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
start: IntValue<'ctx>,
|
||||
end: IntValue<'ctx>,
|
||||
step: IntValue<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
const SYMBOL: &str = "__nac3_range_slice_len";
|
||||
let len_func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
|
||||
let i32_t = ctx.ctx.i32_type();
|
||||
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into(), i32_t.into()], false);
|
||||
ctx.module.add_function(SYMBOL, fn_t, None)
|
||||
});
|
||||
|
||||
// assert step != 0, throw exception if not
|
||||
let not_zero = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::NE, step, step.get_type().const_zero(), "range_step_ne")
|
||||
.unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
not_zero,
|
||||
"0:ValueError",
|
||||
"step must not be zero",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
ctx.builder
|
||||
.build_call(len_func, &[start.into(), end.into(), step.into()], "calc_len")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
39
nac3core/src/codegen/irrt/slice.rs
Normal file
39
nac3core/src/codegen/irrt/slice.rs
Normal file
@ -0,0 +1,39 @@
|
||||
use inkwell::values::{BasicValueEnum, CallSiteValue, IntValue};
|
||||
use itertools::Either;
|
||||
|
||||
use nac3parser::ast::Expr;
|
||||
|
||||
use crate::{
|
||||
codegen::{CodeGenContext, CodeGenerator},
|
||||
typecheck::typedef::Type,
|
||||
};
|
||||
|
||||
/// this function allows index out of range, since python
|
||||
/// allows index out of range in slice (`a = [1,2,3]; a[1:10] == [2,3]`).
|
||||
pub fn handle_slice_index_bound<'ctx, G: CodeGenerator>(
|
||||
i: &Expr<Option<Type>>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
length: IntValue<'ctx>,
|
||||
) -> Result<Option<IntValue<'ctx>>, String> {
|
||||
const SYMBOL: &str = "__nac3_slice_index_bound";
|
||||
let func = ctx.module.get_function(SYMBOL).unwrap_or_else(|| {
|
||||
let i32_t = ctx.ctx.i32_type();
|
||||
let fn_t = i32_t.fn_type(&[i32_t.into(), i32_t.into()], false);
|
||||
ctx.module.add_function(SYMBOL, fn_t, None)
|
||||
});
|
||||
|
||||
let i = if let Some(v) = generator.gen_expr(ctx, i)? {
|
||||
v.to_basic_value_enum(ctx, generator, i.custom.unwrap())?
|
||||
} else {
|
||||
return Ok(None);
|
||||
};
|
||||
Ok(Some(
|
||||
ctx.builder
|
||||
.build_call(func, &[i.into(), length.into()], "bounded_ind")
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap(),
|
||||
))
|
||||
}
|
410
nac3core/src/codegen/llvm_intrinsics.rs
Normal file
410
nac3core/src/codegen/llvm_intrinsics.rs
Normal file
@ -0,0 +1,410 @@
|
||||
use inkwell::{
|
||||
context::Context,
|
||||
intrinsics::Intrinsic,
|
||||
types::{AnyTypeEnum::IntType, FloatType},
|
||||
values::{BasicValueEnum, CallSiteValue, FloatValue, IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
use itertools::Either;
|
||||
|
||||
use super::CodeGenContext;
|
||||
|
||||
/// Returns the string representation for the floating-point type `ft` when used in intrinsic
|
||||
/// functions.
|
||||
fn get_float_intrinsic_repr(ctx: &Context, ft: FloatType) -> &'static str {
|
||||
// Standard LLVM floating-point types
|
||||
if ft == ctx.f16_type() {
|
||||
return "f16";
|
||||
}
|
||||
if ft == ctx.f32_type() {
|
||||
return "f32";
|
||||
}
|
||||
if ft == ctx.f64_type() {
|
||||
return "f64";
|
||||
}
|
||||
if ft == ctx.f128_type() {
|
||||
return "f128";
|
||||
}
|
||||
|
||||
// Non-standard floating-point types
|
||||
if ft == ctx.x86_f80_type() {
|
||||
return "f80";
|
||||
}
|
||||
if ft == ctx.ppc_f128_type() {
|
||||
return "ppcf128";
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
/// Invokes the [`llvm.va_start`](https://llvm.org/docs/LangRef.html#llvm-va-start-intrinsic)
|
||||
/// intrinsic.
|
||||
pub fn call_va_start<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue<'ctx>) {
|
||||
const FN_NAME: &str = "llvm.va_start";
|
||||
|
||||
let intrinsic_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
|
||||
let llvm_void = ctx.ctx.void_type();
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
let fn_type = llvm_void.fn_type(&[llvm_p0i8.into()], false);
|
||||
|
||||
ctx.module.add_function(FN_NAME, fn_type, None)
|
||||
});
|
||||
|
||||
ctx.builder.build_call(intrinsic_fn, &[arglist.into()], "").unwrap();
|
||||
}
|
||||
|
||||
/// Invokes the [`llvm.va_start`](https://llvm.org/docs/LangRef.html#llvm-va-start-intrinsic)
|
||||
/// intrinsic.
|
||||
pub fn call_va_end<'ctx>(ctx: &CodeGenContext<'ctx, '_>, arglist: PointerValue<'ctx>) {
|
||||
const FN_NAME: &str = "llvm.va_end";
|
||||
|
||||
let intrinsic_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
|
||||
let llvm_void = ctx.ctx.void_type();
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
let fn_type = llvm_void.fn_type(&[llvm_p0i8.into()], false);
|
||||
|
||||
ctx.module.add_function(FN_NAME, fn_type, None)
|
||||
});
|
||||
|
||||
ctx.builder.build_call(intrinsic_fn, &[arglist.into()], "").unwrap();
|
||||
}
|
||||
|
||||
/// Invokes the [`llvm.stacksave`](https://llvm.org/docs/LangRef.html#llvm-stacksave-intrinsic)
|
||||
/// intrinsic.
|
||||
pub fn call_stacksave<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
const FN_NAME: &str = "llvm.stacksave";
|
||||
|
||||
let intrinsic_fn = Intrinsic::find(FN_NAME)
|
||||
.and_then(|intrinsic| intrinsic.get_declaration(&ctx.module, &[]))
|
||||
.unwrap();
|
||||
|
||||
ctx.builder
|
||||
.build_call(intrinsic_fn, &[], name.unwrap_or_default())
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_pointer_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Invokes the
|
||||
/// [`llvm.stackrestore`](https://llvm.org/docs/LangRef.html#llvm-stackrestore-intrinsic) intrinsic.
|
||||
///
|
||||
/// - `ptr`: The pointer storing the address to restore the stack to.
|
||||
pub fn call_stackrestore<'ctx>(ctx: &CodeGenContext<'ctx, '_>, ptr: PointerValue<'ctx>) {
|
||||
const FN_NAME: &str = "llvm.stackrestore";
|
||||
|
||||
/*
|
||||
SEE https://github.com/TheDan64/inkwell/issues/496
|
||||
|
||||
We want `llvm.stackrestore`, but the following would generate `llvm.stackrestore.p0i8`.
|
||||
```ignore
|
||||
let intrinsic_fn = Intrinsic::find(FN_NAME)
|
||||
.and_then(|intrinsic| intrinsic.get_declaration(&ctx.module, &[llvm_p0i8.into()]))
|
||||
.unwrap();
|
||||
```
|
||||
|
||||
Temp workaround by manually declaring the intrinsic with the correct function name instead.
|
||||
*/
|
||||
let intrinsic_fn = ctx.module.get_function(FN_NAME).unwrap_or_else(|| {
|
||||
let llvm_void = ctx.ctx.void_type();
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
let fn_type = llvm_void.fn_type(&[llvm_p0i8.into()], false);
|
||||
|
||||
ctx.module.add_function(FN_NAME, fn_type, None)
|
||||
});
|
||||
|
||||
ctx.builder.build_call(intrinsic_fn, &[ptr.into()], "").unwrap();
|
||||
}
|
||||
|
||||
/// Invokes the [`llvm.memcpy`](https://llvm.org/docs/LangRef.html#llvm-memcpy-intrinsic) intrinsic.
|
||||
///
|
||||
/// * `dest` - The pointer to the destination. Must be a pointer to an integer type.
|
||||
/// * `src` - The pointer to the source. Must be a pointer to an integer type.
|
||||
/// * `len` - The number of bytes to copy.
|
||||
/// * `is_volatile` - Whether the `memcpy` operation should be `volatile`.
|
||||
pub fn call_memcpy<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
dest: PointerValue<'ctx>,
|
||||
src: PointerValue<'ctx>,
|
||||
len: IntValue<'ctx>,
|
||||
is_volatile: IntValue<'ctx>,
|
||||
) {
|
||||
const FN_NAME: &str = "llvm.memcpy";
|
||||
|
||||
debug_assert!(dest.get_type().get_element_type().is_int_type());
|
||||
debug_assert!(src.get_type().get_element_type().is_int_type());
|
||||
debug_assert_eq!(
|
||||
dest.get_type().get_element_type().into_int_type().get_bit_width(),
|
||||
src.get_type().get_element_type().into_int_type().get_bit_width(),
|
||||
);
|
||||
debug_assert!(matches!(len.get_type().get_bit_width(), 32 | 64));
|
||||
debug_assert_eq!(is_volatile.get_type().get_bit_width(), 1);
|
||||
|
||||
let llvm_dest_t = dest.get_type();
|
||||
let llvm_src_t = src.get_type();
|
||||
let llvm_len_t = len.get_type();
|
||||
|
||||
let intrinsic_fn = Intrinsic::find(FN_NAME)
|
||||
.and_then(|intrinsic| {
|
||||
intrinsic.get_declaration(
|
||||
&ctx.module,
|
||||
&[llvm_dest_t.into(), llvm_src_t.into(), llvm_len_t.into()],
|
||||
)
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
ctx.builder
|
||||
.build_call(intrinsic_fn, &[dest.into(), src.into(), len.into(), is_volatile.into()], "")
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Invokes the `llvm.memcpy` intrinsic.
|
||||
///
|
||||
/// Unlike [`call_memcpy`], this function accepts any type of pointer value. If `dest` or `src` is
|
||||
/// not a pointer to an integer, the pointer(s) will be cast to `i8*` before invoking `memcpy`.
|
||||
pub fn call_memcpy_generic<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
dest: PointerValue<'ctx>,
|
||||
src: PointerValue<'ctx>,
|
||||
len: IntValue<'ctx>,
|
||||
is_volatile: IntValue<'ctx>,
|
||||
) {
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
|
||||
let dest_elem_t = dest.get_type().get_element_type();
|
||||
let src_elem_t = src.get_type().get_element_type();
|
||||
|
||||
let dest = if matches!(dest_elem_t, IntType(t) if t.get_bit_width() == 8) {
|
||||
dest
|
||||
} else {
|
||||
ctx.builder
|
||||
.build_bit_cast(dest, llvm_p0i8, "")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap()
|
||||
};
|
||||
let src = if matches!(src_elem_t, IntType(t) if t.get_bit_width() == 8) {
|
||||
src
|
||||
} else {
|
||||
ctx.builder
|
||||
.build_bit_cast(src, llvm_p0i8, "")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
call_memcpy(ctx, dest, src, len, is_volatile);
|
||||
}
|
||||
|
||||
/// Invokes the `llvm.memcpy` intrinsic.
|
||||
///
|
||||
/// Unlike [`call_memcpy`], this function accepts any type of pointer value. If `dest` or `src` is
|
||||
/// not a pointer to an integer, the pointer(s) will be cast to `i8*` before invoking `memcpy`.
|
||||
/// Moreover, `len` now refers to the number of elements to copy (rather than number of bytes to
|
||||
/// copy).
|
||||
pub fn call_memcpy_generic_array<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
dest: PointerValue<'ctx>,
|
||||
src: PointerValue<'ctx>,
|
||||
len: IntValue<'ctx>,
|
||||
is_volatile: IntValue<'ctx>,
|
||||
) {
|
||||
let llvm_i8 = ctx.ctx.i8_type();
|
||||
let llvm_p0i8 = llvm_i8.ptr_type(AddressSpace::default());
|
||||
let llvm_sizeof_expr_t = llvm_i8.size_of().get_type();
|
||||
|
||||
let dest_elem_t = dest.get_type().get_element_type();
|
||||
let src_elem_t = src.get_type().get_element_type();
|
||||
|
||||
let dest = if matches!(dest_elem_t, IntType(t) if t.get_bit_width() == 8) {
|
||||
dest
|
||||
} else {
|
||||
ctx.builder
|
||||
.build_bit_cast(dest, llvm_p0i8, "")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap()
|
||||
};
|
||||
let src = if matches!(src_elem_t, IntType(t) if t.get_bit_width() == 8) {
|
||||
src
|
||||
} else {
|
||||
ctx.builder
|
||||
.build_bit_cast(src, llvm_p0i8, "")
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let len = ctx.builder.build_int_z_extend_or_bit_cast(len, llvm_sizeof_expr_t, "").unwrap();
|
||||
let len = ctx.builder.build_int_mul(len, src_elem_t.size_of().unwrap(), "").unwrap();
|
||||
|
||||
call_memcpy(ctx, dest, src, len, is_volatile);
|
||||
}
|
||||
|
||||
/// Macro to find and generate build call for llvm intrinsic (body of llvm intrinsic function)
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `$ctx:ident`: Reference to the current Code Generation Context
|
||||
/// * `$name:ident`: Optional name to be assigned to the llvm build call (Option<&str>)
|
||||
/// * `$llvm_name:literal`: Name of underlying llvm intrinsic function
|
||||
/// * `$map_fn:ident`: Mapping function to be applied on `BasicValue` (`BasicValue` -> Function Return Type).
|
||||
/// Use `BasicValueEnum::into_int_value` for Integer return type and
|
||||
/// `BasicValueEnum::into_float_value` for Float return type
|
||||
/// * `$llvm_ty:ident`: Type of first operand
|
||||
/// * `,($val:ident)*`: Comma separated list of operands
|
||||
macro_rules! generate_llvm_intrinsic_fn_body {
|
||||
($ctx:ident, $name:ident, $llvm_name:literal, $map_fn:expr, $llvm_ty:ident $(,$val:ident)*) => {{
|
||||
const FN_NAME: &str = concat!("llvm.", $llvm_name);
|
||||
let intrinsic_fn = Intrinsic::find(FN_NAME).and_then(|intrinsic| intrinsic.get_declaration(&$ctx.module, &[$llvm_ty.into()])).unwrap();
|
||||
$ctx.builder.build_call(intrinsic_fn, &[$($val.into()),*], $name.unwrap_or_default()).map(CallSiteValue::try_as_basic_value).map(|v| v.map_left($map_fn)).map(Either::unwrap_left).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
/// Macro to generate the llvm intrinsic function using [`generate_llvm_intrinsic_fn_body`].
|
||||
///
|
||||
/// Arguments:
|
||||
/// * `float/int`: Indicates the return and argument type of the function
|
||||
/// * `$fn_name:ident`: The identifier of the rust function to be generated
|
||||
/// * `$llvm_name:literal`: Name of underlying llvm intrinsic function.
|
||||
/// Omit "llvm." prefix from the function name i.e. use "ceil" instead of "llvm.ceil"
|
||||
/// * `$val:ident`: The operand for unary operations
|
||||
/// * `$val1:ident`, `$val2:ident`: The operands for binary operations
|
||||
macro_rules! generate_llvm_intrinsic_fn {
|
||||
("float", $fn_name:ident, $llvm_name:literal, $val:ident) => {
|
||||
#[doc = concat!("Invokes the [`", stringify!($llvm_name), "`](https://llvm.org/docs/LangRef.html#llvm-", stringify!($llvm_name), "-intrinsic) intrinsic." )]
|
||||
pub fn $fn_name<'ctx> (
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
$val: FloatValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> FloatValue<'ctx> {
|
||||
let llvm_ty = $val.get_type();
|
||||
generate_llvm_intrinsic_fn_body!(ctx, name, $llvm_name, BasicValueEnum::into_float_value, llvm_ty, $val)
|
||||
}
|
||||
};
|
||||
("float", $fn_name:ident, $llvm_name:literal, $val1:ident, $val2:ident) => {
|
||||
#[doc = concat!("Invokes the [`", stringify!($llvm_name), "`](https://llvm.org/docs/LangRef.html#llvm-", stringify!($llvm_name), "-intrinsic) intrinsic." )]
|
||||
pub fn $fn_name<'ctx> (
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
$val1: FloatValue<'ctx>,
|
||||
$val2: FloatValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> FloatValue<'ctx> {
|
||||
debug_assert_eq!($val1.get_type(), $val2.get_type());
|
||||
let llvm_ty = $val1.get_type();
|
||||
generate_llvm_intrinsic_fn_body!(ctx, name, $llvm_name, BasicValueEnum::into_float_value, llvm_ty, $val1, $val2)
|
||||
}
|
||||
};
|
||||
("int", $fn_name:ident, $llvm_name:literal, $val1:ident, $val2:ident) => {
|
||||
#[doc = concat!("Invokes the [`", stringify!($llvm_name), "`](https://llvm.org/docs/LangRef.html#llvm-", stringify!($llvm_name), "-intrinsic) intrinsic." )]
|
||||
pub fn $fn_name<'ctx> (
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
$val1: IntValue<'ctx>,
|
||||
$val2: IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> IntValue<'ctx> {
|
||||
debug_assert_eq!($val1.get_type().get_bit_width(), $val2.get_type().get_bit_width());
|
||||
let llvm_ty = $val1.get_type();
|
||||
generate_llvm_intrinsic_fn_body!(ctx, name, $llvm_name, BasicValueEnum::into_int_value, llvm_ty, $val1, $val2)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Invokes the [`llvm.abs`](https://llvm.org/docs/LangRef.html#llvm-abs-intrinsic) intrinsic.
|
||||
///
|
||||
/// * `src` - The value for which the absolute value is to be returned.
|
||||
/// * `is_int_min_poison` - Whether `poison` is to be returned if `src` is `INT_MIN`.
|
||||
pub fn call_int_abs<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
src: IntValue<'ctx>,
|
||||
is_int_min_poison: IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> IntValue<'ctx> {
|
||||
debug_assert_eq!(is_int_min_poison.get_type().get_bit_width(), 1);
|
||||
debug_assert!(is_int_min_poison.is_const());
|
||||
|
||||
let src_type = src.get_type();
|
||||
generate_llvm_intrinsic_fn_body!(
|
||||
ctx,
|
||||
name,
|
||||
"abs",
|
||||
BasicValueEnum::into_int_value,
|
||||
src_type,
|
||||
src,
|
||||
is_int_min_poison
|
||||
)
|
||||
}
|
||||
|
||||
generate_llvm_intrinsic_fn!("int", call_int_smax, "smax", a, b);
|
||||
generate_llvm_intrinsic_fn!("int", call_int_smin, "smin", a, b);
|
||||
generate_llvm_intrinsic_fn!("int", call_int_umax, "umax", a, b);
|
||||
generate_llvm_intrinsic_fn!("int", call_int_umin, "umin", a, b);
|
||||
generate_llvm_intrinsic_fn!("int", call_expect, "expect", val, expected_val);
|
||||
|
||||
generate_llvm_intrinsic_fn!("float", call_float_sqrt, "sqrt", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_sin, "sin", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_cos, "cos", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_pow, "pow", val, power);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_exp, "exp", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_exp2, "exp2", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_log, "log", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_log10, "log10", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_log2, "log2", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_fabs, "fabs", src);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_minnum, "minnum", val, power);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_maxnum, "maxnum", val, power);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_copysign, "copysign", mag, sgn);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_floor, "floor", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_ceil, "ceil", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_round, "round", val);
|
||||
generate_llvm_intrinsic_fn!("float", call_float_rint, "rint", val);
|
||||
|
||||
/// Invokes the [`llvm.powi`](https://llvm.org/docs/LangRef.html#llvm-powi-intrinsic) intrinsic.
|
||||
pub fn call_float_powi<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
val: FloatValue<'ctx>,
|
||||
power: IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> FloatValue<'ctx> {
|
||||
const FN_NAME: &str = "llvm.powi";
|
||||
|
||||
let llvm_val_t = val.get_type();
|
||||
let llvm_power_t = power.get_type();
|
||||
|
||||
let intrinsic_fn = Intrinsic::find(FN_NAME)
|
||||
.and_then(|intrinsic| {
|
||||
intrinsic.get_declaration(&ctx.module, &[llvm_val_t.into(), llvm_power_t.into()])
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
ctx.builder
|
||||
.build_call(intrinsic_fn, &[val.into(), power.into()], name.unwrap_or_default())
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_float_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Invokes the [`llvm.ctpop`](https://llvm.org/docs/LangRef.html#llvm-ctpop-intrinsic) intrinsic.
|
||||
pub fn call_int_ctpop<'ctx>(
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
src: IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> IntValue<'ctx> {
|
||||
const FN_NAME: &str = "llvm.ctpop";
|
||||
|
||||
let llvm_src_t = src.get_type();
|
||||
|
||||
let intrinsic_fn = Intrinsic::find(FN_NAME)
|
||||
.and_then(|intrinsic| intrinsic.get_declaration(&ctx.module, &[llvm_src_t.into()]))
|
||||
.unwrap();
|
||||
|
||||
ctx.builder
|
||||
.build_call(intrinsic_fn, &[src.into()], name.unwrap_or_default())
|
||||
.map(CallSiteValue::try_as_basic_value)
|
||||
.map(|v| v.map_left(BasicValueEnum::into_int_value))
|
||||
.map(Either::unwrap_left)
|
||||
.unwrap()
|
||||
}
|
File diff suppressed because it is too large
Load Diff
2508
nac3core/src/codegen/numpy.rs
Normal file
2508
nac3core/src/codegen/numpy.rs
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,25 +1,37 @@
|
||||
use crate::{
|
||||
codegen::{
|
||||
concrete_type::ConcreteTypeStore, CodeGenContext, CodeGenTask, DefaultCodeGenerator,
|
||||
WithCall, WorkerRegistry,
|
||||
},
|
||||
symbol_resolver::{SymbolResolver, ValueEnum},
|
||||
toplevel::{
|
||||
composer::TopLevelComposer, DefinitionId, FunInstance, TopLevelContext, TopLevelDef,
|
||||
},
|
||||
typecheck::{
|
||||
type_inferencer::{FunctionData, Inferencer, PrimitiveStore},
|
||||
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier},
|
||||
},
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use indoc::indoc;
|
||||
use inkwell::{
|
||||
targets::{InitializationConfig, Target},
|
||||
OptimizationLevel,
|
||||
};
|
||||
use nac3parser::{
|
||||
ast::{fold::Fold, StrRef},
|
||||
ast::{fold::Fold, FileName, StrRef},
|
||||
parser::parse_program,
|
||||
};
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{
|
||||
concrete_type::ConcreteTypeStore,
|
||||
types::{ndarray::NDArrayType, ListType, ProxyType, RangeType},
|
||||
CodeGenContext, CodeGenLLVMOptions, CodeGenTargetMachineOptions, CodeGenTask, CodeGenerator,
|
||||
DefaultCodeGenerator, WithCall, WorkerRegistry,
|
||||
};
|
||||
use crate::{
|
||||
symbol_resolver::{SymbolResolver, ValueEnum},
|
||||
toplevel::{
|
||||
composer::{ComposerConfig, TopLevelComposer},
|
||||
DefinitionId, FunInstance, TopLevelContext, TopLevelDef,
|
||||
},
|
||||
typecheck::{
|
||||
type_inferencer::{FunctionData, IdentifierInfo, Inferencer, PrimitiveStore},
|
||||
typedef::{FunSignature, FuncArg, Type, TypeEnum, Unifier, VarMap},
|
||||
},
|
||||
};
|
||||
|
||||
struct Resolver {
|
||||
id_to_type: HashMap<StrRef, Type>,
|
||||
@ -48,23 +60,24 @@ impl SymbolResolver for Resolver {
|
||||
_: &PrimitiveStore,
|
||||
str: StrRef,
|
||||
) -> Result<Type, String> {
|
||||
self.id_to_type.get(&str).cloned().ok_or_else(|| format!("cannot find symbol `{}`", str))
|
||||
self.id_to_type.get(&str).copied().ok_or_else(|| format!("cannot find symbol `{str}`"))
|
||||
}
|
||||
|
||||
fn get_symbol_value<'ctx, 'a>(
|
||||
fn get_symbol_value<'ctx>(
|
||||
&self,
|
||||
_: StrRef,
|
||||
_: &mut CodeGenContext<'ctx, 'a>,
|
||||
_: &mut CodeGenContext<'ctx, '_>,
|
||||
_: &mut dyn CodeGenerator,
|
||||
) -> Option<ValueEnum<'ctx>> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, String> {
|
||||
fn get_identifier_def(&self, id: StrRef) -> Result<DefinitionId, HashSet<String>> {
|
||||
self.id_to_def
|
||||
.read()
|
||||
.get(&id)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("cannot find symbol `{}`", id))
|
||||
.copied()
|
||||
.ok_or_else(|| HashSet::from([format!("cannot find symbol `{id}`")]))
|
||||
}
|
||||
|
||||
fn get_string_id(&self, _: &str) -> i32 {
|
||||
@ -83,9 +96,9 @@ fn test_primitives() {
|
||||
d = a if c == 1 else 0
|
||||
return d
|
||||
"};
|
||||
let statements = parse_program(source, Default::default()).unwrap();
|
||||
let statements = parse_program(source, FileName::default()).unwrap();
|
||||
|
||||
let composer: TopLevelComposer = Default::default();
|
||||
let composer = TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 32).0;
|
||||
let mut unifier = composer.unifier.clone();
|
||||
let primitives = composer.primitives_ty;
|
||||
let top_level = Arc::new(composer.make_top_level_context());
|
||||
@ -94,17 +107,27 @@ fn test_primitives() {
|
||||
let resolver = Arc::new(Resolver {
|
||||
id_to_type: HashMap::new(),
|
||||
id_to_def: RwLock::new(HashMap::new()),
|
||||
class_names: Default::default(),
|
||||
class_names: HashMap::default(),
|
||||
}) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
|
||||
let threads = vec![DefaultCodeGenerator::new("test".into(), 32).into()];
|
||||
let signature = FunSignature {
|
||||
args: vec![
|
||||
FuncArg { name: "a".into(), ty: primitives.int32, default_value: None },
|
||||
FuncArg { name: "b".into(), ty: primitives.int32, default_value: None },
|
||||
FuncArg {
|
||||
name: "a".into(),
|
||||
ty: primitives.int32,
|
||||
default_value: None,
|
||||
is_vararg: false,
|
||||
},
|
||||
FuncArg {
|
||||
name: "b".into(),
|
||||
ty: primitives.int32,
|
||||
default_value: None,
|
||||
is_vararg: false,
|
||||
},
|
||||
],
|
||||
ret: primitives.int32,
|
||||
vars: HashMap::new(),
|
||||
vars: VarMap::new(),
|
||||
};
|
||||
|
||||
let mut store = ConcreteTypeStore::new();
|
||||
@ -119,12 +142,13 @@ fn test_primitives() {
|
||||
};
|
||||
let mut virtual_checks = Vec::new();
|
||||
let mut calls = HashMap::new();
|
||||
let mut identifiers: HashSet<_> = ["a".into(), "b".into()].iter().cloned().collect();
|
||||
let mut identifiers: HashMap<_, _> =
|
||||
["a".into(), "b".into()].map(|id| (id, IdentifierInfo::default())).into();
|
||||
let mut inferencer = Inferencer {
|
||||
top_level: &top_level,
|
||||
function_data: &mut function_data,
|
||||
unifier: &mut unifier,
|
||||
variable_mapping: Default::default(),
|
||||
variable_mapping: HashMap::default(),
|
||||
primitives: &primitives,
|
||||
virtual_checks: &mut virtual_checks,
|
||||
calls: &mut calls,
|
||||
@ -148,7 +172,7 @@ fn test_primitives() {
|
||||
});
|
||||
|
||||
let task = CodeGenTask {
|
||||
subst: Default::default(),
|
||||
subst: Vec::default(),
|
||||
symbol_name: "testing".into(),
|
||||
body: Arc::new(statements),
|
||||
unifier_index: 0,
|
||||
@ -180,28 +204,48 @@ fn test_primitives() {
|
||||
let expected = indoc! {"
|
||||
; ModuleID = 'test'
|
||||
source_filename = \"test\"
|
||||
|
||||
define i32 @testing(i32 %0, i32 %1) {
|
||||
target datalayout = \"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128\"
|
||||
target triple = \"x86_64-unknown-linux-gnu\"
|
||||
|
||||
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn
|
||||
define i32 @testing(i32 %0, i32 %1) local_unnamed_addr #0 !dbg !4 {
|
||||
init:
|
||||
%add = add i32 %0, %1
|
||||
%cmp = icmp eq i32 %add, 1
|
||||
br i1 %cmp, label %then, label %else
|
||||
|
||||
then: ; preds = %init
|
||||
br label %cont
|
||||
|
||||
else: ; preds = %init
|
||||
br label %cont
|
||||
|
||||
cont: ; preds = %else, %then
|
||||
%if_exp_result.0 = phi i32 [ %0, %then ], [ 0, %else ]
|
||||
ret i32 %if_exp_result.0
|
||||
%add = add i32 %1, %0, !dbg !9
|
||||
%cmp = icmp eq i32 %add, 1, !dbg !10
|
||||
%. = select i1 %cmp, i32 %0, i32 0, !dbg !11
|
||||
ret i32 %., !dbg !12
|
||||
}
|
||||
"}
|
||||
|
||||
attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn }
|
||||
|
||||
!llvm.module.flags = !{!0, !1}
|
||||
!llvm.dbg.cu = !{!2}
|
||||
|
||||
!0 = !{i32 2, !\"Debug Info Version\", i32 3}
|
||||
!1 = !{i32 2, !\"Dwarf Version\", i32 4}
|
||||
!2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
|
||||
!3 = !DIFile(filename: \"unknown\", directory: \"\")
|
||||
!4 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !5, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !8)
|
||||
!5 = !DISubroutineType(flags: DIFlagPublic, types: !6)
|
||||
!6 = !{!7}
|
||||
!7 = !DIBasicType(name: \"_\", flags: DIFlagPublic)
|
||||
!8 = !{}
|
||||
!9 = !DILocation(line: 1, column: 9, scope: !4)
|
||||
!10 = !DILocation(line: 2, column: 15, scope: !4)
|
||||
!11 = !DILocation(line: 0, scope: !4)
|
||||
!12 = !DILocation(line: 3, column: 8, scope: !4)
|
||||
"}
|
||||
.trim();
|
||||
assert_eq!(expected, module.print_to_string().to_str().unwrap().trim());
|
||||
})));
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, f);
|
||||
|
||||
Target::initialize_all(&InitializationConfig::default());
|
||||
|
||||
let llvm_options = CodeGenLLVMOptions {
|
||||
opt_level: OptimizationLevel::Default,
|
||||
target: CodeGenTargetMachineOptions::from_host_triple(),
|
||||
};
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &llvm_options, &f);
|
||||
registry.add_task(task);
|
||||
registry.wait_tasks_complete(handles);
|
||||
}
|
||||
@ -212,23 +256,28 @@ fn test_simple_call() {
|
||||
a = foo(a)
|
||||
return a * 2
|
||||
"};
|
||||
let statements_1 = parse_program(source_1, Default::default()).unwrap();
|
||||
let statements_1 = parse_program(source_1, FileName::default()).unwrap();
|
||||
|
||||
let source_2 = indoc! { "
|
||||
return a + 1
|
||||
"};
|
||||
let statements_2 = parse_program(source_2, Default::default()).unwrap();
|
||||
let statements_2 = parse_program(source_2, FileName::default()).unwrap();
|
||||
|
||||
let composer: TopLevelComposer = Default::default();
|
||||
let composer = TopLevelComposer::new(Vec::new(), Vec::new(), ComposerConfig::default(), 32).0;
|
||||
let mut unifier = composer.unifier.clone();
|
||||
let primitives = composer.primitives_ty;
|
||||
let top_level = Arc::new(composer.make_top_level_context());
|
||||
unifier.top_level = Some(top_level.clone());
|
||||
|
||||
let signature = FunSignature {
|
||||
args: vec![FuncArg { name: "a".into(), ty: primitives.int32, default_value: None }],
|
||||
args: vec![FuncArg {
|
||||
name: "a".into(),
|
||||
ty: primitives.int32,
|
||||
default_value: None,
|
||||
is_vararg: false,
|
||||
}],
|
||||
ret: primitives.int32,
|
||||
vars: HashMap::new(),
|
||||
vars: VarMap::new(),
|
||||
};
|
||||
let fun_ty = unifier.add_ty(TypeEnum::TFunc(signature.clone()));
|
||||
let mut store = ConcreteTypeStore::new();
|
||||
@ -252,7 +301,7 @@ fn test_simple_call() {
|
||||
let resolver = Resolver {
|
||||
id_to_type: HashMap::new(),
|
||||
id_to_def: RwLock::new(HashMap::new()),
|
||||
class_names: Default::default(),
|
||||
class_names: HashMap::default(),
|
||||
};
|
||||
resolver.add_id_def("foo".into(), DefinitionId(foo_id));
|
||||
let resolver = Arc::new(resolver) as Arc<dyn SymbolResolver + Send + Sync>;
|
||||
@ -273,12 +322,13 @@ fn test_simple_call() {
|
||||
};
|
||||
let mut virtual_checks = Vec::new();
|
||||
let mut calls = HashMap::new();
|
||||
let mut identifiers: HashSet<_> = ["a".into(), "foo".into()].iter().cloned().collect();
|
||||
let mut identifiers: HashMap<_, _> =
|
||||
["a".into(), "foo".into()].map(|id| (id, IdentifierInfo::default())).into();
|
||||
let mut inferencer = Inferencer {
|
||||
top_level: &top_level,
|
||||
function_data: &mut function_data,
|
||||
unifier: &mut unifier,
|
||||
variable_mapping: Default::default(),
|
||||
variable_mapping: HashMap::default(),
|
||||
primitives: &primitives,
|
||||
virtual_checks: &mut virtual_checks,
|
||||
calls: &mut calls,
|
||||
@ -307,11 +357,11 @@ fn test_simple_call() {
|
||||
&mut *top_level.definitions.read()[foo_id].write()
|
||||
{
|
||||
instance_to_stmt.insert(
|
||||
"".to_string(),
|
||||
String::new(),
|
||||
FunInstance {
|
||||
body: Arc::new(statements_2),
|
||||
calls: Arc::new(inferencer.calls.clone()),
|
||||
subst: Default::default(),
|
||||
subst: IndexMap::default(),
|
||||
unifier_id: 0,
|
||||
},
|
||||
);
|
||||
@ -327,7 +377,7 @@ fn test_simple_call() {
|
||||
});
|
||||
|
||||
let task = CodeGenTask {
|
||||
subst: Default::default(),
|
||||
subst: Vec::default(),
|
||||
symbol_name: "testing".to_string(),
|
||||
body: Arc::new(statements_1),
|
||||
calls: Arc::new(calls1),
|
||||
@ -341,24 +391,86 @@ fn test_simple_call() {
|
||||
let expected = indoc! {"
|
||||
; ModuleID = 'test'
|
||||
source_filename = \"test\"
|
||||
target datalayout = \"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128\"
|
||||
target triple = \"x86_64-unknown-linux-gnu\"
|
||||
|
||||
define i32 @testing(i32 %0) {
|
||||
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn
|
||||
define i32 @testing(i32 %0) local_unnamed_addr #0 !dbg !5 {
|
||||
init:
|
||||
%call = call i32 @foo.0(i32 %0)
|
||||
%mul = mul i32 %call, 2
|
||||
ret i32 %mul
|
||||
%add.i = shl i32 %0, 1, !dbg !10
|
||||
%mul = add i32 %add.i, 2, !dbg !10
|
||||
ret i32 %mul, !dbg !10
|
||||
}
|
||||
|
||||
define i32 @foo.0(i32 %0) {
|
||||
; Function Attrs: mustprogress nofree norecurse nosync nounwind readnone willreturn
|
||||
define i32 @foo.0(i32 %0) local_unnamed_addr #0 !dbg !11 {
|
||||
init:
|
||||
%add = add i32 %0, 1
|
||||
ret i32 %add
|
||||
%add = add i32 %0, 1, !dbg !12
|
||||
ret i32 %add, !dbg !12
|
||||
}
|
||||
"}
|
||||
|
||||
attributes #0 = { mustprogress nofree norecurse nosync nounwind readnone willreturn }
|
||||
|
||||
!llvm.module.flags = !{!0, !1}
|
||||
!llvm.dbg.cu = !{!2, !4}
|
||||
|
||||
!0 = !{i32 2, !\"Debug Info Version\", i32 3}
|
||||
!1 = !{i32 2, !\"Dwarf Version\", i32 4}
|
||||
!2 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
|
||||
!3 = !DIFile(filename: \"unknown\", directory: \"\")
|
||||
!4 = distinct !DICompileUnit(language: DW_LANG_Python, file: !3, producer: \"NAC3\", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
|
||||
!5 = distinct !DISubprogram(name: \"testing\", linkageName: \"testing\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !2, retainedNodes: !9)
|
||||
!6 = !DISubroutineType(flags: DIFlagPublic, types: !7)
|
||||
!7 = !{!8}
|
||||
!8 = !DIBasicType(name: \"_\", flags: DIFlagPublic)
|
||||
!9 = !{}
|
||||
!10 = !DILocation(line: 2, column: 12, scope: !5)
|
||||
!11 = distinct !DISubprogram(name: \"foo.0\", linkageName: \"foo.0\", scope: null, file: !3, line: 1, type: !6, scopeLine: 1, flags: DIFlagPublic, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !4, retainedNodes: !9)
|
||||
!12 = !DILocation(line: 1, column: 12, scope: !11)
|
||||
"}
|
||||
.trim();
|
||||
assert_eq!(expected, module.print_to_string().to_str().unwrap().trim());
|
||||
})));
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, f);
|
||||
|
||||
Target::initialize_all(&InitializationConfig::default());
|
||||
|
||||
let llvm_options = CodeGenLLVMOptions {
|
||||
opt_level: OptimizationLevel::Default,
|
||||
target: CodeGenTargetMachineOptions::from_host_triple(),
|
||||
};
|
||||
let (registry, handles) = WorkerRegistry::create_workers(threads, top_level, &llvm_options, &f);
|
||||
registry.add_task(task);
|
||||
registry.wait_tasks_complete(handles);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classes_list_type_new() {
|
||||
let ctx = inkwell::context::Context::create();
|
||||
let generator = DefaultCodeGenerator::new(String::new(), 64);
|
||||
|
||||
let llvm_i32 = ctx.i32_type();
|
||||
let llvm_usize = generator.get_size_type(&ctx);
|
||||
|
||||
let llvm_list = ListType::new(&generator, &ctx, llvm_i32.into());
|
||||
assert!(ListType::is_representable(llvm_list.as_base_type(), llvm_usize).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classes_range_type_new() {
|
||||
let ctx = inkwell::context::Context::create();
|
||||
|
||||
let llvm_range = RangeType::new(&ctx);
|
||||
assert!(RangeType::is_representable(llvm_range.as_base_type()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_classes_ndarray_type_new() {
|
||||
let ctx = inkwell::context::Context::create();
|
||||
let generator = DefaultCodeGenerator::new(String::new(), 64);
|
||||
|
||||
let llvm_i32 = ctx.i32_type();
|
||||
let llvm_usize = generator.get_size_type(&ctx);
|
||||
|
||||
let llvm_ndarray = NDArrayType::new(&generator, &ctx, llvm_i32.into(), None);
|
||||
assert!(NDArrayType::is_representable(llvm_ndarray.as_base_type(), llvm_usize).is_ok());
|
||||
}
|
||||
|
206
nac3core/src/codegen/types/list.rs
Normal file
206
nac3core/src/codegen/types/list.rs
Normal file
@ -0,0 +1,206 @@
|
||||
use inkwell::{
|
||||
context::Context,
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
|
||||
values::IntValue,
|
||||
AddressSpace,
|
||||
};
|
||||
|
||||
use super::ProxyType;
|
||||
use crate::codegen::{
|
||||
values::{ArraySliceValue, ListValue, ProxyValue},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
/// Proxy type for a `list` type in LLVM.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct ListType<'ctx> {
|
||||
ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx> ListType<'ctx> {
|
||||
/// Checks whether `llvm_ty` represents a `list` type, returning [Err] if it does not.
|
||||
pub fn is_representable(
|
||||
llvm_ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
let llvm_list_ty = llvm_ty.get_element_type();
|
||||
let AnyTypeEnum::StructType(llvm_list_ty) = llvm_list_ty else {
|
||||
return Err(format!("Expected struct type for `list` type, got {llvm_list_ty}"));
|
||||
};
|
||||
if llvm_list_ty.count_fields() != 2 {
|
||||
return Err(format!(
|
||||
"Expected 2 fields in `list`, got {}",
|
||||
llvm_list_ty.count_fields()
|
||||
));
|
||||
}
|
||||
|
||||
let list_size_ty = llvm_list_ty.get_field_type_at_index(0).unwrap();
|
||||
let Ok(_) = PointerType::try_from(list_size_ty) else {
|
||||
return Err(format!("Expected pointer type for `list.0`, got {list_size_ty}"));
|
||||
};
|
||||
|
||||
let list_data_ty = llvm_list_ty.get_field_type_at_index(1).unwrap();
|
||||
let Ok(list_data_ty) = IntType::try_from(list_data_ty) else {
|
||||
return Err(format!("Expected int type for `list.1`, got {list_data_ty}"));
|
||||
};
|
||||
if list_data_ty.get_bit_width() != llvm_usize.get_bit_width() {
|
||||
return Err(format!(
|
||||
"Expected {}-bit int type for `list.1`, got {}-bit int",
|
||||
llvm_usize.get_bit_width(),
|
||||
list_data_ty.get_bit_width()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates an LLVM type corresponding to the expected structure of a `List`.
|
||||
#[must_use]
|
||||
fn llvm_type(
|
||||
ctx: &'ctx Context,
|
||||
element_type: BasicTypeEnum<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> PointerType<'ctx> {
|
||||
// struct List { data: T*, size: size_t }
|
||||
let field_tys = [element_type.ptr_type(AddressSpace::default()).into(), llvm_usize.into()];
|
||||
|
||||
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
|
||||
}
|
||||
|
||||
/// Creates an instance of [`ListType`].
|
||||
#[must_use]
|
||||
pub fn new<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
element_type: BasicTypeEnum<'ctx>,
|
||||
) -> Self {
|
||||
let llvm_usize = generator.get_size_type(ctx);
|
||||
let llvm_list = Self::llvm_type(ctx, element_type, llvm_usize);
|
||||
|
||||
ListType::from_type(llvm_list, llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`ListType`] from a [`PointerType`].
|
||||
#[must_use]
|
||||
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
|
||||
|
||||
ListType { ty: ptr_ty, llvm_usize }
|
||||
}
|
||||
|
||||
/// Returns the type of the `size` field of this `list` type.
|
||||
#[must_use]
|
||||
pub fn size_type(&self) -> IntType<'ctx> {
|
||||
self.as_base_type()
|
||||
.get_element_type()
|
||||
.into_struct_type()
|
||||
.get_field_type_at_index(1)
|
||||
.map(BasicTypeEnum::into_int_type)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Returns the element type of this `list` type.
|
||||
#[must_use]
|
||||
pub fn element_type(&self) -> AnyTypeEnum<'ctx> {
|
||||
self.as_base_type()
|
||||
.get_element_type()
|
||||
.into_struct_type()
|
||||
.get_field_type_at_index(0)
|
||||
.map(BasicTypeEnum::into_pointer_type)
|
||||
.map(PointerType::get_element_type)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Allocates an instance of [`ListValue`] as if by calling `alloca` on the base type.
|
||||
#[must_use]
|
||||
pub fn alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
self.raw_alloca(generator, ctx, name),
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts an existing value into a [`ListValue`].
|
||||
#[must_use]
|
||||
pub fn map_value(
|
||||
&self,
|
||||
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyType<'ctx> for ListType<'ctx> {
|
||||
type Base = PointerType<'ctx>;
|
||||
type Value = ListValue<'ctx>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
|
||||
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
|
||||
} else {
|
||||
Err(format!("Expected pointer type, got {llvm_ty:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
|
||||
}
|
||||
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base {
|
||||
generator
|
||||
.gen_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
generator
|
||||
.gen_array_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
size,
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn as_base_type(&self) -> Self::Base {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<ListType<'ctx>> for PointerType<'ctx> {
|
||||
fn from(value: ListType<'ctx>) -> Self {
|
||||
value.as_base_type()
|
||||
}
|
||||
}
|
76
nac3core/src/codegen/types/mod.rs
Normal file
76
nac3core/src/codegen/types/mod.rs
Normal file
@ -0,0 +1,76 @@
|
||||
//! This module contains abstraction over all intrinsic composite types of NAC3.
|
||||
//!
|
||||
//! # `raw_alloca` vs `alloca` vs `construct`
|
||||
//!
|
||||
//! There are three ways of creating a new object instance using the abstractions provided by this
|
||||
//! module.
|
||||
//!
|
||||
//! - `raw_alloca`: Allocates the object on the stack, returning an instance of
|
||||
//! [`impl BasicValue`][inkwell::values::BasicValue]. This is similar to a `malloc` expression in
|
||||
//! C++ but the object is allocated on the stack.
|
||||
//! - `alloca`: Similar to `raw_alloca`, but also wraps the allocated object with
|
||||
//! [`<Self as ProxyType<'ctx>>::Value`][ProxyValue], and returns the wrapped object. The returned
|
||||
//! object will not initialize any value or fields. This is similar to a type-safe `malloc`
|
||||
//! expression in C++ but the object is allocated on the stack.
|
||||
//! - `construct`: Similar to `alloca`, but performs some initialization on the value or fields of
|
||||
//! the returned object. This is similar to a `new` expression in C++ but the object is allocated
|
||||
//! on the stack.
|
||||
|
||||
use inkwell::{context::Context, types::BasicType, values::IntValue};
|
||||
|
||||
use super::{
|
||||
values::{ArraySliceValue, ProxyValue},
|
||||
{CodeGenContext, CodeGenerator},
|
||||
};
|
||||
pub use list::*;
|
||||
pub use range::*;
|
||||
|
||||
mod list;
|
||||
pub mod ndarray;
|
||||
mod range;
|
||||
pub mod structure;
|
||||
pub mod utils;
|
||||
|
||||
/// A LLVM type that is used to represent a corresponding type in NAC3.
|
||||
pub trait ProxyType<'ctx>: Into<Self::Base> {
|
||||
/// The LLVM type of which values of this type possess. This is usually a
|
||||
/// [LLVM pointer type][PointerType] for any non-primitive types.
|
||||
type Base: BasicType<'ctx>;
|
||||
|
||||
/// The type of values represented by this type.
|
||||
type Value: ProxyValue<'ctx, Type = Self>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// Checks whether `llvm_ty` can be represented by this [`ProxyType`].
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String>;
|
||||
|
||||
/// Creates a new value of this type, returning the LLVM instance of this value.
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base;
|
||||
|
||||
/// Creates a new array value of this type, returning an [`ArraySliceValue`] encapsulating the
|
||||
/// resulting array.
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx>;
|
||||
|
||||
/// Returns the [base type][Self::Base] of this proxy.
|
||||
fn as_base_type(&self) -> Self::Base;
|
||||
}
|
257
nac3core/src/codegen/types/ndarray/contiguous.rs
Normal file
257
nac3core/src/codegen/types/ndarray/contiguous.rs
Normal file
@ -0,0 +1,257 @@
|
||||
use inkwell::{
|
||||
context::Context,
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
use crate::{
|
||||
codegen::{
|
||||
types::{
|
||||
structure::{
|
||||
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
|
||||
},
|
||||
ProxyType,
|
||||
},
|
||||
values::{ndarray::ContiguousNDArrayValue, ArraySliceValue, ProxyValue},
|
||||
CodeGenContext, CodeGenerator,
|
||||
},
|
||||
toplevel::numpy::unpack_ndarray_var_tys,
|
||||
typecheck::typedef::Type,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct ContiguousNDArrayType<'ctx> {
|
||||
ty: PointerType<'ctx>,
|
||||
item: BasicTypeEnum<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct ContiguousNDArrayFields<'ctx> {
|
||||
#[value_type(usize)]
|
||||
pub ndims: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(usize.ptr_type(AddressSpace::default()))]
|
||||
pub shape: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
pub data: StructField<'ctx, PointerValue<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> ContiguousNDArrayFields<'ctx> {
|
||||
#[must_use]
|
||||
pub fn new_typed(item: BasicTypeEnum<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
|
||||
let mut counter = FieldIndexCounter::default();
|
||||
|
||||
ContiguousNDArrayFields {
|
||||
ndims: StructField::create(&mut counter, "ndims", llvm_usize),
|
||||
shape: StructField::create(
|
||||
&mut counter,
|
||||
"shape",
|
||||
llvm_usize.ptr_type(AddressSpace::default()),
|
||||
),
|
||||
data: StructField::create(&mut counter, "data", item.ptr_type(AddressSpace::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ContiguousNDArrayType<'ctx> {
|
||||
/// Checks whether `llvm_ty` represents a `ndarray` type, returning [Err] if it does not.
|
||||
pub fn is_representable(
|
||||
llvm_ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
let ctx = llvm_ty.get_context();
|
||||
|
||||
let llvm_ty = llvm_ty.get_element_type();
|
||||
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
|
||||
return Err(format!(
|
||||
"Expected struct type for `ContiguousNDArray` type, got {llvm_ty}"
|
||||
));
|
||||
};
|
||||
|
||||
let fields = ContiguousNDArrayFields::new(ctx, llvm_usize);
|
||||
|
||||
check_struct_type_matches_fields(
|
||||
fields,
|
||||
llvm_ty,
|
||||
"ContiguousNDArray",
|
||||
&[(fields.data.name(), &|ty| {
|
||||
if ty.is_pointer_type() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Expected T* for `ContiguousNDArray.data`, got {ty}"))
|
||||
}
|
||||
})],
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
|
||||
#[must_use]
|
||||
fn fields(
|
||||
item: BasicTypeEnum<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> ContiguousNDArrayFields<'ctx> {
|
||||
ContiguousNDArrayFields::new_typed(item, llvm_usize)
|
||||
}
|
||||
|
||||
/// See [`NDArrayType::fields`].
|
||||
// TODO: Move this into e.g. StructProxyType
|
||||
#[must_use]
|
||||
pub fn get_fields(&self) -> ContiguousNDArrayFields<'ctx> {
|
||||
Self::fields(self.item, self.llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an LLVM type corresponding to the expected structure of an `NDArray`.
|
||||
#[must_use]
|
||||
fn llvm_type(
|
||||
ctx: &'ctx Context,
|
||||
item: BasicTypeEnum<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> PointerType<'ctx> {
|
||||
let field_tys =
|
||||
Self::fields(item, llvm_usize).into_iter().map(|field| field.1).collect_vec();
|
||||
|
||||
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
|
||||
}
|
||||
|
||||
/// Creates an instance of [`ContiguousNDArrayType`].
|
||||
#[must_use]
|
||||
pub fn new<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
item: BasicTypeEnum<'ctx>,
|
||||
) -> Self {
|
||||
let llvm_usize = generator.get_size_type(ctx);
|
||||
let llvm_cndarray = Self::llvm_type(ctx, item, llvm_usize);
|
||||
|
||||
Self { ty: llvm_cndarray, item, llvm_usize }
|
||||
}
|
||||
|
||||
/// Creates an [`ContiguousNDArrayType`] from a [unifier type][Type].
|
||||
#[must_use]
|
||||
pub fn from_unifier_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ty: Type,
|
||||
) -> Self {
|
||||
let (dtype, _) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
|
||||
|
||||
let llvm_dtype = ctx.get_llvm_type(generator, dtype);
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
|
||||
Self { ty: Self::llvm_type(ctx.ctx, llvm_dtype, llvm_usize), item: llvm_dtype, llvm_usize }
|
||||
}
|
||||
|
||||
/// Creates an [`ContiguousNDArrayType`] from a [`PointerType`] representing an `NDArray`.
|
||||
#[must_use]
|
||||
pub fn from_type(
|
||||
ptr_ty: PointerType<'ctx>,
|
||||
item: BasicTypeEnum<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
|
||||
|
||||
Self { ty: ptr_ty, item, llvm_usize }
|
||||
}
|
||||
|
||||
/// Allocates an instance of [`ContiguousNDArrayValue`] as if by calling `alloca` on the base type.
|
||||
#[must_use]
|
||||
pub fn alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
self.raw_alloca(generator, ctx, name),
|
||||
self.item,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts an existing value into a [`ContiguousNDArrayValue`].
|
||||
#[must_use]
|
||||
pub fn map_value(
|
||||
&self,
|
||||
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
value,
|
||||
self.item,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyType<'ctx> for ContiguousNDArrayType<'ctx> {
|
||||
type Base = PointerType<'ctx>;
|
||||
type Value = ContiguousNDArrayValue<'ctx>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
|
||||
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
|
||||
} else {
|
||||
Err(format!("Expected pointer type, got {llvm_ty:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
|
||||
}
|
||||
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base {
|
||||
generator
|
||||
.gen_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
generator
|
||||
.gen_array_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
size,
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn as_base_type(&self) -> Self::Base {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<ContiguousNDArrayType<'ctx>> for PointerType<'ctx> {
|
||||
fn from(value: ContiguousNDArrayType<'ctx>) -> Self {
|
||||
value.as_base_type()
|
||||
}
|
||||
}
|
215
nac3core/src/codegen/types/ndarray/indexing.rs
Normal file
215
nac3core/src/codegen/types/ndarray/indexing.rs
Normal file
@ -0,0 +1,215 @@
|
||||
use inkwell::{
|
||||
context::{AsContextRef, Context},
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
use crate::codegen::{
|
||||
types::{
|
||||
structure::{check_struct_type_matches_fields, StructField, StructFields},
|
||||
ProxyType,
|
||||
},
|
||||
values::{
|
||||
ndarray::{NDIndexValue, RustNDIndex},
|
||||
ArrayLikeIndexer, ArraySliceValue, ProxyValue,
|
||||
},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct NDIndexType<'ctx> {
|
||||
ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct NDIndexStructFields<'ctx> {
|
||||
#[value_type(i8_type())]
|
||||
pub type_: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
pub data: StructField<'ctx, PointerValue<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> NDIndexType<'ctx> {
|
||||
/// Checks whether `llvm_ty` represents a `ndindex` type, returning [Err] if it does not.
|
||||
pub fn is_representable(
|
||||
llvm_ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
let ctx = llvm_ty.get_context();
|
||||
|
||||
let llvm_ty = llvm_ty.get_element_type();
|
||||
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
|
||||
return Err(format!(
|
||||
"Expected struct type for `ContiguousNDArray` type, got {llvm_ty}"
|
||||
));
|
||||
};
|
||||
|
||||
let fields = NDIndexStructFields::new(ctx, llvm_usize);
|
||||
|
||||
check_struct_type_matches_fields(fields, llvm_ty, "NDIndex", &[])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn fields(
|
||||
ctx: impl AsContextRef<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> NDIndexStructFields<'ctx> {
|
||||
NDIndexStructFields::new(ctx, llvm_usize)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn get_fields(&self) -> NDIndexStructFields<'ctx> {
|
||||
Self::fields(self.ty.get_context(), self.llvm_usize)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
|
||||
let field_tys =
|
||||
Self::fields(ctx, llvm_usize).into_iter().map(|field| field.1).collect_vec();
|
||||
|
||||
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn new<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
|
||||
let llvm_usize = generator.get_size_type(ctx);
|
||||
let llvm_ndindex = Self::llvm_type(ctx, llvm_usize);
|
||||
|
||||
Self { ty: llvm_ndindex, llvm_usize }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
|
||||
|
||||
Self { ty: ptr_ty, llvm_usize }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
self.raw_alloca(generator, ctx, name),
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Serialize a list of [`RustNDIndex`] as a newly allocated LLVM array of [`NDIndexValue`].
|
||||
#[must_use]
|
||||
pub fn construct_ndindices<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
in_ndindices: &[RustNDIndex<'ctx>],
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
// Allocate the LLVM ndindices.
|
||||
let num_ndindices = self.llvm_usize.const_int(in_ndindices.len() as u64, false);
|
||||
let ndindices = self.array_alloca(generator, ctx, num_ndindices, None);
|
||||
|
||||
// Initialize all of them.
|
||||
for (i, in_ndindex) in in_ndindices.iter().enumerate() {
|
||||
let pndindex = unsafe {
|
||||
ndindices.ptr_offset_unchecked(
|
||||
ctx,
|
||||
generator,
|
||||
&ctx.ctx.i64_type().const_int(u64::try_from(i).unwrap(), false),
|
||||
None,
|
||||
)
|
||||
};
|
||||
|
||||
in_ndindex.write_to_ndindex(
|
||||
generator,
|
||||
ctx,
|
||||
NDIndexValue::from_pointer_value(pndindex, self.llvm_usize, None),
|
||||
);
|
||||
}
|
||||
|
||||
ndindices
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn map_value(
|
||||
&self,
|
||||
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(value, self.llvm_usize, name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyType<'ctx> for NDIndexType<'ctx> {
|
||||
type Base = PointerType<'ctx>;
|
||||
type Value = NDIndexValue<'ctx>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
|
||||
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
|
||||
} else {
|
||||
Err(format!("Expected pointer type, got {llvm_ty:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
|
||||
}
|
||||
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base {
|
||||
generator
|
||||
.gen_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
generator
|
||||
.gen_array_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
size,
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn as_base_type(&self) -> Self::Base {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<NDIndexType<'ctx>> for PointerType<'ctx> {
|
||||
fn from(value: NDIndexType<'ctx>) -> Self {
|
||||
value.as_base_type()
|
||||
}
|
||||
}
|
469
nac3core/src/codegen/types/ndarray/mod.rs
Normal file
469
nac3core/src/codegen/types/ndarray/mod.rs
Normal file
@ -0,0 +1,469 @@
|
||||
use inkwell::{
|
||||
context::{AsContextRef, Context},
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
|
||||
values::{BasicValue, IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
use super::{
|
||||
structure::{check_struct_type_matches_fields, StructField, StructFields},
|
||||
ProxyType,
|
||||
};
|
||||
use crate::{
|
||||
codegen::{
|
||||
values::{ndarray::NDArrayValue, ArraySliceValue, ProxyValue, TypedArrayLikeMutator},
|
||||
{CodeGenContext, CodeGenerator},
|
||||
},
|
||||
toplevel::{helper::extract_ndims, numpy::unpack_ndarray_var_tys},
|
||||
typecheck::typedef::Type,
|
||||
};
|
||||
pub use contiguous::*;
|
||||
pub use indexing::*;
|
||||
pub use nditer::*;
|
||||
|
||||
mod contiguous;
|
||||
mod indexing;
|
||||
mod nditer;
|
||||
|
||||
/// Proxy type for a `ndarray` type in LLVM.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct NDArrayType<'ctx> {
|
||||
ty: PointerType<'ctx>,
|
||||
dtype: BasicTypeEnum<'ctx>,
|
||||
ndims: Option<u64>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct NDArrayStructFields<'ctx> {
|
||||
/// The size of each `NDArray` element in bytes.
|
||||
#[value_type(usize)]
|
||||
pub itemsize: StructField<'ctx, IntValue<'ctx>>,
|
||||
/// Number of dimensions in the array.
|
||||
#[value_type(usize)]
|
||||
pub ndims: StructField<'ctx, IntValue<'ctx>>,
|
||||
/// Pointer to an array containing the shape of the `NDArray`.
|
||||
#[value_type(usize.ptr_type(AddressSpace::default()))]
|
||||
pub shape: StructField<'ctx, PointerValue<'ctx>>,
|
||||
/// Pointer to an array indicating the number of bytes between each element at a dimension
|
||||
#[value_type(usize.ptr_type(AddressSpace::default()))]
|
||||
pub strides: StructField<'ctx, PointerValue<'ctx>>,
|
||||
/// Pointer to an array containing the array data
|
||||
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
pub data: StructField<'ctx, PointerValue<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> NDArrayType<'ctx> {
|
||||
/// Checks whether `llvm_ty` represents a `ndarray` type, returning [Err] if it does not.
|
||||
pub fn is_representable(
|
||||
llvm_ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
let ctx = llvm_ty.get_context();
|
||||
|
||||
let llvm_ndarray_ty = llvm_ty.get_element_type();
|
||||
let AnyTypeEnum::StructType(llvm_ndarray_ty) = llvm_ndarray_ty else {
|
||||
return Err(format!("Expected struct type for `NDArray` type, got {llvm_ndarray_ty}"));
|
||||
};
|
||||
|
||||
check_struct_type_matches_fields(
|
||||
Self::fields(ctx, llvm_usize),
|
||||
llvm_ndarray_ty,
|
||||
"NDArray",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
|
||||
#[must_use]
|
||||
fn fields(
|
||||
ctx: impl AsContextRef<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> NDArrayStructFields<'ctx> {
|
||||
NDArrayStructFields::new(ctx, llvm_usize)
|
||||
}
|
||||
|
||||
/// See [`NDArrayType::fields`].
|
||||
// TODO: Move this into e.g. StructProxyType
|
||||
#[must_use]
|
||||
pub fn get_fields(&self, ctx: impl AsContextRef<'ctx>) -> NDArrayStructFields<'ctx> {
|
||||
Self::fields(ctx, self.llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an LLVM type corresponding to the expected structure of an `NDArray`.
|
||||
#[must_use]
|
||||
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
|
||||
let field_tys =
|
||||
Self::fields(ctx, llvm_usize).into_iter().map(|field| field.1).collect_vec();
|
||||
|
||||
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
|
||||
}
|
||||
|
||||
/// Creates an instance of [`NDArrayType`].
|
||||
#[must_use]
|
||||
pub fn new<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
dtype: BasicTypeEnum<'ctx>,
|
||||
ndims: Option<u64>,
|
||||
) -> Self {
|
||||
let llvm_usize = generator.get_size_type(ctx);
|
||||
let llvm_ndarray = Self::llvm_type(ctx, llvm_usize);
|
||||
|
||||
NDArrayType { ty: llvm_ndarray, dtype, ndims, llvm_usize }
|
||||
}
|
||||
|
||||
/// Creates an instance of [`NDArrayType`] with `ndims` of 0.
|
||||
#[must_use]
|
||||
pub fn new_unsized<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
dtype: BasicTypeEnum<'ctx>,
|
||||
) -> Self {
|
||||
let llvm_usize = generator.get_size_type(ctx);
|
||||
let llvm_ndarray = Self::llvm_type(ctx, llvm_usize);
|
||||
|
||||
NDArrayType { ty: llvm_ndarray, dtype, ndims: Some(0), llvm_usize }
|
||||
}
|
||||
|
||||
/// Creates an [`NDArrayType`] from a [unifier type][Type].
|
||||
#[must_use]
|
||||
pub fn from_unifier_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ty: Type,
|
||||
) -> Self {
|
||||
let (dtype, ndims) = unpack_ndarray_var_tys(&mut ctx.unifier, ty);
|
||||
|
||||
let llvm_dtype = ctx.get_llvm_type(generator, dtype);
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
let ndims = extract_ndims(&ctx.unifier, ndims);
|
||||
|
||||
NDArrayType {
|
||||
ty: Self::llvm_type(ctx.ctx, llvm_usize),
|
||||
dtype: llvm_dtype,
|
||||
ndims: Some(ndims),
|
||||
llvm_usize,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an [`NDArrayType`] from a [`PointerType`] representing an `NDArray`.
|
||||
#[must_use]
|
||||
pub fn from_type(
|
||||
ptr_ty: PointerType<'ctx>,
|
||||
dtype: BasicTypeEnum<'ctx>,
|
||||
ndims: Option<u64>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
|
||||
|
||||
NDArrayType { ty: ptr_ty, dtype, ndims, llvm_usize }
|
||||
}
|
||||
|
||||
/// Returns the type of the `size` field of this `ndarray` type.
|
||||
#[must_use]
|
||||
pub fn size_type(&self) -> IntType<'ctx> {
|
||||
self.llvm_usize
|
||||
}
|
||||
|
||||
/// Returns the element type of this `ndarray` type.
|
||||
#[must_use]
|
||||
pub fn element_type(&self) -> BasicTypeEnum<'ctx> {
|
||||
self.dtype
|
||||
}
|
||||
|
||||
/// Returns the number of dimensions of this `ndarray` type.
|
||||
#[must_use]
|
||||
pub fn ndims(&self) -> Option<u64> {
|
||||
self.ndims
|
||||
}
|
||||
|
||||
/// Allocates an instance of [`NDArrayValue`] as if by calling `alloca` on the base type.
|
||||
#[must_use]
|
||||
pub fn alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
self.raw_alloca(generator, ctx, name),
|
||||
self.dtype,
|
||||
self.ndims,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Allocates an [`NDArrayValue`] on the stack and initializes all fields as follows:
|
||||
///
|
||||
/// - `data`: uninitialized.
|
||||
/// - `itemsize`: set to the size of `self.dtype`.
|
||||
/// - `ndims`: set to the value of `ndims`.
|
||||
/// - `shape`: allocated on the stack with an array of length `ndims` with uninitialized values.
|
||||
/// - `strides`: allocated on the stack with an array of length `ndims` with uninitialized
|
||||
/// values.
|
||||
#[must_use]
|
||||
fn construct_impl<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ndims: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
let ndarray = self.alloca(generator, ctx, name);
|
||||
|
||||
let itemsize = ctx
|
||||
.builder
|
||||
.build_int_truncate_or_bit_cast(self.dtype.size_of().unwrap(), self.llvm_usize, "")
|
||||
.unwrap();
|
||||
ndarray.store_itemsize(ctx, generator, itemsize);
|
||||
|
||||
ndarray.store_ndims(ctx, generator, ndims);
|
||||
|
||||
ndarray.create_shape(ctx, self.llvm_usize, ndims);
|
||||
ndarray.create_strides(ctx, self.llvm_usize, ndims);
|
||||
|
||||
ndarray
|
||||
}
|
||||
|
||||
/// Allocate an [`NDArrayValue`] on the stack using `dtype` and `ndims` of this [`NDArrayType`]
|
||||
/// instance.
|
||||
///
|
||||
/// The returned ndarray's content will be:
|
||||
/// - `data`: uninitialized.
|
||||
/// - `itemsize`: set to the size of `dtype`.
|
||||
/// - `ndims`: set to the value of `self.ndims`.
|
||||
/// - `shape`: allocated on the stack with an array of length `ndims` with uninitialized values.
|
||||
/// - `strides`: allocated on the stack with an array of length `ndims` with uninitialized
|
||||
/// values.
|
||||
#[must_use]
|
||||
pub fn construct_uninitialized<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
assert!(self.ndims.is_some(), "NDArrayType::construct can only be called on an instance with compile-time known ndims (self.ndims = Some(ndims))");
|
||||
|
||||
let Some(ndims) = self.ndims.map(|ndims| self.llvm_usize.const_int(ndims, false)) else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
self.construct_impl(generator, ctx, ndims, name)
|
||||
}
|
||||
|
||||
/// Allocate an [`NDArrayValue`] on the stack given its `ndims` and `dtype`.
|
||||
///
|
||||
/// `shape` and `strides` will be automatically allocated onto the stack.
|
||||
///
|
||||
/// The returned ndarray's content will be:
|
||||
/// - `data`: uninitialized.
|
||||
/// - `itemsize`: set to the size of `dtype`.
|
||||
/// - `ndims`: set to the value of `ndims`.
|
||||
/// - `shape`: allocated with an array of length `ndims` with uninitialized values.
|
||||
/// - `strides`: allocated with an array of length `ndims` with uninitialized values.
|
||||
#[deprecated = "Prefer construct_uninitialized or construct_*_shape."]
|
||||
#[must_use]
|
||||
pub fn construct_dyn_ndims<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ndims: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
assert!(self.ndims.is_none(), "NDArrayType::construct_dyn_ndims can only be called on an instance with compile-time unknown ndims (self.ndims = None)");
|
||||
|
||||
self.construct_impl(generator, ctx, ndims, name)
|
||||
}
|
||||
|
||||
/// Convenience function. Allocate an [`NDArrayValue`] with a statically known shape.
|
||||
///
|
||||
/// The returned [`NDArrayValue`]'s `data` and `strides` are uninitialized.
|
||||
#[must_use]
|
||||
pub fn construct_const_shape<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
shape: &[u64],
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
assert!(self.ndims.is_none_or(|ndims| shape.len() as u64 == ndims));
|
||||
|
||||
let ndarray = Self::new(generator, ctx.ctx, self.dtype, Some(shape.len() as u64))
|
||||
.construct_uninitialized(generator, ctx, name);
|
||||
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
|
||||
// Write shape
|
||||
let ndarray_shape = ndarray.shape();
|
||||
for (i, dim) in shape.iter().enumerate() {
|
||||
let dim = llvm_usize.const_int(*dim, false);
|
||||
unsafe {
|
||||
ndarray_shape.set_typed_unchecked(
|
||||
ctx,
|
||||
generator,
|
||||
&llvm_usize.const_int(i as u64, false),
|
||||
dim,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ndarray
|
||||
}
|
||||
|
||||
/// Convenience function. Allocate an [`NDArrayValue`] with a dynamically known shape.
|
||||
///
|
||||
/// The returned [`NDArrayValue`]'s `data` and `strides` are uninitialized.
|
||||
#[must_use]
|
||||
pub fn construct_dyn_shape<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
shape: &[IntValue<'ctx>],
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
assert!(self.ndims.is_none_or(|ndims| shape.len() as u64 == ndims));
|
||||
|
||||
let ndarray = Self::new(generator, ctx.ctx, self.dtype, Some(shape.len() as u64))
|
||||
.construct_uninitialized(generator, ctx, name);
|
||||
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
|
||||
// Write shape
|
||||
let ndarray_shape = ndarray.shape();
|
||||
for (i, dim) in shape.iter().enumerate() {
|
||||
assert_eq!(
|
||||
dim.get_type(),
|
||||
llvm_usize,
|
||||
"Expected {} but got {}",
|
||||
llvm_usize.print_to_string(),
|
||||
dim.get_type().print_to_string()
|
||||
);
|
||||
unsafe {
|
||||
ndarray_shape.set_typed_unchecked(
|
||||
ctx,
|
||||
generator,
|
||||
&llvm_usize.const_int(i as u64, false),
|
||||
*dim,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ndarray
|
||||
}
|
||||
|
||||
/// Create an unsized ndarray to contain `value`.
|
||||
#[must_use]
|
||||
pub fn construct_unsized<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
value: &impl BasicValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> NDArrayValue<'ctx> {
|
||||
let value = value.as_basic_value_enum();
|
||||
|
||||
assert_eq!(value.get_type(), self.dtype);
|
||||
assert!(self.ndims.is_none_or(|ndims| ndims == 0));
|
||||
|
||||
// We have to put the value on the stack to get a data pointer.
|
||||
let data = ctx.builder.build_alloca(value.get_type(), "construct_unsized").unwrap();
|
||||
ctx.builder.build_store(data, value).unwrap();
|
||||
let data = ctx
|
||||
.builder
|
||||
.build_pointer_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "")
|
||||
.unwrap();
|
||||
|
||||
let ndarray = Self::new_unsized(generator, ctx.ctx, value.get_type())
|
||||
.construct_uninitialized(generator, ctx, name);
|
||||
ctx.builder.build_store(ndarray.ptr_to_data(ctx), data).unwrap();
|
||||
ndarray
|
||||
}
|
||||
|
||||
/// Converts an existing value into a [`NDArrayValue`].
|
||||
#[must_use]
|
||||
pub fn map_value(
|
||||
&self,
|
||||
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
value,
|
||||
self.dtype,
|
||||
self.ndims,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyType<'ctx> for NDArrayType<'ctx> {
|
||||
type Base = PointerType<'ctx>;
|
||||
type Value = NDArrayValue<'ctx>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
|
||||
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
|
||||
} else {
|
||||
Err(format!("Expected pointer type, got {llvm_ty:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
|
||||
}
|
||||
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base {
|
||||
generator
|
||||
.gen_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
generator
|
||||
.gen_array_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
size,
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn as_base_type(&self) -> Self::Base {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<NDArrayType<'ctx>> for PointerType<'ctx> {
|
||||
fn from(value: NDArrayType<'ctx>) -> Self {
|
||||
value.as_base_type()
|
||||
}
|
||||
}
|
241
nac3core/src/codegen/types/ndarray/nditer.rs
Normal file
241
nac3core/src/codegen/types/ndarray/nditer.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use inkwell::{
|
||||
context::{AsContextRef, Context},
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
use super::ProxyType;
|
||||
use crate::codegen::{
|
||||
irrt,
|
||||
types::structure::{check_struct_type_matches_fields, StructField, StructFields},
|
||||
values::{
|
||||
ndarray::{NDArrayValue, NDIterValue},
|
||||
ArraySliceValue, ProxyValue,
|
||||
},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct NDIterType<'ctx> {
|
||||
ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct NDIterStructFields<'ctx> {
|
||||
#[value_type(usize)]
|
||||
pub ndims: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(usize.ptr_type(AddressSpace::default()))]
|
||||
pub shape: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(usize.ptr_type(AddressSpace::default()))]
|
||||
pub strides: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(usize.ptr_type(AddressSpace::default()))]
|
||||
pub indices: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
pub nth: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(i8_type().ptr_type(AddressSpace::default()))]
|
||||
pub element: StructField<'ctx, PointerValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
pub size: StructField<'ctx, IntValue<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> NDIterType<'ctx> {
|
||||
/// Checks whether `llvm_ty` represents a `nditer` type, returning [Err] if it does not.
|
||||
pub fn is_representable(
|
||||
llvm_ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
let ctx = llvm_ty.get_context();
|
||||
|
||||
let llvm_ty = llvm_ty.get_element_type();
|
||||
let AnyTypeEnum::StructType(llvm_ndarray_ty) = llvm_ty else {
|
||||
return Err(format!("Expected struct type for `NDIter` type, got {llvm_ty}"));
|
||||
};
|
||||
|
||||
check_struct_type_matches_fields(
|
||||
Self::fields(ctx, llvm_usize),
|
||||
llvm_ndarray_ty,
|
||||
"NDIter",
|
||||
&[],
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns an instance of [`StructFields`] containing all field accessors for this type.
|
||||
#[must_use]
|
||||
fn fields(ctx: impl AsContextRef<'ctx>, llvm_usize: IntType<'ctx>) -> NDIterStructFields<'ctx> {
|
||||
NDIterStructFields::new(ctx, llvm_usize)
|
||||
}
|
||||
|
||||
/// See [`NDIterType::fields`].
|
||||
// TODO: Move this into e.g. StructProxyType
|
||||
#[must_use]
|
||||
pub fn get_fields(&self, ctx: impl AsContextRef<'ctx>) -> NDIterStructFields<'ctx> {
|
||||
Self::fields(ctx, self.llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an LLVM type corresponding to the expected structure of an `NDIter`.
|
||||
#[must_use]
|
||||
fn llvm_type(ctx: &'ctx Context, llvm_usize: IntType<'ctx>) -> PointerType<'ctx> {
|
||||
let field_tys =
|
||||
Self::fields(ctx, llvm_usize).into_iter().map(|field| field.1).collect_vec();
|
||||
|
||||
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
|
||||
}
|
||||
|
||||
/// Creates an instance of [`NDIter`].
|
||||
#[must_use]
|
||||
pub fn new<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
|
||||
let llvm_usize = generator.get_size_type(ctx);
|
||||
let llvm_nditer = Self::llvm_type(ctx, llvm_usize);
|
||||
|
||||
Self { ty: llvm_nditer, llvm_usize }
|
||||
}
|
||||
|
||||
/// Creates an [`NDIterType`] from a [`PointerType`] representing an `NDIter`.
|
||||
#[must_use]
|
||||
pub fn from_type(ptr_ty: PointerType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr_ty, llvm_usize).is_ok());
|
||||
|
||||
Self { ty: ptr_ty, llvm_usize }
|
||||
}
|
||||
|
||||
/// Returns the type of the `size` field of this `nditer` type.
|
||||
#[must_use]
|
||||
pub fn size_type(&self) -> IntType<'ctx> {
|
||||
self.llvm_usize
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
parent: NDArrayValue<'ctx>,
|
||||
indices: ArraySliceValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
self.raw_alloca(generator, ctx, name),
|
||||
parent,
|
||||
indices,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Allocate an [`NDIter`] that iterates through the given `ndarray`.
|
||||
#[must_use]
|
||||
pub fn construct<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ndarray: NDArrayValue<'ctx>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
let nditer = self.raw_alloca(generator, ctx, None);
|
||||
let ndims = ndarray.load_ndims(ctx);
|
||||
|
||||
// The caller has the responsibility to allocate 'indices' for `NDIter`.
|
||||
let indices =
|
||||
generator.gen_array_var_alloc(ctx, self.llvm_usize.into(), ndims, None).unwrap();
|
||||
|
||||
let nditer = <Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
nditer,
|
||||
ndarray,
|
||||
indices,
|
||||
self.llvm_usize,
|
||||
None,
|
||||
);
|
||||
|
||||
irrt::ndarray::call_nac3_nditer_initialize(generator, ctx, nditer, ndarray, indices);
|
||||
|
||||
nditer
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn map_value(
|
||||
&self,
|
||||
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
|
||||
parent: NDArrayValue<'ctx>,
|
||||
indices: ArraySliceValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
value,
|
||||
parent,
|
||||
indices,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyType<'ctx> for NDIterType<'ctx> {
|
||||
type Base = PointerType<'ctx>;
|
||||
type Value = NDIterValue<'ctx>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
|
||||
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
|
||||
} else {
|
||||
Err(format!("Expected pointer type, got {llvm_ty:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
|
||||
}
|
||||
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base {
|
||||
generator
|
||||
.gen_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
generator
|
||||
.gen_array_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
size,
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn as_base_type(&self) -> Self::Base {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<NDIterType<'ctx>> for PointerType<'ctx> {
|
||||
fn from(value: NDIterType<'ctx>) -> Self {
|
||||
value.as_base_type()
|
||||
}
|
||||
}
|
170
nac3core/src/codegen/types/range.rs
Normal file
170
nac3core/src/codegen/types/range.rs
Normal file
@ -0,0 +1,170 @@
|
||||
use inkwell::{
|
||||
context::Context,
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
|
||||
values::IntValue,
|
||||
AddressSpace,
|
||||
};
|
||||
|
||||
use super::ProxyType;
|
||||
use crate::codegen::{
|
||||
values::{ArraySliceValue, ProxyValue, RangeValue},
|
||||
{CodeGenContext, CodeGenerator},
|
||||
};
|
||||
|
||||
/// Proxy type for a `range` type in LLVM.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct RangeType<'ctx> {
|
||||
ty: PointerType<'ctx>,
|
||||
}
|
||||
|
||||
impl<'ctx> RangeType<'ctx> {
|
||||
/// Checks whether `llvm_ty` represents a `range` type, returning [Err] if it does not.
|
||||
pub fn is_representable(llvm_ty: PointerType<'ctx>) -> Result<(), String> {
|
||||
let llvm_range_ty = llvm_ty.get_element_type();
|
||||
let AnyTypeEnum::ArrayType(llvm_range_ty) = llvm_range_ty else {
|
||||
return Err(format!("Expected array type for `range` type, got {llvm_range_ty}"));
|
||||
};
|
||||
if llvm_range_ty.len() != 3 {
|
||||
return Err(format!(
|
||||
"Expected 3 elements for `range` type, got {}",
|
||||
llvm_range_ty.len()
|
||||
));
|
||||
}
|
||||
|
||||
let llvm_range_elem_ty = llvm_range_ty.get_element_type();
|
||||
let Ok(llvm_range_elem_ty) = IntType::try_from(llvm_range_elem_ty) else {
|
||||
return Err(format!(
|
||||
"Expected int type for `range` element type, got {llvm_range_elem_ty}"
|
||||
));
|
||||
};
|
||||
if llvm_range_elem_ty.get_bit_width() != 32 {
|
||||
return Err(format!(
|
||||
"Expected 32-bit int type for `range` element type, got {}",
|
||||
llvm_range_elem_ty.get_bit_width()
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates an LLVM type corresponding to the expected structure of a `Range`.
|
||||
#[must_use]
|
||||
fn llvm_type(ctx: &'ctx Context) -> PointerType<'ctx> {
|
||||
// typedef int32_t Range[3];
|
||||
let llvm_i32 = ctx.i32_type();
|
||||
llvm_i32.array_type(3).ptr_type(AddressSpace::default())
|
||||
}
|
||||
|
||||
/// Creates an instance of [`RangeType`].
|
||||
#[must_use]
|
||||
pub fn new(ctx: &'ctx Context) -> Self {
|
||||
let llvm_range = Self::llvm_type(ctx);
|
||||
|
||||
RangeType::from_type(llvm_range)
|
||||
}
|
||||
|
||||
/// Creates an [`RangeType`] from a [`PointerType`].
|
||||
#[must_use]
|
||||
pub fn from_type(ptr_ty: PointerType<'ctx>) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr_ty).is_ok());
|
||||
|
||||
RangeType { ty: ptr_ty }
|
||||
}
|
||||
|
||||
/// Returns the type of all fields of this `range` type.
|
||||
#[must_use]
|
||||
pub fn value_type(&self) -> IntType<'ctx> {
|
||||
self.as_base_type().get_element_type().into_array_type().get_element_type().into_int_type()
|
||||
}
|
||||
|
||||
/// Allocates an instance of [`RangeValue`] as if by calling `alloca` on the base type.
|
||||
#[must_use]
|
||||
pub fn alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
self.raw_alloca(generator, ctx, name),
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts an existing value into a [`RangeValue`].
|
||||
#[must_use]
|
||||
pub fn map_value(
|
||||
&self,
|
||||
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(value, name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyType<'ctx> for RangeType<'ctx> {
|
||||
type Base = PointerType<'ctx>;
|
||||
type Value = RangeValue<'ctx>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
|
||||
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
|
||||
} else {
|
||||
Err(format!("Expected pointer type, got {llvm_ty:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
_: &G,
|
||||
_: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_representable(llvm_ty)
|
||||
}
|
||||
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base {
|
||||
generator
|
||||
.gen_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
generator
|
||||
.gen_array_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
size,
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn as_base_type(&self) -> Self::Base {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<RangeType<'ctx>> for PointerType<'ctx> {
|
||||
fn from(value: RangeType<'ctx>) -> Self {
|
||||
value.as_base_type()
|
||||
}
|
||||
}
|
255
nac3core/src/codegen/types/structure.rs
Normal file
255
nac3core/src/codegen/types/structure.rs
Normal file
@ -0,0 +1,255 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use inkwell::{
|
||||
context::AsContextRef,
|
||||
types::{BasicTypeEnum, IntType, StructType},
|
||||
values::{BasicValue, BasicValueEnum, IntValue, PointerValue, StructValue},
|
||||
};
|
||||
|
||||
use crate::codegen::CodeGenContext;
|
||||
|
||||
/// Trait indicating that the structure is a field-wise representation of an LLVM structure.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// For example, for a simple C-slice LLVM structure:
|
||||
///
|
||||
/// ```ignore
|
||||
/// struct CSliceFields<'ctx> {
|
||||
/// ptr: StructField<'ctx, PointerValue<'ctx>>,
|
||||
/// len: StructField<'ctx, IntValue<'ctx>>
|
||||
/// }
|
||||
/// ```
|
||||
pub trait StructFields<'ctx>: Eq + Copy {
|
||||
/// Creates an instance of [`StructFields`] using the given `ctx` and `size_t` types.
|
||||
fn new(ctx: impl AsContextRef<'ctx>, llvm_usize: IntType<'ctx>) -> Self;
|
||||
|
||||
/// Returns a [`Vec`] that contains the fields of the structure in the order as they appear in
|
||||
/// the type definition.
|
||||
#[must_use]
|
||||
fn to_vec(&self) -> Vec<(&'static str, BasicTypeEnum<'ctx>)>;
|
||||
|
||||
/// Returns a [`Iterator`] that contains the fields of the structure in the order as they appear
|
||||
/// in the type definition.
|
||||
#[must_use]
|
||||
fn iter(&self) -> impl Iterator<Item = (&'static str, BasicTypeEnum<'ctx>)> {
|
||||
self.to_vec().into_iter()
|
||||
}
|
||||
|
||||
/// Returns a [`Vec`] that contains the fields of the structure in the order as they appear in
|
||||
/// the type definition.
|
||||
#[must_use]
|
||||
fn into_vec(self) -> Vec<(&'static str, BasicTypeEnum<'ctx>)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.to_vec()
|
||||
}
|
||||
|
||||
/// Returns a [`Iterator`] that contains the fields of the structure in the order as they appear
|
||||
/// in the type definition.
|
||||
#[must_use]
|
||||
fn into_iter(self) -> impl Iterator<Item = (&'static str, BasicTypeEnum<'ctx>)>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.into_vec().into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
/// A single field of an LLVM structure.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct StructField<'ctx, Value>
|
||||
where
|
||||
Value: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>, Error = ()>,
|
||||
{
|
||||
/// The index of this field within the structure.
|
||||
index: u32,
|
||||
|
||||
/// The name of this field.
|
||||
name: &'static str,
|
||||
|
||||
/// The type of this field.
|
||||
ty: BasicTypeEnum<'ctx>,
|
||||
|
||||
/// Instance of [`PhantomData`] containing [`Value`], used to implement automatic downcasts.
|
||||
_value_ty: PhantomData<Value>,
|
||||
}
|
||||
|
||||
impl<'ctx, Value> StructField<'ctx, Value>
|
||||
where
|
||||
Value: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>, Error = ()>,
|
||||
{
|
||||
/// Creates an instance of [`StructField`].
|
||||
///
|
||||
/// * `idx_counter` - The instance of [`FieldIndexCounter`] used to track the current field
|
||||
/// index.
|
||||
/// * `name` - Name of the field.
|
||||
/// * `ty` - The type of this field.
|
||||
pub fn create(
|
||||
idx_counter: &mut FieldIndexCounter,
|
||||
name: &'static str,
|
||||
ty: impl Into<BasicTypeEnum<'ctx>>,
|
||||
) -> Self {
|
||||
StructField { index: idx_counter.increment(), name, ty: ty.into(), _value_ty: PhantomData }
|
||||
}
|
||||
|
||||
/// Creates an instance of [`StructField`] with a given index.
|
||||
///
|
||||
/// * `index` - The index of this field within its enclosing structure.
|
||||
/// * `name` - Name of the field.
|
||||
/// * `ty` - The type of this field.
|
||||
pub fn create_at(index: u32, name: &'static str, ty: impl Into<BasicTypeEnum<'ctx>>) -> Self {
|
||||
StructField { index, name, ty: ty.into(), _value_ty: PhantomData }
|
||||
}
|
||||
|
||||
/// Returns the name of this field.
|
||||
#[must_use]
|
||||
pub fn name(&self) -> &'static str {
|
||||
self.name
|
||||
}
|
||||
|
||||
/// Creates a pointer to this field in an arbitrary structure by performing a `getelementptr i32
|
||||
/// {idx...}, i32 {self.index}`.
|
||||
pub fn ptr_by_array_gep(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
pobj: PointerValue<'ctx>,
|
||||
idx: &[IntValue<'ctx>],
|
||||
) -> PointerValue<'ctx> {
|
||||
unsafe {
|
||||
ctx.builder.build_in_bounds_gep(
|
||||
pobj,
|
||||
&[idx, &[ctx.ctx.i32_type().const_int(u64::from(self.index), false)]].concat(),
|
||||
"",
|
||||
)
|
||||
}
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Creates a pointer to this field in an arbitrary structure by performing the equivalent of
|
||||
/// `getelementptr i32 0, i32 {self.index}`.
|
||||
pub fn ptr_by_gep(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
pobj: PointerValue<'ctx>,
|
||||
obj_name: Option<&'ctx str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
ctx.builder
|
||||
.build_struct_gep(
|
||||
pobj,
|
||||
self.index,
|
||||
&obj_name.map(|name| format!("{name}.{}.addr", self.name)).unwrap_or_default(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Gets the value of this field for a given `obj`.
|
||||
#[must_use]
|
||||
pub fn get_from_value(&self, obj: StructValue<'ctx>) -> Value {
|
||||
obj.get_field_at_index(self.index).and_then(|value| Value::try_from(value).ok()).unwrap()
|
||||
}
|
||||
|
||||
/// Sets the value of this field for a given `obj`.
|
||||
pub fn set_for_value(&self, obj: StructValue<'ctx>, value: Value) {
|
||||
obj.set_field_at_index(self.index, value);
|
||||
}
|
||||
|
||||
/// Gets the value of this field for a pointer-to-structure.
|
||||
pub fn get(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
pobj: PointerValue<'ctx>,
|
||||
obj_name: Option<&'ctx str>,
|
||||
) -> Value {
|
||||
ctx.builder
|
||||
.build_load(
|
||||
self.ptr_by_gep(ctx, pobj, obj_name),
|
||||
&obj_name.map(|name| format!("{name}.{}", self.name)).unwrap_or_default(),
|
||||
)
|
||||
.map_err(|_| ())
|
||||
.and_then(|value| Value::try_from(value))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Sets the value of this field for a pointer-to-structure.
|
||||
pub fn set(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
pobj: PointerValue<'ctx>,
|
||||
value: Value,
|
||||
obj_name: Option<&'ctx str>,
|
||||
) {
|
||||
ctx.builder.build_store(self.ptr_by_gep(ctx, pobj, obj_name), value).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, Value> From<StructField<'ctx, Value>> for (&'static str, BasicTypeEnum<'ctx>)
|
||||
where
|
||||
Value: BasicValue<'ctx> + TryFrom<BasicValueEnum<'ctx>, Error = ()>,
|
||||
{
|
||||
fn from(value: StructField<'ctx, Value>) -> Self {
|
||||
(value.name, value.ty)
|
||||
}
|
||||
}
|
||||
|
||||
/// A counter that tracks the next index of a field using a monotonically increasing counter.
|
||||
#[derive(Default, Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct FieldIndexCounter(u32);
|
||||
|
||||
impl FieldIndexCounter {
|
||||
/// Increments the number stored by this counter, returning the previous value.
|
||||
///
|
||||
/// Functionally equivalent to `i++` in C-based languages.
|
||||
pub fn increment(&mut self) -> u32 {
|
||||
let v = self.0;
|
||||
self.0 += 1;
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
type FieldTypeVerifier<'ctx> = dyn Fn(BasicTypeEnum<'ctx>) -> Result<(), String>;
|
||||
|
||||
/// Checks whether [`llvm_ty`][StructType] contains the fields described by the given
|
||||
/// [`StructFields`] instance.
|
||||
///
|
||||
/// By default, this function will compare the type of each field in `expected_fields` against
|
||||
/// `llvm_ty`. To override this behavior for individual fields, pass in overrides to
|
||||
/// `custom_verifiers`, which will use the specified verifier when a field with the matching field
|
||||
/// name is being checked.
|
||||
pub(super) fn check_struct_type_matches_fields<'ctx>(
|
||||
expected_fields: impl StructFields<'ctx>,
|
||||
llvm_ty: StructType<'ctx>,
|
||||
ty_name: &'static str,
|
||||
custom_verifiers: &[(&str, &FieldTypeVerifier<'ctx>)],
|
||||
) -> Result<(), String> {
|
||||
let expected_fields = expected_fields.to_vec();
|
||||
|
||||
if llvm_ty.count_fields() != u32::try_from(expected_fields.len()).unwrap() {
|
||||
return Err(format!(
|
||||
"Expected {} fields in `{ty_name}`, got {}",
|
||||
expected_fields.len(),
|
||||
llvm_ty.count_fields(),
|
||||
));
|
||||
}
|
||||
|
||||
expected_fields
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, (field_name, expected_ty))| {
|
||||
(field_name, expected_ty, llvm_ty.get_field_type_at_index(i as u32).unwrap())
|
||||
})
|
||||
.try_for_each(|(field_name, expected_ty, actual_ty)| {
|
||||
if let Some((_, verifier)) =
|
||||
custom_verifiers.iter().find(|verifier| verifier.0 == field_name)
|
||||
{
|
||||
verifier(actual_ty)
|
||||
} else if expected_ty == actual_ty {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Expected {expected_ty} for `{ty_name}.{field_name}`, got {actual_ty}"))
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
3
nac3core/src/codegen/types/utils/mod.rs
Normal file
3
nac3core/src/codegen/types/utils/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub use slice::*;
|
||||
|
||||
mod slice;
|
254
nac3core/src/codegen/types/utils/slice.rs
Normal file
254
nac3core/src/codegen/types/utils/slice.rs
Normal file
@ -0,0 +1,254 @@
|
||||
use inkwell::{
|
||||
context::{AsContextRef, Context, ContextRef},
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType, PointerType},
|
||||
values::IntValue,
|
||||
AddressSpace,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nac3core_derive::StructFields;
|
||||
|
||||
use crate::codegen::{
|
||||
types::{
|
||||
structure::{
|
||||
check_struct_type_matches_fields, FieldIndexCounter, StructField, StructFields,
|
||||
},
|
||||
ProxyType,
|
||||
},
|
||||
values::{utils::SliceValue, ArraySliceValue, ProxyValue},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct SliceType<'ctx> {
|
||||
ty: PointerType<'ctx>,
|
||||
int_ty: IntType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, StructFields)]
|
||||
pub struct SliceFields<'ctx> {
|
||||
#[value_type(bool_type())]
|
||||
pub start_defined: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
pub start: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(bool_type())]
|
||||
pub stop_defined: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
pub stop: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(bool_type())]
|
||||
pub step_defined: StructField<'ctx, IntValue<'ctx>>,
|
||||
#[value_type(usize)]
|
||||
pub step: StructField<'ctx, IntValue<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> SliceFields<'ctx> {
|
||||
/// Creates a new instance of [`SliceFields`] with a custom integer type for its range values.
|
||||
#[must_use]
|
||||
pub fn new_sized(ctx: &impl AsContextRef<'ctx>, int_ty: IntType<'ctx>) -> Self {
|
||||
let ctx = unsafe { ContextRef::new(ctx.as_ctx_ref()) };
|
||||
let mut counter = FieldIndexCounter::default();
|
||||
|
||||
SliceFields {
|
||||
start_defined: StructField::create(&mut counter, "start_defined", ctx.bool_type()),
|
||||
start: StructField::create(&mut counter, "start", int_ty),
|
||||
stop_defined: StructField::create(&mut counter, "stop_defined", ctx.bool_type()),
|
||||
stop: StructField::create(&mut counter, "stop", int_ty),
|
||||
step_defined: StructField::create(&mut counter, "step_defined", ctx.bool_type()),
|
||||
step: StructField::create(&mut counter, "step", int_ty),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> SliceType<'ctx> {
|
||||
/// Checks whether `llvm_ty` represents a `slice` type, returning [Err] if it does not.
|
||||
pub fn is_representable(
|
||||
llvm_ty: PointerType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
let ctx = llvm_ty.get_context();
|
||||
|
||||
let fields = SliceFields::new(ctx, llvm_usize);
|
||||
|
||||
let llvm_ty = llvm_ty.get_element_type();
|
||||
let AnyTypeEnum::StructType(llvm_ty) = llvm_ty else {
|
||||
return Err(format!("Expected struct type for `Slice` type, got {llvm_ty}"));
|
||||
};
|
||||
|
||||
check_struct_type_matches_fields(
|
||||
fields,
|
||||
llvm_ty,
|
||||
"Slice",
|
||||
&[
|
||||
(fields.start.name(), &|ty| {
|
||||
if ty.is_int_type() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Expected int type for `Slice.start`, got {ty}"))
|
||||
}
|
||||
}),
|
||||
(fields.stop.name(), &|ty| {
|
||||
if ty.is_int_type() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Expected int type for `Slice.stop`, got {ty}"))
|
||||
}
|
||||
}),
|
||||
(fields.step.name(), &|ty| {
|
||||
if ty.is_int_type() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(format!("Expected int type for `Slice.step`, got {ty}"))
|
||||
}
|
||||
}),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: Move this into e.g. StructProxyType
|
||||
#[must_use]
|
||||
pub fn get_fields(&self) -> SliceFields<'ctx> {
|
||||
SliceFields::new_sized(&self.int_ty.get_context(), self.int_ty)
|
||||
}
|
||||
|
||||
/// Creates an LLVM type corresponding to the expected structure of a `Slice`.
|
||||
#[must_use]
|
||||
fn llvm_type(ctx: &'ctx Context, int_ty: IntType<'ctx>) -> PointerType<'ctx> {
|
||||
let field_tys = SliceFields::new_sized(&int_ty.get_context(), int_ty)
|
||||
.into_iter()
|
||||
.map(|field| field.1)
|
||||
.collect_vec();
|
||||
|
||||
ctx.struct_type(&field_tys, false).ptr_type(AddressSpace::default())
|
||||
}
|
||||
|
||||
/// Creates an instance of [`SliceType`] with `int_ty` as its backing integer type.
|
||||
#[must_use]
|
||||
pub fn new(ctx: &'ctx Context, int_ty: IntType<'ctx>, llvm_usize: IntType<'ctx>) -> Self {
|
||||
let llvm_ty = Self::llvm_type(ctx, int_ty);
|
||||
|
||||
Self { ty: llvm_ty, int_ty, llvm_usize }
|
||||
}
|
||||
|
||||
/// Creates an instance of [`SliceType`] with `usize` as its backing integer type.
|
||||
#[must_use]
|
||||
pub fn new_usize<G: CodeGenerator + ?Sized>(generator: &G, ctx: &'ctx Context) -> Self {
|
||||
let llvm_usize = generator.get_size_type(ctx);
|
||||
Self::new(ctx, llvm_usize, llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`SliceType`] from a [`PointerType`] representing a `slice`.
|
||||
#[must_use]
|
||||
pub fn from_type(
|
||||
ptr_ty: PointerType<'ctx>,
|
||||
int_ty: IntType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr_ty, int_ty).is_ok());
|
||||
|
||||
Self { ty: ptr_ty, int_ty, llvm_usize }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn element_type(&self) -> IntType<'ctx> {
|
||||
self.int_ty
|
||||
}
|
||||
|
||||
/// Allocates an instance of [`ContiguousNDArrayValue`] as if by calling `alloca` on the base type.
|
||||
#[must_use]
|
||||
pub fn alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
self.raw_alloca(generator, ctx, name),
|
||||
self.int_ty,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
|
||||
/// Converts an existing value into a [`ContiguousNDArrayValue`].
|
||||
#[must_use]
|
||||
pub fn map_value(
|
||||
&self,
|
||||
value: <<Self as ProxyType<'ctx>>::Value as ProxyValue<'ctx>>::Base,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self as ProxyType<'ctx>>::Value {
|
||||
<Self as ProxyType<'ctx>>::Value::from_pointer_value(
|
||||
value,
|
||||
self.int_ty,
|
||||
self.llvm_usize,
|
||||
name,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyType<'ctx> for SliceType<'ctx> {
|
||||
type Base = PointerType<'ctx>;
|
||||
type Value = SliceValue<'ctx>;
|
||||
|
||||
fn is_type<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: impl BasicType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
if let BasicTypeEnum::PointerType(ty) = llvm_ty.as_basic_type_enum() {
|
||||
<Self as ProxyType<'ctx>>::is_representable(generator, ctx, ty)
|
||||
} else {
|
||||
Err(format!("Expected pointer type, got {llvm_ty:?}"))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
llvm_ty: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_representable(llvm_ty, generator.get_size_type(ctx))
|
||||
}
|
||||
|
||||
fn raw_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> <Self::Value as ProxyValue<'ctx>>::Base {
|
||||
generator
|
||||
.gen_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn array_alloca<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
generator
|
||||
.gen_array_var_alloc(
|
||||
ctx,
|
||||
self.as_base_type().get_element_type().into_struct_type().into(),
|
||||
size,
|
||||
name,
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn as_base_type(&self) -> Self::Base {
|
||||
self.ty
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<SliceType<'ctx>> for PointerType<'ctx> {
|
||||
fn from(value: SliceType<'ctx>) -> Self {
|
||||
value.as_base_type()
|
||||
}
|
||||
}
|
426
nac3core/src/codegen/values/array.rs
Normal file
426
nac3core/src/codegen/values/array.rs
Normal file
@ -0,0 +1,426 @@
|
||||
use inkwell::{
|
||||
types::AnyTypeEnum,
|
||||
values::{BasicValueEnum, IntValue, PointerValue},
|
||||
IntPredicate,
|
||||
};
|
||||
|
||||
use crate::codegen::{CodeGenContext, CodeGenerator};
|
||||
|
||||
/// An LLVM value that is array-like, i.e. it contains a contiguous, sequenced collection of
|
||||
/// elements.
|
||||
pub trait ArrayLikeValue<'ctx> {
|
||||
/// Returns the element type of this array-like value.
|
||||
fn element_type<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> AnyTypeEnum<'ctx>;
|
||||
|
||||
/// Returns the base pointer to the array.
|
||||
fn base_ptr<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> PointerValue<'ctx>;
|
||||
|
||||
/// Returns the size of this array-like value.
|
||||
fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> IntValue<'ctx>;
|
||||
|
||||
/// Returns a [`ArraySliceValue`] representing this value.
|
||||
fn as_slice_value<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> ArraySliceValue<'ctx> {
|
||||
ArraySliceValue::from_ptr_val(
|
||||
self.base_ptr(ctx, generator),
|
||||
self.size(ctx, generator),
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// An array-like value that can be indexed by memory offset.
|
||||
pub trait ArrayLikeIndexer<'ctx, Index = IntValue<'ctx>>: ArrayLikeValue<'ctx> {
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called with a valid index.
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx>;
|
||||
|
||||
/// Returns the pointer to the data at the `idx`-th index.
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx>;
|
||||
}
|
||||
|
||||
/// An array-like value that can have its array elements accessed as a [`BasicValueEnum`].
|
||||
pub trait UntypedArrayLikeAccessor<'ctx, Index = IntValue<'ctx>>:
|
||||
ArrayLikeIndexer<'ctx, Index>
|
||||
{
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called with a valid index.
|
||||
unsafe fn get_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ptr = unsafe { self.ptr_offset_unchecked(ctx, generator, idx, name) };
|
||||
ctx.builder.build_load(ptr, name.unwrap_or_default()).unwrap()
|
||||
}
|
||||
|
||||
/// Returns the data at the `idx`-th index.
|
||||
fn get<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let ptr = self.ptr_offset(ctx, generator, idx, name);
|
||||
ctx.builder.build_load(ptr, name.unwrap_or_default()).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// An array-like value that can have its array elements mutated as a [`BasicValueEnum`].
|
||||
pub trait UntypedArrayLikeMutator<'ctx, Index = IntValue<'ctx>>:
|
||||
ArrayLikeIndexer<'ctx, Index>
|
||||
{
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called with a valid index.
|
||||
unsafe fn set_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
let ptr = unsafe { self.ptr_offset_unchecked(ctx, generator, idx, None) };
|
||||
ctx.builder.build_store(ptr, value).unwrap();
|
||||
}
|
||||
|
||||
/// Sets the data at the `idx`-th index.
|
||||
fn set<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
let ptr = self.ptr_offset(ctx, generator, idx, None);
|
||||
ctx.builder.build_store(ptr, value).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// An array-like value that can have its array elements accessed as an arbitrary type `T`.
|
||||
pub trait TypedArrayLikeAccessor<'ctx, T, Index = IntValue<'ctx>>:
|
||||
UntypedArrayLikeAccessor<'ctx, Index>
|
||||
{
|
||||
/// Casts an element from [`BasicValueEnum`] into `T`.
|
||||
fn downcast_to_type(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) -> T;
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called with a valid index.
|
||||
unsafe fn get_typed_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> T {
|
||||
let value = unsafe { self.get_unchecked(ctx, generator, idx, name) };
|
||||
self.downcast_to_type(ctx, value)
|
||||
}
|
||||
|
||||
/// Returns the data at the `idx`-th index.
|
||||
fn get_typed<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> T {
|
||||
let value = self.get(ctx, generator, idx, name);
|
||||
self.downcast_to_type(ctx, value)
|
||||
}
|
||||
}
|
||||
|
||||
/// An array-like value that can have its array elements mutated as an arbitrary type `T`.
|
||||
pub trait TypedArrayLikeMutator<'ctx, T, Index = IntValue<'ctx>>:
|
||||
UntypedArrayLikeMutator<'ctx, Index>
|
||||
{
|
||||
/// Casts an element from T into [`BasicValueEnum`].
|
||||
fn upcast_from_type(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
value: T,
|
||||
) -> BasicValueEnum<'ctx>;
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// This function should be called with a valid index.
|
||||
unsafe fn set_typed_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
value: T,
|
||||
) {
|
||||
let value = self.upcast_from_type(ctx, value);
|
||||
unsafe { self.set_unchecked(ctx, generator, idx, value) }
|
||||
}
|
||||
|
||||
/// Sets the data at the `idx`-th index.
|
||||
fn set_typed<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
value: T,
|
||||
) {
|
||||
let value = self.upcast_from_type(ctx, value);
|
||||
self.set(ctx, generator, idx, value);
|
||||
}
|
||||
}
|
||||
|
||||
/// Type alias for a function that casts a [`BasicValueEnum`] into a `T`.
|
||||
type ValueDowncastFn<'ctx, T> =
|
||||
Box<dyn Fn(&mut CodeGenContext<'ctx, '_>, BasicValueEnum<'ctx>) -> T + 'ctx>;
|
||||
/// Type alias for a function that casts a `T` into a [`BasicValueEnum`].
|
||||
type ValueUpcastFn<'ctx, T> = Box<dyn Fn(&mut CodeGenContext<'ctx, '_>, T) -> BasicValueEnum<'ctx>>;
|
||||
|
||||
/// An adapter for constraining untyped array values as typed values.
|
||||
pub struct TypedArrayLikeAdapter<'ctx, T, Adapted: ArrayLikeValue<'ctx> = ArraySliceValue<'ctx>> {
|
||||
adapted: Adapted,
|
||||
downcast_fn: ValueDowncastFn<'ctx, T>,
|
||||
upcast_fn: ValueUpcastFn<'ctx, T>,
|
||||
}
|
||||
|
||||
impl<'ctx, T, Adapted> TypedArrayLikeAdapter<'ctx, T, Adapted>
|
||||
where
|
||||
Adapted: ArrayLikeValue<'ctx>,
|
||||
{
|
||||
/// Creates a [`TypedArrayLikeAdapter`].
|
||||
///
|
||||
/// * `adapted` - The value to be adapted.
|
||||
/// * `downcast_fn` - The function converting a [`BasicValueEnum`] into a `T`.
|
||||
/// * `upcast_fn` - The function converting a T into a [`BasicValueEnum`].
|
||||
pub fn from(
|
||||
adapted: Adapted,
|
||||
downcast_fn: ValueDowncastFn<'ctx, T>,
|
||||
upcast_fn: ValueUpcastFn<'ctx, T>,
|
||||
) -> Self {
|
||||
TypedArrayLikeAdapter { adapted, downcast_fn, upcast_fn }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, T, Adapted> ArrayLikeValue<'ctx> for TypedArrayLikeAdapter<'ctx, T, Adapted>
|
||||
where
|
||||
Adapted: ArrayLikeValue<'ctx>,
|
||||
{
|
||||
fn element_type<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> AnyTypeEnum<'ctx> {
|
||||
self.adapted.element_type(ctx, generator)
|
||||
}
|
||||
|
||||
fn base_ptr<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> PointerValue<'ctx> {
|
||||
self.adapted.base_ptr(ctx, generator)
|
||||
}
|
||||
|
||||
fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> IntValue<'ctx> {
|
||||
self.adapted.size(ctx, generator)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, T, Index, Adapted> ArrayLikeIndexer<'ctx, Index>
|
||||
for TypedArrayLikeAdapter<'ctx, T, Adapted>
|
||||
where
|
||||
Adapted: ArrayLikeIndexer<'ctx, Index>,
|
||||
{
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
unsafe { self.adapted.ptr_offset_unchecked(ctx, generator, idx, name) }
|
||||
}
|
||||
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &Index,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
self.adapted.ptr_offset(ctx, generator, idx, name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, T, Index, Adapted> UntypedArrayLikeAccessor<'ctx, Index>
|
||||
for TypedArrayLikeAdapter<'ctx, T, Adapted>
|
||||
where
|
||||
Adapted: UntypedArrayLikeAccessor<'ctx, Index>,
|
||||
{
|
||||
}
|
||||
impl<'ctx, T, Index, Adapted> UntypedArrayLikeMutator<'ctx, Index>
|
||||
for TypedArrayLikeAdapter<'ctx, T, Adapted>
|
||||
where
|
||||
Adapted: UntypedArrayLikeMutator<'ctx, Index>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<'ctx, T, Index, Adapted> TypedArrayLikeAccessor<'ctx, T, Index>
|
||||
for TypedArrayLikeAdapter<'ctx, T, Adapted>
|
||||
where
|
||||
Adapted: UntypedArrayLikeAccessor<'ctx, Index>,
|
||||
{
|
||||
fn downcast_to_type(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) -> T {
|
||||
(self.downcast_fn)(ctx, value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, T, Index, Adapted> TypedArrayLikeMutator<'ctx, T, Index>
|
||||
for TypedArrayLikeAdapter<'ctx, T, Adapted>
|
||||
where
|
||||
Adapted: UntypedArrayLikeMutator<'ctx, Index>,
|
||||
{
|
||||
fn upcast_from_type(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
value: T,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
(self.upcast_fn)(ctx, value)
|
||||
}
|
||||
}
|
||||
|
||||
/// An LLVM value representing an array slice, consisting of a pointer to the data and the size of
|
||||
/// the slice.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ArraySliceValue<'ctx>(PointerValue<'ctx>, IntValue<'ctx>, Option<&'ctx str>);
|
||||
|
||||
impl<'ctx> ArraySliceValue<'ctx> {
|
||||
/// Creates an [`ArraySliceValue`] from a [`PointerValue`] and its size.
|
||||
#[must_use]
|
||||
pub fn from_ptr_val(
|
||||
ptr: PointerValue<'ctx>,
|
||||
size: IntValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Self {
|
||||
ArraySliceValue(ptr, size, name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<ArraySliceValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: ArraySliceValue<'ctx>) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ArrayLikeValue<'ctx> for ArraySliceValue<'ctx> {
|
||||
fn element_type<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
_: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> AnyTypeEnum<'ctx> {
|
||||
self.0.get_type().get_element_type()
|
||||
}
|
||||
|
||||
fn base_ptr<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
_: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> PointerValue<'ctx> {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
_: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> IntValue<'ctx> {
|
||||
self.1
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ArrayLikeIndexer<'ctx> for ArraySliceValue<'ctx> {
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let var_name = name.map(|v| format!("{v}.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(self.base_ptr(ctx, generator), &[*idx], var_name.as_str())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
debug_assert_eq!(idx.get_type(), generator.get_size_type(ctx.ctx));
|
||||
|
||||
let size = self.size(ctx, generator);
|
||||
let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
in_range,
|
||||
"0:IndexError",
|
||||
"list index out of range",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
unsafe { self.ptr_offset_unchecked(ctx, generator, idx, name) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> UntypedArrayLikeAccessor<'ctx> for ArraySliceValue<'ctx> {}
|
||||
impl<'ctx> UntypedArrayLikeMutator<'ctx> for ArraySliceValue<'ctx> {}
|
241
nac3core/src/codegen/values/list.rs
Normal file
241
nac3core/src/codegen/values/list.rs
Normal file
@ -0,0 +1,241 @@
|
||||
use inkwell::{
|
||||
types::{AnyTypeEnum, BasicType, BasicTypeEnum, IntType},
|
||||
values::{BasicValueEnum, IntValue, PointerValue},
|
||||
AddressSpace, IntPredicate,
|
||||
};
|
||||
|
||||
use super::{
|
||||
ArrayLikeIndexer, ArrayLikeValue, ProxyValue, UntypedArrayLikeAccessor, UntypedArrayLikeMutator,
|
||||
};
|
||||
use crate::codegen::{
|
||||
types::ListType,
|
||||
{CodeGenContext, CodeGenerator},
|
||||
};
|
||||
|
||||
/// Proxy type for accessing a `list` value in LLVM.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ListValue<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
}
|
||||
|
||||
impl<'ctx> ListValue<'ctx> {
|
||||
/// Checks whether `value` is an instance of `list`, returning [Err] if `value` is not an
|
||||
/// instance.
|
||||
pub fn is_representable(
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
ListType::is_representable(value.get_type(), llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`ListValue`] from a [`PointerValue`].
|
||||
#[must_use]
|
||||
pub fn from_pointer_value(
|
||||
ptr: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok());
|
||||
|
||||
ListValue { value: ptr, llvm_usize, name }
|
||||
}
|
||||
|
||||
/// Returns the double-indirection pointer to the `data` array, as if by calling `getelementptr`
|
||||
/// on the field.
|
||||
fn pptr_to_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let var_name = self.name.map(|v| format!("{v}.data.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.as_base_value(),
|
||||
&[llvm_i32.const_zero(), llvm_i32.const_zero()],
|
||||
var_name.as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the pointer to the field storing the size of this `list`.
|
||||
fn ptr_to_size(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let var_name = self.name.map(|v| format!("{v}.size.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.as_base_value(),
|
||||
&[llvm_i32.const_zero(), llvm_i32.const_int(1, true)],
|
||||
var_name.as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the array of data elements `data` into this instance.
|
||||
fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, data: PointerValue<'ctx>) {
|
||||
ctx.builder.build_store(self.pptr_to_data(ctx), data).unwrap();
|
||||
}
|
||||
|
||||
/// Convenience method for creating a new array storing data elements with the given element
|
||||
/// type `elem_ty` and `size`.
|
||||
///
|
||||
/// If `size` is [None], the size stored in the field of this instance is used instead.
|
||||
pub fn create_data(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
elem_ty: BasicTypeEnum<'ctx>,
|
||||
size: Option<IntValue<'ctx>>,
|
||||
) {
|
||||
let size = size.unwrap_or_else(|| self.load_size(ctx, None));
|
||||
|
||||
let data = ctx
|
||||
.builder
|
||||
.build_select(
|
||||
ctx.builder
|
||||
.build_int_compare(IntPredicate::NE, size, self.llvm_usize.const_zero(), "")
|
||||
.unwrap(),
|
||||
ctx.builder.build_array_alloca(elem_ty, size, "").unwrap(),
|
||||
elem_ty.ptr_type(AddressSpace::default()).const_zero(),
|
||||
"",
|
||||
)
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap();
|
||||
self.store_data(ctx, data);
|
||||
}
|
||||
|
||||
/// Returns the double-indirection pointer to the `data` array, as if by calling `getelementptr`
|
||||
/// on the field.
|
||||
#[must_use]
|
||||
pub fn data(&self) -> ListDataProxy<'ctx, '_> {
|
||||
ListDataProxy(self)
|
||||
}
|
||||
|
||||
/// Stores the `size` of this `list` into this instance.
|
||||
pub fn store_size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
size: IntValue<'ctx>,
|
||||
) {
|
||||
debug_assert_eq!(size.get_type(), generator.get_size_type(ctx.ctx));
|
||||
|
||||
let psize = self.ptr_to_size(ctx);
|
||||
ctx.builder.build_store(psize, size).unwrap();
|
||||
}
|
||||
|
||||
/// Returns the size of this `list` as a value.
|
||||
pub fn load_size(&self, ctx: &CodeGenContext<'ctx, '_>, name: Option<&str>) -> IntValue<'ctx> {
|
||||
let psize = self.ptr_to_size(ctx);
|
||||
let var_name = name
|
||||
.map(ToString::to_string)
|
||||
.or_else(|| self.name.map(|v| format!("{v}.size")))
|
||||
.unwrap_or_default();
|
||||
|
||||
ctx.builder
|
||||
.build_load(psize, var_name.as_str())
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyValue<'ctx> for ListValue<'ctx> {
|
||||
type Base = PointerValue<'ctx>;
|
||||
type Type = ListType<'ctx>;
|
||||
|
||||
fn get_type(&self) -> Self::Type {
|
||||
ListType::from_type(self.as_base_value().get_type(), self.llvm_usize)
|
||||
}
|
||||
|
||||
fn as_base_value(&self) -> Self::Base {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<ListValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: ListValue<'ctx>) -> Self {
|
||||
value.as_base_value()
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy type for accessing the `data` array of an `list` instance in LLVM.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ListDataProxy<'ctx, 'a>(&'a ListValue<'ctx>);
|
||||
|
||||
impl<'ctx> ArrayLikeValue<'ctx> for ListDataProxy<'ctx, '_> {
|
||||
fn element_type<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
_: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> AnyTypeEnum<'ctx> {
|
||||
self.0.value.get_type().get_element_type()
|
||||
}
|
||||
|
||||
fn base_ptr<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> PointerValue<'ctx> {
|
||||
let var_name = self.0.name.map(|v| format!("{v}.data")).unwrap_or_default();
|
||||
|
||||
ctx.builder
|
||||
.build_load(self.0.pptr_to_data(ctx), var_name.as_str())
|
||||
.map(BasicValueEnum::into_pointer_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> IntValue<'ctx> {
|
||||
self.0.load_size(ctx, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ArrayLikeIndexer<'ctx> for ListDataProxy<'ctx, '_> {
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let var_name = name.map(|v| format!("{v}.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(self.base_ptr(ctx, generator), &[*idx], var_name.as_str())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
debug_assert_eq!(idx.get_type(), generator.get_size_type(ctx.ctx));
|
||||
|
||||
let size = self.size(ctx, generator);
|
||||
let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
in_range,
|
||||
"0:IndexError",
|
||||
"list index out of range",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
unsafe { self.ptr_offset_unchecked(ctx, generator, idx, name) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> UntypedArrayLikeAccessor<'ctx> for ListDataProxy<'ctx, '_> {}
|
||||
impl<'ctx> UntypedArrayLikeMutator<'ctx> for ListDataProxy<'ctx, '_> {}
|
47
nac3core/src/codegen/values/mod.rs
Normal file
47
nac3core/src/codegen/values/mod.rs
Normal file
@ -0,0 +1,47 @@
|
||||
use inkwell::{context::Context, values::BasicValue};
|
||||
|
||||
use super::types::ProxyType;
|
||||
use crate::codegen::CodeGenerator;
|
||||
pub use array::*;
|
||||
pub use list::*;
|
||||
pub use range::*;
|
||||
|
||||
mod array;
|
||||
mod list;
|
||||
pub mod ndarray;
|
||||
mod range;
|
||||
pub mod utils;
|
||||
|
||||
/// A LLVM type that is used to represent a non-primitive value in NAC3.
|
||||
pub trait ProxyValue<'ctx>: Into<Self::Base> {
|
||||
/// The type of LLVM values represented by this instance. This is usually the
|
||||
/// [LLVM pointer type][PointerValue].
|
||||
type Base: BasicValue<'ctx>;
|
||||
|
||||
/// The type of this value.
|
||||
type Type: ProxyType<'ctx, Value = Self>;
|
||||
|
||||
/// Checks whether `value` can be represented by this [`ProxyValue`].
|
||||
fn is_instance<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
value: impl BasicValue<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
Self::Type::is_type(generator, ctx, value.as_basic_value_enum().get_type())
|
||||
}
|
||||
|
||||
/// Checks whether `value` can be represented by this [`ProxyValue`].
|
||||
fn is_representable<G: CodeGenerator + ?Sized>(
|
||||
generator: &G,
|
||||
ctx: &'ctx Context,
|
||||
value: Self::Base,
|
||||
) -> Result<(), String> {
|
||||
Self::is_instance(generator, ctx, value.as_basic_value_enum())
|
||||
}
|
||||
|
||||
/// Returns the [type][ProxyType] of this value.
|
||||
fn get_type(&self) -> Self::Type;
|
||||
|
||||
/// Returns the [base value][Self::Base] of this proxy.
|
||||
fn as_base_value(&self) -> Self::Base;
|
||||
}
|
202
nac3core/src/codegen/values/ndarray/contiguous.rs
Normal file
202
nac3core/src/codegen/values/ndarray/contiguous.rs
Normal file
@ -0,0 +1,202 @@
|
||||
use inkwell::{
|
||||
types::{BasicType, BasicTypeEnum, IntType},
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
|
||||
use super::{ArrayLikeValue, NDArrayValue, ProxyValue};
|
||||
use crate::codegen::{
|
||||
stmt::gen_if_callback,
|
||||
types::{
|
||||
ndarray::{ContiguousNDArrayType, NDArrayType},
|
||||
structure::StructField,
|
||||
},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ContiguousNDArrayValue<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
item: BasicTypeEnum<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
}
|
||||
|
||||
impl<'ctx> ContiguousNDArrayValue<'ctx> {
|
||||
/// Checks whether `value` is an instance of `ContiguousNDArray`, returning [Err] if `value` is
|
||||
/// not an instance.
|
||||
pub fn is_representable(
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
<Self as ProxyValue<'ctx>>::Type::is_representable(value.get_type(), llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`ContiguousNDArrayValue`] from a [`PointerValue`].
|
||||
#[must_use]
|
||||
pub fn from_pointer_value(
|
||||
ptr: PointerValue<'ctx>,
|
||||
dtype: BasicTypeEnum<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok());
|
||||
|
||||
Self { value: ptr, item: dtype, llvm_usize, name }
|
||||
}
|
||||
|
||||
fn ndims_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().ndims
|
||||
}
|
||||
|
||||
pub fn store_ndims(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
|
||||
self.ndims_field().set(ctx, self.as_base_value(), value, self.name);
|
||||
}
|
||||
|
||||
fn shape_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
|
||||
self.get_type().get_fields().shape
|
||||
}
|
||||
|
||||
pub fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
|
||||
self.shape_field().set(ctx, self.as_base_value(), value, self.name);
|
||||
}
|
||||
|
||||
pub fn load_shape(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
self.shape_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
fn data_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
|
||||
self.get_type().get_fields().data
|
||||
}
|
||||
|
||||
pub fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
|
||||
self.data_field().set(ctx, self.as_base_value(), value, self.name);
|
||||
}
|
||||
|
||||
pub fn load_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
self.data_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyValue<'ctx> for ContiguousNDArrayValue<'ctx> {
|
||||
type Base = PointerValue<'ctx>;
|
||||
type Type = ContiguousNDArrayType<'ctx>;
|
||||
|
||||
fn get_type(&self) -> Self::Type {
|
||||
<Self as ProxyValue<'ctx>>::Type::from_type(
|
||||
self.as_base_value().get_type(),
|
||||
self.item,
|
||||
self.llvm_usize,
|
||||
)
|
||||
}
|
||||
|
||||
fn as_base_value(&self) -> Self::Base {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<ContiguousNDArrayValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: ContiguousNDArrayValue<'ctx>) -> Self {
|
||||
value.as_base_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> NDArrayValue<'ctx> {
|
||||
/// Create a [`ContiguousNDArrayValue`] from the contents of this ndarray.
|
||||
///
|
||||
/// This function may or may not be expensive depending on if this ndarray has contiguous data.
|
||||
///
|
||||
/// If this ndarray is not C-contiguous, this function will allocate memory on the stack for the
|
||||
/// `data` field of the returned [`ContiguousNDArrayValue`] and copy contents of this ndarray to
|
||||
/// there.
|
||||
///
|
||||
/// If this ndarray is C-contiguous, contents of this ndarray will not be copied. The created
|
||||
/// [`ContiguousNDArrayValue`] will share memory with this ndarray.
|
||||
pub fn make_contiguous_ndarray<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
) -> ContiguousNDArrayValue<'ctx> {
|
||||
let result = ContiguousNDArrayType::new(generator, ctx.ctx, self.dtype)
|
||||
.alloca(generator, ctx, self.name);
|
||||
|
||||
// Set ndims and shape.
|
||||
let ndims = self
|
||||
.ndims
|
||||
.map_or_else(|| self.load_ndims(ctx), |ndims| self.llvm_usize.const_int(ndims, false));
|
||||
result.store_ndims(ctx, ndims);
|
||||
|
||||
let shape = self.shape();
|
||||
result.store_shape(ctx, shape.base_ptr(ctx, generator));
|
||||
|
||||
gen_if_callback(
|
||||
generator,
|
||||
ctx,
|
||||
|generator, ctx| Ok(self.is_c_contiguous(generator, ctx)),
|
||||
|_, ctx| {
|
||||
// This ndarray is contiguous.
|
||||
let data = self.data_field(ctx).get(ctx, self.as_base_value(), self.name);
|
||||
let data = ctx
|
||||
.builder
|
||||
.build_pointer_cast(data, result.item.ptr_type(AddressSpace::default()), "")
|
||||
.unwrap();
|
||||
result.store_data(ctx, data);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
|generator, ctx| {
|
||||
// This ndarray is not contiguous. Do a full-copy on `data`. `make_copy` produces an
|
||||
// ndarray with contiguous `data`.
|
||||
let copied_ndarray = self.make_copy(generator, ctx);
|
||||
let data = copied_ndarray.data().base_ptr(ctx, generator);
|
||||
let data = ctx
|
||||
.builder
|
||||
.build_pointer_cast(data, result.item.ptr_type(AddressSpace::default()), "")
|
||||
.unwrap();
|
||||
result.store_data(ctx, data);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Create an [`NDArrayValue`] from a [`ContiguousNDArrayValue`].
|
||||
///
|
||||
/// The operation is cheap. The newly created [`NDArrayValue`] will share the same memory as the
|
||||
/// [`ContiguousNDArrayValue`].
|
||||
///
|
||||
/// `ndims` has to be provided as [`NDArrayValue`] requires a statically known `ndims` value,
|
||||
/// despite the fact that the information should be contained within the
|
||||
/// [`ContiguousNDArrayValue`].
|
||||
pub fn from_contiguous_ndarray<G: CodeGenerator + ?Sized>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
carray: ContiguousNDArrayValue<'ctx>,
|
||||
ndims: u64,
|
||||
) -> Self {
|
||||
// TODO: Debug assert `ndims == carray.ndims` to catch bugs.
|
||||
|
||||
// Allocate the resulting ndarray.
|
||||
let ndarray = NDArrayType::new(generator, ctx.ctx, carray.item, Some(ndims))
|
||||
.construct_uninitialized(generator, ctx, carray.name);
|
||||
|
||||
// Copy shape and update strides
|
||||
let shape = carray.load_shape(ctx);
|
||||
ndarray.copy_shape_from_array(generator, ctx, shape);
|
||||
ndarray.set_strides_contiguous(generator, ctx);
|
||||
|
||||
// Share data
|
||||
let data = carray.load_data(ctx);
|
||||
ndarray.store_data(
|
||||
ctx,
|
||||
ctx.builder
|
||||
.build_pointer_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "")
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
ndarray
|
||||
}
|
||||
}
|
262
nac3core/src/codegen/values/ndarray/indexing.rs
Normal file
262
nac3core/src/codegen/values/ndarray/indexing.rs
Normal file
@ -0,0 +1,262 @@
|
||||
use inkwell::{
|
||||
types::IntType,
|
||||
values::{IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
|
||||
use nac3parser::ast::{Expr, ExprKind};
|
||||
|
||||
use crate::{
|
||||
codegen::{
|
||||
irrt,
|
||||
types::{
|
||||
ndarray::{NDArrayType, NDIndexType},
|
||||
structure::StructField,
|
||||
utils::SliceType,
|
||||
},
|
||||
values::{ndarray::NDArrayValue, utils::RustSlice, ProxyValue},
|
||||
CodeGenContext, CodeGenerator,
|
||||
},
|
||||
typecheck::typedef::Type,
|
||||
};
|
||||
|
||||
/// An IRRT representation of an ndarray subscript index.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NDIndexValue<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
}
|
||||
|
||||
impl<'ctx> NDIndexValue<'ctx> {
|
||||
/// Checks whether `value` is an instance of `ndindex`, returning [Err] if `value` is not an
|
||||
/// instance.
|
||||
pub fn is_representable(
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
<Self as ProxyValue<'ctx>>::Type::is_representable(value.get_type(), llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`NDIndexValue`] from a [`PointerValue`].
|
||||
#[must_use]
|
||||
pub fn from_pointer_value(
|
||||
ptr: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok());
|
||||
|
||||
Self { value: ptr, llvm_usize, name }
|
||||
}
|
||||
|
||||
fn type_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().type_
|
||||
}
|
||||
|
||||
pub fn load_type(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.type_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
pub fn store_type(&self, ctx: &CodeGenContext<'ctx, '_>, value: IntValue<'ctx>) {
|
||||
self.type_field().set(ctx, self.value, value, self.name);
|
||||
}
|
||||
|
||||
fn data_field(&self) -> StructField<'ctx, PointerValue<'ctx>> {
|
||||
self.get_type().get_fields().data
|
||||
}
|
||||
|
||||
pub fn load_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
self.data_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
pub fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, value: PointerValue<'ctx>) {
|
||||
self.data_field().set(ctx, self.value, value, self.name);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyValue<'ctx> for NDIndexValue<'ctx> {
|
||||
type Base = PointerValue<'ctx>;
|
||||
type Type = NDIndexType<'ctx>;
|
||||
|
||||
fn get_type(&self) -> Self::Type {
|
||||
Self::Type::from_type(self.value.get_type(), self.llvm_usize)
|
||||
}
|
||||
|
||||
fn as_base_value(&self) -> Self::Base {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<NDIndexValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: NDIndexValue<'ctx>) -> Self {
|
||||
value.as_base_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> NDArrayValue<'ctx> {
|
||||
/// Get the expected `ndims` after indexing with `indices`.
|
||||
#[must_use]
|
||||
fn deduce_ndims_after_indexing_with(&self, indices: &[RustNDIndex<'ctx>]) -> Option<u64> {
|
||||
let mut ndims = self.ndims?;
|
||||
|
||||
for index in indices {
|
||||
match index {
|
||||
RustNDIndex::SingleElement(_) => {
|
||||
ndims -= 1; // Single elements decrements ndims
|
||||
}
|
||||
RustNDIndex::NewAxis => {
|
||||
ndims += 1; // `np.newaxis` / `none` adds a new axis
|
||||
}
|
||||
RustNDIndex::Ellipsis | RustNDIndex::Slice(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
Some(ndims)
|
||||
}
|
||||
|
||||
/// Index into the ndarray, and return a newly-allocated view on this ndarray.
|
||||
///
|
||||
/// This function behaves like NumPy's ndarray indexing, but if the indices index
|
||||
/// into a single element, an unsized ndarray is returned.
|
||||
#[must_use]
|
||||
pub fn index<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
indices: &[RustNDIndex<'ctx>],
|
||||
) -> Self {
|
||||
assert!(self.ndims.is_some(), "NDArrayValue::index is only supported for instances with compile-time known ndims (self.ndims = Some(...))");
|
||||
|
||||
let dst_ndims = self.deduce_ndims_after_indexing_with(indices);
|
||||
let dst_ndarray = NDArrayType::new(generator, ctx.ctx, self.dtype, dst_ndims)
|
||||
.construct_uninitialized(generator, ctx, None);
|
||||
|
||||
let indices =
|
||||
NDIndexType::new(generator, ctx.ctx).construct_ndindices(generator, ctx, indices);
|
||||
irrt::ndarray::call_nac3_ndarray_index(generator, ctx, indices, *self, dst_ndarray);
|
||||
|
||||
dst_ndarray
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience enum representing a [`NDIndexValue`].
|
||||
// TODO: Rename to CTConstNDIndex
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RustNDIndex<'ctx> {
|
||||
SingleElement(IntValue<'ctx>),
|
||||
Slice(RustSlice<'ctx>),
|
||||
NewAxis,
|
||||
Ellipsis,
|
||||
}
|
||||
|
||||
impl<'ctx> RustNDIndex<'ctx> {
|
||||
/// Generate LLVM code to transform an ndarray subscript expression to
|
||||
/// its list of [`RustNDIndex`]
|
||||
///
|
||||
/// i.e.,
|
||||
/// ```python
|
||||
/// my_ndarray[::3, 1, :2:]
|
||||
/// ^^^^^^^^^^^ Then these into a three `RustNDIndex`es
|
||||
/// ```
|
||||
pub fn from_subscript_expr<G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
subscript: &Expr<Option<Type>>,
|
||||
) -> Result<Vec<RustNDIndex<'ctx>>, String> {
|
||||
// Annoying notes about `slice`
|
||||
// - `my_array[5]`
|
||||
// - slice is a `Constant`
|
||||
// - `my_array[:5]`
|
||||
// - slice is a `Slice`
|
||||
// - `my_array[:]`
|
||||
// - slice is a `Slice`, but lower upper step would all be `Option::None`
|
||||
// - `my_array[:, :]`
|
||||
// - slice is now a `Tuple` of two `Slice`-s
|
||||
//
|
||||
// In summary:
|
||||
// - when there is a comma "," within [], `slice` will be a `Tuple` of the entries.
|
||||
// - when there is not comma "," within [] (i.e., just a single entry), `slice` will be that entry itself.
|
||||
//
|
||||
// So we first "flatten" out the slice expression
|
||||
let index_exprs = match &subscript.node {
|
||||
ExprKind::Tuple { elts, .. } => elts.iter().collect_vec(),
|
||||
_ => vec![subscript],
|
||||
};
|
||||
|
||||
// Process all index expressions
|
||||
let mut rust_ndindices: Vec<RustNDIndex> = Vec::with_capacity(index_exprs.len()); // Not using iterators here because `?` is used here.
|
||||
for index_expr in index_exprs {
|
||||
// NOTE: Currently nac3core's slices do not have an object representation,
|
||||
// so the code/implementation looks awkward - we have to do pattern matching on the expression
|
||||
let ndindex = if let ExprKind::Slice { lower, upper, step } = &index_expr.node {
|
||||
// Handle slices
|
||||
let slice = RustSlice::from_slice_expr(generator, ctx, lower, upper, step)?;
|
||||
RustNDIndex::Slice(slice)
|
||||
} else {
|
||||
// Treat and handle everything else as a single element index.
|
||||
let index = generator.gen_expr(ctx, index_expr)?.unwrap().to_basic_value_enum(
|
||||
ctx,
|
||||
generator,
|
||||
ctx.primitives.int32, // Must be int32, this checks for illegal values
|
||||
)?;
|
||||
let index = index.into_int_value();
|
||||
|
||||
RustNDIndex::SingleElement(index)
|
||||
};
|
||||
rust_ndindices.push(ndindex);
|
||||
}
|
||||
Ok(rust_ndindices)
|
||||
}
|
||||
|
||||
/// Get the value to set `NDIndex::type` for this variant.
|
||||
#[must_use]
|
||||
pub fn get_type_id(&self) -> u64 {
|
||||
// Defined in IRRT, must be in sync
|
||||
match self {
|
||||
RustNDIndex::SingleElement(_) => 0,
|
||||
RustNDIndex::Slice(_) => 1,
|
||||
RustNDIndex::NewAxis => 2,
|
||||
RustNDIndex::Ellipsis => 3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize this [`RustNDIndex`] by writing it into an LLVM [`NDIndexValue`].
|
||||
pub fn write_to_ndindex<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
dst_ndindex: NDIndexValue<'ctx>,
|
||||
) {
|
||||
let llvm_pi8 = ctx.ctx.i8_type().ptr_type(AddressSpace::default());
|
||||
|
||||
// Set `dst_ndindex.type`
|
||||
dst_ndindex.store_type(ctx, ctx.ctx.i8_type().const_int(self.get_type_id(), false));
|
||||
|
||||
// Set `dst_ndindex_ptr->data`
|
||||
match self {
|
||||
RustNDIndex::SingleElement(in_index) => {
|
||||
let index_ptr = ctx.builder.build_alloca(ctx.ctx.i32_type(), "").unwrap();
|
||||
ctx.builder.build_store(index_ptr, *in_index).unwrap();
|
||||
|
||||
dst_ndindex.store_data(
|
||||
ctx,
|
||||
ctx.builder.build_pointer_cast(index_ptr, llvm_pi8, "").unwrap(),
|
||||
);
|
||||
}
|
||||
RustNDIndex::Slice(in_rust_slice) => {
|
||||
let user_slice_ptr =
|
||||
SliceType::new(ctx.ctx, ctx.ctx.i32_type(), generator.get_size_type(ctx.ctx))
|
||||
.alloca(generator, ctx, None);
|
||||
in_rust_slice.write_to_slice(ctx, user_slice_ptr);
|
||||
|
||||
dst_ndindex.store_data(
|
||||
ctx,
|
||||
ctx.builder.build_pointer_cast(user_slice_ptr.into(), llvm_pi8, "").unwrap(),
|
||||
);
|
||||
}
|
||||
RustNDIndex::NewAxis | RustNDIndex::Ellipsis => {}
|
||||
}
|
||||
}
|
||||
}
|
933
nac3core/src/codegen/values/ndarray/mod.rs
Normal file
933
nac3core/src/codegen/values/ndarray/mod.rs
Normal file
@ -0,0 +1,933 @@
|
||||
use inkwell::{
|
||||
types::{AnyType, AnyTypeEnum, BasicType, BasicTypeEnum, IntType},
|
||||
values::{BasicValueEnum, IntValue, PointerValue},
|
||||
AddressSpace, IntPredicate,
|
||||
};
|
||||
|
||||
use super::{
|
||||
ArrayLikeIndexer, ArrayLikeValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeMutator,
|
||||
UntypedArrayLikeAccessor, UntypedArrayLikeMutator,
|
||||
};
|
||||
use crate::codegen::{
|
||||
irrt,
|
||||
llvm_intrinsics::{call_int_umin, call_memcpy_generic_array},
|
||||
stmt::gen_for_callback_incrementing,
|
||||
type_aligned_alloca,
|
||||
types::{ndarray::NDArrayType, structure::StructField},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
pub use contiguous::*;
|
||||
pub use indexing::*;
|
||||
pub use nditer::*;
|
||||
pub use view::*;
|
||||
|
||||
mod contiguous;
|
||||
mod indexing;
|
||||
mod nditer;
|
||||
mod view;
|
||||
|
||||
/// Proxy type for accessing an `NDArray` value in LLVM.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NDArrayValue<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
dtype: BasicTypeEnum<'ctx>,
|
||||
ndims: Option<u64>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
}
|
||||
|
||||
impl<'ctx> NDArrayValue<'ctx> {
|
||||
/// Checks whether `value` is an instance of `NDArray`, returning [Err] if `value` is not an
|
||||
/// instance.
|
||||
pub fn is_representable(
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
NDArrayType::is_representable(value.get_type(), llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`NDArrayValue`] from a [`PointerValue`].
|
||||
#[must_use]
|
||||
pub fn from_pointer_value(
|
||||
ptr: PointerValue<'ctx>,
|
||||
dtype: BasicTypeEnum<'ctx>,
|
||||
ndims: Option<u64>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok());
|
||||
|
||||
NDArrayValue { value: ptr, dtype, ndims, llvm_usize, name }
|
||||
}
|
||||
|
||||
fn ndims_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields(ctx.ctx).ndims
|
||||
}
|
||||
|
||||
/// Returns the pointer to the field storing the number of dimensions of this `NDArray`.
|
||||
fn ptr_to_ndims(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
self.ndims_field(ctx).ptr_by_gep(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
/// Stores the number of dimensions `ndims` into this instance.
|
||||
pub fn store_ndims<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
ndims: IntValue<'ctx>,
|
||||
) {
|
||||
debug_assert_eq!(ndims.get_type(), generator.get_size_type(ctx.ctx));
|
||||
|
||||
let pndims = self.ptr_to_ndims(ctx);
|
||||
ctx.builder.build_store(pndims, ndims).unwrap();
|
||||
}
|
||||
|
||||
/// Returns the number of dimensions of this `NDArray` as a value.
|
||||
pub fn load_ndims(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
let pndims = self.ptr_to_ndims(ctx);
|
||||
ctx.builder.build_load(pndims, "").map(BasicValueEnum::into_int_value).unwrap()
|
||||
}
|
||||
|
||||
fn itemsize_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields(ctx.ctx).itemsize
|
||||
}
|
||||
|
||||
/// Stores the size of each element `itemsize` into this instance.
|
||||
pub fn store_itemsize<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
itemsize: IntValue<'ctx>,
|
||||
) {
|
||||
debug_assert_eq!(itemsize.get_type(), generator.get_size_type(ctx.ctx));
|
||||
|
||||
self.itemsize_field(ctx).set(ctx, self.value, itemsize, self.name);
|
||||
}
|
||||
|
||||
/// Returns the size of each element of this `NDArray` as a value.
|
||||
pub fn load_itemsize(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.itemsize_field(ctx).get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
fn shape_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> {
|
||||
self.get_type().get_fields(ctx.ctx).shape
|
||||
}
|
||||
|
||||
/// Returns the double-indirection pointer to the `shape` array, as if by calling
|
||||
/// `getelementptr` on the field.
|
||||
fn ptr_to_shape(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
self.shape_field(ctx).ptr_by_gep(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
/// Stores the array of dimension sizes `dims` into this instance.
|
||||
fn store_shape(&self, ctx: &CodeGenContext<'ctx, '_>, dims: PointerValue<'ctx>) {
|
||||
self.shape_field(ctx).set(ctx, self.as_base_value(), dims, self.name);
|
||||
}
|
||||
|
||||
/// Convenience method for creating a new array storing dimension sizes with the given `size`.
|
||||
pub fn create_shape(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
size: IntValue<'ctx>,
|
||||
) {
|
||||
self.store_shape(ctx, ctx.builder.build_array_alloca(llvm_usize, size, "").unwrap());
|
||||
}
|
||||
|
||||
/// Returns a proxy object to the field storing the size of each dimension of this `NDArray`.
|
||||
#[must_use]
|
||||
pub fn shape(&self) -> NDArrayShapeProxy<'ctx, '_> {
|
||||
NDArrayShapeProxy(self)
|
||||
}
|
||||
|
||||
fn strides_field(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
) -> StructField<'ctx, PointerValue<'ctx>> {
|
||||
self.get_type().get_fields(ctx.ctx).strides
|
||||
}
|
||||
|
||||
/// Returns the double-indirection pointer to the `strides` array, as if by calling
|
||||
/// `getelementptr` on the field.
|
||||
fn ptr_to_strides(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
self.strides_field(ctx).ptr_by_gep(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
/// Stores the array of stride sizes `strides` into this instance.
|
||||
fn store_strides(&self, ctx: &CodeGenContext<'ctx, '_>, strides: PointerValue<'ctx>) {
|
||||
self.strides_field(ctx).set(ctx, self.as_base_value(), strides, self.name);
|
||||
}
|
||||
|
||||
/// Convenience method for creating a new array storing the stride with the given `size`.
|
||||
pub fn create_strides(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
size: IntValue<'ctx>,
|
||||
) {
|
||||
self.store_strides(ctx, ctx.builder.build_array_alloca(llvm_usize, size, "").unwrap());
|
||||
}
|
||||
|
||||
/// Returns a proxy object to the field storing the stride of each dimension of this `NDArray`.
|
||||
#[must_use]
|
||||
pub fn strides(&self) -> NDArrayStridesProxy<'ctx, '_> {
|
||||
NDArrayStridesProxy(self)
|
||||
}
|
||||
|
||||
fn data_field(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> {
|
||||
self.get_type().get_fields(ctx.ctx).data
|
||||
}
|
||||
|
||||
/// Returns the double-indirection pointer to the `data` array, as if by calling `getelementptr`
|
||||
/// on the field.
|
||||
pub fn ptr_to_data(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
self.data_field(ctx).ptr_by_gep(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
/// Stores the array of data elements `data` into this instance.
|
||||
fn store_data(&self, ctx: &CodeGenContext<'ctx, '_>, data: PointerValue<'ctx>) {
|
||||
let data = ctx
|
||||
.builder
|
||||
.build_bit_cast(data, ctx.ctx.i8_type().ptr_type(AddressSpace::default()), "")
|
||||
.unwrap();
|
||||
self.data_field(ctx).set(ctx, self.as_base_value(), data.into_pointer_value(), self.name);
|
||||
}
|
||||
|
||||
/// Convenience method for creating a new array storing data elements with the given element
|
||||
/// type `elem_ty` and `size`.
|
||||
///
|
||||
/// The data buffer will be allocated on the stack, and is considered to be owned by this ndarray instance.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The caller must ensure that `shape` and `itemsize` of this ndarray instance is initialized.
|
||||
pub unsafe fn create_data<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
) {
|
||||
let nbytes = self.nbytes(generator, ctx);
|
||||
|
||||
let data = type_aligned_alloca(generator, ctx, self.dtype, nbytes, None);
|
||||
self.store_data(ctx, data);
|
||||
|
||||
self.set_strides_contiguous(generator, ctx);
|
||||
}
|
||||
|
||||
/// Returns a proxy object to the field storing the data of this `NDArray`.
|
||||
#[must_use]
|
||||
pub fn data(&self) -> NDArrayDataProxy<'ctx, '_> {
|
||||
NDArrayDataProxy(self)
|
||||
}
|
||||
|
||||
/// Copy shape dimensions from an array.
|
||||
pub fn copy_shape_from_array<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
shape: PointerValue<'ctx>,
|
||||
) {
|
||||
let num_items = self.load_ndims(ctx);
|
||||
|
||||
call_memcpy_generic_array(
|
||||
ctx,
|
||||
self.shape().base_ptr(ctx, generator),
|
||||
shape,
|
||||
num_items,
|
||||
ctx.ctx.bool_type().const_zero(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Copy shape dimensions from an ndarray.
|
||||
/// Panics if `ndims` mismatches.
|
||||
pub fn copy_shape_from_ndarray<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
src_ndarray: NDArrayValue<'ctx>,
|
||||
) {
|
||||
if self.ndims.is_some() && src_ndarray.ndims.is_some() {
|
||||
assert_eq!(self.ndims, src_ndarray.ndims);
|
||||
} else {
|
||||
let self_ndims = self.load_ndims(ctx);
|
||||
let src_ndims = src_ndarray.load_ndims(ctx);
|
||||
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
ctx.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
self_ndims,
|
||||
src_ndims,
|
||||
""
|
||||
).unwrap(),
|
||||
"0:AssertionError",
|
||||
"NDArrayValue::copy_shape_from_ndarray: Expected self.ndims ({0}) == src_ndarray.ndims ({1})",
|
||||
[Some(self_ndims), Some(src_ndims), None],
|
||||
ctx.current_loc
|
||||
);
|
||||
}
|
||||
|
||||
let src_shape = src_ndarray.shape().base_ptr(ctx, generator);
|
||||
self.copy_shape_from_array(generator, ctx, src_shape);
|
||||
}
|
||||
|
||||
/// Copy strides dimensions from an array.
|
||||
pub fn copy_strides_from_array<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
strides: PointerValue<'ctx>,
|
||||
) {
|
||||
let num_items = self.load_ndims(ctx);
|
||||
|
||||
call_memcpy_generic_array(
|
||||
ctx,
|
||||
self.strides().base_ptr(ctx, generator),
|
||||
strides,
|
||||
num_items,
|
||||
ctx.ctx.bool_type().const_zero(),
|
||||
);
|
||||
}
|
||||
|
||||
/// Copy strides dimensions from an ndarray.
|
||||
/// Panics if `ndims` mismatches.
|
||||
pub fn copy_strides_from_ndarray<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
src_ndarray: NDArrayValue<'ctx>,
|
||||
) {
|
||||
if self.ndims.is_some() && src_ndarray.ndims.is_some() {
|
||||
assert_eq!(self.ndims, src_ndarray.ndims);
|
||||
} else {
|
||||
let self_ndims = self.load_ndims(ctx);
|
||||
let src_ndims = src_ndarray.load_ndims(ctx);
|
||||
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
ctx.builder.build_int_compare(
|
||||
IntPredicate::EQ,
|
||||
self_ndims,
|
||||
src_ndims,
|
||||
""
|
||||
).unwrap(),
|
||||
"0:AssertionError",
|
||||
"NDArrayValue::copy_shape_from_ndarray: Expected self.ndims ({0}) == src_ndarray.ndims ({1})",
|
||||
[Some(self_ndims), Some(src_ndims), None],
|
||||
ctx.current_loc
|
||||
);
|
||||
}
|
||||
|
||||
let src_strides = src_ndarray.strides().base_ptr(ctx, generator);
|
||||
self.copy_strides_from_array(generator, ctx, src_strides);
|
||||
}
|
||||
|
||||
/// Get the `np.size()` of this ndarray.
|
||||
pub fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
) -> IntValue<'ctx> {
|
||||
irrt::ndarray::call_nac3_ndarray_size(generator, ctx, *self)
|
||||
}
|
||||
|
||||
/// Get the `ndarray.nbytes` of this ndarray.
|
||||
pub fn nbytes<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
) -> IntValue<'ctx> {
|
||||
irrt::ndarray::call_nac3_ndarray_nbytes(generator, ctx, *self)
|
||||
}
|
||||
|
||||
/// Get the `len()` of this ndarray.
|
||||
pub fn len<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
) -> IntValue<'ctx> {
|
||||
irrt::ndarray::call_nac3_ndarray_len(generator, ctx, *self)
|
||||
}
|
||||
|
||||
/// Check if this ndarray is C-contiguous.
|
||||
///
|
||||
/// See NumPy's `flags["C_CONTIGUOUS"]`: <https://numpy.org/doc/stable/reference/generated/numpy.ndarray.flags.html#numpy.ndarray.flags>
|
||||
pub fn is_c_contiguous<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
) -> IntValue<'ctx> {
|
||||
irrt::ndarray::call_nac3_ndarray_is_c_contiguous(generator, ctx, *self)
|
||||
}
|
||||
|
||||
/// Call [`call_nac3_ndarray_set_strides_by_shape`] on this ndarray to update `strides`.
|
||||
///
|
||||
/// Update the ndarray's strides to make the ndarray contiguous.
|
||||
pub fn set_strides_contiguous<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
) {
|
||||
irrt::ndarray::call_nac3_ndarray_set_strides_by_shape(generator, ctx, *self);
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn make_copy<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
) -> Self {
|
||||
let clone = if self.ndims.is_some() {
|
||||
self.get_type().construct_uninitialized(generator, ctx, None)
|
||||
} else {
|
||||
self.get_type().construct_dyn_ndims(generator, ctx, self.load_ndims(ctx), None)
|
||||
};
|
||||
|
||||
let shape = self.shape();
|
||||
clone.copy_shape_from_array(generator, ctx, shape.base_ptr(ctx, generator));
|
||||
unsafe { clone.create_data(generator, ctx) };
|
||||
clone.copy_data_from(generator, ctx, *self);
|
||||
clone
|
||||
}
|
||||
|
||||
/// Copy data from another ndarray.
|
||||
///
|
||||
/// This ndarray and `src` is that their `np.size()` should be the same. Their shapes
|
||||
/// do not matter. The copying order is determined by how their flattened views look.
|
||||
///
|
||||
/// Panics if the `dtype`s of ndarrays are different.
|
||||
pub fn copy_data_from<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
src: NDArrayValue<'ctx>,
|
||||
) {
|
||||
assert_eq!(self.dtype, src.dtype, "self and src dtype should match");
|
||||
irrt::ndarray::call_nac3_ndarray_copy_data(generator, ctx, src, *self);
|
||||
}
|
||||
|
||||
/// Returns true if this ndarray is unsized - `ndims == 0` and only contains a scalar.
|
||||
#[must_use]
|
||||
pub fn is_unsized(&self) -> Option<bool> {
|
||||
self.ndims.map(|ndims| ndims == 0)
|
||||
}
|
||||
|
||||
/// If this ndarray is unsized, return its sole value as an [`AnyObject`].
|
||||
/// Otherwise, do nothing and return the ndarray itself.
|
||||
// TODO: Rename to get_unsized_element
|
||||
pub fn split_unsized<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
) -> ScalarOrNDArray<'ctx> {
|
||||
let Some(is_unsized) = self.is_unsized() else { todo!() };
|
||||
|
||||
if is_unsized {
|
||||
// NOTE: `np.size(self) == 0` here is never possible.
|
||||
let zero = generator.get_size_type(ctx.ctx).const_zero();
|
||||
let value = unsafe { self.data().get_unchecked(ctx, generator, &zero, None) };
|
||||
|
||||
ScalarOrNDArray::Scalar(value)
|
||||
} else {
|
||||
ScalarOrNDArray::NDArray(*self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyValue<'ctx> for NDArrayValue<'ctx> {
|
||||
type Base = PointerValue<'ctx>;
|
||||
type Type = NDArrayType<'ctx>;
|
||||
|
||||
fn get_type(&self) -> Self::Type {
|
||||
NDArrayType::from_type(
|
||||
self.as_base_value().get_type(),
|
||||
self.dtype,
|
||||
self.ndims,
|
||||
self.llvm_usize,
|
||||
)
|
||||
}
|
||||
|
||||
fn as_base_value(&self) -> Self::Base {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<NDArrayValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: NDArrayValue<'ctx>) -> Self {
|
||||
value.as_base_value()
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy type for accessing the `shape` array of an `NDArray` instance in LLVM.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NDArrayShapeProxy<'ctx, 'a>(&'a NDArrayValue<'ctx>);
|
||||
|
||||
impl<'ctx> ArrayLikeValue<'ctx> for NDArrayShapeProxy<'ctx, '_> {
|
||||
fn element_type<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> AnyTypeEnum<'ctx> {
|
||||
self.0.shape().base_ptr(ctx, generator).get_type().get_element_type()
|
||||
}
|
||||
|
||||
fn base_ptr<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> PointerValue<'ctx> {
|
||||
self.0.shape_field(ctx).get(ctx, self.0.as_base_value(), self.0.name)
|
||||
}
|
||||
|
||||
fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> IntValue<'ctx> {
|
||||
self.0.load_ndims(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let var_name = name.map(|v| format!("{v}.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(self.base_ptr(ctx, generator), &[*idx], var_name.as_str())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let size = self.size(ctx, generator);
|
||||
let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
in_range,
|
||||
"0:IndexError",
|
||||
"index {0} is out of bounds for axis 0 with size {1}",
|
||||
[Some(*idx), Some(self.0.load_ndims(ctx)), None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
unsafe { self.ptr_offset_unchecked(ctx, generator, idx, name) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> UntypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {}
|
||||
impl<'ctx> UntypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {}
|
||||
|
||||
impl<'ctx> TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {
|
||||
fn downcast_to_type(
|
||||
&self,
|
||||
_: &mut CodeGenContext<'ctx, '_>,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
value.into_int_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> TypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayShapeProxy<'ctx, '_> {
|
||||
fn upcast_from_type(
|
||||
&self,
|
||||
_: &mut CodeGenContext<'ctx, '_>,
|
||||
value: IntValue<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy type for accessing the `strides` array of an `NDArray` instance in LLVM.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NDArrayStridesProxy<'ctx, 'a>(&'a NDArrayValue<'ctx>);
|
||||
|
||||
impl<'ctx> ArrayLikeValue<'ctx> for NDArrayStridesProxy<'ctx, '_> {
|
||||
fn element_type<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> AnyTypeEnum<'ctx> {
|
||||
self.0.strides().base_ptr(ctx, generator).get_type().get_element_type()
|
||||
}
|
||||
|
||||
fn base_ptr<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> PointerValue<'ctx> {
|
||||
self.0.strides_field(ctx).get(ctx, self.0.as_base_value(), self.0.name)
|
||||
}
|
||||
|
||||
fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> IntValue<'ctx> {
|
||||
self.0.load_ndims(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ArrayLikeIndexer<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let var_name = name.map(|v| format!("{v}.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(self.base_ptr(ctx, generator), &[*idx], var_name.as_str())
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let size = self.size(ctx, generator);
|
||||
let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, size, "").unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
in_range,
|
||||
"0:IndexError",
|
||||
"index {0} is out of bounds for axis 0 with size {1}",
|
||||
[Some(*idx), Some(self.0.load_ndims(ctx)), None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
unsafe { self.ptr_offset_unchecked(ctx, generator, idx, name) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> UntypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {}
|
||||
impl<'ctx> UntypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {}
|
||||
|
||||
impl<'ctx> TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {
|
||||
fn downcast_to_type(
|
||||
&self,
|
||||
_: &mut CodeGenContext<'ctx, '_>,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) -> IntValue<'ctx> {
|
||||
value.into_int_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> TypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayStridesProxy<'ctx, '_> {
|
||||
fn upcast_from_type(
|
||||
&self,
|
||||
_: &mut CodeGenContext<'ctx, '_>,
|
||||
value: IntValue<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
value.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Proxy type for accessing the `data` array of an `NDArray` instance in LLVM.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NDArrayDataProxy<'ctx, 'a>(&'a NDArrayValue<'ctx>);
|
||||
|
||||
impl<'ctx> ArrayLikeValue<'ctx> for NDArrayDataProxy<'ctx, '_> {
|
||||
fn element_type<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
_: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> AnyTypeEnum<'ctx> {
|
||||
self.0.dtype.as_any_type_enum()
|
||||
}
|
||||
|
||||
fn base_ptr<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
_: &G,
|
||||
) -> PointerValue<'ctx> {
|
||||
self.0.data_field(ctx).get(ctx, self.0.as_base_value(), self.0.name)
|
||||
}
|
||||
|
||||
fn size<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
generator: &G,
|
||||
) -> IntValue<'ctx> {
|
||||
irrt::ndarray::call_ndarray_calc_size(
|
||||
generator,
|
||||
ctx,
|
||||
&self.as_slice_value(ctx, generator),
|
||||
(None, None),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ArrayLikeIndexer<'ctx> for NDArrayDataProxy<'ctx, '_> {
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let sizeof_elem = ctx
|
||||
.builder
|
||||
.build_int_truncate_or_bit_cast(
|
||||
self.element_type(ctx, generator).size_of().unwrap(),
|
||||
idx.get_type(),
|
||||
"",
|
||||
)
|
||||
.unwrap();
|
||||
let idx = ctx.builder.build_int_mul(*idx, sizeof_elem, "").unwrap();
|
||||
let ptr = unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.base_ptr(ctx, generator),
|
||||
&[idx],
|
||||
name.unwrap_or_default(),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
// Current implementation is transparent - The returned pointer type is
|
||||
// already cast into the expected type, allowing for immediately
|
||||
// load/store.
|
||||
ctx.builder
|
||||
.build_pointer_cast(
|
||||
ptr,
|
||||
BasicTypeEnum::try_from(self.element_type(ctx, generator))
|
||||
.unwrap()
|
||||
.ptr_type(AddressSpace::default()),
|
||||
"",
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
idx: &IntValue<'ctx>,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let data_sz = self.size(ctx, generator);
|
||||
let in_range = ctx.builder.build_int_compare(IntPredicate::ULT, *idx, data_sz, "").unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
in_range,
|
||||
"0:IndexError",
|
||||
"index {0} is out of bounds with size {1}",
|
||||
[Some(*idx), Some(self.0.load_ndims(ctx)), None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
let ptr = unsafe { self.ptr_offset_unchecked(ctx, generator, idx, name) };
|
||||
|
||||
// Current implementation is transparent - The returned pointer type is
|
||||
// already cast into the expected type, allowing for immediately
|
||||
// load/store.
|
||||
ctx.builder
|
||||
.build_pointer_cast(
|
||||
ptr,
|
||||
BasicTypeEnum::try_from(self.element_type(ctx, generator))
|
||||
.unwrap()
|
||||
.ptr_type(AddressSpace::default()),
|
||||
"",
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> UntypedArrayLikeAccessor<'ctx, IntValue<'ctx>> for NDArrayDataProxy<'ctx, '_> {}
|
||||
impl<'ctx> UntypedArrayLikeMutator<'ctx, IntValue<'ctx>> for NDArrayDataProxy<'ctx, '_> {}
|
||||
|
||||
impl<'ctx, Index: UntypedArrayLikeAccessor<'ctx>> ArrayLikeIndexer<'ctx, Index>
|
||||
for NDArrayDataProxy<'ctx, '_>
|
||||
{
|
||||
unsafe fn ptr_offset_unchecked<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
indices: &Index,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
|
||||
let indices_elem_ty = indices
|
||||
.ptr_offset(ctx, generator, &llvm_usize.const_zero(), None)
|
||||
.get_type()
|
||||
.get_element_type();
|
||||
let Ok(indices_elem_ty) = IntType::try_from(indices_elem_ty) else {
|
||||
panic!("Expected list[int32] but got {indices_elem_ty}")
|
||||
};
|
||||
assert_eq!(
|
||||
indices_elem_ty.get_bit_width(),
|
||||
32,
|
||||
"Expected list[int32] but got list[int{}]",
|
||||
indices_elem_ty.get_bit_width()
|
||||
);
|
||||
|
||||
let index = irrt::ndarray::call_ndarray_flatten_index(generator, ctx, *self.0, indices);
|
||||
let sizeof_elem = ctx
|
||||
.builder
|
||||
.build_int_truncate_or_bit_cast(
|
||||
self.element_type(ctx, generator).size_of().unwrap(),
|
||||
index.get_type(),
|
||||
"",
|
||||
)
|
||||
.unwrap();
|
||||
let index = ctx.builder.build_int_mul(index, sizeof_elem, "").unwrap();
|
||||
|
||||
let ptr = unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.base_ptr(ctx, generator),
|
||||
&[index],
|
||||
name.unwrap_or_default(),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
// TODO: Current implementation is transparent
|
||||
ctx.builder
|
||||
.build_pointer_cast(
|
||||
ptr,
|
||||
BasicTypeEnum::try_from(self.element_type(ctx, generator))
|
||||
.unwrap()
|
||||
.ptr_type(AddressSpace::default()),
|
||||
"",
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn ptr_offset<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut G,
|
||||
indices: &Index,
|
||||
name: Option<&str>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let llvm_usize = generator.get_size_type(ctx.ctx);
|
||||
|
||||
let indices_size = indices.size(ctx, generator);
|
||||
let nidx_leq_ndims = ctx
|
||||
.builder
|
||||
.build_int_compare(IntPredicate::SLE, indices_size, self.0.load_ndims(ctx), "")
|
||||
.unwrap();
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
nidx_leq_ndims,
|
||||
"0:IndexError",
|
||||
"invalid index to scalar variable",
|
||||
[None, None, None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
let indices_len = indices.size(ctx, generator);
|
||||
let ndarray_len = self.0.load_ndims(ctx);
|
||||
let len = call_int_umin(ctx, indices_len, ndarray_len, None);
|
||||
gen_for_callback_incrementing(
|
||||
generator,
|
||||
ctx,
|
||||
None,
|
||||
llvm_usize.const_zero(),
|
||||
(len, false),
|
||||
|generator, ctx, _, i| {
|
||||
let (dim_idx, dim_sz) = unsafe {
|
||||
(
|
||||
indices.get_unchecked(ctx, generator, &i, None).into_int_value(),
|
||||
self.0.shape().get_typed_unchecked(ctx, generator, &i, None),
|
||||
)
|
||||
};
|
||||
let dim_idx = ctx
|
||||
.builder
|
||||
.build_int_z_extend_or_bit_cast(dim_idx, dim_sz.get_type(), "")
|
||||
.unwrap();
|
||||
|
||||
let dim_lt =
|
||||
ctx.builder.build_int_compare(IntPredicate::SLT, dim_idx, dim_sz, "").unwrap();
|
||||
|
||||
ctx.make_assert(
|
||||
generator,
|
||||
dim_lt,
|
||||
"0:IndexError",
|
||||
"index {0} is out of bounds for axis 0 with size {1}",
|
||||
[Some(dim_idx), Some(dim_sz), None],
|
||||
ctx.current_loc,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
},
|
||||
llvm_usize.const_int(1, false),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ptr = unsafe { self.ptr_offset_unchecked(ctx, generator, indices, name) };
|
||||
// TODO: Current implementation is transparent
|
||||
ctx.builder
|
||||
.build_pointer_cast(
|
||||
ptr,
|
||||
BasicTypeEnum::try_from(self.element_type(ctx, generator))
|
||||
.unwrap()
|
||||
.ptr_type(AddressSpace::default()),
|
||||
"",
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx, Index: UntypedArrayLikeAccessor<'ctx>> UntypedArrayLikeAccessor<'ctx, Index>
|
||||
for NDArrayDataProxy<'ctx, '_>
|
||||
{
|
||||
}
|
||||
impl<'ctx, Index: UntypedArrayLikeAccessor<'ctx>> UntypedArrayLikeMutator<'ctx, Index>
|
||||
for NDArrayDataProxy<'ctx, '_>
|
||||
{
|
||||
}
|
||||
|
||||
/// A version of [`call_nac3_ndarray_set_strides_by_shape`] in Rust.
|
||||
///
|
||||
/// This function is used generating strides for globally defined contiguous ndarrays.
|
||||
#[must_use]
|
||||
pub fn make_contiguous_strides(itemsize: u64, ndims: u64, shape: &[u64]) -> Vec<u64> {
|
||||
let mut strides = Vec::with_capacity(ndims as usize);
|
||||
let mut stride_product = 1u64;
|
||||
for i in 0..ndims {
|
||||
let axis = ndims - i - 1;
|
||||
strides[axis as usize] = stride_product * itemsize;
|
||||
stride_product *= shape[axis as usize];
|
||||
}
|
||||
strides
|
||||
}
|
||||
|
||||
/// A convenience enum for implementing functions that acts on scalars or ndarrays or both.
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum ScalarOrNDArray<'ctx> {
|
||||
Scalar(BasicValueEnum<'ctx>),
|
||||
NDArray(NDArrayValue<'ctx>),
|
||||
}
|
||||
|
||||
impl<'ctx> ScalarOrNDArray<'ctx> {
|
||||
/// Get the underlying [`BasicValueEnum<'ctx>`] of this [`ScalarOrNDArray`].
|
||||
#[must_use]
|
||||
pub fn to_basic_value_enum(self) -> BasicValueEnum<'ctx> {
|
||||
match self {
|
||||
ScalarOrNDArray::Scalar(scalar) => scalar,
|
||||
ScalarOrNDArray::NDArray(ndarray) => ndarray.as_base_value().into(),
|
||||
}
|
||||
}
|
||||
}
|
176
nac3core/src/codegen/values/ndarray/nditer.rs
Normal file
176
nac3core/src/codegen/values/ndarray/nditer.rs
Normal file
@ -0,0 +1,176 @@
|
||||
use inkwell::{
|
||||
types::{BasicType, IntType},
|
||||
values::{BasicValueEnum, IntValue, PointerValue},
|
||||
AddressSpace,
|
||||
};
|
||||
|
||||
use super::{NDArrayValue, ProxyValue, TypedArrayLikeAccessor, TypedArrayLikeMutator};
|
||||
use crate::codegen::{
|
||||
irrt,
|
||||
stmt::{gen_for_callback, BreakContinueHooks},
|
||||
types::{ndarray::NDIterType, structure::StructField},
|
||||
values::{ArraySliceValue, TypedArrayLikeAdapter},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NDIterValue<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
parent: NDArrayValue<'ctx>,
|
||||
indices: ArraySliceValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
}
|
||||
|
||||
impl<'ctx> NDIterValue<'ctx> {
|
||||
/// Checks whether `value` is an instance of `NDArray`, returning [Err] if `value` is not an
|
||||
/// instance.
|
||||
pub fn is_representable(
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
<Self as ProxyValue>::Type::is_representable(value.get_type(), llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`NDArrayValue`] from a [`PointerValue`].
|
||||
#[must_use]
|
||||
pub fn from_pointer_value(
|
||||
ptr: PointerValue<'ctx>,
|
||||
parent: NDArrayValue<'ctx>,
|
||||
indices: ArraySliceValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok());
|
||||
|
||||
Self { value: ptr, parent, indices, llvm_usize, name }
|
||||
}
|
||||
|
||||
/// Is the current iteration valid?
|
||||
///
|
||||
/// If true, then `element`, `indices` and `nth` contain details about the current element.
|
||||
///
|
||||
/// If `ndarray` is unsized, this returns true only for the first iteration.
|
||||
/// If `ndarray` is 0-sized, this always returns false.
|
||||
#[must_use]
|
||||
pub fn has_element<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &G,
|
||||
ctx: &CodeGenContext<'ctx, '_>,
|
||||
) -> IntValue<'ctx> {
|
||||
irrt::ndarray::call_nac3_nditer_has_element(generator, ctx, *self)
|
||||
}
|
||||
|
||||
/// Go to the next element. If `has_element()` is false, then this has undefined behavior.
|
||||
///
|
||||
/// If `ndarray` is unsized, this can only be called once.
|
||||
/// If `ndarray` is 0-sized, this can never be called.
|
||||
pub fn next<G: CodeGenerator + ?Sized>(&self, generator: &G, ctx: &CodeGenContext<'ctx, '_>) {
|
||||
irrt::ndarray::call_nac3_nditer_next(generator, ctx, *self);
|
||||
}
|
||||
|
||||
fn element(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, PointerValue<'ctx>> {
|
||||
self.get_type().get_fields(ctx.ctx).element
|
||||
}
|
||||
|
||||
/// Get pointer to the current element.
|
||||
#[must_use]
|
||||
pub fn get_pointer(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
let elem_ty = self.parent.dtype;
|
||||
|
||||
let p = self.element(ctx).get(ctx, self.as_base_value(), None);
|
||||
ctx.builder
|
||||
.build_pointer_cast(p, elem_ty.ptr_type(AddressSpace::default()), "element")
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Get the value of the current element.
|
||||
#[must_use]
|
||||
pub fn get_scalar(&self, ctx: &CodeGenContext<'ctx, '_>) -> BasicValueEnum<'ctx> {
|
||||
let p = self.get_pointer(ctx);
|
||||
ctx.builder.build_load(p, "value").unwrap()
|
||||
}
|
||||
|
||||
fn nth(&self, ctx: &CodeGenContext<'ctx, '_>) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields(ctx.ctx).nth
|
||||
}
|
||||
|
||||
/// Get the index of the current element if this ndarray were a flat ndarray.
|
||||
#[must_use]
|
||||
pub fn get_index(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.nth(ctx).get(ctx, self.as_base_value(), None)
|
||||
}
|
||||
|
||||
/// Get the indices of the current element.
|
||||
#[must_use]
|
||||
pub fn get_indices(
|
||||
&'ctx self,
|
||||
) -> impl TypedArrayLikeAccessor<'ctx, IntValue<'ctx>> + TypedArrayLikeMutator<'ctx, IntValue<'ctx>>
|
||||
{
|
||||
TypedArrayLikeAdapter::from(
|
||||
self.indices,
|
||||
Box::new(|ctx, val| {
|
||||
ctx.builder
|
||||
.build_int_z_extend_or_bit_cast(val.into_int_value(), self.llvm_usize, "")
|
||||
.unwrap()
|
||||
}),
|
||||
Box::new(|_, val| val.into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyValue<'ctx> for NDIterValue<'ctx> {
|
||||
type Base = PointerValue<'ctx>;
|
||||
type Type = NDIterType<'ctx>;
|
||||
|
||||
fn get_type(&self) -> Self::Type {
|
||||
NDIterType::from_type(self.as_base_value().get_type(), self.llvm_usize)
|
||||
}
|
||||
|
||||
fn as_base_value(&self) -> Self::Base {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<NDIterValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: NDIterValue<'ctx>) -> Self {
|
||||
value.as_base_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> NDArrayValue<'ctx> {
|
||||
/// Iterate through every element in the ndarray.
|
||||
///
|
||||
/// `body` has access to [`BreakContinueHooks`] to short-circuit and [`NDIterValue`] to
|
||||
/// get properties of the current iteration (e.g., the current element, indices, etc.)
|
||||
pub fn foreach<'a, G, F>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
body: F,
|
||||
) -> Result<(), String>
|
||||
where
|
||||
G: CodeGenerator + ?Sized,
|
||||
F: FnOnce(
|
||||
&mut G,
|
||||
&mut CodeGenContext<'ctx, 'a>,
|
||||
BreakContinueHooks<'ctx>,
|
||||
NDIterValue<'ctx>,
|
||||
) -> Result<(), String>,
|
||||
{
|
||||
gen_for_callback(
|
||||
generator,
|
||||
ctx,
|
||||
Some("ndarray_foreach"),
|
||||
|generator, ctx| {
|
||||
Ok(NDIterType::new(generator, ctx.ctx).construct(generator, ctx, *self))
|
||||
},
|
||||
|generator, ctx, nditer| Ok(nditer.has_element(generator, ctx)),
|
||||
|generator, ctx, hooks, nditer| body(generator, ctx, hooks, nditer),
|
||||
|generator, ctx, nditer| {
|
||||
nditer.next(generator, ctx);
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
36
nac3core/src/codegen/values/ndarray/view.rs
Normal file
36
nac3core/src/codegen/values/ndarray/view.rs
Normal file
@ -0,0 +1,36 @@
|
||||
use std::iter::{once, repeat_n};
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::codegen::{
|
||||
values::ndarray::{NDArrayValue, RustNDIndex},
|
||||
CodeGenContext, CodeGenerator,
|
||||
};
|
||||
|
||||
impl<'ctx> NDArrayValue<'ctx> {
|
||||
/// Make sure the ndarray is at least `ndmin`-dimensional.
|
||||
///
|
||||
/// If this ndarray's `ndims` is less than `ndmin`, a view is created on this with 1s prepended
|
||||
/// to the shape. Otherwise, this function does nothing and return this ndarray.
|
||||
#[must_use]
|
||||
pub fn atleast_nd<G: CodeGenerator + ?Sized>(
|
||||
&self,
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
ndmin: u64,
|
||||
) -> Self {
|
||||
assert!(self.ndims.is_some(), "NDArrayValue::atleast_nd is only supported for instances with compile-time known ndims (self.ndims = Some(...))");
|
||||
|
||||
let ndims = self.ndims.unwrap();
|
||||
|
||||
if ndims < ndmin {
|
||||
// Extend the dimensions with np.newaxis.
|
||||
let indices = repeat_n(RustNDIndex::NewAxis, (ndmin - ndims) as usize)
|
||||
.chain(once(RustNDIndex::Ellipsis))
|
||||
.collect_vec();
|
||||
self.index(generator, ctx, &indices)
|
||||
} else {
|
||||
*self
|
||||
}
|
||||
}
|
||||
}
|
153
nac3core/src/codegen/values/range.rs
Normal file
153
nac3core/src/codegen/values/range.rs
Normal file
@ -0,0 +1,153 @@
|
||||
use inkwell::values::{BasicValueEnum, IntValue, PointerValue};
|
||||
|
||||
use super::ProxyValue;
|
||||
use crate::codegen::{types::RangeType, CodeGenContext};
|
||||
|
||||
/// Proxy type for accessing a `range` value in LLVM.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct RangeValue<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
}
|
||||
|
||||
impl<'ctx> RangeValue<'ctx> {
|
||||
/// Checks whether `value` is an instance of `range`, returning [Err] if `value` is not an instance.
|
||||
pub fn is_representable(value: PointerValue<'ctx>) -> Result<(), String> {
|
||||
RangeType::is_representable(value.get_type())
|
||||
}
|
||||
|
||||
/// Creates an [`RangeValue`] from a [`PointerValue`].
|
||||
#[must_use]
|
||||
pub fn from_pointer_value(ptr: PointerValue<'ctx>, name: Option<&'ctx str>) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr).is_ok());
|
||||
|
||||
RangeValue { value: ptr, name }
|
||||
}
|
||||
|
||||
fn ptr_to_start(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let var_name = self.name.map(|v| format!("{v}.start.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.as_base_value(),
|
||||
&[llvm_i32.const_zero(), llvm_i32.const_int(0, false)],
|
||||
var_name.as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_to_end(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let var_name = self.name.map(|v| format!("{v}.end.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.as_base_value(),
|
||||
&[llvm_i32.const_zero(), llvm_i32.const_int(1, false)],
|
||||
var_name.as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
fn ptr_to_step(&self, ctx: &CodeGenContext<'ctx, '_>) -> PointerValue<'ctx> {
|
||||
let llvm_i32 = ctx.ctx.i32_type();
|
||||
let var_name = self.name.map(|v| format!("{v}.step.addr")).unwrap_or_default();
|
||||
|
||||
unsafe {
|
||||
ctx.builder
|
||||
.build_in_bounds_gep(
|
||||
self.as_base_value(),
|
||||
&[llvm_i32.const_zero(), llvm_i32.const_int(2, false)],
|
||||
var_name.as_str(),
|
||||
)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the `start` value into this instance.
|
||||
pub fn store_start(&self, ctx: &CodeGenContext<'ctx, '_>, start: IntValue<'ctx>) {
|
||||
debug_assert_eq!(start.get_type().get_bit_width(), 32);
|
||||
|
||||
let pstart = self.ptr_to_start(ctx);
|
||||
ctx.builder.build_store(pstart, start).unwrap();
|
||||
}
|
||||
|
||||
/// Returns the `start` value of this `range`.
|
||||
pub fn load_start(&self, ctx: &CodeGenContext<'ctx, '_>, name: Option<&str>) -> IntValue<'ctx> {
|
||||
let pstart = self.ptr_to_start(ctx);
|
||||
let var_name = name
|
||||
.map(ToString::to_string)
|
||||
.or_else(|| self.name.map(|v| format!("{v}.start")))
|
||||
.unwrap_or_default();
|
||||
|
||||
ctx.builder
|
||||
.build_load(pstart, var_name.as_str())
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// Stores the `end` value into this instance.
|
||||
pub fn store_end(&self, ctx: &CodeGenContext<'ctx, '_>, end: IntValue<'ctx>) {
|
||||
debug_assert_eq!(end.get_type().get_bit_width(), 32);
|
||||
|
||||
let pend = self.ptr_to_end(ctx);
|
||||
ctx.builder.build_store(pend, end).unwrap();
|
||||
}
|
||||
|
||||
/// Returns the `end` value of this `range`.
|
||||
pub fn load_end(&self, ctx: &CodeGenContext<'ctx, '_>, name: Option<&str>) -> IntValue<'ctx> {
|
||||
let pend = self.ptr_to_end(ctx);
|
||||
let var_name = name
|
||||
.map(ToString::to_string)
|
||||
.or_else(|| self.name.map(|v| format!("{v}.end")))
|
||||
.unwrap_or_default();
|
||||
|
||||
ctx.builder.build_load(pend, var_name.as_str()).map(BasicValueEnum::into_int_value).unwrap()
|
||||
}
|
||||
|
||||
/// Stores the `step` value into this instance.
|
||||
pub fn store_step(&self, ctx: &CodeGenContext<'ctx, '_>, step: IntValue<'ctx>) {
|
||||
debug_assert_eq!(step.get_type().get_bit_width(), 32);
|
||||
|
||||
let pstep = self.ptr_to_step(ctx);
|
||||
ctx.builder.build_store(pstep, step).unwrap();
|
||||
}
|
||||
|
||||
/// Returns the `step` value of this `range`.
|
||||
pub fn load_step(&self, ctx: &CodeGenContext<'ctx, '_>, name: Option<&str>) -> IntValue<'ctx> {
|
||||
let pstep = self.ptr_to_step(ctx);
|
||||
let var_name = name
|
||||
.map(ToString::to_string)
|
||||
.or_else(|| self.name.map(|v| format!("{v}.step")))
|
||||
.unwrap_or_default();
|
||||
|
||||
ctx.builder
|
||||
.build_load(pstep, var_name.as_str())
|
||||
.map(BasicValueEnum::into_int_value)
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyValue<'ctx> for RangeValue<'ctx> {
|
||||
type Base = PointerValue<'ctx>;
|
||||
type Type = RangeType<'ctx>;
|
||||
|
||||
fn get_type(&self) -> Self::Type {
|
||||
RangeType::from_type(self.value.get_type())
|
||||
}
|
||||
|
||||
fn as_base_value(&self) -> Self::Base {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<RangeValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: RangeValue<'ctx>) -> Self {
|
||||
value.as_base_value()
|
||||
}
|
||||
}
|
3
nac3core/src/codegen/values/utils/mod.rs
Normal file
3
nac3core/src/codegen/values/utils/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub use slice::*;
|
||||
|
||||
mod slice;
|
231
nac3core/src/codegen/values/utils/slice.rs
Normal file
231
nac3core/src/codegen/values/utils/slice.rs
Normal file
@ -0,0 +1,231 @@
|
||||
use inkwell::{
|
||||
types::IntType,
|
||||
values::{IntValue, PointerValue},
|
||||
};
|
||||
|
||||
use nac3parser::ast::Expr;
|
||||
|
||||
use crate::{
|
||||
codegen::{
|
||||
types::{structure::StructField, utils::SliceType},
|
||||
values::ProxyValue,
|
||||
CodeGenContext, CodeGenerator,
|
||||
},
|
||||
typecheck::typedef::Type,
|
||||
};
|
||||
|
||||
/// An IRRT representation of an (unresolved) slice.
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct SliceValue<'ctx> {
|
||||
value: PointerValue<'ctx>,
|
||||
int_ty: IntType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
}
|
||||
|
||||
impl<'ctx> SliceValue<'ctx> {
|
||||
/// Checks whether `value` is an instance of `ContiguousNDArray`, returning [Err] if `value` is
|
||||
/// not an instance.
|
||||
pub fn is_representable(
|
||||
value: PointerValue<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
) -> Result<(), String> {
|
||||
<Self as ProxyValue<'ctx>>::Type::is_representable(value.get_type(), llvm_usize)
|
||||
}
|
||||
|
||||
/// Creates an [`SliceValue`] from a [`PointerValue`].
|
||||
#[must_use]
|
||||
pub fn from_pointer_value(
|
||||
ptr: PointerValue<'ctx>,
|
||||
int_ty: IntType<'ctx>,
|
||||
llvm_usize: IntType<'ctx>,
|
||||
name: Option<&'ctx str>,
|
||||
) -> Self {
|
||||
debug_assert!(Self::is_representable(ptr, llvm_usize).is_ok());
|
||||
|
||||
Self { value: ptr, int_ty, llvm_usize, name }
|
||||
}
|
||||
|
||||
fn start_defined_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().start_defined
|
||||
}
|
||||
|
||||
pub fn load_start_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.start_defined_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
fn start_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().start
|
||||
}
|
||||
|
||||
pub fn load_start(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.start_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
pub fn store_start(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) {
|
||||
match value {
|
||||
Some(start) => {
|
||||
self.start_defined_field().set(
|
||||
ctx,
|
||||
self.value,
|
||||
ctx.ctx.bool_type().const_all_ones(),
|
||||
self.name,
|
||||
);
|
||||
self.start_field().set(ctx, self.value, start, self.name);
|
||||
}
|
||||
|
||||
None => self.start_defined_field().set(
|
||||
ctx,
|
||||
self.value,
|
||||
ctx.ctx.bool_type().const_zero(),
|
||||
self.name,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn stop_defined_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().stop_defined
|
||||
}
|
||||
|
||||
pub fn load_stop_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.stop_defined_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
fn stop_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().stop
|
||||
}
|
||||
|
||||
pub fn load_stop(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.stop_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
pub fn store_stop(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) {
|
||||
match value {
|
||||
Some(stop) => {
|
||||
self.stop_defined_field().set(
|
||||
ctx,
|
||||
self.value,
|
||||
ctx.ctx.bool_type().const_all_ones(),
|
||||
self.name,
|
||||
);
|
||||
self.stop_field().set(ctx, self.value, stop, self.name);
|
||||
}
|
||||
|
||||
None => self.stop_defined_field().set(
|
||||
ctx,
|
||||
self.value,
|
||||
ctx.ctx.bool_type().const_zero(),
|
||||
self.name,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn step_defined_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().step_defined
|
||||
}
|
||||
|
||||
pub fn load_step_defined(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.step_defined_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
fn step_field(&self) -> StructField<'ctx, IntValue<'ctx>> {
|
||||
self.get_type().get_fields().step
|
||||
}
|
||||
|
||||
pub fn load_step(&self, ctx: &CodeGenContext<'ctx, '_>) -> IntValue<'ctx> {
|
||||
self.step_field().get(ctx, self.value, self.name)
|
||||
}
|
||||
|
||||
pub fn store_step(&self, ctx: &CodeGenContext<'ctx, '_>, value: Option<IntValue<'ctx>>) {
|
||||
match value {
|
||||
Some(step) => {
|
||||
self.step_defined_field().set(
|
||||
ctx,
|
||||
self.value,
|
||||
ctx.ctx.bool_type().const_all_ones(),
|
||||
self.name,
|
||||
);
|
||||
self.step_field().set(ctx, self.value, step, self.name);
|
||||
}
|
||||
|
||||
None => self.step_defined_field().set(
|
||||
ctx,
|
||||
self.value,
|
||||
ctx.ctx.bool_type().const_zero(),
|
||||
self.name,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> ProxyValue<'ctx> for SliceValue<'ctx> {
|
||||
type Base = PointerValue<'ctx>;
|
||||
type Type = SliceType<'ctx>;
|
||||
|
||||
fn get_type(&self) -> Self::Type {
|
||||
Self::Type::from_type(self.value.get_type(), self.int_ty, self.llvm_usize)
|
||||
}
|
||||
|
||||
fn as_base_value(&self) -> Self::Base {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ctx> From<SliceValue<'ctx>> for PointerValue<'ctx> {
|
||||
fn from(value: SliceValue<'ctx>) -> Self {
|
||||
value.as_base_value()
|
||||
}
|
||||
}
|
||||
|
||||
/// A slice represented in compile-time by `start`, `stop` and `step`, all held as LLVM values.
|
||||
// TODO: Rename this to CTConstSlice
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct RustSlice<'ctx> {
|
||||
int_ty: IntType<'ctx>,
|
||||
start: Option<IntValue<'ctx>>,
|
||||
stop: Option<IntValue<'ctx>>,
|
||||
step: Option<IntValue<'ctx>>,
|
||||
}
|
||||
|
||||
impl<'ctx> RustSlice<'ctx> {
|
||||
/// Generate LLVM IR for an [`ExprKind::Slice`] and convert it into a [`RustSlice`].
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn from_slice_expr<G: CodeGenerator>(
|
||||
generator: &mut G,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
lower: &Option<Box<Expr<Option<Type>>>>,
|
||||
upper: &Option<Box<Expr<Option<Type>>>>,
|
||||
step: &Option<Box<Expr<Option<Type>>>>,
|
||||
) -> Result<RustSlice<'ctx>, String> {
|
||||
let mut value_mapper = |value_expr: &Option<Box<Expr<Option<Type>>>>| -> Result<_, String> {
|
||||
Ok(match value_expr {
|
||||
None => None,
|
||||
Some(value_expr) => {
|
||||
let value_expr = generator
|
||||
.gen_expr(ctx, value_expr)?
|
||||
.map(|value| {
|
||||
value.to_basic_value_enum(ctx, generator, ctx.primitives.int32)
|
||||
})
|
||||
.unwrap()?;
|
||||
|
||||
Some(value_expr.into_int_value())
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let start = value_mapper(lower)?;
|
||||
let stop = value_mapper(upper)?;
|
||||
let step = value_mapper(step)?;
|
||||
|
||||
Ok(RustSlice { int_ty: ctx.ctx.i32_type(), start, stop, step })
|
||||
}
|
||||
|
||||
/// Write the contents to an LLVM [`SliceValue`].
|
||||
pub fn write_to_slice(&self, ctx: &CodeGenContext<'ctx, '_>, dst_slice_ptr: SliceValue<'ctx>) {
|
||||
assert_eq!(self.int_ty, dst_slice_ptr.int_ty);
|
||||
|
||||
dst_slice_ptr.store_start(ctx, self.start);
|
||||
dst_slice_ptr.store_stop(ctx, self.stop);
|
||||
dst_slice_ptr.store_step(ctx, self.step);
|
||||
}
|
||||
}
|
@ -1,7 +1,25 @@
|
||||
#![warn(clippy::all)]
|
||||
#![allow(dead_code)]
|
||||
#![deny(future_incompatible, let_underscore, nonstandard_style, clippy::all)]
|
||||
#![warn(clippy::pedantic)]
|
||||
#![allow(
|
||||
dead_code,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::cast_sign_loss,
|
||||
clippy::enum_glob_use,
|
||||
clippy::missing_errors_doc,
|
||||
clippy::missing_panics_doc,
|
||||
clippy::module_name_repetitions,
|
||||
clippy::similar_names,
|
||||
clippy::too_many_lines,
|
||||
clippy::wildcard_imports
|
||||
)]
|
||||
|
||||
// users of nac3core need to use the same version of these dependencies, so expose them as nac3core::*
|
||||
pub use inkwell;
|
||||
pub use nac3parser;
|
||||
|
||||
pub mod codegen;
|
||||
pub mod symbol_resolver;
|
||||
pub mod toplevel;
|
||||
pub mod typecheck;
|
||||
|
||||
extern crate self as nac3core;
|
||||
|
@ -1,23 +1,24 @@
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
use std::{collections::HashMap, fmt::Display};
|
||||
|
||||
use crate::typecheck::typedef::TypeEnum;
|
||||
use crate::{
|
||||
codegen::CodeGenContext,
|
||||
toplevel::{DefinitionId, TopLevelDef},
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::{Debug, Display},
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue, StructValue};
|
||||
use itertools::{chain, izip, Itertools};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use nac3parser::ast::{Constant, Expr, Location, StrRef};
|
||||
|
||||
use crate::{
|
||||
codegen::CodeGenerator,
|
||||
codegen::{CodeGenContext, CodeGenerator},
|
||||
toplevel::{type_annotation::TypeAnnotation, DefinitionId, TopLevelDef},
|
||||
typecheck::{
|
||||
type_inferencer::PrimitiveStore,
|
||||
typedef::{Type, Unifier},
|
||||
typedef::{Type, TypeEnum, Unifier, VarMap},
|
||||
},
|
||||
};
|
||||
use inkwell::values::{BasicValueEnum, FloatValue, IntValue, PointerValue, StructValue};
|
||||
use itertools::{chain, izip};
|
||||
use nac3parser::ast::{Expr, Location, StrRef};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub enum SymbolValue {
|
||||
@ -33,15 +34,192 @@ pub enum SymbolValue {
|
||||
OptionNone,
|
||||
}
|
||||
|
||||
impl SymbolValue {
|
||||
/// Creates a [`SymbolValue`] from a [`Constant`].
|
||||
///
|
||||
/// * `constant` - The constant to create the value from.
|
||||
/// * `expected_ty` - The expected type of the [`SymbolValue`].
|
||||
pub fn from_constant(
|
||||
constant: &Constant,
|
||||
expected_ty: Type,
|
||||
primitives: &PrimitiveStore,
|
||||
unifier: &mut Unifier,
|
||||
) -> Result<Self, String> {
|
||||
match constant {
|
||||
Constant::None => {
|
||||
if unifier.unioned(expected_ty, primitives.option) {
|
||||
Ok(SymbolValue::OptionNone)
|
||||
} else {
|
||||
Err(format!("Expected {expected_ty:?}, but got Option"))
|
||||
}
|
||||
}
|
||||
Constant::Bool(b) => {
|
||||
if unifier.unioned(expected_ty, primitives.bool) {
|
||||
Ok(SymbolValue::Bool(*b))
|
||||
} else {
|
||||
Err(format!("Expected {expected_ty:?}, but got bool"))
|
||||
}
|
||||
}
|
||||
Constant::Str(s) => {
|
||||
if unifier.unioned(expected_ty, primitives.str) {
|
||||
Ok(SymbolValue::Str(s.to_string()))
|
||||
} else {
|
||||
Err(format!("Expected {expected_ty:?}, but got str"))
|
||||
}
|
||||
}
|
||||
Constant::Int(i) => {
|
||||
if unifier.unioned(expected_ty, primitives.int32) {
|
||||
i32::try_from(*i).map(SymbolValue::I32).map_err(|e| e.to_string())
|
||||
} else if unifier.unioned(expected_ty, primitives.int64) {
|
||||
i64::try_from(*i).map(SymbolValue::I64).map_err(|e| e.to_string())
|
||||
} else if unifier.unioned(expected_ty, primitives.uint32) {
|
||||
u32::try_from(*i).map(SymbolValue::U32).map_err(|e| e.to_string())
|
||||
} else if unifier.unioned(expected_ty, primitives.uint64) {
|
||||
u64::try_from(*i).map(SymbolValue::U64).map_err(|e| e.to_string())
|
||||
} else {
|
||||
Err(format!("Expected {}, but got int", unifier.stringify(expected_ty)))
|
||||
}
|
||||
}
|
||||
Constant::Tuple(t) => {
|
||||
let expected_ty = unifier.get_ty(expected_ty);
|
||||
let TypeEnum::TTuple { ty, is_vararg_ctx } = expected_ty.as_ref() else {
|
||||
return Err(format!(
|
||||
"Expected {:?}, but got Tuple",
|
||||
expected_ty.get_type_name()
|
||||
));
|
||||
};
|
||||
|
||||
assert!(*is_vararg_ctx || ty.len() == t.len());
|
||||
|
||||
let elems = t
|
||||
.iter()
|
||||
.zip(ty)
|
||||
.map(|(constant, ty)| Self::from_constant(constant, *ty, primitives, unifier))
|
||||
.collect::<Result<Vec<SymbolValue>, _>>()?;
|
||||
Ok(SymbolValue::Tuple(elems))
|
||||
}
|
||||
Constant::Float(f) => {
|
||||
if unifier.unioned(expected_ty, primitives.float) {
|
||||
Ok(SymbolValue::Double(*f))
|
||||
} else {
|
||||
Err(format!("Expected {expected_ty:?}, but got float"))
|
||||
}
|
||||
}
|
||||
_ => Err(format!("Unsupported value type {constant:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`SymbolValue`] from a [`Constant`], with its type being inferred from the constant value.
|
||||
///
|
||||
/// * `constant` - The constant to create the value from.
|
||||
pub fn from_constant_inferred(constant: &Constant) -> Result<Self, String> {
|
||||
match constant {
|
||||
Constant::None => Ok(SymbolValue::OptionNone),
|
||||
Constant::Bool(b) => Ok(SymbolValue::Bool(*b)),
|
||||
Constant::Str(s) => Ok(SymbolValue::Str(s.to_string())),
|
||||
Constant::Int(i) => {
|
||||
let i = *i;
|
||||
if i >= 0 {
|
||||
i32::try_from(i)
|
||||
.map(SymbolValue::I32)
|
||||
.or_else(|_| i64::try_from(i).map(SymbolValue::I64))
|
||||
.map_err(|_| {
|
||||
format!("Literal cannot be expressed as any integral type: {i}")
|
||||
})
|
||||
} else {
|
||||
u32::try_from(i)
|
||||
.map(SymbolValue::U32)
|
||||
.or_else(|_| u64::try_from(i).map(SymbolValue::U64))
|
||||
.map_err(|_| {
|
||||
format!("Literal cannot be expressed as any integral type: {i}")
|
||||
})
|
||||
}
|
||||
}
|
||||
Constant::Tuple(t) => {
|
||||
let elems = t
|
||||
.iter()
|
||||
.map(Self::from_constant_inferred)
|
||||
.collect::<Result<Vec<SymbolValue>, _>>()?;
|
||||
Ok(SymbolValue::Tuple(elems))
|
||||
}
|
||||
Constant::Float(f) => Ok(SymbolValue::Double(*f)),
|
||||
_ => Err(format!("Unsupported value type {constant:?}")),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`Type`] representing the data type of this value.
|
||||
pub fn get_type(&self, primitives: &PrimitiveStore, unifier: &mut Unifier) -> Type {
|
||||
match self {
|
||||
SymbolValue::I32(_) => primitives.int32,
|
||||
SymbolValue::I64(_) => primitives.int64,
|
||||
SymbolValue::U32(_) => primitives.uint32,
|
||||
SymbolValue::U64(_) => primitives.uint64,
|
||||
SymbolValue::Str(_) => primitives.str,
|
||||
SymbolValue::Double(_) => primitives.float,
|
||||
SymbolValue::Bool(_) => primitives.bool,
|
||||
SymbolValue::Tuple(vs) => {
|
||||
let vs_tys = vs.iter().map(|v| v.get_type(primitives, unifier)).collect::<Vec<_>>();
|
||||
unifier.add_ty(TypeEnum::TTuple { ty: vs_tys, is_vararg_ctx: false })
|
||||
}
|
||||
SymbolValue::OptionSome(_) | SymbolValue::OptionNone => primitives.option,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`TypeAnnotation`] representing the data type of this value.
|
||||
pub fn get_type_annotation(
|
||||
&self,
|
||||
primitives: &PrimitiveStore,
|
||||
unifier: &mut Unifier,
|
||||
) -> TypeAnnotation {
|
||||
match self {
|
||||
SymbolValue::Bool(..)
|
||||
| SymbolValue::Double(..)
|
||||
| SymbolValue::I32(..)
|
||||
| SymbolValue::I64(..)
|
||||
| SymbolValue::U32(..)
|
||||
| SymbolValue::U64(..)
|
||||
| SymbolValue::Str(..) => TypeAnnotation::Primitive(self.get_type(primitives, unifier)),
|
||||
SymbolValue::Tuple(vs) => {
|
||||
let vs_tys = vs
|
||||
.iter()
|
||||
.map(|v| v.get_type_annotation(primitives, unifier))
|
||||
.collect::<Vec<_>>();
|
||||
TypeAnnotation::Tuple(vs_tys)
|
||||
}
|
||||
SymbolValue::OptionNone => TypeAnnotation::CustomClass {
|
||||
id: primitives.option.obj_id(unifier).unwrap(),
|
||||
params: Vec::default(),
|
||||
},
|
||||
SymbolValue::OptionSome(v) => {
|
||||
let ty = v.get_type_annotation(primitives, unifier);
|
||||
TypeAnnotation::CustomClass {
|
||||
id: primitives.option.obj_id(unifier).unwrap(),
|
||||
params: vec![ty],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the [`TypeEnum`] representing the data type of this value.
|
||||
pub fn get_type_enum(
|
||||
&self,
|
||||
primitives: &PrimitiveStore,
|
||||
unifier: &mut Unifier,
|
||||
) -> Rc<TypeEnum> {
|
||||
let ty = self.get_type(primitives, unifier);
|
||||
unifier.get_ty(ty)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for SymbolValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
SymbolValue::I32(i) => write!(f, "{}", i),
|
||||
SymbolValue::I64(i) => write!(f, "int64({})", i),
|
||||
SymbolValue::U32(i) => write!(f, "uint32({})", i),
|
||||
SymbolValue::U64(i) => write!(f, "uint64({})", i),
|
||||
SymbolValue::Str(s) => write!(f, "\"{}\"", s),
|
||||
SymbolValue::Double(d) => write!(f, "{}", d),
|
||||
SymbolValue::I32(i) => write!(f, "{i}"),
|
||||
SymbolValue::I64(i) => write!(f, "int64({i})"),
|
||||
SymbolValue::U32(i) => write!(f, "uint32({i})"),
|
||||
SymbolValue::U64(i) => write!(f, "uint64({i})"),
|
||||
SymbolValue::Str(s) => write!(f, "\"{s}\""),
|
||||
SymbolValue::Double(d) => write!(f, "{d}"),
|
||||
SymbolValue::Bool(b) => {
|
||||
if *b {
|
||||
write!(f, "True")
|
||||
@ -50,39 +228,82 @@ impl Display for SymbolValue {
|
||||
}
|
||||
}
|
||||
SymbolValue::Tuple(t) => {
|
||||
write!(f, "({})", t.iter().map(|v| format!("{}", v)).collect::<Vec<_>>().join(", "))
|
||||
write!(f, "({})", t.iter().map(|v| format!("{v}")).collect::<Vec<_>>().join(", "))
|
||||
}
|
||||
SymbolValue::OptionSome(v) => write!(f, "Some({})", v),
|
||||
SymbolValue::OptionSome(v) => write!(f, "Some({v})"),
|
||||
SymbolValue::OptionNone => write!(f, "none"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SymbolValue> for u64 {
|
||||
type Error = ();
|
||||
|
||||
/// Tries to convert a [`SymbolValue`] into a [`u64`], returning [`Err`] if the value is not
|
||||
/// numeric or if the value cannot be converted into a `u64` without overflow.
|
||||
fn try_from(value: SymbolValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
SymbolValue::I32(v) => u64::try_from(v).map_err(|_| ()),
|
||||
SymbolValue::I64(v) => u64::try_from(v).map_err(|_| ()),
|
||||
SymbolValue::U32(v) => Ok(u64::from(v)),
|
||||
SymbolValue::U64(v) => Ok(v),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<SymbolValue> for i128 {
|
||||
type Error = ();
|
||||
|
||||
/// Tries to convert a [`SymbolValue`] into a [`i128`], returning [`Err`] if the value is not
|
||||
/// numeric.
|
||||
fn try_from(value: SymbolValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
SymbolValue::I32(v) => Ok(i128::from(v)),
|
||||
SymbolValue::I64(v) => Ok(i128::from(v)),
|
||||
SymbolValue::U32(v) => Ok(i128::from(v)),
|
||||
SymbolValue::U64(v) => Ok(i128::from(v)),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait StaticValue {
|
||||
/// Returns a unique identifier for this value.
|
||||
fn get_unique_identifier(&self) -> u64;
|
||||
|
||||
fn get_const_obj<'ctx, 'a>(
|
||||
/// Returns the constant object represented by this unique identifier.
|
||||
fn get_const_obj<'ctx>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> BasicValueEnum<'ctx>;
|
||||
|
||||
fn to_basic_value_enum<'ctx, 'a>(
|
||||
/// Converts this value to a LLVM [`BasicValueEnum`].
|
||||
fn to_basic_value_enum<'ctx>(
|
||||
&self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
expected_ty: Type,
|
||||
) -> Result<BasicValueEnum<'ctx>, String>;
|
||||
|
||||
fn get_field<'ctx, 'a>(
|
||||
/// Returns a field within this value.
|
||||
fn get_field<'ctx>(
|
||||
&self,
|
||||
name: StrRef,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
) -> Option<ValueEnum<'ctx>>;
|
||||
|
||||
/// Returns a single element of this tuple.
|
||||
fn get_tuple_element<'ctx>(&self, index: u32) -> Option<ValueEnum<'ctx>>;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum ValueEnum<'ctx> {
|
||||
/// [`ValueEnum`] representing a static value.
|
||||
Static(Arc<dyn StaticValue + Send + Sync>),
|
||||
|
||||
/// [`ValueEnum`] representing a dynamic value.
|
||||
Dynamic(BasicValueEnum<'ctx>),
|
||||
}
|
||||
|
||||
@ -117,20 +338,22 @@ impl<'ctx> From<StructValue<'ctx>> for ValueEnum<'ctx> {
|
||||
}
|
||||
|
||||
impl<'ctx> ValueEnum<'ctx> {
|
||||
/// Converts this [`ValueEnum`] to a [`BasicValueEnum`].
|
||||
pub fn to_basic_value_enum<'a>(
|
||||
self,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
expected_ty: Type,
|
||||
) -> Result<BasicValueEnum<'ctx>, String> {
|
||||
match self {
|
||||
ValueEnum::Static(v) => v.to_basic_value_enum(ctx, generator),
|
||||
ValueEnum::Static(v) => v.to_basic_value_enum(ctx, generator, expected_ty),
|
||||
ValueEnum::Dynamic(v) => Ok(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SymbolResolver {
|
||||
// get type of type variable identifier or top-level function type
|
||||
/// Get type of type variable identifier or top-level function type,
|
||||
fn get_symbol_type(
|
||||
&self,
|
||||
unifier: &mut Unifier,
|
||||
@ -139,16 +362,17 @@ pub trait SymbolResolver {
|
||||
str: StrRef,
|
||||
) -> Result<Type, String>;
|
||||
|
||||
// get the top-level definition of identifiers
|
||||
fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, String>;
|
||||
/// Get the top-level definition of identifiers.
|
||||
fn get_identifier_def(&self, str: StrRef) -> Result<DefinitionId, HashSet<String>>;
|
||||
|
||||
fn get_symbol_value<'ctx, 'a>(
|
||||
fn get_symbol_value<'ctx>(
|
||||
&self,
|
||||
str: StrRef,
|
||||
ctx: &mut CodeGenContext<'ctx, 'a>,
|
||||
ctx: &mut CodeGenContext<'ctx, '_>,
|
||||
generator: &mut dyn CodeGenerator,
|
||||
) -> Option<ValueEnum<'ctx>>;
|
||||
|
||||
fn get_default_param_value(&self, expr: &nac3parser::ast::Expr) -> Option<SymbolValue>;
|
||||
fn get_default_param_value(&self, expr: &Expr) -> Option<SymbolValue>;
|
||||
fn get_string_id(&self, s: &str) -> i32;
|
||||
fn get_exception_id(&self, tyid: usize) -> usize;
|
||||
|
||||
@ -156,7 +380,7 @@ pub trait SymbolResolver {
|
||||
&self,
|
||||
_unifier: &mut Unifier,
|
||||
_top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
_primitives: &PrimitiveStore
|
||||
_primitives: &PrimitiveStore,
|
||||
) -> Result<(), String> {
|
||||
Ok(())
|
||||
}
|
||||
@ -169,23 +393,23 @@ thread_local! {
|
||||
"float".into(),
|
||||
"bool".into(),
|
||||
"virtual".into(),
|
||||
"list".into(),
|
||||
"tuple".into(),
|
||||
"str".into(),
|
||||
"Exception".into(),
|
||||
"uint32".into(),
|
||||
"uint64".into(),
|
||||
"Literal".into(),
|
||||
];
|
||||
}
|
||||
|
||||
// convert type annotation into type
|
||||
/// Converts a type annotation into a [Type].
|
||||
pub fn parse_type_annotation<T>(
|
||||
resolver: &dyn SymbolResolver,
|
||||
top_level_defs: &[Arc<RwLock<TopLevelDef>>],
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
expr: &Expr<T>,
|
||||
) -> Result<Type, String> {
|
||||
) -> Result<Type, HashSet<String>> {
|
||||
use nac3parser::ast::ExprKind::*;
|
||||
let ids = IDENTIFIER_ID.with(|ids| *ids);
|
||||
let int32_id = ids[0];
|
||||
@ -193,12 +417,12 @@ pub fn parse_type_annotation<T>(
|
||||
let float_id = ids[2];
|
||||
let bool_id = ids[3];
|
||||
let virtual_id = ids[4];
|
||||
let list_id = ids[5];
|
||||
let tuple_id = ids[6];
|
||||
let str_id = ids[7];
|
||||
let exn_id = ids[8];
|
||||
let uint32_id = ids[9];
|
||||
let uint64_id = ids[10];
|
||||
let tuple_id = ids[5];
|
||||
let str_id = ids[6];
|
||||
let exn_id = ids[7];
|
||||
let uint32_id = ids[8];
|
||||
let uint64_id = ids[9];
|
||||
let literal_id = ids[10];
|
||||
|
||||
let name_handling = |id: &StrRef, loc: Location, unifier: &mut Unifier| {
|
||||
if *id == int32_id {
|
||||
@ -219,39 +443,33 @@ pub fn parse_type_annotation<T>(
|
||||
Ok(primitives.exception)
|
||||
} else {
|
||||
let obj_id = resolver.get_identifier_def(*id);
|
||||
match obj_id {
|
||||
Ok(obj_id) => {
|
||||
let def = top_level_defs[obj_id.0].read();
|
||||
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
|
||||
if !type_vars.is_empty() {
|
||||
return Err(format!(
|
||||
"Unexpected number of type parameters: expected {} but got 0",
|
||||
type_vars.len()
|
||||
));
|
||||
}
|
||||
let fields = chain(
|
||||
fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
|
||||
methods.iter().map(|(k, v, _)| (*k, (*v, false))),
|
||||
)
|
||||
.collect();
|
||||
Ok(unifier.add_ty(TypeEnum::TObj {
|
||||
obj_id,
|
||||
fields,
|
||||
params: Default::default(),
|
||||
}))
|
||||
} else {
|
||||
Err(format!("Cannot use function name as type at {}", loc))
|
||||
if let Ok(obj_id) = obj_id {
|
||||
let def = top_level_defs[obj_id.0].read();
|
||||
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
|
||||
if !type_vars.is_empty() {
|
||||
return Err(HashSet::from([format!(
|
||||
"Unexpected number of type parameters: expected {} but got 0",
|
||||
type_vars.len()
|
||||
)]));
|
||||
}
|
||||
let fields = chain(
|
||||
fields.iter().map(|(k, v, m)| (*k, (*v, *m))),
|
||||
methods.iter().map(|(k, v, _)| (*k, (*v, false))),
|
||||
)
|
||||
.collect();
|
||||
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: VarMap::default() }))
|
||||
} else {
|
||||
Err(HashSet::from([format!("Cannot use function name as type at {loc}")]))
|
||||
}
|
||||
Err(_) => {
|
||||
let ty = resolver
|
||||
.get_symbol_type(unifier, top_level_defs, primitives, *id)
|
||||
.map_err(|e| format!("Unknown type annotation at {}: {}", loc, e))?;
|
||||
if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) {
|
||||
Ok(ty)
|
||||
} else {
|
||||
Err(format!("Unknown type annotation {} at {}", id, loc))
|
||||
}
|
||||
} else {
|
||||
let ty =
|
||||
resolver.get_symbol_type(unifier, top_level_defs, primitives, *id).map_err(
|
||||
|e| HashSet::from([format!("Unknown type annotation at {loc}: {e}")]),
|
||||
)?;
|
||||
if let TypeEnum::TVar { .. } = &*unifier.get_ty(ty) {
|
||||
Ok(ty)
|
||||
} else {
|
||||
Err(HashSet::from([format!("Unknown type annotation {id} at {loc}")]))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -261,9 +479,6 @@ pub fn parse_type_annotation<T>(
|
||||
if *id == virtual_id {
|
||||
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
|
||||
Ok(unifier.add_ty(TypeEnum::TVirtual { ty }))
|
||||
} else if *id == list_id {
|
||||
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, slice)?;
|
||||
Ok(unifier.add_ty(TypeEnum::TList { ty }))
|
||||
} else if *id == tuple_id {
|
||||
if let Tuple { elts, .. } = &slice.node {
|
||||
let ty = elts
|
||||
@ -272,10 +487,33 @@ pub fn parse_type_annotation<T>(
|
||||
parse_type_annotation(resolver, top_level_defs, unifier, primitives, elt)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
Ok(unifier.add_ty(TypeEnum::TTuple { ty }))
|
||||
Ok(unifier.add_ty(TypeEnum::TTuple { ty, is_vararg_ctx: false }))
|
||||
} else {
|
||||
Err("Expected multiple elements for tuple".into())
|
||||
Err(HashSet::from(["Expected multiple elements for tuple".into()]))
|
||||
}
|
||||
} else if *id == literal_id {
|
||||
let mut parse_literal = |elt: &Expr<T>| {
|
||||
let ty = parse_type_annotation(resolver, top_level_defs, unifier, primitives, elt)?;
|
||||
let ty_enum = &*unifier.get_ty_immutable(ty);
|
||||
match ty_enum {
|
||||
TypeEnum::TLiteral { values, .. } => Ok(values.clone()),
|
||||
_ => Err(HashSet::from([format!(
|
||||
"Expected literal in type argument for Literal at {}",
|
||||
elt.location
|
||||
)])),
|
||||
}
|
||||
};
|
||||
|
||||
let values = if let Tuple { elts, .. } = &slice.node {
|
||||
elts.iter().map(&mut parse_literal).collect::<Result<Vec<_>, _>>()?
|
||||
} else {
|
||||
vec![parse_literal(slice)?]
|
||||
}
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.collect_vec();
|
||||
|
||||
Ok(unifier.get_fresh_literal(values, Some(slice.location)))
|
||||
} else {
|
||||
let types = if let Tuple { elts, .. } = &slice.node {
|
||||
elts.iter()
|
||||
@ -291,13 +529,13 @@ pub fn parse_type_annotation<T>(
|
||||
let def = top_level_defs[obj_id.0].read();
|
||||
if let TopLevelDef::Class { fields, methods, type_vars, .. } = &*def {
|
||||
if types.len() != type_vars.len() {
|
||||
return Err(format!(
|
||||
return Err(HashSet::from([format!(
|
||||
"Unexpected number of type parameters: expected {} but got {}",
|
||||
type_vars.len(),
|
||||
types.len()
|
||||
));
|
||||
)]));
|
||||
}
|
||||
let mut subst = HashMap::new();
|
||||
let mut subst = VarMap::new();
|
||||
for (var, ty) in izip!(type_vars.iter(), types.iter()) {
|
||||
let id = if let TypeEnum::TVar { id, .. } = &*unifier.get_ty(*var) {
|
||||
*id
|
||||
@ -319,7 +557,7 @@ pub fn parse_type_annotation<T>(
|
||||
}));
|
||||
Ok(unifier.add_ty(TypeEnum::TObj { obj_id, fields, params: subst }))
|
||||
} else {
|
||||
Err("Cannot use function name as type".into())
|
||||
Err(HashSet::from(["Cannot use function name as type".into()]))
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -330,10 +568,13 @@ pub fn parse_type_annotation<T>(
|
||||
if let Name { id, .. } = &value.node {
|
||||
subscript_name_handle(id, slice, unifier)
|
||||
} else {
|
||||
Err(format!("unsupported type expression at {}", expr.location))
|
||||
Err(HashSet::from([format!("unsupported type expression at {}", expr.location)]))
|
||||
}
|
||||
}
|
||||
_ => Err(format!("unsupported type expression at {}", expr.location)),
|
||||
Constant { value, .. } => SymbolValue::from_constant_inferred(value)
|
||||
.map(|v| unifier.get_fresh_literal(vec![v], Some(expr.location)))
|
||||
.map_err(|err| HashSet::from([err])),
|
||||
_ => Err(HashSet::from([format!("unsupported type expression at {}", expr.location)])),
|
||||
}
|
||||
}
|
||||
|
||||
@ -344,7 +585,7 @@ impl dyn SymbolResolver + Send + Sync {
|
||||
unifier: &mut Unifier,
|
||||
primitives: &PrimitiveStore,
|
||||
expr: &Expr<T>,
|
||||
) -> Result<Type, String> {
|
||||
) -> Result<Type, HashSet<String>> {
|
||||
parse_type_annotation(self, top_level_defs, unifier, primitives, expr)
|
||||
}
|
||||
|
||||
@ -357,13 +598,13 @@ impl dyn SymbolResolver + Send + Sync {
|
||||
unifier.internal_stringify(
|
||||
ty,
|
||||
&mut |id| {
|
||||
if let TopLevelDef::Class { name, .. } = &*top_level_defs[id].read() {
|
||||
name.to_string()
|
||||
} else {
|
||||
let TopLevelDef::Class { name, .. } = &*top_level_defs[id].read() else {
|
||||
unreachable!("expected class definition")
|
||||
}
|
||||
};
|
||||
|
||||
name.to_string()
|
||||
},
|
||||
&mut |id| format!("var{}", id),
|
||||
&mut |id| format!("typevar{id}"),
|
||||
&mut None,
|
||||
)
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user