gcc: Import from the demos repo

This commit is contained in:
Ry 2023-02-03 17:53:38 -08:00
parent b334cdeb3d
commit ad1f64ea98
9 changed files with 495 additions and 0 deletions

14
gcc/README.md Normal file
View File

@ -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`

1
gcc/fox32-c++ Symbolic link
View File

@ -0,0 +1 @@
fox32-compile

1
gcc/fox32-cc Symbolic link
View File

@ -0,0 +1 @@
fox32-compile

1
gcc/fox32-clang Symbolic link
View File

@ -0,0 +1 @@
fox32-compile

1
gcc/fox32-clang++ Symbolic link
View File

@ -0,0 +1 @@
fox32-compile

170
gcc/fox32-compile Executable file
View File

@ -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()

1
gcc/fox32-g++ Symbolic link
View File

@ -0,0 +1 @@
fox32-compile

1
gcc/fox32-gcc Symbolic link
View File

@ -0,0 +1 @@
fox32-compile

305
gcc/rv2fox Executable file
View File

@ -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()