postprocess-asm   [plain text]


#!/usr/bin/env ruby

# Copyright (C) 2020 Igalia S. L.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
# THE POSSIBILITY OF SUCH DAMAGE.

# Wrapper for a .cpp -> .o compilation command. It
# 1. converts the command to generate a `.s' file
# 2. runs the ASM postprocessor on it to generate the final `.s` file
# 3. assembles the `.s` file to a `.o` file

$asm_suffix_pre = ".pre.s"
$asm_suffix = ".s"
$postprocessor = "#{File.dirname($0)}/resolve-asm-file-conflicts.rb"


$intermediate_paths = []

# We need to work with indices a lot and unfortunately 'getoptlong' in
# the standard library doesn't expose optind, so we're going with
# array searches for simplicity.
def index_nofail(ary, f, errmsg)
  idx = ary.index { |el|
    f.call(el)
  }
  if idx.nil?
    $stderr.puts(errmsg)
    exit(3)
  end
  idx
end

# Find and return the source file for this compilation command,
# removing it from the args. Note that the path (A) as it appears here
# (coming from a cmake rule) is likely to be different to the
# anonymous argument (B) to the compilation command. However, A will
# be a suffix of B.
#
# Exit with an error if the argument is not there. This has already
# been checked by `cxx-wrapper`, otherwise we wouldn't be running.
def extract_input!(args)
  prefix = '-DPOSTPROCESS_ASM='

  idx = index_nofail(args, Proc.new { |arg|
    arg.start_with?(prefix)
  }, "No `-DPOSTPROCESS_ASM` argument`")

  path = args[idx][prefix.size..-1]
  if path.size == 0
    $stderr.puts("Empty path in -DPOSTPROCESS_ASM=")
    exit(3)
  end
  # We only need this to be defined for the preprocessor (not any
  # wrapper) from now on.
  args[idx] = "-DPOSTPROCESS_ASM"
  return path
end

# Get the index of the first argument ending in this suffix.
#
# Exit with an error if the argument isn't there. We're only ever
# called with arguments we know are being passed in by the build
# system.
def get_arg_idx_suffix(args, wanted)
  index_nofail(args, Proc.new { |arg|
                 arg.end_with?(wanted)
               }, "No argument ends with #{wanted}")
end

# Get index of a given argument. Die if it's not there.
def get_arg_idx(args, wanted)
  index_nofail(args, Proc.new { |arg|
                 arg == wanted
               }, "No `#{wanted}` argument")
end

# Get the index of `-o` and verify that an argument follows.
# Both are guaranteed to exist (from our build system).
def get_o_idx(args)
  i = get_arg_idx(args, '-o')
  if (i + 1) >= args.size
    $stderr.puts("No argument to `-o`")
    exit(3)
  end
  i
end

# Run command and die if it fails, propagating the exit code.
def run_cmd(cmd)
  pid = Process.spawn(*cmd)
  Process.waitpid(pid)
  ret = $?
  if not ret.success?
    $stderr.puts("Error running cmd: #{ret}")
    exit(ret.exitstatus)
  end
end

# Convert
#    cxx -o blah.o -c blah.cpp
# to
#    cxx -o blah.s -S blah.cpp
def build_cxx_cmd(args)
  c_idx = get_arg_idx(args, '-c')
  o_idx = get_o_idx(args)

  cxx_args = args.clone

  cxx_args[c_idx] = '-S'
  o_path = cxx_args[o_idx + 1]
  cxx_args[o_idx + 1] = o_path.sub(/[.]o$/, $asm_suffix)
  $intermediate_paths << cxx_args[o_idx + 1]
  if cxx_args[o_idx + 1] == o_path
    $stderr.puts("Output file name not an object file: `#{o_path}`")
    exit(3)
  end
  cxx_args
end

# Do
#     mv blah.S blah.pre.S
# The reason we do a rename instead of directly generating the .pre.s
# file when compiling is so that the corresponding .dwo file will have
# the correct name embedded.
def rename_s_file(args)
  o_path = args[get_o_idx(args) + 1]
  File.rename(o_path.sub(/[.]o$/, $asm_suffix),
              o_path.sub(/[.]o$/, $asm_suffix_pre))
end

# Build
#     postprocessor blah.pre.S blah.S
def build_postprocessor_cmd(args)
  o_path = args[get_o_idx(args) + 1]

  pp_args = [
    $postprocessor,
    o_path.sub(/[.]o$/, $asm_suffix_pre), # input
    o_path.sub(/[.]o$/, $asm_suffix) # output
  ]
  $intermediate_paths << pp_args[-2]
  $intermediate_paths << pp_args[-1]
  pp_args
end

# Build
#     cxx -o blah.o -c blah.S
def build_as_cmd(args, i_path)
  i_idx = get_arg_idx_suffix(args, i_path)
  o_path = args[get_o_idx(args) + 1]

  as_args = args.clone
  i_path = as_args[i_idx]
  as_args[i_idx] = o_path.sub(/[.]o$/, $asm_suffix)
  as_args
end

args = ARGV.to_a
i_path = extract_input!(args)

begin
  run_cmd(build_cxx_cmd(args))
  rename_s_file(args)
  run_cmd(build_postprocessor_cmd(args))
  run_cmd(build_as_cmd(args, i_path))
ensure
  $intermediate_paths.each { |p|
    if File.exist?(p)
      File.delete(p)
    end
  }
end