| #!/usr/bin/env python3 |
| |
| # Copyright 2018 The SwiftShader Authors. All Rights Reserved. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| import argparse |
| import contextlib |
| import multiprocessing |
| import os |
| import platform |
| import re |
| import shutil |
| import subprocess |
| import sys |
| import tempfile |
| from os import path |
| |
| # LLVM_BRANCH must match the value of the same variable in third_party/update-llvm-16.sh |
| LLVM_BRANCH = "release/16.x" |
| |
| # LLVM_COMMIT must be set to the commit hash that we last updated to when running third_party/update-llvm-16.sh. |
| # Run 'git show -s origin/llvm16-clean' and look for 'llvm-16-update: <hash>' to retrieve it. |
| LLVM_COMMIT = "fce3e75e01babe38576b1519dab5f752955525f9" |
| |
| SCRIPT_DIR = path.dirname(path.realpath(sys.argv[0])) |
| LLVM_STAGING_DIR = path.abspath(path.join(tempfile.gettempdir(), "llvm-16")) |
| LLVM_DIR = path.abspath(path.join(LLVM_STAGING_DIR, "llvm")) |
| LLVM_OBJS = path.join(LLVM_STAGING_DIR, "build") |
| LLVM_CONFIGS = path.abspath(path.join(SCRIPT_DIR, '..', 'configs')) |
| |
| # List of all arches SwiftShader supports |
| LLVM_TARGETS = [ |
| ('AArch64', ('__aarch64__',)), |
| ('ARM', ('__arm__',)), |
| ('X86', ('__i386__', '__x86_64__')), |
| ('Mips', ('__mips__',)), |
| ('PowerPC', ('__powerpc64__',)), |
| ('RISCV', ('__riscv',)), |
| ] |
| |
| # Per-platform arches |
| LLVM_TRIPLES = { |
| 'android': [ |
| ('__x86_64__', 'x86_64-linux-android'), |
| ('__i386__', 'i686-linux-android'), |
| ('__arm__', 'armv7-linux-androideabi'), |
| ('__aarch64__', 'aarch64-linux-android'), |
| ], |
| 'linux': [ |
| ('__x86_64__', 'x86_64-unknown-linux-gnu'), |
| ('__i386__', 'i686-pc-linux-gnu'), |
| ('__arm__', 'armv7-linux-gnueabihf'), |
| ('__aarch64__', 'aarch64-linux-gnu'), |
| ('__mips__', 'mipsel-linux-gnu'), |
| ('__mips64', 'mips64el-linux-gnuabi64'), |
| ('__powerpc64__', 'powerpc64le-unknown-linux-gnu'), |
| ('__riscv', 'riscv64-unknown-linux-gnu'), |
| ], |
| 'darwin': [ |
| ('__x86_64__', 'x86_64-apple-darwin'), |
| ('__aarch64__', 'arm64-apple-darwin'), |
| ], |
| 'windows': [ |
| ('__x86_64__', 'x86_64-pc-win32'), |
| ('__i386__', 'i686-pc-win32'), |
| ('__arm__', 'armv7-pc-win32'), |
| ('__aarch64__', 'aarch64-pc-win32'), |
| ('__mips__', 'mipsel-pc-win32'), |
| ('__mips64', 'mips64el-pc-win32'), |
| ], |
| 'fuchsia': [ |
| ('__x86_64__', 'x86_64-unknown-fuchsia'), |
| ('__aarch64__', 'aarch64-unknown-fuchsia'), |
| ] |
| } |
| |
| # Mapping of target platform to the host it must be built on |
| LLVM_PLATFORM_TO_HOST_SYSTEM = { |
| 'android': 'Linux', |
| 'darwin': 'Darwin', |
| 'linux': 'Linux', |
| 'windows': 'Windows', |
| 'fuchsia': 'Linux' |
| } |
| |
| # LLVM configurations to be undefined. |
| LLVM_UNDEF_MACROS = [ |
| 'BACKTRACE_HEADER', |
| 'ENABLE_BACKTRACES', |
| 'ENABLE_CRASH_OVERRIDES', |
| 'HAVE_BACKTRACE', |
| 'HAVE_POSIX_SPAWN', |
| 'HAVE_PTHREAD_GETNAME_NP', |
| 'HAVE_PTHREAD_SETNAME_NP', |
| 'HAVE_TERMIOS_H', |
| 'HAVE_ZLIB_H', |
| 'HAVE__UNWIND_BACKTRACE', |
| ] |
| |
| # General CMake options for building LLVM |
| LLVM_CMAKE_OPTIONS = [ |
| '-DCMAKE_BUILD_TYPE=Release', |
| '-DLLVM_ENABLE_THREADS=ON', |
| '-DLLVM_ENABLE_TERMINFO=OFF', |
| '-DLLVM_ENABLE_LIBXML2=OFF', |
| '-DLLVM_ENABLE_LIBEDIT=OFF', |
| '-DLLVM_ENABLE_LIBPFM=OFF', |
| '-DLLVM_ENABLE_ZLIB=OFF', |
| '-DLLVM_INCLUDE_BENCHMARKS=OFF', |
| '-DLLVM_INCLUDE_TESTS=OFF', |
| '-DLLVM_TEMPORARILY_ALLOW_OLD_TOOLCHAIN=ON' |
| ] |
| |
| # Used when building LLVM for darwin. Affects values set in the generated config files. |
| MIN_MACOS_VERSION = '10.10' |
| |
| @contextlib.contextmanager |
| def pushd(new_dir): |
| previous_dir = os.getcwd() |
| os.chdir(new_dir) |
| try: |
| yield |
| finally: |
| os.chdir(previous_dir) |
| |
| |
| def log(message, level=1): |
| print(' ' * level + '> ' + message) |
| |
| |
| def run_command(command, log_level=1): |
| log(command, log_level) |
| os.system(command) |
| |
| |
| def run_subprocess(*popenargs, log_level=1, cwd=None): |
| log([' '.join(t) for t in popenargs][0], log_level) |
| return subprocess.run(*popenargs, cwd=cwd) |
| |
| |
| def _parse_args(): |
| parser = argparse.ArgumentParser() |
| parser.add_argument('name', help='destination name', |
| choices=['android', 'linux', 'darwin', 'windows', 'fuchsia']) |
| parser.add_argument('-j', '--jobs', help='parallel compilation', type=int) |
| return parser.parse_args() |
| |
| |
| def validate_args(args): |
| host = platform.system() |
| if host not in LLVM_PLATFORM_TO_HOST_SYSTEM.values(): |
| raise Exception(f"Host system not supported: '{host}'") |
| |
| if args.name not in LLVM_PLATFORM_TO_HOST_SYSTEM.keys(): |
| raise Exception(f"Unknown target platform: '{args.name}'") |
| |
| expected_host = LLVM_PLATFORM_TO_HOST_SYSTEM[args.name] |
| if LLVM_PLATFORM_TO_HOST_SYSTEM[args.name] != host: |
| raise Exception( |
| f"Target platform '{args.name}' must be built on '{expected_host}', not on '{host}'") |
| |
| |
| def get_cmake_targets_to_build(platform): |
| """Returns list of LLVM targets to build for the input platform""" |
| targets = set() |
| for arch_def, triplet in LLVM_TRIPLES[platform]: |
| for arch, defs in LLVM_TARGETS: |
| if arch_def in defs: |
| targets.add(arch) |
| |
| # Maintain the sort order of LLVM_TARGETS as this affects how config |
| # headers are generated |
| return [t[0] for t in LLVM_TARGETS if t[0] in targets] |
| |
| |
| def clone_llvm(): |
| log("Cloning/Updating LLVM", 1) |
| # Clone or update staging directory |
| if not path.exists(LLVM_STAGING_DIR): |
| os.mkdir(LLVM_STAGING_DIR) |
| with pushd(LLVM_STAGING_DIR): |
| run_command('git init', 2) |
| run_command( |
| 'git remote add origin https://github.com/llvm/llvm-project.git', 2) |
| run_command('git config core.sparsecheckout true', 2) |
| run_command('echo /llvm > .git/info/sparse-checkout', 2) |
| run_command('echo /cmake >> .git/info/sparse-checkout', 2) |
| |
| with pushd(LLVM_STAGING_DIR): |
| run_command('echo /llvm > .git/info/sparse-checkout', 2) |
| run_command('echo /cmake >> .git/info/sparse-checkout', 2) |
| run_command('git fetch origin {}'.format(LLVM_BRANCH), 2) |
| run_command('git checkout {}'.format(LLVM_COMMIT), 2) |
| return |
| |
| |
| def build_llvm(name, num_jobs): |
| """Build LLVM and get all generated files.""" |
| log("Building LLVM", 1) |
| if num_jobs is None: |
| num_jobs = multiprocessing.cpu_count() |
| |
| """On Windows we need to have CMake generate build files for the 64-bit |
| Visual Studio host toolchain.""" |
| host = '-Thost=x64' if name == 'windows' else '' |
| |
| cmake_options = LLVM_CMAKE_OPTIONS + ['-DLLVM_TARGETS_TO_BUILD=' + |
| ';'.join(t for t in get_cmake_targets_to_build(name))] |
| |
| if name == 'darwin': |
| cmake_options.append('-DCMAKE_OSX_DEPLOYMENT_TARGET={}'.format(MIN_MACOS_VERSION)) |
| |
| os.makedirs(LLVM_OBJS, exist_ok=True) |
| run_subprocess(['cmake', host, LLVM_DIR] + |
| cmake_options, log_level=2, cwd=LLVM_OBJS) |
| run_subprocess(['cmake', '--build', '.', '-j', |
| str(num_jobs)], log_level=2, cwd=LLVM_OBJS) |
| |
| |
| def list_files(src_base, src, dst_base, suffixes): |
| """Enumerate the files that are under `src` and end with one of the |
| `suffixes` and yield the source path and the destination path.""" |
| src_base = path.abspath(src_base) |
| src = path.join(src_base, src) |
| for base_dir, dirnames, filenames in os.walk(src): |
| for filename in filenames: |
| if path.splitext(filename)[1] in suffixes: |
| relative = path.relpath(base_dir, src_base) |
| yield (path.join(base_dir, filename), |
| path.join(dst_base, relative, filename)) |
| |
| |
| def copy_common_generated_files(dst_base): |
| """Copy platform-independent generated files.""" |
| log("Copying platform-independent generated files", 1) |
| suffixes = {'.inc', '.h', '.def'} |
| subdirs = [ |
| path.join('include', 'llvm', 'IR'), |
| path.join('include', 'llvm', 'Support'), |
| path.join('include', 'llvm', 'TargetParser'), |
| path.join('include', 'llvm', 'Frontend'), |
| path.join('lib', 'IR'), |
| path.join('lib', 'ExecutionEngine'), |
| path.join('lib', 'Transforms', 'InstCombine'), |
| ] + [path.join('lib', 'Target', arch) for arch, defs in LLVM_TARGETS] |
| for subdir in subdirs: |
| for src, dst in list_files(LLVM_OBJS, subdir, dst_base, suffixes): |
| log('{} -> {}'.format(src, dst), 2) |
| os.makedirs(path.dirname(dst), exist_ok=True) |
| shutil.copyfile(src, dst) |
| |
| |
| def copy_platform_file(platform, src, dst): |
| """Copy platform-dependent generated files and add platform-specific |
| modifications.""" |
| |
| # LLVM configuration patterns to be post-processed. |
| llvm_target_pattern = re.compile('^LLVM_[A-Z_]+\\(([A-Za-z0-9_]+)\\)$') |
| llvm_native_pattern = re.compile( |
| '^#define LLVM_NATIVE_([A-Z]+) (LLVMInitialize)?(.*)$') |
| llvm_triple_pattern = re.compile('^#define (LLVM_[A-Z_]+_TRIPLE) "(.*)"$') |
| llvm_define_pattern = re.compile('^#define ([A-Za-z0-9_]+) (.*)$') |
| |
| # Build architecture-specific conditions. |
| conds = {} |
| for arch, defs in LLVM_TARGETS: |
| conds[arch] = ' || '.join('defined(' + v + ')' for v in defs) |
| |
| # Get a set of platform-specific triples. |
| triples = LLVM_TRIPLES[platform] |
| |
| with open(src, 'r') as src_file: |
| os.makedirs(path.dirname(dst), exist_ok=True) |
| with open(dst, 'w') as dst_file: |
| for line in src_file: |
| if line == '#define LLVM_CONFIG_H\n': |
| print(line, file=dst_file, end='') |
| print('', file=dst_file) |
| print('#if !defined(__i386__) && defined(_M_IX86)', |
| file=dst_file) |
| print('#define __i386__ 1', file=dst_file) |
| print('#endif', file=dst_file) |
| print('', file=dst_file) |
| print( |
| '#if !defined(__x86_64__) && (defined(_M_AMD64) || defined (_M_X64))', file=dst_file) |
| print('#define __x86_64__ 1', file=dst_file) |
| print('#endif', file=dst_file) |
| print('', file=dst_file) |
| |
| match = llvm_target_pattern.match(line) |
| if match: |
| arch = match.group(1) |
| print('#if ' + conds[arch], file=dst_file) |
| print(line, file=dst_file, end='') |
| print('#endif', file=dst_file) |
| continue |
| |
| match = llvm_native_pattern.match(line) |
| if match: |
| name = match.group(1) |
| init = match.group(2) or '' |
| arch = match.group(3) |
| end = '' |
| if arch.lower().endswith(name.lower()): |
| end = arch[-len(name):] |
| directive = '#if ' |
| for arch, defs in LLVM_TARGETS: |
| print(directive + conds[arch], file=dst_file) |
| print('#define LLVM_NATIVE_' + name + ' ' + |
| init + arch + end, file=dst_file) |
| directive = '#elif ' |
| print('#else', file=dst_file) |
| print('#error "unknown architecture"', file=dst_file) |
| print('#endif', file=dst_file) |
| continue |
| |
| match = llvm_triple_pattern.match(line) |
| if match: |
| name = match.group(1) |
| directive = '#if' |
| for defs, triple in triples: |
| print(directive + ' defined(' + defs + ')', |
| file=dst_file) |
| print('#define ' + name + ' "' + triple + '"', |
| file=dst_file) |
| directive = '#elif' |
| print('#else', file=dst_file) |
| print('#error "unknown architecture"', file=dst_file) |
| print('#endif', file=dst_file) |
| continue |
| |
| match = llvm_define_pattern.match(line) |
| if match and match.group(1) in LLVM_UNDEF_MACROS: |
| print('/* #undef ' + match.group(1) + ' */', file=dst_file) |
| continue |
| |
| print(line, file=dst_file, end='') |
| |
| |
| def copy_platform_generated_files(platform, dst_base): |
| """Copy platform-specific generated files.""" |
| log("Copying platform-specific generated files", 1) |
| suffixes = {'.inc', '.h', '.def'} |
| src_dir = path.join('include', 'llvm', 'Config') |
| for src, dst in list_files(LLVM_OBJS, src_dir, dst_base, suffixes): |
| log('{}, {} -> {}'.format(platform, src, dst), 2) |
| copy_platform_file(platform, src, dst) |
| |
| |
| def main(): |
| args = _parse_args() |
| validate_args(args) |
| clone_llvm() |
| build_llvm(args.name, args.jobs) |
| copy_common_generated_files(path.join(LLVM_CONFIGS, 'common')) |
| copy_platform_generated_files( |
| args.name, path.join(LLVM_CONFIGS, args.name)) |
| |
| |
| if __name__ == '__main__': |
| main() |