Skip to content
Snippets Groups Projects
qtc_assembler_preview.py 4.89 KiB
#!/usr/bin/env python3

"""
Takes 2 args

 qtc_assembler_preview.py <build_dir> <file.c/c++>

Currently GCC is assumed
"""


import sys
import os
import shlex
import subprocess

BUILD_DIR = sys.argv[-2]
SOURCE_FILE = sys.argv[-1]

# TODO, support other compilers
COMPILER_ID = 'GCC'

def find_arg(source, data):
    source_base = os.path.basename(source)
    for l in data:
        # chances are high that we found the file
        if source_base in l:
            # check if this file is in the line
            l_split = shlex.split(l)
            for w in l_split:
                if w.endswith(source_base):
                    if os.path.isabs(w):
                        if os.path.samefile(w, source):
                            # print(l)
                            return l
                    else:
                        # check trailing path (a/b/c/d/e.c == d/e.c)
                        w_sep = os.path.normpath(w).split(os.sep)
                        s_sep = os.path.normpath(source).split(os.sep)
                        m = min(len(w_sep), len(s_sep))
                        if w_sep[-m:] == s_sep[-m:]:
                            # print(l)
                            return l


def find_build_args_ninja(source):
    make_exe = "ninja"
    process = subprocess.Popen([make_exe, "-t", "commands"],
                                stdout=subprocess.PIPE,
                               )
    while process.poll():
        time.sleep(1)

    out = process.stdout.read()
    process.stdout.close()
    # print("done!", len(out), "bytes")
    data = out.decode("utf-8", errors="ignore").split("\n")
    return find_arg(source, data)

def find_build_args_make(source):
    make_exe = "make"
    process = subprocess.Popen([make_exe, "--always-make", "--dry-run", "--keep-going", "VERBOSE=1"],
                                stdout=subprocess.PIPE,
                               )
    while process.poll():
        time.sleep(1)

    out = process.stdout.read()
    process.stdout.close()

    # print("done!", len(out), "bytes")
    data = out.decode("utf-8", errors="ignore").split("\n")
    return find_arg(source, data)

def main():

    # currently only supports ninja or makefiles
    build_file_ninja = os.path.join(BUILD_DIR, "build.ninja")
    build_file_make = os.path.join(BUILD_DIR, "Makefile")
    if os.path.exists(build_file_ninja):
        print("Using Ninja")
        arg = find_build_args_ninja(SOURCE_FILE)
    elif os.path.exists(build_file_make):
        print("Using Make")
        arg = find_build_args_make(SOURCE_FILE)
    else:
        sys.stderr.write("Can't find Ninja or Makefile (%r or %r), aborting" % (build_file_ninja, build_file_make))
        return

    if arg is None:
        sys.stderr.write("Can't find file %r in build command output of %r, aborting" % (SOURCE_FILE, BUILD_DIR))
        return

    # now we need to get arg and modify it to produce assembler
    arg_split = shlex.split(arg)

    # get rid of: 'cd /a/b/c && ' prefix used by make (ninja doesn't need)
    try:
        i = arg_split.index("&&")
    except ValueError:
        i = -1
    if i != -1:
        del arg_split[:i + 1] 

    if COMPILER_ID == 'GCC':
        # remove arg pairs
        for arg, n in (("-o", 2), ("-MF", 2), ("-MT", 2), ("-MMD", 1)):
            if arg in arg_split:
                i = arg_split.index(arg)
                del arg_split[i : i + n]

        # --- Switch debug for optimized ---
        for arg, n in (("-O0", 1),
                       ("-g", 1), ("-g1", 1), ("-g2", 1), ("-g3", 1),
                       ("-ggdb", 1), ("-ggdb", 1), ("-ggdb1", 1), ("-ggdb2", 1), ("-ggdb3", 1),
                       ("-fno-inline", 1),
                       ("-fsanitize=address", 1),
                       ("-fsanitize=undefined", 1),
                       ("-DDEBUG", 1), ("-D_DEBUG", 1),
                       ):
            if arg in arg_split:
                i = arg_split.index(arg)
                del arg_split[i : i + n]

        # add optimized args
        arg_split += ["-O3", "-fomit-frame-pointer", "-DNDEBUG", "-Wno-error"]

        # not essential but interesting to know
        arg_split += ["-ftree-vectorizer-verbose=1"]

        arg_split += ["-S"]
        # arg_split += ["-masm=intel"]  # optional
        # arg_split += ["-fverbose-asm"]  # optional but handy
    else:
        sys.stderr.write("Compiler %r not supported" % COMPILER_ID)
        return

    source_asm = SOURCE_FILE + ".asm"

    # Never overwrite existing files
    i = 1
    while os.path.exists(source_asm):
        source_asm = SOURCE_FILE + ".asm.%d" % i
        i += 1

    arg_split += ["-o", source_asm]

    # print("Executing:", arg_split)
    subprocess.call(arg_split)

    if not os.path.exists(source_asm):
        sys.stderr.write("Did not create %r from calling %r" % (source_asm, " ".join(arg_split)))
        return
    print("Running: %r" % " ".join(arg_split))
    print("Created: %r" % source_asm)


if __name__ == "__main__":
    main()