mirror of
https://github.com/rui314/mold.git
synced 2026-11-09 04:49:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8de38c35a2 | ||
|
|
602d8b42ad | ||
|
|
6e322219ca | ||
|
|
b4fe61fbf7 | ||
|
|
b575a42641 | ||
|
|
47bf6a030a | ||
|
|
e2b599c3c1 | ||
|
|
c4cd78d93a | ||
|
|
63a9f80d1c | ||
|
|
6c0d688966 | ||
|
|
1e66190ff1 | ||
|
|
89148f0a36 | ||
|
|
3da398023e | ||
|
|
596475ee28 | ||
|
|
59d20ed5ea | ||
|
|
e819287122 | ||
|
|
b3307426e8 | ||
|
|
56f1250f77 | ||
|
|
cb9ba2a6a5 | ||
|
|
8041014c3e | ||
|
|
ffad459a57 | ||
|
|
6a44075396 | ||
|
|
fb20353ea4 | ||
|
|
93d75ca0ee | ||
|
|
a0795de60c | ||
|
|
06893555e7 | ||
|
|
78753b0fe4 | ||
|
|
d088db65b1 | ||
|
|
4165d0e82e | ||
|
|
100987dd0e | ||
|
|
6bdd637973 | ||
|
|
e10a25f478 | ||
|
|
9e320c4012 | ||
|
|
10b162491a | ||
|
|
6f8dbb64b2 | ||
|
|
b7d67b020e | ||
|
|
a247ca27d3 | ||
|
|
f20f7bdfa7 | ||
|
|
56ee0f8c1b | ||
|
|
17b7b85a88 | ||
|
|
97acaca6d2 | ||
|
|
d62424b202 | ||
|
|
c544bc7a81 | ||
|
|
9b91621465 | ||
|
|
3717460027 | ||
|
|
2ffdd1256c | ||
|
|
13e6cc0d59 | ||
|
|
81339386c9 | ||
|
|
bd97ebc907 | ||
|
|
64325c31cc | ||
|
|
ee6fb251da | ||
|
|
24d32398f5 | ||
|
|
99173586d6 | ||
|
|
3a1b9c6c28 | ||
|
|
48428d3890 | ||
|
|
f03b745952 | ||
|
|
4ba7792e43 | ||
|
|
a36a1611e6 | ||
|
|
1ac5ff77e7 | ||
|
|
85d55f3fa4 | ||
|
|
767260f53d | ||
|
|
6b60f75821 | ||
|
|
f94d51fd53 | ||
|
|
8a360ff453 | ||
|
|
db723dfbac | ||
|
|
037d1d57e6 | ||
|
|
04b493edf1 | ||
|
|
2ca893e9ab | ||
|
|
033d127859 | ||
|
|
39e6a01e25 | ||
|
|
aed5a38d1d | ||
|
|
b7048f555a | ||
|
|
f10b0c40c8 | ||
|
|
b3566fac1a | ||
|
|
56b862b23c | ||
|
|
d5781d582c | ||
|
|
abb7b4ef64 | ||
|
|
696ed71789 | ||
|
|
0ee71d395c | ||
|
|
ceab12c56b | ||
|
|
c9e4cc4b6e | ||
|
|
a551ec5cc3 | ||
|
|
4c9aad3726 | ||
|
|
4c26f1649b | ||
|
|
c0f9cd6665 | ||
|
|
96d02824ee | ||
|
|
8233a75df1 | ||
|
|
9aacb62c03 | ||
|
|
49129fbd59 | ||
|
|
b2c3d1dc67 | ||
|
|
6df3fd23bb | ||
|
|
269fbb959c | ||
|
|
614e2a0326 | ||
|
|
92161ddf08 | ||
|
|
d9cf1f099e | ||
|
|
3dcd1df382 | ||
|
|
fdf84222d8 | ||
|
|
7c8b80520a | ||
|
|
52febe9fae | ||
|
|
efda2fc149 | ||
|
|
b34a3082e1 | ||
|
|
acc8854e93 | ||
|
|
d3b2a74fa0 | ||
|
|
80baa5dbac | ||
|
|
8f17166297 | ||
|
|
8b9b014847 | ||
|
|
5b22233017 | ||
|
|
a368d2233d | ||
|
|
04e7dd8f70 | ||
|
|
e3bc3490b7 | ||
|
|
d2b88ab395 | ||
|
|
fdab1c33b2 | ||
|
|
bc4272f76d | ||
|
|
a0d62d97f7 | ||
|
|
3d7bed0abd | ||
|
|
9685b424d8 | ||
|
|
c925e791ce | ||
|
|
1a694020fe | ||
|
|
fcd61c6e57 | ||
|
|
f9828086ad | ||
|
|
0ea3c18bc1 | ||
|
|
a9c709b8a4 | ||
|
|
6b16418c6d | ||
|
|
04d6d800e0 | ||
|
|
3e51fa1915 | ||
|
|
ee6a2fa617 | ||
|
|
5d44b9f41d | ||
|
|
427e9604ab | ||
|
|
471142f238 | ||
|
|
8ea4678ba7 | ||
|
|
d947c52b3e | ||
|
|
29d22ccdc7 | ||
|
|
c1a509a3ff | ||
|
|
ee668f78a3 | ||
|
|
25d2a3ecf1 | ||
|
|
fd13e83077 | ||
|
|
9a4ad937d6 | ||
|
|
d17b876f6a | ||
|
|
3028b2c18c | ||
|
|
7f1b5647cc | ||
|
|
339e870f18 | ||
|
|
3ce0175c15 | ||
|
|
516456132e | ||
|
|
00b398ffd2 | ||
|
|
4eb5f294b1 | ||
|
|
e5a4e9351a | ||
|
|
cb2004af8d | ||
|
|
830a8609aa | ||
|
|
6fc6e1916f | ||
|
|
13d2fa222c | ||
|
|
51e23d44d7 | ||
|
|
e874910ccb | ||
|
|
7ec87c99ce | ||
|
|
6d33903650 | ||
|
|
dd3aaef2ef | ||
|
|
a6c0d9de96 | ||
|
|
a7828b5986 | ||
|
|
3f7da561ce | ||
|
|
50e0643cb1 | ||
|
|
9b50d69058 | ||
|
|
b4bed3505e | ||
|
|
3bfc9b6d97 | ||
|
|
1261269fd8 | ||
|
|
377abf91e0 | ||
|
|
48cae3a63a | ||
|
|
4348731de1 | ||
|
|
8335147fe2 | ||
|
|
61fa2e17ca | ||
|
|
99d79c429b | ||
|
|
7fe68bf109 | ||
|
|
659780e6c4 | ||
|
|
254ccb339c | ||
|
|
a5511c1a7b | ||
|
|
2d4a214d6e | ||
|
|
c73791304f | ||
|
|
89a9b334e8 | ||
|
|
c19c87988f | ||
|
|
15459ac587 | ||
|
|
8afbeb85e3 | ||
|
|
c2e3a64db0 | ||
|
|
a4b64d86b4 | ||
|
|
94adbf6e7f | ||
|
|
2777a8229e | ||
|
|
2157032638 | ||
|
|
cfcd3137df | ||
|
|
135b1e1a06 | ||
|
|
244154313a | ||
|
|
c6a2cbc577 | ||
|
|
a9800541d0 | ||
|
|
a31c187dbb | ||
|
|
20bbba9101 | ||
|
|
7f26bc1097 | ||
|
|
d567d10f15 | ||
|
|
43a004eab6 | ||
|
|
4afe0ffd5b | ||
|
|
0e9fa3df08 | ||
|
|
444cfcf971 | ||
|
|
0b3094f73a | ||
|
|
2e6a67f188 | ||
|
|
5331eafc08 | ||
|
|
0ab442dbc6 | ||
|
|
60b97830ca | ||
|
|
d83916655c | ||
|
|
54b68f6e42 | ||
|
|
36ec82df73 | ||
|
|
c5f69d9181 | ||
|
|
b56daa27b7 | ||
|
|
3bf748a8fd | ||
|
|
3ca1cb6579 | ||
|
|
733952657a | ||
|
|
6fad4d5cf1 | ||
|
|
2aeddd8659 | ||
|
|
722d8f02bc | ||
|
|
75a935d071 | ||
|
|
0fec5eec58 | ||
|
|
f615c28339 | ||
|
|
e41ed90846 | ||
|
|
ec2c9fcbc2 | ||
|
|
1b17bdf0d2 | ||
|
|
6cd991a3e4 | ||
|
|
8dfbccd8a6 | ||
|
|
ec3a8d247b | ||
|
|
ebbda8f4bc | ||
|
|
61c0854ddc | ||
|
|
1efb465bfa | ||
|
|
fa30ac90cb | ||
|
|
9b9d9d7ef9 | ||
|
|
8ceae6875f | ||
|
|
e61c7dbcb7 | ||
|
|
d64a0ce248 | ||
|
|
a7e9aac9d3 | ||
|
|
af97c80062 | ||
|
|
d659f5930f | ||
|
|
011e194896 | ||
|
|
8fe0ffb586 | ||
|
|
8794986da3 | ||
|
|
44bfa2a9b2 | ||
|
|
294e22a86a | ||
|
|
60f9b36dcf | ||
|
|
b24a27a9f2 | ||
|
|
b56649e972 | ||
|
|
f4edcbd37e | ||
|
|
1f6b4b4c32 | ||
|
|
0e41bb98e4 | ||
|
|
c52c765f98 | ||
|
|
3ccb8e2291 | ||
|
|
8a56434bae | ||
|
|
4658ec473a | ||
|
|
b2effe0931 | ||
|
|
e918027933 | ||
|
|
ebc789e41f | ||
|
|
46bbf3df03 | ||
|
|
19162bea6c | ||
|
|
c77f241e9b | ||
|
|
bb257d5a37 | ||
|
|
67ba746692 | ||
|
|
284c571636 | ||
|
|
1c639473ab | ||
|
|
8e0967d72b | ||
|
|
9d9f0e96e7 | ||
|
|
65479de34d | ||
|
|
9bca3eacef | ||
|
|
15b984a27c | ||
|
|
d6039a2a58 | ||
|
|
4980d26ad8 | ||
|
|
d677902cc0 | ||
|
|
95fef65b28 | ||
|
|
2712c5e183 | ||
|
|
7d6d588ee3 | ||
|
|
de4910ff57 | ||
|
|
2308083712 | ||
|
|
f7e0ff86a1 | ||
|
|
1e8dae17c2 | ||
|
|
aa4a728710 | ||
|
|
47a35e8b38 | ||
|
|
e2e85b3fc7 | ||
|
|
3d67399506 | ||
|
|
946c71146d | ||
|
|
972347aae9 | ||
|
|
da0de56368 | ||
|
|
5e5db87568 | ||
|
|
3be3c7faef | ||
|
|
ef0e3553b4 | ||
|
|
f58bf44acf | ||
|
|
846d42aa73 | ||
|
|
66e7950766 | ||
|
|
d3c8f63e19 | ||
|
|
fbe442097a | ||
|
|
2569831fbf | ||
|
|
ae5095177b | ||
|
|
433eca009e | ||
|
|
bcba36dc9a | ||
|
|
92e2afcc28 | ||
|
|
c3cea186f9 | ||
|
|
89596a87d6 | ||
|
|
6131f9b77b | ||
|
|
c3cbd297fc | ||
|
|
0380abbb7b | ||
|
|
b5aee2485e | ||
|
|
6c504e434d | ||
|
|
87badfdb83 | ||
|
|
8fe5b00ea3 | ||
|
|
b9d8f49037 | ||
|
|
db51683955 | ||
|
|
fbb314400c | ||
|
|
c0ef81a144 | ||
|
|
8c29736bd8 | ||
|
|
73c5095f1e | ||
|
|
900d689cb3 | ||
|
|
fa71e1b222 | ||
|
|
ed2801b547 | ||
|
|
747a7508ca | ||
|
|
322056731f | ||
|
|
48db48cda2 | ||
|
|
385dadf43d | ||
|
|
2bfbce1933 | ||
|
|
f08abb1b3c | ||
|
|
f7edd50ff1 | ||
|
|
ec231699c7 | ||
|
|
a50a793cda | ||
|
|
557f3689c2 | ||
|
|
a78277b2f4 | ||
|
|
5c5cba2179 | ||
|
|
9f316eb875 | ||
|
|
52b713b996 | ||
|
|
5053a1926e | ||
|
|
ec685cb37a | ||
|
|
a026122121 | ||
|
|
1dede4350e | ||
|
|
aed83a2109 | ||
|
|
d6bf14c5b3 | ||
|
|
653406ff4d | ||
|
|
f50cfe847a | ||
|
|
04f026cbcc | ||
|
|
da8e459060 | ||
|
|
abedadff59 | ||
|
|
96e6a38d08 | ||
|
|
40ad3e5700 | ||
|
|
750523e694 | ||
|
|
86498df474 | ||
|
|
135b13f3cc | ||
|
|
d52673605f | ||
|
|
ffca665b28 | ||
|
|
be74299a61 | ||
|
|
74908a7035 | ||
|
|
7d1dee4a3a | ||
|
|
b2775275f8 | ||
|
|
4eb13f32c4 | ||
|
|
96ea73e773 | ||
|
|
df5c9e146d | ||
|
|
3d715f6571 | ||
|
|
a21598f0ef | ||
|
|
4f04b640a8 | ||
|
|
c9cf514677 | ||
|
|
2f7fd93117 | ||
|
|
0052475b7d | ||
|
|
a58c342b76 | ||
|
|
77bce45d83 | ||
|
|
2cc4d6a81c | ||
|
|
54f549c31e | ||
|
|
643bb2aefb | ||
|
|
3a62c12861 | ||
|
|
c8e2be01ca | ||
|
|
a1d0c8a6a3 | ||
|
|
e21540ed26 | ||
|
|
0d3bc19530 | ||
|
|
0faea2291f | ||
|
|
9226b64d6d | ||
|
|
0f9aea233b | ||
|
|
5430746f22 | ||
|
|
055332c946 | ||
|
|
7340b1c35e | ||
|
|
a029e10c62 | ||
|
|
c7632feaa4 | ||
|
|
22b16f16cd | ||
|
|
b4867dd3ff | ||
|
|
86ff43bf2d | ||
|
|
56b8bb797e | ||
|
|
c068d03df4 | ||
|
|
b8ae5a8c7d | ||
|
|
d43a8b286a | ||
|
|
941a8d8d7c | ||
|
|
0395c30370 | ||
|
|
8bbf7cf05c | ||
|
|
da5c77278e | ||
|
|
b2ae03fdb8 | ||
|
|
b19ea1a396 | ||
|
|
d0f01784ff | ||
|
|
a1fedf5821 | ||
|
|
ace37f38f7 | ||
|
|
7fecd46e67 | ||
|
|
c73f59645d | ||
|
|
9b051c1d43 | ||
|
|
dc127e5280 | ||
|
|
c9041967d1 | ||
|
|
99f51f03eb | ||
|
|
c5a8e52925 | ||
|
|
df5a97ede2 | ||
|
|
dcf75d7d8b | ||
|
|
6d2d640802 | ||
|
|
6b502f1581 | ||
|
|
a0fe942d2d | ||
|
|
a58e6f14ff | ||
|
|
5e936482cb | ||
|
|
7ec6910413 | ||
|
|
3cc86fe2c5 | ||
|
|
d74d0d0ef4 | ||
|
|
86dd63ead2 | ||
|
|
48ab9a44f3 | ||
|
|
4fa0487aa3 | ||
|
|
e09ce32f73 | ||
|
|
2294f1d7f7 | ||
|
|
160c059352 | ||
|
|
93d79e105f | ||
|
|
d1e99e7a86 | ||
|
|
3d06f6484e | ||
|
|
6f8aa2f99d | ||
|
|
b78feeaf7f | ||
|
|
cc01683a5c | ||
|
|
1a39c9318d | ||
|
|
42d21ec936 | ||
|
|
7449dc3873 | ||
|
|
7d6e9e8a26 | ||
|
|
e82b5d9419 | ||
|
|
283712d80d | ||
|
|
81b0eb991d | ||
|
|
a6a78bce25 | ||
|
|
a221b33e4c | ||
|
|
03a77ed441 | ||
|
|
be273267d6 | ||
|
|
7ffd95453f | ||
|
|
714f673cbf | ||
|
|
ba7760e427 | ||
|
|
c718a0def7 | ||
|
|
2cb26dab38 | ||
|
|
5a87ab3a50 | ||
|
|
5ec85a91bf | ||
|
|
1c097506eb | ||
|
|
b057cc82be | ||
|
|
67f9388285 | ||
|
|
7f640167a2 | ||
|
|
b7e53ea5e6 | ||
|
|
e3a89f222d | ||
|
|
60ba943a6f | ||
|
|
833be2a105 | ||
|
|
85a3c6f135 | ||
|
|
3dfe91ce8b | ||
|
|
d756118ab3 | ||
|
|
a6d4f75da8 | ||
|
|
301382e5a2 | ||
|
|
4e56b6d371 | ||
|
|
6968a402a1 | ||
|
|
af8c689722 | ||
|
|
7a7d03d186 | ||
|
|
f359d51458 | ||
|
|
a25435ec43 | ||
|
|
f6265afa79 | ||
|
|
3fd572410d | ||
|
|
c39ecd6e07 | ||
|
|
db7753b997 | ||
|
|
6c27ac899c | ||
|
|
725db8a81a | ||
|
|
1967c81832 | ||
|
|
05ee232da0 | ||
|
|
71401cb632 | ||
|
|
6f151f5889 | ||
|
|
cf3ad924aa | ||
|
|
434a0d7499 | ||
|
|
0380bea1dc | ||
|
|
9c7f14afdf | ||
|
|
3898598581 | ||
|
|
9196e3821d | ||
|
|
9494e857ac | ||
|
|
9ae2154d65 | ||
|
|
5ce808338e | ||
|
|
e36bf62474 | ||
|
|
b20d780768 | ||
|
|
47513055fb | ||
|
|
d4a2b4b63c | ||
|
|
e8a68b1bbb | ||
|
|
46efea10eb | ||
|
|
5dfefa867a | ||
|
|
0800e0ed0d | ||
|
|
28ddca3727 | ||
|
|
81054189b7 | ||
|
|
7d98a49a2f | ||
|
|
fc3271050f | ||
|
|
5963506d8d | ||
|
|
01b59b2155 | ||
|
|
2316c952ad | ||
|
|
f42386cf28 | ||
|
|
abc06691d2 | ||
|
|
6d3e8f3ff4 | ||
|
|
875e3541c4 | ||
|
|
ea8ee584b5 | ||
|
|
e81d70eebd | ||
|
|
4037f207fe | ||
|
|
d09c44da9a | ||
|
|
f7a381610c | ||
|
|
a3d83c5327 | ||
|
|
65279c9624 | ||
|
|
613422a00e | ||
|
|
b11bcd8e6d | ||
|
|
bd95fc90ae | ||
|
|
8d59a33536 | ||
|
|
ee1a4c6cd5 | ||
|
|
a01c78f771 | ||
|
|
b8876bc125 | ||
|
|
68f9f33f3a | ||
|
|
6bbfbcfc64 | ||
|
|
66224d9ec2 | ||
|
|
bc051b2e02 | ||
|
|
620669c7a0 | ||
|
|
c2fe4663d2 | ||
|
|
fffec1424a | ||
|
|
d3540dcab2 | ||
|
|
7cc7c4c99c | ||
|
|
a6debf1ad3 | ||
|
|
c8f06bb1a5 | ||
|
|
2eef5735f0 | ||
|
|
6fcdeb9088 | ||
|
|
01d51ca47c | ||
|
|
f9dd7ab7c6 | ||
|
|
19416424bb | ||
|
|
35e5d81cf1 | ||
|
|
c4b3b1ff7c | ||
|
|
f564f2e4b8 | ||
|
|
05f4bb3c84 | ||
|
|
93b13c89eb | ||
|
|
103c68bec5 | ||
|
|
4a0485c774 | ||
|
|
182785e0c4 | ||
|
|
d7ee65d5d6 | ||
|
|
6dd1c44ad9 | ||
|
|
046a28ce3d | ||
|
|
34be96e62e | ||
|
|
b18be913d5 | ||
|
|
f477f0e277 | ||
|
|
1b1c50a24f | ||
|
|
cc69390144 | ||
|
|
e70f3ca23e | ||
|
|
d3e688cd83 | ||
|
|
e9f327a938 | ||
|
|
417d17cbea | ||
|
|
4f20b0e648 | ||
|
|
3d0fa5d93c | ||
|
|
ee48017b66 | ||
|
|
1ce76ea568 | ||
|
|
3396be62b4 | ||
|
|
7a340c6469 | ||
|
|
f6f2e909a6 | ||
|
|
68f8cbc3d6 | ||
|
|
94e2104d70 | ||
|
|
4322dad7c2 | ||
|
|
de1123d600 | ||
|
|
a37ae7e290 | ||
|
|
20d42d3a31 | ||
|
|
2e731001d9 | ||
|
|
c433f96074 | ||
|
|
36ad403f7c | ||
|
|
b8ba529e57 | ||
|
|
3881cd2a69 | ||
|
|
6ce8c8fcda | ||
|
|
958bce3d88 | ||
|
|
4959c02bd6 | ||
|
|
da5cc1049c | ||
|
|
5768f27eef | ||
|
|
17214ae348 | ||
|
|
a98878f988 | ||
|
|
414976992d | ||
|
|
d4963fbc38 | ||
|
|
5075908706 | ||
|
|
ee7446a9d7 | ||
|
|
19dce43b67 | ||
|
|
3dc5e88f6d | ||
|
|
2cf15f20f0 | ||
|
|
19d8d87797 | ||
|
|
f55084e9dc | ||
|
|
273693951d | ||
|
|
a2b72c33f8 | ||
|
|
d29ee0c4c5 | ||
|
|
344773166c | ||
|
|
4bffbbd530 | ||
|
|
b8d9d12619 | ||
|
|
8ab361da56 | ||
|
|
e8ab792f46 | ||
|
|
879e9bed13 | ||
|
|
d0aecfd12a | ||
|
|
5dcfcbedb2 | ||
|
|
43964392e1 | ||
|
|
5051b051be | ||
|
|
fed11f6eca | ||
|
|
13757779c5 | ||
|
|
063a8fda2a | ||
|
|
6e5fa07222 | ||
|
|
a53b8db45b | ||
|
|
33261b57c8 | ||
|
|
890313dd9c | ||
|
|
dceb9bbb4b | ||
|
|
6d9d6fe801 | ||
|
|
305fddf397 | ||
|
|
bffdaa5b1f | ||
|
|
b6603f2d3c | ||
|
|
9192ec01bc | ||
|
|
c472dc5b34 | ||
|
|
c1ce9ebb75 | ||
|
|
c6393dfd84 | ||
|
|
678d194a68 | ||
|
|
df81f7363c | ||
|
|
8857b8796d | ||
|
|
d77d5e36d9 | ||
|
|
2a2453f93d | ||
|
|
8c837eef01 | ||
|
|
21787c1417 | ||
|
|
63452ee050 | ||
|
|
bfff76057f | ||
|
|
80c1b9c258 | ||
|
|
801fc08a37 | ||
|
|
cd04a2f32d | ||
|
|
dc15979a21 | ||
|
|
c5f5720a7c | ||
|
|
a91d19d5e5 | ||
|
|
a1b835bd5e | ||
|
|
defabc0672 | ||
|
|
25433d61ed | ||
|
|
39b103e44e | ||
|
|
91164c701b | ||
|
|
fbecc040bb | ||
|
|
197c1e8cb9 | ||
|
|
4fbfe4cba7 | ||
|
|
e0cb69af5d | ||
|
|
2fee580c65 | ||
|
|
d879a504e7 | ||
|
|
f41bfcd5c7 |
No files matched your search
@@ -0,0 +1,7 @@
|
||||
# rustc's LoongArch targets use the medium code model, but the cc crate
|
||||
# compiles C code with the C compiler's default normal code model, whose
|
||||
# direct calls reach only ±128 MiB, unless -Ccode-model is given
|
||||
# explicitly. A debug build of mold has more text than that, so we pass
|
||||
# the model explicitly to make the cc crate pass -mcmodel=medium.
|
||||
[target.'cfg(target_arch = "loongarch64")']
|
||||
rustflags = ["-Ccode-model=medium"]
|
||||
@@ -43,13 +43,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v1
|
||||
uses: redhat-actions/podman-login@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -71,20 +71,13 @@ jobs:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
|
||||
cmake --install . --config Release --prefix ../mold-install
|
||||
cd ..
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
$version = $Env:TAG -replace '^v', ''
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-windows-x86_64.zip
|
||||
run: ./.github/workflows/build-windows.ps1
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
|
||||
@@ -21,13 +21,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v1
|
||||
uses: redhat-actions/podman-login@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Set-Location "$PSScriptRoot/../.."
|
||||
|
||||
cargo build --release --locked --package mold-cli
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
$metadata = cargo metadata --locked --no-deps --format-version 1 | ConvertFrom-Json
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
$version = ($metadata.packages | Where-Object name -eq 'mold-cli').version
|
||||
|
||||
$stage = 'mold-install'
|
||||
New-Item -ItemType Directory -Force "$stage/bin", "$stage/share/man/man1", "$stage/share/doc/mold", dist | Out-Null
|
||||
Copy-Item target/release/mold.exe "$stage/bin/"
|
||||
Copy-Item docs/mold.1 "$stage/share/man/man1/"
|
||||
Copy-Item LICENSE "$stage/share/doc/mold/"
|
||||
Compress-Archive -Path "$stage/*" -DestinationPath "dist/mold-$version-x86_64-windows.zip" -Force
|
||||
+111
-91
@@ -1,79 +1,110 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
env:
|
||||
UBSAN_OPTIONS: print_stacktrace=1:halt_on_error=1
|
||||
|
||||
jobs:
|
||||
build-asan-tsan:
|
||||
strategy:
|
||||
matrix:
|
||||
target:
|
||||
- ''
|
||||
- '-DMOLD_USE_ASAN=On'
|
||||
- '-DMOLD_USE_TSAN=On'
|
||||
test-all-targets:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
# Incremental artifacts and mold's own debug info are unused in CI.
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: rui314/setup-mold@staging
|
||||
- run: sudo ./install-build-deps.sh
|
||||
- name: build
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: install test dependencies
|
||||
run: |
|
||||
sudo apt-get install -y clang-18 clang gcc-multilib gdb dwarfdump zstd
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-18 -DCMAKE_CXX_COMPILER=clang++-18 ${{ matrix.target }} ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
sudo ./install-test-deps.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- run: cargo build --locked
|
||||
- name: check the build for 32-bit hosts
|
||||
run: |
|
||||
rustup target add armv7-unknown-linux-gnueabihf i686-unknown-linux-gnu \
|
||||
powerpc-unknown-linux-gnu
|
||||
cargo check --locked --workspace --all-targets --target armv7-unknown-linux-gnueabihf
|
||||
# i386 aligns 64-bit integers to 4 bytes
|
||||
cargo check --locked --workspace --all-targets --target i686-unknown-linux-gnu
|
||||
# 32-bit PowerPC has no 64-bit atomics
|
||||
cargo check --locked --workspace --all-targets --target powerpc-unknown-linux-gnu
|
||||
- name: verify all target toolchains are available
|
||||
run: |
|
||||
cargo test --locked -p mold-cli --test integration -- --list |
|
||||
tee "$RUNNER_TEMP/test-inventory.txt"
|
||||
! grep -q '^unavailable:' "$RUNNER_TEMP/test-inventory.txt"
|
||||
# All workspace crates were built above. Avoid linking empty test binaries
|
||||
# for the target crates and command.
|
||||
- run: cargo test --locked -p mold -p mold-tests -p mold-cli --lib --test integration
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-clang
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
name: test-results-all-targets
|
||||
path: target/debug/mold-test/out/test
|
||||
|
||||
test-sanitizers:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- sanitizer: address
|
||||
# Input files intentionally remain allocated until process exit.
|
||||
runtime-options: detect_leaks=0:halt_on_error=1
|
||||
test-prefix: ''
|
||||
test-timeout: 30
|
||||
- sanitizer: thread
|
||||
runtime-options: halt_on_error=1
|
||||
# TSan cannot model the atomic-fence synchronization in Rayon's
|
||||
# work-stealing queue. Two CPUs still exercise parallel linker code.
|
||||
test-prefix: taskset -c 0,1
|
||||
test-timeout: 60
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CARGO_TARGET_DIR: target/${{ matrix.sanitizer }}
|
||||
RUSTFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
|
||||
RUSTDOCFLAGS: -Zsanitizer=${{ matrix.sanitizer }}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
components: rust-src
|
||||
- run: sudo ./install-test-deps.sh
|
||||
- run: cargo +nightly build --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64
|
||||
- run: |
|
||||
if [ '${{ matrix.sanitizer }}' = address ]; then
|
||||
export ASAN_OPTIONS='${{ matrix.runtime-options }}'
|
||||
else
|
||||
export TSAN_OPTIONS='${{ matrix.runtime-options }}'
|
||||
fi
|
||||
${{ matrix.test-prefix }} cargo +nightly test --locked -Zbuild-std --target x86_64-unknown-linux-gnu -p mold-cli --no-default-features --features system-allocator,x86_64 --test integration -- --native --test-threads 1 --timeout ${{ matrix.test-timeout }}
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ matrix.sanitizer }}
|
||||
path: target/${{ matrix.sanitizer }}/x86_64-unknown-linux-gnu/debug/mold-test/out/test
|
||||
|
||||
build-msan:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: redhat-actions/podman-login@v1
|
||||
- uses: dtolnay/rust-toolchain@nightly
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
components: rust-src
|
||||
- run: sudo ./install-test-deps.sh
|
||||
- run: sudo apt-get install -y clang llvm
|
||||
- run: .github/workflows/run-msan.sh
|
||||
|
||||
build-multi-archs:
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: install-build-deps
|
||||
run: |
|
||||
sudo ./install-build-deps.sh
|
||||
sudo ./install-cross-tools.sh
|
||||
sudo .github/workflows/install-extras.sh
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- name: archive test results
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-multi-archs
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
name: test-results-msan
|
||||
path: target/msan/x86_64-unknown-linux-gnu/debug/mold-test/out/test
|
||||
|
||||
build-distros:
|
||||
test-distros:
|
||||
strategy:
|
||||
matrix:
|
||||
distro:
|
||||
@@ -87,17 +118,17 @@ jobs:
|
||||
- ubuntu:25.04
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
container: ${{ matrix.distro }}
|
||||
# Tumbleweed does not declare PATH in its container configuration.
|
||||
container:
|
||||
image: ${{ matrix.distro }}
|
||||
env:
|
||||
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- run: ./install-build-deps.sh
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
- run: cd build; ctest --output-on-failure -j$(nproc)
|
||||
- run: ./install-test-deps.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
- run: cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
|
||||
- name: sanitize distro name
|
||||
if: failure()
|
||||
id: sanitize
|
||||
@@ -107,66 +138,55 @@ jobs:
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results-${{ steps.sanitize.outputs.name }}
|
||||
path: |
|
||||
build
|
||||
!build/CMakeFiles
|
||||
path: target/debug/mold-test/out/test
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(sysctl -n hw.physicalcpu)
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: build
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . -j $Env:NUMBER_OF_PROCESSORS
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
|
||||
build-msys:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CARGO_NET_GIT_FETCH_WITH_CLI: true
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: UCRT64
|
||||
update: true
|
||||
pacboy: gcc-libs:p libwinpthread-git:p tbb:p zlib:p zstd:p dlfcn:p cc:p cmake:p ninja:p
|
||||
- name: build
|
||||
shell: msys2 {0}
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -GNinja -DMOLD_USE_MIMALLOC=OFF -DMOLD_USE_SYSTEM_TBB=ON ..
|
||||
cmake --build . -j $(nproc)
|
||||
install: git
|
||||
pacboy: rust:p cc:p dlfcn:p
|
||||
- shell: msys2 {0}
|
||||
run: cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
|
||||
build-freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
# The VM has little disk space, so omit unused build artifacts.
|
||||
CARGO_INCREMENTAL: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build and test
|
||||
uses: vmactions/freebsd-vm@v1
|
||||
- uses: vmactions/freebsd-vm@v1
|
||||
with:
|
||||
usesh: true
|
||||
run: |
|
||||
./install-build-deps.sh
|
||||
mkdir build
|
||||
cd build
|
||||
cmake ..
|
||||
cmake --build . -j$(nproc)
|
||||
ctest --output-on-failure -j$(nproc)
|
||||
pkg install -y rust
|
||||
./install-test-deps.sh
|
||||
cargo build --locked -p mold-cli --no-default-features --features x86_64
|
||||
cargo test --locked -p mold-cli --no-default-features --features x86_64 --test integration -- --native
|
||||
@@ -28,13 +28,13 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- name: Install Podman
|
||||
run: sudo apt-get update && sudo apt-get install -y podman qemu-user-static
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: redhat-actions/podman-login@v1
|
||||
uses: redhat-actions/podman-login@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -50,20 +50,13 @@ jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Build and Archive
|
||||
shell: pwsh
|
||||
run: |
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -T clangcl ..
|
||||
cmake --build . --config Release -j $Env:NUMBER_OF_PROCESSORS
|
||||
cmake --install . --config Release --prefix ../mold-install
|
||||
cd ..
|
||||
New-Item -ItemType Directory -Force dist | Out-Null
|
||||
$version = $Env:TAG -replace '^v', ''
|
||||
Compress-Archive -Path mold-install\* -DestinationPath dist\mold-$version-x86_64-windows.zip
|
||||
run: ./.github/workflows/build-windows.ps1
|
||||
|
||||
- uses: actions/upload-artifact@v7
|
||||
with:
|
||||
@@ -74,7 +67,7 @@ jobs:
|
||||
needs: [build-linux, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v7
|
||||
|
||||
- uses: actions/download-artifact@v8
|
||||
with:
|
||||
|
||||
@@ -1,37 +1,34 @@
|
||||
#!/bin/bash
|
||||
set -e -x
|
||||
cd "$(dirname $0)"/../..
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/../.."
|
||||
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-msan
|
||||
image_build="podman build -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created container image in GitHub's container repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-msan
|
||||
image_build="podman build -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
|
||||
fi
|
||||
# MSan requires instrumented Rust, std, and native dependencies:
|
||||
# https://doc.rust-lang.org/unstable-book/compiler-flags/sanitizer.html#memorysanitizer
|
||||
export CARGO_TARGET_DIR="${CARGO_TARGET_DIR:-target/msan}"
|
||||
export RUSTFLAGS='-Zsanitizer=memory -Zsanitizer-memory-track-origins=2'
|
||||
export RUSTDOCFLAGS="$RUSTFLAGS"
|
||||
export CC_x86_64_unknown_linux_gnu=clang
|
||||
export CFLAGS_x86_64_unknown_linux_gnu='-fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer'
|
||||
export LIBZ_SYS_STATIC=1
|
||||
export MSAN_OPTIONS=halt_on_error=1
|
||||
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/ubuntu:24.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc g++ wget ca-certificates make cmake ninja-build python3 clang-20 llvm-20-dev libclang-20-dev libclang-rt-20-dev libtbb-dev mold && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
RUN mkdir /llvm && \
|
||||
cd /llvm && \
|
||||
wget -O- --progress=dot:mega https://github.com/llvm/llvm-project/archive/refs/tags/llvmorg-21.1.1.tar.gz | tar xzf - --strip-components=1 && \
|
||||
cmake -S ./runtimes -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_RUNTIMES='libcxx;libcxxabi;libunwind' -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DLLVM_USE_SANITIZER=MemoryWithOrigins && \
|
||||
cmake --build build -j\$(nproc) && \
|
||||
cmake --install build --prefix /msan && \
|
||||
rm -rf /llvm
|
||||
EOF
|
||||
# The instrumented linker is too slow unoptimized for the heaviest tests
|
||||
# to finish within the per-test timeout.
|
||||
export CARGO_PROFILE_DEV_OPT_LEVEL=1
|
||||
|
||||
# Build mold and run its tests
|
||||
podman run -it --rm --pull=never -v "$(pwd):/mold:ro" --security-opt seccomp=unconfined $image bash -c "
|
||||
set -e
|
||||
mkdir /build
|
||||
cd /build
|
||||
cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_COMPILER=clang-20 -DCMAKE_CXX_COMPILER=clang++-20 -DMOLD_USE_MSAN=ON -DMOLD_STDLIB_PREFIX=/msan -DMOLD_USE_MIMALLOC=0 -DMOLD_USE_SYSTEM_TBB=1 -DMOLD_USE_MOLD=1 /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
setarch -R ctest --output-on-failure -j\$(nproc)
|
||||
"
|
||||
# Use instrumentable implementations instead of BLAKE3/Zstd assembly, and
|
||||
# select mold as well as mold-cli so Cargo enables its dependency features.
|
||||
args=(
|
||||
--locked -Zbuild-std --target x86_64-unknown-linux-gnu
|
||||
--package mold --package mold-cli --no-default-features
|
||||
--features mold-cli/system-allocator,mold-cli/x86_64,blake3/pure,zstd/no_asm
|
||||
--test integration
|
||||
)
|
||||
cargo +nightly test "${args[@]}" --no-run
|
||||
|
||||
# The preload wrapper runs inside uninstrumented compiler processes, not
|
||||
# inside mold. Rebuild it without MSan so those processes can load it.
|
||||
clang -shared -fPIC -o "$CARGO_TARGET_DIR/x86_64-unknown-linux-gnu/debug/mold-wrapper.so" \
|
||||
c/mold-wrapper.c -ldl
|
||||
|
||||
cargo +nightly test "${args[@]}" -- --native --test-threads 1 --timeout 60 "$@"
|
||||
@@ -8,13 +8,16 @@ on:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-manpage:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Install ronn
|
||||
run: sudo apt-get update && sudo apt-get install -y ronn
|
||||
|
||||
+5
-10
@@ -1,11 +1,6 @@
|
||||
*.o
|
||||
*.so
|
||||
*~
|
||||
**/.DS_Store
|
||||
.cache
|
||||
core
|
||||
gentoo
|
||||
/mold-*.tar.gz
|
||||
/build*
|
||||
/mold
|
||||
/target
|
||||
/dist
|
||||
/bench
|
||||
/.claude
|
||||
/.codex
|
||||
*~
|
||||
-504
@@ -1,504 +0,0 @@
|
||||
# You can customize a build by specifying CMake options. An option may be
|
||||
# given in the -Dvariable=value form. For a boolean variable, `ON` or `1`
|
||||
# means true, while `OFF` or `0` means false.
|
||||
#
|
||||
# Here are a couple of common cmake options:
|
||||
#
|
||||
# -DCMAKE_C_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C compiler name to use. The default value is `cc`.
|
||||
#
|
||||
# -DCMAKE_CXX_COMPILER=<command-name>
|
||||
#
|
||||
# Specifies the C++ compiler name to use. The default value is `c++`.
|
||||
#
|
||||
# -DCMAKE_INSTALL_PREFIX=<directory>
|
||||
#
|
||||
# Specifies the install target directory. The default value is `/usr/local`.
|
||||
#
|
||||
# -DCMAKE_BUILD_TYPE=[Debug | Release | RelWithDebInfo | MinSizeRel]
|
||||
#
|
||||
# Specifies the build type. The default is `Release`, which is the right
|
||||
# option unless you are debugging mold.
|
||||
#
|
||||
# An example of a cmake command line is shown below:
|
||||
#
|
||||
# $ cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_CXX_COMPILER=g++-12 ..
|
||||
#
|
||||
# where `..` refers to this directory.
|
||||
#
|
||||
# With cmake, you may run `cmake --install .` instead of `make install` to
|
||||
# install build artifacts to system directories. If you want to install
|
||||
# artifacts to a temporary target directory, run `cmake --install . --prefix
|
||||
# <dir>`.
|
||||
#
|
||||
# You can see the current cmake variables and their values by running
|
||||
# `cmake -N -L .` in a build directory.
|
||||
#
|
||||
# Note that in this file, we provide various dials and knobs to configure
|
||||
# how to build mold. However, as a policy, we do not provide a way to
|
||||
# enable/disable any individual mold's feature. In other words, we do not
|
||||
# provide options like `--enable-foo` or `--disable-foo`. The motivation
|
||||
# behind this is build reproducibility. We want to guarantee that all builds
|
||||
# of the mold linker of the same version will have the exact same set of
|
||||
# features and behave exactly the same.
|
||||
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(mold VERSION 2.42.1)
|
||||
|
||||
include(CMakeDependentOption)
|
||||
include(CheckSymbolExists)
|
||||
include(GNUInstallDirs)
|
||||
|
||||
add_executable(mold)
|
||||
target_compile_features(mold PRIVATE cxx_std_20)
|
||||
|
||||
if(MINGW)
|
||||
target_link_libraries(mold PRIVATE dl)
|
||||
else()
|
||||
target_link_libraries(mold PRIVATE ${CMAKE_DL_LIBS})
|
||||
endif()
|
||||
|
||||
# Build mold itself using mold if -DMOLD_USE_MOLD=ON
|
||||
option(MOLD_USE_MOLD "Use mold to build mold" OFF)
|
||||
if(MOLD_USE_MOLD)
|
||||
target_link_options(mold PRIVATE -fuse-ld=mold)
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "Deb")
|
||||
target_link_options(mold PRIVATE -Wl,--gdb-index)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE MATCHES "^Rel")
|
||||
target_link_options(mold PRIVATE -Wl,--gc-sections,--icf=safe)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT "${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
|
||||
target_compile_options(mold PRIVATE
|
||||
-fno-exceptions
|
||||
-fno-unwind-tables
|
||||
-fno-asynchronous-unwind-tables
|
||||
-ffunction-sections
|
||||
-fdata-sections
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wno-sign-compare
|
||||
-Wno-unused-function
|
||||
-Wno-unused-parameter
|
||||
-Wno-missing-field-initializers
|
||||
-ggnu-pubnames)
|
||||
endif()
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_options(mold PRIVATE -D_GLIBCXX_ASSERTIONS)
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "OpenBSD")
|
||||
set(OPENBSD ON)
|
||||
endif()
|
||||
|
||||
# Build mold with -flto if -DMOLD_LTO=ON
|
||||
option(MOLD_LTO "Build mold with link-time optimization enabled")
|
||||
if(MOLD_LTO)
|
||||
set_property(TARGET mold PROPERTY INTERPROCEDURAL_OPTIMIZATION ON)
|
||||
endif()
|
||||
|
||||
# Enable AddressSanitizer if -DMOLD_USE_ASAN=ON
|
||||
option(MOLD_USE_ASAN "Build mold with AddressSanitizer" OFF)
|
||||
if(MOLD_USE_ASAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
target_link_options(mold PRIVATE -fsanitize=address -fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
# Enabled ThreadSanitizer if -DMOLD_USE_TSAN=ON
|
||||
option(MOLD_USE_TSAN "Build mold with ThreadSanitizer" OFF)
|
||||
if(MOLD_USE_TSAN)
|
||||
target_compile_options(mold PRIVATE -fsanitize=thread)
|
||||
target_link_options(mold PRIVATE -fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Enable MemorySanitizer if -DMOLD_USE_MSAN=ON
|
||||
option(MOLD_USE_MSAN "Build mold with MemorySanitizer" OFF)
|
||||
if(MOLD_USE_MSAN)
|
||||
set(MOLD_STDLIB_PREFIX /usr CACHE FILEPATH "prefix of instrumented stdlib")
|
||||
target_compile_options(mold PRIVATE
|
||||
-g -Og -fsanitize=memory -fsanitize-memory-track-origins
|
||||
-nostdinc++ -isystem ${MOLD_STDLIB_PREFIX}/include/c++/v1
|
||||
-DENABLE_MSAN_UNPOISON
|
||||
)
|
||||
target_link_options(mold PRIVATE
|
||||
-fsanitize=memory
|
||||
-nostdlib++ -L ${MOLD_STDLIB_PREFIX}/lib -lc++
|
||||
-Wl,-rpath,${MOLD_STDLIB_PREFIX}/lib
|
||||
)
|
||||
endif()
|
||||
|
||||
# Statically-link libstdc++ if -DMOLD_MOSTLY_STATIC=ON.
|
||||
#
|
||||
# This option is intended to be used by `./dist.sh` script to create a
|
||||
# mold binary that works on various Linux distros. You probably don't
|
||||
# need nor want to set this to ON.
|
||||
option(MOLD_MOSTLY_STATIC "Statically link libstdc++ and some other libraries" OFF)
|
||||
if(MOLD_MOSTLY_STATIC)
|
||||
target_link_options(mold PRIVATE -static-libstdc++)
|
||||
endif()
|
||||
|
||||
# Find zlib. If libz.so is not found, we compile a bundled one and
|
||||
# statically-link it to mold.
|
||||
find_package(ZLIB QUIET)
|
||||
if(ZLIB_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE ZLIB::ZLIB)
|
||||
else()
|
||||
set(ZLIB_BUILD_SHARED OFF CACHE INTERNAL "")
|
||||
set(ZLIB_BUILD_TESTING OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/zlib EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE zlibstatic)
|
||||
endif()
|
||||
|
||||
# Find BLAKE3 cryptographic hash library. Just like zlib, if libblkae3.so
|
||||
# is not found, we compile a bundled one and statically-link it to mold.
|
||||
find_package(BLAKE3 QUIET)
|
||||
if(BLAKE3_FOUND AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE BLAKE3::blake3)
|
||||
else()
|
||||
function(mold_add_blake3)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
add_subdirectory(third-party/blake3/c EXCLUDE_FROM_ALL)
|
||||
target_link_libraries(mold PRIVATE blake3)
|
||||
target_include_directories(mold PUBLIC third-party/blake3/c)
|
||||
endfunction()
|
||||
|
||||
mold_add_blake3()
|
||||
endif()
|
||||
|
||||
# Find zstd compression library. If zstd.h is not found, we compile a
|
||||
# bundled one and statically-link it to mold.
|
||||
include(CheckIncludeFile)
|
||||
check_include_file(zstd.h HAVE_ZSTD_H)
|
||||
|
||||
if(HAVE_ZSTD_H AND NOT MOLD_MOSTLY_STATIC)
|
||||
target_link_libraries(mold PRIVATE zstd)
|
||||
else()
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_CONTRIB OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
add_subdirectory(third-party/zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
target_include_directories(mold PUBLIC third-party/zstd/lib)
|
||||
target_link_libraries(mold PRIVATE libzstd_static)
|
||||
endif()
|
||||
|
||||
# Find mimalloc. mimalloc is an alternative malloc implementation
|
||||
# optimized for multi-threaded applications.
|
||||
#
|
||||
# If you want to use the usual libc's malloc, pass -DMOLD_USE_MIMALLOC=OFF.
|
||||
#
|
||||
# We enable mimalloc by default for 64-bit targets. It doesn't seem to
|
||||
# be stable on 32-bit targets.
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_MIMALLOC "Use mimalloc" ON
|
||||
"CMAKE_SIZEOF_VOID_P EQUAL 8; NOT APPLE; NOT ANDROID; NOT OPENBSD; NOT MOLD_USE_ASAN; NOT MOLD_USE_TSAN" OFF)
|
||||
|
||||
cmake_dependent_option(
|
||||
MOLD_USE_SYSTEM_MIMALLOC "Use system or vendored mimalloc" OFF
|
||||
MOLD_USE_MIMALLOC OFF)
|
||||
|
||||
# By default, we build a bundled mimalloc and statically-link it to
|
||||
# mold. If you want to dynamically link to the system's
|
||||
# libmimalloc.so, pass -DMOLD_USE_SYSTEM_MIMALLOC=ON.
|
||||
#
|
||||
# We require mimalloc v3 or newer. mimalloc v2 doesn't use transparent
|
||||
# huge pages for the allocation path that serves mold's largest data
|
||||
# structures, which makes linking large programs significantly slower.
|
||||
if(MOLD_USE_MIMALLOC)
|
||||
if(MOLD_USE_SYSTEM_MIMALLOC)
|
||||
find_package(mimalloc 3 REQUIRED)
|
||||
target_link_libraries(mold PRIVATE mimalloc)
|
||||
else()
|
||||
function(mold_add_mimalloc)
|
||||
set(MI_BUILD_STATIC ON CACHE INTERNAL "")
|
||||
set(MI_BUILD_TESTS OFF CACHE INTERNAL "")
|
||||
set(MI_NO_OPT_ARCH ON CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/mimalloc EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(mimalloc-static PRIVATE MI_USE_ENVIRON=0)
|
||||
target_link_libraries(mold PRIVATE mimalloc-static)
|
||||
endfunction()
|
||||
|
||||
mold_add_mimalloc()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Find TBB. TBB (OneTBB or Intel TBB) is a high-level threading library.
|
||||
# Use of this library is mandatory.
|
||||
#
|
||||
# By default, we build a bundled one and statically-link the library
|
||||
# to mold. If you want to link to the system's libtbb2.so, pass
|
||||
# -DMOLD_USE_SYSTEM_TBB=ON.
|
||||
option(MOLD_USE_SYSTEM_TBB "Use system or vendored TBB" OFF)
|
||||
if(MOLD_USE_SYSTEM_TBB OR BLAKE3_USE_TBB)
|
||||
find_package(TBB REQUIRED)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
else()
|
||||
function(mold_add_tbb)
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
set(TBB_TEST OFF CACHE INTERNAL "")
|
||||
set(TBB_STRICT OFF CACHE INTERNAL "")
|
||||
add_subdirectory(third-party/tbb EXCLUDE_FROM_ALL)
|
||||
target_compile_definitions(tbb PRIVATE __TBB_DYNAMIC_LOAD_ENABLED=0)
|
||||
target_link_libraries(mold PRIVATE TBB::tbb)
|
||||
endfunction()
|
||||
|
||||
mold_add_tbb()
|
||||
endif()
|
||||
|
||||
# We always use Clang to build mold on Windows. MSVC can't compile mold.
|
||||
if(WIN32)
|
||||
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR
|
||||
"Your compiler is not supported; install Clang from Visual Studio Installer and re-run cmake with '-T clangcl'")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(mold PRIVATE NOGDI NOMINMAX)
|
||||
if(MINGW)
|
||||
target_compile_definitions(mold PRIVATE _WIN32_WINNT=0xA00)
|
||||
target_link_libraries(mold PRIVATE bcrypt)
|
||||
endif()
|
||||
else()
|
||||
include(CheckLibraryExists)
|
||||
check_library_exists(m pow "" LIBM_FOUND)
|
||||
if(LIBM_FOUND)
|
||||
target_link_libraries(mold PRIVATE m)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Build mold-wrapper.so
|
||||
if(NOT APPLE AND NOT WIN32)
|
||||
add_library(mold-wrapper SHARED)
|
||||
install(TARGETS mold-wrapper DESTINATION ${CMAKE_INSTALL_LIBDIR}/mold)
|
||||
|
||||
# Remove the default `lib` prefix
|
||||
set_target_properties(mold-wrapper PROPERTIES PREFIX "")
|
||||
target_link_libraries(mold-wrapper PRIVATE ${CMAKE_DL_LIBS})
|
||||
target_sources(mold-wrapper PRIVATE src/mold-wrapper.c)
|
||||
endif()
|
||||
|
||||
# If atomics doesn't work by default, add -latomic.
|
||||
# We need the flag on riscv, armv6 and m68k.
|
||||
include(CheckCXXSourceCompiles)
|
||||
check_cxx_source_compiles("#include <atomic>
|
||||
int main() {
|
||||
std::atomic_uint8_t a;
|
||||
std::atomic_uint16_t b;
|
||||
std::atomic_uint32_t c;
|
||||
std::atomic_uint64_t d;
|
||||
return ++a + ++b + ++c + ++d;
|
||||
}" HAVE_FULL_ATOMIC_SUPPORT)
|
||||
|
||||
if(NOT HAVE_FULL_ATOMIC_SUPPORT)
|
||||
target_link_libraries(mold PRIVATE atomic)
|
||||
endif()
|
||||
|
||||
# Add -pthread
|
||||
if(NOT APPLE AND NOT MSVC)
|
||||
target_compile_options(mold PRIVATE -pthread)
|
||||
target_link_options(mold PRIVATE -pthread)
|
||||
endif()
|
||||
|
||||
check_symbol_exists(madvise sys/mman.h HAVE_MADVISE)
|
||||
check_symbol_exists(uname sys/utsname.h HAVE_UNAME)
|
||||
|
||||
# glibc declares fallocate only under _GNU_SOURCE. C++ compilers
|
||||
# predefine it on Linux, but this check runs the C compiler, which
|
||||
# does not.
|
||||
set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE)
|
||||
check_symbol_exists(fallocate fcntl.h HAVE_FALLOCATE)
|
||||
unset(CMAKE_REQUIRED_DEFINITIONS)
|
||||
|
||||
# Create a .cc file containing the current git hash for `mold --version`.
|
||||
add_custom_target(git_hash
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_DIR=${CMAKE_SOURCE_DIR}
|
||||
-DOUTPUT_FILE=${CMAKE_BINARY_DIR}/mold-git-hash.h
|
||||
-P ${CMAKE_SOURCE_DIR}/lib/update-git-hash.cmake
|
||||
DEPENDS lib/update-git-hash.cmake
|
||||
BYPRODUCTS mold-git-hash.h
|
||||
VERBATIM)
|
||||
|
||||
add_dependencies(mold git_hash)
|
||||
|
||||
# Almost all functions are template in mold which take a target type
|
||||
# (e.g. X86_64) as its type parameter. Since we suport more than 10
|
||||
# targets, compiling a single source file for all the targets is very
|
||||
# slow.
|
||||
#
|
||||
# As a workaround, we create a .cc file for each target and spawn many
|
||||
# compiler instances. This is hacky but greatly reduces compile time
|
||||
# on a multicore machine.
|
||||
#
|
||||
# You can build mold for a specific set of targets by passing, e.g.,
|
||||
# -DMOLD_TARGETS='X86_64;ARM64LE', though this is strongly discouraged
|
||||
# for build reproducibility. Use this option only if you build mold
|
||||
# frequently for your personal use; otherwise, always build mold with all
|
||||
# targets enabled. We provide this flag in the trust that you will not
|
||||
# abuse it, so please don’t betray that trust.
|
||||
set(MOLD_TARGETS
|
||||
X86_64 I386 ARM64LE ARM64BE ARM32LE ARM32BE RV32LE RV32BE RV64LE
|
||||
RV64BE PPC32 PPC64V1 PPC64V2 S390X SPARC64 M68K SH4LE SH4BE
|
||||
LOONGARCH32 LOONGARCH64
|
||||
CACHE STRING "List of supported targets")
|
||||
|
||||
list(GET MOLD_TARGETS 0 MOLD_FIRST_TARGET)
|
||||
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/arch-arm32.cc
|
||||
src/arch-arm64.cc
|
||||
src/arch-i386.cc
|
||||
src/arch-loongarch.cc
|
||||
src/arch-m68k.cc
|
||||
src/arch-ppc32.cc
|
||||
src/arch-ppc64v1.cc
|
||||
src/arch-ppc64v2.cc
|
||||
src/arch-riscv.cc
|
||||
src/arch-s390x.cc
|
||||
src/arch-sh4.cc
|
||||
src/arch-sparc64.cc
|
||||
src/arch-x86-64.cc
|
||||
src/archive-file.cc
|
||||
src/cmdline.cc
|
||||
src/error.cc
|
||||
src/filetype.cc
|
||||
src/gc-sections.cc
|
||||
src/gdb-index.cc
|
||||
src/icf.cc
|
||||
src/input-files.cc
|
||||
src/input-sections.cc
|
||||
src/linker-script.cc
|
||||
src/main.cc
|
||||
src/mapfile.cc
|
||||
src/output-chunks.cc
|
||||
src/passes.cc
|
||||
src/relocatable.cc
|
||||
src/shrink-sections.cc
|
||||
src/thunks.cc
|
||||
src/tls.cc
|
||||
)
|
||||
|
||||
if(WIN32 AND NOT MINGW)
|
||||
list(APPEND MOLD_TEMPLATE_FILES src/lto-win32.cc)
|
||||
else()
|
||||
list(APPEND MOLD_TEMPLATE_FILES src/lto-unix.cc)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/output-file-win32.cc
|
||||
src/subprocess-win32.cc
|
||||
)
|
||||
else()
|
||||
list(APPEND MOLD_TEMPLATE_FILES
|
||||
src/output-file-unix.cc
|
||||
src/subprocess-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
function(mold_instantiate_templates SOURCE TARGET)
|
||||
set(PATH ${CMAKE_BINARY_DIR}/${SOURCE}.${TARGET}.cc)
|
||||
if(NOT EXISTS ${PATH})
|
||||
file(WRITE ${PATH} "#define MOLD_${TARGET} 1
|
||||
#define MOLD_TARGET ${TARGET}
|
||||
#include \"${CMAKE_SOURCE_DIR}/${SOURCE}\"
|
||||
")
|
||||
endif()
|
||||
target_sources(mold PRIVATE ${PATH})
|
||||
endfunction()
|
||||
|
||||
foreach (SOURCE IN LISTS MOLD_TEMPLATE_FILES)
|
||||
foreach(TARGET IN LISTS MOLD_TARGETS)
|
||||
mold_instantiate_templates(${SOURCE} ${TARGET})
|
||||
set(HAVE_TARGET_${TARGET} 1)
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
# Add other non-template source files.
|
||||
target_sources(mold PRIVATE
|
||||
lib/aho-corasick.cc
|
||||
lib/arena.cc
|
||||
lib/compress.cc
|
||||
lib/crc32.cc
|
||||
lib/demangle.cc
|
||||
lib/filepath.cc
|
||||
lib/glob.cc
|
||||
lib/hyperloglog.cc
|
||||
lib/perf.cc
|
||||
lib/random.cc
|
||||
lib/tar.cc
|
||||
src/elf.cc
|
||||
src/entry.cc
|
||||
third-party/rust-demangle/rust-demangle.c
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
target_sources(mold PRIVATE
|
||||
src/jobs-win32.cc
|
||||
src/mapped-file-win32.cc
|
||||
src/signal-win32.cc
|
||||
)
|
||||
else()
|
||||
target_sources(mold PRIVATE
|
||||
src/jobs-unix.cc
|
||||
src/mapped-file-unix.cc
|
||||
src/signal-unix.cc
|
||||
)
|
||||
endif()
|
||||
|
||||
# Create config.h file
|
||||
configure_file(lib/config.h.in config.h)
|
||||
include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
# Test configs
|
||||
include(CTest)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
# Create the ld symlinks required for testing
|
||||
if(NOT WIN32)
|
||||
add_custom_command(
|
||||
TARGET mold POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink mold ld
|
||||
BYPRODUCTS ld
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
VERBATIM)
|
||||
endif()
|
||||
|
||||
if(${UNIX})
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||
install(TARGETS mold RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
install(FILES docs/mold.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/)
|
||||
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||
|
||||
function(mold_install_relative_symlink OLD NEW)
|
||||
install(CODE "
|
||||
get_filename_component(PREFIX_ABS \${CMAKE_INSTALL_PREFIX}/ ABSOLUTE)
|
||||
get_filename_component(OLD_ABS ${OLD} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_ABS ${NEW} ABSOLUTE BASE_DIR \${PREFIX_ABS})
|
||||
get_filename_component(NEW_DIR \${NEW_ABS} DIRECTORY)
|
||||
file(RELATIVE_PATH OLD_REL \${NEW_DIR} \${OLD_ABS})
|
||||
message(STATUS \"Installing symlink: \$ENV{DESTDIR}\${NEW_ABS} -> \${OLD_REL}\")
|
||||
file(MAKE_DIRECTORY \$ENV{DESTDIR}\${NEW_DIR})
|
||||
file(CREATE_LINK \${OLD_REL} \$ENV{DESTDIR}\${NEW_ABS} SYMBOLIC)")
|
||||
endfunction()
|
||||
|
||||
if(NOT WIN32)
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_LIBEXECDIR}/mold/ld${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_BINDIR}/mold${CMAKE_EXECUTABLE_SUFFIX}
|
||||
${CMAKE_INSTALL_BINDIR}/ld.mold${CMAKE_EXECUTABLE_SUFFIX})
|
||||
mold_install_relative_symlink(${CMAKE_INSTALL_MANDIR}/man1/mold.1
|
||||
${CMAKE_INSTALL_MANDIR}/man1/ld.mold.1)
|
||||
endif()
|
||||
endif()
|
||||
Generated
+590
@@ -0,0 +1,590 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56"
|
||||
|
||||
[[package]]
|
||||
name = "blake3"
|
||||
version = "1.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d9e454fc11f76977dc803893aff6304ed33d6a26efae8696573bea74baa27ae"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
"cpufeatures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"regex-automata",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273"
|
||||
dependencies = [
|
||||
"find-msvc-tools",
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
|
||||
|
||||
[[package]]
|
||||
name = "cpp_demangle"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "find-msvc-tools"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"libz-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.189"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||
|
||||
[[package]]
|
||||
name = "libmimalloc-sys"
|
||||
version = "0.1.49"
|
||||
source = "git+https://github.com/rui314/mimalloc_rust?rev=3979460494f1cd1e7f936cb8e10f41e927c9f698#3979460494f1cd1e7f936cb8e10f41e927c9f698"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libz-sys"
|
||||
version = "1.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mimalloc"
|
||||
version = "0.1.52"
|
||||
source = "git+https://github.com/rui314/mimalloc_rust?rev=3979460494f1cd1e7f936cb8e10f41e927c9f698#3979460494f1cd1e7f936cb8e10f41e927c9f698"
|
||||
dependencies = [
|
||||
"libmimalloc-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"blake3",
|
||||
"bstr",
|
||||
"cc",
|
||||
"cpp_demangle",
|
||||
"crc32fast",
|
||||
"flate2",
|
||||
"getrandom",
|
||||
"hashbrown",
|
||||
"libc",
|
||||
"libz-sys",
|
||||
"memchr",
|
||||
"memmap2",
|
||||
"portable-atomic",
|
||||
"rayon",
|
||||
"rustc-demangle",
|
||||
"uuid",
|
||||
"xxhash-rust",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-cli"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mimalloc",
|
||||
"mold",
|
||||
"mold-target-arm32",
|
||||
"mold-target-arm32be",
|
||||
"mold-target-arm64",
|
||||
"mold-target-arm64be",
|
||||
"mold-target-i386",
|
||||
"mold-target-loongarch32",
|
||||
"mold-target-loongarch64",
|
||||
"mold-target-m68k",
|
||||
"mold-target-ppc32",
|
||||
"mold-target-ppc64v1",
|
||||
"mold-target-ppc64v2",
|
||||
"mold-target-riscv32",
|
||||
"mold-target-riscv32be",
|
||||
"mold-target-riscv64",
|
||||
"mold-target-riscv64be",
|
||||
"mold-target-s390x",
|
||||
"mold-target-sh4",
|
||||
"mold-target-sh4be",
|
||||
"mold-target-sparc64",
|
||||
"mold-target-x86_64",
|
||||
"mold-tests",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm32be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-arm64be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-i386"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-loongarch32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-loongarch64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-m68k"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-ppc32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-ppc64v1"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-ppc64v2"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv32"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv32be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-riscv64be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-s390x"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-sh4"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-sh4be"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-sparc64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-target-x86_64"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"mold",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mold-tests"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.47"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.229"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "2.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "3.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "xxhash-rust"
|
||||
version = "0.8.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aee1b19627c7c60102ab80d3a9cbe18de90bfe03bfa6c3715447681f0e8c8af6"
|
||||
|
||||
[[package]]
|
||||
name = "zstd"
|
||||
version = "0.13.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a"
|
||||
dependencies = [
|
||||
"zstd-safe",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-safe"
|
||||
version = "7.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d"
|
||||
dependencies = [
|
||||
"zstd-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zstd-sys"
|
||||
version = "2.0.16+zstd.1.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"pkg-config",
|
||||
]
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
[package]
|
||||
name = "mold"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "A high-performance ELF linker, rewritten in Rust"
|
||||
license.workspace = true
|
||||
# tests/lib.rs belongs to the mold-tests workspace crate.
|
||||
autotests = false
|
||||
|
||||
# The linker is generic over the target. Instantiating it for all twenty
|
||||
# targets in one crate keeps the compiler on a single core for minutes, so
|
||||
# each target is instantiated in a crate of its own under arch/, and the
|
||||
# executable in cli/ dispatches to them.
|
||||
[workspace]
|
||||
members = ["cli", "arch/*", "tests"]
|
||||
default-members = [".", "cli", "arch/*", "tests"]
|
||||
|
||||
[workspace.package]
|
||||
version = "3.0.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.95"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.dependencies]
|
||||
libc = "0.2"
|
||||
mold = { path = "." }
|
||||
|
||||
[dependencies]
|
||||
blake3 = "1.8.7"
|
||||
bstr = "1"
|
||||
cpp_demangle = "0.4"
|
||||
crc32fast = "1"
|
||||
flate2 = { version = "1", default-features = false, features = ["zlib"] }
|
||||
getrandom = "0.4.3"
|
||||
hashbrown = "0.17.1"
|
||||
libc.workspace = true
|
||||
libz-sys = "1.1.29"
|
||||
memchr = "2.8.3"
|
||||
memmap2 = "0.9"
|
||||
# std has no 64-bit atomics on hosts such as 32-bit PowerPC.
|
||||
portable-atomic = "1"
|
||||
rayon = "1"
|
||||
rustc-demangle = "0.1"
|
||||
uuid = { version = "1.27", default-features = false, features = ["v4"] }
|
||||
xxhash-rust = { version = "0.8", features = ["xxh3"] }
|
||||
zstd = "0.13"
|
||||
|
||||
[build-dependencies]
|
||||
cc = "1"
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
@@ -131,42 +131,32 @@ and LoongArch are also attached to each
|
||||
|
||||
## How to Build
|
||||
|
||||
mold is written in C++20, so if you build mold yourself, you will need a
|
||||
recent version of a C++ compiler and a C++ standard library. We recommend GCC
|
||||
10.2 or Clang 16.0.0 (or later) and libstdc++ 10 or libc++ 7 (or later).
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
To install build dependencies, run `./install-build-deps.sh` in this
|
||||
directory. It will detect your Linux distribution and attempt to install the
|
||||
necessary packages.
|
||||
mold is written in Rust and built with Cargo. It requires Rust 1.95 or later;
|
||||
if your system's Rust is older, install the stable toolchain with
|
||||
[rustup](https://rustup.rs/). You also need Git and a C compiler such as GCC or
|
||||
Clang, which you can install with your system's package manager.
|
||||
|
||||
### Compile mold
|
||||
|
||||
```shell
|
||||
git clone --branch stable https://github.com/rui314/mold.git
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
sudo ./install-build-deps.sh
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_COMPILER=c++ -B build
|
||||
cmake --build build -j$(nproc)
|
||||
sudo cmake --build build --target install
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
You might need to pass a C++20 compiler command name to `cmake`. In the
|
||||
example above, `c++` is passed. If that doesn't work for you, try a specific
|
||||
version of a compiler, such as `g++-10` or `clang++-12`.
|
||||
The executable and its companion preload library are
|
||||
`target/release/mold` and `target/release/mold-wrapper.so`.
|
||||
|
||||
By default, `mold` is installed to `/usr/local/bin`. You can change the
|
||||
installation location by passing `-DCMAKE_INSTALL_PREFIX=<directory>`.
|
||||
For other cmake options, see the comments in `CMakeLists.txt`.
|
||||
### Install mold
|
||||
|
||||
If you are not using a recent enough Linux distribution, or if `cmake` does
|
||||
not work for you for any reason, you can use Podman to build mold in a
|
||||
container. To do so, run `./dist.sh` in this directory instead of using
|
||||
`cmake`. The shell script will pull a container image, build mold and auxiliary
|
||||
files inside it, and package them into a single tar file named
|
||||
`dist/mold-$version-$arch-linux.tar.gz`. You can extract the tar file anywhere
|
||||
and use the mold executable in it.
|
||||
Run `sudo ./install-mold.sh` to install mold under `/usr/local`. To install it
|
||||
under a different prefix, set `PREFIX`, as in
|
||||
`sudo PREFIX=/usr ./install-mold.sh`. `mold-wrapper.so` is installed to
|
||||
`$PREFIX/lib/mold`. If your system uses another library directory, such as
|
||||
`/usr/lib64`, set `MOLD_LIBDIR` to it for both `cargo build` and
|
||||
`install-mold.sh`.
|
||||
|
||||
You can also run `target/release/mold` directly without installing it.
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -188,11 +178,10 @@ to use mold instead of `/usr/bin/ld`:
|
||||
valid argument, so you need to use the `-B` option instead. The `-B` option
|
||||
tells GCC where to look for external commands like `ld`.
|
||||
|
||||
If you have installed mold with `make install`, there should be a directory
|
||||
named `/usr/libexec/mold` (or `/usr/local/libexec/mold`, depending on your
|
||||
`$PREFIX`), and the `ld` command should be there. The `ld` is actually a
|
||||
symlink to `mold`. So, all you need is to pass `-B/usr/libexec/mold` (or
|
||||
`-B/usr/local/libexec/mold`) to GCC.
|
||||
If you installed mold using the script above, there is a directory named
|
||||
`/usr/local/libexec/mold`, and the `ld` command there is a symlink to mold.
|
||||
Pass `-B/usr/local/libexec/mold` to GCC. If you installed under another
|
||||
prefix, use that prefix instead.
|
||||
|
||||
If you haven't installed `ld.mold` to any `$PATH`, you can still pass
|
||||
`-fuse-ld=/absolute/path/to/mold` to clang to use mold. However, GCC does not
|
||||
@@ -266,7 +255,7 @@ mold -run make <make-options-if-any>
|
||||
Internally, mold invokes a given command with the `LD_PRELOAD` environment
|
||||
variable set to its companion shared object file. The shared object file
|
||||
intercepts all function calls to `exec(3)`-family functions to replace
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.bf`, `ld.gold`, or `ld.lld`.
|
||||
`argv[0]` with `mold` if it is `ld`, `ld.bfd`, `ld.gold`, or `ld.lld`.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# Architecture crates
|
||||
|
||||
Each subdirectory is a small crate that instantiates the generic linker for
|
||||
one target architecture. Separate crates let Cargo compile the architectures
|
||||
in parallel. Instantiating all targets in one crate leaves a long serial
|
||||
compilation step.
|
||||
|
||||
The shared linker implementation lives in [`src/`](../src/), with
|
||||
target-specific code in [`src/target/`](../src/target/). These crates provide
|
||||
entry points that the [`cli/`](../cli/) executable selects for the input
|
||||
files' target.
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-arm32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for arm32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Arm32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-arm32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for arm32be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Arm32Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-arm64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for arm64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Arm64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-arm64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for arm64be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Arm64Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-i386"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for i386"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::I386>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-loongarch32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for loongarch32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::LoongArch32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-loongarch64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for loongarch64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::LoongArch64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-m68k"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for m68k"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::M68k>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-ppc32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for ppc32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Ppc32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-ppc64v1"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for ppc64v1"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Ppc64V1>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-ppc64v2"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for ppc64v2"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Ppc64V2>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-riscv32"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for riscv32"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Riscv32>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-riscv32be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for riscv32be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Riscv32Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-riscv64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for riscv64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Riscv64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-riscv64be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for riscv64be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Riscv64Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-s390x"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for s390x"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::S390x>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-sh4"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for sh4"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Sh4>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-sh4be"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for sh4be"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Sh4Be>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-sparc64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for sparc64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::Sparc64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
[package]
|
||||
name = "mold-target-x86_64"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold linker instantiated for x86_64"
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
|
||||
[dependencies]
|
||||
mold.workspace = true
|
||||
@@ -0,0 +1,3 @@
|
||||
pub fn link(cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
mold::driver::link::<mold::target::X86_64>(cmdline)
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
//! Builds the two small C parts of mold.
|
||||
//!
|
||||
//! `mold-wrapper.so` is the preload library behind `mold -run`. It
|
||||
//! interposes the exec family, including the C-variadic `execl` functions
|
||||
//! that stable Rust cannot define, so it is a C file. It is placed next to
|
||||
//! the `mold` executable, where `-run` looks for it.
|
||||
//!
|
||||
//! `lto-message.c` adapts the LTO plugin's printf-like diagnostics
|
||||
//! callback, which is C-variadic as well, and is linked into mold.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
// Embed the current git commit hash into the mold executable. We ask git
|
||||
// instead of parsing files under .git because the repository format varies
|
||||
// (e.g. packed refs, reftable). When building from a source tarball, there's
|
||||
// no .git, and the hash is simply omitted.
|
||||
fn git_hash(source_dir: &Path) -> Option<String> {
|
||||
let dot_git = source_dir.join(".git");
|
||||
if !dot_git.exists() {
|
||||
// Source archives use the package version. If Git metadata is added
|
||||
// later, a clean rebuild is needed to embed the new commit hash.
|
||||
return None;
|
||||
}
|
||||
if dot_git.is_file() {
|
||||
println!("cargo:rerun-if-changed={}", dot_git.display());
|
||||
}
|
||||
|
||||
let git_path = |name: &str| -> Option<PathBuf> {
|
||||
let output = Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(source_dir)
|
||||
.args(["rev-parse", "--git-path", name])
|
||||
.output()
|
||||
.ok()?;
|
||||
if !output.status.success() {
|
||||
return None;
|
||||
}
|
||||
let path = PathBuf::from(String::from_utf8(output.stdout).ok()?.trim());
|
||||
Some(if path.is_absolute() { path } else { source_dir.join(path) })
|
||||
};
|
||||
|
||||
let reftable = git_path("reftable/tables.list").filter(|p| p.exists());
|
||||
if let Some(head) = git_path("HEAD").filter(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", head.display());
|
||||
if let Ok(contents) = std::fs::read_to_string(&head)
|
||||
&& let Some(reference) =
|
||||
contents.strip_prefix("ref: ").map(str::trim).filter(|_| reftable.is_none())
|
||||
&& let Some(path) = git_path(reference)
|
||||
{
|
||||
// A packed reference may acquire a loose file later.
|
||||
// Watch its nearest existing directory until that happens.
|
||||
if let Some(path) = path.ancestors().find(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
}
|
||||
}
|
||||
for path in git_path("packed-refs").into_iter().chain(reftable).filter(|p| p.exists()) {
|
||||
println!("cargo:rerun-if-changed={}", path.display());
|
||||
}
|
||||
|
||||
let output =
|
||||
Command::new("git").arg("-C").arg(source_dir).args(["rev-parse", "HEAD"]).output().ok()?;
|
||||
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_string())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let source_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let version = std::env::var("CARGO_PKG_VERSION").unwrap();
|
||||
let version = match git_hash(&source_dir) {
|
||||
Some(hash) => format!("mold {version} ({hash}; compatible with GNU ld)"),
|
||||
None => format!("mold {version} (compatible with GNU ld)"),
|
||||
};
|
||||
println!("cargo:rustc-env=MOLD_VERSION={version}");
|
||||
|
||||
println!("cargo:rerun-if-changed=c/mold-wrapper.c");
|
||||
println!("cargo:rerun-if-changed=c/lto-message.c");
|
||||
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
|
||||
if target_os == "windows" && target_env == "msvc" {
|
||||
return;
|
||||
}
|
||||
|
||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
let mut build = cc::Build::new();
|
||||
build.opt_level(2).pic(true);
|
||||
if let Ok(sanitizer) = std::env::var("CARGO_CFG_SANITIZE") {
|
||||
build.flag(format!("-fsanitize={sanitizer}"));
|
||||
}
|
||||
build.file("c/lto-message.c").compile("ltomessage");
|
||||
|
||||
if target_os == "windows" || target_os == "macos" {
|
||||
return;
|
||||
}
|
||||
|
||||
// The build directory is inside the actual profile directory. PROFILE
|
||||
// only describes inheritance and need not be that directory's name.
|
||||
let profile_dir = out_dir
|
||||
.ancestors()
|
||||
.find(|path| path.file_name().is_some_and(|name| name == "build"))
|
||||
.and_then(Path::parent)
|
||||
.expect("OUT_DIR must be inside Cargo's build directory");
|
||||
let wrapper = profile_dir.join("mold-wrapper.so");
|
||||
let mut command = build.get_compiler().to_command();
|
||||
command.args(["-shared", "-o"]);
|
||||
command.arg(&wrapper).arg("c/mold-wrapper.c");
|
||||
if target_os == "android" || target_os == "linux" {
|
||||
command.arg("-ldl");
|
||||
}
|
||||
let status = command.status();
|
||||
if !matches!(status, Ok(s) if s.success()) {
|
||||
println!("cargo:warning=could not build mold-wrapper.so; `mold -run` will not work");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
// The linker plugin reports diagnostics through a printf-like callback.
|
||||
// C-variadic functions can't be defined in stable Rust, so this adapter
|
||||
// formats the message and hands it to Rust.
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
|
||||
void mold_lto_report(int level, const char *msg);
|
||||
|
||||
int mold_lto_message(int level, const char *fmt, ...) {
|
||||
char buf[1000];
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, ap);
|
||||
va_end(ap);
|
||||
mold_lto_report(level, buf);
|
||||
return 0;
|
||||
}
|
||||
File renamed without changes.
@@ -0,0 +1,72 @@
|
||||
[package]
|
||||
name = "mold-cli"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
description = "The mold executable"
|
||||
license.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "mold"
|
||||
path = "main.rs"
|
||||
|
||||
[[test]]
|
||||
name = "integration"
|
||||
path = "integration.rs"
|
||||
harness = false
|
||||
|
||||
[dependencies]
|
||||
mimalloc = { git = "https://github.com/rui314/mimalloc_rust", rev = "3979460494f1cd1e7f936cb8e10f41e927c9f698" }
|
||||
mold.workspace = true
|
||||
mold-target-x86_64 = { path = "../arch/x86_64", optional = true }
|
||||
mold-target-i386 = { path = "../arch/i386", optional = true }
|
||||
mold-target-arm32 = { path = "../arch/arm32", optional = true }
|
||||
mold-target-arm32be = { path = "../arch/arm32be", optional = true }
|
||||
mold-target-arm64 = { path = "../arch/arm64", optional = true }
|
||||
mold-target-arm64be = { path = "../arch/arm64be", optional = true }
|
||||
mold-target-riscv64 = { path = "../arch/riscv64", optional = true }
|
||||
mold-target-riscv64be = { path = "../arch/riscv64be", optional = true }
|
||||
mold-target-riscv32 = { path = "../arch/riscv32", optional = true }
|
||||
mold-target-riscv32be = { path = "../arch/riscv32be", optional = true }
|
||||
mold-target-ppc64v2 = { path = "../arch/ppc64v2", optional = true }
|
||||
mold-target-ppc32 = { path = "../arch/ppc32", optional = true }
|
||||
mold-target-ppc64v1 = { path = "../arch/ppc64v1", optional = true }
|
||||
mold-target-s390x = { path = "../arch/s390x", optional = true }
|
||||
mold-target-sparc64 = { path = "../arch/sparc64", optional = true }
|
||||
mold-target-m68k = { path = "../arch/m68k", optional = true }
|
||||
mold-target-sh4 = { path = "../arch/sh4", optional = true }
|
||||
mold-target-sh4be = { path = "../arch/sh4be", optional = true }
|
||||
mold-target-loongarch64 = { path = "../arch/loongarch64", optional = true }
|
||||
mold-target-loongarch32 = { path = "../arch/loongarch32", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
mold-tests = { path = "../tests" }
|
||||
|
||||
# One feature per target. All are built by default; a build for one
|
||||
# target alone (`--no-default-features --features x86_64`) is much faster.
|
||||
[features]
|
||||
default = [
|
||||
"x86_64", "i386", "arm32", "arm32be", "arm64", "arm64be", "riscv64", "riscv64be", "riscv32", "riscv32be",
|
||||
"ppc64v2", "ppc32", "ppc64v1", "s390x", "sparc64", "m68k", "sh4", "sh4be", "loongarch64", "loongarch32",
|
||||
]
|
||||
system-allocator = []
|
||||
x86_64 = ["dep:mold-target-x86_64"]
|
||||
i386 = ["dep:mold-target-i386"]
|
||||
arm32 = ["dep:mold-target-arm32"]
|
||||
arm32be = ["dep:mold-target-arm32be"]
|
||||
arm64 = ["dep:mold-target-arm64"]
|
||||
arm64be = ["dep:mold-target-arm64be"]
|
||||
riscv64 = ["dep:mold-target-riscv64"]
|
||||
riscv64be = ["dep:mold-target-riscv64be"]
|
||||
riscv32 = ["dep:mold-target-riscv32"]
|
||||
riscv32be = ["dep:mold-target-riscv32be"]
|
||||
ppc64v2 = ["dep:mold-target-ppc64v2"]
|
||||
ppc32 = ["dep:mold-target-ppc32"]
|
||||
ppc64v1 = ["dep:mold-target-ppc64v1"]
|
||||
s390x = ["dep:mold-target-s390x"]
|
||||
sparc64 = ["dep:mold-target-sparc64"]
|
||||
m68k = ["dep:mold-target-m68k"]
|
||||
sh4 = ["dep:mold-target-sh4"]
|
||||
sh4be = ["dep:mold-target-sh4be"]
|
||||
loongarch64 = ["dep:mold-target-loongarch64"]
|
||||
loongarch32 = ["dep:mold-target-loongarch32"]
|
||||
@@ -0,0 +1,15 @@
|
||||
//! Links libatomic into mold on hosts without 64-bit atomics, such as
|
||||
//! 32-bit PowerPC. mimalloc uses 64-bit atomics, which C compilers
|
||||
//! implement as calls into libatomic on those hosts.
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
let widths = std::env::var("CARGO_CFG_TARGET_HAS_ATOMIC").unwrap_or_default();
|
||||
if !widths.split(',').any(|w| w == "64") {
|
||||
// rustc places libraries named by this crate before its dependencies
|
||||
// on the linker command line, where --as-needed would drop libatomic.
|
||||
// Link arguments come after all libraries.
|
||||
println!("cargo:rustc-link-arg-bins=-latomic");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
use std::path::Path;
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let root = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap();
|
||||
let cases = [root.join("tests")];
|
||||
mold_tests::run(&cases, Path::new(env!("CARGO_BIN_EXE_mold")))
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
//! The `mold` executable: runs the linker instantiated for the target the
|
||||
//! inputs are for. Each target is instantiated in a crate of its own so
|
||||
//! that the compiler can build them in parallel, and a feature per target
|
||||
//! decides which of them are built in.
|
||||
|
||||
// A Rust executable can define only one global allocator, so select mimalloc
|
||||
// here rather than in the linker library.
|
||||
#[cfg(not(feature = "system-allocator"))]
|
||||
#[global_allocator]
|
||||
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||
|
||||
type LinkFn = fn(mold::driver::Cmdline) -> mold::driver::LinkResult;
|
||||
|
||||
// Each target has its own monomorphized link function. Start with the first
|
||||
// enabled target and switch to the matching function if the inputs differ.
|
||||
const TARGETS: &[(&str, LinkFn)] = &[
|
||||
#[cfg(feature = "x86_64")]
|
||||
("x86_64", mold_target_x86_64::link),
|
||||
#[cfg(feature = "i386")]
|
||||
("i386", mold_target_i386::link),
|
||||
#[cfg(feature = "arm32")]
|
||||
("arm32", mold_target_arm32::link),
|
||||
#[cfg(feature = "arm32be")]
|
||||
("arm32be", mold_target_arm32be::link),
|
||||
#[cfg(feature = "arm64")]
|
||||
("arm64", mold_target_arm64::link),
|
||||
#[cfg(feature = "arm64be")]
|
||||
("arm64be", mold_target_arm64be::link),
|
||||
#[cfg(feature = "riscv64")]
|
||||
("riscv64", mold_target_riscv64::link),
|
||||
#[cfg(feature = "riscv64be")]
|
||||
("riscv64be", mold_target_riscv64be::link),
|
||||
#[cfg(feature = "riscv32")]
|
||||
("riscv32", mold_target_riscv32::link),
|
||||
#[cfg(feature = "riscv32be")]
|
||||
("riscv32be", mold_target_riscv32be::link),
|
||||
#[cfg(feature = "ppc64v2")]
|
||||
("ppc64v2", mold_target_ppc64v2::link),
|
||||
#[cfg(feature = "ppc32")]
|
||||
("ppc32", mold_target_ppc32::link),
|
||||
#[cfg(feature = "ppc64v1")]
|
||||
("ppc64v1", mold_target_ppc64v1::link),
|
||||
#[cfg(feature = "s390x")]
|
||||
("s390x", mold_target_s390x::link),
|
||||
#[cfg(feature = "sparc64")]
|
||||
("sparc64", mold_target_sparc64::link),
|
||||
#[cfg(feature = "m68k")]
|
||||
("m68k", mold_target_m68k::link),
|
||||
#[cfg(feature = "sh4")]
|
||||
("sh4", mold_target_sh4::link),
|
||||
#[cfg(feature = "sh4be")]
|
||||
("sh4be", mold_target_sh4be::link),
|
||||
#[cfg(feature = "loongarch64")]
|
||||
("loongarch64", mold_target_loongarch64::link),
|
||||
#[cfg(feature = "loongarch32")]
|
||||
("loongarch32", mold_target_loongarch32::link),
|
||||
];
|
||||
|
||||
fn link_for_target(target: &str, cmdline: mold::driver::Cmdline) -> mold::driver::LinkResult {
|
||||
for &(name, link) in TARGETS {
|
||||
if name == target {
|
||||
return link(cmdline);
|
||||
}
|
||||
}
|
||||
eprintln!(
|
||||
"mold: unsupported target: {target}; rebuild mold with the appropriate target support"
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let Some(&(initial_target, _)) = TARGETS.first() else {
|
||||
eprintln!("mold: no targets enabled; rebuild mold with the appropriate target support");
|
||||
std::process::exit(1);
|
||||
};
|
||||
let args = std::env::args_os().collect();
|
||||
let status = mold::driver::main(args, initial_target, link_for_target);
|
||||
std::process::exit(status);
|
||||
}
|
||||
@@ -2,39 +2,33 @@
|
||||
#
|
||||
# This script creates a mold binary distribution. The output is written to
|
||||
# the `dist` directory as `mold-$version-$arch-linux.tar.gz` (e.g.
|
||||
# `mold-2.40.0-x86_64-linux.tar.gz`).
|
||||
# `mold-2.42.0-x86_64-linux.tar.gz`).
|
||||
#
|
||||
# This script aims to produce reproducible outputs. That means each time
|
||||
# it's run on the same git commit, it generates a bit-for-bit identical
|
||||
# binary file regardless of when or where it's executed. This property
|
||||
# serves as a strong safeguard against supply chain attacks. With a
|
||||
# reproducible build, anyone can independently verify that the binary
|
||||
# files published on our GitHub release page were built from the git
|
||||
# commit tagged for release by rebuilding the binaries themselves.
|
||||
# This script aims to produce reproducible outputs. The container images,
|
||||
# Rust toolchain, Cargo dependencies and file timestamps are pinned so that
|
||||
# the same git commit can produce a bit-for-bit identical binary file. This
|
||||
# property serves as a strong safeguard against supply chain attacks. With
|
||||
# a reproducible build, anyone can independently verify that the binary
|
||||
# files published on our GitHub release page were built from the git commit
|
||||
# tagged for release by rebuilding the binaries themselves.
|
||||
#
|
||||
# Debian provides snapshot.debian.org to host all historical binary
|
||||
# packages. We use it to construct a container image pinned to a
|
||||
# particular timestamp. snapshot.debian.org is known to be very slow,
|
||||
# but that shouldn't be a big problem for us because we only need that
|
||||
# site the first time.
|
||||
# packages. Distro package repositories must be pinned as well as the base
|
||||
# image before a build is fully reproducible. The loongarch64 build still
|
||||
# uses the live Debian sid repository, so it does not yet have that property.
|
||||
#
|
||||
# The mold executable created by this script is statically linked to
|
||||
# libstdc++, but dynamically linked to glibc, libm and a few other
|
||||
# libraries, as these libraries are almost always available on any Linux
|
||||
# system. We can't statically link glibc because doing so would disable
|
||||
# dlopen(), which is required to load the LTO linker plugin.
|
||||
# The mold executable created by this script is dynamically linked to the
|
||||
# system C runtime and other standard system libraries. We can't statically
|
||||
# link glibc because doing so would disable dlopen(), which is required to
|
||||
# load the LTO linker plugin.
|
||||
#
|
||||
# We use a reasonably old Debian version for the build environment because
|
||||
# a binary dynamically linked against a newer version of glibc won't work
|
||||
# on a system with an older version of glibc.
|
||||
#
|
||||
# We prefer to build mold with Clang rather than GCC because mold's
|
||||
# Identical Code Folding works best with the LLVM address significance
|
||||
# table (.llvm_addrsig). Building a release binary with GCC produces a
|
||||
# slightly larger binary than with Clang.
|
||||
#
|
||||
# We need a recent version of Clang to build mold. If it's not available
|
||||
# via apt-get, we'll build it ourselves.
|
||||
# The Rust toolchain is downloaded from the official Rust distribution
|
||||
# site. Its version and SHA-256 hash are recorded below, so the toolchain
|
||||
# is another pinned build input.
|
||||
#
|
||||
# This script can be used to create non-native binaries (e.g., building
|
||||
# aarch64 binary on x86-64) because Podman automatically runs everything
|
||||
@@ -42,7 +36,16 @@
|
||||
# non-native builds, you may need to install the qemu-user-static package.
|
||||
|
||||
set -e -x
|
||||
cd "$(dirname $0)"
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# The executable embeds the hash of the Git commit it is built from, and
|
||||
# the commit's timestamp is used as the file timestamp, so this script
|
||||
# must run in a Git clone. A worktree doesn't work either because its
|
||||
# `.git` file refers to a directory outside the build container.
|
||||
if [ ! -d .git ]; then
|
||||
echo "$0: must be run in a Git clone of mold" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
usage() {
|
||||
echo "Usage: $0 [ x86_64 | aarch64 | arm | riscv64 | ppc64le | s390x | loongarch64 ]"
|
||||
@@ -52,215 +55,144 @@ usage() {
|
||||
case $# in
|
||||
0)
|
||||
arch=$(uname -m)
|
||||
if [ $arch = arm64 ]; then
|
||||
if [ "$arch" = arm64 ]; then
|
||||
arch=aarch64
|
||||
elif [[ $arch = arm* ]]; then
|
||||
elif [[ "$arch" = arm* ]]; then
|
||||
arch=arm
|
||||
fi
|
||||
;;
|
||||
1)
|
||||
arch="$1"
|
||||
arch=$1
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
# Create a Podman image.
|
||||
if [ "$GITHUB_REPOSITORY" = '' ]; then
|
||||
image=mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image -"
|
||||
else
|
||||
# If this script is running on GitHub Actions, we want to cache
|
||||
# the created container image in GitHub's container repostiory.
|
||||
image=ghcr.io/$GITHUB_REPOSITORY/mold-builder-$arch
|
||||
image_build="podman build --arch $arch -t $image --output=type=registry --layers --cache-to $image --cache-from $image -"
|
||||
fi
|
||||
rust_version=1.97.1
|
||||
|
||||
# Switch to the pinned snapshot.debian.org sources that the Debian images
|
||||
# ship commented out; the live mirrors no longer carry every architecture.
|
||||
apt_setup="sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list"
|
||||
|
||||
case $arch in
|
||||
x86_64)
|
||||
# Debian 9 (Stretch) released in June 2017.
|
||||
#
|
||||
# We use a Google-provided mirror (gcr.io) instead of the official Docker
|
||||
# Hub (docker.io) because docker.io has a strict rate limit policy.
|
||||
#
|
||||
# The toolchain in Debian 9 is too old to build mold, so we rebuild it
|
||||
# from source. We download source archives from official sites and build
|
||||
# them locally, rather than downloading pre-built binaries from somewhere
|
||||
# else, to avoid relying on unverifiable third-party binary blobs. Each
|
||||
# archive is checked against the SHA-256 hash recorded below before it is
|
||||
# unpacked, so this git commit alone determines every input of the build.
|
||||
# A compromised network or mirror can only make the build fail, never
|
||||
# change its output. Podman caches the result of each RUN command, so
|
||||
# rebuilding is done only once per host.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends wget xz-utils file make gcc g++ git zlib1g-dev libssl-dev ca-certificates && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
|
||||
# Build CMake 3.27
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://cmake.org/files/v3.27/cmake-3.27.7.tar.gz && \
|
||||
echo '08f71a106036bf051f692760ef9558c0577c42ac39e96ba097e7662bd4158d8e cmake-3.27.7.tar.gz' | sha256sum -c && \
|
||||
tar xf cmake-3.27.7.tar.gz --strip-components=1 && \
|
||||
./bootstrap --parallel=\$(nproc) && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GCC 14
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/gcc/gcc-14.2.0/gcc-14.2.0.tar.xz && \
|
||||
echo 'a7b39bc69cbf9e25826c5a60ab26477001f7c08d85cec04bc0e29cabed6f3cc9 gcc-14.2.0.tar.xz' | sha256sum -c && \
|
||||
tar xf gcc-14.2.0.tar.xz --strip-components=1 && \
|
||||
mkdir gmp mpc mpfr && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/gmp/gmp-6.3.0.tar.xz && \
|
||||
echo 'a3c2b80201b89e68616f4ad30bc66aee4927c3ce50e33929ca819d5c43538898 gmp-6.3.0.tar.xz' | sha256sum -c && \
|
||||
tar xf gmp-6.3.0.tar.xz --strip-components=1 -C gmp && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/mpc/mpc-1.3.1.tar.gz && \
|
||||
echo 'ab642492f5cf882b74aa0cb730cd410a81edcdbec895183ce930e706c1c759b8 mpc-1.3.1.tar.gz' | sha256sum -c && \
|
||||
tar xf mpc-1.3.1.tar.gz --strip-components=1 -C mpc && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.1.tar.xz && \
|
||||
echo '277807353a6726978996945af13e52829e3abd7a9a5b7fb2793894e18f1fcbb2 mpfr-4.2.1.tar.xz' | sha256sum -c && \
|
||||
tar xf mpfr-4.2.1.tar.xz --strip-components=1 -C mpfr && \
|
||||
./configure --prefix=/usr --enable-languages=c,c++ --disable-bootstrap --disable-multilib && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
ln -sf /usr/lib64/libstdc++.so.6 /usr/lib/x86_64-linux-gnu/libstdc++.so.6 && \
|
||||
rm -rf /build
|
||||
|
||||
# Build GNU binutils 2.43
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://ftp.gnu.org/gnu/binutils/binutils-2.43.tar.xz && \
|
||||
echo 'b53606f443ac8f01d1d5fc9c39497f2af322d99e14cea5c0b4b124d630379365 binutils-2.43.tar.xz' | sha256sum -c && \
|
||||
tar xf binutils-2.43.tar.xz --strip-components=1 && \
|
||||
./configure --prefix=/usr && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -fr /build
|
||||
|
||||
# Build Python 3.12.7
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://www.python.org/ftp/python/3.12.7/Python-3.12.7.tar.xz && \
|
||||
echo '24887b92e2afd4a2ac602419ad4b596372f67ac9b077190f459aba390faf5550 Python-3.12.7.tar.xz' | sha256sum -c && \
|
||||
tar xf Python-3.12.7.tar.xz --strip-components=1 && \
|
||||
./configure && \
|
||||
make -j\$(nproc) && \
|
||||
make install && \
|
||||
rm -rf /build
|
||||
|
||||
# Build LLVM 20
|
||||
RUN mkdir /build && \
|
||||
cd /build && \
|
||||
wget --progress=dot:mega https://github.com/llvm/llvm-project/releases/download/llvmorg-20.1.3/llvm-project-20.1.3.src.tar.xz && \
|
||||
echo 'b6183c41281ee3f23da7fda790c6d4f5877aed103d1e759763b1008bdd0e2c50 llvm-project-20.1.3.src.tar.xz' | sha256sum -c && \
|
||||
tar xf llvm-project-20.1.3.src.tar.xz --strip-components=1 && \
|
||||
mkdir b && \
|
||||
cd b && \
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS=clang ../llvm && \
|
||||
cmake --build . -j\$(nproc) && \
|
||||
cmake --install . --strip && \
|
||||
rm -rf /build
|
||||
EOF
|
||||
# We use a Google-provided mirror (mirror.gcr.io) instead of the official
|
||||
# Docker Hub (docker.io) because docker.io has a strict rate limit policy.
|
||||
base_image=mirror.gcr.io/library/debian:stretch@sha256:c5c5200ff1e9c73ffbf188b4a67eb1c91531b644856b4aefe86a58d2f0cb05be
|
||||
rust_target=x86_64-unknown-linux-gnu
|
||||
rust_sha256=b4cdbc7cc6b0ee0a2666b1872769fdb2ad8393b28b63952f6493b4b400e4832b
|
||||
;;
|
||||
aarch64 | arm | ppc64le | s390x)
|
||||
# Debian 11 (Bullseye) released in August 2021
|
||||
#
|
||||
# We don't want to build Clang for these targets on QEMU becuase it
|
||||
# would take an extremely long time. Also, I believe old Linux boxes
|
||||
# are typically x86-64.
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^deb/d' -e 's/^# deb /deb /g' /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-10 g++-10 clang-16 cmake git && \
|
||||
ln -sf /usr/bin/clang-16 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-16 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
aarch64)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=aarch64-unknown-linux-gnu
|
||||
rust_sha256=2f2496c70bd336a66a4c8baf2d303ba161f3552f192444c3639ba903c7c1e2c5
|
||||
;;
|
||||
arm)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=armv7-unknown-linux-gnueabihf
|
||||
rust_sha256=e89c5e33aaddc6ef56857000c9117875c2997e9a1a500bd7b16277c9874b002f
|
||||
;;
|
||||
riscv64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-18 cmake git && \
|
||||
ln -sf /usr/bin/clang-18 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-18 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
base_image=mirror.gcr.io/riscv64/debian:unstable-20240926@sha256:25654919c2926f38952cdd14b3300d83d13f2d820715f78c9f4b7a1d9399bf48
|
||||
apt_setup="sed -i -e '/^URIs/d' -e 's/^# http/URIs: http/' /etc/apt/sources.list.d/debian.sources"
|
||||
rust_target=riscv64gc-unknown-linux-gnu
|
||||
rust_sha256=59bec35d8febb2ab918fa41cffbaa5b07146a63bdc33f029ff756d70a3151ece
|
||||
;;
|
||||
ppc64le)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=powerpc64le-unknown-linux-gnu
|
||||
rust_sha256=ff524eef5a59d801df09ccad5cdaf9ea1f0a07d75cbed2a7e9f013a9eb76a3c1
|
||||
;;
|
||||
s390x)
|
||||
# Debian 11 (Bullseye) released in August 2021.
|
||||
base_image=mirror.gcr.io/library/debian:bullseye-20240904@sha256:8ccc486c29a3ad02ad5af7f1156e2152dff3ba5634eec9be375269ef123457d8
|
||||
rust_target=s390x-unknown-linux-gnu
|
||||
rust_sha256=808268af9e880d41b8cb32b242e38c9bd3ea7aba6409b02fbffa0fbc5370c538
|
||||
;;
|
||||
loongarch64)
|
||||
cat <<EOF | $image_build
|
||||
FROM mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential gcc-14 g++-14 clang-19 cmake git && \
|
||||
ln -sf /usr/bin/clang-19 /usr/bin/clang && \
|
||||
ln -sf /usr/bin/clang++-19 /usr/bin/clang++ && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
EOF
|
||||
base_image=mirror.gcr.io/loongarch64/debian:sid@sha256:0356df4e494bbb86bb469377a00789a5b42bbf67d5ff649a3f9721b745cbef77
|
||||
apt_setup="echo 'deb http://deb.debian.org/debian sid main' > /etc/apt/sources.list"
|
||||
rust_target=loongarch64-unknown-linux-gnu
|
||||
rust_sha256=d5a925962854730ae7641420d8337af93988ea4ff47b503a856ec53776c87841
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
;;
|
||||
esac
|
||||
|
||||
version=$(sed -n 's/^project(mold VERSION \(.*\))/\1/p' CMakeLists.txt)
|
||||
dest=mold-$version-$arch-linux
|
||||
# Create a Podman image containing the native C tools and a pinned Rust
|
||||
# toolchain. The downloaded archive is checked before it is unpacked.
|
||||
image=mold-rust-builder-$arch
|
||||
archive=rust-$rust_version-$rust_target.tar.gz
|
||||
|
||||
# Source tarballs available on GitHub don't contain .git directory.
|
||||
# Clone the repo if missing.
|
||||
[ -d .git ] || git clone --branch v$version --depth 1 --bare https://github.com/rui314/mold .git
|
||||
podman build --arch "$arch" -t "$image" - <<EOF
|
||||
FROM $base_image
|
||||
ENV DEBIAN_FRONTEND=noninteractive TZ=UTC
|
||||
RUN $apt_setup && \
|
||||
echo 'Acquire::Retries "10"; Acquire::http::timeout "10"; Acquire::Check-Valid-Until "false";' > /etc/apt/apt.conf.d/80-retries && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential ca-certificates git wget && \
|
||||
rm -rf /var/lib/apt/lists
|
||||
RUN mkdir /tmp/rust && \
|
||||
cd /tmp/rust && \
|
||||
wget --progress=dot:mega https://static.rust-lang.org/dist/$archive && \
|
||||
echo '$rust_sha256 $archive' | sha256sum -c && \
|
||||
tar xf $archive && \
|
||||
./${archive%.tar.gz}/install.sh --prefix=/usr/local --disable-ldconfig && \
|
||||
cd / && \
|
||||
rm -rf /tmp/rust
|
||||
EOF
|
||||
|
||||
version=$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml)
|
||||
dest=mold-$version-$arch-linux
|
||||
|
||||
# We use the timestamp of the last Git commit as the file timestamp
|
||||
# for build artifacts.
|
||||
timestamp=$(git log -1 --format=%ct)
|
||||
|
||||
# `uname -m` in an ARM32 container running on an ARM64 host reports it
|
||||
# not as ARM32 but as ARM64. That confuses BLAKE3's cmake script and
|
||||
# erroneously enables NEON SIMD instructions. `setarch` can be used to
|
||||
# change the output of `uname -m`.
|
||||
# not as ARM32 but as ARM64. Keep the reported machine consistent with
|
||||
# the ARM32 userspace for any build tool that inspects it.
|
||||
setarch=
|
||||
[ $arch = arm ] && setarch='setarch linux32'
|
||||
[ "$arch" = arm ] && setarch='setarch linux32'
|
||||
|
||||
mkdir -p dist
|
||||
mkdir -p dist "target/dist-vendor-$arch"
|
||||
|
||||
# Cargo verifies registry packages against the checksums in Cargo.lock and
|
||||
# checks out Git dependencies at the commit recorded there. Vendor them in a
|
||||
# separate networked step so that the actual build can run without a network.
|
||||
# Cargo's own cache is a tmpfs because the libgit2 inside a 32-bit cargo can't
|
||||
# read a bind-mounted ext4 directory (readdir fails with EOVERFLOW under QEMU).
|
||||
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 \
|
||||
--pull=never --env CARGO_HOME=/cargo --tmpfs /cargo -v "$(pwd):/mold:ro" \
|
||||
-v "$(pwd)/target/dist-vendor-$arch:/vendor" "$image" $setarch \
|
||||
bash -c 'cd /mold && cargo vendor --locked /vendor/sources > /vendor/config.toml'
|
||||
|
||||
# Build mold in a container.
|
||||
#
|
||||
# SOURCE_DATE_EPOCH is a standardized environment variable that allows
|
||||
# build artifacts to appear as if they were built at a specific time.
|
||||
# We use it to control how the compiler expands the C/C++ __DATE__ and
|
||||
# __TIME__ macros.
|
||||
podman run --arch $arch -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
--pull=never -v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" $image \
|
||||
$setarch bash -c "
|
||||
# Fixed source, vendor and target paths keep embedded build paths stable.
|
||||
podman run --arch "$arch" -it --rm --userns=host --pids-limit=-1 --network=none \
|
||||
--pull=never --env SOURCE_DATE_EPOCH="$timestamp" --env DEST="$dest" \
|
||||
-v "$(pwd):/mold:ro" -v "$(pwd)/dist:/dist" \
|
||||
-v "$(pwd)/target/dist-vendor-$arch:/vendor:ro" "$image" \
|
||||
$setarch bash -c '
|
||||
set -e
|
||||
export SOURCE_DATE_EPOCH=$timestamp
|
||||
mkdir /build
|
||||
export CARGO_TARGET_DIR=/build/target
|
||||
cd /mold
|
||||
cargo build --release --frozen --config /vendor/config.toml --package mold-cli
|
||||
stage=/build/$DEST
|
||||
DESTDIR=/build PREFIX=/$DEST ./install-mold.sh
|
||||
strip --strip-unneeded "$stage/bin/mold" "$stage/lib/mold/mold-wrapper.so"
|
||||
find "$stage" -print | xargs touch --no-dereference --date="@$SOURCE_DATE_EPOCH"
|
||||
cd /build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DMOLD_MOSTLY_STATIC=1 -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ /mold
|
||||
cmake --build . -j\$(nproc)
|
||||
cmake --install .
|
||||
cmake -DMOLD_USE_MOLD=1 .
|
||||
cmake --build . -j\$(nproc)
|
||||
ctest --output-on-failure -j\$(nproc)
|
||||
cmake --install . --prefix $dest --strip
|
||||
find $dest -print | xargs touch --no-dereference --date=@$timestamp
|
||||
find $dest -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > /dist/$dest.tar.gz
|
||||
cp mold /dist
|
||||
sha256sum /dist/$dest.tar.gz
|
||||
"
|
||||
find "$DEST" -print | sort | tar -cf - --no-recursion --files-from=- | gzip -9nc > "/dist/$DEST.tar.gz"
|
||||
sha256sum "/dist/$DEST.tar.gz"
|
||||
'
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
This is a note about interesting bugs that I met during the
|
||||
development of the mold linker.
|
||||
|
||||
## GNU IFUNC
|
||||
|
||||
Problem: A statically-linked "hello world" program mysteriously
|
||||
crashed in `__libc_start_main` function which is called just after
|
||||
`_start`.
|
||||
|
||||
Investigation: I opened up gdb and found that the program reads a
|
||||
bogus value from some array. It looks like `memcpy` failed to copy
|
||||
proper data there. After some investigation, I noticed that `memcpy`
|
||||
did't copy data at all but instead returned the address of
|
||||
`__memcpy_avx_unaligned` function, which is a real `memcpy` function
|
||||
optimized for machines with the AVX registers.
|
||||
|
||||
This odd issue was caused by the GNU IFUNC mechanism. That is, if a
|
||||
function symbol has type `STT_GNU_IFUNC`, the function does not do
|
||||
what its name suggests to do but instead returns a pointer to a
|
||||
function that does the actual job. In this case, `memcpy` is an IFUNC
|
||||
function, and it returns an address of `__memcpy_avx_unaligned` which
|
||||
is a real `memcpy` function.
|
||||
|
||||
IFUNC function addresses are stored to `.got` section in an ELF
|
||||
executable. The dynamic loader executes all IFUNC functions at
|
||||
startup to replace their GOT entries with their return values. This
|
||||
mechanism allows programs to choose the best implementation among
|
||||
variants of the same function at runtime based on the machine info.
|
||||
|
||||
If a program is statically-linked, there's no dynamic loader that
|
||||
rewrites the GOT entries. Therefore, if a program is
|
||||
statically-linked, a libc's startup routine does that on behalf of the
|
||||
dynamic loader. Concretely, a startup routine interprets all dynamic
|
||||
relocations between `__rela_iplt_start` and `__rela_iplt_stop`
|
||||
symbols. It is linker's responsibility to emit dynamic relocations
|
||||
for IFUNC symbols even if it is linking a statically-linked program
|
||||
and mark the beginning and the ending of a `.rela.dyn` section with
|
||||
the symbols, so that the startup routine can find the relocations.
|
||||
|
||||
The bug was my linker didn't define `__rela_iplt_start` and
|
||||
`__rela_iplt_stop` symbols. Since these symbols are weak, they are
|
||||
initialized to zero. From the point of the initializer function,
|
||||
there's no dynamic relocations between `__rela_iplt_start` and
|
||||
`__rela_iplt_stop` symbols. That left GOT entries for IFUNC symbols
|
||||
untouched.
|
||||
|
||||
The proper fix was to emit dynamic relocations for IFUNC symbols and
|
||||
define the linker-synthesized symbols. I did that, and the bug was
|
||||
fixed.
|
||||
|
||||
## stdio buffering
|
||||
|
||||
Problem: A statically-linked "Hello world" program prints out the
|
||||
message if executed as `./hello`, but it doesn't output anything if
|
||||
executed as `./hello | cat`.
|
||||
|
||||
Investigation: I knew that the default buffering mode for stdout is
|
||||
line buffering (buffer is flushed on every '\n'), but if it is not
|
||||
connected to the terminal (i.e. `isatty(2)` returns 0 on
|
||||
`STDOUT_FILENO`), it automatically switches to full buffering (buffer
|
||||
is flushed when it becomes full). So, it looks like libc failed to
|
||||
flush the stdout on program exit for some reason.
|
||||
|
||||
I traced all function calls using gdb and noticed that `__libc_atexit`
|
||||
was not called. That function seemed to be responsible for buffer
|
||||
flushing. I don't know how exactly I found the root cause, but after
|
||||
spending an hour or two, I found that `__start___libc_atexit` and
|
||||
`__stop___libc_atexit` have value 0 in my linker's output while they
|
||||
mark a section containing the address of `__libc_atexit` in GNU ld's
|
||||
output.
|
||||
|
||||
So, libc doesn't directly call `__libc_atexit` but instead call all
|
||||
function pointers between `__start___libc_atexit` and
|
||||
`__stop___libc_atexit` symbols. libc puts `__libc_atexit` address in
|
||||
`_libc_atexit` section, expecting that the linker automatically
|
||||
creates the start and the end marker symbols for the section.
|
||||
|
||||
There's an obscure linker feature: if a section name is valid as a C
|
||||
identifier (e.g. `foo` or `_foo_bar` but not `.foo`), the linker
|
||||
automatically creates marker symbols by prepending `__start_` and
|
||||
`__stop_` to the section name. My linker lacked the feature.
|
||||
|
||||
I implemented the feature, and the bug was fixed.
|
||||
|
||||
## TLS variable initialization
|
||||
|
||||
Problem: A statically-linked "hello world" program crashes after
|
||||
reading a thread-local variable.
|
||||
|
||||
Investigation: Thread-local variables are very different from other
|
||||
types of varaibles because there may be more than one instance of the
|
||||
same variable in memory. Each thread has its copy of thread-local
|
||||
varaibles. `%fs` segment register points the end of the variable area
|
||||
for the current thread, and the variables are accessed as an offset
|
||||
from `%fs`.
|
||||
|
||||
Thread-local variables may be initialized (e.g. `thread_local int x =
|
||||
5;`). The linker gathers all thread-local variables and put them into
|
||||
`PT_TLS` segment. At runtime, the contents of the segment is used as
|
||||
an "initialization image" for new threads. When a new thread is
|
||||
created, the image is memcpy'ed to the new thread's thread-local
|
||||
variable area. The initialization image itself is read-only at
|
||||
runtime.
|
||||
|
||||
It took more than a day to find out that memcpy copies the
|
||||
initialization image to a different place than the thread-local
|
||||
variables reside. That means, thread-local variables had garbage as
|
||||
initial values, and the program crashed when using them.
|
||||
|
||||
The problem is that I set a very large value (4096) to the alignment
|
||||
of `PT_TLS` segment. All `PT_LOAD` segments are naturally aligned to
|
||||
the page boundary, so I use the same value for `PT_TLS`, but that was
|
||||
a mistake. When a thread initialization routine sets a value to `%fs`,
|
||||
it first aligns the end of the thread-local variable area address to
|
||||
`PT_TLS` alignment value. So, if you set a large value to `PT_TLS`
|
||||
alignment, `%fs` is set to a wrong place.
|
||||
|
||||
I fixed `PT_TLS` alignment, and the bug was gone.
|
||||
|
||||
## stdio buffering (another issue)
|
||||
|
||||
I noticed that a dynamically-linked "hello world" program didn't
|
||||
flush its stdout buffer on exit. The cause of the problem was that the
|
||||
executable had more than one DT_NEEDED entry for `libc.so`.
|
||||
|
||||
DT_NEEDED entries in `.dynamic` section specify a list of shared
|
||||
object file names which need to be linked at runtime. I added one
|
||||
DT_NEEDED entry for each library specified with the `-l` option.
|
||||
The pitfall is, unlike object files, libraries are allowed to
|
||||
appear more than once in a command line, and the linker has to
|
||||
de-duplicate them before processing. Adding more than one DT_NEEDED
|
||||
entry for the same shared object causes mysterious issues like this.
|
||||
|
||||
# Copy relocations and symbol aliases
|
||||
|
||||
environ, _environ and __environ point to the same location in libc.so,
|
||||
so when we create a copy relocation for one of the symbols, we need to
|
||||
do that for all of them. Otherwise, they'll end up pointing to different
|
||||
places which causes a very mysterious issue.
|
||||
|
||||
# DT_DEBUG and gdb
|
||||
|
||||
If you forget to add an entry with type DT_DEBUG to .dynamic, gdb's
|
||||
`info sharedlibrary` command doesn't print out a list of shared
|
||||
libraries loaded to memory. The value of the entry doesn't matter, so
|
||||
it can be just zero. The existence of it is important.
|
||||
|
||||
#
|
||||
|
||||
__EH_FRAME_BEGIN__ in libgcc/crtstuff.c
|
||||
@@ -1,46 +0,0 @@
|
||||
# Coding Guidelines
|
||||
|
||||
mold is written in C++20, but as is the case with every C++ project,
|
||||
it has local coding rules. In this document, I'll explain some of them
|
||||
and try to give justifications for why I chose such rules.
|
||||
|
||||
## DOs
|
||||
|
||||
- Always use `i64` (which is a type alias for `int64_t` in mold) for
|
||||
integers unless you have a reason not to. For example, even if you know
|
||||
that a loop counter won't exceed 100, you should stop thinking about it
|
||||
and just use `i64`.
|
||||
|
||||
Justification: Local variables are usually on CPU registers, so on
|
||||
64-bit CPUs, there's no performance peanlty on choosing `i64` over
|
||||
`i32`. Even if a compiler has to spill register values to the stack,
|
||||
I don't think there's an observable difference between `i32` and
|
||||
`i64`. Therefore, extra 32 bits are essentially free. On 32-bit CPUs,
|
||||
they are not free, but that's OK because we are writing mold for modern
|
||||
computers. mold will still run on 32-bit computers but a bit slowly.
|
||||
|
||||
By always using `i64`, we can eliminate the need to think about the
|
||||
"right" size for each variable. It also reduces the risk of integer
|
||||
overflow.
|
||||
|
||||
Exceptions: If you have to allocate a very large number (e.g. millions)
|
||||
of the same object, its size matters. In that case, use a smaller type.
|
||||
|
||||
## DON'Ts
|
||||
|
||||
- Don't use `auto` unless its actual type is obvious in the very narrow
|
||||
context. Currently, we use `auto` only for lambdas.
|
||||
|
||||
Justification: I think `auto` makes code writing easier but code reading
|
||||
harder, because readers have to make a guess as to what is the actual
|
||||
type of `auto`. If you are already familiar with the existing codebase,
|
||||
you may be able to guess it easily, but that's not always the case.
|
||||
I want to keep the mold codebase friendly to first-time readers.
|
||||
|
||||
- Don't over-use inheritance. In mold, most classes don't have parents,
|
||||
and even if they do, their class hierarchy is very shallow. Currently
|
||||
its height is just two (i.e. abstract classes and their implementations).
|
||||
|
||||
Justification: Designing class hierarchies is fun as it feels like
|
||||
taxonomy, but I don't think that always help writing code. It looks like
|
||||
simpler class hierarchy makes its code simpler.
|
||||
-491
@@ -1,491 +0,0 @@
|
||||
[This document was written in 2020, and the contents are outdated.
|
||||
Specifically, we no longer believe that object preloading is a good
|
||||
idea. That being said, most of the points in this document still hold
|
||||
even today. Therefore, I'll keep this document as-is.]
|
||||
|
||||
## Design and implementation of mold
|
||||
|
||||
For the rest of this documentation, I'll explain the design and the
|
||||
implementation of mold. If you are only interested in using mold, you
|
||||
don't need to read the below.
|
||||
|
||||
### Motivation
|
||||
|
||||
Here is why I'm writing a new linker:
|
||||
|
||||
- Even though lld has significantly improved the situation, linking is
|
||||
still one of the slowest steps in a build. It is especially
|
||||
annoying when I changed one line of code and had to wait for a few
|
||||
seconds or even more for a linker to complete. It should be
|
||||
instantaneous. There's a need for a faster linker.
|
||||
|
||||
- The number of cores on a PC has increased a lot lately, and this
|
||||
trend is expected to continue. However, the existing linkers can't
|
||||
take the advantage of the trend because they don't scale well for more
|
||||
cores. I have a 64-core/128-thread machine, so my goal is to create
|
||||
a linker that uses the CPU nicely. mold should be much faster than
|
||||
other linkers on 4 or 8-core machines too, though.
|
||||
|
||||
- It looks to me that the designs of the existing linkers are somewhat
|
||||
too similar, and I believe there are a lot of drastically different
|
||||
designs that haven't been explored yet. Developers generally don't
|
||||
care about linkers as long as they work correctly, and they don't
|
||||
even think about creating a new one. So there may be lots of low
|
||||
hanging fruits there in this area.
|
||||
|
||||
### Basic design
|
||||
|
||||
- In order to achieve a `cp`-like performance, the most important
|
||||
thing is to fix the layout of an output file as quickly as possible, so
|
||||
that we can start copying actual data from input object files to an
|
||||
output file as soon as possible.
|
||||
|
||||
- Copying data from input files to an output file is I/O-bounded, so
|
||||
there should be room for doing computationally-intensive tasks while
|
||||
copying data from one file to another.
|
||||
|
||||
- We should allow the linker to preload object files from disk and
|
||||
parse them in memory before a complete set of input object files
|
||||
is ready. To do so, we need
|
||||
to split the linker into two in such a way that the latter half of
|
||||
the process finishes as quickly as possible by speculatively parsing
|
||||
and preprocessing input files in the first half of the process.
|
||||
|
||||
- One of the most computationally-intensive stage among linker stages
|
||||
is symbol resolution. To resolve symbols, we basically have to throw
|
||||
all symbol strings into a hash table to match undefined symbols with
|
||||
defined symbols. But this can be done in the preloading stage using
|
||||
[string interning](https://en.wikipedia.org/wiki/String_interning).
|
||||
|
||||
- Object files may contain a special section called a mergeable string
|
||||
section. The section contains lots of null-terminated strings, and
|
||||
the linker is expected to gather all mergeable string sections and
|
||||
merge their contents. So, if two object files contain the same
|
||||
string literal, for example, the resulting output will contain a
|
||||
single merged string. This step is computationally intensive, but string
|
||||
merging can be done in the preloading stage using string interning.
|
||||
|
||||
- Static archives (.a files) contain object files, but the static
|
||||
archive's string table contains only defined symbols of member
|
||||
object files and lacks other types of symbols. That makes static
|
||||
archives unsuitable for speculative parsing. Therefore, the linker
|
||||
should ignore the symbol table of static archive and directly read
|
||||
static archive members.
|
||||
|
||||
- If there's a relocation that uses a GOT of a symbol, then we have to
|
||||
create a GOT entry for that symbol. Otherwise, we shouldn't. That
|
||||
means we need to scan all relocation tables to fix the length and
|
||||
the contents of a .got section. This is computationally intensive,
|
||||
but this step is parallelizable.
|
||||
|
||||
### Linker Script
|
||||
|
||||
Linker script is an embedded language for the linker. It is mainly
|
||||
used to control how input sections are mapped to output sections and
|
||||
the layout of the output, but it can also do a lot of tricky stuff.
|
||||
Its feature is useful especially for embedded programming, but it's
|
||||
also an awfully underdocumented and complex language.
|
||||
|
||||
We have to implement a subset of the linker script language anwyay,
|
||||
because on Linux, /usr/lib/x86_64-linux-gnu/libc.so is (despite its
|
||||
name) not a shared object file but actually an ASCII file containing
|
||||
linker script code to load the _actual_ libc.so file. But the feature
|
||||
set for this purpose is very limited, and it is okay to implement them
|
||||
to mold.
|
||||
|
||||
Besides that, we really don't want to implement the linker script
|
||||
language. But at the same time, we want to satisfy the user needs that
|
||||
are currently satisfied with the linker script language. So, what
|
||||
should we do? Here is my observation:
|
||||
|
||||
- Linker script allows doing a lot of tricky stuff, such as specifying
|
||||
the exact layout of a file, inserting arbitrary bytes between
|
||||
sections, etc. But most of them can be done with a post-link binary
|
||||
editing tool (such as `objcopy`).
|
||||
|
||||
- It looks like there are two things that truly cannot be done by a
|
||||
post-link editing tool: (a) mapping input sections to output
|
||||
sections, and (b) applying relocations.
|
||||
|
||||
From the above observation, I believe we need to provide only the
|
||||
following features instead of the entire linker script language:
|
||||
|
||||
- A method to specify how input sections are mapped to output
|
||||
sections, and
|
||||
|
||||
- a method to set addresses to output sections, so that relocations
|
||||
are applied based on desired addresses.
|
||||
|
||||
I believe everything else can be done with a post-link binary editing
|
||||
tool.
|
||||
|
||||
### Details
|
||||
|
||||
- As we aim to the 1-second goal for Chromium, every millisecond
|
||||
counts. We can't ignore the latency of process exit. If we mmap a
|
||||
lot of files, \_exit(2) is not instantaneous but takes a few hundred
|
||||
milliseconds because the kernel has to clean up a lot of
|
||||
resources. As a workaround, we should organize the linker command as
|
||||
two processes; the first process forks the second process, and the
|
||||
second process does the actual work. As soon as the second process
|
||||
writes a result file to a filesystem, it notifies the first process,
|
||||
and the first process exits. The second process can take time to
|
||||
exit, because it is not an interactive process.
|
||||
|
||||
- At least on Linux, it looks like the filesystem's performance to
|
||||
allocate new blocks to a new file is the limiting factor when
|
||||
creating a new large file and filling its contents using mmap.
|
||||
If you already have a large file in the buffer cache, writing to it is
|
||||
much faster than creating a new fresh file and writing to it.
|
||||
Based on this observation, mold overwrites to an existing
|
||||
executable file if exists. My quick benchmark showed that I could
|
||||
save 300 milliseconds when creating a 2 GiB output file.
|
||||
Linux doesn't allow to open an executable for writing if it is
|
||||
running (you'll get a "text busy" error if you attempt). mold
|
||||
falls back to the usual way if it fails to open an output file.
|
||||
|
||||
- The output from the linker should be deterministic for the sake of
|
||||
[build reproducibility](https://en.wikipedia.org/wiki/Reproducible_builds)
|
||||
and ease of debugging. This might add a little bit of overhead to
|
||||
the linker, but that shouldn't be too much.
|
||||
|
||||
- A .build-id, a unique ID embedded to an output file, is usually
|
||||
computed by applying a cryptographic hash function (e.g. SHA-1) to
|
||||
an output file. This is a slow step, but we can speed it up by
|
||||
splitting a file into small chunks, computing SHA-1 for each chunk,
|
||||
and then computing SHA-1 of the concatenated SHA-1 hashes
|
||||
(i.e. constructing a [Merkle
|
||||
Tree](https://en.wikipedia.org/wiki/Merkle_tree) of height 2).
|
||||
Modern x86 processors have purpose-built instructions for SHA-1 and
|
||||
can compute SHA-1 pretty quickly at about 2 GiB/s. Using 16
|
||||
cores, a build-id for a 2 GiB executable can be computed in 60 to 70
|
||||
milliseconds.
|
||||
|
||||
- BFD, gold, and lld support section garbage collection. That is, a
|
||||
linker runs a mark-sweep garbage collection on an input graph, where
|
||||
sections are vertices and relocations are edges, to discard all
|
||||
sections that are not reachable from the entry point symbol
|
||||
(i.e. `_start`) or a few other root sections. In mold, we are using
|
||||
multiple threads to mark sections concurrently.
|
||||
|
||||
- Similarly, BFD, gold an lld support Identical Comdat Folding (ICF)
|
||||
as yet another size optimization. ICF merges two or more read-only
|
||||
sections that happen to have the same contents and relocations.
|
||||
To do that, we have to find isomorphic subgraphs from larger graphs.
|
||||
I implemented a new algorithm for mold, which is 5x faster than lld
|
||||
to do ICF for Chromium (from 5 seconds to 1 second).
|
||||
|
||||
- [Intel Threading Building
|
||||
Blocks](https://github.com/oneapi-src/oneTBB) (TBB) is a good
|
||||
library for parallel execution and has several concurrent
|
||||
containers. We are particularly interested in using
|
||||
`parallel_for_each` and `concurrent_hash_map`.
|
||||
|
||||
- TBB provides `tbbmalloc` which works better for multi-threaded
|
||||
applications than the glib'c malloc, but it looks like
|
||||
[jemalloc](https://github.com/jemalloc/jemalloc) and
|
||||
[mimalloc](https://github.com/microsoft/mimalloc) are a little bit
|
||||
more scalable than `tbbmalloc`.
|
||||
|
||||
### Size of the problem
|
||||
|
||||
When linking Chrome, a linker reads 3,430,966,844 bytes of data in
|
||||
total. The data contains the following items:
|
||||
|
||||
| Data item | Number
|
||||
| ------------------------ | ------
|
||||
| Object files | 30,723
|
||||
| Public undefined symbols | 1,428,149
|
||||
| Mergeable strings | 1,579,996
|
||||
| Comdat groups | 9,914,510
|
||||
| Regular sections¹ | 10,345,314
|
||||
| Public defined symbols | 10,512,135
|
||||
| Symbols | 23,953,607
|
||||
| Sections | 27,543,225
|
||||
| Relocations against SHF_ALLOC sections | 39,496,375
|
||||
| Relocations | 62,024,719
|
||||
|
||||
¹ Sections that have to be copied from input object files to an
|
||||
output file. Sections that contain relocations or symbols are for
|
||||
example excluded.
|
||||
|
||||
### Internals
|
||||
|
||||
In this section, I'll explain the internals of mold linker.
|
||||
|
||||
#### A brief history of Unix and the Unix linker
|
||||
|
||||
Conceptually, what a linker does is pretty simple. A compiler compiles
|
||||
a fragment of a program (a single source file) into a fragment of
|
||||
machine code and data (an object file, which typically has the .o
|
||||
extension), and a linker stitches them together into a single
|
||||
executable or a shared library image.
|
||||
|
||||
In reality, modern linkers for Unix-like systems are much more
|
||||
complicated than the naive understanding because they have gradually
|
||||
gained one feature at a time over the 50 years history of Unix, and
|
||||
they are now something like a bag of lots of miscellaneous features in
|
||||
which none of the features is more important than the others. It is
|
||||
very easy to miss the forest for the trees, since for those who don't
|
||||
know the details of the Unix linker, it is not clear which feature is
|
||||
essential and which is not.
|
||||
|
||||
That being said, one thing is clear that at any point of Unix history,
|
||||
a Unix linker has a coherent feature set for the Unix of that age. So,
|
||||
let me entangle the history to see how the operating system, runtime,
|
||||
and linker have gained features that we see today. That should give
|
||||
you an idea of why a particular feature has been added to a linker in the
|
||||
first place.
|
||||
|
||||
1. Original Unix didn't support shared libraries, and a program was
|
||||
always loaded to a fixed address. An executable was something like
|
||||
a memory dump that was just loaded to a particular address by the
|
||||
kernel. After loading, the kernel started executing the program by
|
||||
setting the instruction pointer to a particular address.
|
||||
|
||||
The most essential feature for any linker is relocation processing.
|
||||
The original Unix linker of course supported that. Let me explain
|
||||
what that is.
|
||||
|
||||
Individual object files are inevitably incomplete as a program,
|
||||
because when a compiler created them, it only see a part of an
|
||||
entire program. For example, if an object file contains a function
|
||||
call that refers to another object file, the `call` instruction in the
|
||||
object cannot be complete, as the compiler has no idea as to what
|
||||
is the called function's address. To deal with this, the compiler
|
||||
emits a placeholder value (typically just zero) instead of a real
|
||||
address and leaves metadata in an object file saying "fix offset X
|
||||
of this file with an address of Y". That metadata is called
|
||||
"relocation". Relocations are typically processed by the linker.
|
||||
|
||||
It is easy for a linker to apply relocations for the original Unix
|
||||
because a program is always loaded to a fixed address. It exactly
|
||||
knows the addresses of all functions and data when linking a
|
||||
program.
|
||||
|
||||
Static library support, which is still an important feature of Unix
|
||||
linker, also dates back to this early period of Unix history.
|
||||
To understand what it is, imagine that you are trying to compile
|
||||
a program for the early Unix. You don't want to waste time to
|
||||
compile libc functions every time you compile your program (the
|
||||
computers of the era were incredibly slow), so you have already
|
||||
placed each libc function into a separate source file and compiled
|
||||
them individually. That means you have object files for each libc
|
||||
function, e.g., printf.o, scanf.o, atoi.o, write.o, etc.
|
||||
|
||||
Given this configuration, all you have to do to link your program
|
||||
against libc functions is to pick up the right set of libc object
|
||||
files and give them to the linker along with the object files of your
|
||||
program. But, keeping the linker command line in sync with the
|
||||
libc functions you are using in your program is bothersome. You can
|
||||
be conservative; you can specify all libc object files to the
|
||||
command line, but that leads to program bloat because the linker
|
||||
unconditionally link all object files given to it no matter whether
|
||||
they are used or not. So, a new feature was added to the linker to
|
||||
fix the problem. That is the static library, which is also called
|
||||
the archive file.
|
||||
|
||||
An archive file is just a bundle of object files, just like zip
|
||||
file but in an uncompressed form. An archive file typically has the
|
||||
.a file extension and named after its contents. For example, the
|
||||
archive file containing all libc objects is named `libc.a`.
|
||||
|
||||
If you pass an archive file along with other object files to the
|
||||
linker, the linker pulls out an object file from the archive _only
|
||||
when_ it is referenced by other object files. In other words,
|
||||
unlike object files directly given to a linker, object files
|
||||
wrapped in an archive are not linked to the output by default.
|
||||
An archive works as a supplement to complete your program.
|
||||
|
||||
Even today, you can still find a libc archive file. Run `ar t
|
||||
/usr/lib/x86_64-linux-gnu/libc.a` on Linux should give you a list
|
||||
of object files in the libc archive.
|
||||
|
||||
2. In the '80s, Sun Microsystems, a leading commercial Unix vendor at the
|
||||
time, added shared library support to their Unix variant, SunOS.
|
||||
|
||||
(This section is incomplete.)
|
||||
|
||||
### Concurrency strategy
|
||||
|
||||
In this section, I'll explain the high-level concurrency strategy of
|
||||
mold.
|
||||
|
||||
In most places, mold adopts data parallelism. That is, we have a huge
|
||||
number of pieces of data of the same kind, and we process each of them
|
||||
individually using parallel for-loop. For example, after identifying
|
||||
the exact set of input object files, we need to scan all relocation
|
||||
tables to determine the sizes of .got and .plt sections. We do that
|
||||
using a parallel for-loop. The granularity of parallel processing in
|
||||
this case is the relocation table.
|
||||
|
||||
Data parallelism is very efficient and scalable because there's no
|
||||
need for threads to communicate with each other while working on each
|
||||
element of data. In addition to that, data parallelism is easy to
|
||||
understand, as it is just a for-loop in which multiple iterations may
|
||||
be executed in parallel. We don't use high-level communication or
|
||||
synchronization mechanisms such as channels, futures, promises,
|
||||
latches or something like that in mold.
|
||||
|
||||
In some cases, we need to share a little bit of data between threads
|
||||
while executing a parallel for-loop. For example, the loop to scan
|
||||
relocations turns on "requires GOT" or "requires PLT" flags in a
|
||||
symbol. Symbol is a shared resource, and writing to them from multiple
|
||||
threads without synchronization is unsafe. To deal with it, we made
|
||||
the flag an atomic variable.
|
||||
|
||||
The other common pattern you can find in mold which is build on top of
|
||||
the parallel for-loop is the map-reduce pattern. That is, we run a
|
||||
parallel for-loop on a large data set to produce a small data set and
|
||||
process the small data set with a single thread. Let me take a
|
||||
build-id computation as an example. Build-id is typically computed by
|
||||
applying a cryptographic hash function such as SHA-1 on a linker's
|
||||
output file. To compute it, we first consider an output as a sequence
|
||||
of 1 MiB blocks and compute a SHA-1 hash for each block in parallel.
|
||||
Then, we concatenate the SHA-1 hashes and compute a SHA-1 hash on the
|
||||
hashes to get a final build-id.
|
||||
|
||||
Finally, we use concurrent hashmap at a few places in mold. Concurrent
|
||||
hashmap is a hashmap to which multiple threads can safely insert items
|
||||
in parallel. We use it in the symbol resolution stage, for example.
|
||||
To resolve symbols, we basically have to throw in all defined symbols
|
||||
into a hash table, so that we can find a matching defined symbol for
|
||||
an undefined symbol by name. We do the hash table insertion from a
|
||||
parallel for-loop which iterates over a list of input files.
|
||||
|
||||
Overall, even though mold is highly scalable, it succeeded to avoid
|
||||
complexties you often find in complex parallel programs. From high
|
||||
level, mold just serially executes the linker's internal passes one by
|
||||
one. Each pass is parallelized using parallel for-loops.
|
||||
|
||||
### Rejected ideas
|
||||
|
||||
In this section, I'll explain the alternative designs I currently do
|
||||
not plan to implement and why I turned them down.
|
||||
|
||||
- Placing variable-length sections at end of an output file and start
|
||||
copying file contents before fixing the output file layout
|
||||
|
||||
Idea: Fixing the layout of regular sections seems easy, and if we
|
||||
place them at beginning of a file, we can start copying their
|
||||
contents from their input files to an output file. While copying
|
||||
file contents, we can compute the sizes of variable-length sections
|
||||
such as .got or .plt and place them at end of the file.
|
||||
|
||||
Reason for rejection: I did not choose this design because I doubt
|
||||
if it could actually shorten link time and I think I don't need it
|
||||
anyway.
|
||||
|
||||
The linker has to de-duplicate comdat sections (i.e. inline
|
||||
functions that are included in multiple object files), so we
|
||||
cannot compute the layout of regular sections until we resolve all
|
||||
symbols and de-duplicate comdats. That takes a few hundred
|
||||
milliseconds. After that, we can compute the sizes of
|
||||
variable-length sections in less than 100 milliseconds. It's quite
|
||||
fast, so it doesn't seem to make much sense to proceed without
|
||||
fixing the final file layout.
|
||||
|
||||
The other reason to reject this idea is because there's good a
|
||||
chance for this idea to have a negative impact on linker's overall
|
||||
performance. If we copy file contents before fixing the layout, we
|
||||
can't apply relocations to them while copying because symbol
|
||||
addresses are not available yet. If we fix the file layout first, we
|
||||
can apply relocations while copying, which is effectively zero-cost
|
||||
due to a very good data locality. On the other hand, if we apply
|
||||
relocations long after we copy file contents, it's pretty expensive
|
||||
because section contents are very likely to have been evicted from
|
||||
CPU cache.
|
||||
|
||||
- Incremental linking
|
||||
|
||||
Idea: Incremental linking is a technique to patch a previous linker's
|
||||
output file so that only functions or data that are updated from the
|
||||
previous build are written to it. It is expected to significantly
|
||||
reduce the amount of data copied from input files to an output file
|
||||
and thus speed up linking. GNU BFD and gold linkers support it.
|
||||
|
||||
Reason for rejection: I turned it down because it (1) is
|
||||
complicated, (2) doesn't seem to speed it up that much and (3) has
|
||||
several practical issues. Let me explain each of them.
|
||||
|
||||
First, incremental linking for real C/C++ programs is not as easy as
|
||||
one might think. Let me take malloc as an example. malloc is usually
|
||||
defined by libc, but you can implement it in your program, and if
|
||||
that's the case, the symbol `malloc` will be resolved to your
|
||||
function instead of the one in libc. If you include a library that
|
||||
defines malloc (such as libjemalloc or libtbbmallc) before libc,
|
||||
their malloc will override libc's malloc.
|
||||
|
||||
Assume that you are using a nonstandard malloc. What if you remove
|
||||
your malloc from your code, or remove `-ljemalloc` from your
|
||||
Makefile? The linker has to include a malloc from libc, which may
|
||||
include more object files to satisfy its dependencies. Such code
|
||||
change can affect the entire program rather than just replacing one
|
||||
function. The same is true for adding malloc to your program. Making
|
||||
a local change doesn't necessarily result in a local change in the
|
||||
binary level. It can easily have cascading effects.
|
||||
|
||||
Some ELF fancy features make incremental linking even harder to
|
||||
implement. Take the weak symbol as an example. If you define `atoi`
|
||||
as a weak symbol in your program, and if you are not using `atoi`
|
||||
at all in your program, that symbol will be resolved to address
|
||||
0. But if you start using some libc function that indirectly calls
|
||||
`atoi`, then `atoi` will be included in your program, and your weak
|
||||
symbol will be resolved to that function. I don't know how to
|
||||
efficiently fix up a binary for this case.
|
||||
|
||||
This is a hard problem, so existing linkers don't try too hard to
|
||||
solve it. For example, IIRC, gold falls back to full link if any
|
||||
function is removed from a previous build. If you want to not annoy
|
||||
users in the fallback case, you need to make full link fast anyway.
|
||||
|
||||
Second, incremental linking itself has an overhead. It has to detect
|
||||
updated files, patch an existing output file and write additional
|
||||
data to an output file for future incremental linking. GNU gold, for
|
||||
instance, takes almost 30 seconds on my machine to do a null
|
||||
incremental link (i.e. no object files are updated from a previous
|
||||
build) for chrome. It's just too slow.
|
||||
|
||||
Third, there are other practical issues in incremental linking. It's
|
||||
not reproducible, so your binary isn't going to be the same as other
|
||||
binaries even if you are compiling the same source tree using the
|
||||
same compiler toolchain. Or, it is complex and there might be a bug
|
||||
in it. If something doesn't work correctly, "remove --incremental
|
||||
from your Makefile and try again" could be a piece of advice, but
|
||||
that isn't ideal.
|
||||
|
||||
So, all in all, incremental linking is tricky. I wanted to make full
|
||||
link as fast as possible, so that we don't have to think about how
|
||||
to work around the slowness of full link.
|
||||
|
||||
- Defining a completely new file format and use it
|
||||
|
||||
Idea: Sometimes, the ELF file format itself seems to be a limiting
|
||||
factor in improving the linker's performance. We might be able to make a
|
||||
far better one if we create a new file format.
|
||||
|
||||
Reason for rejection: I rejected the idea because it apparently has
|
||||
a practical issue (backward compatibility issue) and also doesn't
|
||||
seem to improve the performance of linkers that much. As clearly
|
||||
demonstrated by mold, we can create a fast linker for ELF. I believe
|
||||
ELF isn't that bad, after all. The semantics of the existing Unix
|
||||
linkers, such as the name resolution algorithm or the linker script,
|
||||
have slowed the linkers down, but that's not a problem of the file
|
||||
format itself.
|
||||
|
||||
- Watching object files using inotify(2)
|
||||
|
||||
Idea: When mold is running as a daemon for preloading, use
|
||||
inotify(2) to watch file system updates so that it can reload files
|
||||
as soon as they are updated.
|
||||
|
||||
Reason for rejection: Just like the maximum number of files you can
|
||||
simultaneously open, the maximum number of files you can watch using
|
||||
inotify(2) isn't that large. Maybe just a single instance of mold is
|
||||
fine with inotify(2), but it may fail if you run multiple of it.
|
||||
|
||||
The other reason for not doing it is because mold is quite fast
|
||||
without it anyway. Invoking stat(2) on each file for file update
|
||||
check takes less than 100 milliseconds for Chrome, and if most of
|
||||
the input files are not updated, parsing updated files takes almost
|
||||
no time.
|
||||
@@ -1,55 +0,0 @@
|
||||
This page explains the following warning message and how to fix it.
|
||||
mold emits this message when it sees an object file that may not be
|
||||
compatible with mold.
|
||||
|
||||
|
||||
```
|
||||
mold: warning: foo.o: this file may cause a segmentation fault because it requires an executable stack. See https://github.com/rui314/mold/tree/main/docs/execstack.md for more info.
|
||||
```
|
||||
|
||||
# Background
|
||||
|
||||
On modern computers, the stack area (to which local variables are
|
||||
stored) cannot contain executable code. If the control reaches the
|
||||
stack area, the CPU refuses to execute any code there and the program
|
||||
is usually terminated due to segmentation fault.
|
||||
|
||||
This is a security measure. The stack area used to be executable (old
|
||||
CPUs generally execute any code as long as it is in a readable memory
|
||||
region), but that provided an easy attack vector to a malicious user.
|
||||
They wrote executable code to the stack area using some buffer
|
||||
overflow bug and jumped there to run arbitrary code in a remote server
|
||||
process.
|
||||
|
||||
To prevent this type of attack, the stack area is no longer executable
|
||||
since the early 2000s. On Linux, the stack's executable-ness is
|
||||
controlled by a bit in an executable, and the loader respects that
|
||||
bit. The bit is set by the linker.
|
||||
|
||||
GCC had (and still has) a feature that depends on the executable
|
||||
stack, so they invented a way to tell the linker to mark the stack
|
||||
executable. Specifically, if an object file contains a
|
||||
`.note.GNU-stack` section with the `SHF_EXECSTACK` bit, GNU linker
|
||||
silently makes the stack of an output file executable.
|
||||
|
||||
But the GNU linker's behavior is dangerous. If you accidentally link
|
||||
an object file that has that marker section, the entire stack area
|
||||
silently becomes executable, disabling the security mechanism.
|
||||
|
||||
Therefore, mold simply ignores that marker section. If you are using
|
||||
mold, you need to explicitly pass `-z execstack` to the linker to make
|
||||
the stack executable.
|
||||
|
||||
# What caused this issue?
|
||||
|
||||
You are likely to use GCC's [Nested
|
||||
Functions](https://gcc.gnu.org/onlinedocs/gcc/Nested-Functions.html)
|
||||
feature which still depends on the executable stack.
|
||||
|
||||
# How to fix it?
|
||||
|
||||
If you know what you are doing, pass `-z execstack` to mold. Beware
|
||||
that this will significantly weaken your program's security.
|
||||
|
||||
If you don't want to pass `-z execstack`, rewrite your code so that
|
||||
your code does not depend on the executable stack.
|
||||
@@ -1,158 +0,0 @@
|
||||
The very concept of linking is simple: a compiler compiles a piece of
|
||||
source code into an object file (a file containing machine code), and
|
||||
a linker combines object files into a single executable or a shared
|
||||
library file. However, the actual implementation of the linker for
|
||||
modern systems is much more complicated because hardware, operating
|
||||
system, compiler and linker all have many more features.
|
||||
|
||||
In this file, I'll explain random topics in the glossary format that
|
||||
you need to understand to read mold code.
|
||||
|
||||
## DSO
|
||||
|
||||
A .so file. Short for Dynamic Shared Object. Often called as a
|
||||
shared library, a dynamic libray or a shared object as well.
|
||||
|
||||
An DSO contains common functions and data that are used by multiple
|
||||
executables and/or other DSOs. At runtime, a DSO is loaded to a
|
||||
contiguous region in the virtual address.
|
||||
|
||||
## Object file
|
||||
|
||||
A .o file. An object file contains machine code and data, but it
|
||||
cannot be executed because it's not self-contained. For example,
|
||||
if you compile a C source file containing a call of `printf`,
|
||||
the actual function code of `printf` is not included in the resulting
|
||||
object file. You include `stdio.h`, but that teaches the compiler
|
||||
only about `printf`'s type, and the compiler still don't know what
|
||||
`printf` actually does. Therefore, it cannot emit code for `printf`.
|
||||
|
||||
You need to link an object file with other object file or a shared
|
||||
library to make it exectuable.
|
||||
|
||||
## Virtual address space
|
||||
|
||||
A pointer has a value like 0x803020 which is an address of the
|
||||
pointee. But it doesn't mean that the pointee resides at the
|
||||
physical memory address 0x803020 on the computer. Modern CPUs
|
||||
contains so-called Mmeory Management Unit (MMU), and all access to
|
||||
the memory are first translated by MMU to the physical address.
|
||||
The address before translation is called the "virtual address".
|
||||
Unless you are doing the kernel programming, all addresses you
|
||||
handle are virtual addresses.
|
||||
|
||||
The OS kernel controls the MMU so that each process owns the entire
|
||||
virtual address space. So, even if two process uses the same virtual
|
||||
address, they don't conflict. They are mapped to different physical
|
||||
addresses.
|
||||
|
||||
The existence of MMU has several implications to the linker. First,
|
||||
we can link the main executable to a specific address. On process
|
||||
startup, there's no code or data in the virtual address space, so
|
||||
the mapping of the main executable always succeed. However, it's not
|
||||
true to DSOs because they are loaded after the main executable and
|
||||
possibly other DSOs. Therefore, shared libraries must be linked in a
|
||||
way that they can be loaded to any address in the virtual address
|
||||
space.
|
||||
|
||||
## Relocation
|
||||
|
||||
A piece of information for the linker as to how to link object files
|
||||
or a dynamic objects.
|
||||
|
||||
Object files can refer functions or data in other object files. For
|
||||
example, if you compile a function which calls a non-local function
|
||||
`foo`, the resulting code contains something like this:
|
||||
|
||||
```
|
||||
26: e8 00 00 00 00 callq 2b <bar+0xb>
|
||||
27: R_X86_64_PLT32 foo-0x4
|
||||
```
|
||||
|
||||
The above `callq` is the instruction to call a function at the
|
||||
machine code level. It's opcode is `0xe8` in x86-64, so the
|
||||
instruction begins with `0xe8`. The following four bytes are
|
||||
displacement; that is, the address of the branch target relative to
|
||||
the end of this `callq` instruction. Notice that the displacement is
|
||||
0. The compiler couldn't fill the displacement because it has no
|
||||
idea as to where `foo` will be at runtime. So, the compiler write 0
|
||||
as a placeholder and instead write a relocation `R_X86_64_PLT32`
|
||||
with `foo` as its associated symbol. The linker reads this
|
||||
relocation, computes the offsets between this call instruction and
|
||||
function `foo` and overwrite the placeholder value 0 with an actual
|
||||
displacement.
|
||||
|
||||
There are many different types of relocations. For example, if you
|
||||
want to fix up not with a displacement but with an absolute address
|
||||
of a symbol, you need to use `R_X86_64_ABS64` instead.
|
||||
|
||||
## Static library
|
||||
|
||||
A .a file. Often called as an archive file or just archive as well.
|
||||
|
||||
A static library is a container just like tar or zip. Actually,
|
||||
there's no technical reason to not use tar or (uncompressed) zip,
|
||||
but traditionally the .a file format is used by the linker.
|
||||
|
||||
A static library contains object files and can be passed to the
|
||||
linker along with other object files and/or archives.
|
||||
|
||||
A linker pulls out object files from an archive only if it is needed
|
||||
to resolve undefined symbols. In other words, object files in an
|
||||
archive are not linked by default and used as a complement to supply
|
||||
missing definitions. This is ideal for a library because you don't
|
||||
want to link library functions unless you are actually using them.
|
||||
|
||||
Contrary to archive files, object files directly given to a linker
|
||||
are always linked to the output.
|
||||
|
||||
To maximize the benefit of archive files, a library often used as a
|
||||
static library is broken down to small files to separate each
|
||||
function individually (for example, look at
|
||||
https://git.musl-libc.org/cgit/musl/tree/src/stdio). By doing this,
|
||||
you import only used functions.
|
||||
|
||||
A static file is created by `ar`, whose command line arguments are
|
||||
similar to `tar`. A static library contains the symbol table which
|
||||
offers a quick way to look up an object file for a defined symbol,
|
||||
but mold does not use the static library's symbol table. mold
|
||||
doesdn't need a symbol table to exist in an archive, and if exists,
|
||||
mold just ignores it.
|
||||
|
||||
See also: DSO (dynamic library)
|
||||
|
||||
## Symbol
|
||||
|
||||
A symbol is a label assigned to a specific location in an input file
|
||||
or an output file. For example, if you define function `foo` and
|
||||
compile it, the resulting object file contains a symbol `foo`
|
||||
pointing to the beginning of the machine code for `foo`.
|
||||
|
||||
Usually, a symbol name is a function or a variable name. If an
|
||||
object is anonymous (such the one for a string literal), a compiler
|
||||
generated a unique symbol, which often starts with `.` to avoid
|
||||
conflict with user-defined symbols.
|
||||
|
||||
For C++, symbol name is a complex "mangled" name. We need to mangle
|
||||
identifiers because a simple name such as `foo` cannot be uniquely
|
||||
identify a function or a data in C++, because for example `foo` may
|
||||
be in a namespace or defined as a static member in some class. If
|
||||
`foo` is an overloaded function, we need to distinguish different
|
||||
`foo`s by its type. Therefore, C++ compiler mangles an identifier by
|
||||
appending nmaepsace names, type information and such so that
|
||||
different things get different names.
|
||||
|
||||
For example, a function `int foo(int)` in a namespace `bar` is
|
||||
mangled as `_ZN3bar3fooEi`.
|
||||
|
||||
A symbol can be either defined or undefined. A defined symbol points
|
||||
to some location in a file which may contain the function's machine
|
||||
code or the variable's initial value. An undefined symbol does not
|
||||
point to anywhere. It needs to be merged with a defined symbol with
|
||||
the same name at link-time. This merging process is called "name
|
||||
resolution".
|
||||
|
||||
For example, if your program is using `printf`, it usually contains
|
||||
`printf` as an undefined symbol. You need to link it with `libc.a`
|
||||
or `libc.so`, which contain a defined symbol of `printf`, to make a
|
||||
complete program.
|
||||
@@ -1,179 +0,0 @@
|
||||
# Instrumenting mold with MemorySanitizer
|
||||
|
||||
## Introduction
|
||||
|
||||
Per <https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo>:
|
||||
|
||||
> If you want MemorySanitizer to work properly and not produce any false
|
||||
> positives, you must ensure that all the code in your program and in
|
||||
> libraries it uses is instrumented (i.e. built with `-fsanitize=memory`).
|
||||
> In particular, you would need to link against an MSan-instrumented C++
|
||||
> standard library. We recommend to use [libc++](https://libcxx.llvm.org/)
|
||||
> for that purpose.
|
||||
|
||||
## Building instrumented libc++
|
||||
|
||||
Build an MSan-instrumented libc++ from source:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone https://github.com/llvm/llvm-project
|
||||
cd llvm-project
|
||||
cmake -S ./runtimes -B build-libcxx -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DLLVM_ENABLE_RUNTIMES="libcxx;libcxxabi" \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins
|
||||
cmake --build build-libcxx -- cxx cxxabi
|
||||
export LIBCXX="$HOME/llvm-project/build-libcxx" # for subsequent build steps
|
||||
```
|
||||
|
||||
Upon success, `./build-libcxx/{include,lib}` will contain the resulting
|
||||
headers and shared libraries.
|
||||
|
||||
## Linking mold against instrumented libc++
|
||||
|
||||
Use the `MOLD_USE_MSAN` and `MOLD_STDLIB_PREFIX` cmake variables to link
|
||||
mold against the instrumented build of libc++:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone https://github.com/rui314/mold.git
|
||||
cd mold
|
||||
cmake -B build -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DMOLD_USE_MSAN=ON \
|
||||
-DMOLD_STDLIB_PREFIX="$LIBCXX" \
|
||||
-DMOLD_USE_MIMALLOC=OFF \
|
||||
-DMOLD_USE_SYSTEM_TBB=ON
|
||||
cmake --build build
|
||||
```
|
||||
|
||||
Most of mold's tests (except those for `-flto`) should work at this
|
||||
point. Run them like normal:
|
||||
|
||||
```sh
|
||||
ctest --test-dir build
|
||||
```
|
||||
|
||||
Any resulting MemorySanitizer errors should be visible in
|
||||
`./build/Temporary/Testing/LastTest.log`.
|
||||
|
||||
## Building instrumented LTO plugin (experimental)
|
||||
|
||||
Exercising `-flto` with MemorySanitizer (and without false positives)
|
||||
requires instrumenting the transitive dependencies of `lto-unix.cc`:
|
||||
|
||||
- `libiberty.a`
|
||||
- `liblto_plugin.so`
|
||||
- `LLVMgold.so`
|
||||
|
||||
> [!NOTE]
|
||||
> This is more involved and time-consuming than building only libc++ from
|
||||
> source. The cost-benefit ratio of this additional instrumentation and
|
||||
> test coverage may be unfavorable in many cases.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The following steps are experimental and unlikely to work exactly as-is
|
||||
> under CI runners and individual developer environments. Consider the
|
||||
> following a starting point rather than a complete HOWTO.
|
||||
|
||||
Build an MSan-instrumented GNU libiberty:
|
||||
|
||||
```sh
|
||||
cd ~
|
||||
git clone [email protected]:gcc-mirror/gcc.git
|
||||
cd gcc/libiberty
|
||||
export CC=clang
|
||||
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
|
||||
export LDFLAGS="-fsanitize=memory"
|
||||
./configure
|
||||
make -j$(nproc)
|
||||
unset CC CFLAGS LDFLAGS
|
||||
```
|
||||
|
||||
Install the resulting `./libiberty.a` into the build toolchain being
|
||||
tested with mold. If the system image is ephemeral or disposable (e.g. a
|
||||
short-lived VM or container), a quick-and-dirty install could look like:
|
||||
|
||||
```sh
|
||||
sudo cp /usr/lib/libiberty.a{,.bak}
|
||||
sudo cp ./libiberty.a /usr/lib/
|
||||
```
|
||||
|
||||
Build an MSan-instrumented GCC LTO plugin library:
|
||||
|
||||
```sh
|
||||
cd ~/gcc/lto-plugin # use already-cloned repo from earlier step
|
||||
export CC=clang
|
||||
export CFLAGS="-g -Og -fsanitize=memory -fsanitize-memory-track-origins"
|
||||
export LDFLAGS="-fsanitize=memory"
|
||||
./configure --with-libiberty=/usr/lib
|
||||
make -j$(nproc)
|
||||
unset CC CFLAGS LDFLAGS
|
||||
```
|
||||
|
||||
Install the resulting `liblto_plugin.so`. With the same caveats discussed
|
||||
above, a simple install into an ephemeral environment could look like:
|
||||
|
||||
```sh
|
||||
export GCC_VERSION="$(gcc -dumpversion)"
|
||||
sudo cp /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/liblto_plugin.so{,.bak}
|
||||
sudo cp .libs/liblto_plugin.so /usr/lib/gcc/x86_64-pc-linux-gnu/$GCC_VERSION/
|
||||
```
|
||||
|
||||
Build an MSan-instrumented LLVM LTO plugin library:
|
||||
|
||||
```sh
|
||||
cd ~/llvm-project # use already-cloned repo from earlier step
|
||||
|
||||
CF="-nostdinc++ -isystem $LIBCXX/include/c++/v1"
|
||||
INCDIR="$(find /usr -name plugin-api.h -type f | head -1 | xargs dirname)"
|
||||
|
||||
function configure
|
||||
{
|
||||
cmake -S ./llvm -B build-plugin -G Ninja \
|
||||
-DLLVM_ENABLE_PROJECTS=clang \
|
||||
-DLLVM_TARGETS_TO_BUILD=X86 \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_C_COMPILER=clang \
|
||||
-DCMAKE_CXX_COMPILER=clang++ \
|
||||
-DCMAKE_C_FLAGS="$CF" \
|
||||
-DCMAKE_CXX_FLAGS="$CF" \
|
||||
-DCMAKE_EXE_LINKER_FLAGS="$1" \
|
||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
|
||||
-DLLVM_BINUTILS_INCDIR="$INCDIR" \
|
||||
-DLLVM_USE_SANITIZER=MemoryWithOrigins
|
||||
}
|
||||
|
||||
# workaround for linker issues: configure twice with different LDFLAGS
|
||||
configure "-nostdlib++ -L $LIBCXX/lib -Wl,--rpath=$LIBCXX/lib"
|
||||
configure "-nostdlib++ -L $LIBCXX/lib -lc++ -Wl,--rpath=$LIBCXX/lib"
|
||||
|
||||
cmake --build build-plugin -- LLVMgold.so
|
||||
```
|
||||
|
||||
Install the resulting `LLVMgold.so`. With the same caveats discussed
|
||||
above, a simple install into an ephemeral environment could look like:
|
||||
|
||||
```sh
|
||||
sudo cp /usr/lib/LLVMgold.so{,.bak}
|
||||
sudo cp ./build-plugin/lib/LLVMgold.so /usr/lib/
|
||||
```
|
||||
|
||||
mold's tests for `-flto` should now (mostly) work, though there do seem
|
||||
to be issues around `gcc` inferring `-flto` when not explicitly specified.
|
||||
One workaround is to use `clang` as `TEST_CC`:
|
||||
|
||||
```sh
|
||||
cd ~/mold # use already-cloned and built repo from earlier step
|
||||
TEST_CC=clang TEST_CXX=clang++ ctest --test-dir build
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- <https://github.com/google/sanitizers/wiki/MemorySanitizerBootstrappingClang>
|
||||
- <https://llvm.org/docs/GoldPlugin.html#lto-how-to-build>
|
||||
+10
-8
@@ -1,6 +1,6 @@
|
||||
.\" generated with Ronn-NG/v0.9.1
|
||||
.\" http://github.com/apjanke/ronn-ng/tree/0.9.1
|
||||
.TH "MOLD" "1" "August 2026" ""
|
||||
.TH "MOLD" "1" "October 2026" ""
|
||||
.SH "NAME"
|
||||
\fBmold\fR \- a modern linker
|
||||
.SH "SYNOPSIS"
|
||||
@@ -48,9 +48,11 @@ Whether good or bad, you should keep these semantics in mind to understand Unix
|
||||
.P
|
||||
\fBmold\fR does not have any host\-specific default settings\. This is contrary to the GNU linkers, for which some configurable values, such as system\-dependent library search paths, are hard\-coded\. \fBmold\fR depends only on its command\-line arguments\.
|
||||
.SH "OPTION NOTATIONS"
|
||||
Multi\-letter long options may precede either a single dash or double dashes, except for those starting with the letter "o"\. For historical reasons, long options beginning with "o" must precede double dashes\.
|
||||
Long options are spelled with two dashes\. Some also accept a single dash, but which ones do is somewhat arbitrary for historical reasons\. \fBmold\fR follows the conventions of other linkers for compatibility\. For example, \fB\-\-export\-dynamic\fR can be spelled \fB\-export\-dynamic\fR, while \fB\-\-execute\-only\fR requires two dashes\.
|
||||
.P
|
||||
For example, you can spell \fB\-\-as\-needed\fR as \fB\-as\-needed\fR, but \fB\-\-omagic\fR must not be spelled as \fB\-omagic\fR\. \fB\-omagic\fR will be interpreted not as \fB\-\-omagic\fR but as \fB\-o magic\fR\.
|
||||
This can cause unexpected behavior when constructing command\-line options in a script\. For example, you might use \fB"\-e$foo"\fR to set the entry point symbol to the value of \fB$foo\fR\. However, if \fB$foo\fR is \fBxport\-dynamic\fR, the argument becomes \fB\-export\-dynamic\fR and is interpreted as \fB\-\-export\-dynamic\fR\.
|
||||
.P
|
||||
To avoid such ambiguities, we recommend always using two dashes for long options and separating a single\-letter option from its argument with a space\. For example, use \fB\-e $foo\fR or \fB\-\-entry=$foo\fR to specify the entry point symbol in a script\.
|
||||
.SH "MOLD\-SPECIFIC OPTIONS"
|
||||
.TP
|
||||
\fB\-\-chroot\fR=\fIdir\fR
|
||||
@@ -515,20 +517,20 @@ If one memory page contains multiple segments, the page protection bits are set
|
||||
.IP
|
||||
\fBseparate\-loadable\-segments\fR adds paddings between segments with different attributes so that they do not share the same page\.
|
||||
.IP
|
||||
\fBseparate\-code\fR adds paddings only between executable and non\-executable segments\. This is the default\.
|
||||
\fBseparate\-code\fR adds paddings only between executable and non\-executable segments\.
|
||||
.IP
|
||||
\fBnoseparate\-code\fR does not add any paddings between segments\.
|
||||
\fBnoseparate\-code\fR does not add any paddings between segments\. This is the default\.
|
||||
.TP
|
||||
\fB\-z defs\fR, \fB\-z nodefs\fR
|
||||
\fB\-z defs\fR, \fB\-z undefs\fR
|
||||
Report undefined symbols (even with \fB\-\-shared\fR)\.
|
||||
.TP
|
||||
\fB\-z shstk\fR
|
||||
Enforce shadow stack by turning \fBGNU_PROPERTY_X86_FEATURE_1_SHSTK\fR bit in \fB\.note\.gnu\.property\fR output section\. Shadow stack is part of Intel Control\-flow Enforcement Technology (CET), which is available since Tiger Lake (2020)\.
|
||||
.TP
|
||||
\fB\-z start_stop_visibility\fR=[ \fBhidden\fR | \fBprotected\fR ]
|
||||
\fB\-z start\-stop\-visibility\fR=[ \fBhidden\fR | \fBprotected\fR ]
|
||||
If a section name is valid as a C identifier (i\.e\., it matches \fB/^[_a\-zA\-Z][_a\-zA\-Z0\-9]*$/\fR), mold creates \fB__start_SECNAME\fR and \fB__stop_SECNAME\fR symbols to mark the beginning and end of the section, where \fBSECNAME\fR is the section name\.
|
||||
.IP
|
||||
You can make these marker symbols visible from other ELF modules by passing \fB\-z start_stop_visibility=protected\fR\. Default is \fBhidden\fR\.
|
||||
You can make these marker symbols visible from other ELF modules by passing \fB\-z start\-stop\-visibility=protected\fR\. Default is \fBhidden\fR\.
|
||||
.TP
|
||||
\fB\-z text\fR, \fB\-z notext\fR, \fB\-z textoff\fR
|
||||
\fBmold\fR by default reports an error if dynamic relocations are created in read\-only sections\. If \fB\-z notext\fR or \fB\-z textoff\fR are given, \fBmold\fR creates such dynamic relocations without reporting an error\. \fB\-z text\fR restores the default behavior\.
|
||||
|
||||
+20
-11
@@ -127,13 +127,21 @@ arguments.
|
||||
|
||||
## OPTION NOTATIONS
|
||||
|
||||
Multi-letter long options may precede either a single dash or double dashes,
|
||||
except for those starting with the letter "o". For historical reasons, long
|
||||
options beginning with "o" must precede double dashes.
|
||||
Long options are spelled with two dashes. Some also accept a single dash,
|
||||
but which ones do is somewhat arbitrary for historical reasons. `mold`
|
||||
follows the conventions of other linkers for compatibility. For example,
|
||||
`--export-dynamic` can be spelled `-export-dynamic`, while `--execute-only`
|
||||
requires two dashes.
|
||||
|
||||
For example, you can spell `--as-needed` as `-as-needed`, but `--omagic` must
|
||||
not be spelled as `-omagic`. `-omagic` will be interpreted not as `--omagic`
|
||||
but as `-o magic`.
|
||||
This can cause unexpected behavior when constructing command-line options in
|
||||
a script. For example, you might use `"-e$foo"` to set the entry point symbol
|
||||
to the value of `$foo`. However, if `$foo` is `xport-dynamic`, the argument
|
||||
becomes `-export-dynamic` and is interpreted as `--export-dynamic`.
|
||||
|
||||
To avoid such ambiguities, we recommend always using two dashes for long
|
||||
options and separating a single-letter option from its argument with a
|
||||
space. For example, use `-e $foo` or `--entry=$foo` to specify the entry
|
||||
point symbol in a script.
|
||||
|
||||
## MOLD-SPECIFIC OPTIONS
|
||||
|
||||
@@ -896,11 +904,12 @@ but as `-o magic`.
|
||||
attributes so that they do not share the same page.
|
||||
|
||||
`separate-code` adds paddings only between executable and non-executable
|
||||
segments. This is the default.
|
||||
segments.
|
||||
|
||||
`noseparate-code` does not add any paddings between segments.
|
||||
`noseparate-code` does not add any paddings between segments. This is the
|
||||
default.
|
||||
|
||||
* `-z defs`, `-z nodefs`:
|
||||
* `-z defs`, `-z undefs`:
|
||||
Report undefined symbols (even with `--shared`).
|
||||
|
||||
* `-z shstk`:
|
||||
@@ -909,14 +918,14 @@ but as `-o magic`.
|
||||
Control-flow Enforcement Technology (CET), which is available since Tiger
|
||||
Lake (2020).
|
||||
|
||||
* `-z start_stop_visibility`=[ `hidden` | `protected` ]:
|
||||
* `-z start-stop-visibility`=[ `hidden` | `protected` ]:
|
||||
If a section name is valid as a C identifier (i.e., it matches
|
||||
`/^[_a-zA-Z][_a-zA-Z0-9]*$/`), mold creates `__start_SECNAME` and
|
||||
`__stop_SECNAME` symbols to mark the beginning and end of the section,
|
||||
where `SECNAME` is the section name.
|
||||
|
||||
You can make these marker symbols visible from other ELF modules by passing
|
||||
`-z start_stop_visibility=protected`. Default is `hidden`.
|
||||
`-z start-stop-visibility=protected`. Default is `hidden`.
|
||||
|
||||
* `-z text`, `-z notext`, `-z textoff`:
|
||||
`mold` by default reports an error if dynamic relocations are created in
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/sh
|
||||
# This script installs binary packages needed to build mold.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
|
||||
apt-get update
|
||||
apt-get install -y cmake gcc g++ clang gdb
|
||||
;;
|
||||
fedora | fedora-* | amzn | rhel | centos)
|
||||
dnf install -y gcc-c++ cmake glibc-static libstdc++-static diffutils util-linux tar
|
||||
;;
|
||||
rocky | ol)
|
||||
dnf install -y gcc-c++ cmake diffutils util-linux
|
||||
;;
|
||||
opensuse-*)
|
||||
zypper install -y make cmake gcc-c++ glibc-devel-static tar diffutils util-linux gawk
|
||||
;;
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge dev-build/cmake
|
||||
;;
|
||||
arch | archarm | artix | endeavouros | manjaro | cachyos)
|
||||
pacman -Sy --needed --noconfirm base-devel cmake util-linux
|
||||
;;
|
||||
void)
|
||||
xbps-install -Sy xbps bash make cmake gcc tar diffutils util-linux
|
||||
;;
|
||||
alpine)
|
||||
apk update
|
||||
apk add bash make linux-headers cmake gcc g++
|
||||
;;
|
||||
clear-linux-os)
|
||||
swupd update
|
||||
swupd bundle-add c-basic diffutils
|
||||
;;
|
||||
almalinux)
|
||||
dnf install -y gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel cmake diffutils
|
||||
;;
|
||||
altlinux)
|
||||
apt-get update
|
||||
apt-get install -y gcc-c++ make cmake ctest diffutils util-linux
|
||||
;;
|
||||
freebsd)
|
||||
pkg update
|
||||
pkg install -y cmake bash binutils gcc
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
# This script install packages for -DMOLD_ENABLE_QEMU_TESTS=1
|
||||
# to enable cross-target tests.
|
||||
#
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian)
|
||||
apt-get install -y qemu-user {gcc,g++}-{i686,aarch64,riscv64,powerpc,powerpc64,powerpc64le,s390x,sparc64,m68k,sh4}-linux-gnu {gcc,g++}-arm-linux-gnueabihf
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about build dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
Executable
+31
@@ -0,0 +1,31 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
PREFIX=${PREFIX:-/usr/local}
|
||||
MOLD_LIBDIR=${MOLD_LIBDIR:-$PREFIX/lib}
|
||||
|
||||
srcdir=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
||||
artifact_dir="${CARGO_TARGET_DIR:-$srcdir/target}/release"
|
||||
|
||||
if [ ! -x "$artifact_dir/mold" ] ||
|
||||
[ ! -f "$artifact_dir/mold-wrapper.so" ]; then
|
||||
echo "install-mold.sh: release artifacts are missing" >&2
|
||||
echo "Run 'cargo build --release' first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
bindir="$DESTDIR$PREFIX/bin"
|
||||
libdir="$DESTDIR$MOLD_LIBDIR/mold"
|
||||
libexecdir="$DESTDIR$PREFIX/libexec/mold"
|
||||
mandir="$DESTDIR$PREFIX/share/man/man1"
|
||||
docdir="$DESTDIR$PREFIX/share/doc/mold"
|
||||
|
||||
install -d "$bindir" "$libdir" "$libexecdir" "$mandir" "$docdir"
|
||||
install -m 755 "$artifact_dir/mold" "$bindir"
|
||||
install -m 755 "$artifact_dir/mold-wrapper.so" "$libdir"
|
||||
install -m 644 "$srcdir/docs/mold.1" "$mandir"
|
||||
install -m 644 "$srcdir/LICENSE" "$docdir"
|
||||
|
||||
ln -sf mold "$bindir/ld.mold"
|
||||
ln -sf ../../bin/mold "$libexecdir/ld"
|
||||
ln -sf mold.1 "$mandir/ld.mold.1"
|
||||
Executable
+68
@@ -0,0 +1,68 @@
|
||||
#!/bin/sh
|
||||
# This script installs binary packages needed to test mold.
|
||||
# Feel free to send me a PR if your OS is not on this list.
|
||||
|
||||
set -e
|
||||
. /etc/os-release
|
||||
|
||||
set -x
|
||||
|
||||
case "$ID" in
|
||||
ubuntu | pop | linuxmint | debian | raspbian | neon | zorin)
|
||||
apt-get update
|
||||
apt-get install -y curl gcc g++ clang gdb
|
||||
apt-get install -y qemu-user \
|
||||
gcc-i686-linux-gnu g++-i686-linux-gnu \
|
||||
gcc-aarch64-linux-gnu g++-aarch64-linux-gnu \
|
||||
gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \
|
||||
gcc-powerpc-linux-gnu g++-powerpc-linux-gnu \
|
||||
gcc-powerpc64-linux-gnu g++-powerpc64-linux-gnu \
|
||||
gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \
|
||||
gcc-s390x-linux-gnu g++-s390x-linux-gnu \
|
||||
gcc-sparc64-linux-gnu g++-sparc64-linux-gnu \
|
||||
gcc-m68k-linux-gnu g++-m68k-linux-gnu \
|
||||
gcc-sh4-linux-gnu g++-sh4-linux-gnu \
|
||||
gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf
|
||||
;;
|
||||
fedora | fedora-* | amzn | rhel | centos)
|
||||
dnf install -y curl gcc-c++ glibc-static libstdc++-static diffutils util-linux tar
|
||||
;;
|
||||
rocky | ol)
|
||||
dnf install -y curl gcc-c++ diffutils util-linux
|
||||
;;
|
||||
opensuse-*)
|
||||
zypper install -y bash curl gcc-c++ glibc-devel-static tar diffutils util-linux gawk
|
||||
;;
|
||||
gentoo)
|
||||
emerge-webrsync
|
||||
FEATURES='getbinpkg binpkg-request-signature' emerge net-misc/curl
|
||||
;;
|
||||
arch | archarm | artix | endeavouros | manjaro | cachyos)
|
||||
pacman -Sy --needed --noconfirm base-devel curl util-linux
|
||||
;;
|
||||
void)
|
||||
xbps-install -Sy xbps bash curl gcc tar diffutils util-linux
|
||||
;;
|
||||
alpine)
|
||||
apk update
|
||||
apk add bash curl linux-headers gcc g++
|
||||
;;
|
||||
clear-linux-os)
|
||||
swupd update
|
||||
swupd bundle-add c-basic diffutils
|
||||
;;
|
||||
almalinux)
|
||||
dnf install -y curl gcc-toolset-13-gcc-c++ gcc-toolset-13-libstdc++-devel diffutils
|
||||
;;
|
||||
altlinux)
|
||||
apt-get update
|
||||
apt-get install -y curl gcc-c++ diffutils util-linux
|
||||
;;
|
||||
freebsd)
|
||||
pkg update
|
||||
pkg install -y bash binutils curl gcc
|
||||
;;
|
||||
*)
|
||||
echo "Error: don't know anything about test dependencies on $ID-$VERSION_ID"
|
||||
exit 1
|
||||
esac
|
||||
@@ -1,146 +0,0 @@
|
||||
// This file implements the Aho-Corasick algorithm to search multiple
|
||||
// strings within an input string simultaneously. It is essentially a
|
||||
// trie with additional links. For details, see
|
||||
// https://en.wikipedia.org/wiki/Aho-Corasick_algorithm.
|
||||
//
|
||||
// We use it for simple glob patterns in version scripts or dynamic
|
||||
// list files. Here are some examples of glob patterns:
|
||||
//
|
||||
// qt_private_api_tag*
|
||||
// *16QAccessibleCache*
|
||||
// *32QAbstractFileIconProviderPrivate*
|
||||
// *17QPixmapIconEngine*
|
||||
//
|
||||
// Aho-Corasick can do only substring search, so it cannot handle
|
||||
// complex glob patterns such as `*foo*bar*`. We handle such patterns
|
||||
// with the Glob class.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <queue>
|
||||
|
||||
namespace mold {
|
||||
|
||||
bool AhoCorasick::can_handle(std::string_view pat) {
|
||||
if (pat.starts_with('*'))
|
||||
pat.remove_prefix(1);
|
||||
if (pat.ends_with('*'))
|
||||
pat.remove_suffix(1);
|
||||
return !pat.empty() && pat.find_first_of("*?[\\") == pat.npos;
|
||||
}
|
||||
|
||||
i32 AhoCorasick::find_child(i32 node, u8 ch) const {
|
||||
if (node == 0)
|
||||
return root_children[ch];
|
||||
|
||||
for (i32 i = nodes[node].first_child; i != -1; i = nodes[i].next_sibling)
|
||||
if (nodes[i].ch == ch)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
i32 AhoCorasick::add_child(i32 node, u8 ch) {
|
||||
if (i32 child = find_child(node, ch); child != -1)
|
||||
return child;
|
||||
|
||||
i32 child = nodes.size();
|
||||
i32 sibling = nodes[node].first_child;
|
||||
nodes.emplace_back();
|
||||
nodes[child].next_sibling = sibling;
|
||||
nodes[child].ch = ch;
|
||||
nodes[node].first_child = child;
|
||||
|
||||
if (node == 0)
|
||||
root_children[ch] = child;
|
||||
return child;
|
||||
}
|
||||
|
||||
i64 AhoCorasick::find(std::string_view str) {
|
||||
if (nodes.empty())
|
||||
return -1;
|
||||
|
||||
i32 idx = 0;
|
||||
i64 val = -1;
|
||||
|
||||
auto walk = [&](u8 c) {
|
||||
for (i32 j = idx; j != -1; j = nodes[j].suffix_link) {
|
||||
if (i32 child = find_child(j, c); child != -1) {
|
||||
idx = child;
|
||||
val = std::max(val, nodes[child].value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
idx = 0;
|
||||
};
|
||||
|
||||
walk('\0');
|
||||
for (u8 c : str) {
|
||||
walk(c);
|
||||
if (val == max_value)
|
||||
return val;
|
||||
}
|
||||
walk('\0');
|
||||
return val;
|
||||
}
|
||||
|
||||
bool AhoCorasick::add(std::string_view pat, i64 val) {
|
||||
assert(can_handle(pat));
|
||||
max_value = std::max(max_value, val);
|
||||
if (nodes.empty()) {
|
||||
root_children.fill(-1);
|
||||
nodes.emplace_back();
|
||||
}
|
||||
i32 idx = 0;
|
||||
|
||||
auto walk = [&](u8 c) { idx = add_child(idx, c); };
|
||||
|
||||
// We handle "foo" as if "\0foo\0", "*foo" as if "foo\0", "foo*" as
|
||||
// if "\0foo", and "*foo*" as if "foo". Aho-Corasick can do only
|
||||
// substring matching, so we use \0 as a beginning/end-of-string
|
||||
// markers.
|
||||
if (!pat.starts_with('*'))
|
||||
walk('\0');
|
||||
for (u8 c : pat)
|
||||
if (c != '*')
|
||||
walk(c);
|
||||
if (!pat.ends_with('*'))
|
||||
walk('\0');
|
||||
|
||||
nodes[idx].value = std::max(nodes[idx].value, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
void AhoCorasick::compile() {
|
||||
if (nodes.empty())
|
||||
return;
|
||||
|
||||
// A failure link may refer to any node at the previous depth, so failure
|
||||
// links must be constructed breadth-first.
|
||||
std::queue<i32> queue;
|
||||
for (i32 child = nodes[0].first_child; child != -1;
|
||||
child = nodes[child].next_sibling) {
|
||||
nodes[child].suffix_link = 0;
|
||||
queue.push(child);
|
||||
}
|
||||
|
||||
while (!queue.empty()) {
|
||||
i32 idx = queue.front();
|
||||
queue.pop();
|
||||
|
||||
for (i32 child = nodes[idx].first_child; child != -1;
|
||||
child = nodes[child].next_sibling) {
|
||||
i32 suffix = nodes[idx].suffix_link;
|
||||
while (suffix != 0 && find_child(suffix, nodes[child].ch) == -1)
|
||||
suffix = nodes[suffix].suffix_link;
|
||||
|
||||
if (i32 next = find_child(suffix, nodes[child].ch); next != -1)
|
||||
suffix = next;
|
||||
|
||||
nodes[child].suffix_link = suffix;
|
||||
nodes[child].value = std::max(nodes[child].value, nodes[suffix].value);
|
||||
queue.push(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr u32 BLOCK_SIZE = 1024 * 1024;
|
||||
static constexpr u32 BLOCK_ALIGNMENT = 64;
|
||||
static constexpr u32 MAX_LOCAL_ALLOC = BLOCK_SIZE / 4;
|
||||
static constexpr u32 NUM_ARENA_SLOTS = 10;
|
||||
|
||||
// Reserved address space is made accessible in chunks of this size.
|
||||
static constexpr u64 COMMIT_CHUNK = 64 * 1024 * 1024;
|
||||
|
||||
static std::atomic<u32> next_arena_slot;
|
||||
static thread_local u32 positions[NUM_ARENA_SLOTS];
|
||||
static thread_local u32 ends[NUM_ARENA_SLOTS];
|
||||
|
||||
[[noreturn]]
|
||||
static void memory_error(std::string_view action, u64 size) {
|
||||
std::cerr << "mold: cannot " << action << ' ' << size
|
||||
<< " bytes of virtual memory\n";
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
ArenaResource::ArenaResource() : arena_slot(next_arena_slot++) {
|
||||
assert(arena_slot < NUM_ARENA_SLOTS);
|
||||
|
||||
// Reserve address space without making it accessible. An inaccessible
|
||||
// mapping is not charged against the system's commit limit even where
|
||||
// overcommit is disabled, and fork() does not charge it either. Pages
|
||||
// are made accessible by commit() as the arena grows.
|
||||
#ifdef _WIN32
|
||||
data = (u8 *)VirtualAlloc(nullptr, (SIZE_T)SIZE, MEM_RESERVE, PAGE_NOACCESS);
|
||||
if (!data)
|
||||
memory_error("reserve", SIZE);
|
||||
#else
|
||||
data = (u8 *)mmap(nullptr, SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
|
||||
if (data == MAP_FAILED)
|
||||
memory_error("reserve", SIZE);
|
||||
#endif
|
||||
|
||||
#ifdef MADV_HUGEPAGE
|
||||
// Large links fill the beginning of the arena densely. Transparent huge
|
||||
// pages reduce address-translation overhead without populating unused pages.
|
||||
madvise(data, SIZE, MADV_HUGEPAGE);
|
||||
#endif
|
||||
}
|
||||
|
||||
ArenaResource::~ArenaResource() {
|
||||
#ifdef _WIN32
|
||||
VirtualFree(data, 0, MEM_RELEASE);
|
||||
#else
|
||||
munmap(data, SIZE);
|
||||
#endif
|
||||
}
|
||||
|
||||
// Makes the reserved range up to `end` accessible. Memory is committed in
|
||||
// whole chunks so that the arena grows with few system calls.
|
||||
void ArenaResource::commit(u64 end) {
|
||||
std::scoped_lock lock(commit_mu);
|
||||
u64 cur = committed.load(std::memory_order_relaxed);
|
||||
if (end <= cur)
|
||||
return;
|
||||
|
||||
u64 next = align_to(end, COMMIT_CHUNK);
|
||||
#ifdef _WIN32
|
||||
if (!VirtualAlloc(data + cur, (SIZE_T)(next - cur), MEM_COMMIT, PAGE_READWRITE))
|
||||
memory_error("commit", next - cur);
|
||||
#else
|
||||
if (mprotect(data + cur, next - cur, PROT_READ | PROT_WRITE) == -1)
|
||||
memory_error("commit", next - cur);
|
||||
#endif
|
||||
committed.store(next, std::memory_order_release);
|
||||
}
|
||||
|
||||
void *ArenaResource::allocate(u64 size, u64 alignment) {
|
||||
assert(std::has_single_bit(alignment));
|
||||
|
||||
// ArenaPtr and base-relative indices both encode offsets in four-byte units.
|
||||
alignment = std::max<u64>(alignment, 4);
|
||||
|
||||
// Standard containers make many small allocations. Reserve them from a
|
||||
// thread-local block to avoid contending on the global bump pointer.
|
||||
if (size <= MAX_LOCAL_ALLOC && alignment <= BLOCK_ALIGNMENT) {
|
||||
// Offsets are from the beginning of the arena in four-byte units.
|
||||
u32 &pos = positions[arena_slot];
|
||||
u32 &end = ends[arena_slot];
|
||||
u32 size4 = align_to(size, 4) / 4;
|
||||
|
||||
if (pos) {
|
||||
u32 begin = align_to(pos, alignment / 4);
|
||||
if (begin <= end && size4 <= end - begin) {
|
||||
pos = begin + size4;
|
||||
return data + (u64)begin * 4;
|
||||
}
|
||||
}
|
||||
|
||||
u8 *begin = (u8 *)allocate_global(BLOCK_SIZE, BLOCK_ALIGNMENT);
|
||||
u32 begin4 = (begin - data) / 4;
|
||||
pos = begin4 + size4;
|
||||
end = begin4 + BLOCK_SIZE / 4;
|
||||
return begin;
|
||||
}
|
||||
|
||||
return allocate_global(size, alignment);
|
||||
}
|
||||
|
||||
void *ArenaResource::allocate_global(u64 size, u64 alignment) {
|
||||
u64 old = offset.load(std::memory_order_relaxed);
|
||||
u64 begin, end;
|
||||
|
||||
for (;;) {
|
||||
u64 padding = -old & (alignment - 1);
|
||||
if (old > SIZE || padding > SIZE - old || size > SIZE - old - padding) {
|
||||
std::cerr << "mold: cannot allocate more than " << SIZE
|
||||
<< " bytes for linker data structures on this host\n";
|
||||
std::exit(1);
|
||||
}
|
||||
|
||||
begin = old + padding;
|
||||
end = begin + size;
|
||||
if (offset.compare_exchange_weak(old, end, std::memory_order_relaxed))
|
||||
break;
|
||||
}
|
||||
|
||||
if (end > committed.load(std::memory_order_acquire))
|
||||
commit(end);
|
||||
return data + begin;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,64 +0,0 @@
|
||||
// This is the same as std::atomic except that the default memory
|
||||
// order is relaxed instead of sequential consistency.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T>
|
||||
struct Atomic : std::atomic<T> {
|
||||
static constexpr std::memory_order relaxed = std::memory_order_relaxed;
|
||||
|
||||
using std::atomic<T>::atomic;
|
||||
|
||||
Atomic(const Atomic<T> &other) : std::atomic<T>(other.load()) {}
|
||||
|
||||
Atomic<T> &operator=(const Atomic<T> &other) {
|
||||
store(other.load());
|
||||
return *this;
|
||||
}
|
||||
|
||||
void operator=(T val) { store(val); }
|
||||
operator T() const { return load(); }
|
||||
|
||||
void store(T val, std::memory_order order = relaxed) {
|
||||
std::atomic<T>::store(val, order);
|
||||
}
|
||||
|
||||
T load(std::memory_order order = relaxed) const {
|
||||
return std::atomic<T>::load(order);
|
||||
}
|
||||
|
||||
T exchange(T val) { return std::atomic<T>::exchange(val, relaxed); }
|
||||
|
||||
T fetch_or(T val, std::memory_order order = relaxed) {
|
||||
return std::atomic<T>::fetch_or(val, order);
|
||||
}
|
||||
|
||||
T fetch_and(T val, std::memory_order order = relaxed) {
|
||||
return std::atomic<T>::fetch_and(val, order);
|
||||
}
|
||||
|
||||
T operator|=(T val) {
|
||||
// Avoid exclusive cache-line ownership if all requested bits are set.
|
||||
T old = load();
|
||||
return (old & val) == val ? old : fetch_or(val);
|
||||
}
|
||||
|
||||
T operator++() { return std::atomic<T>::fetch_add(1, relaxed) + 1; }
|
||||
T operator--() { return std::atomic<T>::fetch_sub(1, relaxed) - 1; }
|
||||
T operator++(int) { return std::atomic<T>::fetch_add(1, relaxed); }
|
||||
T operator--(int) { return std::atomic<T>::fetch_sub(1, relaxed); }
|
||||
|
||||
bool test_and_set() {
|
||||
// A relaxed load + branch (assuming miss) takes only around 20 cycles,
|
||||
// while an atomic RMW can easily take hundreds on x86. We note that it's
|
||||
// common that another thread beat us in marking, so doing an optimistic
|
||||
// early test tends to improve performance in the ~20% ballpark.
|
||||
return load() || exchange(true);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mold
|
||||
-174
@@ -1,174 +0,0 @@
|
||||
// This file implements a multi-threaded zlib and zstd compression
|
||||
// routine.
|
||||
//
|
||||
// zlib-compressed data can be merged just by concatenation as long as
|
||||
// each piece of data is flushed with Z_SYNC_FLUSH. In this file, we
|
||||
// split input data into multiple shards, compress them individually
|
||||
// and concatenate them. We then append a header, a trailer and a
|
||||
// checksum so that the concatenated data is valid zlib-format data.
|
||||
//
|
||||
// zstd-compressed data can be merged in the same way.
|
||||
//
|
||||
// Using threads to compress data has a downside. Since the dictionary
|
||||
// is reset on boundaries of shards, compression ratio is sacrificed
|
||||
// a little bit. However, if a shard size is large enough, that loss
|
||||
// is negligible in practice.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
#include <zstd.h>
|
||||
|
||||
#define CHECK(fn) \
|
||||
do { \
|
||||
[[maybe_unused]] int r = (fn); \
|
||||
assert(r == Z_OK); \
|
||||
} while (0)
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 SHARD_SIZE = 1024 * 1024;
|
||||
|
||||
Compressor::~Compressor() {
|
||||
for (std::span<u8> shard : shards)
|
||||
delete[] shard.data();
|
||||
}
|
||||
|
||||
static std::vector<std::span<u8>> split(std::span<u8> input) {
|
||||
std::vector<std::span<u8>> vec;
|
||||
while (!input.empty()) {
|
||||
i64 sz = std::min<i64>(SHARD_SIZE, input.size());
|
||||
vec.push_back(input.subspan(0, sz));
|
||||
input = input.subspan(sz);
|
||||
}
|
||||
return vec;
|
||||
}
|
||||
|
||||
static std::span<u8> zlib_compress(std::span<u8> input, int level) {
|
||||
// Initialize zlib stream. Since debug info is generally compressed
|
||||
// pretty well with lower compression levels, the default level is 1.
|
||||
z_stream strm = {};
|
||||
CHECK(deflateInit2(&strm, level, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY));
|
||||
|
||||
// Set an input buffer
|
||||
strm.avail_in = input.size();
|
||||
strm.next_in = input.data();
|
||||
|
||||
// Set an output buffer. deflateBound() returns an upper bound
|
||||
// on the compression size. +16 for Z_SYNC_FLUSH.
|
||||
i64 bufsize = deflateBound(&strm, strm.avail_in) + 16;
|
||||
u8 *buf = new u8[bufsize];
|
||||
|
||||
// Compress data. It writes all compressed bytes except the last
|
||||
// partial byte, so up to 7 bits can be held to be written to the
|
||||
// buffer.
|
||||
strm.avail_out = bufsize;
|
||||
strm.next_out = buf;
|
||||
CHECK(deflate(&strm, Z_BLOCK));
|
||||
|
||||
// This is a workaround for libbacktrace before 2022-04-06.
|
||||
//
|
||||
// Zlib is a bit stream, and what Z_SYNC_FLUSH does is to write a
|
||||
// three bit value indicating the start of an uncompressed data
|
||||
// block followed by four byte data 00 00 ff ff which indicates that
|
||||
// the length of the block is zero. libbacktrace uses its own zlib
|
||||
// inflate routine, and it had a bug that if that particular three
|
||||
// bit value happens to end at a byte boundary, it accidentally
|
||||
// skipped the next byte.
|
||||
//
|
||||
// In order to avoid triggering that bug, we should avoid calling
|
||||
// deflate() with Z_SYNC_FLUSH if the current bit position is 5.
|
||||
// If it's 5, we insert an empty block consisting of 10 bits so
|
||||
// that the bit position is 7 in the next byte.
|
||||
//
|
||||
// https://github.com/ianlancetaylor/libbacktrace/pull/87
|
||||
int nbits = 0;
|
||||
deflatePending(&strm, Z_NULL, &nbits);
|
||||
if (nbits == 5)
|
||||
CHECK(deflatePrime(&strm, 10, 2));
|
||||
CHECK(deflate(&strm, Z_SYNC_FLUSH));
|
||||
|
||||
deflateEnd(&strm);
|
||||
return {buf, (size_t)(bufsize - strm.avail_out)};
|
||||
}
|
||||
|
||||
ZlibCompressor::ZlibCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
std::vector<u32> adlers(inputs.size());
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
std::span<u8> in = inputs[i];
|
||||
adlers[i] = adler32(1, in.data(), in.size());
|
||||
shards[i] = zlib_compress(in, level);
|
||||
});
|
||||
|
||||
// Combine checksums
|
||||
checksum = adlers[0];
|
||||
for (i64 i = 1; i < inputs.size(); i++)
|
||||
checksum = adler32_combine(checksum, adlers[i], inputs[i].size());
|
||||
|
||||
// Comput the total size
|
||||
compressed_size = 8; // the header and the trailer
|
||||
for (std::span<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZlibCompressor::write_to(u8 *buf) {
|
||||
// Write a zlib-format header
|
||||
buf[0] = 0x78;
|
||||
buf[1] = 0x9c;
|
||||
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
offsets[0] = 2; // +2 for the header
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
|
||||
// Write a trailer
|
||||
u8 *end = buf + compressed_size;
|
||||
end[-6] = 3;
|
||||
end[-5] = 0;
|
||||
*(ub32 *)(end - 4) = checksum;
|
||||
}
|
||||
|
||||
static std::span<u8> zstd_compress(std::span<u8> input, int level) {
|
||||
i64 bufsize = ZSTD_COMPRESSBOUND(input.size());
|
||||
u8 *buf = new u8[bufsize];
|
||||
size_t sz = ZSTD_compress(buf, bufsize, input.data(), input.size(), level);
|
||||
assert(!ZSTD_isError(sz));
|
||||
return {buf, sz};
|
||||
}
|
||||
|
||||
ZstdCompressor::ZstdCompressor(u8 *buf, i64 size, i64 level) {
|
||||
std::vector<std::span<u8>> inputs = split(std::span(buf, size));
|
||||
shards.resize(inputs.size());
|
||||
|
||||
// Compress each shard
|
||||
tbb::parallel_for((i64)0, (i64)inputs.size(), [&](i64 i) {
|
||||
shards[i] = zstd_compress(inputs[i], level);
|
||||
});
|
||||
|
||||
compressed_size = 0;
|
||||
for (std::span<u8> &shard : shards)
|
||||
compressed_size += shard.size();
|
||||
}
|
||||
|
||||
void ZstdCompressor::write_to(u8 *buf) {
|
||||
// Copy compressed data
|
||||
std::vector<i64> offsets(shards.size());
|
||||
for (i64 i = 1; i < shards.size(); i++)
|
||||
offsets[i] = offsets[i - 1] + shards[i - 1].size();
|
||||
|
||||
tbb::parallel_for((i64)0, (i64)shards.size(), [&](i64 i) {
|
||||
memcpy(buf + offsets[i], shards[i].data(), shards[i].size());
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,30 +0,0 @@
|
||||
#define MOLD_VERSION "@mold_VERSION@"
|
||||
#define MOLD_LIBDIR "@CMAKE_INSTALL_FULL_LIBDIR@"
|
||||
#define MOLD_FIRST_TARGET @MOLD_FIRST_TARGET@
|
||||
|
||||
#cmakedefine01 HAVE_FALLOCATE
|
||||
#cmakedefine01 HAVE_MADVISE
|
||||
#cmakedefine01 HAVE_UNAME
|
||||
#cmakedefine01 MOLD_USE_MIMALLOC
|
||||
#cmakedefine01 MOLD_USE_SYSTEM_MIMALLOC
|
||||
|
||||
#cmakedefine01 HAVE_TARGET_X86_64
|
||||
#cmakedefine01 HAVE_TARGET_I386
|
||||
#cmakedefine01 HAVE_TARGET_ARM64LE
|
||||
#cmakedefine01 HAVE_TARGET_ARM64BE
|
||||
#cmakedefine01 HAVE_TARGET_ARM32LE
|
||||
#cmakedefine01 HAVE_TARGET_ARM32BE
|
||||
#cmakedefine01 HAVE_TARGET_RV32LE
|
||||
#cmakedefine01 HAVE_TARGET_RV32BE
|
||||
#cmakedefine01 HAVE_TARGET_RV64LE
|
||||
#cmakedefine01 HAVE_TARGET_RV64BE
|
||||
#cmakedefine01 HAVE_TARGET_PPC32
|
||||
#cmakedefine01 HAVE_TARGET_PPC64V1
|
||||
#cmakedefine01 HAVE_TARGET_PPC64V2
|
||||
#cmakedefine01 HAVE_TARGET_S390X
|
||||
#cmakedefine01 HAVE_TARGET_SPARC64
|
||||
#cmakedefine01 HAVE_TARGET_M68K
|
||||
#cmakedefine01 HAVE_TARGET_SH4LE
|
||||
#cmakedefine01 HAVE_TARGET_SH4BE
|
||||
#cmakedefine01 HAVE_TARGET_LOONGARCH64
|
||||
#cmakedefine01 HAVE_TARGET_LOONGARCH32
|
||||
@@ -1,60 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <tbb/parallel_for_each.h>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
// This function "forges" a CRC. That is, given the current and a desired
|
||||
// CRC32 value, crc32_solve() returns a binary blob to add to the end of
|
||||
// the original data to yield the desired CRC. Trailing garbage is ignored
|
||||
// by many bianry file formats, so you can create a file with a desired
|
||||
// CRC using crc32_solve(). We need it for --separate-debug-file.
|
||||
std::vector<u8> crc32_solve(u32 current, u32 desired) {
|
||||
constexpr u32 poly = 0xedb88320;
|
||||
u32 x = ~desired;
|
||||
|
||||
// Each iteration computes x = (x * x^-1) mod poly.
|
||||
for (i64 i = 0; i < 32; i++) {
|
||||
x = std::rotl(x, 1);
|
||||
x ^= (x & 1) * (poly << 1);
|
||||
}
|
||||
|
||||
x ^= ~current;
|
||||
|
||||
std::vector<u8> out(4);
|
||||
out[0] = x;
|
||||
out[1] = x >> 8;
|
||||
out[2] = x >> 16;
|
||||
out[3] = x >> 24;
|
||||
return out;
|
||||
}
|
||||
|
||||
// Compute a CRC for given data in parallel
|
||||
u32 compute_crc32(u32 crc, u8 *buf, i64 len) {
|
||||
struct Shard {
|
||||
u8 *buf;
|
||||
i64 len;
|
||||
u32 crc;
|
||||
};
|
||||
|
||||
constexpr i64 shard_size = 1024 * 1024; // 1 MiB
|
||||
std::vector<Shard> shards;
|
||||
|
||||
while (len > 0) {
|
||||
i64 sz = std::min(len, shard_size);
|
||||
shards.push_back({buf, sz, 0});
|
||||
buf += sz;
|
||||
len -= sz;
|
||||
}
|
||||
|
||||
tbb::parallel_for_each(shards, [](Shard &shard) {
|
||||
shard.crc = crc32(0, shard.buf, shard.len);
|
||||
});
|
||||
|
||||
for (Shard &shard : shards)
|
||||
crc = crc32_combine(crc, shard.crc, shard.len);
|
||||
return crc;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <cxxabi.h>
|
||||
#endif
|
||||
|
||||
#include "../third-party/rust-demangle/rust-demangle.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<std::string_view> demangle_cpp(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
static thread_local size_t buflen;
|
||||
|
||||
// TODO(cwasser): Actually demangle Symbols on Windows using e.g.
|
||||
// `UnDecorateSymbolName` from Dbghelp, maybe even Itanium symbols?
|
||||
#ifndef _WIN32
|
||||
if (name.starts_with("_Z")) {
|
||||
int status;
|
||||
char *p = abi::__cxa_demangle(std::string(name).c_str(), buf, &buflen, &status);
|
||||
if (status == 0) {
|
||||
buf = p;
|
||||
return p;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<std::string_view> demangle_rust(std::string_view name) {
|
||||
static thread_local char *buf;
|
||||
free(buf);
|
||||
buf = rust_demangle(std::string(name).c_str(), 0);
|
||||
if (buf)
|
||||
return buf;
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,42 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <mach-o/dyld.h>
|
||||
#endif
|
||||
|
||||
#ifdef __FreeBSD__
|
||||
# include <sys/sysctl.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
// Returns the path of the mold executable itself
|
||||
std::string get_self_path() {
|
||||
#if __APPLE__ || _WIN32
|
||||
fprintf(stderr, "mold: get_self_path is not supported");
|
||||
exit(1);
|
||||
#elif __FreeBSD__
|
||||
// /proc may not be mounted on FreeBSD. The proper way to get the
|
||||
// current executable's path is to use sysctl(2).
|
||||
int mib[4];
|
||||
mib[0] = CTL_KERN;
|
||||
mib[1] = KERN_PROC;
|
||||
mib[2] = KERN_PROC_PATHNAME;
|
||||
mib[3] = -1;
|
||||
|
||||
size_t size;
|
||||
sysctl(mib, 4, NULL, &size, NULL, 0);
|
||||
|
||||
std::string path;
|
||||
path.resize(size);
|
||||
sysctl(mib, 4, path.data(), &size, NULL, 0);
|
||||
return path;
|
||||
#else
|
||||
return std::filesystem::read_symlink("/proc/self/exe").string();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,50 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Work around a deadlock in portage's binary package extraction.
|
||||
#
|
||||
# tar_stream_reader.close() joins its stdin-feeder thread and then waits
|
||||
# for the external decompressor to exit without draining the
|
||||
# decompressor's remaining stdout. tarfile stops reading at the tar
|
||||
# end-of-archive marker, so trailing archive padding is left in the
|
||||
# pipe. If that residue exceeds the pipe buffer, the decompressor
|
||||
# blocks writing forever and close() never returns, freezing the whole
|
||||
# emerge. Small pipe buffers make this reachable in practice: the
|
||||
# kernel shrinks new pipes to one page once a user exceeds
|
||||
# fs.pipe-user-pages-soft, which our 80 parallel containers do.
|
||||
#
|
||||
# The fix is to drain the decompressor's stdout to EOF before joining
|
||||
# and waiting. This script patches the installed gpkg.py at container
|
||||
# image build time and fails loudly if the code no longer matches, so
|
||||
# a portage upgrade that changes (or fixes) this code is noticed.
|
||||
|
||||
import sys
|
||||
import portage.gpkg
|
||||
|
||||
path = portage.gpkg.__file__
|
||||
src = open(path).read()
|
||||
|
||||
old = """ if self.proc is not None:
|
||||
self.thread.join()
|
||||
"""
|
||||
|
||||
new = """ if self.proc is not None:
|
||||
# Drain remaining decompressor output before joining and
|
||||
# waiting; otherwise the decompressor can block forever on
|
||||
# a full stdout pipe. Injected by mold's lib/gentoo-test.sh.
|
||||
try:
|
||||
while self.read_io.read(65536):
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
self.thread.join()
|
||||
"""
|
||||
|
||||
if src.count(old) != 1:
|
||||
sys.exit(f"gpkg workaround: expected pattern not found in {path}; "
|
||||
"portage changed, update lib/gentoo-gpkg-workaround.py")
|
||||
|
||||
with open(path, "w") as f:
|
||||
f.write(src.replace(old, new))
|
||||
|
||||
import py_compile
|
||||
py_compile.compile(path, doraise=True)
|
||||
print(f"gpkg deadlock workaround applied to {path}")
|
||||
@@ -1,186 +0,0 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# This test script takes a Gentoo package name and tries to build it
|
||||
# using mold in a Podman environment. We chose Gentoo Linux as a test
|
||||
# target, because its source-based packages allow us to build programs
|
||||
# locally and run their test suites without any hassle.
|
||||
#
|
||||
# We first build a package with mold acting as /usr/bin/ld. Only if
|
||||
# that fails, we build the same package again with GNU ld to tell
|
||||
# whether the failure is mold's fault. The package's own test suite
|
||||
# runs in both cases; FEATURES=test is scoped to just the package
|
||||
# under test via package.env, because applying it to the entire
|
||||
# dependency graph makes a large fraction of the tree unresolvable.
|
||||
# Results are classified into three directories:
|
||||
#
|
||||
# success: the package built fine with mold (or, for a control
|
||||
# build, with GNU ld)
|
||||
# failure: the package built with GNU ld but not with mold, which
|
||||
# means it hit a mold bug
|
||||
# broken: the package didn't build even with GNU ld, so the
|
||||
# failure is not mold's fault
|
||||
#
|
||||
# You can get a complete list of Gentoo packages available for testing
|
||||
# with the following command:
|
||||
#
|
||||
# podman run --rm mold-gentoo emerge --color n -s '' | \
|
||||
# perl -ne 'next unless m!^\*\s+(\S+/\S+)!; print "$1\n"'
|
||||
|
||||
package="$1"
|
||||
|
||||
if [ "$package" = "" ]; then
|
||||
echo "Usage: $0 gentoo-package-name"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -x
|
||||
|
||||
# Test results are keyed by the commit hash embedded in the mold
|
||||
# binary, so refuse to run with a binary that doesn't have one.
|
||||
git_hash=$(./dist/mold --version | perl -ne '/\(([0-9a-f]{40})\b/ and print $1;')
|
||||
|
||||
if [ "$git_hash" = "" ]; then
|
||||
echo "Cannot get a git hash from ./dist/mold --version. Run this script at the mold source root, with dist/mold built from a git checkout."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cache_dir=/var/cache/mold-gentoo
|
||||
|
||||
# Create a Podman image. The lock prevents parallel invocations of
|
||||
# this script from racing to build the same image.
|
||||
#
|
||||
# A few notes on the container configuration:
|
||||
#
|
||||
# - We accept ~amd64 (testing) packages to test the newest version of
|
||||
# everything. The stage3 image is built for the stable profile, so
|
||||
# we update the whole system to ~amd64 once here; without that,
|
||||
# half-upgraded packages (e.g. perl and its virtuals) cause
|
||||
# unsolvable dependency conflicts in every container.
|
||||
#
|
||||
# - EMERGE_DEFAULT_OPTS makes emerge automatically apply the USE flag
|
||||
# adjustments it would otherwise just print and give up on. An
|
||||
# ephemeral container has no /etc/portage/package.use accumulated by
|
||||
# an administrator, so without this, thousands of packages are
|
||||
# unbuildable out of the box.
|
||||
#
|
||||
# - freetype and harfbuzz mutually depend on each other, so we
|
||||
# pre-install them with the documented two-step bootstrap. During
|
||||
# that step, pillow is built without truetype support to break
|
||||
# another dependency cycle (docutils -> pillow -> harfbuzz -> glib
|
||||
# -> docutils). The override is removed afterwards; once the cycle
|
||||
# participants are installed, they satisfy buildtime dependencies
|
||||
# and the cycle can no longer form.
|
||||
#
|
||||
# - qtbase's test suite requires icu (REQUIRED_USE), which autounmask
|
||||
# doesn't resolve on its own, hence the package.use entry.
|
||||
#
|
||||
# - gcc doesn't auto-select a newly installed major version, so we
|
||||
# switch to the newest slot explicitly after the world update.
|
||||
#
|
||||
# - lib/gentoo-gpkg-workaround.py patches a deadlock in portage's
|
||||
# binary package extraction. See that file for details.
|
||||
#
|
||||
# - static-libs is not in USE because it contradicts REQUIRED_USE
|
||||
# constraints in some packages (e.g. lvm2 forbids static-libs with
|
||||
# udev), and building static archives doesn't exercise the linker
|
||||
# anyway. Where a dependency genuinely needs it, emerge re-enables
|
||||
# it per package via autounmask.
|
||||
#
|
||||
# - /etc/portage/env/test.conf allows marking a single package for
|
||||
# FEATURES=test through package.env, which is how we scope test
|
||||
# suites to just the package under test.
|
||||
#
|
||||
# - The image build compiles a couple hundred packages from source,
|
||||
# because the Gentoo binhost only carries stable versions built with
|
||||
# default USE flags, which rarely match our ~amd64/USE combination.
|
||||
# To pay that cost only once, the build publishes what it compiles
|
||||
# to the GNU ld binary package pool and reuses it, so a rebuilt
|
||||
# image only compiles packages whose version has changed since the
|
||||
# last build. The toolchain step still uses the binhost, where our
|
||||
# configuration does match (e.g. clang). Since it verifies binhost
|
||||
# package signatures and our own pool is unsigned, it runs with a
|
||||
# private PKGDIR so the two never mix.
|
||||
(
|
||||
flock 9
|
||||
if ! podman image exists mold-gentoo; then
|
||||
cat <<EOF | podman build -v $cache_dir/distfiles:/var/cache/distfiles -v $cache_dir/ccache:/ccache -v $cache_dir/binpkgs-ld:/var/cache/binpkgs -v `pwd`/lib:/mold-lib:ro -t mold-gentoo - || exit 1
|
||||
FROM docker.io/gentoo/stage3
|
||||
RUN emerge-webrsync
|
||||
RUN echo 'USE="X ssl elogind -systemd truetype jpeg jpeg2k tiff zstd -perl udev lcms alsa"' >> /etc/portage/make.conf && \
|
||||
echo 'ACCEPT_KEYWORDS="~amd64"' >> /etc/portage/make.conf && \
|
||||
echo 'ACCEPT_LICENSE="* -@EULA"' >> /etc/portage/make.conf && \
|
||||
echo 'FEATURES="nostrip ccache binpkg-multi-instance -ipc-sandbox -network-sandbox -pid-sandbox -sandbox"' >> /etc/portage/make.conf && \
|
||||
echo 'CCACHE_DIR="/ccache"' >> /etc/portage/make.conf && \
|
||||
echo 'MAKEOPTS="-j$(nproc) --load-average=100"' >> /etc/portage/make.conf && \
|
||||
echo 'EMERGE_DEFAULT_OPTS="--autounmask-continue=y --autounmask-license=n --autounmask-backtrack=y --backtrack=100 --jobs=16 --load-average=100"' >> /etc/portage/make.conf && \
|
||||
mkdir -p /etc/portage/env /etc/portage/package.use && \
|
||||
echo 'FEATURES="test"' > /etc/portage/env/test.conf && \
|
||||
printf 'media-libs/freetype harfbuzz\ndev-qt/qtbase icu\n' > /etc/portage/package.use/mold && \
|
||||
echo 'dev-python/pillow -truetype' > /etc/portage/package.use/cycle-break && \
|
||||
FEATURES=buildpkg emerge --usepkg --update --deep --newuse @world && \
|
||||
gcc-config \$(gcc-config -l | tail -n 1 | awk '{print \$2}') && env-update && \
|
||||
USE=-harfbuzz FEATURES=buildpkg emerge --oneshot media-libs/freetype && \
|
||||
FEATURES=buildpkg emerge --usepkg --oneshot media-libs/freetype media-libs/harfbuzz && \
|
||||
rm /etc/portage/package.use/cycle-break && \
|
||||
PKGDIR=/var/tmp/binpkgs FEATURES='getbinpkg binpkg-request-signature' emerge gdb lld llvm-core/clang vim emacs strace ccache dev-build/cmake dev-vcs/git && \
|
||||
rm -rf /var/tmp/portage /var/tmp/binpkgs
|
||||
RUN python3 /mold-lib/gentoo-gpkg-workaround.py
|
||||
EOF
|
||||
fi
|
||||
) 9> /tmp/mold-gentoo-image.lock || exit 1
|
||||
|
||||
if [ "$package" = dev-libs/concurrencykit ]; then
|
||||
echo "Skipping known broken package: $package"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build a given package in Podman.
|
||||
#
|
||||
# Build artifacts are shared between containers through host
|
||||
# directories bind-mounted into every container:
|
||||
#
|
||||
# /var/cache/mold-gentoo/ccache: compiler cache
|
||||
# /var/cache/mold-gentoo/distfiles: source tarballs, so that each
|
||||
# distfile is downloaded only once across all containers and runs
|
||||
# /var/cache/mold-gentoo/binpkgs-{mold,ld}: binary packages, so that each
|
||||
# dependency is built only once instead of once per container that
|
||||
# needs it
|
||||
#
|
||||
# Every emerge runs with FEATURES=buildpkg to publish what it built to
|
||||
# the binary package pool, and dependencies are installed with
|
||||
# --usepkg from the pool when an exact version and USE match exists.
|
||||
# The package under test itself is always built from source. In the
|
||||
# mold run, dependencies are still linked with mold and their binaries
|
||||
# are still exercised by the test suites of everything built on top of
|
||||
# them; the pool just memoizes that work across containers. The mold
|
||||
# and GNU ld runs use separate pools so that a control build can never
|
||||
# install mold-linked binaries.
|
||||
cmd1='(cd /usr/bin; ln -sf /mold/dist/mold $(realpath ld))'
|
||||
cmd2="echo '$package test.conf' > /etc/portage/package.env"
|
||||
cmd3="FEATURES=buildpkg emerge --usepkg --onlydeps $package"
|
||||
cmd4="FEATURES=buildpkg emerge $package"
|
||||
filename=`echo "$package" | sed 's!/!_!g'`
|
||||
podman="podman run --rm --pids-limit=-1 --cap-add=SYS_PTRACE -v `pwd`:/mold:ro -v $cache_dir/ccache:/ccache -v $cache_dir/distfiles:/var/cache/distfiles"
|
||||
run="mold-gentoo timeout -v -k 15s 3h chrt --idle 0 nice -n 19 bash -c"
|
||||
dir=gentoo/$git_hash
|
||||
|
||||
mkdir -p "$dir"/success "$dir"/failure "$dir"/broken
|
||||
|
||||
# Build the package with mold. If this succeeds, we don't need a
|
||||
# control build.
|
||||
$podman -v $cache_dir/binpkgs-mold:/var/cache/binpkgs $run "$cmd1 && $cmd2 && $cmd3 && $cmd4" >& "$dir"/"$filename".mold
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".mold "$dir"/success
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Build the package again with GNU ld to tell whether the failure is
|
||||
# mold's fault.
|
||||
$podman -v $cache_dir/binpkgs-ld:/var/cache/binpkgs $run "$cmd2 && $cmd3 && $cmd4" >& "$dir"/"$filename".ld
|
||||
if [ $? = 0 ]; then
|
||||
mv "$dir"/"$filename".mold "$dir"/failure
|
||||
mv "$dir"/"$filename".ld "$dir"/success
|
||||
else
|
||||
mv "$dir"/"$filename".mold "$dir"/broken
|
||||
mv "$dir"/"$filename".ld "$dir"/broken
|
||||
fi
|
||||
-367
@@ -1,367 +0,0 @@
|
||||
// This file implements the glob matcher used for symbol name patterns.
|
||||
// Exact, prefix and suffix patterns are matched directly, and simple
|
||||
// substring patterns are combined into an Aho-Corasick matcher. The
|
||||
// remaining patterns are matched with the non-recursive algorithm described
|
||||
// at https://research.swtch.com/glob. If there are many such patterns, a
|
||||
// bit-parallel NFA matches them together.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
std::optional<Glob::Pattern> Glob::Pattern::compile(std::string_view pat,
|
||||
i64 value) {
|
||||
std::vector<Token> tokens;
|
||||
|
||||
while (!pat.empty()) {
|
||||
u8 c = pat[0];
|
||||
pat.remove_prefix(1);
|
||||
|
||||
switch (c) {
|
||||
case '[': {
|
||||
// Here are a few bracket pattern examples:
|
||||
//
|
||||
// [abc]: a, b or c
|
||||
// [$\]!]: $, ] or !
|
||||
// [a-czg-i]: a, b, c, z, g, h, or i
|
||||
// [!a-z]: Any character except lowercase letters
|
||||
//
|
||||
// Both `!` and `^` are accepted as negation markers. `!` is the
|
||||
// POSIX/shell convention used by other linkers. `^` was mold's
|
||||
// original syntax and is kept for backward compatibility.
|
||||
tokens.emplace_back(BRACKET);
|
||||
std::bitset<256> &chars = tokens.back().chars;
|
||||
bool negate = false;
|
||||
bool closed = false;
|
||||
|
||||
if (!pat.empty() && (pat[0] == '!' || pat[0] == '^')) {
|
||||
negate = true;
|
||||
pat.remove_prefix(1);
|
||||
}
|
||||
|
||||
while (!pat.empty()) {
|
||||
if (pat[0] == ']') {
|
||||
pat.remove_prefix(1);
|
||||
closed = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pat[0] == '\\') {
|
||||
pat.remove_prefix(1);
|
||||
if (pat.empty())
|
||||
return {};
|
||||
}
|
||||
|
||||
if (pat.size() >= 3 && pat[1] == '-') {
|
||||
u8 start = pat[0];
|
||||
u8 end = pat[2];
|
||||
pat.remove_prefix(3);
|
||||
|
||||
if (end == '\\') {
|
||||
if (pat.empty())
|
||||
return {};
|
||||
end = pat[0];
|
||||
pat.remove_prefix(1);
|
||||
}
|
||||
|
||||
if (end < start)
|
||||
return {};
|
||||
|
||||
for (i64 i = start; i <= end; i++)
|
||||
chars[i] = true;
|
||||
} else {
|
||||
chars[(u8)pat[0]] = true;
|
||||
pat.remove_prefix(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!closed)
|
||||
return {};
|
||||
|
||||
if (negate)
|
||||
chars.flip();
|
||||
break;
|
||||
}
|
||||
case '?':
|
||||
tokens.emplace_back(QUESTION);
|
||||
break;
|
||||
case '*':
|
||||
if (tokens.empty() || tokens.back().kind != STAR)
|
||||
tokens.emplace_back(STAR);
|
||||
break;
|
||||
case '\\':
|
||||
if (pat.empty())
|
||||
return {};
|
||||
if (tokens.empty() || tokens.back().kind != STRING)
|
||||
tokens.emplace_back(STRING);
|
||||
tokens.back().str += pat[0];
|
||||
pat.remove_prefix(1);
|
||||
break;
|
||||
default:
|
||||
if (tokens.empty() || tokens.back().kind != STRING)
|
||||
tokens.emplace_back(STRING);
|
||||
tokens.back().str += c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Pattern(std::move(tokens), value);
|
||||
}
|
||||
|
||||
bool Glob::Pattern::match(std::string_view str) const {
|
||||
if (!tokens.empty() && tokens.back().kind == STRING &&
|
||||
!str.ends_with(tokens.back().str))
|
||||
return false;
|
||||
|
||||
i64 x = 0;
|
||||
i64 y = 0;
|
||||
i64 next_x = -1;
|
||||
i64 next_y = -1;
|
||||
|
||||
while (x < str.size() || y < tokens.size()) {
|
||||
if (y < tokens.size()) {
|
||||
const Token &tok = tokens[y];
|
||||
|
||||
switch (tok.kind) {
|
||||
case STRING:
|
||||
if (str.substr(x).starts_with(tok.str)) {
|
||||
x += tok.str.size();
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case STAR:
|
||||
next_y = y++;
|
||||
next_x = x + 1;
|
||||
|
||||
if (y < tokens.size() && tokens[y].kind == STRING) {
|
||||
const std::string &s = tokens[y].str;
|
||||
i64 pos = str.find(s, x);
|
||||
if (pos == str.npos)
|
||||
return false;
|
||||
next_x = pos + 1;
|
||||
x = pos + s.size();
|
||||
y++;
|
||||
}
|
||||
continue;
|
||||
case QUESTION:
|
||||
if (x < str.size()) {
|
||||
x++;
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
case BRACKET:
|
||||
if (x < str.size() && tok.chars[(u8)str[x]]) {
|
||||
x++;
|
||||
y++;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Retry the last star after assigning one more input byte to it.
|
||||
if (next_x != -1 && next_x <= str.size()) {
|
||||
x = next_x;
|
||||
y = next_y;
|
||||
continue;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Glob::Nfa::compile(std::span<const Pattern> patterns) {
|
||||
i64 num_states = 0;
|
||||
|
||||
for (const Pattern &pattern : patterns) {
|
||||
num_states++;
|
||||
|
||||
for (const Pattern::Token &tok : pattern.tokens) {
|
||||
if (tok.kind == Pattern::STRING)
|
||||
num_states += tok.str.size();
|
||||
else if (tok.kind != Pattern::STAR)
|
||||
num_states++;
|
||||
}
|
||||
}
|
||||
|
||||
i64 num_words = (num_states + 63) / 64;
|
||||
initial_states.resize(num_words);
|
||||
star_states.resize(num_words);
|
||||
accept_states.resize(num_words);
|
||||
char_masks.resize(256 * num_words);
|
||||
values.resize(num_states, -1);
|
||||
|
||||
auto set_bit = [](std::vector<u64> &vec, i64 pos) {
|
||||
vec[pos / 64] |= 1ULL << (pos % 64);
|
||||
};
|
||||
|
||||
i64 state = 0;
|
||||
|
||||
for (const Pattern &pattern : patterns) {
|
||||
set_bit(initial_states, state);
|
||||
|
||||
for (const Pattern::Token &tok : pattern.tokens) {
|
||||
switch (tok.kind) {
|
||||
case Pattern::STRING:
|
||||
for (u8 c : tok.str) {
|
||||
state++;
|
||||
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
|
||||
}
|
||||
break;
|
||||
case Pattern::STAR:
|
||||
set_bit(star_states, state);
|
||||
break;
|
||||
case Pattern::QUESTION:
|
||||
state++;
|
||||
for (i64 c = 0; c < 256; c++)
|
||||
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
|
||||
break;
|
||||
case Pattern::BRACKET:
|
||||
state++;
|
||||
for (i64 c = 0; c < 256; c++)
|
||||
if (tok.chars[c])
|
||||
char_masks[c * num_words + state / 64] |= 1ULL << (state % 64);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
set_bit(accept_states, state);
|
||||
values[state] = pattern.value;
|
||||
state++;
|
||||
}
|
||||
}
|
||||
|
||||
i64 Glob::Nfa::match(std::string_view str) const {
|
||||
static thread_local std::vector<u64> states;
|
||||
states.assign(initial_states.begin(), initial_states.end());
|
||||
|
||||
i64 num_words = states.size();
|
||||
|
||||
for (u8 c : str) {
|
||||
const u64 *mask = char_masks.data() + c * num_words;
|
||||
u64 carry = 0;
|
||||
|
||||
for (i64 i = 0; i < num_words; i++) {
|
||||
u64 old = states[i];
|
||||
u64 next = (old << 1) | carry;
|
||||
states[i] = (old & star_states[i]) | (next & mask[i]);
|
||||
carry = old >> 63;
|
||||
}
|
||||
}
|
||||
|
||||
i64 value = -1;
|
||||
|
||||
for (i64 i = 0; i < num_words; i++) {
|
||||
u64 word = states[i] & accept_states[i];
|
||||
|
||||
while (word) {
|
||||
i64 bit = std::countr_zero(word);
|
||||
value = std::max(value, values[i * 64 + bit]);
|
||||
word &= word - 1;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool is_literal(std::string_view pat) {
|
||||
return pat.find_first_of("*?[\\") == pat.npos;
|
||||
}
|
||||
|
||||
bool Glob::add(std::string_view pat, i64 val) {
|
||||
assert(val >= 0);
|
||||
assert(!is_compiled);
|
||||
is_empty = false;
|
||||
max_value = std::max(max_value, val);
|
||||
|
||||
// Match-all, exact, prefix and suffix patterns are handled with
|
||||
// plain string comparisons instead of the matchers below, which
|
||||
// have to scan the entire input string on every query.
|
||||
if (pat == "*") {
|
||||
match_all = std::max(match_all, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_literal(pat)) {
|
||||
exacts.push_back({std::string(pat), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pat.ends_with('*') && is_literal(pat.substr(0, pat.size() - 1))) {
|
||||
prefixes.push_back({std::string(pat.substr(0, pat.size() - 1)), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pat.starts_with('*') && is_literal(pat.substr(1))) {
|
||||
suffixes.push_back({std::string(pat.substr(1)), val});
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the pattern requires only a single substring search, the
|
||||
// Aho-Corasick algorithm is even faster than our glob matcher.
|
||||
if (aho_corasick.can_handle(pat))
|
||||
return aho_corasick.add(pat, val);
|
||||
|
||||
if (std::optional<Pattern> pattern = Pattern::compile(pat, val)) {
|
||||
patterns.push_back(std::move(*pattern));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
i64 Glob::find(std::string_view str) {
|
||||
std::call_once(once, [&] {
|
||||
// If the same name was added more than once, keep only the entry
|
||||
// with the largest value, as find() returns the largest match.
|
||||
// Sorting by (name, negated value) places that entry first in
|
||||
// each run of duplicates, which is the one unique() keeps.
|
||||
ranges::sort(exacts, {}, [](const LiteralPattern &p) {
|
||||
return std::pair<std::string_view, i64>(p.pat, -p.value);
|
||||
});
|
||||
|
||||
auto dup = ranges::unique(exacts, {}, &LiteralPattern::pat);
|
||||
exacts.erase(dup.begin(), dup.end());
|
||||
|
||||
if (patterns.size() >= 64) {
|
||||
nfa.compile(patterns);
|
||||
patterns.clear();
|
||||
}
|
||||
|
||||
aho_corasick.compile();
|
||||
is_compiled = true;
|
||||
});
|
||||
|
||||
i64 val = match_all;
|
||||
if (val == max_value)
|
||||
return val;
|
||||
|
||||
auto it = ranges::lower_bound(exacts, str, {}, &LiteralPattern::pat);
|
||||
if (it != exacts.end() && it->pat == str)
|
||||
val = std::max(val, it->value);
|
||||
|
||||
for (const LiteralPattern &p : prefixes)
|
||||
if (val < p.value && str.starts_with(p.pat))
|
||||
val = p.value;
|
||||
|
||||
for (const LiteralPattern &p : suffixes)
|
||||
if (val < p.value && str.ends_with(p.pat))
|
||||
val = p.value;
|
||||
|
||||
if (val == max_value)
|
||||
return val;
|
||||
|
||||
val = std::max(val, aho_corasick.find(str));
|
||||
if (val == max_value)
|
||||
return val;
|
||||
|
||||
if (!nfa.empty())
|
||||
val = std::max(val, nfa.match(str));
|
||||
|
||||
for (const Pattern &p : patterns)
|
||||
if (val < p.value && p.match(str))
|
||||
val = p.value;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,25 +0,0 @@
|
||||
// This file implements HyperLogLog algorithm, which estimates
|
||||
// the number of unique items in a given multiset.
|
||||
//
|
||||
// For more info, read
|
||||
// https://engineering.fb.com/2018/12/13/data-infrastructure/hyperloglog
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace mold {
|
||||
|
||||
i64 HyperLogLog::get_cardinality() const {
|
||||
u8 buckets[NBUCKETS] = {};
|
||||
for (const Sketch &sketch : sketches)
|
||||
for (i64 i = 0; i < NBUCKETS; i++)
|
||||
buckets[i] = std::max(buckets[i], sketch.buckets[i]);
|
||||
|
||||
double z = 0;
|
||||
for (i64 val : buckets)
|
||||
z += std::ldexp(1.0, -val);
|
||||
return ALPHA * NBUCKETS * NBUCKETS / z;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-127
@@ -1,127 +0,0 @@
|
||||
// This file defines integral types for file input/output. We need to use
|
||||
// these types instead of the plain integers (such as uint32_t or int32_t)
|
||||
// when reading from/writing to an mmap'ed file area for the following
|
||||
// reasons:
|
||||
//
|
||||
// 1. mold is always a cross linker and should not depend on what host it
|
||||
// is running on. For example, users should be able to run mold on a
|
||||
// little-endian x86 machine to create a big-endian s390x binary.
|
||||
//
|
||||
// 2. Even though data members in all ELF data strucutres are naturally
|
||||
// aligned, they are not guaranteed to be aligned on memory because of
|
||||
// archive files. Archive files (.a files) align each file only to a
|
||||
// 2 byte boundary, so anything larger than 2 bytes may be misaligned
|
||||
// in an mmap'ed memory. Misaligned access is an undefined behavior in
|
||||
// C/C++, so we shouldn't cast an arbitrary pointer to a uint32_t, for
|
||||
// example, to read a 32 bit value.
|
||||
//
|
||||
// The data types defined in this file are independent of the host byte
|
||||
// order and are designed to avoid unaligned access.
|
||||
//
|
||||
// Note that in C/C++, memcpy is a portable and efficient way to access
|
||||
// unaligned data, as it is typically treated as an intrinsic. Compilers
|
||||
// can easily optimize memcpy calls in this file into a single load or
|
||||
// store instruction.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__)
|
||||
# if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define __LITTLE_ENDIAN__ 1
|
||||
# elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define __BIG_ENDIAN__ 1
|
||||
# else
|
||||
# error "unknown host byte order"
|
||||
# endif
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
template <typename T, bool is_le, int size = sizeof(T)>
|
||||
class Integer {
|
||||
public:
|
||||
constexpr Integer() = default;
|
||||
|
||||
constexpr Integer(T v) {
|
||||
if (std::is_constant_evaluated() || size == 3) {
|
||||
for (int i = 0; i < size; i++)
|
||||
buf[is_le ? i : (size - i - 1)] = v >> (i * 8);
|
||||
} else {
|
||||
if (!is_native)
|
||||
v = bswap(v);
|
||||
memcpy(buf, &v, size);
|
||||
}
|
||||
}
|
||||
|
||||
operator T() const {
|
||||
if constexpr (size == 3) {
|
||||
if constexpr (is_le)
|
||||
return buf[2] << 16 | buf[1] << 8 | buf[0];
|
||||
return buf[0] << 16 | buf[1] << 8 | buf[2];
|
||||
} else {
|
||||
T v;
|
||||
memcpy(&v, buf, size);
|
||||
return is_native ? v : bswap(v);
|
||||
}
|
||||
}
|
||||
|
||||
Integer &operator=(T v) { new (this) Integer(v); return *this; }
|
||||
Integer &operator++() { return *this = *this + 1; }
|
||||
Integer operator++(int) { auto x = *this; ++*this; return x; }
|
||||
Integer &operator--() { return *this = *this - 1; }
|
||||
Integer operator--(int) { auto x = *this; --*this; return x; }
|
||||
Integer &operator+=(T v) { return *this = *this + v; }
|
||||
Integer &operator-=(T v) { return *this = *this - v; }
|
||||
Integer &operator&=(T v) { return *this = *this & v; }
|
||||
Integer &operator|=(T v) { return *this = *this | v; }
|
||||
|
||||
private:
|
||||
static constexpr bool is_native =
|
||||
(std::endian::native == (is_le ? std::endian::little : std::endian::big));
|
||||
|
||||
static T bswap(T v) {
|
||||
switch (size) {
|
||||
case 2: return __builtin_bswap16(v);
|
||||
case 4: return __builtin_bswap32(v);
|
||||
case 8: return __builtin_bswap64(v);
|
||||
default: __builtin_unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t buf[size];
|
||||
};
|
||||
|
||||
using i8 = int8_t;
|
||||
using i16 = int16_t;
|
||||
using i32 = int32_t;
|
||||
using i64 = int64_t;
|
||||
|
||||
using u8 = uint8_t;
|
||||
using u16 = uint16_t;
|
||||
using u32 = uint32_t;
|
||||
using u64 = uint64_t;
|
||||
|
||||
using il16 = Integer<i16, true>;
|
||||
using il32 = Integer<i32, true>;
|
||||
using il64 = Integer<i64, true>;
|
||||
|
||||
using ul16 = Integer<u16, true>;
|
||||
using ul24 = Integer<u32, true, 3>;
|
||||
using ul32 = Integer<u32, true>;
|
||||
using ul64 = Integer<u64, true>;
|
||||
|
||||
using ib16 = Integer<i16, false>;
|
||||
using ib32 = Integer<i32, false>;
|
||||
using ib64 = Integer<i64, false>;
|
||||
|
||||
using ub16 = Integer<u16, false>;
|
||||
using ub24 = Integer<u32, false, 3>;
|
||||
using ub32 = Integer<u32, false>;
|
||||
using ub64 = Integer<u64, false>;
|
||||
|
||||
} // namespace mold
|
||||
-118
@@ -1,118 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <functional>
|
||||
#include <iomanip>
|
||||
#include <ios>
|
||||
#include <tbb/concurrent_vector.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/resource.h>
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
i64 Counter::get_value() {
|
||||
return values.combine(std::plus());
|
||||
}
|
||||
|
||||
void Counter::print() {
|
||||
ranges::stable_sort(instances, {}, [](Counter *x) { return x->get_value(); });
|
||||
|
||||
for (Counter *c : instances)
|
||||
std::cout << std::setw(20) << std::right << c->name
|
||||
<< "=" << c->get_value() << "\n";
|
||||
}
|
||||
|
||||
static i64 now_nsec() {
|
||||
return (i64)std::chrono::steady_clock::now().time_since_epoch().count();
|
||||
}
|
||||
|
||||
static std::pair<i64, i64> get_usage() {
|
||||
#ifdef _WIN32
|
||||
auto to_nsec = [](FILETIME t) -> i64 {
|
||||
return (((u64)t.dwHighDateTime << 32) + (u64)t.dwLowDateTime) * 100;
|
||||
};
|
||||
|
||||
FILETIME creation, exit, kernel, user;
|
||||
GetProcessTimes(GetCurrentProcess(), &creation, &exit, &kernel, &user);
|
||||
return {to_nsec(user), to_nsec(kernel)};
|
||||
#else
|
||||
auto to_nsec = [](struct timeval t) -> i64 {
|
||||
return (i64)t.tv_sec * 1'000'000'000 + t.tv_usec * 1'000;
|
||||
};
|
||||
|
||||
struct rusage ru;
|
||||
getrusage(RUSAGE_SELF, &ru);
|
||||
return {to_nsec(ru.ru_utime), to_nsec(ru.ru_stime)};
|
||||
#endif
|
||||
}
|
||||
|
||||
TimerRecord::TimerRecord(std::string name, TimerRecord *parent)
|
||||
: name(name), parent(parent) {
|
||||
start = now_nsec();
|
||||
std::tie(user, sys) = get_usage();
|
||||
if (parent)
|
||||
parent->children.push_back(this);
|
||||
}
|
||||
|
||||
void TimerRecord::stop() {
|
||||
if (stopped)
|
||||
return;
|
||||
stopped = true;
|
||||
|
||||
i64 user2;
|
||||
i64 sys2;
|
||||
std::tie(user2, sys2) = get_usage();
|
||||
|
||||
end = now_nsec();
|
||||
user = user2 - user;
|
||||
sys = sys2 - sys;
|
||||
}
|
||||
|
||||
static void print_rec(TimerRecord &rec, i64 indent) {
|
||||
printf(" % 8.3f % 8.3f % 8.3f %s%s\n",
|
||||
((double)rec.user / 1'000'000'000),
|
||||
((double)rec.sys / 1'000'000'000),
|
||||
(((double)rec.end - rec.start) / 1'000'000'000),
|
||||
std::string(indent * 2, ' ').c_str(),
|
||||
rec.name.c_str());
|
||||
|
||||
ranges::stable_sort(rec.children, {}, &TimerRecord::start);
|
||||
|
||||
for (TimerRecord *child : rec.children)
|
||||
print_rec(*child, indent + 1);
|
||||
}
|
||||
|
||||
void print_timer_records(
|
||||
tbb::concurrent_vector<std::unique_ptr<TimerRecord>> &records) {
|
||||
for (i64 i = records.size() - 1; i >= 0; i--)
|
||||
records[i]->stop();
|
||||
|
||||
for (i64 i = 0; i < records.size(); i++) {
|
||||
TimerRecord &inner = *records[i];
|
||||
if (inner.parent)
|
||||
continue;
|
||||
|
||||
for (i64 j = i - 1; j >= 0; j--) {
|
||||
TimerRecord &outer = *records[j];
|
||||
if (outer.start <= inner.start && inner.end <= outer.end) {
|
||||
inner.parent = &outer;
|
||||
outer.children.push_back(&inner);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << " User System Real Name\n";
|
||||
|
||||
for (std::unique_ptr<TimerRecord> &rec : records)
|
||||
if (!rec->parent)
|
||||
print_rec(*rec, 0);
|
||||
|
||||
std::cout << std::flush;
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,20 +0,0 @@
|
||||
#include "lib.h"
|
||||
|
||||
#include <random>
|
||||
|
||||
namespace mold {
|
||||
|
||||
void get_random_bytes(u8 *buf, i64 size) {
|
||||
std::random_device rand;
|
||||
i64 i = 0;
|
||||
|
||||
for (; i < size - 4; i += 4) {
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, 4);
|
||||
}
|
||||
|
||||
u32 val = rand();
|
||||
memcpy(buf + i, &val, size - i);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
// This is a header-only C++20 implementation of SipHash based on the
|
||||
// reference implementation. To use, just copy this header file into
|
||||
// your project and #include it.
|
||||
//
|
||||
// https://github.com/rui314/siphash/blob/main/siphash.h
|
||||
|
||||
#include <bit>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
template <int C_ROUNDS, int D_ROUNDS, int OUTLEN>
|
||||
class SipHashTmpl {
|
||||
public:
|
||||
static_assert(OUTLEN == 64 || OUTLEN == 128);
|
||||
|
||||
SipHashTmpl(void *key) {
|
||||
uint64_t k0 = read64(key);
|
||||
uint64_t k1 = read64((char *)key + 8);
|
||||
|
||||
v0 = 0x736f6d6570736575 ^ k0;
|
||||
v1 = 0x646f72616e646f6d ^ k1;
|
||||
v2 = 0x6c7967656e657261 ^ k0;
|
||||
v3 = 0x7465646279746573 ^ k1;
|
||||
|
||||
if (OUTLEN == 128)
|
||||
v1 ^= 0xee;
|
||||
}
|
||||
|
||||
void update(void *msgp, int64_t msglen) {
|
||||
char *msg = (char *)msgp;
|
||||
sum += msglen;
|
||||
|
||||
if (buflen) {
|
||||
if (buflen + msglen < 8) {
|
||||
memcpy(buf + buflen, msg, msglen);
|
||||
buflen += msglen;
|
||||
return;
|
||||
}
|
||||
|
||||
int j = 8 - buflen;
|
||||
memcpy(buf + buflen, msg, j);
|
||||
compress(read64(buf));
|
||||
|
||||
msg += j;
|
||||
msglen -= j;
|
||||
buflen = 0;
|
||||
}
|
||||
|
||||
while (msglen >= 8) {
|
||||
compress(read64(msg));
|
||||
msg += 8;
|
||||
msglen -= 8;
|
||||
}
|
||||
|
||||
memcpy(buf, msg, msglen);
|
||||
buflen = msglen;
|
||||
}
|
||||
|
||||
void finish(void *out) {
|
||||
memset(buf + buflen, 0, 8 - buflen);
|
||||
compress(((uint64_t)sum << 56) | read64(buf));
|
||||
|
||||
v2 ^= (OUTLEN == 128) ? 0xee : 0xff;
|
||||
finalize();
|
||||
write64(out, v0 ^ v1 ^ v2 ^ v3);
|
||||
|
||||
if (OUTLEN == 128) {
|
||||
v1 ^= 0xdd;
|
||||
finalize();
|
||||
write64((char *)out + 8, v0 ^ v1 ^ v2 ^ v3);
|
||||
}
|
||||
}
|
||||
|
||||
static void hash(void *out, void *key, void *in, int inlen) {
|
||||
SipHashTmpl<C_ROUNDS, D_ROUNDS, OUTLEN> h(key);
|
||||
h.update(in, inlen);
|
||||
h.finish(out);
|
||||
}
|
||||
|
||||
private:
|
||||
uint64_t v0, v1, v2, v3;
|
||||
uint8_t buf[8];
|
||||
uint8_t buflen = 0;
|
||||
uint8_t sum = 0;
|
||||
|
||||
uint64_t read64(void *loc) {
|
||||
uint64_t val;
|
||||
memcpy(&val, loc, 8);
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
return val;
|
||||
}
|
||||
|
||||
void write64(void *loc, uint64_t val) {
|
||||
if (std::endian::native == std::endian::big)
|
||||
val = bswap(val);
|
||||
memcpy(loc, &val, 8);
|
||||
}
|
||||
|
||||
uint64_t bswap(uint64_t val) {
|
||||
return ((val << 56) & 0xff00000000000000) |
|
||||
((val << 40) & 0x00ff000000000000) |
|
||||
((val << 24) & 0x0000ff0000000000) |
|
||||
((val << 8) & 0x000000ff00000000) |
|
||||
((val >> 8) & 0x00000000ff000000) |
|
||||
((val >> 24) & 0x0000000000ff0000) |
|
||||
((val >> 40) & 0x000000000000ff00) |
|
||||
((val >> 56) & 0x00000000000000ff);
|
||||
}
|
||||
|
||||
void round() {
|
||||
v0 += v1;
|
||||
v1 = std::rotl(v1, 13);
|
||||
v1 ^= v0;
|
||||
v0 = std::rotl(v0, 32);
|
||||
v2 += v3;
|
||||
v3 = std::rotl(v3, 16);
|
||||
v3 ^= v2;
|
||||
v0 += v3;
|
||||
v3 = std::rotl(v3, 21);
|
||||
v3 ^= v0;
|
||||
v2 += v1;
|
||||
v1 = std::rotl(v1, 17);
|
||||
v1 ^= v2;
|
||||
v2 = std::rotl(v2, 32);
|
||||
}
|
||||
|
||||
void compress(uint64_t m) {
|
||||
v3 ^= m;
|
||||
for (int i = 0; i < C_ROUNDS; i++)
|
||||
round();
|
||||
v0 ^= m;
|
||||
}
|
||||
|
||||
void finalize() {
|
||||
for (int i = 0; i < D_ROUNDS; i++)
|
||||
round();
|
||||
}
|
||||
};
|
||||
|
||||
using SipHash = SipHashTmpl<2, 4, 64>;
|
||||
using SipHash128 = SipHashTmpl<2, 4, 128>;
|
||||
using SipHash13 = SipHashTmpl<1, 3, 64>;
|
||||
using SipHash13_128 = SipHashTmpl<1, 3, 128>;
|
||||
-119
@@ -1,119 +0,0 @@
|
||||
// This file contains functions to create a tar file.
|
||||
|
||||
#include "lib.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
# define ftruncate _chsize_s
|
||||
#endif
|
||||
|
||||
namespace mold {
|
||||
|
||||
static constexpr i64 BLOCK_SIZE = 512;
|
||||
|
||||
// A tar file consists of one or more Ustar header followed by data.
|
||||
// Each Ustar header represents a single file in an archive.
|
||||
//
|
||||
// tar is an old file format, and its `name` field is only 100 bytes long.
|
||||
// If `name` is longer than 100 bytes, we can emit a PAX header before a
|
||||
// Ustar header to store a long filename.
|
||||
//
|
||||
// For simplicity, we always emit a PAX header even for a short filename.
|
||||
struct UstarHeader {
|
||||
char name[100];
|
||||
char mode[8];
|
||||
char uid[8];
|
||||
char gid[8];
|
||||
char size[12];
|
||||
char mtime[12];
|
||||
char checksum[8];
|
||||
char typeflag[1];
|
||||
char linkname[100];
|
||||
char magic[6];
|
||||
char version[2];
|
||||
char uname[32];
|
||||
char gname[32];
|
||||
char devmajor[8];
|
||||
char devminor[8];
|
||||
char prefix[155];
|
||||
char pad[12];
|
||||
};
|
||||
|
||||
static_assert(sizeof(UstarHeader) == BLOCK_SIZE);
|
||||
|
||||
static void finalize(UstarHeader &hdr) {
|
||||
memset(hdr.checksum, ' ', sizeof(hdr.checksum));
|
||||
memcpy(hdr.magic, "ustar", 5);
|
||||
memcpy(hdr.version, "00", 2);
|
||||
|
||||
// Compute checksum
|
||||
int sum = 0;
|
||||
for (i64 i = 0; i < sizeof(hdr); i++)
|
||||
sum += ((u8 *)&hdr)[i];
|
||||
|
||||
// We need to convince the compiler that sum isn't too big to silence
|
||||
// -Werror=format-truncation.
|
||||
if (sum >= 01'000'000)
|
||||
unreachable();
|
||||
snprintf(hdr.checksum, sizeof(hdr.checksum), "%06o", sum);
|
||||
}
|
||||
|
||||
static std::string encode_path(std::string basedir, std::string path) {
|
||||
path = path_clean(basedir + "/" + path);
|
||||
|
||||
// Construct a string which contains something like
|
||||
// "16 path=foo/bar\n" where 16 is the size of the string
|
||||
// including the size string itself.
|
||||
i64 len = std::string(" path=\n").size() + path.size();
|
||||
i64 total = std::to_string(len).size() + len;
|
||||
total = std::to_string(total).size() + len;
|
||||
return std::to_string(total) + " path=" + path + "\n";
|
||||
}
|
||||
|
||||
std::unique_ptr<TarWriter>
|
||||
TarWriter::open(std::string output_path, std::string basedir) {
|
||||
FILE *out = fopen(output_path.c_str(), "w");
|
||||
if (!out)
|
||||
return nullptr;
|
||||
return std::unique_ptr<TarWriter>(new TarWriter(out, basedir));
|
||||
}
|
||||
|
||||
TarWriter::~TarWriter() {
|
||||
fclose(out);
|
||||
}
|
||||
|
||||
void TarWriter::append(std::string path, std::string_view data) {
|
||||
// Write PAX header
|
||||
UstarHeader pax = {};
|
||||
|
||||
std::string attr = encode_path(basedir, path);
|
||||
|
||||
if (attr.size() >= 0100'000'000'000)
|
||||
unreachable();
|
||||
snprintf(pax.size, sizeof(pax.size), "%011zo", attr.size());
|
||||
pax.name[0] = '/';
|
||||
pax.typeflag[0] = 'x';
|
||||
finalize(pax);
|
||||
fwrite(&pax, sizeof(pax), 1, out);
|
||||
|
||||
// Write pathname
|
||||
fwrite(attr.data(), attr.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// Write Ustar header
|
||||
UstarHeader ustar = {};
|
||||
memcpy(ustar.mode, "0000664", 8);
|
||||
if (data.size() >= 0100'000'000'000)
|
||||
unreachable();
|
||||
snprintf(ustar.size, sizeof(ustar.size), "%011zo", data.size());
|
||||
finalize(ustar);
|
||||
fwrite(&ustar, sizeof(ustar), 1, out);
|
||||
|
||||
// Write file contents
|
||||
fwrite(data.data(), data.size(), 1, out);
|
||||
fseek(out, align_to(ftell(out), BLOCK_SIZE), SEEK_SET);
|
||||
|
||||
// A tar file must ends with two empty blocks
|
||||
(void)!ftruncate(fileno(out), ftell(out) + BLOCK_SIZE * 2);
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
@@ -1,32 +0,0 @@
|
||||
# Embed the current git commit hash into the mold executable. We ask git
|
||||
# instead of parsing files under .git because the repository format varies
|
||||
# (e.g. packed refs, reftable). When building from a source tarball, there's
|
||||
# no .git, and the hash is simply omitted.
|
||||
if(EXISTS "${SOURCE_DIR}/.git")
|
||||
execute_process(
|
||||
COMMAND git -C "${SOURCE_DIR}" rev-parse HEAD
|
||||
OUTPUT_VARIABLE HASH
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_QUIET
|
||||
RESULT_VARIABLE EXIT_CODE)
|
||||
|
||||
if(NOT EXIT_CODE EQUAL 0)
|
||||
set(HASH "")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Create new file contents and update a given file if necessary.
|
||||
if("${HASH}" STREQUAL "")
|
||||
set(NEW_CONTENTS "")
|
||||
else()
|
||||
set(NEW_CONTENTS "#define MOLD_GIT_HASH \"${HASH}\"\n")
|
||||
endif()
|
||||
|
||||
if(EXISTS "${OUTPUT_FILE}")
|
||||
file(READ "${OUTPUT_FILE}" OLD_CONTENTS)
|
||||
if(NOT "${NEW_CONTENTS}" STREQUAL "${OLD_CONTENTS}")
|
||||
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
|
||||
endif()
|
||||
else()
|
||||
file(WRITE "${OUTPUT_FILE}" "${NEW_CONTENTS}")
|
||||
endif()
|
||||
@@ -0,0 +1,2 @@
|
||||
[toolchain]
|
||||
channel = "stable"
|
||||
@@ -0,0 +1,2 @@
|
||||
use_small_heuristics = "Max"
|
||||
use_field_init_shorthand = true
|
||||
@@ -1,917 +0,0 @@
|
||||
// ARM32 is a bit special from the linker's viewpoint because ARM
|
||||
// processors support two different instruction encodings: Thumb and
|
||||
// ARM (in a narrower sense). Thumb instructions are either 16 bits or
|
||||
// 32 bits, while ARM instructions are all 32 bits. Feature-wise,
|
||||
// Thumb is a subset of ARM, so not all ARM instructions are
|
||||
// representable in Thumb.
|
||||
//
|
||||
// ARM processors originally supported only ARM instructions. Thumb
|
||||
// instructions were later added to increase code density.
|
||||
//
|
||||
// ARM processors runs in either ARM mode or Thumb mode. The mode can
|
||||
// be switched using BX (branch and mode exchange)-family instructions.
|
||||
// We need to use that instructions to, for example, call a function
|
||||
// encoded in Thumb from a function encoded in ARM. Sometimes, the
|
||||
// linker even has to emit interworking thunk code to switch mode.
|
||||
//
|
||||
// ARM instructions are aligned to 4 byte boundaries. Thumb are to 2
|
||||
// byte boundaries. So the least significant bit of a function address
|
||||
// is always 0.
|
||||
//
|
||||
// To distinguish Thumb functions from ARM fucntions, the LSB of a
|
||||
// function address is repurposed as a boolean flag. If the LSB is 0,
|
||||
// the function referred to by the address is encoded in ARM;
|
||||
// otherwise, Thumb.
|
||||
//
|
||||
// For example, if a symbol `foo` is of type STT_FUNC and has value
|
||||
// 0x2001, `foo` is a function using Thumb instructions whose address
|
||||
// is 0x2000 (not 0x2001, as Thumb instructions are always 2-byte
|
||||
// aligned). Likewise, if a function pointer has value 0x2001, it
|
||||
// refers a Thumb function at 0x2000.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst
|
||||
|
||||
#if MOLD_ARM32LE || MOLD_ARM32BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/parallel_for_each.h>
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
U32<E> *arm = (U32<E> *)loc;
|
||||
U16<E> *thm = (U16<E> *)loc;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
return (I32<E>)*arm;
|
||||
case R_ARM_THM_JUMP8:
|
||||
return sign_extend(thm[0], 8) << 1;
|
||||
case R_ARM_THM_JUMP11:
|
||||
return sign_extend(thm[0], 11) << 1;
|
||||
case R_ARM_THM_JUMP19: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/B--Branch-
|
||||
u32 S = bit(thm[0], 10);
|
||||
u32 J2 = bit(thm[1], 11);
|
||||
u32 J1 = bit(thm[1], 13);
|
||||
u32 imm6 = bits(thm[0], 5, 0);
|
||||
u32 imm11 = bits(thm[1], 10, 0);
|
||||
u32 val = (S << 20) | (J2 << 19) | (J1 << 18) | (imm6 << 12) | (imm11 << 1);
|
||||
return sign_extend(val, 21);
|
||||
}
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/BL--BLX--immediate---Branch-with-Link-and-optional-Exchange--immediate--
|
||||
u32 S = bit(thm[0], 10);
|
||||
u32 J1 = bit(thm[1], 13);
|
||||
u32 J2 = bit(thm[1], 11);
|
||||
u32 I1 = !(J1 ^ S);
|
||||
u32 I2 = !(J2 ^ S);
|
||||
u32 imm10 = bits(thm[0], 9, 0);
|
||||
u32 imm11 = bits(thm[1], 10, 0);
|
||||
u32 val = (S << 24) | (I1 << 23) | (I2 << 22) | (imm10 << 12) | (imm11 << 1);
|
||||
return sign_extend(val, 25);
|
||||
}
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_TLS_CALL:
|
||||
return sign_extend(*arm, 24) << 2;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOV--MOVS--immediate---Move--immediate--
|
||||
u32 imm4 = bits(*arm, 19, 16);
|
||||
u32 imm12 = bits(*arm, 11, 0);
|
||||
u32 val = (imm4 << 12) | imm12;
|
||||
return sign_extend(val, 16);
|
||||
}
|
||||
case R_ARM_PREL31:
|
||||
return sign_extend(*arm, 31);
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS: {
|
||||
// https://developer.arm.com/documentation/ddi0597/2024-12/Base-Instructions/MOVT--Move-Top-
|
||||
u32 imm4 = bits(thm[0], 3, 0);
|
||||
u32 i = bit(thm[0], 10);
|
||||
u32 imm3 = bits(thm[1], 14, 12);
|
||||
u32 imm8 = bits(thm[1], 7, 0);
|
||||
u32 val = (imm4 << 12) | (i << 11) | (imm3 << 8) | imm8;
|
||||
return sign_extend(val, 16);
|
||||
}
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void write_arm_mov(u8 *loc, u32 val) {
|
||||
u32 imm12 = bits(val, 11, 0);
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xfff0'f000) | (imm4 << 16) | imm12;
|
||||
}
|
||||
|
||||
static void write_thm_b21(u8 *loc, u32 val) {
|
||||
u32 S = bit(val, 20);
|
||||
u32 J2 = bit(val, 19);
|
||||
u32 J1 = bit(val, 18);
|
||||
u32 imm6 = bits(val, 17, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1100'0000) | (S << 10) | imm6;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_b25(u8 *loc, u32 val) {
|
||||
u32 S = bit(val, 24);
|
||||
u32 I1 = bit(val, 23);
|
||||
u32 I2 = bit(val, 22);
|
||||
u32 J1 = !I1 ^ S;
|
||||
u32 J2 = !I2 ^ S;
|
||||
u32 imm10 = bits(val, 21, 12);
|
||||
u32 imm11 = bits(val, 11, 1);
|
||||
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1000'0000'0000) | (S << 10) | imm10;
|
||||
buf[1] = (buf[1] & 0b1101'0000'0000'0000) | (J1 << 13) | (J2 << 11) | imm11;
|
||||
}
|
||||
|
||||
static void write_thm_mov(u8 *loc, u32 val) {
|
||||
u32 imm4 = bits(val, 15, 12);
|
||||
u32 i = bit(val, 11);
|
||||
u32 imm3 = bits(val, 10, 8);
|
||||
u32 imm8 = bits(val, 7, 0);
|
||||
|
||||
U16<E> *buf = (U16<E> *)loc;
|
||||
buf[0] = (buf[0] & 0b1111'1011'1111'0000) | (i << 10) | imm4;
|
||||
buf[1] = (buf[1] & 0b1000'1111'0000'0000) | (imm3 << 12) | imm8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TLS_GD32:
|
||||
case R_ARM_TLS_LDM32:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_TLS_IE32:
|
||||
case R_ARM_TLS_LE32:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_TARGET2:
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xff00) | bits(val, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
*(U16<E> *)loc = (*(U16<E> *)loc & 0xf800) | bits(val, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_THM_JUMP24:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
write_thm_b25(loc, val);
|
||||
break;
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_arm_mov(loc, val);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0x8000'0000) | (val & 0x7fff'ffff);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov(loc, val);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ul32 insn[] = {
|
||||
0xe52d'e004, // push {lr}
|
||||
0xe59f'e004, // ldr lr, 2f
|
||||
0xe08f'e00e, // 1: add lr, pc, lr
|
||||
0xe5be'f008, // ldr pc, [lr, #8]!
|
||||
0x0000'0000, // 2: .word .got.plt - 1b - 8
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
0x0000'0000, // (padding)
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(U32<E> *)(buf + 16) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 16;
|
||||
}
|
||||
|
||||
static constexpr ul32 plt_entry[] = {
|
||||
0xe59f'c004, // 1: ldr ip, 2f
|
||||
0xe08c'c00f, // add ip, ip, pc
|
||||
0xe59c'f000, // ldr pc, [ip]
|
||||
0x0000'0000, // 2: .word sym@GOT - 1b
|
||||
};
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(U32<E> *)(buf + 12) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
memcpy(buf, plt_entry, sizeof(plt_entry));
|
||||
*(U32<E> *)(buf + 12) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 12;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
*(U32<E> *)loc = val;
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(U32<E> *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || rel.r_type == R_ARM_V4BX)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 T = is_thumb_func(ctx, sym);
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto get_thumb_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P); };
|
||||
auto get_arm_thunk_addr = [&] { return sym.get_thunk_addr(ctx, P) + 4; };
|
||||
|
||||
auto get_tlsdesc_trampoline_addr = [&] {
|
||||
auto it = ranges::upper_bound(output_section->thunks, P, {},
|
||||
[](std::unique_ptr<Thunk<E>> &thunk) {
|
||||
return thunk->get_addr();
|
||||
});
|
||||
return (*it)->get_addr();
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
break;
|
||||
case R_ARM_REL32:
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_ARM_THM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP.W
|
||||
break;
|
||||
}
|
||||
|
||||
// THM_CALL relocation refers to either BL or BLX instruction.
|
||||
// They are different in only one bit. We need to use BLX if the
|
||||
// jump target is an ARM function. Otherwise, use BL.
|
||||
i64 val1 = S + A - P;
|
||||
i64 val2 = align_to(S + A - P, 4);
|
||||
bool arm = is_arm_func(ctx, sym);
|
||||
|
||||
if (!arm && is_int(val1, 25)) {
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, val1);
|
||||
} else if (arm && is_int(val2, 25)) {
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // BLX
|
||||
write_thm_b25(loc, val2);
|
||||
} else {
|
||||
*(U16<E> *)(loc + 2) |= 0x1000; // BL
|
||||
write_thm_b25(loc, get_thumb_thunk_addr() + A - P);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_BASE_PREL:
|
||||
*(U32<E> *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_ARM_GOTOFF32:
|
||||
*(U32<E> *)loc = ((S + A) | T) - GOT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_TARGET2:
|
||||
*(U32<E> *)loc = GOT + G + A - P;
|
||||
break;
|
||||
case R_ARM_GOT_BREL:
|
||||
*(U32<E> *)loc = G + A;
|
||||
break;
|
||||
case R_ARM_CALL: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like THM_CALL, ARM_CALL relocation refers to either BL or
|
||||
// BLX instruction. We may need to rewrite BL → BLX or BLX → BL.
|
||||
bool is_bl = ((*(U32<E> *)loc & 0xff00'0000) == 0xeb00'0000);
|
||||
bool is_blx = ((*(U32<E> *)loc & 0xfe00'0000) == 0xfa00'0000);
|
||||
if (!is_bl && !is_blx)
|
||||
Fatal(ctx) << *this << ": R_ARM_CALL refers to neither BL nor BLX";
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (is_int(val, 26)) {
|
||||
if (T) {
|
||||
*(U32<E> *)loc = 0xfa00'0000; // BLX
|
||||
*(U32<E> *)loc |= (bit(val, 1) << 24) | bits(val, 25, 2);
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(val, 25, 2);
|
||||
}
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xeb00'0000; // BL
|
||||
*(U32<E> *)loc |= bits(get_arm_thunk_addr() + A - P, 25, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case R_ARM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// These relocs refers to a B (unconditional branch) instruction.
|
||||
// Unlike BL or BLX, we can't rewrite B to BX in place when the
|
||||
// processor mode switch is required because BX doesn't takes an
|
||||
// immediate; it takes only a register. So if mode switch is
|
||||
// required, we jump to a linker-synthesized thunk which does the
|
||||
// job with a longer code sequence.
|
||||
i64 val = S + A - P;
|
||||
if (T || !is_int(val, 26))
|
||||
val = get_arm_thunk_addr() + A - P;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
break;
|
||||
}
|
||||
case R_ARM_PLT32:
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0xe320'f000; // NOP
|
||||
} else {
|
||||
u64 val = (T ? get_arm_thunk_addr() : S) + A - P;
|
||||
*(U32<E> *)loc = (*(U32<E> *)loc & 0xff00'0000) | bits(val, 25, 2);
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_JUMP8:
|
||||
check(S + A - P, -(1 << 8), 1 << 8);
|
||||
*(U16<E> *)loc &= 0xff00;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 8, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP11:
|
||||
check(S + A - P, -(1 << 11), 1 << 11);
|
||||
*(U16<E> *)loc &= 0xf800;
|
||||
*(U16<E> *)loc |= bits(S + A - P, 11, 1);
|
||||
break;
|
||||
case R_ARM_THM_JUMP19:
|
||||
check(S + A - P, -(1 << 20), 1 << 20);
|
||||
write_thm_b21(loc, S + A - P);
|
||||
break;
|
||||
case R_ARM_THM_JUMP24: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
*(U32<E> *)loc = 0x8000'f3af; // NOP
|
||||
break;
|
||||
}
|
||||
|
||||
// Just like R_ARM_JUMP24, we need to jump to a thunk if we need to
|
||||
// switch processor mode.
|
||||
i64 val = S + A - P;
|
||||
if (is_arm_func(ctx, sym) || !is_int(val, 25))
|
||||
val = get_thumb_thunk_addr() + A - P;
|
||||
write_thm_b25(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
write_arm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
write_arm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
write_thm_mov(loc, ((S + A) | T) - P);
|
||||
break;
|
||||
case R_ARM_PREL31:
|
||||
check(S + A - P, -(1LL << 30), 1LL << 30);
|
||||
*(U32<E> *)loc &= 0x8000'0000;
|
||||
*(U32<E> *)loc |= (S + A - P) & 0x7fff'ffff;
|
||||
break;
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
write_thm_mov(loc, (S + A) | T);
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
write_arm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
write_thm_mov(loc, (S + A - P) >> 16);
|
||||
break;
|
||||
case R_ARM_MOVT_ABS:
|
||||
write_arm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
write_thm_mov(loc, (S + A) >> 16);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
*(U32<E> *)loc = sym.get_tlsgd_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
*(U32<E> *)loc = ctx.got->get_tlsld_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) + A - P;
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
*(U32<E> *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
// ARM32 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in r0.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: bl foo
|
||||
// R_ARM_TLS_CALL
|
||||
// .L2: .word foo + . - .L1
|
||||
// R_ARM_TLS_GOTDESC
|
||||
//
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: nop
|
||||
// ...
|
||||
// .L2: .word foo(tpoff)
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// ldr r0, .L2
|
||||
// .L1: ldr r0, [pc, r0]
|
||||
// ...
|
||||
// .L2: .word foo(gottpoff) + . - .L1
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// A is odd if the corresponding TLS_CALL is Thumb.
|
||||
*(U32<E> *)loc = sym.get_tlsdesc_addr(ctx) - P + A - ((A & 1) ? 6 : 4);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(U32<E> *)loc = sym.get_gottp_addr(ctx) - P + A - ((A & 1) ? 5 : 8);
|
||||
} else {
|
||||
*(U32<E> *)loc = S - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(U32<E> *)loc = 0xeb00'0000; // bl 0
|
||||
*(U32<E> *)loc |= bits(get_tlsdesc_trampoline_addr() - P - 8, 25, 2);
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(U32<E> *)loc = 0xe79f'0000; // ldr r0, [pc, r0]
|
||||
} else {
|
||||
*(U32<E> *)loc = 0xe320'f000; // nop
|
||||
}
|
||||
break;
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
u64 val = align_to(get_tlsdesc_trampoline_addr() - P - 4, 4);
|
||||
write_thm_b25(loc, val);
|
||||
*(U16<E> *)(loc + 2) &= ~0x1000; // rewrite BL with BLX
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
// Since `ldr r0, [pc, r0]` is not representable in Thumb,
|
||||
// we use two instructions instead.
|
||||
*(U16<E> *)loc = 0x4478; // add r0, pc
|
||||
*(U16<E> *)(loc + 2) = 0x6800; // ldr r0, [r0]
|
||||
} else {
|
||||
*(U32<E> *)loc = 0x8000'f3af; // nop.w
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(*this, rel);
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_ABS32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_ARM_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U32<E> *)loc = *val;
|
||||
else
|
||||
*(U32<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_ARM_MOVW_ABS_NC:
|
||||
case R_ARM_THM_MOVW_ABS_NC:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_THM_CALL:
|
||||
case R_ARM_CALL:
|
||||
case R_ARM_JUMP24:
|
||||
case R_ARM_PLT32:
|
||||
case R_ARM_THM_JUMP24:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_ARM_GOT_PREL:
|
||||
case R_ARM_GOT_BREL:
|
||||
case R_ARM_TARGET2:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_ARM_MOVT_PREL:
|
||||
case R_ARM_THM_MOVT_PREL:
|
||||
case R_ARM_PREL31:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_TLS_GD32:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_ARM_TLS_LDM32:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_ARM_TLS_IE32:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_ARM_TLS_CALL:
|
||||
case R_ARM_THM_TLS_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_ARM_TLS_LE32:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_ARM_ABS32:
|
||||
case R_ARM_TARGET1:
|
||||
case R_ARM_MOVT_ABS:
|
||||
case R_ARM_THM_MOVT_ABS:
|
||||
case R_ARM_REL32:
|
||||
case R_ARM_BASE_PREL:
|
||||
case R_ARM_GOTOFF32:
|
||||
case R_ARM_THM_JUMP8:
|
||||
case R_ARM_THM_JUMP11:
|
||||
case R_ARM_THM_JUMP19:
|
||||
case R_ARM_MOVW_PREL_NC:
|
||||
case R_ARM_THM_MOVW_PREL_NC:
|
||||
case R_ARM_TLS_LDO32:
|
||||
case R_ARM_V4BX:
|
||||
case R_ARM_TLS_GOTDESC:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// TLS trampoline code. ARM32's TLSDESC is designed so that this
|
||||
// common piece of code is factored out from object files to reduce
|
||||
// output size. Since no one provide, the linker has to synthesize it.
|
||||
constexpr ul32 hdr[] = {
|
||||
0xe08e'0000, // add r0, lr, r0
|
||||
0xe590'1004, // ldr r1, [r0, #4]
|
||||
0xe12f'ff11, // bx r1
|
||||
0xe320'f000, // nop
|
||||
};
|
||||
|
||||
// This is a range extension and mode switch thunk.
|
||||
// It has two entry points: +0 for Thumb and +4 for ARM.
|
||||
constexpr u8 entry[] = {
|
||||
// .thumb
|
||||
0x78, 0x47, // bx pc # jumps to 1f
|
||||
0xc0, 0x46, // nop
|
||||
// .arm
|
||||
0x00, 0xc0, 0x9f, 0xe5, // 1: ldr ip, 3f
|
||||
0x0f, 0xf0, 0x8c, 0xe0, // 2: add pc, ip, pc
|
||||
0x00, 0x00, 0x00, 0x00, // 3: .word sym - 2b
|
||||
};
|
||||
|
||||
static_assert(E::thunk_hdr_size == sizeof(hdr));
|
||||
static_assert(E::thunk_size == sizeof(entry));
|
||||
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
memcpy(base, hdr, sizeof(hdr));
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
memcpy(buf, entry, sizeof(entry));
|
||||
*(U32<E> *)(buf + 12) = S - P - 16;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
u64 get_eflags(Context<E> &ctx) {
|
||||
if constexpr (E::is_le)
|
||||
return EF_ARM_EABI_VER5;
|
||||
else
|
||||
return EF_ARM_EABI_VER5 | EF_ARM_BE8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void create_arm_exidx_section<E>(Context<E> &ctx) {
|
||||
for (i64 i = 0; i < ctx.chunks.size(); i++) {
|
||||
OutputSection<E> *osec = ctx.chunks[i]->to_osec();
|
||||
|
||||
if (osec && osec->shdr.sh_type == SHT_ARM_EXIDX) {
|
||||
auto *sec = new Arm32ExidxSection(*osec);
|
||||
ctx.extra.exidx = sec;
|
||||
ctx.chunks[i] = sec;
|
||||
ctx.chunk_pool.emplace_back(sec);
|
||||
|
||||
for (InputSection<E> *isec : osec->members)
|
||||
isec->kill();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::compute_section_size(Context<E> &ctx) {
|
||||
output_section.compute_section_size(ctx);
|
||||
this->shdr.sh_size = output_section.shdr.sh_size + 8; // +8 for sentinel
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::update_shdr(Context<E> &ctx) {
|
||||
// .ARM.exidx's sh_link should be set to the .text section index.
|
||||
// Runtime doesn't care about it, but the binutils's strip command does.
|
||||
if (Chunk<E> *chunk = find_chunk(ctx, ".text"))
|
||||
this->shdr.sh_link = chunk->shndx;
|
||||
}
|
||||
|
||||
// Returns the end of the text segment
|
||||
static u64 get_text_end(Context<E> &ctx) {
|
||||
u64 ret = 0;
|
||||
for (Chunk<E> *chunk : ctx.chunks)
|
||||
if (chunk->shdr.sh_flags & SHF_EXECINSTR)
|
||||
ret = std::max<u64>(ret, chunk->shdr.sh_addr + chunk->shdr.sh_size);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ARM executables use an .ARM.exidx section to look up an exception
|
||||
// handling record for the current instruction pointer. The table needs
|
||||
// to be sorted by their addresses.
|
||||
//
|
||||
// Other target uses .eh_frame_hdr instead for the same purpose.
|
||||
// I don't know why only ARM uses the different mechanism, but it's
|
||||
// likely that it's due to some historical reason.
|
||||
//
|
||||
// This function returns contents of .ARM.exidx.
|
||||
template <>
|
||||
std::vector<u8> Arm32ExidxSection<E>::get_contents(Context<E> &ctx) {
|
||||
// .ARM.exidx records consists of a signed 31-bit relative address
|
||||
// and a 32-bit value. The relative address indicates the start
|
||||
// address of a function that the record covers. The value is one of
|
||||
// the followings:
|
||||
//
|
||||
// 1. CANTUNWIND indicating that there's no unwinding info for the function,
|
||||
// 2. a compact unwinding record encoded into a 32-bit value, or
|
||||
// 3. a 31-bit relative address which points to a larger record in
|
||||
// the .ARM.extab section.
|
||||
//
|
||||
// CANTUNWIND is value 1. The most significant bit is set in (2) but
|
||||
// not in (3). So we can distinguished them just by looking at a value.
|
||||
const u32 CANTUNWIND = 1;
|
||||
|
||||
struct Entry {
|
||||
U32<E> addr;
|
||||
U32<E> val;
|
||||
};
|
||||
|
||||
// We reserve one extra slot for the sentinel
|
||||
i64 num_entries = output_section.shdr.sh_size / sizeof(Entry) + 1;
|
||||
std::vector<u8> buf(num_entries * sizeof(Entry));
|
||||
Entry *ent = (Entry *)buf.data();
|
||||
|
||||
// Write section contents to the buffer
|
||||
output_section.shdr.sh_addr = this->shdr.sh_addr;
|
||||
output_section.write_to(ctx, buf.data());
|
||||
|
||||
// Fill in sentinel fields
|
||||
u64 sentinel_addr = this->shdr.sh_addr + sizeof(Entry) * (num_entries - 1);
|
||||
ent[num_entries - 1].addr = get_text_end(ctx) - sentinel_addr;
|
||||
ent[num_entries - 1].val = CANTUNWIND;
|
||||
|
||||
// Entry's addresses are relative to themselves. In order to sort
|
||||
// records by address, we first translate them so that the addresses
|
||||
// are relative to the beginning of the section.
|
||||
auto is_relative = [](u32 val) {
|
||||
return val != CANTUNWIND && !(val & 0x8000'0000);
|
||||
};
|
||||
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = sign_extend(ent[i].addr, 31) + offset;
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val + offset);
|
||||
});
|
||||
|
||||
ranges::sort(ent, ent + num_entries, {}, &Entry::addr);
|
||||
|
||||
// Remove duplicate adjacent entries. That is, if two adjacent functions
|
||||
// have the same compact unwind info or are both CANTUNWIND, we can
|
||||
// merge them into a single address range.
|
||||
auto tail = ranges::unique(ent, ent + num_entries, {}, &Entry::val);
|
||||
num_entries -= tail.size();
|
||||
buf.resize(num_entries * sizeof(Entry));
|
||||
|
||||
// Make addresses relative to themselves.
|
||||
tbb::parallel_for((i64)0, num_entries, [&](i64 i) {
|
||||
i64 offset = sizeof(Entry) * i;
|
||||
ent[i].addr = 0x7fff'ffff & (ent[i].addr - offset);
|
||||
if (is_relative(ent[i].val))
|
||||
ent[i].val = 0x7fff'ffff & (ent[i].val - offset);
|
||||
});
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::remove_duplicate_entries(Context<E> &ctx) {
|
||||
this->shdr.sh_size = get_contents(ctx).size();
|
||||
}
|
||||
|
||||
template <>
|
||||
void Arm32ExidxSection<E>::copy_buf(Context<E> &ctx) {
|
||||
std::vector<u8> contents = get_contents(ctx);
|
||||
assert(this->shdr.sh_size == contents.size());
|
||||
write_vector(ctx.buf + this->shdr.sh_offset, contents);
|
||||
}
|
||||
|
||||
// Even though using ARM32 in big-endian mode is very rare, the processor
|
||||
// technically supports both little- and big-endian modes. There are two
|
||||
// variants of big-endian mode: BE32 and BE8. In BE32, instructions and
|
||||
// data are encoded in big-endian. In BE8, instructions are encoded in
|
||||
// little-endian, and only data is in big-endian. BE8 is the de facto
|
||||
// standard for ARMv6 or later. We support only BE8.
|
||||
//
|
||||
// A tricky thing is that instructions in an object file are always
|
||||
// big-endian if the file is compiled for big-endian mode. In other words,
|
||||
// the compiler always emit code in BE32 if -mbig-endian is specified. It
|
||||
// is the linker's responsibility to rewrite instructions from big-endian
|
||||
// to little-endian for an BE8 output. This function does that.
|
||||
//
|
||||
// The text section may contain a mix of 32-bit ARM instructions, 16-bit
|
||||
// Thumb instructions, and data. We need to distinguish them to swap 4
|
||||
// bytes, 2 bytes, or not swap bytes, respectively. The beginning of ARM
|
||||
// code, Thumb code, and data is labeled with a mapping symbol of $a, $t,
|
||||
// and $d, respectively. We use mapping symbols to determine what to do
|
||||
// with the text section.
|
||||
//
|
||||
// This function is called after we copy the input section contents to the
|
||||
// output file. We rewrite instructions in the output buffer in place.
|
||||
#if MOLD_ARM32BE
|
||||
void arm32be_swap_bytes(Context<E> &ctx) {
|
||||
tbb::parallel_for_each(ctx.objs, [&](ObjectFile<E> *file) {
|
||||
// Collect mapping symbols
|
||||
std::vector<Symbol<E> *> syms;
|
||||
|
||||
for (Symbol<E> *sym : file->get_local_syms())
|
||||
if (InputSection<E> *isec = sym->get_input_section())
|
||||
if (isec->is_alive() && (isec->shdr().sh_flags & SHF_EXECINSTR))
|
||||
if (std::string_view x = sym->name();
|
||||
x == "$a" || x.starts_with("$a.") ||
|
||||
x == "$t" || x.starts_with("$t.") ||
|
||||
x == "$d" || x.starts_with("$d."))
|
||||
syms.push_back(sym);
|
||||
|
||||
// Group mapping symbols by input section and sort by address
|
||||
ranges::stable_sort(syms, {}, [](const Symbol<E> *sym) {
|
||||
return std::tuple{(uintptr_t)sym->get_input_section(), sym->value};
|
||||
});
|
||||
|
||||
// Swap bytes
|
||||
for (i64 i = 0; i < syms.size(); i++) {
|
||||
Symbol<E> &sym = *syms[i];
|
||||
if (sym.name().starts_with("$d"))
|
||||
continue;
|
||||
|
||||
InputSection<E> &isec = *sym.get_input_section();
|
||||
u8 *base = ctx.buf + isec.output_section->shdr.sh_offset + isec.offset;
|
||||
u8 *start = base + sym.value;
|
||||
u8 *end;
|
||||
|
||||
if (i + 1 < syms.size() && syms[i + 1]->get_input_section() == &isec)
|
||||
end = base + syms[i + 1]->value;
|
||||
else
|
||||
end = base + isec.sh_size;
|
||||
|
||||
i64 sz = sym.name().starts_with("$a") ? 4 : 2;
|
||||
for (u8 *p = start; p < end; p += sz)
|
||||
std::reverse(p, p + sz);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -1,729 +0,0 @@
|
||||
// This file contains ARM64-specific code. Being new, the ARM64's ELF
|
||||
// psABI doesn't have anything peculiar. ARM64 is a clean RISC
|
||||
// instruction set that supports PC-relative load/store instructions.
|
||||
//
|
||||
// Unlike ARM32, instructions length doesn't vary. All ARM64
|
||||
// instructions are 4 bytes long.
|
||||
//
|
||||
// Branch instructions used for function call can jump within ±128 MiB.
|
||||
// We need to create range extension thunks to support binaries whose
|
||||
// .text is larger than that.
|
||||
//
|
||||
// Unlike most other targets, the TLSDESC access model is used by default
|
||||
// for -fPIC to access thread-local variables instead of the less
|
||||
// efficient GD model. You can still enable GD but it needs the
|
||||
// -mtls-dialect=trad flag. Since GD is used rarely, we don't need to
|
||||
// implement GD → LE relaxation.
|
||||
//
|
||||
// https://github.com/ARM-software/abi-aa/blob/main/aaelf64/aaelf64.rst
|
||||
|
||||
#if MOLD_ARM64LE || MOLD_ARM64BE
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = MOLD_TARGET;
|
||||
|
||||
static void write_adrp(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 13, 12) << 29) | (bits(val, 32, 14) << 5);
|
||||
}
|
||||
|
||||
static void write_adr(u8 *buf, u64 val) {
|
||||
*(ul32 *)buf |= (bits(val, 1, 0) << 29) | (bits(val, 20, 2) << 5);
|
||||
}
|
||||
|
||||
static void write_movn_movz(u8 *buf, i64 val) {
|
||||
*(ul32 *)buf &= 0b0000'0000'0110'0000'0000'0000'0001'1111;
|
||||
|
||||
if (val >= 0)
|
||||
*(ul32 *)buf |= 0xd280'0000 | (bits(val, 15, 0) << 5); // rewrite to movz
|
||||
else
|
||||
*(ul32 *)buf |= 0x9280'0000 | (bits(~val, 15, 0) << 5); // rewrite to movn
|
||||
}
|
||||
|
||||
static u64 page(u64 val) {
|
||||
return val & 0xffff'ffff'ffff'f000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
constexpr ul32 insn[] = {
|
||||
0xa9bf'7bf0, // stp x16, x30, [sp,#-16]!
|
||||
0x9000'0010, // adrp x16, .got.plt[2]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[2]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[2]
|
||||
0xd61f'0220, // br x17
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 gotplt = ctx.gotplt->shdr.sh_addr + 16;
|
||||
u64 plt = ctx.plt->shdr.sh_addr;
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf + 4, page(gotplt) - page(plt + 4));
|
||||
*(ul32 *)(buf + 8) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 12) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
constexpr ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, .got.plt[n]
|
||||
0xf940'0211, // ldr x17, [x16, .got.plt[n]]
|
||||
0x9100'0210, // add x16, x16, .got.plt[n]
|
||||
0xd61f'0220, // br x17
|
||||
};
|
||||
|
||||
u64 gotplt = sym.get_gotplt_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(gotplt) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(gotplt, 11, 3) << 10;
|
||||
*(ul32 *)(buf + 8) |= (gotplt & 0xfff) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
constexpr ul32 insn[] = {
|
||||
0x9000'0010, // adrp x16, GOT[n]
|
||||
0xf940'0211, // ldr x17, [x16, GOT[n]]
|
||||
0xd61f'0220, // br x17
|
||||
0xd420'7d00, // brk
|
||||
};
|
||||
|
||||
u64 got = sym.get_got_pltgot_addr(ctx);
|
||||
u64 plt = sym.get_plt_addr(ctx);
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
write_adrp(buf, page(got) - page(plt));
|
||||
*(ul32 *)(buf + 4) |= bits(got, 11, 3) << 10;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
u64 P = this->shdr.sh_addr + offset;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, isec, rel, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
*(U64<E> *)loc = val;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
check(val - P, -(1LL << 31), 1LL << 31);
|
||||
*(U32<E> *)loc = val - P;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(U64<E> *)loc = val - P;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_adrp(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADRP--Form-PC-relative-address-to-4KB-page-
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 24) & 0b1001'1111) == 0b1001'0000;
|
||||
}
|
||||
|
||||
static bool is_ldr(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/LDR--immediate---Load-Register--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1111'1001'0100;
|
||||
}
|
||||
|
||||
static bool is_add(u8 *loc) {
|
||||
// https://developer.arm.com/documentation/ddi0596/2021-12/Base-Instructions/ADD--immediate---Add--immediate--
|
||||
u32 insn = *(ul32 *)loc;
|
||||
return (bits(insn, 31, 20) & 0b1111'1111'1100) == 0b1001'0001'0000;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto rewrite = [&](i64 idx, u32 ty) {
|
||||
if (ctx.arg.emit_relocs)
|
||||
rels[idx].r_type = ty;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
break;
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 1) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 2) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 11, 4) << 10;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
check(S + A, 0, 1LL << 48);
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
*(ul32 *)loc |= bits(S + A, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G0:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, S + A - P);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G1:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, (i64)(S + A - P) >> 16);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G2:
|
||||
check(S + A - P, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, (i64)(S + A - P) >> 32);
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G2_NC:
|
||||
*(ul32 *)loc |= bits(S + A - P, 47, 32) << 5;
|
||||
break;
|
||||
case R_AARCH64_MOVW_PREL_G3:
|
||||
*(ul32 *)loc |= bits(S + A - P, 63, 48) << 5;
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
if (sym.has_got(ctx)) {
|
||||
i64 val = page(G + GOT + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
// Relax GOT-loading ADRP+LDR to an immediate ADRP+ADD
|
||||
i64 val = page(S + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
|
||||
u32 reg = bits(*(ul32 *)loc, 4, 0);
|
||||
*(ul32 *)(loc + 4) = 0x9100'0000 | (reg << 5) | reg; // ADD
|
||||
*(ul32 *)(loc + 4) |= bits(S + A, 11, 0) << 10;
|
||||
|
||||
rewrite(i, R_AARCH64_ADR_PREL_PG_HI21);
|
||||
rewrite(i + 1, R_AARCH64_ADD_ABS_LO12_NC);
|
||||
i++;
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC: {
|
||||
// The ARM64 psABI defines that an `ADRP x0, foo` and `ADD x0, x0,
|
||||
// :lo12: foo` instruction pair to materialize a PC-relative address
|
||||
// in a register can be relaxed to `NOP` followed by `ADR x0, foo`
|
||||
// if foo is in PC ± 1 MiB.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
i64 val = S + A - P - 4;
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (is_int(val, 21) &&
|
||||
rel2.r_type == R_AARCH64_ADD_ABS_LO12_NC &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_addend == rel.r_addend &&
|
||||
is_adrp(loc) &&
|
||||
is_add(loc + 4)) {
|
||||
u32 reg1 = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 reg2 = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (reg1 == reg2) {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
*(ul32 *)(loc + 4) = 0x1000'0000 | reg1; // adr
|
||||
write_adr(loc + 4, val);
|
||||
rewrite(i, R_NONE);
|
||||
rewrite(i + 1, R_AARCH64_ADR_PREL_LO21);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i64 val = page(S + A) - page(P);
|
||||
if (rel.r_type == R_AARCH64_ADR_PREL_PG_HI21)
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
write_adr(loc, S + A - P);
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26: {
|
||||
if (sym.is_remaining_undef_weak()) {
|
||||
// On ARM, calling an weak undefined symbol jumps to the
|
||||
// next instruction.
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
break;
|
||||
}
|
||||
|
||||
i64 val = S + A - P;
|
||||
if (!is_int(val, 28))
|
||||
val = sym.get_thunk_addr(ctx, P) + A - P;
|
||||
*(ul32 *)loc |= bits(val, 27, 2);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_PLT32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_GOTPCREL32:
|
||||
check(G + GOT + A - P, -(1LL << 31), 1LL << 31);
|
||||
*(U32<E> *)loc = G + GOT + A - P;
|
||||
break;
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
check(S + A - P, -(1LL << 20), 1LL << 20);
|
||||
*(ul32 *)loc |= bits(S + A - P, 20, 2) << 5;
|
||||
break;
|
||||
case R_AARCH64_PREL16:
|
||||
check(S + A - P, -(1LL << 15), 1LL << 16);
|
||||
*(U16<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_PREL32:
|
||||
check(S + A - P, -(1LL << 31), 1LL << 32);
|
||||
*(U32<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_PREL64:
|
||||
*(U64<E> *)loc = S + A - P;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
*(ul32 *)loc |= bits(G + GOT + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15: {
|
||||
i64 val = G + GOT + A - page(GOT);
|
||||
check(val, 0, 1 << 15);
|
||||
*(ul32 *)loc |= bits(val, 14, 3) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: {
|
||||
i64 val = page(sym.get_gottp_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
write_movn_movz(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 31), 1LL << 31);
|
||||
write_movn_movz(loc, val >> 16);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 31, 16) << 5;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, -(1LL << 47), 1LL << 47);
|
||||
write_movn_movz(loc, val >> 32);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12: {
|
||||
i64 val = S + A - ctx.tp_addr;
|
||||
check(val, 0, 1LL << 24);
|
||||
*(ul32 *)loc |= bits(val, 23, 12) << 10;
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
check(S + A - ctx.tp_addr, 0, 1 << 12);
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21: {
|
||||
i64 val = page(sym.get_tlsgd_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
break;
|
||||
}
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
*(ul32 *)loc |= bits(sym.get_tlsgd_addr(ctx) + A, 11, 0) << 10;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
// ARM64 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in x0.
|
||||
//
|
||||
// adrp x0, 0
|
||||
// R_AARCH64_TLSDESC_ADR_PAGE21 foo
|
||||
// ldr x1, [x0]
|
||||
// R_AARCH64_TLSDESC_LD64_LO12 foo
|
||||
// add x0, x0, #0
|
||||
// R_AARCH64_TLSDESC_ADD_LO12 foo
|
||||
// blr x1
|
||||
// R_AARCH64_TLSDESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// movz x0, :tls_offset_hi:foo, lsl #16
|
||||
// movk x0, :tls_offset_lo:foo
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// nop
|
||||
// nop
|
||||
// adrp x0, :gottprel:foo
|
||||
// ldr x0, [x0, :gottprel_lo12:foo]
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
i64 val = page(sym.get_tlsdesc_addr(ctx) + A) - page(P);
|
||||
check(val, -(1LL << 32), 1LL << 32);
|
||||
write_adrp(loc, val);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
rewrite(i, R_NONE);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 3) << 10;
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd503'201f; // nop
|
||||
rewrite(i, R_NONE);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc |= bits(sym.get_tlsdesc_addr(ctx) + A, 11, 0) << 10;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0x9000'0000; // adrp x0, 0
|
||||
write_adrp(loc, page(sym.get_gottp_addr(ctx) + A) - page(P));
|
||||
rewrite(i, R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xd2a0'0000; // movz x0, 0, lsl #16
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 32, 16) << 5;
|
||||
rewrite(i, R_AARCH64_TLSLE_MOVW_TPREL_G1);
|
||||
}
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
// Do nothing
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
*(ul32 *)loc = 0xf940'0000; // ldr x0, [x0, 0]
|
||||
*(ul32 *)loc |= bits(sym.get_gottp_addr(ctx) + A, 11, 3) << 10;
|
||||
rewrite(i, R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC);
|
||||
} else {
|
||||
*(ul32 *)loc = 0xf280'0000; // movk x0, 0
|
||||
*(ul32 *)loc |= bits(S + A - ctx.tp_addr, 15, 0) << 5;
|
||||
rewrite(i, R_AARCH64_TLSLE_MOVW_TPREL_G0_NC);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_ABS64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(U64<E> *)loc = S + A;
|
||||
break;
|
||||
case R_AARCH64_ABS32:
|
||||
check(S + A, 0, 1LL << 32);
|
||||
*(U32<E> *)loc = S + A;
|
||||
break;
|
||||
case R_AARCH64_TLS_DTPREL64:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(U64<E> *)loc = *val;
|
||||
else
|
||||
*(U64<E> *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = contents + rel.r_offset;
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_AARCH64_MOVW_UABS_G3:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ADR_GOT_PAGE:
|
||||
// An ADR_GOT_PAGE and GOT_LO12_NC relocation pair is used to load a
|
||||
// symbol's address from GOT. If the GOT value is a link-time
|
||||
// constant, we may be able to rewrite the ADRP+LDR instruction pair
|
||||
// with an ADRP+ADD, eliminating a GOT memory load.
|
||||
if (ctx.arg.relax && sym.is_pcrel_linktime_const(ctx) &&
|
||||
i + 1 < rels.size()) {
|
||||
// ADRP+LDR must be consecutive and use the same register to relax.
|
||||
const ElfRel<E> &rel2 = rels[i + 1];
|
||||
if (rel2.r_type == R_AARCH64_LD64_GOT_LO12_NC &&
|
||||
rel2.r_offset == rel.r_offset + 4 &&
|
||||
rel2.r_sym == rel.r_sym &&
|
||||
rel.r_addend == 0 &&
|
||||
rel2.r_addend == 0 &&
|
||||
is_adrp(loc) &&
|
||||
is_ldr(loc + 4)) {
|
||||
u32 rd = bits(*(ul32 *)loc, 4, 0);
|
||||
u32 rn = bits(*(ul32 *)(loc + 4), 9, 5);
|
||||
u32 rt = bits(*(ul32 *)(loc + 4), 4, 0);
|
||||
if (rd == rn && rn == rt) {
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_LD64_GOT_LO12_NC:
|
||||
case R_AARCH64_LD64_GOTPAGE_LO15:
|
||||
case R_AARCH64_GOTPCREL32:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_AARCH64_CALL26:
|
||||
case R_AARCH64_JUMP26:
|
||||
case R_AARCH64_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
|
||||
case R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_AARCH64_ADR_PREL_PG_HI21:
|
||||
case R_AARCH64_ADR_PREL_PG_HI21_NC:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_TLSGD_ADR_PAGE21:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_AARCH64_TLSDESC_CALL:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G2:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_AARCH64_ABS64:
|
||||
case R_AARCH64_ADD_ABS_LO12_NC:
|
||||
case R_AARCH64_ADR_PREL_LO21:
|
||||
case R_AARCH64_CONDBR19:
|
||||
case R_AARCH64_LD_PREL_LO19:
|
||||
case R_AARCH64_LDST16_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST32_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST64_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST128_ABS_LO12_NC:
|
||||
case R_AARCH64_LDST8_ABS_LO12_NC:
|
||||
case R_AARCH64_MOVW_UABS_G0:
|
||||
case R_AARCH64_MOVW_UABS_G0_NC:
|
||||
case R_AARCH64_MOVW_UABS_G1:
|
||||
case R_AARCH64_MOVW_UABS_G1_NC:
|
||||
case R_AARCH64_MOVW_UABS_G2:
|
||||
case R_AARCH64_MOVW_UABS_G2_NC:
|
||||
case R_AARCH64_MOVW_PREL_G0:
|
||||
case R_AARCH64_MOVW_PREL_G0_NC:
|
||||
case R_AARCH64_MOVW_PREL_G1:
|
||||
case R_AARCH64_MOVW_PREL_G1_NC:
|
||||
case R_AARCH64_MOVW_PREL_G2:
|
||||
case R_AARCH64_MOVW_PREL_G2_NC:
|
||||
case R_AARCH64_MOVW_PREL_G3:
|
||||
case R_AARCH64_PREL16:
|
||||
case R_AARCH64_PREL32:
|
||||
case R_AARCH64_PREL64:
|
||||
case R_AARCH64_TLSGD_ADD_LO12_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1:
|
||||
case R_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
|
||||
case R_AARCH64_TLSLE_ADD_TPREL_HI12:
|
||||
case R_AARCH64_TLSDESC_ADR_PAGE21:
|
||||
case R_AARCH64_TLSDESC_LD64_LO12:
|
||||
case R_AARCH64_TLSDESC_ADD_LO12:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The size of a thunk entry varies on ARM64 depending on the distance to
|
||||
// the branch target. This function computes the size of each thunk entry.
|
||||
template <>
|
||||
void Thunk<E>::shrink_size(Context<E> &ctx) {
|
||||
offsets.clear();
|
||||
offsets.push_back(0);
|
||||
i64 off = 0;
|
||||
|
||||
// The distance between S and P is only reduced by shrink_size(), but
|
||||
// page(S) – page(P) may still increase by one page due to address
|
||||
// changes, so we add a safety margin.
|
||||
//
|
||||
// For example, page(0x1200) – page(0x1000) is 0, whereas
|
||||
// page(0x1100) – page(0xfff) is 0x1000, even though the latter
|
||||
// distance is shorter than the former.
|
||||
auto is_small = [](i64 prel) {
|
||||
return is_int(prel + 0x1000, 33) && is_int(prel - 0x1000, 33);
|
||||
};
|
||||
|
||||
for (Symbol<E> *sym : symbols) {
|
||||
u64 S = sym->get_addr(ctx);
|
||||
u64 P = get_addr() + off;
|
||||
i64 prel = page(S) - page(P);
|
||||
off += is_small(prel) ? 12 : 24;
|
||||
offsets.push_back(off);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void Thunk<E>::copy_buf(Context<E> &ctx) {
|
||||
// Short thunk with a 33 bit displacement
|
||||
constexpr ul32 insn1[] = {
|
||||
0x9000'0010, // adrp x16, 0
|
||||
0x9100'0210, // add x16, x16
|
||||
0xd61f'0200, // br x16
|
||||
};
|
||||
|
||||
// Long thunk with a 64 bit displacement
|
||||
constexpr ul32 insn2[] = {
|
||||
0x1000'0010, // adr x16, 0
|
||||
0xd2a0'0011, // movz x17, 0, lsl #16
|
||||
0xf2c0'0011, // movk x17, 0, lsl #32
|
||||
0xf2e0'0011, // movk x17, 0, lsl #48
|
||||
0x8b11'0210, // add x16, x16, x17
|
||||
0xd61f'0200, // br x16
|
||||
};
|
||||
|
||||
u8 *base = ctx.buf + output_section.shdr.sh_offset + offset;
|
||||
|
||||
for (i64 i = 0; i < symbols.size(); i++) {
|
||||
u64 S = symbols[i]->get_addr(ctx);
|
||||
u64 P = get_addr() + offsets[i];
|
||||
u8 *buf = base + offsets[i];
|
||||
|
||||
if (offsets[i + 1] - offsets[i] == sizeof(insn1)) {
|
||||
i64 prel = page(S) - page(P);
|
||||
assert(is_int(prel, 33));
|
||||
memcpy(buf, insn1, sizeof(insn1));
|
||||
write_adrp(buf, prel);
|
||||
*(ul32 *)(buf + 4) |= bits(S, 11, 0) << 10;
|
||||
} else {
|
||||
memcpy(buf, insn2, sizeof(insn2));
|
||||
write_adr(buf, bits(S - P, 15, 0));
|
||||
*(ul32 *)(buf + 4) |= bits(S - P, 31, 16) << 5;
|
||||
*(ul32 *)(buf + 8) |= bits(S - P, 47, 32) << 5;
|
||||
*(ul32 *)(buf + 12) |= bits(S - P, 63, 48) << 5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template class Thunk<E>;
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
@@ -1,604 +0,0 @@
|
||||
// i386 is similar to x86-64 but lacks PC-relative memory access
|
||||
// instructions. So it's not straightforward to support position-
|
||||
// independent code (PIC) on that target.
|
||||
//
|
||||
// If an object file is compiled with -fPIC, a function that needs to load
|
||||
// a value from memory first obtains its own address with the following
|
||||
// code
|
||||
//
|
||||
// call __x86.get_pc_thunk.bx
|
||||
//
|
||||
// where __x86.get_pc_thunk.bx is defined as
|
||||
//
|
||||
// __x86.get_pc_thunk.bx:
|
||||
// mov (%esp), %ebx # move the return address to %ebx
|
||||
// ret
|
||||
//
|
||||
// . With the function's own address (or, more precisely, the address
|
||||
// immediately after the call instruction), the function can compute an
|
||||
// absolute address of a variable with its address + link-time constant.
|
||||
//
|
||||
// Executing call-mov-ret isn't very cheap, and allocating one register to
|
||||
// store PC isn't cheap too, especially given that i386 has only 8
|
||||
// general-purpose registers. But that's the cost of PIC on i386. You need
|
||||
// to pay it when creating a .so and a position-independent executable.
|
||||
//
|
||||
// When a position-independent function calls another function, it sets
|
||||
// %ebx to the address of .got. Position-independent PLT entries use that
|
||||
// register to load values from .got.plt/.got.
|
||||
//
|
||||
// If we are creating a position-dependent executable (PDE), we can't
|
||||
// assume that %ebx is set to .got. For PDE, we need to create position-
|
||||
// dependent PLT entries which don't use %ebx.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/i386.pdf
|
||||
|
||||
#if MOLD_I386
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = I386;
|
||||
|
||||
template <>
|
||||
i64 get_addend(u8 *loc, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
return *(i8 *)loc;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
return *(il16 *)loc;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
return *(il32 *)loc;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_addend(u8 *loc, i64 val, const ElfRel<E> &rel) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_NONE:
|
||||
break;
|
||||
case R_386_8:
|
||||
case R_386_PC8:
|
||||
*loc = val;
|
||||
break;
|
||||
case R_386_16:
|
||||
case R_386_PC16:
|
||||
*(ul16 *)loc = val;
|
||||
break;
|
||||
case R_386_32:
|
||||
case R_386_PC32:
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
case R_386_PLT32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_GOTPC:
|
||||
case R_386_TLS_LDM:
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_LE:
|
||||
case R_386_TLS_IE:
|
||||
case R_386_TLS_GD:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_GOTDESC:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0x51, // push %ecx
|
||||
0x8d, 0x8b, 0, 0, 0, 0, // lea GOTPLT+4(%ebx), %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 3) = ctx.gotplt->shdr.sh_addr - ctx.got->shdr.sh_addr + 4;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0x51, // push %ecx
|
||||
0xb9, 0, 0, 0, 0, // mov GOTPLT+4, %ecx
|
||||
0xff, 0x31, // push (%ecx)
|
||||
0xff, 0x61, 0x04, // jmp *0x4(%ecx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = ctx.gotplt->shdr.sh_addr + 4;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xb9, 0, 0, 0, 0, // mov $reloc_offset, %ecx
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, 0xcc, 0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 1) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ul32 *)(buf + 7) = sym.get_gotplt_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
if (ctx.arg.pic) {
|
||||
static const u8 insn[] = {
|
||||
0xff, 0xa3, 0, 0, 0, 0, // jmp *foo@GOT(%ebx)
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx) - ctx.got->shdr.sh_addr;
|
||||
} else {
|
||||
static const u8 insn[] = {
|
||||
0xff, 0x25, 0, 0, 0, 0, // jmp *foo@GOT
|
||||
0xcc, 0xcc, // (padding)
|
||||
};
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ul32 *)(buf + 2) = sym.get_got_pltgot_addr(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_386_32:
|
||||
*(ul32 *)loc = val;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_got32x(u8 *loc) {
|
||||
// mov imm(%reg1), %reg2 -> lea imm(%reg1), %reg2
|
||||
if (loc[0] == 0x8b)
|
||||
return 0x8d00 | loc[1];
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Relax GD to LE
|
||||
static void relax_gd_to_le(u8 *loc, ElfRel<E> rel, u64 val) {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xc0, 0, 0, 0, 0, // add $tp_offset, %eax
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32:
|
||||
memcpy(loc - 3, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = val;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X:
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = val;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
// Relax LD to LE
|
||||
static void relax_ld_to_le(u8 *loc, ElfRel<E> rel, u64 tls_size) {
|
||||
switch (rel.r_type) {
|
||||
case R_386_PLT32:
|
||||
case R_386_PC32: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x2d, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 5) = tls_size;
|
||||
break;
|
||||
}
|
||||
case R_386_GOT32:
|
||||
case R_386_GOT32X: {
|
||||
static const u8 insn[] = {
|
||||
0x65, 0xa1, 0, 0, 0, 0, // mov %gs:0, %eax
|
||||
0x81, 0xe8, 0, 0, 0, 0, // sub $tls_size, %eax
|
||||
};
|
||||
memcpy(loc - 2, insn, sizeof(insn));
|
||||
*(ul32 *)(loc + 6) = tls_size;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_ie(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x8b83; // lea 0(%ebx), %eax -> mov 0(%ebx), %eax
|
||||
case 0x8d9b: return 0x8b9b; // lea 0(%ebx), %ebx -> mov 0(%ebx), %ebx
|
||||
case 0x8d8b: return 0x8b8b; // lea 0(%ebx), %ecx -> mov 0(%ebx), %ecx
|
||||
case 0x8d93: return 0x8b93; // lea 0(%ebx), %edx -> mov 0(%ebx), %edx
|
||||
case 0x8db3: return 0x8bb3; // lea 0(%ebx), %esi -> mov 0(%ebx), %esi
|
||||
case 0x8dbb: return 0x8bbb; // lea 0(%ebx), %edi -> mov 0(%ebx), %edi
|
||||
case 0x8da3: return 0x8ba3; // lea 0(%ebx), %esp -> mov 0(%ebx), %esp
|
||||
case 0x8dab: return 0x8bab; // lea 0(%ebx), %ebp -> mov 0(%ebx), %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u32 relax_tlsdesc_to_le(u8 *loc) {
|
||||
switch ((loc[0] << 8) | loc[1]) {
|
||||
case 0x8d83: return 0x90b8; // lea 0(%ebx), %eax -> mov $0, %eax
|
||||
case 0x8d9b: return 0x90bb; // lea 0(%ebx), %ebx -> mov $0, %ebx
|
||||
case 0x8d8b: return 0x90b9; // lea 0(%ebx), %ecx -> mov $0, %ecx
|
||||
case 0x8d93: return 0x90ba; // lea 0(%ebx), %edx -> mov $0, %edx
|
||||
case 0x8db3: return 0x90be; // lea 0(%ebx), %esi -> mov $0, %esi
|
||||
case 0x8dbb: return 0x90bf; // lea 0(%ebx), %edi -> mov $0, %edi
|
||||
case 0x8da3: return 0x90bc; // lea 0(%ebx), %esp -> mov $0, %esp
|
||||
case 0x8dab: return 0x90bd; // lea 0(%ebx), %ebp -> mov $0, %ebp
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = get_addend(*this, rel);
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A - P, -(1 << 7), 1 << 7);
|
||||
*loc = S + A - P;
|
||||
break;
|
||||
case R_386_PC16:
|
||||
check(S + A - P, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
case R_386_PLT32:
|
||||
*(ul32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
*(ul32 *)loc = G + A;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
if (sym.has_got(ctx)) {
|
||||
*(ul32 *)loc = G + A;
|
||||
} else {
|
||||
u32 insn = relax_got32x(loc - 2);
|
||||
assert(insn);
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
if (ctx.arg.emit_relocs)
|
||||
rel.r_type = R_386_GOTOFF;
|
||||
}
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_386_TLS_IE:
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
if (sym.has_tlsgd(ctx))
|
||||
*(ul32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
else
|
||||
relax_gd_to_le(loc, rels[++i], S - ctx.tp_addr);
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
if (ctx.got->has_tlsld(ctx))
|
||||
*(ul32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
else
|
||||
relax_ld_to_le(loc, rels[++i], ctx.tp_addr - ctx.tls_begin);
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
// i386 TLSDESC uses the following code sequence to materialize
|
||||
// a TP-relative address in %eax.
|
||||
//
|
||||
// lea 0(%ebx), %eax
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// We may relax the instructions to the following if its TP-relative
|
||||
// address is known at link-time
|
||||
//
|
||||
// mov $foo@TPOFF, %eax
|
||||
// nop
|
||||
//
|
||||
// or to the following if the TP-relative address is known at
|
||||
// process startup time.
|
||||
//
|
||||
// mov foo@GOTTPOFF(%ebx), %eax
|
||||
// nop
|
||||
//
|
||||
// We allow the following alternative code sequence too because
|
||||
// LLVM emits such code.
|
||||
//
|
||||
// lea 0(%ebx), %reg
|
||||
// R_386_TLS_GOTDESC foo
|
||||
// mov %reg, %eax
|
||||
// call *(%eax)
|
||||
// R_386_TLS_DESC_CALL foo
|
||||
//
|
||||
// Note that the compiler always uses the local-exec TLS model
|
||||
// for -fno-pic, so TLSDESC code is always PIC (i.e. uses %ebx to
|
||||
// store the address of GOT.)
|
||||
if (sym.has_tlsdesc(ctx)) {
|
||||
*(ul32 *)loc = sym.get_tlsdesc_addr(ctx) + A - GOT;
|
||||
} else if (sym.has_gottp(ctx)) {
|
||||
u32 insn = relax_tlsdesc_to_ie(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
} else {
|
||||
u32 insn = relax_tlsdesc_to_le(loc - 2);
|
||||
if (!insn)
|
||||
Fatal(ctx) << *this << ": illegal instruction sequence for TLSDESC";
|
||||
loc[-2] = insn >> 8;
|
||||
loc[-1] = insn;
|
||||
*(ul32 *)loc = S + A - ctx.tp_addr;
|
||||
}
|
||||
break;
|
||||
case R_386_TLS_DESC_CALL:
|
||||
if (!sym.has_tlsdesc(ctx)) {
|
||||
// call *(%eax) -> nop
|
||||
loc[0] = 0x66;
|
||||
loc[1] = 0x90;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64 i) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : get_addend(*this, rel);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
check(S + A, 0, 1 << 8);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_16:
|
||||
check(S + A, 0, 1 << 16);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC8:
|
||||
check(S + A, -(1 << 7), 1 << 7);
|
||||
*loc = S + A;
|
||||
break;
|
||||
case R_386_PC16:
|
||||
check(S + A, -(1 << 15), 1 << 15);
|
||||
*(ul16 *)loc = S + A;
|
||||
break;
|
||||
case R_386_PC32:
|
||||
*(ul32 *)loc = S + A;
|
||||
break;
|
||||
case R_386_GOTPC:
|
||||
*(ul32 *)loc = GOT + A;
|
||||
break;
|
||||
case R_386_GOTOFF:
|
||||
*(ul32 *)loc = S + A - GOT;
|
||||
break;
|
||||
case R_386_TLS_LDO_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ul32 *)loc = *val;
|
||||
else
|
||||
*(ul32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_386_SIZE32:
|
||||
*(ul32 *)loc = sym.esym().st_size + A;
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
// Scan relocations
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = contents + rel.r_offset;
|
||||
|
||||
if (sym.is_ifunc())
|
||||
sym.flags |= NEEDS_GOT | NEEDS_PLT;
|
||||
|
||||
if (rel.r_type == R_386_TLS_GD || rel.r_type == R_386_TLS_LDM) {
|
||||
if (i + 1 == rels.size())
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
|
||||
if (u32 ty = rels[i + 1].r_type;
|
||||
ty != R_386_PLT32 && ty != R_386_PC32 &&
|
||||
ty != R_386_GOT32 && ty != R_386_GOT32X)
|
||||
Fatal(ctx) << *this << ": " << rel << " must be followed by PLT or GOT32";
|
||||
}
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_386_8:
|
||||
case R_386_16:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_PC8:
|
||||
case R_386_PC16:
|
||||
case R_386_PC32:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_GOT32:
|
||||
case R_386_GOTPC:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_386_GOT32X:
|
||||
// We always want to relax GOT32X even if --no-relax is given
|
||||
// because static PIE doesn't work without it.
|
||||
if (sym.is_pcrel_linktime_const(ctx) && relax_got32x(loc - 2)) {
|
||||
// Do nothing
|
||||
} else {
|
||||
sym.flags |= NEEDS_GOT;
|
||||
}
|
||||
break;
|
||||
case R_386_PLT32:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_386_TLS_GOTIE:
|
||||
case R_386_TLS_IE:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_386_TLS_GD:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && sym.is_tprel_linktime_const(ctx)))
|
||||
i++;
|
||||
else
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_386_TLS_LDM:
|
||||
// We always relax if -static because libc.a doesn't contain
|
||||
// __tls_get_addr().
|
||||
if (ctx.arg.static_ || (ctx.arg.relax && !ctx.arg.shared))
|
||||
i++;
|
||||
else
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_386_TLS_GOTDESC:
|
||||
scan_tlsdesc(ctx, sym);
|
||||
break;
|
||||
case R_386_TLS_LE:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_386_32:
|
||||
case R_386_GOTOFF:
|
||||
case R_386_TLS_LDO_32:
|
||||
case R_386_SIZE32:
|
||||
case R_386_TLS_DESC_CALL:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,322 +0,0 @@
|
||||
// This file contains code for the Motorola 68000 series microprocessors,
|
||||
// which is often abbreviated as m68k. Running a Unix-like system on a
|
||||
// m68k-based machine today is a retro-computing hobby activity, but the
|
||||
// processor was a popular choice to build Unix computers during '80s.
|
||||
// Early Sun workstations for example used m68k. Macintosh until 1994 were
|
||||
// based on m68k as well until they switched to PowerPC (and then to x86
|
||||
// and to ARM.)
|
||||
//
|
||||
// From the linker's point of view, it is not hard to support m68k. It's
|
||||
// just a 32-bit big-endian CISC ISA. Compared to comtemporary i386,
|
||||
// m68k's psABI is actually simpler because m68k has PC-relative memory
|
||||
// access instructions and therefore can support position-independent
|
||||
// code without too much hassle.
|
||||
//
|
||||
// https://github.com/rui314/psabi/blob/main/m68k.pdf
|
||||
|
||||
#if MOLD_M68K
|
||||
|
||||
#include "mold.h"
|
||||
|
||||
namespace mold {
|
||||
|
||||
using E = M68K;
|
||||
|
||||
template <>
|
||||
void write_plt_header(Context<E> &ctx, u8 *buf) {
|
||||
static const u8 insn[] = {
|
||||
0x2f, 0x00, // move.l %d0, -(%sp)
|
||||
0x2f, 0x3b, 0x01, 0x70, 0, 0, 0, 0, // move.l (GOTPLT+4, %pc), -(%sp)
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT+8, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 6) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr;
|
||||
*(ub32 *)(buf + 14) = ctx.gotplt->shdr.sh_addr - ctx.plt->shdr.sh_addr - 4;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_plt_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x20, 0x3c, 0, 0, 0, 0, // move.l PLT_OFFSET, %d0
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOTPLT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 2) = sym.get_plt_idx(ctx) * sizeof(ElfRel<E>);
|
||||
*(ub32 *)(buf + 10) = sym.get_gotplt_addr(ctx) - sym.get_plt_addr(ctx) - 8;
|
||||
}
|
||||
|
||||
template <>
|
||||
void write_pltgot_entry(Context<E> &ctx, u8 *buf, Symbol<E> &sym) {
|
||||
static const u8 insn[] = {
|
||||
0x4e, 0xfb, 0x01, 0x71, 0, 0, 0, 0, // jmp ([GOT_ENTRY, %pc])
|
||||
};
|
||||
|
||||
memcpy(buf, insn, sizeof(insn));
|
||||
*(ub32 *)(buf + 4) = sym.get_got_pltgot_addr(ctx) - sym.get_plt_addr(ctx) - 2;
|
||||
}
|
||||
|
||||
template <>
|
||||
void EhFrameSection<E>::apply_eh_reloc(Context<E> &ctx, InputSection<E> &isec,
|
||||
const ElfRel<E> &rel, u64 offset, u64 val) {
|
||||
u8 *loc = ctx.buf + this->shdr.sh_offset + offset;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_NONE:
|
||||
break;
|
||||
case R_68K_32:
|
||||
*(ub32 *)loc = val;
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
*(ub32 *)loc = val - this->shdr.sh_addr - offset;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << "unsupported relocation in .eh_frame: " << rel;
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_alloc(Context<E> &ctx, u8 *base) {
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE)
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
if (sym.get_type() == STT_TLS && sym.is_remaining_undef_weak())
|
||||
continue;
|
||||
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
u64 S = sym.get_addr(ctx);
|
||||
u64 A = rel.r_addend;
|
||||
u64 P = get_addr() + rel.r_offset;
|
||||
u64 G = sym.get_got_idx(ctx) * sizeof(Word<E>);
|
||||
u64 GOT = ctx.got->shdr.sh_addr;
|
||||
|
||||
auto check = [&](i64 val, i64 lo, i64 hi) {
|
||||
check_range(ctx, i, val, lo, hi);
|
||||
};
|
||||
|
||||
auto write16 = [&](u64 val) {
|
||||
check(val, 0, 1 << 16);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write16s = [&](u64 val) {
|
||||
check(val, -(1 << 15), 1 << 15);
|
||||
*(ub16 *)loc = val;
|
||||
};
|
||||
|
||||
auto write8 = [&](u64 val) {
|
||||
check(val, 0, 1 << 8);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
auto write8s = [&](u64 val) {
|
||||
check(val, -(1 << 7), 1 << 7);
|
||||
*loc = val;
|
||||
};
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
break;
|
||||
case R_68K_16:
|
||||
write16(S + A);
|
||||
break;
|
||||
case R_68K_8:
|
||||
write8(S + A);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PLT32:
|
||||
*(ub32 *)loc = S + A - P;
|
||||
break;
|
||||
case R_68K_PC16:
|
||||
case R_68K_PLT16:
|
||||
write16s(S + A - P);
|
||||
break;
|
||||
case R_68K_PC8:
|
||||
case R_68K_PLT8:
|
||||
write8s(S + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
*(ub32 *)loc = GOT + A - P;
|
||||
break;
|
||||
case R_68K_GOTPCREL16:
|
||||
write16s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTPCREL8:
|
||||
write8s(GOT + A - P);
|
||||
break;
|
||||
case R_68K_GOTOFF32:
|
||||
*(ub32 *)loc = G + A;
|
||||
break;
|
||||
case R_68K_GOTOFF16:
|
||||
write16(G + A);
|
||||
break;
|
||||
case R_68K_GOTOFF8:
|
||||
write8(G + A);
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
*(ub32 *)loc = sym.get_tlsgd_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_GD16:
|
||||
write16(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_GD8:
|
||||
write8(sym.get_tlsgd_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
*(ub32 *)loc = ctx.got->get_tlsld_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_LDM16:
|
||||
write16(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDM8:
|
||||
write8(ctx.got->get_tlsld_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LDO16:
|
||||
write16s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LDO8:
|
||||
write8s(S + A - ctx.dtp_addr);
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
*(ub32 *)loc = sym.get_gottp_addr(ctx) + A - GOT;
|
||||
break;
|
||||
case R_68K_TLS_IE16:
|
||||
write16(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_IE8:
|
||||
write8(sym.get_gottp_addr(ctx) + A - GOT);
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
*(ub32 *)loc = S + A - ctx.tp_addr;
|
||||
break;
|
||||
case R_68K_TLS_LE16:
|
||||
write16(S + A - ctx.tp_addr);
|
||||
break;
|
||||
case R_68K_TLS_LE8:
|
||||
write8(S + A - ctx.tp_addr);
|
||||
break;
|
||||
default:
|
||||
unreachable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::apply_reloc_nonalloc(Context<E> &ctx, u8 *base) {
|
||||
for_each_reloc(ctx, [&](const ElfRel<E> &rel, i64) ALWAYS_INLINE {
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
return;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
u8 *loc = base + rel.r_offset;
|
||||
|
||||
SectionFragment<E> *frag;
|
||||
i64 frag_addend;
|
||||
std::tie(frag, frag_addend) = get_fragment(ctx, rel);
|
||||
|
||||
u64 S = frag ? frag->get_addr(ctx) : sym.get_addr(ctx);
|
||||
u64 A = frag ? frag_addend : (i64)rel.r_addend;
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A;
|
||||
break;
|
||||
case R_68K_TLS_LDO32:
|
||||
if (std::optional<u64> val = get_tombstone(sym, frag))
|
||||
*(ub32 *)loc = *val;
|
||||
else
|
||||
*(ub32 *)loc = S + A - ctx.dtp_addr;
|
||||
break;
|
||||
default:
|
||||
Fatal(ctx) << *this << ": invalid relocation for non-allocated sections: "
|
||||
<< rel;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void InputSection<E>::scan_relocations(Context<E> &ctx) {
|
||||
assert(shdr().sh_flags & SHF_ALLOC);
|
||||
std::span<const ElfRel<E>> rels = get_rels(ctx);
|
||||
|
||||
for (i64 i = 0; i < rels.size(); i++) {
|
||||
const ElfRel<E> &rel = rels[i];
|
||||
if (rel.r_type == R_NONE || record_undef_error(ctx, rel))
|
||||
continue;
|
||||
|
||||
Symbol<E> &sym = *file->symbols[rel.r_sym];
|
||||
|
||||
if (sym.is_ifunc())
|
||||
Error(ctx) << sym << ": GNU ifunc symbol is not supported on m68k";
|
||||
|
||||
switch (rel.r_type) {
|
||||
case R_68K_16:
|
||||
case R_68K_8:
|
||||
scan_absrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_PC32:
|
||||
case R_68K_PC16:
|
||||
case R_68K_PC8:
|
||||
scan_pcrel(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_GOTPCREL32:
|
||||
case R_68K_GOTPCREL16:
|
||||
case R_68K_GOTPCREL8:
|
||||
case R_68K_GOTOFF32:
|
||||
case R_68K_GOTOFF16:
|
||||
case R_68K_GOTOFF8:
|
||||
sym.flags |= NEEDS_GOT;
|
||||
break;
|
||||
case R_68K_PLT32:
|
||||
case R_68K_PLT16:
|
||||
case R_68K_PLT8:
|
||||
if (sym.is_imported)
|
||||
sym.flags |= NEEDS_PLT;
|
||||
break;
|
||||
case R_68K_TLS_GD32:
|
||||
case R_68K_TLS_GD16:
|
||||
case R_68K_TLS_GD8:
|
||||
sym.flags |= NEEDS_TLSGD;
|
||||
break;
|
||||
case R_68K_TLS_LDM32:
|
||||
case R_68K_TLS_LDM16:
|
||||
case R_68K_TLS_LDM8:
|
||||
ctx.needs_tlsld = true;
|
||||
break;
|
||||
case R_68K_TLS_IE32:
|
||||
case R_68K_TLS_IE16:
|
||||
case R_68K_TLS_IE8:
|
||||
sym.flags |= NEEDS_GOTTP;
|
||||
break;
|
||||
case R_68K_TLS_LE32:
|
||||
case R_68K_TLS_LE16:
|
||||
case R_68K_TLS_LE8:
|
||||
check_tlsle(ctx, sym, rel);
|
||||
break;
|
||||
case R_68K_32:
|
||||
case R_68K_TLS_LDO32:
|
||||
case R_68K_TLS_LDO16:
|
||||
case R_68K_TLS_LDO8:
|
||||
break;
|
||||
default:
|
||||
Error(ctx) << *this << ": unknown relocation: " << rel;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mold
|
||||
|
||||
#endif
|
||||
Loaded 100 of 3731 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user