fox-tools/gcc/fox32-compile
2023-02-03 17:53:38 -08:00

171 lines
5.5 KiB
Python
Executable File

#!/usr/bin/env python3
# fox32-compile - fox32 compiler driver/wrapper
import argparse, sys, subprocess, os, re, shutil
def run(command_line):
print(f"Running command: {command_line}")
if code := subprocess.run(command_line).returncode:
sys.stderr.write(f"Command failed (exit status {code}): {command_line})\n")
sys.exit(1)
class Flavor:
"""
Compiler flavor base class. The compiler flavor determines where we look
for the compiler and how we invoke it.
"""
@classmethod
def command_names(self):
raise Exception(f"Please implement command_names() in class {self.__name__}")
@classmethod
def invoke(self, command, c_file, s_file):
raise Exception(f"Please implement invoke() in class {self.__name__}")
class GCC(Flavor):
basename = 'gcc'
def prefixes():
for bits in ['32', '64']:
yield f'riscv{bits}-linux-gnu-'
@classmethod
def command_names(self):
for p in self.prefixes():
yield p + self.basename
@classmethod
def invoke(self, command, c_file, s_file):
run([command, '-march=rv32im', '-mabi=ilp32', '-Os', '-S', c_file, '-o', s_file])
class GPlusPlus(GCC):
basename = 'g++'
class Clang(Flavor):
basename = 'clang'
@classmethod
def command_names(self):
yield self.basename
@classmethod
def invoke(self, command, c_file, s_file):
run([command, '-target', 'riscv32', '-Os', '-S', c_file, '-o', s_file])
class ClangPlusPlus(Clang):
basename = 'clang++'
class RustC(Flavor):
pass # TODO
class Driver:
"""
Main compiler driver. It finds the right compiler and invokes it
"""
def __init__(self, argv0, args):
self.basename = os.path.basename(argv0)
self.args = args
self.flavors = self.determine_flavors()
self.c_files, self.asm_files = self.classify_inputs(args.INPUT)
self.output = self.determine_output()
self.rv2fox = os.path.dirname(argv0) + '/rv2fox'
self.fox32asm = 'fox32asm'
@staticmethod
def classify_inputs(inputs):
c = []
asm = []
for file in inputs:
if file.endswith('.c'):
c.append(file)
elif file.endswith('.asm'):
asm.append(file)
else:
sys.stderr.write(f"Can't classify filename '{file}'\n")
sys.exit(1)
if not c:
sys.stderr.write(f"No C/C++ input file provided. Can't continue.\n")
sys.exit(1)
if len(c) > 1:
sys.stderr.write(f"More than one C/C++ input file specified. This is currently not supported.\n")
sys.exit(1)
if asm:
sys.stderr.write(f"ASM files specified. This is not yet supported.\n")
sys.exit(1)
return c, asm
def determine_output(self):
if self.args.output:
out = self.args.output
if not out.endswith('.fxf') and not out.endswith('.bin'):
sys.stderr.write(f"Invalid output filename '{out}'. Must end with .fxf or .bin\n")
sys.exit(1)
return out
file = self.c_files[0]
if m := re.fullmatch(r'(.*)\.\w+', file):
return m.groups()[0] + '.fxf'
@staticmethod
def find_flavor(name):
match name:
case 'gcc': return [GCC]
case 'g++': return [GPlusPlus]
case 'clang': return [Clang]
case 'clang++': return [ClangPlusPlus]
case 'cc': return [GCC, Clang]
case 'c++': return [GPlusPlus, ClangPlusPlus]
def determine_flavors(self):
if self.args.flavor:
if not (flavors := self.find_flavor(self.args.flavor)):
sys.stderr.write(f"Specified flavor '{self.args.flavor}' is not supported.\n")
sys.exit(1)
return flavors
m = re.fullmatch(r'fox32-(.*)', self.basename)
if not m or not (flavors := self.find_flavor(*m.groups())):
sys.stderr.write(f"Program was invoked as {self.basename}, can't determine flavor.\n")
sys.stderr.write(f"Please use e.g. '--flavor gcc' or invoke fox32-gcc\n")
sys.exit(1)
return flavors
def determine_command(self):
for flavor in self.flavors:
for command_name in flavor.command_names():
if shutil.which(command_name):
return flavor, command_name
sys.stderr.write(f"No suitable compiler found.\n")
sys.exit(1)
def compile(self):
flavor, command = self.determine_command()
main_asm_files = []
for c_file in self.c_files:
s_file = c_file + '.tmp.s'
asm_file = c_file + '.tmp.asm'
flavor.invoke(command, c_file, s_file)
run([self.rv2fox, s_file, '-o', asm_file])
main_asm_files.append(asm_file)
command_line = [self.fox32asm, main_asm_files[0], self.output]
if self.args.start_file:
command_line += ['--start-file', self.args.start_file]
run(command_line)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='fox32 compiler driver')
parser.add_argument('INPUT', nargs='+', help='Inputs: .c/.cpp and .asm files')
parser.add_argument('--output', '-o', help='Output file (.fxf or .bin)')
parser.add_argument('--flavor', help='Flavor, i.e. gcc, g++, clang, etc.')
parser.add_argument('--start-file', help='Start file to use instead of start.asm')
args = parser.parse_args()
driver = Driver(sys.argv[0], args)
driver.compile()