1#!/usr/bin/env python2
2
3import argparse
4import os
5import subprocess
6import sys
7import tempfile
8
9import targets
10from szbuild import LinkNonsfi
11from utils import FindBaseNaCl, GetObjcopyCmd, get_sfi_string, shellcmd
12
13def main():
14    """Builds a cross-test binary for comparing Subzero and llc translation.
15
16    Each --test argument is compiled once by llc and once by Subzero.  C/C++
17    tests are first compiled down to PNaCl bitcode using pnacl-clang and
18    pnacl-opt.  The --prefix argument ensures that symbol names are different
19    between the two object files, to avoid linking errors.
20
21    There is also a --driver argument that specifies the C/C++ file that calls
22    the test functions with a variety of interesting inputs and compares their
23    results.
24
25    """
26    # arch_map maps a Subzero target string to TargetInfo (e.g., triple).
27    arch_map = { 'x8632': targets.X8632Target,
28                 'x8664': targets.X8664Target,
29                 'arm32': targets.ARM32Target,
30                 'mips32': targets.MIPS32Target}
31    arch_sz_flags = { 'x8632': [],
32                      'x8664': [],
33                      # For ARM, test a large stack offset as well. +/- 4095 is
34                      # the limit, so test somewhere near that boundary.
35                      'arm32': ['--test-stack-extra', '4084'],
36                      'mips32': ['--test-stack-extra', '4084']
37    }
38    arch_llc_flags_extra = {
39        # Use sse2 instructions regardless of input -mattr
40        # argument to avoid differences in (undefined) behavior of
41        # converting NaN to int.
42        'x8632': ['-mattr=sse2'],
43        'x8664': ['-mattr=sse2'],
44        'arm32': [],
45        'mips32':[],
46    }
47    desc = 'Build a cross-test that compares Subzero and llc translation.'
48    argparser = argparse.ArgumentParser(description=desc)
49    argparser.add_argument('--test', required=True, action='append',
50                           metavar='TESTFILE_LIST',
51                           help='List of C/C++/.ll files with test functions')
52    argparser.add_argument('--driver', required=True,
53                           metavar='DRIVER',
54                           help='Driver program')
55    argparser.add_argument('--target', required=False, default='x8632',
56                           choices=arch_map.keys(),
57                           metavar='TARGET',
58                           help='Translation target architecture.' +
59                                ' Default %(default)s.')
60    argparser.add_argument('-O', required=False, default='2', dest='optlevel',
61                           choices=['m1', '-1', '0', '1', '2'],
62                           metavar='OPTLEVEL',
63                           help='Optimization level for llc and Subzero ' +
64                                '(m1 and -1 are equivalent).' +
65                                ' Default %(default)s.')
66    argparser.add_argument('--clang-opt', required=False, default=True,
67                           dest='clang_opt')
68    argparser.add_argument('--mattr',  required=False, default='sse2',
69                           dest='attr', choices=['sse2', 'sse4.1',
70                                                 'neon', 'hwdiv-arm',
71                                                 'base'],
72                           metavar='ATTRIBUTE',
73                           help='Target attribute. Default %(default)s.')
74    argparser.add_argument('--sandbox', required=False, default=0, type=int,
75                           dest='sandbox',
76                           help='Use sandboxing. Default "%(default)s".')
77    argparser.add_argument('--nonsfi', required=False, default=0, type=int,
78                           dest='nonsfi',
79                           help='Use Non-SFI mode. Default "%(default)s".')
80    argparser.add_argument('--prefix', required=True,
81                           metavar='SZ_PREFIX',
82                           help='String prepended to Subzero symbol names')
83    argparser.add_argument('--output', '-o', required=True,
84                           metavar='EXECUTABLE',
85                           help='Executable to produce')
86    argparser.add_argument('--dir', required=False, default='.',
87                           metavar='OUTPUT_DIR',
88                           help='Output directory for all files.' +
89                                ' Default "%(default)s".')
90    argparser.add_argument('--filetype', default='obj', dest='filetype',
91                           choices=['obj', 'asm', 'iasm'],
92                           help='Output file type.  Default %(default)s.')
93    argparser.add_argument('--sz', dest='sz_args', action='append', default=[],
94                           help='Extra arguments to pass to pnacl-sz.')
95    args = argparser.parse_args()
96
97    nacl_root = FindBaseNaCl()
98    bindir = ('{root}/toolchain/linux_x86/pnacl_newlib_raw/bin'
99              .format(root=nacl_root))
100    target_info = arch_map[args.target]
101    triple = target_info.triple
102    if args.sandbox:
103        triple = targets.ConvertTripleToNaCl(triple)
104    llc_flags = target_info.llc_flags + arch_llc_flags_extra[args.target]
105    if args.nonsfi:
106        llc_flags.extend(['-relocation-model=pic',
107                          '-malign-double',
108                          '-force-tls-non-pic',
109                          '-mtls-use-call'])
110    mypath = os.path.abspath(os.path.dirname(sys.argv[0]))
111
112    # Construct a "unique key" for each test so that tests can be run in
113    # parallel without race conditions on temporary file creation.
114    key = '{sb}.O{opt}.{attr}.{target}'.format(
115        target=args.target,
116        sb=get_sfi_string(args, 'sb', 'nonsfi', 'nat'),
117        opt=args.optlevel, attr=args.attr)
118    objs = []
119    for arg in args.test:
120        base, ext = os.path.splitext(arg)
121        if ext == '.ll':
122            bitcode = arg
123        else:
124            # Use pnacl-clang and pnacl-opt to produce PNaCl bitcode.
125            bitcode_nonfinal = os.path.join(args.dir, base + '.' + key + '.bc')
126            bitcode = os.path.join(args.dir, base + '.' + key + '.pnacl.ll')
127            shellcmd(['{bin}/pnacl-clang'.format(bin=bindir),
128                      ('-O2' if args.clang_opt else '-O0'),
129                      ('-DARM32' if args.target == 'arm32' else ''), '-c', arg,
130                      ('-DMIPS32' if args.target == 'mips32' else ''),
131                      '-o', bitcode_nonfinal])
132            shellcmd(['{bin}/pnacl-opt'.format(bin=bindir),
133                      '-pnacl-abi-simplify-preopt',
134                      '-pnacl-abi-simplify-postopt',
135                      '-pnaclabi-allow-debug-metadata',
136                      '-strip-metadata',
137                      '-strip-module-flags',
138                      '-strip-debug',
139                      bitcode_nonfinal, '-S', '-o', bitcode])
140
141        base_sz = '{base}.{key}'.format(base=base, key=key)
142        asm_sz = os.path.join(args.dir, base_sz + '.sz.s')
143        obj_sz = os.path.join(args.dir, base_sz + '.sz.o')
144        obj_llc = os.path.join(args.dir, base_sz + '.llc.o')
145
146        shellcmd(['{path}/pnacl-sz'.format(path=os.path.dirname(mypath)),
147                  ] + args.sz_args + [
148                  '-O' + args.optlevel,
149                  '-mattr=' + args.attr,
150                  '--target=' + args.target,
151                  '--sandbox=' + str(args.sandbox),
152                  '--nonsfi=' + str(args.nonsfi),
153                  '--prefix=' + args.prefix,
154                  '-allow-uninitialized-globals',
155                  '-externalize',
156                  '-filetype=' + args.filetype,
157                  '-o=' + (obj_sz if args.filetype == 'obj' else asm_sz),
158                  bitcode] + arch_sz_flags[args.target])
159        if args.filetype != 'obj':
160            shellcmd(['{bin}/llvm-mc'.format(bin=bindir),
161                      '-triple=' + ('mipsel-linux-gnu'
162                                    if args.target == 'mips32' and args.sandbox
163                                    else triple),
164                      '-filetype=obj',
165                      '-o=' + obj_sz,
166                      asm_sz])
167
168        # Each separately translated Subzero object file contains its own
169        # definition of the __Sz_block_profile_info profiling symbol.  Avoid
170        # linker errors (multiply defined symbol) by making all copies weak.
171        # (This could also be done by Subzero if it supported weak symbol
172        # definitions.)  This approach should be OK because cross tests are
173        # currently the only situation where multiple translated files are
174        # linked into the executable, but when PNaCl supports shared nexe
175        # libraries, this would need to change.  (Note: the same issue applies
176        # to the __Sz_revision symbol.)
177        shellcmd(['{bin}/{objcopy}'.format(bin=bindir,
178                  objcopy=GetObjcopyCmd(args.target)),
179                  '--weaken-symbol=__Sz_block_profile_info',
180                  '--weaken-symbol=__Sz_revision',
181                  '--strip-symbol=nacl_tp_tdb_offset',
182                  '--strip-symbol=nacl_tp_tls_offset',
183                  obj_sz])
184        objs.append(obj_sz)
185        shellcmd(['{bin}/pnacl-llc'.format(bin=bindir),
186                  '-mtriple=' + triple,
187                  '-externalize',
188                  '-filetype=obj',
189                  '-bitcode-format=llvm',
190                  '-o=' + obj_llc,
191                  bitcode] + llc_flags)
192        strip_syms = [] if args.target == 'mips32' else ['nacl_tp_tdb_offset',
193                                                         'nacl_tp_tls_offset']
194        shellcmd(['{bin}/{objcopy}'.format(bin=bindir,
195                  objcopy=GetObjcopyCmd(args.target)),
196                  obj_llc] +
197                 [('--strip-symbol=' + sym) for sym in strip_syms])
198        objs.append(obj_llc)
199
200    # Add szrt_sb_${target}.o or szrt_native_${target}.o.
201    if not args.nonsfi:
202        objs.append((
203                '{root}/toolchain_build/src/subzero/build/runtime/' +
204                'szrt_{sb}_' + args.target + '.o'
205                ).format(root=nacl_root,
206                         sb=get_sfi_string(args, 'sb', 'nonsfi', 'native')))
207
208    target_params = []
209
210    if args.target == 'arm32':
211      target_params.append('-DARM32')
212      target_params.append('-static')
213
214    if args.target == 'mips32':
215      target_params.append('-DMIPS32')
216
217    pure_c = os.path.splitext(args.driver)[1] == '.c'
218    if not args.nonsfi:
219        # Set compiler to clang, clang++, pnacl-clang, or pnacl-clang++.
220        compiler = '{bin}/{prefix}{cc}'.format(
221            bin=bindir, prefix=get_sfi_string(args, 'pnacl-', '', ''),
222            cc='clang' if pure_c else 'clang++')
223        sb_native_args = (['-O0', '--pnacl-allow-native',
224                           '-arch', target_info.compiler_arch,
225                           '-Wn,-defsym=__Sz_AbsoluteZero=0']
226                          if args.sandbox else
227                          ['-g', '-target=' + triple,
228                           '-lm', '-lpthread',
229                           '-Wl,--defsym=__Sz_AbsoluteZero=0'] +
230                          target_info.cross_headers)
231        shellcmd([compiler] + target_params + [args.driver] + objs +
232                 ['-o', os.path.join(args.dir, args.output)] + sb_native_args)
233        return 0
234
235    base, ext = os.path.splitext(args.driver)
236    bitcode_nonfinal = os.path.join(args.dir, base + '.' + key + '.bc')
237    bitcode = os.path.join(args.dir, base + '.' + key + '.pnacl.ll')
238    asm_sz = os.path.join(args.dir, base + '.' + key + '.s')
239    obj_llc = os.path.join(args.dir, base + '.' + key + '.o')
240    compiler = '{bin}/{prefix}{cc}'.format(
241        bin=bindir, prefix='pnacl-',
242        cc='clang' if pure_c else 'clang++')
243    shellcmd([compiler] + target_params + [
244              args.driver,
245              '-O2',
246              '-o', bitcode_nonfinal,
247              '-Wl,-r'
248             ])
249    shellcmd(['{bin}/pnacl-opt'.format(bin=bindir),
250              '-pnacl-abi-simplify-preopt',
251              '-pnacl-abi-simplify-postopt',
252              '-pnaclabi-allow-debug-metadata',
253              '-strip-metadata',
254              '-strip-module-flags',
255              '-strip-debug',
256              '-disable-opt',
257              bitcode_nonfinal, '-S', '-o', bitcode])
258    shellcmd(['{bin}/pnacl-llc'.format(bin=bindir),
259              '-mtriple=' + triple,
260              '-externalize',
261              '-filetype=obj',
262              '-O2',
263              '-bitcode-format=llvm',
264              '-o', obj_llc,
265              bitcode] + llc_flags)
266    if not args.sandbox and not args.nonsfi:
267        shellcmd(['{bin}/{objcopy}'.format(bin=bindir,
268                  objcopy=GetObjcopyCmd(args.target)),
269                  '--redefine-sym', '_start=_user_start',
270                  obj_llc
271                 ])
272    objs.append(obj_llc)
273    if args.nonsfi:
274        LinkNonsfi(objs, os.path.join(args.dir, args.output), args.target)
275    elif args.sandbox:
276        LinkSandbox(objs, os.path.join(args.dir, args.output), args.target)
277    else:
278        LinkNative(objs, os.path.join(args.dir, args.output), args.target)
279
280if __name__ == '__main__':
281    main()
282