From 4351d3948949168f180e74559b7b71c2f68f228c Mon Sep 17 00:00:00 2001 From: Larry Xue Date: Sun, 23 Aug 2020 23:21:58 -0400 Subject: [PATCH] added functions --- c2logic/compiler.py | 98 ++++++++++++++++++++-------- c2logic/{operations.py => consts.py} | 2 + c2logic/instructions.py | 34 +++++++--- 3 files changed, 96 insertions(+), 38 deletions(-) rename c2logic/{operations.py => consts.py} (91%) diff --git a/c2logic/compiler.py b/c2logic/compiler.py index e859ba8..a624f8f 100644 --- a/c2logic/compiler.py +++ b/c2logic/compiler.py @@ -1,13 +1,20 @@ -from pycparser.c_ast import Compound, Constant, DeclList, Enum, FileAST, FuncDecl, Struct, TypeDecl -from .instructions import BinaryOp, Enable, End, JumpCondition, Print, PrintFlush, Radar, RawAsm, RelativeJump, Sensor, Shoot, UnaryOp, Instruction, Set, Noop -from pycparser import c_ast, parse_file from dataclasses import dataclass -from .operations import func_unary_ops, func_binary_ops + +from pycparser import c_ast, parse_file +from pycparser.c_ast import ( + Compound, Constant, DeclList, Enum, FileAST, FuncDecl, Struct, TypeDecl +) + +from .consts import builtins, func_binary_ops, func_unary_ops +from .instructions import ( + BinaryOp, Enable, End, FunctionCall, Instruction, JumpCondition, Print, PrintFlush, Radar, + RawAsm, RelativeJump, Return, Sensor, Set, Shoot, UnaryOp +) @dataclass class Function(): name: str - args: list + params: list instructions: list start: int @@ -40,20 +47,29 @@ class Compiler(c_ast.NodeVisitor): ast = parse_file(filename, use_cpp=True, cpp_args=["-I", "include/"]) self.visit(ast) #TODO actually handle functions properly - out = [] - offset = 0 + init_call = FunctionCall("main") + preamble = [Set("__retaddr", "2"), init_call, End()] + + offset = len(preamble) + #set function starts for function in self.functions.values(): function.start = offset offset += len(function.instructions) + + #rewrite relative jumps and func calls + init_call.func_start = self.functions["main"].start for function in self.functions.values(): instructions = function.instructions - out2 = [] for instruction in instructions: if isinstance(instruction, RelativeJump): instruction.func_start = function.start - out2.append(str(instruction)) - out.append("\n".join(out2)) - return "\n\n".join(out) + if isinstance(instruction, FunctionCall): + instruction.func_start = self.functions[instruction.func_name].start + out = ["\n".join(map(str, preamble))] + out.extend( + "\n".join(map(str, function.instructions)) for function in self.functions.values() + ) + return "\n".join(out) #utilities def push(self, instruction: Instruction): @@ -65,6 +81,9 @@ class Compiler(c_ast.NodeVisitor): def peek(self): return self.curr_function.instructions[-1] + def curr_offset(self): + return len(self.curr_function.instructions) - 1 + def can_avoid_indirection(self, var="__rax"): top = self.peek() return self.opt_level >= 1 and isinstance(top, Set) and top.dest == var @@ -90,18 +109,24 @@ class Compiler(c_ast.NodeVisitor): self.push(RelativeJump(None, JumpCondition("==", "__rax", "0"))) def start_loop(self, cond): - self.loop_start = len(self.curr_function.instructions) + self.loop_start = self.curr_offset() + 1 self.visit(cond) self.push_body_jump() - self.loop_end_jumps = [len(self.curr_function.instructions) - 1] # also used for breaks + self.loop_end_jumps = [self.curr_offset()] # also used for breaks def end_loop(self): self.push(RelativeJump(self.loop_start, JumpCondition.always)) for offset in self.loop_end_jumps: - self.curr_function.instructions[offset].offset = len(self.curr_function.instructions) + self.curr_function.instructions[offset].offset = self.curr_offset() + 1 self.loop_start = None self.loop_end_jumps = None + def push_ret(self): + if self.curr_function.name == "main": + self.push(End()) + else: + self.push(Return()) + def optimize_psuedofunc_args(self, args): if self.opt_level >= 1: for i, arg in reversed(list(enumerate(args))): @@ -115,23 +140,28 @@ class Compiler(c_ast.NodeVisitor): def visit_FuncDef(self, node): # function definitions func_name = node.decl.name func_decl = node.decl.type - args = [arg_decl.name for arg_decl in func_decl.args.params] + params = [param_decl.name for param_decl in func_decl.args.params] - self.curr_function = Function(func_name, args, [], None) + self.curr_function = Function(func_name, params, [], None) self.visit(node.body) - #in case for loop is the last thing in a function to ensure the jump target is valid - if self.loop_start is not None: - self.push(Noop()) + #implicit return + #needed unconditionally in case loop/if body is at end of function + self.push(Set("__rax", "null")) + self.push(Return()) self.functions[func_name] = self.curr_function def visit_Decl(self, node): if isinstance(node.type, TypeDecl): # variable declaration + #TODO fix local/global split if node.init is not None: self.visit(node.init) self.set_to_rax(node.name) elif isinstance(node.type, FuncDecl): - #TODO actually process func declarations - pass + if node.name not in builtins + func_unary_ops + func_binary_ops: + #create placeholder function for forward declarations + self.functions[node.name] = Function( + node.name, [param_decl.name for param_decl in node.type.args.params], [], None + ) elif isinstance(node.type, Struct): if node.type.name != "MindustryObject": #TODO structs @@ -209,7 +239,7 @@ class Compiler(c_ast.NodeVisitor): def visit_DoWhile(self, node): #jump over the condition on the first iterattion self.push(RelativeJump(None, JumpCondition.always)) - init_jump_offset = len(self.curr_function.instructions) - 1 + init_jump_offset = self.curr_offset() self.start_loop(node.cond) self.curr_function.instructions[init_jump_offset].offset = len( self.curr_function.instructions @@ -220,12 +250,12 @@ class Compiler(c_ast.NodeVisitor): def visit_If(self, node): self.visit(node.cond) self.push_body_jump() - cond_jump_offset = len(self.curr_function.instructions) - 1 + cond_jump_offset = self.curr_offset() self.visit(node.iftrue) #jump over else body from end of if body if node.iffalse is not None: self.push(RelativeJump(None, JumpCondition.always)) - cond_jump_offset2 = len(self.curr_function.instructions) - 1 + cond_jump_offset2 = self.curr_offset() self.curr_function.instructions[cond_jump_offset].offset = len( self.curr_function.instructions ) @@ -237,11 +267,15 @@ class Compiler(c_ast.NodeVisitor): def visit_Break(self, node): self.push(RelativeJump(None, JumpCondition.always)) - self.loop_end_jumps.append(len(self.curr_function.instructions) - 1) + self.loop_end_jumps.append(self.curr_offset()) def visit_Continue(self, node): self.push(RelativeJump(self.loop_start, JumpCondition.always)) + def visit_Return(self, node): + self.visit(node.expr) + self.push(Return()) + def visit_FuncCall(self, node): name = node.name.name args = node.args.exprs @@ -249,7 +283,7 @@ class Compiler(c_ast.NodeVisitor): if name == "asm": arg = args[0] if not isinstance(arg, Constant) or arg.type != "string": - raise TypeError("Non-string argument to _asm", node) + raise TypeError("Non-string argument to asm", node) self.push(RawAsm(arg.value[1:-1])) elif name in ("print", "printd"): self.visit(args[0]) @@ -329,7 +363,15 @@ class Compiler(c_ast.NodeVisitor): else: self.push(UnaryOp("__rax", "__rax", name)) else: - raise NotImplementedError(node) + try: + func = self.functions[name] + except KeyError: + raise ValueError(f"{name} is not a function") + for param, arg in zip(func.params, args): + self.visit(arg) + self.set_to_rax(param) + self.push(Set("__retaddr", self.curr_offset() + 2)) + self.push(FunctionCall(name)) def generic_visit(self, node): if isinstance(node, (FileAST, Compound, DeclList)): @@ -347,4 +389,4 @@ def main(): print(Compiler(args.optimization_level).compile(args.file), file=args.output) if __name__ == "__main__": - main() \ No newline at end of file + main() diff --git a/c2logic/operations.py b/c2logic/consts.py similarity index 91% rename from c2logic/operations.py rename to c2logic/consts.py index 6684d3f..6b962f1 100644 --- a/c2logic/operations.py +++ b/c2logic/consts.py @@ -35,3 +35,5 @@ func_binary_ops = ["pow", "max", "min", "atan2", "dst"] func_unary_ops = ["abs", "log", "log10", "sin", "cos", "tan", "floor", "ceil", "sqrt", "rand"] binary_ops.update(dict(zip(func_binary_ops, func_binary_ops))) unary_ops.update(dict(zip(func_unary_ops, func_unary_ops))) + +builtins = ["print", "printd", "printflush", "radar", "sensor", "enable", "shoot", "end"] diff --git a/c2logic/instructions.py b/c2logic/instructions.py index 14f0714..45f59ec 100644 --- a/c2logic/instructions.py +++ b/c2logic/instructions.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from .operations import binary_op_inverses, binary_ops, condition_ops, unary_ops +from .consts import binary_op_inverses, binary_ops, condition_ops, unary_ops class Instruction: pass @@ -9,7 +9,7 @@ class Noop(Instruction): return "noop" class Set(Instruction): - def __init__(self, dest, src): + def __init__(self, dest: str, src: str): self.src = src self.dest = dest @@ -17,7 +17,7 @@ class Set(Instruction): return f"set {self.dest} {self.src}" class BinaryOp(Instruction): - def __init__(self, dest, left, right, op): + def __init__(self, dest: str, left: str, right: str, op: str): self.left = left self.right = right self.op = op @@ -30,7 +30,7 @@ class BinaryOp(Instruction): return f"op {binary_ops[self.op]} {self.dest} {self.left} {self.right}" class UnaryOp(Instruction): - def __init__(self, dest, src, op): + def __init__(self, dest: str, src: str, op: str): self.src = src self.dest = dest self.op = op @@ -62,22 +62,36 @@ class RelativeJump(Instruction): def __str__(self): return f"jump {self.func_start+self.offset} {self.cond}" +class FunctionCall(Instruction): + def __init__(self, func_name: str): + self.func_name = func_name + self.func_start: int = None + + def __str__(self): + return f"jump {self.func_start} {JumpCondition.always}" + +class Return(Instruction): + def __str__(self): + return "set @counter __retaddr" + class Print(Instruction): - def __init__(self, val): + def __init__(self, val: str): self.val = val def __str__(self): return f"print {self.val}" class PrintFlush(Instruction): - def __init__(self, val): + def __init__(self, val: str): self.val = val def __str__(self): return f"printflush {self.val}" class Radar(Instruction): - def __init__(self, dest, src, target1, target2, target3, sort, index): + def __init__( + self, dest: str, src: str, target1: str, target2: str, target3: str, sort: str, index: str + ): self.src = src self.dest = dest self.target1 = target1 @@ -90,7 +104,7 @@ class Radar(Instruction): return f"radar {self.target1} {self.target2} {self.target3} {self.sort} {self.src} {self.index} {self.dest}" class Sensor(Instruction): - def __init__(self, dest, src, prop): + def __init__(self, dest: str, src: str, prop: str): self.dest = dest self.src = src self.prop = prop @@ -99,7 +113,7 @@ class Sensor(Instruction): return f"sensor {self.dest} {self.src} @{self.prop}" class Enable(Instruction): - def __init__(self, obj, enabled): + def __init__(self, obj: str, enabled: str): self.obj = obj self.enabled = enabled @@ -107,7 +121,7 @@ class Enable(Instruction): return f"control enabled {self.obj} {self.enabled} 0 0 0" class Shoot(Instruction): - def __init__(self, obj, x, y, shoot): + def __init__(self, obj: str, x: str, y: str, shoot: str): self.obj = obj self.x = x self.y = y