1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
#!/usr/bin/env python
# Copyright 2019 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
from __future__ import print_function
import glob
import argparse
import os
import subprocess
import sys
import shutil
script_dir = os.path.dirname(os.path.realpath(__file__))
tool_dir = os.path.abspath(os.path.join(script_dir, '../../pylib'))
sys.path.insert(0, tool_dir)
from clang import plugin_testing
class StackMapTest(plugin_testing.ClangPluginTest):
"""Test harness for stack map artefact."""
def __init__(self, test_base, llvm_bin_path, libgc_path, ident_sp_pass_path,
reg_gc_pass_path,):
self._test_base = test_base
self._llvm_bin_path = llvm_bin_path
self._libgc_path = libgc_path
self._ident_sp_pass_path = ident_sp_pass_path
self._reg_gc_pass_path = reg_gc_pass_path
self._clang_path = os.path.join(llvm_bin_path, 'clang++')
self._opt_path = os.path.join(llvm_bin_path, 'opt')
self._llc_path = os.path.join(llvm_bin_path, 'llc')
self._out_dir = os.path.join(
os.path.dirname(os.path.realpath(__file__)), 'out')
def build_commands(self, test_name):
ll_filename = os.path.join(self._out_dir, "%s.ll" % test_name)
ll_with_gc_filename = os.path.join(
self._out_dir, "%s_optimised.ll" % test_name)
asm_filename = os.path.join(self._out_dir, "%s.s" % test_name)
obj_filename = os.path.join(self._out_dir, "%s.o" % test_name)
bin_name = os.path.join(self._out_dir, "%s.out" % test_name)
# Run the clang++ frontend with -O2 but stop after emitting the IR. There
# is a bug in clang which prevents us from running GC related IR phases
# directly from the frontend and requires us to split it into multiple
# build phases.
clang_cmd = [
self._clang_path,
'-std=c++14',
'-fno-omit-frame-pointer',
'-I../',
'-Xclang',
'-load',
'-Xclang',
self._ident_sp_pass_path,
'-O2',
'-S',
'-emit-llvm',
'-o', ll_filename,
'%s.cpp' % test_name
]
# Run two passes on the IR. The first selects which functions will be
# safepointed, the second inserts statepoint relocation sequences and ends
# up in stack maps being generated during the lowering phase.
opt_cmd = [
self._opt_path,
'-load=%s' % self._reg_gc_pass_path,
'-register-gc-fns',
'-rewrite-statepoints-for-gc',
'-S',
'-o', ll_with_gc_filename,
ll_filename
]
# Note: We must ensure each stage of lowering disables omit frame pointer
# optimisation
llc_cmd = [
self._llc_path,
ll_with_gc_filename,
'--frame-pointer=all',
'-o',
asm_filename
]
# The next two stages are required because ToT LLVM emits stackmaps which
# are local only to their object file. In a somewhat hacky fix, we
# globalise this symbol so that it can be used by the independent GC
# runtime library.
make_native = [
self._clang_path,
'-c',
'-o',
obj_filename,
asm_filename,
]
obj_copy = [
'objcopy',
'--globalize-symbol=__LLVM_StackMaps',
obj_filename
]
# Link the GC runtime and create target executable
link_cmd = [
self._clang_path,
obj_filename,
'-fno-omit-frame-pointer',
self._libgc_path,
'-o',
bin_name
]
run_cmd = ['%s' % bin_name]
return [
clang_cmd,
opt_cmd,
llc_cmd,
make_native,
obj_copy,
link_cmd,
run_cmd
]
def Run(self):
"""Runs the tests.
The working directory is temporarily changed to self._test_base while
running the tests.
Returns: the number of failing tests.
"""
print('Using llvm tools in %s...' % self._llvm_bin_path)
os.chdir(self._test_base)
passing = []
failing = []
tests = glob.glob('*.cpp')
# Delete out directory if it already exists
if (os.path.exists(self._out_dir)):
shutil.rmtree(self._out_dir)
os.mkdir(self._out_dir)
for test in tests:
sys.stdout.write('Testing %s...' % test)
test_name, _ = os.path.splitext(test)
cmds = self.build_commands(test_name)
failure_message = self.RunOneTest(test_name, cmds)
if failure_message:
print('\n\tfailed: %s' % failure_message)
failing.append(test_name)
else:
print('\tpassed!')
passing.append(test_name)
print('Ran %d tests: %d succeeded, %d failed' % (
len(passing) + len(failing), len(passing), len(failing)))
for test in failing:
print(' %s' % test)
return len(failing)
def RunOneTest(self, test_name, cmds):
for cmd in cmds:
try:
failure_message = ""
subprocess.check_output(cmd, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
failure_message = e.output
break
except Exception as e:
return 'could not execute %s (%s)' % (cmd, e)
return self.ProcessOneResult(test_name, failure_message)
def ProcessOneResult(self, test_name, failure_message):
if failure_message:
return failure_message.replace('\r\n', '\n')
def main():
parser = argparse.ArgumentParser()
parser.add_argument(
'llvm_bin_path', help='The path to the llvm tools bin dir.')
parser.add_argument('libgc_path', help='The path to the runtime gc library.')
parser.add_argument('identify_safepoints_path',
help='The path to the identify safepoints IR pass.')
parser.add_argument('reg_gc_fns_path',
help='The path to the register GC functions IR pass.')
args = parser.parse_args()
return StackMapTest(
os.path.dirname(os.path.realpath(__file__)),
args.llvm_bin_path,
args.libgc_path,
args.identify_safepoints_path,
args.reg_gc_fns_path,
).Run()
if __name__ == '__main__':
sys.exit(main())
|