#!/usr/bin/env python """Compiles C into assembly for the practicum processor (PP2) Copyright (C) 2011-2014 Peter Wu Licensed under the MIT license . """ import re __author__ = "Peter Wu" __copyright__ = "Copyright (C) 2011-2014 Peter Wu" __credits__ = ["Peter Wu"] __license__ = "MIT" __version__ = "1.0" __maintainer__ = "Peter Wu" __email__ = "lekensteyn@gmail.com" class Registers(object): """Register related functions Some functions were supposed to optimize things (alloc and free), but it's not implemented """ def __init__(self): # which registers are in use? self.registers = {} for reg in range(0, 8): self.registers[str(reg)] = False # the "Base Pointer", local variables are relative to this address. The # value of this register points to a location in the stack self.BP = "R5" def alloc(self, register=None): """Retrieves a register which is marked unused, marks it as in use and return it""" # if a register was explicitly requested if register: if not self.is_register(register): raise RuntimeError("'{}' is not a register".format(register)) register = register[1] if self.registers[register]: raise RuntimeError("Register 'R{}' is already in use".format(register)) else: for register in range(0, 5): register = str(register) # find a free register if not self.registers[register]: break else: raise RuntimeError("No free registers are available") self.registers[register] = True return "R" + register def free(self, register): """Marks a register as unused Keyword arguments: register -- a register in the format Rn where 0 <= n < 8 """ # remove leading R register = register[1:] if register in self.registers: if self.registers[register]: self.registers[register] = False; else: raise RuntimeError("free() of unused register") else: raise RuntimeError("free() of invalid register") def next_free(self, use_register=None): """Mark use_register as in use, find the next free register and then free both. """ if use_register: self.alloc(use_register) next_avail = self.alloc() self.free(next_avail) if use_register: self.free(use_register) return next_avail def get_register(self, text): """Retrieve the first register from a binary instruction""" text = text.strip() # ignore commented lines if text.startswith(";"): return None # skip labels if any text = text.upper().split(":", 1)[-1] # exclude the first operand, e.g. LOAD text = text.lstrip(" ").split(" ", 1)[-1] # retieve the first element before the next space, e.g. R0 text = text.lstrip(" ").split(" ", 1)[0] # is it a register? if self.is_register(text): return text # out of luck return None def is_register(self, text): """Returns True if text is a register, false otherwise""" return len(text) == 2 and text[0] == "R" and text[1] in self.registers def get_instruction(self, text): """Retrieve the instruction from text skipping labels and comments""" text = text.strip() # ignore commented lines if text.startswith(";"): return None # skip labels if any text = text.upper().split(":", 1)[-1] # the first element is assumed to be an instruction text = text.lstrip(" ").split(" ", 1)[0] # instructions have a length between 2 (OR) and 4 (LOAD) if len(text) >= 2 and len(text) <= 4: return text return None def find_register(self, instructions_list, fatal=False): """Finds the last modified register in a list of instructions""" for line in reversed(instructions_list): reg = self.get_register(line) if reg: return reg else: instruction = self.get_instruction(line) # convention: non-void functions store their result value in R0 if instruction == "BRS": return "R0" if fatal: raise RuntimeError("No register found in instructions list") return None def is_register_changed(self, line, register): """Returns True if the register is possibly modified in the line Keyword arguments: line -- The instruction line to be analyzed register -- The register to be looked for in the line """ line = line.split(";", 1)[0].strip().upper() register = register.upper() if not self.is_register(register): raise RuntimeError("Invalid register argument") # split into at most 3 elements (instruction, register, operand) matches = re.split("\s+", line, 2) if len(matches) == 2: instruction, reg = matches if (instruction == "PULL" and self.is_register(reg) and reg == register): return True # Assume function calls to be malicious if instruction == "BRS": return True elif len(matches) == 3: instruction, reg, operand = matches # remove whitespace from the operand. LF and CR do not occur operand = operand.replace("\t", "").replace(" ", "") if (operand.startswith("[--" + register) or operand.endswith(register + "++]")): return True if instruction == "STOR" and operand == register: return True # MULL and DVMD modify two registers if (instruction in ("MULL", "DVMD") and self.is_register(reg) and int(reg[1]) + 1 == int(register[1])): return True if instruction not in ("CMP", "CHCK") and reg == register: return True return False