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