Initial commit
This commit is contained in:
45
tools/as.ebnf
Normal file
45
tools/as.ebnf
Normal file
@@ -0,0 +1,45 @@
|
||||
COMMENT: /\/\/[^\n]*/
|
||||
|
||||
!opcode: "nop"i
|
||||
| "load"i
|
||||
| "store"i
|
||||
| "add"i
|
||||
| "sub"i
|
||||
| "and"i
|
||||
| "or"i
|
||||
| "not"i
|
||||
| "xor"i
|
||||
| "seth"i
|
||||
| "shr"i
|
||||
| "cmp"i
|
||||
| "mul"i
|
||||
| "beq"i
|
||||
| "set"i
|
||||
| "bneq"i
|
||||
|
||||
!register: REGISTER
|
||||
| "sp"i
|
||||
| "lr"i
|
||||
| "pc"i
|
||||
|
||||
LABEL: /[a-z_]\w*/i
|
||||
|
||||
REGISTER: /[Rr]\d+/
|
||||
|
||||
start: line*
|
||||
?line: label | statement | raw_word | symbol_def
|
||||
label: LABEL ":"
|
||||
statement: opcode [param ("," param)*]
|
||||
!label_ref: LABEL
|
||||
!immediate: SIGNED_NUMBER | HEX_LITTERAL
|
||||
?param: register | label_ref | reg_offset | immediate
|
||||
reg_offset: "[" register ("," immediate)? "]"
|
||||
raw_word: ".word" immediate
|
||||
symbol_def: ".global" label_ref
|
||||
|
||||
HEX_LITTERAL: /0x[a-fA-F0-9]+/
|
||||
|
||||
%import common.WS
|
||||
%import common.SIGNED_NUMBER
|
||||
%ignore COMMENT
|
||||
%ignore WS
|
186
tools/as.py
Normal file
186
tools/as.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import argparse
|
||||
import enum
|
||||
import lark
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import obj_pb2
|
||||
|
||||
GRAMMAR_FILE = '/home/paulmathieu/vhdl/bin/as.ebnf'
|
||||
|
||||
opcodes = {
|
||||
'nop' : lambda: '0000',
|
||||
'load' : lambda p0, p1, p2: f'1{p0:x}{p1:x}{(p2 >> 1)&0xf:x}',
|
||||
'store': lambda p0, p1, p2: f'2{p0:x}{p1:x}{(p2 >> 1)&0xf:x}',
|
||||
'add' : lambda p0, p1, p2: f'3{p0:x}{p1:x}{p2:x}',
|
||||
'sub' : lambda p0, p1, p2: f'4{p0:x}{p1:x}{p2:x}',
|
||||
'or' : lambda p0, p1, p2: f'5{p0:x}{p1:x}{p2:x}',
|
||||
'and' : lambda p0, p1, p2: f'6{p0:x}{p1:x}{p2:x}',
|
||||
'not' : lambda p0, p1: f'7{p0:x}{p1:x}0',
|
||||
'xor' : lambda p0, p1, p2: f'8{p0:x}{p1:x}{p2:x}',
|
||||
'seth' : lambda p0, p1: f'9{p0:x}{p1&0xff:02x}',
|
||||
'shr' : lambda p0, p1, p2: f'a{p0:x}{p1:x}{p2:x}',
|
||||
'mul' : lambda p0, p1, p2: f'b{p0:x}{p1:x}{p2:x}',
|
||||
'cmp' : lambda p0, p1: f'c{p0:x}{p1:x}0',
|
||||
'beq' : lambda p0, p1: f'd{p0:x}{p1&0xff:02x}',
|
||||
'set' : lambda p0, p1: f'e{p0:x}{p1&0xff:02x}',
|
||||
'bneq' : lambda p0, p1: f'f{p0:x}{p1&0xff:02x}',
|
||||
'.word': lambda p0: f'{p0:04x}',
|
||||
}
|
||||
|
||||
|
||||
class AsTransformer(lark.Transformer):
|
||||
def __init__(self):
|
||||
self.addr = 0
|
||||
|
||||
def statement(self, s):
|
||||
opcode, *params = s
|
||||
addr = self.addr
|
||||
self.addr += 2
|
||||
return ('OP', addr, opcode, *params)
|
||||
|
||||
def opcode(self, o):
|
||||
(o, ) = o
|
||||
return str(o)
|
||||
|
||||
def reg_offset(self, r):
|
||||
if len(r) < 2:
|
||||
return r[0], 0
|
||||
return tuple(r[:])
|
||||
|
||||
LABEL = str
|
||||
HEX_LITTERAL = lambda _, x: int(x[2:], 16)
|
||||
|
||||
def label(self, l):
|
||||
(l,) = l
|
||||
return ('LBL', l, self.addr)
|
||||
|
||||
def label_ref(self, l):
|
||||
(l,) = l
|
||||
return l
|
||||
|
||||
def raw_word(self, w):
|
||||
(w,) = w
|
||||
addr = self.addr
|
||||
self.addr += 2
|
||||
return ('OP', addr, '.word', w)
|
||||
|
||||
def register(self, r):
|
||||
(r,) = r
|
||||
if r == 'pc':
|
||||
return 14
|
||||
if r == 'lr':
|
||||
return 13
|
||||
if r == 'sp':
|
||||
return 12
|
||||
return int(r[1:])
|
||||
|
||||
def immediate(self, i):
|
||||
(i,) = i
|
||||
return int(i)
|
||||
|
||||
def symbol_def(self, s):
|
||||
(sym,) = s
|
||||
return ('SYM', s[0])
|
||||
|
||||
start = list
|
||||
|
||||
|
||||
def generate_ops(ops, labels, relocs):
|
||||
def filter_label(params, pc):
|
||||
for p in params:
|
||||
if isinstance(p, str): # label ref
|
||||
if len(params) == 1: # branch
|
||||
yield 14 # pc
|
||||
yield labels[p] - pc - 2
|
||||
else: # set, allow relocs here
|
||||
relocs.append((pc, p))
|
||||
yield 0xff
|
||||
elif isinstance(p, tuple): # reg offset
|
||||
yield p[0]
|
||||
yield p[1]
|
||||
else:
|
||||
yield p
|
||||
|
||||
for op in ops:
|
||||
addr = op[0]
|
||||
fmt = opcodes[op[1]]
|
||||
params = filter_label(op[2:], addr)
|
||||
yield fmt(*params)
|
||||
|
||||
|
||||
def larkparse(f, debug=False):
|
||||
with open(GRAMMAR_FILE) as g:
|
||||
asparser = lark.Lark(g.read())
|
||||
tree = asparser.parse(f.read())
|
||||
if debug:
|
||||
print(tree.pretty())
|
||||
lines = AsTransformer().transform(tree)
|
||||
labels = {l[1]: l[2] for l in lines if l[0] == 'LBL'}
|
||||
ops = [l[1:] for l in lines if l[0] == 'OP']
|
||||
syms = [(l[1], labels[l[1]]) for l in lines if l[0] == 'SYM']
|
||||
relocs = []
|
||||
return [int(x, 16) for x in generate_ops(ops, labels, relocs)], syms, relocs
|
||||
|
||||
|
||||
# any record: >BHs, type, length, data
|
||||
# header: >cccc, "pol0"
|
||||
# text record: >BHs, name, text_len, text
|
||||
# reloc record: >pHip, txt_rec_name, offset, target
|
||||
|
||||
def write_obj(fout, ops, syms, relocs):
|
||||
obj = obj_pb2.ObjFile(header=obj_pb2.Header(magic='pol0'))
|
||||
|
||||
ends = [s[1] for s in syms[1:]] + [2 * len(ops)]
|
||||
data = b''.join(struct.pack('>H', op) for op in ops)
|
||||
|
||||
for (name, begin), end in zip(syms, ends):
|
||||
section = obj_pb2.Section(name=name, text=data[begin:end])
|
||||
obj.sections.append(section)
|
||||
for offs, target in relocs:
|
||||
if offs >= begin and offs < end:
|
||||
section_off = offs - begin
|
||||
reloc = obj_pb2.Reloc(section=name, offset=section_off, target=target)
|
||||
obj.relocs.append(reloc)
|
||||
|
||||
fout.write(obj.SerializeToString())
|
||||
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Assemble.')
|
||||
group = parser.add_mutually_exclusive_group()
|
||||
group.add_argument('--vhdl', action='store_true',
|
||||
help='output VHDL code')
|
||||
group.add_argument('--coe', action='store_true',
|
||||
help='output a Vivado COE file')
|
||||
parser.add_argument('--debug', action='store_true',
|
||||
help='print the AST')
|
||||
parser.add_argument('--input', '-c', type=argparse.FileType('r'),
|
||||
default=sys.stdin, help='input file (default: stdin)')
|
||||
parser.add_argument('--output', '-o', type=argparse.FileType('wb'),
|
||||
default=sys.stdout.buffer, help='output file')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
ops, syms, relocs = larkparse(args.input, debug=args.debug)
|
||||
|
||||
if args.vhdl:
|
||||
print(',\n'.join(f'x"{x:04x}"' for x in ops))
|
||||
for addr, target in relocs:
|
||||
print(f'-- reloc: {target} @{addr}')
|
||||
elif args.coe:
|
||||
assert not relocs, "relocs not supported with coe output"
|
||||
print('memory_initialization_radix=16;')
|
||||
print('memory_initialization_vector=')
|
||||
code = ',\n'.join(f'{x:04x}' for x in ops)
|
||||
print(f'{code};')
|
||||
else:
|
||||
write_obj(args.output, ops, syms, relocs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
159
tools/cc.ebnf
Normal file
159
tools/cc.ebnf
Normal file
@@ -0,0 +1,159 @@
|
||||
start: top_level*
|
||||
|
||||
?top_level: fun_decl
|
||||
| global_var
|
||||
| fun_def
|
||||
| struct_def
|
||||
| typedef
|
||||
|
||||
fun_decl: fun_prot ";"
|
||||
fun_prot: type symbol "(" [fun_param ("," fun_param)*] ")"
|
||||
fun_param: type [symbol]
|
||||
|
||||
global_var: type symbol sized_array* empty_array? [ "=" litteral ] ";"
|
||||
|
||||
fun_def: fun_prot body
|
||||
|
||||
typedef: "typedef" type IDENTIFIER ";"
|
||||
|
||||
body: "{" statement* "}"
|
||||
|
||||
statement: if_stat
|
||||
| while_stat
|
||||
| for_stat
|
||||
| do_while_stat
|
||||
| "break" ";" -> break
|
||||
| "continue" ";" -> continue
|
||||
| "goto" label ";" -> goto // yay \o/
|
||||
| "return" expression ";" -> return_stat
|
||||
| local_var ";"
|
||||
| local_array
|
||||
| expression ";"
|
||||
| body
|
||||
|
||||
if_stat: "if" "(" expression ")" statement ["else" statement]
|
||||
while_stat: "while" "(" expression ")" statement
|
||||
for_stat: "for" "(" local_var? ";" expression? ";" iter_expression? ")" statement
|
||||
do_while_stat: "do" statement "while" "(" expression ")" statement
|
||||
|
||||
iter_expression: expression
|
||||
local_var: type symbol initializer?
|
||||
local_array: type symbol sized_array* (sized_array | empty_array) initializer? ";"
|
||||
empty_array: "[" "]"
|
||||
sized_array: "[" array_size "]"
|
||||
initializer: "=" (expression | initializer_list)
|
||||
initializer_list: "{" [init_list_field ("," init_list_field)* ","? ] "}"
|
||||
?init_list_field: "." field "=" expression
|
||||
| expression
|
||||
|
||||
// precedence from https://en.cppreference.com/w/c/language/operator_precedence
|
||||
|
||||
?expression: comma_expr
|
||||
|
||||
?comma_expr: prec14_expr
|
||||
| comma_expr "," prec14_expr
|
||||
|
||||
?prec14_expr: prec13_expr
|
||||
| prec2_expr "=" prec14_expr -> assignment
|
||||
| prec2_expr "+=" prec14_expr
|
||||
| prec2_expr "-=" prec14_expr
|
||||
| prec2_expr "*=" prec14_expr
|
||||
| prec2_expr "/=" prec14_expr
|
||||
| prec2_expr "%=" prec14_expr
|
||||
| prec2_expr "<<=" prec14_expr
|
||||
| prec2_expr ">>=" prec14_expr
|
||||
| prec2_expr "&=" prec14_expr
|
||||
| prec2_expr "^=" prec14_expr
|
||||
| prec2_expr "|=" prec14_expr
|
||||
|
||||
?prec13_expr: prec12_expr
|
||||
| prec12_expr "?" prec13_expr ":" prec13_expr
|
||||
|
||||
?prec12_expr: prec11_expr
|
||||
| prec12_expr "||" prec11_expr
|
||||
?prec11_expr: prec10_expr
|
||||
| prec11_expr "&&" prec10_expr
|
||||
?prec10_expr: prec9_expr
|
||||
| prec10_expr "|" prec9_expr
|
||||
?prec9_expr: prec8_expr
|
||||
| prec9_expr "^" prec8_expr
|
||||
?prec8_expr: prec7_expr
|
||||
| prec8_expr "&" prec7_expr -> _and // reserved work in python
|
||||
?prec7_expr: prec6_expr
|
||||
| prec7_expr "==" prec6_expr -> eq
|
||||
| prec7_expr "!=" prec6_expr -> neq
|
||||
?prec6_expr: prec5_expr
|
||||
| prec6_expr "<" prec5_expr -> lt
|
||||
| prec6_expr "<=" prec5_expr -> lte
|
||||
| prec6_expr ">" prec5_expr -> gt
|
||||
| prec6_expr ">=" prec5_expr -> gte
|
||||
?prec5_expr: prec4_expr
|
||||
| prec5_expr "<<" prec4_expr -> shl
|
||||
| prec5_expr ">>" prec4_expr -> shr
|
||||
?prec4_expr: prec3_expr
|
||||
| prec4_expr "+" prec3_expr -> add
|
||||
| prec4_expr "-" prec3_expr -> su
|
||||
?prec3_expr: prec2_expr
|
||||
| prec3_expr "*" prec2_expr -> mul
|
||||
| prec3_expr "/" prec2_expr -> div
|
||||
| prec3_expr "%" prec2_expr -> mod
|
||||
?prec2_expr: prec1_expr
|
||||
| "++" prec2_expr -> pre_increment
|
||||
| "--" prec2_expr -> pre_decrement
|
||||
| "+" prec2_expr
|
||||
| "-" prec2_expr
|
||||
| "!" prec2_expr -> bool_not
|
||||
| "~" prec2_expr
|
||||
| "(" type ")" prec2_expr -> cast
|
||||
| "*" prec2_expr -> dereference
|
||||
| "&" prec2_expr -> address_of
|
||||
| "sizeof" prec2_expr -> sizeof
|
||||
|
||||
?prec1_expr: litteral
|
||||
| identifier
|
||||
| "(" expression ")"
|
||||
| prec1_expr "++" -> post_increment
|
||||
| prec1_expr "--" -> post_decrement
|
||||
| prec1_expr "(" expression? ")" -> fun_call
|
||||
| prec1_expr "[" expression "]" -> array_item
|
||||
| prec1_expr "." field -> field_access
|
||||
| prec1_expr "->" field -> pointer_access
|
||||
|
||||
|
||||
|
||||
|
||||
label: IDENTIFIER
|
||||
litteral: SIGNED_NUMBER | ESCAPED_STRING | HEX_LITTERAL
|
||||
field: IDENTIFIER
|
||||
identifier: IDENTIFIER
|
||||
?symbol: IDENTIFIER
|
||||
?type: type_qualifier* IDENTIFIER
|
||||
| struct_type
|
||||
| type "*" -> pointer
|
||||
?array_size: INT
|
||||
|
||||
?type_qualifier: "volatile" -> volatile
|
||||
| "const" -> const
|
||||
| "static" -> static
|
||||
| "extern" -> extern
|
||||
|
||||
struct_type: "struct" (IDENTIFIER | IDENTIFIER? struct_body)
|
||||
struct_def: "struct" IDENTIFIER struct_body ";"
|
||||
struct_body: "{" struct_field* "}"
|
||||
struct_field: type IDENTIFIER sized_array* ";"
|
||||
|
||||
IDENTIFIER: /[_a-zA-Z]\w*/
|
||||
COMMENT: /\/\/.*/
|
||||
HEX_LITTERAL: /0x[a-fA-F0-9]+/
|
||||
|
||||
|
||||
%import common.WS
|
||||
%import common.CNAME
|
||||
%import common.C_COMMENT
|
||||
%import common.SIGNED_NUMBER
|
||||
%import common.INT
|
||||
%import common.ESCAPED_STRING
|
||||
|
||||
%ignore COMMENT
|
||||
%ignore C_COMMENT
|
||||
%ignore WS
|
997
tools/cc.py
Normal file
997
tools/cc.py
Normal file
@@ -0,0 +1,997 @@
|
||||
import collections
|
||||
import contextlib
|
||||
import re
|
||||
import sys
|
||||
|
||||
import lark
|
||||
|
||||
GRAMMAR_FILE = '/home/paulmathieu/vhdl/bin/cc.ebnf'
|
||||
|
||||
|
||||
class Scope:
|
||||
def __init__(self):
|
||||
self.symbols = {}
|
||||
self.parent = None
|
||||
|
||||
|
||||
class Variable:
|
||||
def __init__(self, type, name, volatile=False, addr_reg=None):
|
||||
self.type = type
|
||||
self.name = name
|
||||
self.volatile = volatile
|
||||
self.addr_reg = addr_reg
|
||||
|
||||
def __repr__(self):
|
||||
return f'<Var: {self.type} {self.name}>'
|
||||
|
||||
@classmethod
|
||||
def from_def(cls, tree):
|
||||
volatile = False
|
||||
type = tree.children[0]
|
||||
if isinstance(type, lark.Tree):
|
||||
for c in type.children:
|
||||
if c == "volatile":
|
||||
volatile = True
|
||||
name = tree.children[1]
|
||||
return cls(type, name, volatile=volatile)
|
||||
|
||||
@classmethod
|
||||
def from_dereference(cls, reg):
|
||||
return cls('deref', 'deref', addr_reg=reg)
|
||||
|
||||
|
||||
# - all registers pointing to unwritten stuff can be dropped as soon as we're
|
||||
# done with them:
|
||||
# - already fed them into the operations
|
||||
# - end of statement
|
||||
# - maybe should have a separate storeage for special registers?
|
||||
# need ways to:
|
||||
# - assign a list of registers into r0, ... rn for function call
|
||||
# - ... and run all callbacks
|
||||
# - get the register for an identifier
|
||||
# - dereference an expression:
|
||||
# - essentially turn a temp into an lvalue-address
|
||||
# - if read, need a second reg for its value
|
||||
# - mark a variable to have its address in a register
|
||||
# - delay reading the identifier if it's lhs
|
||||
# - store the value to memory when registers are claimed
|
||||
# - if it was modified through:
|
||||
# - assignment
|
||||
# - pre-post *crement
|
||||
# - retrieve the memory type:
|
||||
# - stack
|
||||
# - absolute
|
||||
# - register
|
||||
# - or just store the value when the variable is assigned
|
||||
# - get a temporary register
|
||||
# - return it
|
||||
# - I guess dereferencing is an upgrade
|
||||
|
||||
def type(self, tree):
|
||||
print(tree)
|
||||
|
||||
|
||||
class RegBank:
|
||||
def __init__(self, logger=None):
|
||||
self.reset()
|
||||
self.log = logger or print
|
||||
|
||||
def reset(self):
|
||||
self.available = [f'r{i}' for i in range(12)]
|
||||
self.vars = {}
|
||||
self.varregs = {}
|
||||
self.cleanup = collections.defaultdict(list)
|
||||
|
||||
def take(self, reg=None):
|
||||
if reg is not None:
|
||||
if reg not in self.available:
|
||||
self.evict(self.var(reg))
|
||||
self.available.remove(reg)
|
||||
return reg
|
||||
if not self.available:
|
||||
assert self.vars, "nothing to clean, no more regs :/"
|
||||
# storing one random var
|
||||
var = self.vars.keys()[0]
|
||||
self.evict(var)
|
||||
return self.available.pop(0)
|
||||
|
||||
def give(self, reg):
|
||||
if reg in self.varregs:
|
||||
# Need to call evict() with the var to free it.
|
||||
return
|
||||
self.available.insert(0, reg)
|
||||
|
||||
def loaded(self, var, reg, cleanup=None):
|
||||
"""Tells the regbank some variable was loaded into the given register."""
|
||||
self.vars[var] = reg
|
||||
self.varregs[reg] = var
|
||||
self.take(reg)
|
||||
if cleanup is not None:
|
||||
self.log(f'recording cleanup for {reg}({var.name})')
|
||||
self.cleanup[reg].append(cleanup)
|
||||
|
||||
def stored(self, var):
|
||||
"""Tells the regbank the given var was stored to memory, register can be freed."""
|
||||
assert var in self.vars
|
||||
reg = self.vars.pop(var)
|
||||
del self.varregs[reg]
|
||||
self.give(reg)
|
||||
|
||||
def load(self, var):
|
||||
"""Returns the reg associated with the var, or a new reg if none was,
|
||||
and True if the var was created, False otherwise."""
|
||||
self.log(f'vars: {self.vars}, varregs: {self.varregs}')
|
||||
if var not in self.vars:
|
||||
reg = self.take()
|
||||
self.vars[var] = reg
|
||||
self.varregs[reg] = var
|
||||
return reg, True
|
||||
return self.vars[var], False
|
||||
|
||||
def assign(self, var, reg, cleanup=None):
|
||||
"""Assign a previously-used register to a variable."""
|
||||
if var in self.vars:
|
||||
self.stored(var)
|
||||
if reg in self.varregs:
|
||||
for cb in self.cleanup.pop(reg, []):
|
||||
cb(reg)
|
||||
self.stored(self.varregs[reg])
|
||||
self.vars[var] = reg
|
||||
self.varregs[reg] = var
|
||||
if cleanup is not None:
|
||||
self.cleanup[reg].append(cleanup)
|
||||
|
||||
def var(self, reg):
|
||||
return self.varregs.get(reg, None)
|
||||
|
||||
def evict(self, var):
|
||||
"""Runs var callbacks & frees the register."""
|
||||
if var not in self.vars:
|
||||
return
|
||||
reg = self.vars[var]
|
||||
for cb in self.cleanup.pop(var, []):
|
||||
cb(reg)
|
||||
self.stored(var)
|
||||
|
||||
def flush_all(self):
|
||||
for reg in list(self.cleanup):
|
||||
self.log(f'flushing {reg}({self.varregs[reg].name})')
|
||||
for cb in self.cleanup.pop(reg):
|
||||
cb(reg)
|
||||
self.reset()
|
||||
|
||||
def swapped(self, reg0, reg1):
|
||||
var0 = self.varregs.get(reg0, None)
|
||||
var1 = self.varregs.get(reg1, None)
|
||||
|
||||
if var0 is not None:
|
||||
self.stored(var0)
|
||||
elif reg0 not in self.available:
|
||||
self.give(reg0)
|
||||
|
||||
if var1 is not None:
|
||||
self.stored(var1)
|
||||
elif reg1 not in self.available:
|
||||
self.give(reg1)
|
||||
|
||||
if var0 is not None:
|
||||
self.loaded(var0, reg1)
|
||||
if var1 is not None:
|
||||
self.loaded(var1, reg0)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def borrow(self, howmany):
|
||||
regs = [self.take() for i in range(howmany)]
|
||||
yield regs
|
||||
for reg in regs:
|
||||
self.give(reg)
|
||||
|
||||
|
||||
class FunctionSpec:
|
||||
def __init__(self, fun_prot):
|
||||
self.return_type = fun_prot.children[0]
|
||||
self.name = fun_prot.children[1]
|
||||
self.param_types = [x.children[0] for x in fun_prot.children[2:]]
|
||||
|
||||
def __repr__(self):
|
||||
params = ', '.join(self.param_types)
|
||||
return f'<Function: {self.return_type} {self.name}({params})>'
|
||||
|
||||
|
||||
class Function:
|
||||
def __init__(self, fun_prot):
|
||||
self.locals = {}
|
||||
self.spec = FunctionSpec(fun_prot)
|
||||
self.params = [Variable(*x.children) for x in fun_prot.children[2:]]
|
||||
self.ret = None
|
||||
self.nextstack = 0
|
||||
self.statements = []
|
||||
self.regs = RegBank(logger=self.log)
|
||||
self.deferred_ops = []
|
||||
self.fun_calls = 0
|
||||
self.ops = []
|
||||
|
||||
def log(self, line):
|
||||
self.ops.append(lambda: [f'// {line}'])
|
||||
|
||||
@property
|
||||
def stack_usage(self):
|
||||
return self.nextstack + 2
|
||||
|
||||
def get_stack(self, size=2):
|
||||
stk = self.nextstack
|
||||
self.nextstack += size
|
||||
return stk
|
||||
|
||||
def param_dict(self):
|
||||
return {p.name: p for p in self.params}
|
||||
|
||||
def __repr__(self):
|
||||
return repr(self.spec)
|
||||
|
||||
def synth(self):
|
||||
if self.fun_calls > 0:
|
||||
preamble = [f'store lr, [sp, -2]',
|
||||
f'set r4, {self.stack_usage}',
|
||||
f'sub sp, sp, r4']
|
||||
else:
|
||||
preamble = []
|
||||
|
||||
ops = preamble
|
||||
for op in self.ops:
|
||||
ops += op()
|
||||
indented = [f' {x}' if x[-1] == ':' else f' {x}' for x in ops]
|
||||
return [f'.global {self.spec.name}',
|
||||
f'{self.spec.name}:'] + indented
|
||||
|
||||
|
||||
class CcTransform(lark.visitors.Transformer):
|
||||
def _binary_op(litt):
|
||||
@lark.v_args(tree=True)
|
||||
def _f(self, tree):
|
||||
left, right = tree.children
|
||||
if left.data == 'litteral' and right.data == 'litteral':
|
||||
tree.data = 'litteral'
|
||||
tree.children = [litt(left.children[0], right.children[0])]
|
||||
return tree
|
||||
return _f
|
||||
|
||||
def array_item(self, children):
|
||||
# transform blarg[foo] into *(blarg + foo) because reasons
|
||||
addop = lark.Tree('add', children)
|
||||
return lark.Tree('dereference', [addop])
|
||||
|
||||
# operations on litterals can be done by the compiler
|
||||
add = _binary_op(lambda a, b: a+b)
|
||||
sub = _binary_op(lambda a, b: a-b)
|
||||
mul = _binary_op(lambda a, b: a*b)
|
||||
shl = _binary_op(lambda a, b: a<<b)
|
||||
|
||||
IDENTIFIER = str
|
||||
SIGNED_NUMBER = int
|
||||
HEX_LITTERAL = lambda _, x: int(x[2:], 16)
|
||||
|
||||
|
||||
class AsmOp:
|
||||
scratch_need = 0
|
||||
|
||||
def synth(self, scratches):
|
||||
return [f'nop']
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return None
|
||||
|
||||
|
||||
class Reg:
|
||||
scratch_need = 0
|
||||
|
||||
def __init__(self, reg):
|
||||
self.out = reg
|
||||
|
||||
def synth(self, scratches):
|
||||
return []
|
||||
|
||||
|
||||
class BinOp(AsmOp):
|
||||
scratch_need = 0
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.fun = fun
|
||||
self.dest, self.left, self.right = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.dest
|
||||
|
||||
|
||||
def make_cpu_bin_op(cpu_op):
|
||||
class _C(BinOp):
|
||||
def synth(self, _):
|
||||
return [f'{cpu_op} {self.dest}, {self.left}, {self.right}']
|
||||
return _C
|
||||
|
||||
|
||||
AddOp = make_cpu_bin_op('add')
|
||||
SubOp = make_cpu_bin_op('sub')
|
||||
MulOp = make_cpu_bin_op('mul')
|
||||
# no div
|
||||
# no mod either
|
||||
AndOp = make_cpu_bin_op('and')
|
||||
orOp = make_cpu_bin_op('or')
|
||||
XorOp = make_cpu_bin_op('xor')
|
||||
|
||||
|
||||
def combo(UnOp2, Op1):
|
||||
"""Apply UnOp2 on the output of Op1."""
|
||||
|
||||
class _C(AsmOp):
|
||||
scratch_need = max(Op1.scratch_need, UnOp2.scratch_need)
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.op2.out
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.op1 = Op1(fun, ops)
|
||||
self.op2 = Op2(fun, self.op1.out)
|
||||
|
||||
def synth(self, scratches):
|
||||
self.op1.synth(scratches)
|
||||
self.op2.synth(scratches)
|
||||
|
||||
return _C
|
||||
|
||||
|
||||
class LtOp(BinOp):
|
||||
scratch_need = 1
|
||||
def synth(self, scratches):
|
||||
sc0 = scratches[0]
|
||||
return [f'set {self.dest}, 0',
|
||||
f'sub {sc0}, {self.left}, {self.right}',
|
||||
f'bneq [pc, 2]',
|
||||
f'set {self.dest}, 1']
|
||||
|
||||
class GtOp(LtOp):
|
||||
def __init__(self, fun, ops):
|
||||
dest, left, right = ops
|
||||
super(GtOp, self).__init__(fun, [dest, right, left])
|
||||
|
||||
|
||||
class UnOp(AsmOp):
|
||||
scratch_need = 0
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.fun = fun
|
||||
self.dest, self.operand = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.dest
|
||||
|
||||
|
||||
class Incr(UnOp):
|
||||
scratch_need = 1
|
||||
def synth(self, scratches):
|
||||
sc0 = scratches[0]
|
||||
return [f'set {sc0}, 1',
|
||||
f'add {self.dest}, {self.operand}, {sc0}',
|
||||
f'or {self.operand}, {self.dest}, {self.dest}']
|
||||
|
||||
|
||||
class NotOp(UnOp):
|
||||
def synth(self, scratches):
|
||||
return [f'not {self.dest}, {self.operand}']
|
||||
|
||||
class BoolNot(UnOp):
|
||||
def synth(self, scratches):
|
||||
return [f'set {self.dest}, 0',
|
||||
f'cmp {self.dest}, {self.operand}',
|
||||
f'bneq [pc, 2]',
|
||||
f'set {self.dest}, 1']
|
||||
|
||||
class NeqOp(BinOp):
|
||||
def synth(self, scratches):
|
||||
return [f'sub {self.dest}, {self.left}, {self.right}']
|
||||
|
||||
EqOp = combo(BoolNot, NeqOp)
|
||||
LeOp = combo(BoolNot, GtOp)
|
||||
GeOp = combo(BoolNot, LtOp)
|
||||
|
||||
|
||||
|
||||
class FnCall(AsmOp):
|
||||
scratch_need = 1
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.fun = fun
|
||||
self.dest_fn, self.params = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return 'r0'
|
||||
|
||||
def synth(self, scratches):
|
||||
out = []
|
||||
sc0 = scratches[0]
|
||||
fn = self.dest_fn
|
||||
|
||||
return out + [f'set {sc0}, 2',
|
||||
f'add lr, pc, {sc0}',
|
||||
f'or pc, {fn}, {fn}']
|
||||
|
||||
|
||||
class ReturnReg(AsmOp):
|
||||
scratch_need = 1
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.fun = fun
|
||||
(self.ret_reg,) = ops
|
||||
|
||||
def synth(self, scratches):
|
||||
if self.fun.fun_calls == 0:
|
||||
return [f'or r0, {self.ret_reg}, {self.ret_reg}',
|
||||
f'or pc, lr, lr']
|
||||
sc0 = scratches[0]
|
||||
stack_usage = self.fun.stack_usage
|
||||
ret = self.ret_reg
|
||||
assert stack_usage < 255
|
||||
return [f'set {sc0}, {stack_usage}',
|
||||
f'add sp, sp, {sc0}',
|
||||
f'or r0, {ret}, {ret}',
|
||||
f'load pc, [sp, -2] // return']
|
||||
|
||||
|
||||
class Load(AsmOp):
|
||||
scratch_need = 0
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.fun = fun
|
||||
self.dest, self.var = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.dest
|
||||
|
||||
def synth(self, scratches):
|
||||
reg = self.dest
|
||||
if self.var.name in self.fun.locals:
|
||||
src = self.var.stackaddr
|
||||
return [f'load {reg}, [sp, {src}]']
|
||||
elif self.var.addr_reg is not None:
|
||||
return [f'load {reg}, [{self.var.addr_reg}]']
|
||||
else:
|
||||
return [f'set {reg}, {self.var.name}',
|
||||
f'nop // in case we load a far global',
|
||||
f'load {reg}, [{reg}]']
|
||||
|
||||
class Store(AsmOp):
|
||||
scratch_need = 1
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.fun = fun
|
||||
self.src, self.var = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return None
|
||||
|
||||
def synth(self, scratches):
|
||||
(sc,) = scratches
|
||||
reg = self.src
|
||||
if self.var.name in self.fun.locals:
|
||||
dst = self.var.stackaddr
|
||||
self.fun.log(f'storing {self.var}({reg}) to [sp, {dst}]')
|
||||
return [f'store {reg}, [sp, {dst}]']
|
||||
elif self.var.addr_reg is not None:
|
||||
return [f'store {reg}, [{self.var.addr_reg}]']
|
||||
return [f'set {sc}, {self.var.name}',
|
||||
f'nop // you know, in case blah',
|
||||
f'store {reg}, [{sc}]']
|
||||
|
||||
|
||||
class Assign(AsmOp):
|
||||
scratch_need = 0
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.fun = fun
|
||||
self.src, self.var = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.var
|
||||
|
||||
def synth(self, scratches):
|
||||
return [f'or {self.var}, {self.src}, {self.src}']
|
||||
|
||||
|
||||
class SetAddr(AsmOp):
|
||||
scratch_need = 0
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.dest, self.ident = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.dest
|
||||
|
||||
def synth(self, scratches):
|
||||
reg = self.dest
|
||||
return [f'set {reg}, {self.ident}',
|
||||
f'nop // placeholder for a long address']
|
||||
|
||||
|
||||
class Set16Imm(AsmOp):
|
||||
scratch_need = 0
|
||||
|
||||
def __init__(self, fun, ops):
|
||||
self.dest, self.imm16 = ops
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.dest
|
||||
|
||||
def synth(self, scratches):
|
||||
reg = self.dest
|
||||
hi = (self.imm16 >> 8) & 0xff
|
||||
lo = (self.imm16 >> 0) & 0xff
|
||||
if hi != 0:
|
||||
return [f'set {reg}, {lo}',
|
||||
f'seth {reg}, {hi}']
|
||||
else:
|
||||
return [f'set {reg}, {lo}']
|
||||
|
||||
class Swap(AsmOp):
|
||||
scratch_need = 1
|
||||
|
||||
def __init__(self, a0, a1):
|
||||
self.a0 = a0
|
||||
self.a1 = a1
|
||||
|
||||
def synth(self, scratches):
|
||||
(sc0,) = scratches
|
||||
return [f'or {sc0}, {self.a0}, {self.a0}',
|
||||
f'or {self.a0}, {self.a1}, {self.a1}',
|
||||
f'or {self.a1}, {sc0}, {sc0}']
|
||||
|
||||
|
||||
class IfOp(AsmOp):
|
||||
scratch_need = 1
|
||||
|
||||
def __init__(self, fun, op):
|
||||
self.fun = fun
|
||||
self.cond, mark, self.has_else = op
|
||||
|
||||
self.then_mark = f'_then_{mark}'
|
||||
self.else_mark = f'_else_{mark}'
|
||||
self.endif_mark = f'_endif_{mark}'
|
||||
|
||||
def synth(self, scratches):
|
||||
sc0 = scratches[0]
|
||||
if self.has_else:
|
||||
return [f'set {sc0}, 0',
|
||||
f'cmp {sc0}, {self.cond}', # flag if cond == 0
|
||||
f'beq {self.else_mark}']
|
||||
else:
|
||||
return [f'set {sc0}, 0',
|
||||
f'cmp {sc0}, {self.cond}',
|
||||
f'beq {self.endif_mark}']
|
||||
|
||||
def synth_else(self):
|
||||
return [f'cmp r0, r0', # trick because beq is better than "or pc, ."
|
||||
f'beq {self.endif_mark}',
|
||||
f'{self.else_mark}:']
|
||||
|
||||
def synth_endif(self):
|
||||
return [f'{self.endif_mark}:']
|
||||
|
||||
|
||||
class WhileOp(AsmOp):
|
||||
scratch_need = 1
|
||||
|
||||
@staticmethod
|
||||
def synth_loop(mark):
|
||||
loop_mark = f'_loop_{mark}'
|
||||
return [f'{loop_mark}:']
|
||||
|
||||
def __init__(self, cond, mark):
|
||||
self.cond = cond
|
||||
self.loop_mark = f'_loop_{mark}'
|
||||
self.endwhile_mark = f'_endwhile_{mark}'
|
||||
|
||||
def synth(self, scratches):
|
||||
sc0 = scratches[0]
|
||||
return [f'set {sc0}, 0',
|
||||
f'cmp {sc0}, {self.cond}',
|
||||
f'beq {self.endwhile_mark}']
|
||||
|
||||
def synth_endwhile(self):
|
||||
return [f'cmp r0, r0',
|
||||
f'beq {self.loop_mark}',
|
||||
f'{self.endwhile_mark}:']
|
||||
|
||||
|
||||
class Delayed(AsmOp):
|
||||
def __init__(self, out_cb):
|
||||
self.out_cb = out_cb
|
||||
|
||||
@property
|
||||
def out(self):
|
||||
return self.out_cb()
|
||||
|
||||
def synth(self):
|
||||
return []
|
||||
|
||||
|
||||
class CcInterp(lark.visitors.Interpreter):
|
||||
def __init__(self):
|
||||
self.global_scope = Scope()
|
||||
self.cur_scope = self.global_scope
|
||||
self.cur_fun = None
|
||||
self.funs = []
|
||||
self.next_reg = 0
|
||||
self.next_marker = 0
|
||||
|
||||
def _lookup_symbol(self, s):
|
||||
scope = self.cur_scope
|
||||
while scope is not None:
|
||||
if s in scope.symbols:
|
||||
return scope.symbols[s]
|
||||
scope = scope.parent
|
||||
return None
|
||||
|
||||
def _get_reg(self):
|
||||
return self.cur_fun.regs.take()
|
||||
|
||||
def _get_marker(self):
|
||||
mark = self.next_marker
|
||||
self.next_marker += 1
|
||||
return mark
|
||||
|
||||
def _synth(self, op):
|
||||
with self.cur_fun.regs.borrow(op.scratch_need) as scratches:
|
||||
self._log(f'{op.__class__.__name__}')
|
||||
self.cur_fun.ops.append(lambda: op.synth(scratches))
|
||||
|
||||
def _load(self, ident):
|
||||
s = self._lookup_symbol(ident)
|
||||
assert s is not None, f'unknown identifier {ident}'
|
||||
if isinstance(s, FunctionSpec) or s in self.global_scope.symbols:
|
||||
reg = self._get_reg()
|
||||
return SetAddr(self.cur_fun, [reg, ident])
|
||||
else:
|
||||
if s.volatile:
|
||||
self._log(f'loading volatile {s}')
|
||||
return Load(self.cur_fun, [reg, s])
|
||||
reg, created = self.cur_fun.regs.load(s)
|
||||
if created:
|
||||
return Load(self.cur_fun, [reg, s])
|
||||
else:
|
||||
self._log(f'{s} was already in {reg}')
|
||||
return Reg(reg)
|
||||
|
||||
def identifier(self, tree):
|
||||
# TODO: not actually load the value until it's used in an expression
|
||||
# could have the op.out() function have a side effect that does that
|
||||
# if it's an assignment, we need to make a Variable with the proper
|
||||
# address and assign the register to it.
|
||||
# If it's volatile, we need to flush it.
|
||||
def delayed_load():
|
||||
self._log(f'delay-loading {tree.children[0]}')
|
||||
op = self._load(tree.children[0])
|
||||
self._synth(op)
|
||||
return op.out
|
||||
|
||||
tree.op = Delayed(delayed_load)
|
||||
tree.var = self._lookup_symbol(tree.children[0])
|
||||
|
||||
def litteral(self, tree):
|
||||
imm = tree.children[0]
|
||||
reg = self._get_reg()
|
||||
assert self.cur_fun is not None
|
||||
tree.op = Set16Imm(self.cur_fun, [reg, imm])
|
||||
self._synth(tree.op)
|
||||
|
||||
def _assign(self, left, right):
|
||||
# need to make sure there's a variable and either:
|
||||
# - to store it, or
|
||||
# - to mark a register for it
|
||||
if left.volatile:
|
||||
self._synth(Store(self.cur_fun, [right, left]))
|
||||
self.cur_fun.stored(left)
|
||||
return
|
||||
self._log(f'assigning {left} = {right}')
|
||||
# cleanup = lambda reg: self._synth(Store(self.cur_fun, [reg, left]))
|
||||
self.cur_fun.regs.assign(left, right)
|
||||
self._synth(Store(self.cur_fun, [right, left]))
|
||||
|
||||
def assignment(self, tree):
|
||||
self.visit_children(tree)
|
||||
val = tree.children[1].op.out
|
||||
|
||||
# left hand side is an lvalue, retrieve it
|
||||
var = tree.children[0].var
|
||||
self._assign(var, val)
|
||||
|
||||
def global_var(self, tree):
|
||||
self.visit_children(tree)
|
||||
var = Variable.from_def(tree)
|
||||
self.global_scope.symbols[var.name] = var
|
||||
val = 0
|
||||
if len(tree.children) > 2:
|
||||
val = tree.children[2].children[0].value
|
||||
|
||||
def fun_decl(self, tree):
|
||||
fun = FunctionSpec(tree.children[0])
|
||||
self.cur_scope.symbols[fun.name] = fun
|
||||
|
||||
def _prep_fun_call(self, fn_reg, params):
|
||||
"""Move all params to r0-rn."""
|
||||
def swap(old, new):
|
||||
if old == new:
|
||||
return
|
||||
oldpos = params.index(old)
|
||||
try:
|
||||
newpos = params.index(new)
|
||||
except ValueError:
|
||||
params[oldpos] = new
|
||||
else:
|
||||
params[newpos], params[oldpos] = params[oldpos], params[newpos]
|
||||
self._synth(Swap(old, new))
|
||||
|
||||
if fn_reg in [f'r{i}' for i in range(len(params))]:
|
||||
new_fn = f'r{len(params)}'
|
||||
self._synth(Swap(fn_reg, new_fn))
|
||||
fn_reg = new_fn
|
||||
for i, param in enumerate(params):
|
||||
new = f'r{i}'
|
||||
swap(param, new)
|
||||
return fn_reg
|
||||
|
||||
def fun_call(self, tree):
|
||||
self.cur_fun.fun_calls += 1
|
||||
self.visit_children(tree)
|
||||
fn_reg = tree.children[0].op.out
|
||||
param_regs = [c.op.out for c in tree.children[1:]]
|
||||
self.cur_fun.regs.flush_all()
|
||||
for i in range(len(param_regs)):
|
||||
self.cur_fun.regs.take(f'r{i}')
|
||||
fn_reg = self._prep_fun_call(fn_reg, param_regs)
|
||||
self.cur_fun.regs.take(fn_reg)
|
||||
tree.op = FnCall(self.cur_fun, [fn_reg, param_regs])
|
||||
self._synth(tree.op)
|
||||
self.cur_fun.regs.reset()
|
||||
self.cur_fun.regs.take('r0')
|
||||
|
||||
def statement(self, tree):
|
||||
self.visit_children(tree)
|
||||
if self.cur_fun.deferred_ops:
|
||||
self._log(f'deferred logic: {len(self.cur_fun.deferred_ops)}')
|
||||
for op in self.cur_fun.deferred_ops:
|
||||
self._synth(op)
|
||||
self.cur_fun.deferred_ops = []
|
||||
|
||||
iter_expression = statement
|
||||
|
||||
def if_stat(self, tree):
|
||||
self.visit(tree.children[0])
|
||||
mark = self._get_marker()
|
||||
has_else = len(tree.children) > 2
|
||||
op = IfOp(self.cur_fun, [tree.children[0].op.out, mark, has_else])
|
||||
self._synth(op)
|
||||
self.visit(tree.children[1])
|
||||
if has_else:
|
||||
self.cur_fun.ops.append(op.synth_else)
|
||||
self.visit(tree.children[2])
|
||||
self.cur_fun.ops.append(op.synth_endif)
|
||||
|
||||
def while_stat(self, tree):
|
||||
mark = self._get_marker()
|
||||
self.cur_fun.ops.append(lambda: WhileOp.synth_loop(mark))
|
||||
begin_vars = dict(self.cur_fun.regs.vars)
|
||||
self.visit(tree.children[0])
|
||||
op = WhileOp(tree.children[0].op.out, mark)
|
||||
self._synth(op)
|
||||
self.visit(tree.children[1])
|
||||
for v, r in begin_vars.items():
|
||||
rvars = self.cur_fun.regs.vars
|
||||
if v not in rvars or rvars[v] != r:
|
||||
self._log(f'loading missing var {v}')
|
||||
self._synth(Load(self.cur_fun, [r, v]))
|
||||
self.cur_fun.ops.append(op.synth_endwhile)
|
||||
|
||||
def for_stat(self, tree):
|
||||
mark = self._get_marker()
|
||||
self.visit(tree.children[0]) # initialization
|
||||
self.cur_fun.ops.append(lambda: WhileOp.synth_loop(mark))
|
||||
begin_vars = dict(self.cur_fun.regs.vars)
|
||||
self.visit(tree.children[1])
|
||||
op = WhileOp(tree.children[1].op.out, mark)
|
||||
self._synth(op)
|
||||
self.visit(tree.children[3])
|
||||
self.visit(tree.children[2]) # 3rd statement
|
||||
for v, r in begin_vars.items():
|
||||
rvars = self.cur_fun.regs.vars
|
||||
if v not in rvars or rvars[v] != r:
|
||||
self._log(f'loading missing var {v}')
|
||||
self._synth(Load(self.cur_fun, [r, v]))
|
||||
self.cur_fun.ops.append(op.synth_endwhile)
|
||||
|
||||
def _unary_op(op):
|
||||
def _f(self, tree):
|
||||
self.visit_children(tree)
|
||||
operand = tree.children[0].op.out
|
||||
reg = self.cur_fun.regs.take()
|
||||
tree.op = op(self.cur_fun, [reg, operand])
|
||||
self._synth(tree.op)
|
||||
self.cur_fun.regs.give(operand)
|
||||
return _f
|
||||
|
||||
def dereference(self, tree):
|
||||
self.visit_children(tree)
|
||||
reg = tree.children[0].op.out
|
||||
var = Variable.from_dereference(reg)
|
||||
self._log(f'making var {var} from derefing reg {reg}')
|
||||
tree.var = var
|
||||
def delayed_load():
|
||||
self._log(f'delay-loading {tree.children[0]}')
|
||||
op = Load(self.cur_fun, [reg, var])
|
||||
self._synth(op)
|
||||
return op.out
|
||||
tree.op = Delayed(delayed_load)
|
||||
|
||||
def post_increment(self, tree):
|
||||
self.visit_children(tree)
|
||||
tree.op = Reg(tree.children[0].op.out)
|
||||
var = tree.children[0].var
|
||||
reg = tree.op.out
|
||||
self.cur_fun.deferred_ops.append(Incr(self.cur_fun, [reg, reg]))
|
||||
self.cur_fun.deferred_ops.append(Store(self.cur_fun, [reg, var]))
|
||||
|
||||
pre_increment = _unary_op(Incr)
|
||||
bool_not = _unary_op(BoolNot)
|
||||
|
||||
def _binary_op(op):
|
||||
def _f(self, tree):
|
||||
self.visit_children(tree)
|
||||
left, right = (x.op.out for x in tree.children)
|
||||
dest = self.cur_fun.regs.take()
|
||||
tree.op = op(self.cur_fun, [dest, left, right])
|
||||
self._synth(tree.op)
|
||||
self.cur_fun.regs.give(left)
|
||||
self.cur_fun.regs.give(right)
|
||||
return _f
|
||||
|
||||
add = _binary_op(AddOp)
|
||||
sub = _binary_op(SubOp)
|
||||
mul = _binary_op(MulOp)
|
||||
_and = _binary_op(AndOp)
|
||||
# ...
|
||||
gt = _binary_op(GtOp)
|
||||
lt = _binary_op(LtOp)
|
||||
neq = _binary_op(NeqOp)
|
||||
|
||||
def _forward_op(self, tree):
|
||||
self.visit_children(tree)
|
||||
tree.op = tree.children[0].op
|
||||
|
||||
def cast(self, tree):
|
||||
self.visit_children(tree)
|
||||
tree.op = tree.children[1].op
|
||||
|
||||
def _log(self, line):
|
||||
self.cur_fun.log(line)
|
||||
|
||||
def local_var(self, tree):
|
||||
self.visit_children(tree)
|
||||
assert self.cur_fun is not None
|
||||
assert self.cur_scope is not None
|
||||
var = Variable.from_def(tree)
|
||||
var.stackaddr = self.cur_fun.get_stack() # will have to invert
|
||||
self.cur_scope.symbols[var.name] = var
|
||||
self.cur_fun.locals[var.name] = var
|
||||
if len(tree.children) > 2:
|
||||
initval = tree.children[2].children[0].op.out
|
||||
cleanup = lambda reg: self._synth(Store(self.cur_fun, [reg, var]))
|
||||
self.cur_fun.regs.assign(var, initval, cleanup)
|
||||
self._log(f'assigning {var} = {initval}')
|
||||
|
||||
def fun_def(self, tree):
|
||||
prot, body = tree.children
|
||||
fun = Function(prot)
|
||||
assert self.cur_fun is None
|
||||
self.cur_fun = fun
|
||||
|
||||
self.cur_scope.symbols[fun.spec.name] = fun.spec
|
||||
|
||||
params = fun.param_dict()
|
||||
|
||||
def getcleanup(var):
|
||||
return lambda reg: self._synth(Store(self.cur_fun, [reg, var]))
|
||||
|
||||
for i, param in enumerate(fun.params):
|
||||
fun.locals[param.name] = param
|
||||
param.stackaddr = fun.get_stack()
|
||||
self._log(f'param [sp, {param.stackaddr}]: {param.name} in r{i}')
|
||||
cleanup = getcleanup(param)
|
||||
fun.regs.loaded(param, f'r{i}', cleanup)
|
||||
|
||||
fun_scope = Scope()
|
||||
fun_scope.parent = self.cur_scope
|
||||
fun_scope.symbols.update(params)
|
||||
|
||||
body.scope = fun_scope
|
||||
self.visit_children(tree)
|
||||
|
||||
if fun.ret is None:
|
||||
if fun.spec.name == 'main':
|
||||
self._synth(Set16Imm(fun, ['r0', 0]))
|
||||
self._synth(ReturnReg(fun, ['r0']))
|
||||
elif fun.spec.return_type == 'void':
|
||||
self._synth(ReturnReg(fun, ['r0']))
|
||||
else:
|
||||
assert fun.ret is not None
|
||||
self.cur_fun = None
|
||||
self.funs.append(fun)
|
||||
|
||||
def body(self, tree):
|
||||
bscope = getattr(tree, 'scope', Scope())
|
||||
bscope.parent = self.cur_scope
|
||||
self.cur_scope = bscope
|
||||
self.visit_children(tree)
|
||||
self.cur_scope = bscope.parent
|
||||
|
||||
def return_stat(self, tree):
|
||||
assert self.cur_fun is not None
|
||||
self.cur_fun.ret = True
|
||||
self.visit_children(tree)
|
||||
expr_reg = tree.children[0].op.out
|
||||
tree.op = ReturnReg(self.cur_fun, [expr_reg])
|
||||
self._synth(tree.op)
|
||||
|
||||
preamble = [f'_start:',
|
||||
f'xor r0, r0, r0',
|
||||
f'xor r1, r1, r1',
|
||||
f'set sp, 0',
|
||||
f'seth sp, {0x11}', # 256 bytes of stack ought to be enough
|
||||
f'set r2, main',
|
||||
f'set r3, 2',
|
||||
f'add lr, pc, r3',
|
||||
f'or pc, r2, r2',
|
||||
f'or pc, pc, pc // loop forever',
|
||||
]
|
||||
|
||||
def filter_dupes(ops):
|
||||
dupe_re = re.compile(r'or (r\d+), \1, \1')
|
||||
for op in ops:
|
||||
if dupe_re.search(op):
|
||||
continue
|
||||
yield op
|
||||
|
||||
def parse_tree(tree):
|
||||
tr = CcTransform()
|
||||
tree = tr.transform(tree)
|
||||
|
||||
# print(tree.pretty())
|
||||
|
||||
inte = CcInterp()
|
||||
inte.visit(tree)
|
||||
|
||||
out = []
|
||||
|
||||
for fun in inte.funs:
|
||||
out += fun.synth()
|
||||
out.append('')
|
||||
return '\n'.join(filter_dupes(out))
|
||||
|
||||
|
||||
def larkparse(f):
|
||||
with open(GRAMMAR_FILE) as g:
|
||||
asparser = lark.Lark(g.read())
|
||||
tree = asparser.parse(f.read())
|
||||
return parse_tree(tree)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# print(',\n'.join(parse(sys.stdin)))
|
||||
print(larkparse(sys.stdin))
|
10
tools/crt0.s
Normal file
10
tools/crt0.s
Normal file
@@ -0,0 +1,10 @@
|
||||
.global _start
|
||||
_start:
|
||||
set sp, 0
|
||||
seth sp, 0x01
|
||||
set r2, main
|
||||
nop
|
||||
set r3, 2
|
||||
add lr, pc, r3
|
||||
or pc, r2, r2
|
||||
or pc, pc, pc // loop forever
|
100
tools/ld.py
Normal file
100
tools/ld.py
Normal file
@@ -0,0 +1,100 @@
|
||||
import argparse
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import obj_pb2
|
||||
|
||||
CRT0_FILE = '/home/paulmathieu/vhdl/bin/crt0.o'
|
||||
|
||||
|
||||
def parse_objs(objs):
|
||||
sections = []
|
||||
relocs = []
|
||||
|
||||
for obj in objs:
|
||||
of = obj_pb2.ObjFile()
|
||||
of.ParseFromString(obj.read())
|
||||
sections += of.sections
|
||||
relocs += of.relocs
|
||||
|
||||
return sections, relocs
|
||||
|
||||
|
||||
def map_sections(sections):
|
||||
secmap = []
|
||||
|
||||
addr = 0
|
||||
|
||||
# _start goes first
|
||||
for sec in sections:
|
||||
if sec.name == '_start':
|
||||
secmap.append((addr, sec))
|
||||
addr += len(sec.text)
|
||||
sections.remove(sec)
|
||||
break
|
||||
assert secmap, "could not find symbol _start :/"
|
||||
|
||||
for sec in sections:
|
||||
secmap.append((addr, sec))
|
||||
addr += len(sec.text)
|
||||
|
||||
return secmap
|
||||
|
||||
|
||||
def do_relocs(secmap, relocs):
|
||||
namemap = {s[1].name: s for s in secmap}
|
||||
for reloc in relocs:
|
||||
assert reloc.section in namemap
|
||||
assert reloc.target in namemap
|
||||
_, sec = namemap[reloc.section]
|
||||
# the reloc hex should look like /e.ff0000/
|
||||
buff = bytearray(sec.text)
|
||||
target_addr = namemap[reloc.target][0]
|
||||
reg = buff[reloc.offset] & 0xf
|
||||
buff[reloc.offset+0:reloc.offset+4] = [
|
||||
0xf0 | reg, (target_addr >> 0) & 0xff,
|
||||
0x90 | reg, (target_addr >> 8) & 0xff,
|
||||
]
|
||||
sec.text = bytes(buff)
|
||||
|
||||
|
||||
def dump(secmap):
|
||||
out = bytearray()
|
||||
for _, sec in secmap:
|
||||
out += sec.text
|
||||
return out
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Assemble.')
|
||||
parser.add_argument('--debug', action='store_true',
|
||||
help='print debug info')
|
||||
parser.add_argument('objfiles', metavar='O', nargs='+',
|
||||
type=argparse.FileType('rb'),
|
||||
help='input file (default: stdin)')
|
||||
parser.add_argument('--output', '-o', type=argparse.FileType('wb'),
|
||||
default=sys.stdout.buffer, help='output file')
|
||||
parser.add_argument('--vhdl', action='store_true',
|
||||
help='vhdl output')
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
|
||||
with open(CRT0_FILE, 'rb') as crt0:
|
||||
sections, relocs = parse_objs(args.objfiles + [crt0])
|
||||
|
||||
sectionmap = map_sections(sections)
|
||||
do_relocs(sectionmap, relocs)
|
||||
text = dump(sectionmap)
|
||||
|
||||
if args.vhdl:
|
||||
args.output.write(',\n'.join(f'x"{x:04x}"' for x in
|
||||
struct.unpack(f'>{len(text) // 2}H', text)).encode())
|
||||
else:
|
||||
args.output.write(text)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
17
tools/makefile
Normal file
17
tools/makefile
Normal file
@@ -0,0 +1,17 @@
|
||||
all: crt0.o
|
||||
|
||||
crt0.o: crt0.s as.py
|
||||
python as.py -c $< -o $@
|
||||
|
||||
as.py: obj_pb2.py
|
||||
|
||||
obj_pb2.py: obj.proto
|
||||
protoc --python_out=. $<
|
||||
|
||||
PHONY: clean distclean
|
||||
|
||||
clean:
|
||||
rm -rf crt0.o
|
||||
|
||||
distclean: clean
|
||||
rm -rf obj_pb2.py __pycache__
|
23
tools/obj.proto
Normal file
23
tools/obj.proto
Normal file
@@ -0,0 +1,23 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message Section {
|
||||
string name = 1;
|
||||
bytes text = 2;
|
||||
}
|
||||
|
||||
// there is always exactly ONE header with a magic "pol0" at the start
|
||||
message Header {
|
||||
string magic = 1;
|
||||
}
|
||||
|
||||
message Reloc {
|
||||
string section = 1;
|
||||
int32 offset = 2;
|
||||
string target = 3;
|
||||
}
|
||||
|
||||
message ObjFile {
|
||||
Header header = 1;
|
||||
repeated Section sections = 2;
|
||||
repeated Reloc relocs = 3;
|
||||
}
|
2
tools/requirements.txt
Normal file
2
tools/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
protobuf
|
||||
lark
|
Reference in New Issue
Block a user