[MemProf] Templatize CallStackRadixTreeBuilder (NFC) (#117014)
[llvm-project.git] / llvm-libgcc / generate_version_script.py
blob5332869494a358687dc8f39e39bcbc95ee6dbcf7
1 #!/usr/bin/env python3
3 # Generates a version script for an architecture so that it can be incorporated
4 # into gcc_s.ver.
6 from collections import defaultdict
7 from itertools import chain
8 import argparse, subprocess, sys, os
11 def split_suffix(symbol):
12 """
13 Splits a symbol such as `__gttf2@GCC_3.0` into a triple representing its
14 function name (__gttf2), version name (GCC_3.0), and version number (300).
16 The version number acts as a priority. Since earlier versions are more
17 accessible and are likely to be used more, the lower the number is, the higher
18 its priortiy. A symbol that has a '@@' instead of '@' has been designated by
19 the linker as the default symbol, and is awarded a priority of -1.
20 """
21 if "@" not in symbol:
22 return None
23 data = [i for i in filter(lambda s: s, symbol.split("@"))]
24 _, version = data[-1].split("_")
25 version = version.replace(".", "")
26 priority = -1 if "@@" in symbol else int(version + "0" * (3 - len(version)))
27 return data[0], data[1], priority
30 def invert_mapping(symbol_map):
31 """Transforms a map from Key->Value to Value->Key."""
32 store = defaultdict(list)
33 for symbol, (version, _) in symbol_map.items():
34 store[version].append(symbol)
35 result = []
36 for k, v in store.items():
37 v.sort()
38 result.append((k, v))
39 result.sort(key=lambda x: x[0])
40 return result
43 def intersection(llvm, gcc):
44 """
45 Finds the intersection between the symbols extracted from compiler-rt.a/libunwind.a
46 and libgcc_s.so.1.
47 """
48 common_symbols = {}
49 for i in gcc:
50 suffix_triple = split_suffix(i)
51 if not suffix_triple:
52 continue
54 symbol, version_name, version_number = suffix_triple
55 if symbol in llvm:
56 if symbol not in common_symbols:
57 common_symbols[symbol] = (version_name, version_number)
58 continue
59 if version_number < common_symbols[symbol][1]:
60 common_symbols[symbol] = (version_name, version_number)
61 return invert_mapping(common_symbols)
64 def find_function_names(path):
65 """
66 Runs readelf on a binary and reduces to only defined functions. Equivalent to
67 `llvm-readelf --wide ${path} | grep 'FUNC' | grep -v 'UND' | awk '{print $8}'`.
68 """
69 result = subprocess.run(args=["llvm-readelf", "-su", path], capture_output=True)
71 if result.returncode != 0:
72 print(result.stderr.decode("utf-8"), file=sys.stderr)
73 sys.exit(1)
75 stdout = result.stdout.decode("utf-8")
76 stdout = filter(lambda x: "FUNC" in x and "UND" not in x, stdout.split("\n"))
77 stdout = chain(map(lambda x: filter(None, x), (i.split(" ") for i in stdout)))
79 return [list(i)[7] for i in stdout]
82 def to_file(versioned_symbols):
83 path = f"{os.path.dirname(os.path.realpath(__file__))}/new-gcc_s-symbols"
84 with open(path, "w") as f:
85 f.write(
86 "Do not check this version script in: you should instead work "
87 "out which symbols are missing in `lib/gcc_s.ver` and then "
88 "integrate them into `lib/gcc_s.ver`. For more information, "
89 "please see `doc/LLVMLibgcc.rst`.\n"
91 for version, symbols in versioned_symbols:
92 f.write(f"{version} {{\n")
93 for i in symbols:
94 f.write(f" {i};\n")
95 f.write("};\n\n")
98 def read_args():
99 parser = argparse.ArgumentParser()
100 parser.add_argument(
101 "--compiler_rt",
102 type=str,
103 help="Path to `libclang_rt.builtins-${ARCH}.a`.",
104 required=True,
106 parser.add_argument(
107 "--libunwind", type=str, help="Path to `libunwind.a`.", required=True
109 parser.add_argument(
110 "--libgcc_s",
111 type=str,
112 help="Path to `libgcc_s.so.1`. Note that unlike the other two arguments, this is a dynamic library.",
113 required=True,
115 return parser.parse_args()
118 def main():
119 args = read_args()
120 llvm = find_function_names(args.compiler_rt) + find_function_names(args.libunwind)
121 gcc = find_function_names(args.libgcc_s)
122 versioned_symbols = intersection(llvm, gcc)
123 # TODO(cjdb): work out a way to integrate new symbols in with the existing
124 # ones
125 to_file(versioned_symbols)
128 if __name__ == "__main__":
129 main()