xref: /llvm-project/llvm-libgcc/generate_version_script.py (revision f98ee40f4b5d7474fc67e82824bf6abbaedb7b1c)
1#!/usr/bin/env python3
2
3# Generates a version script for an architecture so that it can be incorporated
4# into gcc_s.ver.
5
6from collections import defaultdict
7from itertools import chain
8import argparse, subprocess, sys, os
9
10
11def 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).
15
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
28
29
30def 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
41
42
43def 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
53
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)
62
63
64def 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)
70
71    if result.returncode != 0:
72        print(result.stderr.decode("utf-8"), file=sys.stderr)
73        sys.exit(1)
74
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)))
78
79    return [list(i)[7] for i in stdout]
80
81
82def 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"
90        )
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")
96
97
98def 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,
105    )
106    parser.add_argument(
107        "--libunwind", type=str, help="Path to `libunwind.a`.", required=True
108    )
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,
114    )
115    return parser.parse_args()
116
117
118def 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)
126
127
128if __name__ == "__main__":
129    main()
130