From 90ec610f709dc7cb25bbb3c288ec3e80ae0bc97e Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Mon, 28 Nov 2011 23:16:58 +0000 Subject: Support for switch statement, initialize global variables by 0 by default You shouldn't rely on the values of uninitialized variables, but it should not be initialized by 1 by default (typo: DW had to be DS) --- README | 5 ++- pp2cc.py | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 136 insertions(+), 6 deletions(-) diff --git a/README b/README index db9c929..dcf6f4e 100644 --- a/README +++ b/README @@ -85,10 +85,11 @@ A9 Statements A9.1 Labeled statements - not supported A9.2 Expression statement - supported by parser A9.3 Compound statement - supported -A9.4 Selection statements - if and if/else are supported, switch is not +A9.4 Selection statements - if and if/else are supported. switch is also + supported including fallthrough and case/ default labels support A9.5 Iteration statements - while, do/while and for are supported. A missing second expression in the for is equivalent to a non-zero constant -A9.6 Jump statements - goto is unsupported. continue, break are supported. +A9.6 Jump statements - goto is unsupported. continue and break are supported. return is supported with and without value. The function result is undefined for the second case diff --git a/pp2cc.py b/pp2cc.py index 14de70a..ecd73a8 100755 --- a/pp2cc.py +++ b/pp2cc.py @@ -10,7 +10,7 @@ permission. The generated output (assembly and messages) are not subject to this license. """ -import sys, re, os +import sys, re, os, operator from pycparser import c_parser, c_ast __author__ = "Peter Wu" @@ -336,7 +336,7 @@ class Parse(object): output.append("@DATA") for varName in self.globalVars: padding = " " * (16 - len(varName) - 1) - output.append(self.nameToVar(varName) + padding + " DW 1") + output.append(self.nameToVar(varName) + padding + " DS 1") output.append("") output.append("@CODE") # initialization of global variables @@ -966,12 +966,59 @@ class Parse(object): def parseEmptyStatement(self, linked_node): """Returns an empty list for an "empty statement" (duh)""" return [] + def parseSwitch(self, linked_node): + lines = [] + lines_cases = [] + lines_stmts = [] + node = linked_node.node + stmt = node.stmt + cond = self.parseStatement(node.cond, linked_node) + switch_reg = self.registers.find_register(cond, fatal=True) + lines += cond + if not isinstance(stmt, c_ast.Compound): + raise RuntimeError("The switch statement is expected to have a" + " compound statement") + + switch_end = self.uniqLbl("switchEnd") + linked_node.setBreak(switch_end) + lbl_default = None + for cn in stmt.block_items: + linked_cn = LinkedNode(cn, linked_node) + if linked_cn.type == "Case": + lbl_case = self.uniqLbl("case") + try: + case_val = self.determineConstValue(LinkedNode(cn.expr, + linked_cn)) + except RuntimeError: + e = sys.exc_info()[1] + print "case label does not reduce to an integer constant" + print e + raise + + lines_cases.append(self.asm.binary_op("CMP", switch_reg, case_val)) + lines_cases.append(self.asm.branch_op("BEQ", lbl_case)) + lines_stmts.append(self.asm.noop(lbl_case)) + lines_stmts += self.parseStatement(cn.stmt, linked_cn) + elif linked_cn.type == "Default": + lbl_default = self.uniqLbl("default") + lines_stmts.append(self.asm.noop(lbl_default)) + lines_stmts += self.parseStatement(cn.stmt, linked_cn) + else: + lines_stmts += self.parseStatement(cn, linked_cn) + lines += lines_cases + if lbl_default: + lines.append(self.asm.branch_op("BRA", lbl_default)) + else: + lines.append(self.asm.branch_op("BRA", switch_end)) + lines += lines_stmts + lines.append(self.asm.noop(switch_end)) + return lines def parseStatement(self, node, parent_linked_node, level_increment=False): linked_node = LinkedNode(node, parent_linked_node, level_increment=level_increment) self.asm.level = linked_node.level lines = [] if linked_node.type in ("Compound", "If", "Return", "DoWhile", "While", "For", - "Decl", "FuncDef", "Break", "Continue", "EmptyStatement"): + "Decl", "FuncDef", "Break", "Continue", "EmptyStatement", "Switch"): lines += getattr(self, "parse" + linked_node.type)(linked_node) elif linked_node.type in ("FuncDecl", "ArrayDecl", "Case", "DeclList", "Default", "EllipsisParam",# (int a, ...) @@ -979,7 +1026,7 @@ class Parse(object): "Enumerator", # enum value "EnumeratorList", # list of enum values "FuncDecl", "Goto", "Label", "ParamList", "PtrDecl", "Struct", - "TypeDecl", "Typedef", "Switch", "Union"): + "TypeDecl", "Typedef", "Union"): raise RuntimeError("Not implemented for type " + repr(node)) else: lines += self.parseExpression(node, parent_linked_node, level_increment) @@ -1007,6 +1054,88 @@ class Parse(object): else: raise RuntimeError("Not implemented expression and unknown type: " + repr(node)) return lines + def determineConstValue(self, linked_node): + node = linked_node.node + value = None + + # most significant bit, i.e. the bit that is one if negative + msb = pow(2, self.WORDSIZE - 1) + + if linked_node.type == "Constant": + if node.type != "int": + raise RuntimeError("Unsupported constant type '{}'".format(node.type)) + value = self.convertStrToInteger(self.convertInteger(node.value)) + elif linked_node.type == "UnaryOp": + op = node.op + operators = { + "-": "neg", + "+": "pos", + "!": "not", + "~": "invert" + } + if op not in operators: + raise RuntimeError("Operator '{}' is not supported".format(op)) + else: + operand = self.determineConstValue(node.expr) + if op == "-": + # negating a value in PP2 is flipping the WORDSIZE-th bit + value = operand ^ msb + elif op == "~": + # invert all bits + value = operand ^ (pow(2, self.WORDSIZE) - 1) + else: + value = int(getattr(operator, op)(operand)) + elif linked_node.type == "BinaryOp": + op = node.op + operators = { + "*": "mul", + "/": "div", + "%": "mod", + "+": "add", + "-": "sub", + # invert result if the sign bits do not equal + "<": "lt", + ">": "gt", + "<=": "le", + ">=": "ge", + # end special treatment + "==": "eq", + "!=": "ne", + "&": "and_", + "^": "xor", + "|": "or_", + "<<": "lshift", + ">>": "rshift" + } + if op not in operators: + raise RuntimeError("Operator '{}' is not supported".format(op)) + else: + operand1 = self.determineConstValue(node.left) + operand2 = self.determineConstValue(node.right) + if op in ("<", ">", "<=", ">="): + value = int(getattr(operator, op)(operand1, operand2)) + # 0 < 1 and -2 < -1 holds, but -1 < 0 does not. If the sign + # bits are not equal, the result needs to be flipped + if operand2 & msb != operand2 & msb: + value = not value + elif op == "/" and operand2 & msb: + raise RuntimeError("Division by a negative value is" + "undefined", linked_node=linked_node) + elif op == "/" and operand2 == 0: + raise RuntimeError("Division by zero", + linked_node=linked_node) + else: + value = int(getattr(operator, op)(operand1, operand2)) + else: + raise RuntimeError("Unsupported node type '{}'".format(linked_node.type)) + # The PP2 can only count in 18-bit, therefore AND the result. Note that + # overflow may occur for large numbes. This is a non-issue for unary + + # - ~ (and ! because it results in 0 or 1). For binary comparison + # operators and bitwise operations, it's neither an issue because the + # word length does not increase. Right shift works fine, left shift may + # change the sign bit (as expected) + value &= pow(2, self.WORDSIZE) - 1 + return value if __name__ == "__main__": settings = { -- cgit v1.2.1