1#!/usr/bin/env python3
2
3# Copyright 2018 The SwiftShader Authors. All Rights Reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#    http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from subprocess import run
18
19import argparse
20import multiprocessing
21import os
22import re
23import shutil
24
25
26LLVM_DIR = os.path.abspath(os.path.join('..', 'llvm'))
27
28LLVM_CONFIGS = os.path.abspath(os.path.join('..', 'configs'))
29
30LLVM_OBJS = os.path.join(os.getcwd(), 'llvm_objs')
31
32LLVM_TARGETS = [
33    ('AArch64', ('__aarch64__',)),
34    ('ARM', ('__arm__',)),
35    ('X86', ('__i386__', '__x86_64__')),
36    ('Mips', ('__mips__',)),
37]
38
39LLVM_TRIPLES = {
40    'android': [
41        ('__x86_64__', 'x86_64-linux-android'),
42        ('__i386__', 'i686-linux-android'),
43        ('__arm__', 'armv7-linux-androideabi'),
44        ('__aarch64__', 'aarch64-linux-android'),
45    ],
46    'linux': [
47        ('__x86_64__', 'x86_64-unknown-linux-gnu'),
48        ('__i386__', 'i686-pc-linux-gnu'),
49        ('__arm__', 'armv7-linux-gnueabihf'),
50        ('__aarch64__', 'aarch64-linux-gnu'),
51        ('__mips__', 'mipsel-linux-gnu'),
52        ('__mips64', 'mips64el-linux-gnuabi64'),
53    ],
54    'darwin': [
55        ('__x86_64__', 'x86_64-apple-darwin'),
56    ],
57}
58
59LLVM_OPTIONS = [
60    '-DCMAKE_BUILD_TYPE=Release',
61    '-DLLVM_TARGETS_TO_BUILD=' + ';'.join(t[0] for t in LLVM_TARGETS),
62    '-DLLVM_ENABLE_TERMINFO=OFF',
63    '-DLLVM_ENABLE_LIBXML2=OFF',
64    '-DLLVM_ENABLE_LIBEDIT=OFF',
65    '-DLLVM_ENABLE_LIBPFM=OFF',
66    '-DLLVM_ENABLE_ZLIB=OFF',
67]
68
69
70def _parse_args():
71    parser = argparse.ArgumentParser()
72    parser.add_argument('name', help='destination name',
73                        choices=['android', 'linux', 'darwin'])
74    parser.add_argument('-j', '--jobs', help='parallel compilation', type=int)
75    return parser.parse_args()
76
77
78def build_llvm(num_jobs):
79    """Build LLVM and get all generated files."""
80    if num_jobs is None:
81        num_jobs = multiprocessing.cpu_count()
82
83    os.makedirs(LLVM_OBJS, exist_ok=True)
84    run(['cmake', LLVM_DIR] + LLVM_OPTIONS, cwd=LLVM_OBJS)
85    run(['make', '-j' + str(num_jobs)], cwd=LLVM_OBJS)
86
87
88def list_files(src_base, src, dst_base, suffixes):
89    """Enumerate the files that are under `src` and end with one of the
90    `suffixes` and yield the source path and the destination path."""
91    src_base = os.path.abspath(src_base)
92    src = os.path.join(src_base, src)
93    for base_dir, dirnames, filenames in os.walk(src):
94        for filename in filenames:
95            if os.path.splitext(filename)[1] in suffixes:
96                relative = os.path.relpath(base_dir, src_base)
97                yield (os.path.join(base_dir, filename),
98                       os.path.join(dst_base, relative, filename))
99
100
101def copy_common_generated_files(dst_base):
102    """Copy platform-independent generated files."""
103    suffixes = {'.inc', '.h', '.def'}
104    subdirs = [
105        os.path.join('include', 'llvm', 'IR'),
106        os.path.join('include', 'llvm', 'Support'),
107        os.path.join('lib', 'IR'),
108        os.path.join('lib', 'Target', 'AArch64'),
109        os.path.join('lib', 'Target', 'ARM'),
110        os.path.join('lib', 'Target', 'X86'),
111        os.path.join('lib', 'Target', 'Mips'),
112        os.path.join('lib', 'Transforms', 'InstCombine'),
113    ]
114    for subdir in subdirs:
115        for src, dst in list_files(LLVM_OBJS, subdir, dst_base, suffixes):
116            os.makedirs(os.path.dirname(dst), exist_ok=True)
117            shutil.copyfile(src, dst)
118
119
120def copy_platform_file(platform, src, dst):
121    """Copy platform-dependent generated files and add platform-specific
122    modifications."""
123
124    # LLVM configuration patterns to be post-processed.
125    llvm_target_pattern = re.compile('^LLVM_[A-Z_]+\\(([A-Za-z0-9_]+)\\)$')
126    llvm_native_pattern = re.compile(
127        '^#define LLVM_NATIVE_([A-Z]+) (LLVMInitialize)?(.*)$')
128    llvm_triple_pattern = re.compile('^#define (LLVM_[A-Z_]+_TRIPLE) "(.*)"$')
129    llvm_define_pattern = re.compile('^#define ([A-Za-z0-9_]+) (.*)$')
130
131    # LLVM configurations to be undefined.
132    undef_names = [
133        'BACKTRACE_HEADER',
134        'ENABLE_BACKTRACES',
135        'ENABLE_CRASH_OVERRIDES',
136        'HAVE_BACKTRACE',
137        'HAVE_POSIX_SPAWN',
138        'HAVE_PTHREAD_GETNAME_NP',
139        'HAVE_PTHREAD_SETNAME_NP',
140        'HAVE_TERMIOS_H',
141        'HAVE_ZLIB_H',
142        'HAVE__UNWIND_BACKTRACE',
143    ]
144
145    # Build architecture-specific conditions.
146    conds = {}
147    for arch, defs in LLVM_TARGETS:
148        conds[arch] = ' || '.join('defined(' + v + ')' for v in defs)
149
150    # Get a set of platform-specific triples.
151    triples = LLVM_TRIPLES[platform]
152
153    with open(src, 'r') as src_file:
154        os.makedirs(os.path.dirname(dst), exist_ok=True)
155        with open(dst, 'w') as dst_file:
156            for line in src_file:
157                match = llvm_target_pattern.match(line)
158                if match:
159                    arch = match.group(1)
160                    print('#if ' + conds[arch], file=dst_file)
161                    print(line, file=dst_file, end='')
162                    print('#endif', file=dst_file)
163                    continue
164
165                match = llvm_native_pattern.match(line)
166                if match:
167                    name = match.group(1)
168                    init = match.group(2) or ''
169                    arch = match.group(3)
170                    end = ''
171                    if arch.lower().endswith(name.lower()):
172                        end = arch[-len(name):]
173                    directive = '#if '
174                    for arch, defs in LLVM_TARGETS:
175                        print(directive + conds[arch], file=dst_file)
176                        print('#define LLVM_NATIVE_' + name + ' ' +
177                              init + arch + end, file=dst_file)
178                        directive = '#elif '
179                    print('#else', file=dst_file)
180                    print('#error "unknown architecture"', file=dst_file)
181                    print('#endif', file=dst_file)
182                    continue
183
184                match = llvm_triple_pattern.match(line)
185                if match:
186                    name = match.group(1)
187                    directive = '#if'
188                    for defs, triple in triples:
189                        print(directive + ' defined(' + defs + ')',
190                              file=dst_file)
191                        print('#define ' + name + ' "' + triple + '"',
192                              file=dst_file)
193                        directive = '#elif'
194                    print('#else', file=dst_file)
195                    print('#error "unknown architecture"', file=dst_file)
196                    print('#endif', file=dst_file)
197                    continue
198
199                match = llvm_define_pattern.match(line)
200                if match and match.group(1) in undef_names:
201                    print('/* #undef ' + match.group(1) + ' */', file=dst_file)
202                    continue
203
204                print(line, file=dst_file, end='')
205
206
207def copy_platform_generated_files(platform, dst_base):
208    """Copy platform-specific generated files."""
209    suffixes = {'.inc', '.h', '.def'}
210    src_dir = os.path.join('include', 'llvm', 'Config')
211    for src, dst in list_files(LLVM_OBJS, src_dir, dst_base, suffixes):
212        copy_platform_file(platform, src, dst)
213
214
215def main():
216    args = _parse_args()
217    build_llvm(args.jobs)
218    copy_common_generated_files(os.path.join(LLVM_CONFIGS, 'common'))
219    copy_platform_generated_files(
220        args.name, os.path.join(LLVM_CONFIGS, args.name))
221
222
223if __name__ == '__main__':
224    main()
225