fox-tools/gcc/rv2fox
2023-02-07 01:04:10 -08:00

359 lines
11 KiB
Python
Executable File

#!/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 zero(self, *data):
"""
Emit zeros
"""
for d in data:
self.write(f'data.fill 0, {d}')
def strz(self, *data):
"""
Emit null-terminated string
"""
for d in data:
self.write(f'data.strz \"{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)
def rta(self, dest, source):
"""
Emit an rta instruction
"""
source = self._adjust_label(source)
self.insn(f'rta', dest, source)
def halt(self):
"""
Emit an halt instruction
"""
self.insn('halt')
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 = [f'x{n}' for n in range(32)]
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.regs += [f's{n}' for n in range(2, 12)]
self.regs += [f't{n}' for n in range(3, 7)]
self.regidx = { r: i for i, r in enumerate(self.regs) }
self.xregidx = { r: i for i, r in enumerate(self.xregs) }
self.tmp = 'r0' # temporary register
self.branches = ['bge', 'bgeu', 'blt', 'bltu', 'ble', 'bleu', 'bne', 'bneu']
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,
'lbu': 8, 'lhu': 16,
'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())
elif m := re.fullmatch(r'zero\s+([^\s]+)', line):
self.e.zero(*m.groups())
elif m := re.fullmatch(r'string\s+\"(.*)\"', line):
self.e.strz(*m.groups())
elif m := re.fullmatch(r'asciz\s+\"(.*)\"', line):
self.e.strz(*m.groups())
elif m := re.match(r'set\s([^,]*),', line):
self.e.label(*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]}'
elif reg in self.xregidx:
return f'r{self.xregidx[reg]}'
else:
raise Exception(f'Unknown register {reg}')
@staticmethod
def convert_arithmetic_op(op):
if op in ['add', 'addi']: return 'add'
if op in ['sub', 'subi']: return 'sub'
if op in ['mul']: return 'mul'
if op in ['div']: return 'idiv'
if op in ['divu']: return 'div'
if op in ['rem']: return 'irem'
if op in ['remu']: return 'rem'
if op in ['and', 'andi']: return 'and'
if op in ['or', 'ori']: return 'or'
if op in ['xor', 'xori']: return 'xor'
if op in ['sll', 'slli']: return 'sla'
if op in ['srl', 'srli']: return 'srl'
else:
raise Exception(f'Unknown op {op}')
@staticmethod
def convert_condition(op):
if op in ['blt', 'bltu']: return 'iflt', False
if op in ['bne', 'bnez']: return 'ifnz', False
if op in ['ble', 'bleu']: return 'ifgt', True
if op in ['bge', 'bgeu']: return 'ifgteq', False
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', 'subi', 'div', 'rem', 'andi', 'ori', 'xori', 'slli', 'srli']:
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', 'sub', 'mul', 'divu', 'remu', 'and', 'nor', 'xor', 'sll', 'srl']:
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', 'sh', 'lh', 'lhu', 'sb', 'lb', 'lbu']:
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))
elif insn == 'wfi':
self.e.halt()
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()