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