106f32e7eSjoerg#===- perf-helper.py - Clang Python Bindings -----------------*- python -*--===# 206f32e7eSjoerg# 306f32e7eSjoerg# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 406f32e7eSjoerg# See https://llvm.org/LICENSE.txt for license information. 506f32e7eSjoerg# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 606f32e7eSjoerg# 706f32e7eSjoerg#===------------------------------------------------------------------------===# 806f32e7eSjoerg 906f32e7eSjoergfrom __future__ import absolute_import, division, print_function 1006f32e7eSjoerg 1106f32e7eSjoergimport sys 1206f32e7eSjoergimport os 1306f32e7eSjoergimport subprocess 1406f32e7eSjoergimport argparse 1506f32e7eSjoergimport time 1606f32e7eSjoergimport bisect 1706f32e7eSjoergimport shlex 1806f32e7eSjoergimport tempfile 1906f32e7eSjoerg 2006f32e7eSjoergtest_env = { 'PATH' : os.environ['PATH'] } 2106f32e7eSjoerg 2206f32e7eSjoergdef findFilesWithExtension(path, extension): 2306f32e7eSjoerg filenames = [] 2406f32e7eSjoerg for root, dirs, files in os.walk(path): 2506f32e7eSjoerg for filename in files: 2606f32e7eSjoerg if filename.endswith(extension): 2706f32e7eSjoerg filenames.append(os.path.join(root, filename)) 2806f32e7eSjoerg return filenames 2906f32e7eSjoerg 3006f32e7eSjoergdef clean(args): 3106f32e7eSjoerg if len(args) != 2: 3206f32e7eSjoerg print('Usage: %s clean <path> <extension>\n' % __file__ + 3306f32e7eSjoerg '\tRemoves all files with extension from <path>.') 3406f32e7eSjoerg return 1 3506f32e7eSjoerg for filename in findFilesWithExtension(args[0], args[1]): 3606f32e7eSjoerg os.remove(filename) 3706f32e7eSjoerg return 0 3806f32e7eSjoerg 3906f32e7eSjoergdef merge(args): 4006f32e7eSjoerg if len(args) != 3: 4106f32e7eSjoerg print('Usage: %s clean <llvm-profdata> <output> <path>\n' % __file__ + 4206f32e7eSjoerg '\tMerges all profraw files from path into output.') 4306f32e7eSjoerg return 1 4406f32e7eSjoerg cmd = [args[0], 'merge', '-o', args[1]] 4506f32e7eSjoerg cmd.extend(findFilesWithExtension(args[2], "profraw")) 4606f32e7eSjoerg subprocess.check_call(cmd) 4706f32e7eSjoerg return 0 4806f32e7eSjoerg 4906f32e7eSjoergdef dtrace(args): 5006f32e7eSjoerg parser = argparse.ArgumentParser(prog='perf-helper dtrace', 5106f32e7eSjoerg description='dtrace wrapper for order file generation') 5206f32e7eSjoerg parser.add_argument('--buffer-size', metavar='size', type=int, required=False, 5306f32e7eSjoerg default=1, help='dtrace buffer size in MB (default 1)') 5406f32e7eSjoerg parser.add_argument('--use-oneshot', required=False, action='store_true', 5506f32e7eSjoerg help='Use dtrace\'s oneshot probes') 5606f32e7eSjoerg parser.add_argument('--use-ustack', required=False, action='store_true', 5706f32e7eSjoerg help='Use dtrace\'s ustack to print function names') 5806f32e7eSjoerg parser.add_argument('--cc1', required=False, action='store_true', 5906f32e7eSjoerg help='Execute cc1 directly (don\'t profile the driver)') 6006f32e7eSjoerg parser.add_argument('cmd', nargs='*', help='') 6106f32e7eSjoerg 6206f32e7eSjoerg # Use python's arg parser to handle all leading option arguments, but pass 6306f32e7eSjoerg # everything else through to dtrace 6406f32e7eSjoerg first_cmd = next(arg for arg in args if not arg.startswith("--")) 6506f32e7eSjoerg last_arg_idx = args.index(first_cmd) 6606f32e7eSjoerg 6706f32e7eSjoerg opts = parser.parse_args(args[:last_arg_idx]) 6806f32e7eSjoerg cmd = args[last_arg_idx:] 6906f32e7eSjoerg 7006f32e7eSjoerg if opts.cc1: 7106f32e7eSjoerg cmd = get_cc1_command_for_args(cmd, test_env) 7206f32e7eSjoerg 7306f32e7eSjoerg if opts.use_oneshot: 7406f32e7eSjoerg target = "oneshot$target:::entry" 7506f32e7eSjoerg else: 7606f32e7eSjoerg target = "pid$target:::entry" 7706f32e7eSjoerg predicate = '%s/probemod=="%s"/' % (target, os.path.basename(cmd[0])) 7806f32e7eSjoerg log_timestamp = 'printf("dtrace-TS: %d\\n", timestamp)' 7906f32e7eSjoerg if opts.use_ustack: 8006f32e7eSjoerg action = 'ustack(1);' 8106f32e7eSjoerg else: 8206f32e7eSjoerg action = 'printf("dtrace-Symbol: %s\\n", probefunc);' 8306f32e7eSjoerg dtrace_script = "%s { %s; %s }" % (predicate, log_timestamp, action) 8406f32e7eSjoerg 8506f32e7eSjoerg dtrace_args = [] 8606f32e7eSjoerg if not os.geteuid() == 0: 8706f32e7eSjoerg print( 8806f32e7eSjoerg 'Script must be run as root, or you must add the following to your sudoers:' 8906f32e7eSjoerg + '%%admin ALL=(ALL) NOPASSWD: /usr/sbin/dtrace') 9006f32e7eSjoerg dtrace_args.append("sudo") 9106f32e7eSjoerg 9206f32e7eSjoerg dtrace_args.extend(( 9306f32e7eSjoerg 'dtrace', '-xevaltime=exec', 9406f32e7eSjoerg '-xbufsize=%dm' % (opts.buffer_size), 9506f32e7eSjoerg '-q', '-n', dtrace_script, 9606f32e7eSjoerg '-c', ' '.join(cmd))) 9706f32e7eSjoerg 9806f32e7eSjoerg if sys.platform == "darwin": 9906f32e7eSjoerg dtrace_args.append('-xmangled') 10006f32e7eSjoerg 10106f32e7eSjoerg start_time = time.time() 10206f32e7eSjoerg 10306f32e7eSjoerg with open("%d.dtrace" % os.getpid(), "w") as f: 10406f32e7eSjoerg f.write("### Command: %s" % dtrace_args) 10506f32e7eSjoerg subprocess.check_call(dtrace_args, stdout=f, stderr=subprocess.PIPE) 10606f32e7eSjoerg 10706f32e7eSjoerg elapsed = time.time() - start_time 10806f32e7eSjoerg print("... data collection took %.4fs" % elapsed) 10906f32e7eSjoerg 11006f32e7eSjoerg return 0 11106f32e7eSjoerg 11206f32e7eSjoergdef get_cc1_command_for_args(cmd, env): 11306f32e7eSjoerg # Find the cc1 command used by the compiler. To do this we execute the 11406f32e7eSjoerg # compiler with '-###' to figure out what it wants to do. 11506f32e7eSjoerg cmd = cmd + ['-###'] 11606f32e7eSjoerg cc_output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, env=env, universal_newlines=True).strip() 11706f32e7eSjoerg cc_commands = [] 11806f32e7eSjoerg for ln in cc_output.split('\n'): 11906f32e7eSjoerg # Filter out known garbage. 12006f32e7eSjoerg if (ln == 'Using built-in specs.' or 12106f32e7eSjoerg ln.startswith('Configured with:') or 12206f32e7eSjoerg ln.startswith('Target:') or 12306f32e7eSjoerg ln.startswith('Thread model:') or 12406f32e7eSjoerg ln.startswith('InstalledDir:') or 12506f32e7eSjoerg ln.startswith('LLVM Profile Note') or 126*13fbcb42Sjoerg ln.startswith(' (in-process)') or 12706f32e7eSjoerg ' version ' in ln): 12806f32e7eSjoerg continue 12906f32e7eSjoerg cc_commands.append(ln) 13006f32e7eSjoerg 13106f32e7eSjoerg if len(cc_commands) != 1: 13206f32e7eSjoerg print('Fatal error: unable to determine cc1 command: %r' % cc_output) 13306f32e7eSjoerg exit(1) 13406f32e7eSjoerg 13506f32e7eSjoerg cc1_cmd = shlex.split(cc_commands[0]) 13606f32e7eSjoerg if not cc1_cmd: 13706f32e7eSjoerg print('Fatal error: unable to determine cc1 command: %r' % cc_output) 13806f32e7eSjoerg exit(1) 13906f32e7eSjoerg 14006f32e7eSjoerg return cc1_cmd 14106f32e7eSjoerg 14206f32e7eSjoergdef cc1(args): 14306f32e7eSjoerg parser = argparse.ArgumentParser(prog='perf-helper cc1', 14406f32e7eSjoerg description='cc1 wrapper for order file generation') 14506f32e7eSjoerg parser.add_argument('cmd', nargs='*', help='') 14606f32e7eSjoerg 14706f32e7eSjoerg # Use python's arg parser to handle all leading option arguments, but pass 14806f32e7eSjoerg # everything else through to dtrace 14906f32e7eSjoerg first_cmd = next(arg for arg in args if not arg.startswith("--")) 15006f32e7eSjoerg last_arg_idx = args.index(first_cmd) 15106f32e7eSjoerg 15206f32e7eSjoerg opts = parser.parse_args(args[:last_arg_idx]) 15306f32e7eSjoerg cmd = args[last_arg_idx:] 15406f32e7eSjoerg 15506f32e7eSjoerg # clear the profile file env, so that we don't generate profdata 15606f32e7eSjoerg # when capturing the cc1 command 15706f32e7eSjoerg cc1_env = test_env 15806f32e7eSjoerg cc1_env["LLVM_PROFILE_FILE"] = os.devnull 15906f32e7eSjoerg cc1_cmd = get_cc1_command_for_args(cmd, cc1_env) 16006f32e7eSjoerg 16106f32e7eSjoerg subprocess.check_call(cc1_cmd) 16206f32e7eSjoerg return 0 16306f32e7eSjoerg 16406f32e7eSjoergdef parse_dtrace_symbol_file(path, all_symbols, all_symbols_set, 16506f32e7eSjoerg missing_symbols, opts): 16606f32e7eSjoerg def fix_mangling(symbol): 16706f32e7eSjoerg if sys.platform == "darwin": 16806f32e7eSjoerg if symbol[0] != '_' and symbol != 'start': 16906f32e7eSjoerg symbol = '_' + symbol 17006f32e7eSjoerg return symbol 17106f32e7eSjoerg 17206f32e7eSjoerg def get_symbols_with_prefix(symbol): 17306f32e7eSjoerg start_index = bisect.bisect_left(all_symbols, symbol) 17406f32e7eSjoerg for s in all_symbols[start_index:]: 17506f32e7eSjoerg if not s.startswith(symbol): 17606f32e7eSjoerg break 17706f32e7eSjoerg yield s 17806f32e7eSjoerg 17906f32e7eSjoerg # Extract the list of symbols from the given file, which is assumed to be 18006f32e7eSjoerg # the output of a dtrace run logging either probefunc or ustack(1) and 18106f32e7eSjoerg # nothing else. The dtrace -xdemangle option needs to be used. 18206f32e7eSjoerg # 18306f32e7eSjoerg # This is particular to OS X at the moment, because of the '_' handling. 18406f32e7eSjoerg with open(path) as f: 18506f32e7eSjoerg current_timestamp = None 18606f32e7eSjoerg for ln in f: 18706f32e7eSjoerg # Drop leading and trailing whitespace. 18806f32e7eSjoerg ln = ln.strip() 18906f32e7eSjoerg if not ln.startswith("dtrace-"): 19006f32e7eSjoerg continue 19106f32e7eSjoerg 19206f32e7eSjoerg # If this is a timestamp specifier, extract it. 19306f32e7eSjoerg if ln.startswith("dtrace-TS: "): 19406f32e7eSjoerg _,data = ln.split(': ', 1) 19506f32e7eSjoerg if not data.isdigit(): 19606f32e7eSjoerg print("warning: unrecognized timestamp line %r, ignoring" % ln, 19706f32e7eSjoerg file=sys.stderr) 19806f32e7eSjoerg continue 19906f32e7eSjoerg current_timestamp = int(data) 20006f32e7eSjoerg continue 20106f32e7eSjoerg elif ln.startswith("dtrace-Symbol: "): 20206f32e7eSjoerg 20306f32e7eSjoerg _,ln = ln.split(': ', 1) 20406f32e7eSjoerg if not ln: 20506f32e7eSjoerg continue 20606f32e7eSjoerg 20706f32e7eSjoerg # If there is a '`' in the line, assume it is a ustack(1) entry in 20806f32e7eSjoerg # the form of <modulename>`<modulefunc>, where <modulefunc> is never 20906f32e7eSjoerg # truncated (but does need the mangling patched). 21006f32e7eSjoerg if '`' in ln: 21106f32e7eSjoerg yield (current_timestamp, fix_mangling(ln.split('`',1)[1])) 21206f32e7eSjoerg continue 21306f32e7eSjoerg 21406f32e7eSjoerg # Otherwise, assume this is a probefunc printout. DTrace on OS X 21506f32e7eSjoerg # seems to have a bug where it prints the mangled version of symbols 21606f32e7eSjoerg # which aren't C++ mangled. We just add a '_' to anything but start 21706f32e7eSjoerg # which doesn't already have a '_'. 21806f32e7eSjoerg symbol = fix_mangling(ln) 21906f32e7eSjoerg 22006f32e7eSjoerg # If we don't know all the symbols, or the symbol is one of them, 22106f32e7eSjoerg # just return it. 22206f32e7eSjoerg if not all_symbols_set or symbol in all_symbols_set: 22306f32e7eSjoerg yield (current_timestamp, symbol) 22406f32e7eSjoerg continue 22506f32e7eSjoerg 22606f32e7eSjoerg # Otherwise, we have a symbol name which isn't present in the 22706f32e7eSjoerg # binary. We assume it is truncated, and try to extend it. 22806f32e7eSjoerg 22906f32e7eSjoerg # Get all the symbols with this prefix. 23006f32e7eSjoerg possible_symbols = list(get_symbols_with_prefix(symbol)) 23106f32e7eSjoerg if not possible_symbols: 23206f32e7eSjoerg continue 23306f32e7eSjoerg 23406f32e7eSjoerg # If we found too many possible symbols, ignore this as a prefix. 23506f32e7eSjoerg if len(possible_symbols) > 100: 23606f32e7eSjoerg print( "warning: ignoring symbol %r " % symbol + 23706f32e7eSjoerg "(no match and too many possible suffixes)", file=sys.stderr) 23806f32e7eSjoerg continue 23906f32e7eSjoerg 24006f32e7eSjoerg # Report that we resolved a missing symbol. 24106f32e7eSjoerg if opts.show_missing_symbols and symbol not in missing_symbols: 24206f32e7eSjoerg print("warning: resolved missing symbol %r" % symbol, file=sys.stderr) 24306f32e7eSjoerg missing_symbols.add(symbol) 24406f32e7eSjoerg 24506f32e7eSjoerg # Otherwise, treat all the possible matches as having occurred. This 24606f32e7eSjoerg # is an over-approximation, but it should be ok in practice. 24706f32e7eSjoerg for s in possible_symbols: 24806f32e7eSjoerg yield (current_timestamp, s) 24906f32e7eSjoerg 25006f32e7eSjoergdef uniq(list): 25106f32e7eSjoerg seen = set() 25206f32e7eSjoerg for item in list: 25306f32e7eSjoerg if item not in seen: 25406f32e7eSjoerg yield item 25506f32e7eSjoerg seen.add(item) 25606f32e7eSjoerg 25706f32e7eSjoergdef form_by_call_order(symbol_lists): 25806f32e7eSjoerg # Simply strategy, just return symbols in order of occurrence, even across 25906f32e7eSjoerg # multiple runs. 26006f32e7eSjoerg return uniq(s for symbols in symbol_lists for s in symbols) 26106f32e7eSjoerg 26206f32e7eSjoergdef form_by_call_order_fair(symbol_lists): 26306f32e7eSjoerg # More complicated strategy that tries to respect the call order across all 26406f32e7eSjoerg # of the test cases, instead of giving a huge preference to the first test 26506f32e7eSjoerg # case. 26606f32e7eSjoerg 26706f32e7eSjoerg # First, uniq all the lists. 26806f32e7eSjoerg uniq_lists = [list(uniq(symbols)) for symbols in symbol_lists] 26906f32e7eSjoerg 27006f32e7eSjoerg # Compute the successors for each list. 27106f32e7eSjoerg succs = {} 27206f32e7eSjoerg for symbols in uniq_lists: 27306f32e7eSjoerg for a,b in zip(symbols[:-1], symbols[1:]): 27406f32e7eSjoerg succs[a] = items = succs.get(a, []) 27506f32e7eSjoerg if b not in items: 27606f32e7eSjoerg items.append(b) 27706f32e7eSjoerg 27806f32e7eSjoerg # Emit all the symbols, but make sure to always emit all successors from any 27906f32e7eSjoerg # call list whenever we see a symbol. 28006f32e7eSjoerg # 28106f32e7eSjoerg # There isn't much science here, but this sometimes works better than the 28206f32e7eSjoerg # more naive strategy. Then again, sometimes it doesn't so more research is 28306f32e7eSjoerg # probably needed. 28406f32e7eSjoerg return uniq(s 28506f32e7eSjoerg for symbols in symbol_lists 28606f32e7eSjoerg for node in symbols 28706f32e7eSjoerg for s in ([node] + succs.get(node,[]))) 28806f32e7eSjoerg 28906f32e7eSjoergdef form_by_frequency(symbol_lists): 29006f32e7eSjoerg # Form the order file by just putting the most commonly occurring symbols 29106f32e7eSjoerg # first. This assumes the data files didn't use the oneshot dtrace method. 29206f32e7eSjoerg 29306f32e7eSjoerg counts = {} 29406f32e7eSjoerg for symbols in symbol_lists: 29506f32e7eSjoerg for a in symbols: 29606f32e7eSjoerg counts[a] = counts.get(a,0) + 1 29706f32e7eSjoerg 29806f32e7eSjoerg by_count = list(counts.items()) 29906f32e7eSjoerg by_count.sort(key = lambda __n: -__n[1]) 30006f32e7eSjoerg return [s for s,n in by_count] 30106f32e7eSjoerg 30206f32e7eSjoergdef form_by_random(symbol_lists): 30306f32e7eSjoerg # Randomize the symbols. 30406f32e7eSjoerg merged_symbols = uniq(s for symbols in symbol_lists 30506f32e7eSjoerg for s in symbols) 30606f32e7eSjoerg random.shuffle(merged_symbols) 30706f32e7eSjoerg return merged_symbols 30806f32e7eSjoerg 30906f32e7eSjoergdef form_by_alphabetical(symbol_lists): 31006f32e7eSjoerg # Alphabetize the symbols. 31106f32e7eSjoerg merged_symbols = list(set(s for symbols in symbol_lists for s in symbols)) 31206f32e7eSjoerg merged_symbols.sort() 31306f32e7eSjoerg return merged_symbols 31406f32e7eSjoerg 31506f32e7eSjoergmethods = dict((name[len("form_by_"):],value) 31606f32e7eSjoerg for name,value in locals().items() if name.startswith("form_by_")) 31706f32e7eSjoerg 31806f32e7eSjoergdef genOrderFile(args): 31906f32e7eSjoerg parser = argparse.ArgumentParser( 32006f32e7eSjoerg "%prog [options] <dtrace data file directories>]") 32106f32e7eSjoerg parser.add_argument('input', nargs='+', help='') 32206f32e7eSjoerg parser.add_argument("--binary", metavar="PATH", type=str, dest="binary_path", 32306f32e7eSjoerg help="Path to the binary being ordered (for getting all symbols)", 32406f32e7eSjoerg default=None) 32506f32e7eSjoerg parser.add_argument("--output", dest="output_path", 32606f32e7eSjoerg help="path to output order file to write", default=None, required=True, 32706f32e7eSjoerg metavar="PATH") 32806f32e7eSjoerg parser.add_argument("--show-missing-symbols", dest="show_missing_symbols", 32906f32e7eSjoerg help="show symbols which are 'fixed up' to a valid name (requires --binary)", 33006f32e7eSjoerg action="store_true", default=None) 33106f32e7eSjoerg parser.add_argument("--output-unordered-symbols", 33206f32e7eSjoerg dest="output_unordered_symbols_path", 33306f32e7eSjoerg help="write a list of the unordered symbols to PATH (requires --binary)", 33406f32e7eSjoerg default=None, metavar="PATH") 33506f32e7eSjoerg parser.add_argument("--method", dest="method", 33606f32e7eSjoerg help="order file generation method to use", choices=list(methods.keys()), 33706f32e7eSjoerg default='call_order') 33806f32e7eSjoerg opts = parser.parse_args(args) 33906f32e7eSjoerg 34006f32e7eSjoerg # If the user gave us a binary, get all the symbols in the binary by 34106f32e7eSjoerg # snarfing 'nm' output. 34206f32e7eSjoerg if opts.binary_path is not None: 34306f32e7eSjoerg output = subprocess.check_output(['nm', '-P', opts.binary_path], universal_newlines=True) 34406f32e7eSjoerg lines = output.split("\n") 34506f32e7eSjoerg all_symbols = [ln.split(' ',1)[0] 34606f32e7eSjoerg for ln in lines 34706f32e7eSjoerg if ln.strip()] 34806f32e7eSjoerg print("found %d symbols in binary" % len(all_symbols)) 34906f32e7eSjoerg all_symbols.sort() 35006f32e7eSjoerg else: 35106f32e7eSjoerg all_symbols = [] 35206f32e7eSjoerg all_symbols_set = set(all_symbols) 35306f32e7eSjoerg 35406f32e7eSjoerg # Compute the list of input files. 35506f32e7eSjoerg input_files = [] 35606f32e7eSjoerg for dirname in opts.input: 35706f32e7eSjoerg input_files.extend(findFilesWithExtension(dirname, "dtrace")) 35806f32e7eSjoerg 35906f32e7eSjoerg # Load all of the input files. 36006f32e7eSjoerg print("loading from %d data files" % len(input_files)) 36106f32e7eSjoerg missing_symbols = set() 36206f32e7eSjoerg timestamped_symbol_lists = [ 36306f32e7eSjoerg list(parse_dtrace_symbol_file(path, all_symbols, all_symbols_set, 36406f32e7eSjoerg missing_symbols, opts)) 36506f32e7eSjoerg for path in input_files] 36606f32e7eSjoerg 36706f32e7eSjoerg # Reorder each symbol list. 36806f32e7eSjoerg symbol_lists = [] 36906f32e7eSjoerg for timestamped_symbols_list in timestamped_symbol_lists: 37006f32e7eSjoerg timestamped_symbols_list.sort() 37106f32e7eSjoerg symbol_lists.append([symbol for _,symbol in timestamped_symbols_list]) 37206f32e7eSjoerg 37306f32e7eSjoerg # Execute the desire order file generation method. 37406f32e7eSjoerg method = methods.get(opts.method) 37506f32e7eSjoerg result = list(method(symbol_lists)) 37606f32e7eSjoerg 37706f32e7eSjoerg # Report to the user on what percentage of symbols are present in the order 37806f32e7eSjoerg # file. 37906f32e7eSjoerg num_ordered_symbols = len(result) 38006f32e7eSjoerg if all_symbols: 38106f32e7eSjoerg print("note: order file contains %d/%d symbols (%.2f%%)" % ( 38206f32e7eSjoerg num_ordered_symbols, len(all_symbols), 38306f32e7eSjoerg 100.*num_ordered_symbols/len(all_symbols)), file=sys.stderr) 38406f32e7eSjoerg 38506f32e7eSjoerg if opts.output_unordered_symbols_path: 38606f32e7eSjoerg ordered_symbols_set = set(result) 38706f32e7eSjoerg with open(opts.output_unordered_symbols_path, 'w') as f: 38806f32e7eSjoerg f.write("\n".join(s for s in all_symbols if s not in ordered_symbols_set)) 38906f32e7eSjoerg 39006f32e7eSjoerg # Write the order file. 39106f32e7eSjoerg with open(opts.output_path, 'w') as f: 39206f32e7eSjoerg f.write("\n".join(result)) 39306f32e7eSjoerg f.write("\n") 39406f32e7eSjoerg 39506f32e7eSjoerg return 0 39606f32e7eSjoerg 39706f32e7eSjoergcommands = {'clean' : clean, 39806f32e7eSjoerg 'merge' : merge, 39906f32e7eSjoerg 'dtrace' : dtrace, 40006f32e7eSjoerg 'cc1' : cc1, 40106f32e7eSjoerg 'gen-order-file' : genOrderFile} 40206f32e7eSjoerg 40306f32e7eSjoergdef main(): 40406f32e7eSjoerg f = commands[sys.argv[1]] 40506f32e7eSjoerg sys.exit(f(sys.argv[2:])) 40606f32e7eSjoerg 40706f32e7eSjoergif __name__ == '__main__': 40806f32e7eSjoerg main() 409