| import json | |
| import os | |
| import re | |
| import sys | |
| import subprocess | |
| def run_command(command, cwd=None): | |
| p = subprocess.Popen(command, cwd=cwd) | |
| if p.wait() != 0: | |
| print("command `{}` failed...".format(" ".join(command))) | |
| sys.exit(1) | |
| def clone_repository(repo_name, path, repo_url, sub_paths): | |
| if os.path.exists(path): | |
| while True: | |
| choice = input("There is already a `{}` folder, do you want to update it? [y/N]".format(path)) | |
| if choice == "" or choice.lower() == "n": | |
| print("Skipping repository update.") | |
| return | |
| elif choice.lower() == "y": | |
| print("Updating repository...") | |
| run_command(["git", "pull", "origin", "main"], cwd=path) | |
| return | |
| else: | |
| print("Didn't understand answer...") | |
| print("Cloning {} repository...".format(repo_name)) | |
| run_command(["git", "clone", repo_url, "--filter=tree:0", "--no-checkout", path]) | |
| run_command(["git", "sparse-checkout", "init"], cwd=path) | |
| run_command(["git", "sparse-checkout", "set", *sub_paths], cwd=path) | |
| run_command(["git", "checkout"], cwd=path) | |
| def append_intrinsic(array, intrinsic_name, translation): | |
| array.append((intrinsic_name, translation)) | |
| def convert_to_string(content): | |
| if content.__class__.__name__ == 'bytes': | |
| return content.decode('utf-8') | |
| return content | |
| def extract_intrinsics_from_llvm(llvm_path): | |
| intrinsics = {} | |
| command = ["llvm-tblgen", "llvm/IR/Intrinsics.td", "--dump-json"] | |
| cwd = os.path.join(llvm_path, "llvm/include") | |
| print("=> Running command `{}` from `{}`".format(command, cwd)) | |
| p = subprocess.Popen(command, cwd=cwd, stdout=subprocess.PIPE) | |
| output, err = p.communicate() | |
| content = json.loads(convert_to_string(output)) | |
| for intrinsic in content: | |
| data = content[intrinsic] | |
| if not isinstance(data, dict): | |
| continue | |
| current_arch = data.get("TargetPrefix") | |
| builtin_name = data.get("ClangBuiltinName") | |
| if current_arch is None or current_arch == "" or builtin_name is None: | |
| continue | |
| intrinsic = intrinsic.split("_") | |
| if len(intrinsic) < 2 or intrinsic[0] != "int": | |
| continue | |
| intrinsic[0] = "llvm" | |
| intrinsic = ".".join(intrinsic) | |
| if current_arch not in intrinsics: | |
| intrinsics[current_arch] = [] | |
| append_intrinsic(intrinsics[current_arch], intrinsic, builtin_name) | |
| return intrinsics | |
| def update_intrinsics(llvm_path): | |
| intrinsics = extract_intrinsics_from_llvm(llvm_path) | |
| archs = [arch for arch in intrinsics] | |
| archs.sort() | |
| output_file = os.path.join( | |
| os.path.dirname(os.path.abspath(__file__)), | |
| "../src/intrinsic/archs.rs", | |
| ) | |
| # A hashmap of all architectures. This allows us to first match on the architecture, and then on the intrinsics. | |
| # This speeds up the comparison, and makes our code considerably smaller. | |
| # Since all intrinsic names start with "llvm.", we skip that prefix. | |
| print("Updating content of `{}`...".format(output_file)) | |
| with open(output_file, "w", encoding="utf8") as out: | |
| out.write("""// File generated by `rustc_codegen_gcc/tools/generate_intrinsics.py` | |
| // DO NOT EDIT IT! | |
| /// Translate a given LLVM intrinsic name to an equivalent GCC one. | |
| fn map_arch_intrinsic(full_name:&str)-> &'static str { | |
| let Some(name) = full_name.strip_prefix("llvm.") else { unimplemented!("***** unsupported LLVM intrinsic {}", full_name) }; | |
| let Some((arch, name)) = name.split_once('.') else { unimplemented!("***** unsupported LLVM intrinsic llvm.{}", name) }; | |
| let old_arch_res = old_archs(arch, name); | |
| if let ArchCheckResult::Ok(res) = old_arch_res { | |
| return res; | |
| } | |
| match arch {""") | |
| for arch in archs: | |
| if len(intrinsics[arch]) == 0: | |
| continue | |
| attribute = "#[expect(non_snake_case)]" if arch[0].isupper() else "" | |
| out.write("\"{}\" => {{ {} fn {}(name: &str,full_name:&str) -> &'static str {{ match name {{".format(arch, attribute, arch)) | |
| intrinsics[arch].sort(key=lambda x: (x[0], x[1])) | |
| out.write(' // {}\n'.format(arch)) | |
| for entry in intrinsics[arch]: | |
| llvm_name = entry[0].removeprefix("llvm."); | |
| llvm_name = llvm_name.removeprefix(arch); | |
| llvm_name = llvm_name.removeprefix("."); | |
| if "_round_mask" in entry[1]: | |
| out.write(' // [INVALID CONVERSION]: "{}" => "{}",\n'.format(llvm_name, entry[1])) | |
| else: | |
| out.write(' "{}" => "{}",\n'.format(llvm_name, entry[1])) | |
| out.write(' _ => unimplemented!("***** unsupported LLVM intrinsic {full_name}"),\n') | |
| out.write("}} }} {}(name,full_name) }}\n,".format(arch)) | |
| out.write(""" _ => { | |
| match old_arch_res { | |
| ArchCheckResult::UnknownIntrinsic => unimplemented!("***** unsupported LLVM intrinsic {full_name}"), | |
| ArchCheckResult::UnknownArch => unimplemented!("***** unsupported LLVM architecture {arch}, intrinsic: {full_name}"), | |
| ArchCheckResult::Ok(_) => unreachable!(), | |
| } | |
| }""") | |
| out.write("}\n}") | |
| subprocess.call(["rustfmt", output_file]) | |
| print("Done!") | |
| def main(): | |
| llvm_path = os.path.join( | |
| os.path.dirname(os.path.abspath(__file__)), | |
| "llvm-project", | |
| ) | |
| # First, we clone the LLVM repository if it's not already here. | |
| clone_repository( | |
| "llvm-project", | |
| llvm_path, | |
| "https://github.com/llvm/llvm-project", | |
| ["llvm/include/llvm/IR", "llvm/include/llvm/CodeGen/"], | |
| ) | |
| update_intrinsics(llvm_path) | |
| # llvm-tblgen can be built with: | |
| # | |
| # mkdir llvm-tblgen-build && cd llvm-tblgen-build | |
| # cmake -G Ninja -DLLVM_ENABLE_PROJECTS="llvm" -DCMAKE_BUILD_TYPE=Release ../llvm | |
| # ninja llvm-tblgen | |
| if __name__ == "__main__": | |
| sys.exit(main()) |