From ad1f64ea985799d52faae021b36e8c0367d6158e Mon Sep 17 00:00:00 2001 From: Ry Date: Fri, 3 Feb 2023 17:53:38 -0800 Subject: [PATCH] gcc: Import from the demos repo --- gcc/README.md | 14 +++ gcc/fox32-c++ | 1 + gcc/fox32-cc | 1 + gcc/fox32-clang | 1 + gcc/fox32-clang++ | 1 + gcc/fox32-compile | 170 ++++++++++++++++++++++++++ gcc/fox32-g++ | 1 + gcc/fox32-gcc | 1 + gcc/rv2fox | 305 ++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 495 insertions(+) create mode 100644 gcc/README.md create mode 120000 gcc/fox32-c++ create mode 120000 gcc/fox32-cc create mode 120000 gcc/fox32-clang create mode 120000 gcc/fox32-clang++ create mode 100755 gcc/fox32-compile create mode 120000 gcc/fox32-g++ create mode 120000 gcc/fox32-gcc create mode 100755 gcc/rv2fox diff --git a/gcc/README.md b/gcc/README.md new file mode 100644 index 0000000..5f3ce28 --- /dev/null +++ b/gcc/README.md @@ -0,0 +1,14 @@ +# Compiling C code for fox32 + +How does this work? + +- GCC compiles your C code with `riscv*-gcc -S hello.c -march=rv32im -mabi=ilp32 -O2` +- `rv2fox` converts the generated RISC-V assembly to fox32 assembly +- `fox32asm` makes an FXF binary + +## TODO: + +- commandline argument passing +- calls out of C, into the ROM +- testcase sha256 program that hashes the ROM +- rust support: `rustc --emit asm --target riscv32im-unknown-none-elf test.rs -O` diff --git a/gcc/fox32-c++ b/gcc/fox32-c++ new file mode 120000 index 0000000..fd169f9 --- /dev/null +++ b/gcc/fox32-c++ @@ -0,0 +1 @@ +fox32-compile \ No newline at end of file diff --git a/gcc/fox32-cc b/gcc/fox32-cc new file mode 120000 index 0000000..fd169f9 --- /dev/null +++ b/gcc/fox32-cc @@ -0,0 +1 @@ +fox32-compile \ No newline at end of file diff --git a/gcc/fox32-clang b/gcc/fox32-clang new file mode 120000 index 0000000..fd169f9 --- /dev/null +++ b/gcc/fox32-clang @@ -0,0 +1 @@ +fox32-compile \ No newline at end of file diff --git a/gcc/fox32-clang++ b/gcc/fox32-clang++ new file mode 120000 index 0000000..fd169f9 --- /dev/null +++ b/gcc/fox32-clang++ @@ -0,0 +1 @@ +fox32-compile \ No newline at end of file diff --git a/gcc/fox32-compile b/gcc/fox32-compile new file mode 100755 index 0000000..facf4cc --- /dev/null +++ b/gcc/fox32-compile @@ -0,0 +1,170 @@ +#!/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() diff --git a/gcc/fox32-g++ b/gcc/fox32-g++ new file mode 120000 index 0000000..fd169f9 --- /dev/null +++ b/gcc/fox32-g++ @@ -0,0 +1 @@ +fox32-compile \ No newline at end of file diff --git a/gcc/fox32-gcc b/gcc/fox32-gcc new file mode 120000 index 0000000..fd169f9 --- /dev/null +++ b/gcc/fox32-gcc @@ -0,0 +1 @@ +fox32-compile \ No newline at end of file diff --git a/gcc/rv2fox b/gcc/rv2fox new file mode 100755 index 0000000..f977568 --- /dev/null +++ b/gcc/rv2fox @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +# RISC-V assembly to fox32 translator +import argparse +import re + +class Emitter: + """ + The Emitter class emits fox32 assembly code. + """ + + def __init__(self, filename): + self.filename = filename + self.f = open(filename, 'w') + + def write(self, line): + self.f.write(line + '\n') + + @staticmethod + def _adjust_label(label): + """ + Adjust a label to the requirements of fox32asm + """ + l = label.replace('.', '_') + if l.startswith('_'): + return 'U' + l[1:] + return l + + @staticmethod + def _adjust_operand(op): + """ + Adjust an operand to the requirements of fox32asm + """ + if re.fullmatch(r'-?[0-9]x?[0-9a-fA-F]*', op): + return f'{int(op, 0) & 0xffffffff:#x}' + return op + + def insn(self, operation, *operands): + """ + Emit an instruction + """ + line = '\t' + operation + if operands: + line += '\t' + ', '.join([self._adjust_operand(op) for op in operands]) + self.write(line) + + def include(self, filename): + """ + Emit an #include directive + """ + self.write(f'#include "{filename}"') + + def comment(self, line): + """ + Emit a comment + """ + self.write(f'; {line}') + + def label(self, label): + """ + Emit a label definition + """ + self.write(self._adjust_label(label) + ':') + + def data(self, *data, size=32): + """ + Emit data + """ + for d in data: + self.write(f'data.{size} {d}') + + def nop(self): + """ + Emit a nop instruction + """ + self.insn('nop') + + def jump(self, label, condition=''): + """ + Emit a jump to a label, optionally with a condition code + """ + label = self._adjust_label(label) + if condition: + self.insn(f'{condition} rjmp', label) + else: + self.insn('rjmp', label) + + def jump_reg(self, reg): + """ + Emit a jump to a register + """ + self.insn('jmp', reg) + + def mov(self, dest, source, size=32): + """ + Emit a mov instruction + """ + self.insn(f'movz.{size}', dest, source) + + def store(self, rmem, rval, size=32): + """ + Emit a mov instruction that performs a memory store + """ + self.mov(f'[{rmem}]', rval, size) + + def load(self, rmem, rval, size=32): + """ + Emit a mov instruction that performs a memory load + """ + self.mov(rval, f'[{rmem}]', size) + + +class Converter: + """ + This class converts RISC-V assembly code to fox32 assembly code + """ + def __init__(self, input, output, *, debug=False, start_file='start.asm', additional_includes=[]): + self.input_file = open(input, 'r') + self.e = Emitter(output) + + self.debug = debug + self.start_file = start_file + self.additional_includes = additional_includes + + self.xregs = ['x{n}' for n in range(32)] # TODO: map all registers, consider calling conventions + self.regs = ['zero', 'ra', 'sp', 'gp', 'tp'] + self.regs += [f't{n}' for n in range(3)] + self.regs += [f's{n}' for n in range(2)] + self.regs += [f'a{n}' for n in range(8)] + self.regidx = { r: i for i, r in enumerate(self.regs) } + + self.tmp = 'r0' # temporary register + self.branches = ['bge', 'blt', 'ble', 'bne'] + + self.label_gen = iter(range(1000000)) + + def unique_label(self): + return f'rv2fox_{(next(self.label_gen))}' + + @staticmethod + def ldst_to_size(insn): + size_map = { + 'lb': 8, 'lh': 16, 'lw': 32, + 'sb': 8, 'sh': 16, 'sw': 32, + } + if insn in size_map: + return size_map[insn] + + def ldst(self, insn, rval, rbase, offset): + size = self.ldst_to_size(insn) + if int(offset, 0) != 0: + self.e.mov(self.tmp, rbase) + self.e.insn('add', self.tmp, offset) + rmem = self.tmp + else: + rmem = rbase + if insn.startswith('s'): + self.e.store(rmem, rval, size) + else: + self.e.load(rmem, rval, size) + + def convert_directive(self, line): + if line in ['text', 'data']: + pass # ignore + elif m := re.fullmatch(r'word\s+([^\s]+)', line): + self.e.data(*m.groups()) + + def reg(self, reg): + """ + Convert a RISC-V register to a fox32 register + """ + if reg == 'sp': + return 'rsp' + elif reg in self.regidx: + return f'r{self.regidx[reg]}' + else: + raise Exception(f'Unknown register {reg}') + + @staticmethod + def convert_arithmetic_op(op): + if op in ['add', 'addi']: return 'add' + if op in ['sll', 'slli']: return 'sla' + else: + raise Exception(f'Unknown op {op}') + + @staticmethod + def convert_condition(op): + if op in ['blt']: return 'iflt', False + if op in ['bne', 'bnez']: return 'ifnz', False + if op in ['ble']: return 'ifgt', True + + def convert_branch(self, insn, rs1, rs2, label): + cond, reverse = self.convert_condition(insn) + if reverse: + rs1, rs2 = rs2, rs1 + self.e.insn('cmp', self.reg(rs1), self.reg(rs2)) + self.e.jump(label, cond) + + def convert_branch_imm(self, insn, rs, label, imm): + cond, reverse = self.convert_condition(insn) + if reverse: + rs1, rs2 = rs2, rs1 + self.e.insn('cmp', self.reg(rs), str(imm)) + self.e.jump(label, cond) + + @staticmethod + def iter_operands(operands): + if operands: + for op in operands.split(','): + op = op.strip() + if op != '': + yield op + + def convert_insn(self, insn, operands=None): + tmp = self.tmp + operands = list(self.iter_operands(operands)) + + if insn == 'nop': + self.e.nop() + elif insn in ['addi', 'slli']: + rd, rs, imm = operands + op = self.convert_arithmetic_op(insn) + if rd == rs: + self.e.insn(op, self.reg(rd), imm) + else: + self.e.mov(tmp, self.reg(rs)) + self.e.insn(op, tmp, imm) + self.e.mov(self.reg(rd), tmp) + elif insn in ['add']: + rd, rs1, rs2 = operands + if rd == rs1: + op = self.convert_arithmetic_op(insn) + self.e.insn(op, self.reg(rd), self.reg(rs2)) + else: + op = self.convert_arithmetic_op(insn) + self.e.mov(tmp, self.reg(rs1)) + self.e.insn(op, tmp, self.reg(rs2)) + self.e.mov(self.reg(rd), tmp) + elif insn in ['sw', 'lw']: + rval, address = operands + offset, rbase = re.fullmatch(r'(-?[0-9]+)\(([a-z0-9]+)\)', address).groups() + self.ldst(insn, self.reg(rval), self.reg(rbase), offset) + elif insn in ['j']: + self.e.jump(*operands) + elif insn in ['jr']: + self.e.jump_reg(self.reg(*operands)) + elif insn in ['ret']: + self.e.jump_reg(self.reg('ra')) + elif insn in ['lla']: + rd, label = operands + self.e.rta(self.reg(rd), label) + elif insn in ['mv']: + rd, rs = operands + self.e.mov(self.reg(rd), self.reg(rs)) + elif insn in ['call']: + label = self.unique_label() + self.e.mov(self.reg('ra'), label) + self.e.jump(*operands) + self.e.label(label) + elif insn in self.branches: + self.convert_branch(insn, *operands) + elif insn in ['bnez']: + self.convert_branch_imm(insn, *operands, 0) + elif insn in ['li']: + rd, imm = operands + self.e.mov(self.reg(rd), imm) + elif insn in ['lui']: + rd, imm = operands + self.e.mov(self.reg(rd), str(int(imm, 0) << 12)) + else: + raise Exception(f'Unknown instruction {insn} {operands}') + + def convert(self): + self.e.comment(f'Generated with rv2fox') + + self.e.include(self.start_file) + for file in self.additional_includes: + self.e.include(file) + + for i, line in enumerate(self.input_file): + num = i + 1 + line = line.strip() + + if self.debug: + self.e.write(f'\t; {line}') + + if line == '' or re.fullmatch(r'#.*', line): + pass + elif re.fullmatch(r'\.[a-z]+.*', line): + self.convert_directive(line[1:]) + elif m := re.fullmatch(r'([a-zA-Z0-9_.]+):', line): + self.e.label(*m.groups()) + elif m := re.fullmatch(r'([a-z]+)', line) or \ + re.fullmatch(r'([a-z]+)\s+([^\s].*)', line): + self.convert_insn(*m.groups()) + else: + raise Exception(f'Unknown line: {line}') + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Convert RISC-V assembly to fox32 assembly') + parser.add_argument('input', help='RISC-V input') + parser.add_argument('-o', '--output', help='fox32 output') + parser.add_argument('--debug', help='produce verbose output to allow debugging this tool', action='store_true') + args = parser.parse_args() + c = Converter(args.input, args.output, debug=args.debug) + c.convert()