#!/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()