Browse Source

Add script to compile to intermediate IR for easier pass test authoring (#5101)

This script automates some operations to make it easier to write IR tests:
  1. Gets the pass list for an HLSL compilation using -Odump
  2. Compiles HLSL with -fcgl and outputs to intermediate IR
  3. Collects list of passes before the desired pass and adds
     -hlsl-passes-pause to write correct metadata
  4. Invokes dxopt to run passes on -fcgl output and write bitcode result
  5. Disassembles bitcode to .ll file for use as a test
  6. Inserts RUN line with -hlsl-passes-resume and desired pass
Tex Riddell 2 years ago
parent
commit
f4ea912411
1 changed files with 132 additions and 0 deletions
  1. 132 0
      utils/hct/ExtractIRForPassTest.py

+ 132 - 0
utils/hct/ExtractIRForPassTest.py

@@ -0,0 +1,132 @@
+#! /usr/bin/env python3
+"""ExtractIRForPassTest.py - extract IR just before selected pass would be run.
+
+This script automates some operations to make it easier to write IR tests:
+  1. Gets the pass list for an HLSL compilation using -Odump
+  2. Compiles HLSL with -fcgl and outputs to intermediate IR
+  3. Collects list of passes before the desired pass and adds
+     -hlsl-passes-pause to write correct metadata
+  4. Invokes dxopt to run passes on -fcgl output and write bitcode result
+  5. Disassembles bitcode to .ll file for use as a test
+  6. Inserts RUN line with -hlsl-passes-resume and desired pass
+
+Examples:
+  ExtractIRForPassTest.py -p scalarrepl-param-hlsl -o my_test.ll my_test.hlsl -- -T cs_6_0 -Od
+    - stop before 'scalarrepl-param-hlsl' pass; output to my_test.ll
+  ExtractIRForPassTest.py -p simplifycfg -n 2 -o my_test.ll my_test.hlsl -- -T cs_6_0
+    - stop before the second invocation of 'simplifycfg' pass
+
+Use dxc with -Odump to dump the pass sequence for reference.
+"""
+
+import os
+import sys
+import subprocess
+import tempfile
+import argparse
+
+def ParseArgs():
+  parser = argparse.ArgumentParser(
+    formatter_class=argparse.RawDescriptionHelpFormatter, description=__doc__)
+  parser.add_argument(
+    '-p', dest='desired_pass', metavar='<desired-pass>', required=True,
+    help='stop before this module pass (per-function prepass not supported).')
+  parser.add_argument(
+    '-n', dest='invocation', metavar='<invocation>', type=int, default=1,
+    help='pass invocation number on which to stop (default=1)')
+  parser.add_argument(
+    'hlsl_file', metavar='<hlsl-file>',
+    help='input HLSL file path to compile')
+  parser.add_argument(
+    '-o', dest='output_file', metavar='<output-file>', required=True,
+    help='output file name')
+  parser.add_argument(
+    'compilation_options', nargs='*',
+    metavar='-- <DXC options>',
+    help='set of compilation options needed to compile the HLSL file with DXC')
+  return parser.parse_args()
+
+def SplitAtPass(passes, pass_name, invocation = 1):
+  pass_name = '-' + pass_name
+  before = []
+  fn_passes = True
+  count = 0
+  after = None
+  for line in passes:
+    line = line.strip()
+    if not line or line.startswith('#'):
+      continue
+    if line == '-opt-mod-passes':
+      fn_passes = False
+    if after:
+      after.append(line)
+      continue
+    if not fn_passes:
+      if line == pass_name:
+        count += 1
+        if count >= invocation:
+          after = [line]
+          continue
+    before.append(line)
+  return before, after
+
+def GetTempFilename(*args, **kwargs):
+  "Get temp filename and close the file handle for use by others"
+  fd, name = tempfile.mkstemp(*args, **kwargs)
+  os.close(fd)
+  return name
+
+def main(args):
+  try:
+    # 1. Gets the pass list for an HLSL compilation using -Odump
+    cmd = ['dxc', '/Odump', args.hlsl_file] + args.compilation_options
+    # print(cmd)
+    all_passes = subprocess.check_output(cmd, text=True)
+    all_passes = all_passes.splitlines()
+
+    # 2. Compiles HLSL with -fcgl and outputs to intermediate IR
+    fcgl_file = GetTempFilename('.ll')
+    cmd = (['dxc', '-fcgl', '-Fc', fcgl_file, args.hlsl_file]
+           + args.compilation_options)
+    # print(cmd)
+    subprocess.check_call(cmd)
+
+    # 3. Collects list of passes before the desired pass and adds
+    #    -hlsl-passes-pause to write correct metadata
+    passes_before, passes_after = SplitAtPass(
+      all_passes, args.desired_pass, args.invocation)
+    print('\nPasses before: {}\n\nRemaining passes: {}'
+            .format(' '.join(passes_before), ' '.join(passes_after)))
+    passes_before.append('-hlsl-passes-pause')
+
+    # 4. Invokes dxopt to run passes on -fcgl output and write bitcode result
+    bitcode_file = GetTempFilename('.bc')
+    cmd = ['dxopt', '-o=' + bitcode_file, fcgl_file] + passes_before
+    # print(cmd)
+    subprocess.check_call(cmd)
+
+    # 5. Disassembles bitcode to .ll file for use as a test
+    temp_out = GetTempFilename('.ll')
+    cmd = ['dxc', '/dumpbin', '-Fc', temp_out, bitcode_file]
+    # print(cmd)
+    subprocess.check_call(cmd)
+
+    # 6. Inserts RUN line with -hlsl-passes-resume and desired pass
+    with open(args.output_file, 'wt') as f:
+      f.write('; RUN: %opt %s -hlsl-passes-resume {} -S | FileCheck %s\n\n'
+                .format(args.desired_pass))
+      with open(temp_out, 'rt') as f_in:
+        f.write(f_in.read())
+
+    # Clean up temp files
+    os.unlink(fcgl_file)
+    os.unlink(bitcode_file)
+    os.unlink(temp_out)
+  except:
+    print(f'\nSomething went wrong!\nMost recent command and arguments: {cmd}\n')
+    raise
+
+if __name__=='__main__':
+  args = ParseArgs()
+  main(args)
+  print('\nSuccess!  See output file:\n{}'.format(args.output_file))