#!/usr/bin/env python2 import argparse import os import subprocess import sys import tempfile import targets from szbuild import LinkNonsfi from utils import FindBaseNaCl, GetObjcopyCmd, get_sfi_string, shellcmd def main(): """Builds a cross-test binary for comparing Subzero and llc translation. Each --test argument is compiled once by llc and once by Subzero. C/C++ tests are first compiled down to PNaCl bitcode using pnacl-clang and pnacl-opt. The --prefix argument ensures that symbol names are different between the two object files, to avoid linking errors. There is also a --driver argument that specifies the C/C++ file that calls the test functions with a variety of interesting inputs and compares their results. """ # arch_map maps a Subzero target string to TargetInfo (e.g., triple). arch_map = { 'x8632': targets.X8632Target, 'x8664': targets.X8664Target, 'arm32': targets.ARM32Target, 'mips32': targets.MIPS32Target} arch_sz_flags = { 'x8632': [], 'x8664': [], # For ARM, test a large stack offset as well. +/- 4095 is # the limit, so test somewhere near that boundary. 'arm32': ['--test-stack-extra', '4084'], 'mips32': ['--test-stack-extra', '4084'] } arch_llc_flags_extra = { # Use sse2 instructions regardless of input -mattr # argument to avoid differences in (undefined) behavior of # converting NaN to int. 'x8632': ['-mattr=sse2'], 'x8664': ['-mattr=sse2'], 'arm32': [], 'mips32':[], } desc = 'Build a cross-test that compares Subzero and llc translation.' argparser = argparse.ArgumentParser(description=desc) argparser.add_argument('--test', required=True, action='append', metavar='TESTFILE_LIST', help='List of C/C++/.ll files with test functions') argparser.add_argument('--driver', required=True, metavar='DRIVER', help='Driver program') argparser.add_argument('--target', required=False, default='x8632', choices=arch_map.keys(), metavar='TARGET', help='Translation target architecture.' + ' Default %(default)s.') argparser.add_argument('-O', required=False, default='2', dest='optlevel', choices=['m1', '-1', '0', '1', '2'], metavar='OPTLEVEL', help='Optimization level for llc and Subzero ' + '(m1 and -1 are equivalent).' + ' Default %(default)s.') argparser.add_argument('--clang-opt', required=False, default=True, dest='clang_opt') argparser.add_argument('--mattr', required=False, default='sse2', dest='attr', choices=['sse2', 'sse4.1', 'neon', 'hwdiv-arm', 'base'], metavar='ATTRIBUTE', help='Target attribute. Default %(default)s.') argparser.add_argument('--sandbox', required=False, default=0, type=int, dest='sandbox', help='Use sandboxing. Default "%(default)s".') argparser.add_argument('--nonsfi', required=False, default=0, type=int, dest='nonsfi', help='Use Non-SFI mode. Default "%(default)s".') argparser.add_argument('--prefix', required=True, metavar='SZ_PREFIX', help='String prepended to Subzero symbol names') argparser.add_argument('--output', '-o', required=True, metavar='EXECUTABLE', help='Executable to produce') argparser.add_argument('--dir', required=False, default='.', metavar='OUTPUT_DIR', help='Output directory for all files.' + ' Default "%(default)s".') argparser.add_argument('--filetype', default='obj', dest='filetype', choices=['obj', 'asm', 'iasm'], help='Output file type. Default %(default)s.') argparser.add_argument('--sz', dest='sz_args', action='append', default=[], help='Extra arguments to pass to pnacl-sz.') args = argparser.parse_args() nacl_root = FindBaseNaCl() bindir = ('{root}/toolchain/linux_x86/pnacl_newlib_raw/bin' .format(root=nacl_root)) target_info = arch_map[args.target] triple = target_info.triple if args.sandbox: triple = targets.ConvertTripleToNaCl(triple) llc_flags = target_info.llc_flags + arch_llc_flags_extra[args.target] if args.nonsfi: llc_flags.extend(['-relocation-model=pic', '-malign-double', '-force-tls-non-pic', '-mtls-use-call']) mypath = os.path.abspath(os.path.dirname(sys.argv[0])) # Construct a "unique key" for each test so that tests can be run in # parallel without race conditions on temporary file creation. key = '{sb}.O{opt}.{attr}.{target}'.format( target=args.target, sb=get_sfi_string(args, 'sb', 'nonsfi', 'nat'), opt=args.optlevel, attr=args.attr) objs = [] for arg in args.test: base, ext = os.path.splitext(arg) if ext == '.ll': bitcode = arg else: # Use pnacl-clang and pnacl-opt to produce PNaCl bitcode. bitcode_nonfinal = os.path.join(args.dir, base + '.' + key + '.bc') bitcode = os.path.join(args.dir, base + '.' + key + '.pnacl.ll') shellcmd(['{bin}/pnacl-clang'.format(bin=bindir), ('-O2' if args.clang_opt else '-O0'), ('-DARM32' if args.target == 'arm32' else ''), '-c', arg, ('-DMIPS32' if args.target == 'mips32' else ''), '-o', bitcode_nonfinal]) shellcmd(['{bin}/pnacl-opt'.format(bin=bindir), '-pnacl-abi-simplify-preopt', '-pnacl-abi-simplify-postopt', '-pnaclabi-allow-debug-metadata', '-strip-metadata', '-strip-module-flags', '-strip-debug', bitcode_nonfinal, '-S', '-o', bitcode]) base_sz = '{base}.{key}'.format(base=base, key=key) asm_sz = os.path.join(args.dir, base_sz + '.sz.s') obj_sz = os.path.join(args.dir, base_sz + '.sz.o') obj_llc = os.path.join(args.dir, base_sz + '.llc.o') shellcmd(['{path}/pnacl-sz'.format(path=os.path.dirname(mypath)), ] + args.sz_args + [ '-O' + args.optlevel, '-mattr=' + args.attr, '--target=' + args.target, '--sandbox=' + str(args.sandbox), '--nonsfi=' + str(args.nonsfi), '--prefix=' + args.prefix, '-allow-uninitialized-globals', '-externalize', '-filetype=' + args.filetype, '-o=' + (obj_sz if args.filetype == 'obj' else asm_sz), bitcode] + arch_sz_flags[args.target]) if args.filetype != 'obj': shellcmd(['{bin}/llvm-mc'.format(bin=bindir), '-triple=' + ('mipsel-linux-gnu' if args.target == 'mips32' and args.sandbox else triple), '-filetype=obj', '-o=' + obj_sz, asm_sz]) # Each separately translated Subzero object file contains its own # definition of the __Sz_block_profile_info profiling symbol. Avoid # linker errors (multiply defined symbol) by making all copies weak. # (This could also be done by Subzero if it supported weak symbol # definitions.) This approach should be OK because cross tests are # currently the only situation where multiple translated files are # linked into the executable, but when PNaCl supports shared nexe # libraries, this would need to change. (Note: the same issue applies # to the __Sz_revision symbol.) shellcmd(['{bin}/{objcopy}'.format(bin=bindir, objcopy=GetObjcopyCmd(args.target)), '--weaken-symbol=__Sz_block_profile_info', '--weaken-symbol=__Sz_revision', '--strip-symbol=nacl_tp_tdb_offset', '--strip-symbol=nacl_tp_tls_offset', obj_sz]) objs.append(obj_sz) shellcmd(['{bin}/pnacl-llc'.format(bin=bindir), '-mtriple=' + triple, '-externalize', '-filetype=obj', '-bitcode-format=llvm', '-o=' + obj_llc, bitcode] + llc_flags) strip_syms = [] if args.target == 'mips32' else ['nacl_tp_tdb_offset', 'nacl_tp_tls_offset'] shellcmd(['{bin}/{objcopy}'.format(bin=bindir, objcopy=GetObjcopyCmd(args.target)), obj_llc] + [('--strip-symbol=' + sym) for sym in strip_syms]) objs.append(obj_llc) # Add szrt_sb_${target}.o or szrt_native_${target}.o. if not args.nonsfi: objs.append(( '{root}/toolchain_build/src/subzero/build/runtime/' + 'szrt_{sb}_' + args.target + '.o' ).format(root=nacl_root, sb=get_sfi_string(args, 'sb', 'nonsfi', 'native'))) target_params = [] if args.target == 'arm32': target_params.append('-DARM32') target_params.append('-static') if args.target == 'mips32': target_params.append('-DMIPS32') pure_c = os.path.splitext(args.driver)[1] == '.c' if not args.nonsfi: # Set compiler to clang, clang++, pnacl-clang, or pnacl-clang++. compiler = '{bin}/{prefix}{cc}'.format( bin=bindir, prefix=get_sfi_string(args, 'pnacl-', '', ''), cc='clang' if pure_c else 'clang++') sb_native_args = (['-O0', '--pnacl-allow-native', '-arch', target_info.compiler_arch, '-Wn,-defsym=__Sz_AbsoluteZero=0'] if args.sandbox else ['-g', '-target=' + triple, '-lm', '-lpthread', '-Wl,--defsym=__Sz_AbsoluteZero=0'] + target_info.cross_headers) shellcmd([compiler] + target_params + [args.driver] + objs + ['-o', os.path.join(args.dir, args.output)] + sb_native_args) return 0 base, ext = os.path.splitext(args.driver) bitcode_nonfinal = os.path.join(args.dir, base + '.' + key + '.bc') bitcode = os.path.join(args.dir, base + '.' + key + '.pnacl.ll') asm_sz = os.path.join(args.dir, base + '.' + key + '.s') obj_llc = os.path.join(args.dir, base + '.' + key + '.o') compiler = '{bin}/{prefix}{cc}'.format( bin=bindir, prefix='pnacl-', cc='clang' if pure_c else 'clang++') shellcmd([compiler] + target_params + [ args.driver, '-O2', '-o', bitcode_nonfinal, '-Wl,-r' ]) shellcmd(['{bin}/pnacl-opt'.format(bin=bindir), '-pnacl-abi-simplify-preopt', '-pnacl-abi-simplify-postopt', '-pnaclabi-allow-debug-metadata', '-strip-metadata', '-strip-module-flags', '-strip-debug', '-disable-opt', bitcode_nonfinal, '-S', '-o', bitcode]) shellcmd(['{bin}/pnacl-llc'.format(bin=bindir), '-mtriple=' + triple, '-externalize', '-filetype=obj', '-O2', '-bitcode-format=llvm', '-o', obj_llc, bitcode] + llc_flags) if not args.sandbox and not args.nonsfi: shellcmd(['{bin}/{objcopy}'.format(bin=bindir, objcopy=GetObjcopyCmd(args.target)), '--redefine-sym', '_start=_user_start', obj_llc ]) objs.append(obj_llc) if args.nonsfi: LinkNonsfi(objs, os.path.join(args.dir, args.output), args.target) elif args.sandbox: LinkSandbox(objs, os.path.join(args.dir, args.output), args.target) else: LinkNative(objs, os.path.join(args.dir, args.output), args.target) if __name__ == '__main__': main()