def new_member_val(cls, node, name, dest_reg): """ Extract value of an already initialized member while initializing some other member. """ cls, obj_ptr = cls.instantiating_class m_offset = cls.get_member_idx(name) * CC.var_size node.add_instr(CC.MOV, src=Loc.const(m_offset), dest=dest_reg) node.add_instr(CC.ADD, lhs=obj_ptr, rhs=dest_reg) node.add_instr(CC.MOV, src=Loc.mem(str(dest_reg)), dest=dest_reg)
def _gen_code_boolop(self, **kwargs): self.label_true = kwargs.get('on_true', CC.new_label()) self.label_false = kwargs.get('on_false', CC.new_label()) self.label_right = CC.new_label( ) # additional label to jump to the right operand for case in switch(self.type.type): if case(LP.AND): self.add_child_by_idx(0, on_true=self.label_right, on_false=self.label_false) self.add_instr(CC.LABEL, label=self.label_right) self.add_child_by_idx(1, on_true=self.label_true, on_false=self.label_false) break if case(LP.OR): self.add_child_by_idx(0, on_true=self.label_true, on_false=self.label_right) self.add_instr(CC.LABEL, label=self.label_right) self.add_child_by_idx(1, on_true=self.label_true, on_false=self.label_false) break if case(): raise InternalError('wrong bool op type %s' % str(self.type)) # if no jump keywords were given, the result will be used as a value -- push it if not self.has_jump_codes(kwargs): self.label_after = CC.new_label() self.add_instr(CC.LABEL, label=self.label_true) self.add_instr(CC.PUSH, src=Loc.const(1)) self.add_instr(CC.JUMP, label=self.label_after) self.add_instr(CC.LABEL, label=self.label_false) self.add_instr(CC.PUSH, src=Loc.const(0)) self.add_instr(CC.LABEL, label=self.label_after)
def gen_code(self, **kwargs): for case in switch(self.type.type): if case(LP.RETURN): if self.children: # Evaluate the expression and pop the result to eax for returning. self.add_child_by_idx(0) self.add_instr(CC.POP, dest=Loc.reg('a')) # Jump to the return section self.add_instr(CC.JUMP, label=self.get_cur_fun().ret_label) break if case(LP.IF): # children: cond, (then-block)?, (else-block)? self.add_child_by_idx(0, on_true=self.label_then, on_false=self.label_else) if len(self.children) > 1: # there is a then-block self.add_instr(CC.LABEL, label=self.label_then) self.add_child_by_idx(1) if len(self.children) > 2: # there is an else-block self.add_instr( CC.JUMP, label=self.label_after) # jump out of then-block self.add_instr(CC.LABEL, label=self.label_else) self.add_child_by_idx(2) self.add_instr(CC.LABEL, label=self.label_after) break if case(LP.WHILE): # children: cond, (block)? if len(self.children) > 1: # there is a loop block self.add_instr(CC.JUMP, label=self.label_cond) self.add_instr(CC.LABEL, label=self.label_block) self.add_child_by_idx(1) self.add_instr(CC.LABEL, label=self.label_cond) self.add_child_by_idx(0, on_true=self.label_block, on_false=self.label_after) self.add_instr(CC.LABEL, label=self.label_after) break if case(LP.ASSIGN): # compute assigned value on stack self.add_child_by_idx(1) # compute the destination address, if needed self.add_child_by_idx(0, addr_only=True) # put the value into destination address self.add_instr(CC.POP, dest=Loc.reg('a')) var_loc = self.children[0].get_loc() self.add_instr(CC.MOV, src=Loc.reg('a'), dest=var_loc) break if case(LP.INCR, LP.DECR): op = CC.ADD if self.type.type == LP.INCR else CC.SUB # compute the destination address, if needed self.add_child_by_idx(0, addr_only=True) var_loc = self.children[0].get_loc() self.add_instr(op, lhs=Loc.const(1), rhs=var_loc) break if case(): raise NotImplementedError('unknown statement type: %s' % str(self.type))
def check_unused_result(self): """ Drop result pushed on stack which wouldn't be used. Both this add and the push itself will be optimized out later. """ if self.tree.unused_result: debug('POP UNUSED RESULT', self.tree.pos) self.add_instr(CC.ADD, lhs=Loc.const(CC.var_size), rhs=Loc.reg('top'), comment=CC.S_UNUSED_RESULT)
def get_loc(self): """ Return where the variable is located after the generated code is executed. """ for case in switch(self.type.type): if case(LP.IDENT): return Loc.sym(self.tree.symbol(self.value)) if case(LP.ATTR): return Loc.mem(Loc.reg_d) if case(LP.ELEM): return Loc.mem(Loc.reg_d)
def _gen_code_as_value(self, **kwargs): for case in switch(self.type.type): if case(LP.BOOLEAN, LP.INT, LP.OBJECT, LP.ARRAY): self.add_instr(CC.PUSH, src=Loc.const(self.value)) break if case(LP.STRING): self.add_instr(CC.PUSH, src=Loc.stringlit(self)) break if case(): raise InternalError('invalid literal type %s' % str(self.type.type))
def _gen_code_load_member(self, dest_reg, addr_only=False): cls = self.tree.cls m_idx = cls.get_member_idx(self.value) self.add_child_by_idx(0) # evaluate the object's base address self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr(CC.MOV, src=Loc.const(m_idx), dest=Loc.reg('d')) # idx must be a register self._gen_code_load_memory(dest_reg, Loc.reg_a, idx=Loc.reg('d'), mult=CC.var_size, addr_only=addr_only)
def _gen_code_load_array_elem(self, dest_reg, addr_only=False): self.add_child_by_idx(0) # evaluate the array address self.add_child_by_idx(1) # evaluate the array index self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr(CC.POP, dest=Loc.reg('d')) # Calculate the elem address -- with offset +1 because of array size stored at 0. self._gen_code_load_memory(dest_reg, Loc.reg_d, offset=CC.var_size, idx=Loc.reg_a, mult=CC.var_size, addr_only=addr_only)
def default_asm_value(type): """ Return Loc of a value that should be assigned to allocated but unset objects. """ for case in switch(type): if case(LP.INT, LP.BOOLEAN, LP.ARRAY, LP.OBJECT): return Loc.const( 0) # False=0 for boolean, NULL=0 for array and object. if case(LP.STRING): return Loc.stringlit(LiteralTree( LP.STRING, '""')) # Pointer to "" constant. if case(): raise InternalError('no default asm value for type %s' % str(type))
def gen_code(self, **kwargs): for case in switch(self.type.type): if case(LP.NEG): # integer negation self.add_child_by_idx(0) self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr(CC.NEG, rhs=Loc.reg('a')) self.add_instr(CC.PUSH, src=Loc.reg('a')) break if case(LP.NOT): # logical not if self.has_jump_codes(kwargs): # called as part of condition evaluation -- just forward the jump labels self.add_child_by_idx(0, on_true=kwargs['on_false'], on_false=kwargs['on_true']) return # otherwise -- evaluate the boolean value: arg == false (0) self.add_child_by_idx(0) self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr(CC.BOOL_OP, lhs=Loc.const(0), rhs=Loc.reg('a'), op='sete', dest=Loc.reg('a')) self.add_instr(CC.PUSH, src=Loc.reg('a')) break if case(): raise InternalError('wrong unop value type') self.check_unused_result()
def gen_code(self, **kwargs): fun = self.get_cur_fun() block = self.get_cur_block() # For each declared item, compute its address on stack (and assign the value if needed). for item in self.items: addr = Loc.var_addr(fun.next_var_num()) sym = Symbol(item.name, self.decl_type.type, addr) if item.expr: self.add_child_by_idx(item.expr_child_idx) self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr(CC.MOV, src=Loc.reg('a'), dest=Loc.sym(sym)) # Important: we add symbol containing the new var's address *after* assigned expression # is evaluated (because of e.g. int i = i+7); but still inside the loop -- so next # declarations use the new symbol. block.tree.add_symbol(sym)
def scan_labels(self): """ A function that indexes the labels and jumps in the given codes. """ self.labels = {} self.jumps = {} for i in self.matcher.code_iter(): code = self.codes[i] if self.matcher.match(code, type=CC.LABEL): self.labels[code['label']] = i else: if self.matcher.match(code, type=AnyOf(CC.JUMP, CC.IF_JUMP, CC.CALL)): # functions begin with labels -- collect their calls, it might be useful later label = code['label'] elif self.matcher.match(code, type=AnyOf(CC.PUSH, CC.MOV), src=Loc.stringlit(Loc.ANY)): # collect uses of string constants label = code['src'].value else: continue if label in self.jumps: self.jumps[label].append(i) else: self.jumps[label] = [i] debug('--- label maps ---') for label in self.labels: debug('[%d]' % self.labels[label], label, ': ', str(self.jumps.get(label, None)))
def _gen_code_in_cond(self, **kwargs): # [0]: load the bool value into a register. for case in switch(self.type.type): if case(LP.IDENT) and self.tree.symbol( self.value).type == LP.BOOLEAN: # A variable referenced while instantiating is surely a class member. if NewCode.instantiating_class: NewCode.new_member_val(self, self.value, Loc.reg('a')) else: self.add_instr(CC.MOV, src=Loc.sym(self.tree.symbol(self.value)), dest=Loc.reg('a')) break if case(LP.ELEM) and self.tree.value_type.type == LP.BOOLEAN: self._gen_code_load_array_elem(dest_reg=Loc.reg('a')) break if case(LP.ATTR) and self.tree.value_type.type == LP.BOOLEAN: self._gen_code_load_member(dest_reg=Loc.reg('a')) if case(): raise InternalError( 'jump-expr codes for non-bool %s expression at %s!' % (str(self.type), self.tree.pos)) # [1]: Compare and jump (note: comparing with 0, so on equality jump to false!) self.add_instr(CC.IF_JUMP, lhs=Loc.const(0), rhs=Loc.reg('a'), op='je', label=kwargs['on_false']) self.add_instr(CC.JUMP, label=kwargs['on_true'])
def _gen_code_load_memory(self, dest_reg, base_ptr, offset=None, idx=None, mult=None, addr_only=False): """ Evaluate element/member address and get its value. """ self.add_instr(CC.LEA, src=Loc.mem(base_ptr, offset=offset, idx=idx, mult=mult), dest=dest_reg, drop_reg1=Loc.reg('a'), drop_reg2=Loc.reg('d')) if addr_only: return self.add_instr(CC.MOV, src=Loc.mem(Loc.reg_d), dest=dest_reg) # load element
def _cp_save_to_pocket(self, pos, code): """ Constant propagation: when moving const to a register, stow it in the pocket instead.""" if (self.matcher.match(code, type=CC.MOV, src=self.CONST_LOCS, dest=Loc.reg(Loc.ANY)) and not self.matcher.match(code, comment=CC.S_PROPAGATED)): debug('mov const %s -> reg %s found at %d' % (code['src'].value, code['dest'].value, pos)) self.pocket[code['dest']] = code['src'] self.mark_deleted(pos, comment=CC.S_PROPAGATED)
def _cp_empty_pocket_if_needed(self, pos, code): """ Constant propagation: empty the pocket in case of calls or jumps. """ # [1] On function call, empty the pocket. if len(self.pocket) and code['type'] == CC.CALL: debug('function call at %d, emptying pocket' % pos) self.pocket = {} # [2] On function exit, drop %eax and empty pocket. if len(self.pocket) and code['type'] == CC.ENDFUNC: if Loc.reg('a') in self.pocket.keys(): self._add_to_apply_pocket(pos, {Loc.reg('a'): self.pocket[Loc.reg('a')]}) debug('ENDFUNC at %d, dropping reg a' % pos) self.pocket = {} # [3] On jump instructions (both in-/out-bound) assign the pocket values anyway. if len(self.pocket) and code['type'] in [CC.JUMP, CC.IF_JUMP, CC.LABEL]: debug('%s at %d, reassigning pocket values' % (CC._code_name(code['type']), pos)) # We can't insert into a list while iterating, so save the pocket for now # TODO we could later skip at least some of pocket's values, if we check that # a value is the same at label and all jumps to it, or the register is not live. self._add_to_apply_pocket(pos, self.pocket.copy()) self.pocket = {}
def __init__(self, tree, **kwargs): super(FunCode, self).__init__(tree, **kwargs) self.ret_type = tree.ret_type self.name = tree.name self.args = tree.args self.add_child(BlockCode(tree.children[0])) self.ret_label = CC.new_label() for i in xrange(len(self.args)): arg = self.args[i] self.tree.add_symbol(Symbol(arg.name, arg.type, Loc.arg_addr(i))) self.var_count = self.children[0].count_local_vars() debug('fun ', self.name, 'VAR COUNT: ', self.var_count) self.used_vars = 0 # how many local variables are currently allocated
def _cp_two_const_operator(self, pos, code): """ Constant propagation for operators: if both operands are consts, calculate the result and propagate it instead.""" # 'lhs' and 'rhs' are both registers with values stored in pocket, or only 'rhs' is and # 'lhs' is an actual constant (e.g. propagated there in previous loop iteration, when 'rhs' # was not yet propagated). if (self.matcher.match( code, type=self.BIN_OPS, lhs=self.CONST_OR_REG, rhs=Loc.reg(Loc.ANY)) and (code['lhs'].is_constant() or code['lhs'] in self.pocket) and code['rhs'] in self.pocket): debug('two-const operator %s at %d' % (CC._code_name(code['type']), pos)) if code['type'] == CC.BOOL_OP: cmp_fun = { 'sete': operator.eq, 'setne': operator.ne, 'setg': operator.gt, 'setge': operator.ge, 'setl': operator.lt, 'setle': operator.le, }[code['op']] # The operator.* bool functions return bool, we want an int to push. op_fun = lambda x, y: int(cmp_fun(x, y)) else: op_fun = { CC.ADD: operator.add, CC.SUB: operator.sub, CC.MUL: operator.mul, CC.DIV: operator.floordiv, CC.MOD: c_modulo }[code['type']] arg1 = int(self.pocket[code['rhs']].value) if code['lhs'].is_constant(): arg2 = int(code['lhs'].value) else: arg2 = int(self.pocket[code['lhs']].value) res_val = op_fun(arg1, arg2) debug(' -> args %d, %d res %d' % (arg1, arg2, res_val)) res_reg = code['dest'] if code['type'] in [CC.DIV, CC.MOD] else code['rhs'] # Delete and re-insert value, to maintain hash properties. if res_reg in self.pocket: del self.pocket[res_reg] self.pocket[res_reg] = Loc.const(res_val) self.mark_deleted(pos, comment=CC.S_PROPAGATED, value=res_val) self.prop_consts += 1 return True
def _cp_overwrite_pocket_values(self, pos, code): """ Constant propagation: if a register is being assigned, delete its entry in pocket.""" # Only attrs modifying their location are 'dest', 'rhs'. # Note: rhs needs to be reviewed first, otherwise if rhs and dest are the same we # would forget the pocket value at dest, while it is still needed by rhs. for attr in [a for a in ['rhs', 'dest'] if a in code.keys()]: if not code[attr].is_reg() or code[attr] not in self.pocket: continue value = self.pocket[code[attr]].value # NEG is a special case here: 'dest' is both source and destination -- but the # value remains constant, so remove the code and adjust value in pocket. if code['type'] == CC.NEG: new_value = value[1:] if '-' in value else '-' + value debug('NEG reg %s with const at %d, adjusting pocket value to %s' % ( code[attr].value, pos, new_value)) # Delete and re-insert value, to maintain hash properties. loc = self.pocket[code[attr]] del self.pocket[code[attr]] loc.value = new_value self.pocket[code[attr]] = loc self.mark_deleted(pos, comment=CC.S_PROPAGATED) self.prop_consts += 1 return True else: # otherwise, just forget the register's value from pocket. # but the right operand for cmpl also needs to be dropped. debug('reg %s is %s at %d, forgetting from pocket' % ( code[attr].value, attr, pos)) if attr == 'rhs': debug(' ^ but applying reg %s' % code['rhs'].value) self._add_to_apply_pocket(pos, {code['rhs']: self.pocket[code['rhs']]}) del self.pocket[code[attr]] # Special case: division -- invalidate %eax and %edx values in pocket as idivl stores # results there. if code['type'] in [CC.DIV, CC.MOD]: for reg in [Loc.reg('a'), Loc.reg('d')]: if reg in self.pocket: del self.pocket[reg]
def _gen_code_relop(self, **kwargs): self.add_child_by_idx(0) self.add_child_by_idx(1) self.add_instr(CC.POP, dest=Loc.reg('d')) self.add_instr(CC.POP, dest=Loc.reg('a')) try: if self.has_jump_codes(kwargs): # part of condition evaluation -- select the conditional jump instruction self.label_true = kwargs['on_true'] self.label_false = kwargs['on_false'] jmp_code = { LP.EQ: 'je', LP.NEQ: 'jne', LP.GT: 'jg', LP.GEQ: 'jge', LP.LT: 'jl', LP.LEQ: 'jle', }[self.type.type.id] self.add_instr(CC.IF_JUMP, lhs=Loc.reg('d'), rhs=Loc.reg('a'), op=jmp_code, label=self.label_true) self.add_instr(CC.JUMP, label=self.label_false) else: # expression returning bool -- select the comparision set instruction set_code = { LP.EQ: 'sete', LP.NEQ: 'setne', LP.GT: 'setg', LP.GEQ: 'setge', LP.LT: 'setl', LP.LEQ: 'setle', }[self.type.type.id] self.add_instr(CC.BOOL_OP, lhs=Loc.reg('d'), rhs=Loc.reg('a'), op=set_code, dest=Loc.reg('a')) self.add_instr(CC.PUSH, src=Loc.reg('a')) except KeyError: raise InternalError('wrong rel op type %s' % str(self.type))
def _gen_code_stringop(self): if self.type.type != LP.PLUS: raise InternalError('wrong string op type %s' % str(self.type)) # only + (concatenation) for now # If at least one operand is a constant or a variable, we can safely just push them in # reversed order (for call to 'concatString' library function). Otherwise we need to # evaluate them in the right order and then reverse them on stack. if isinstance(self.children[0], LiteralCode) or isinstance( self.children[1], LiteralCode): self.add_child_by_idx(1) self.add_child_by_idx(0) else: self.add_child_by_idx(0) self.add_child_by_idx(1) self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr(CC.POP, dest=Loc.reg('d')) self.add_instr(CC.PUSH, src=Loc.reg('a')) self.add_instr(CC.PUSH, src=Loc.reg('d')) self.add_instr(CC.CALL, label=Builtins.STRCAT_FUNCTION) self.add_instr(CC.ADD, lhs=Loc.const(2 * CC.var_size), rhs=Loc.reg('top')) self.add_instr(CC.PUSH, src=Loc.reg('a'))
def _add_nonzero_inits(self, cls): """ Init all class fields which have nonzero values. Start with subclass members, as they are first in the memory block. """ if cls.base: # Init the superclass part. `instantiating_class` can remain the same, as the base # pointer is the same and the offsets are calculated properly in get_member_idx(). self._add_nonzero_inits(cls.base) for decl in cls.decls(): dtype = decl.decl_type.type for item in decl.items: expr_code = ExprFactory(item.expr) if expr_code.is_constant() and expr_code.value == 0: continue # Skip assignments with 0, as the memory is zero-filled. self.add_child_code(expr_code) # Evaluate the assigned value. self.add_instr(CC.POP, dest=Loc.reg('d')) # Compute member address -- object base pointer is still in %ebx. m_offset = cls.get_member_idx(item.name) * CC.var_size self.add_instr(CC.MOV, src=Loc.const(m_offset), dest=Loc.reg('a')) self.add_instr(CC.ADD, lhs=Loc.reg('b'), rhs=Loc.reg('a')) self.add_instr(CC.MOV, src=Loc.reg('d'), dest=Loc.mem(Loc.reg_a))
def _gen_code_as_value(self, addr_only=False, **kwargs): # If addr_only is set, don't push the value, stop after its location is computed. for case in switch(self.type.type): if case(LP.IDENT): if addr_only: return # A variable referenced while instantiating is surely a class member. if NewCode.instantiating_class: NewCode.new_member_val(self, self.value, Loc.reg('a')) else: self.add_instr(CC.MOV, src=Loc.sym(self.tree.symbol(self.value)), dest=Loc.reg('a')) self.add_instr(CC.PUSH, src=Loc.reg('a')) break if case(LP.ATTR): if self.tree.obj_type.type == LP.ARRAY and self.value == Builtins.LENGTH: # Array length is stored in first element of its memory block. self.add_child_by_idx(0) self.add_instr(CC.POP, dest=Loc.reg('d')) if addr_only: return self.add_instr(CC.MOV, src=Loc.mem(Loc.reg_d), dest=Loc.reg('d')) self.add_instr(CC.PUSH, src=Loc.reg('d')) elif self.tree.obj_type.type == LP.OBJECT: self._gen_code_load_member(dest_reg=Loc.reg('d'), addr_only=addr_only) if addr_only: return self.add_instr(CC.PUSH, src=Loc.reg('d')) else: raise InternalError('invalid attr `%s` for type `%s`' % (self.value, str(self.tree.obj_type))) break if case(LP.ELEM): self._gen_code_load_array_elem(dest_reg=Loc.reg('d'), addr_only=addr_only) if addr_only: return self.add_instr(CC.PUSH, src=Loc.reg('d')) break if case(): raise InternalError('invalid variable type %s' % str(self.type.type))
def _gen_code_intop(self): self.add_child_by_idx(0) self.add_child_by_idx(1) for case in switch(self.type.type): if case(LP.PLUS, LP.MINUS, LP.MULT): op = { LP.PLUS: CC.ADD, LP.MINUS: CC.SUB, LP.MULT: CC.MUL }[self.type.type.id] self.add_instr(CC.POP, dest=Loc.reg('d')) self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr(op, lhs=Loc.reg('d'), rhs=Loc.reg('a')) self.add_instr(CC.PUSH, src=Loc.reg('a')) break if case(LP.DIV, LP.MOD): self.add_instr(CC.POP, dest=Loc.reg('c')) self.add_instr(CC.POP, dest=Loc.reg('a')) # quotient in eax, remainder in edx result = { LP.DIV: Loc.reg('a'), LP.MOD: Loc.reg('d') }[self.type.type.id] code = {LP.DIV: CC.DIV, LP.MOD: CC.MOD}[self.type.type.id] self.add_instr(code, lhs=Loc.reg('c'), rhs=Loc.reg('a'), dest=result) self.add_instr(CC.PUSH, src=result) break if case(): raise InternalError('wrong int op type %s' % str(self.type))
class LatteOptimizer(object): INF_PASSES = 100 # number of passes considered 'sufficiently infinite' # Codes that 'don't do anything', e.g. if there are no other codes between a jump and its # label, the jump can be safely deleted. NOOPS = AnyOf(CC.LABEL, CC.EMPTY, CC.DELETED, CC.SCOPE, CC.ENDSCOPE) # Codes that do an operation but no flow control or stack operations, so in [push, <op>, pop] # push and pop can be combined into mov if the operation's arguments are unrelated. NO_STACK_OPS = AnyOf(CC.MOV, CC.ADD, CC.SUB, CC.MUL, CC.NEG) # All locations considered constant. CONST_LOCS = AnyOf(Loc.const(Loc.ANY), Loc.stringlit(Loc.ANY)) # Binary operators that have a result. BIN_OPS = AnyOf(CC.ADD, CC.SUB, CC.MUL, CC.DIV, CC.MOD, CC.BOOL_OP) # Const matcher for constant propagation. CONST_OR_REG = AnyOf(Loc.const(Loc.ANY), Loc.reg(Loc.ANY)) def __init__(self, codes): self.codes = codes self.matcher = CodeMatcher(self) self.labels = {} # Map from label name to position in codes. self.jumps = {} # Map from label name to positions which jump to it. self.print_codes() self.scan_labels() self.opt_counters = {} def run_all(self, max_passes): """ Optimizer main function, which runs the implemented optimizations on the codes. """ if max_passes == 0: debug('optimizer disabled') return self.run_opt(self.del_unused_results) # no need to run this one multiple times for count in xrange(max_passes): debug('------------- global optimizer pass %d (of max %d) -------------' % ( count+1, max_passes)) sum_counters = sum(self.opt_counters.values()) self.run_opt(self.del_jumps_to_next, max_passes=self.INF_PASSES) self.run_opt(self.del_unused_labels) self.run_opt(self.reduce_push_pop, max_passes=self.INF_PASSES) self.run_opt(self.propagate_constants) #self.run_opt(self.clear_deleted_codes) if sum(self.opt_counters.values()) == sum_counters: debug('------------------ all optimizations returned finish -----------------') break # TODO don't assign dead vars # TODO free string memory # TODO [mov mem regA, mov regA regB] # TODO [mov regA memX, mov memX regB] if Flags.optimizer_summary: Status.add_note(LatteError('optimizer case counters:')) for name, count in self.opt_counters.iteritems(): Status.add_note(LatteError(name + ': ' + str(count))) def run_opt(self, function, max_passes=1, **kwargs): """ A function to run a single optimization, possibly with many passes in a row. An optimization is ran again if it returned a non-zero value and it has been started less than max_passes times. Other keyword arguments are passed to the optimization function. """ for count in xrange(max_passes): name = function.__name__ debug('--- OPT:', name, 'pass %d (of max %d)' % (count+1, max_passes)) ret = function(**kwargs) # sum the optimization results, assuming value returned is number of cases resolved self.opt_counters[name] = self.opt_counters.get(name, 0) + ret debug('--- OPT:', name, 'returned', ret or 'finish') if not ret: break def print_codes(self): """ Debug: print the current codes list. """ for i in xrange(len(self.codes)): code = self.codes[i] if (code['type'] == CC.EMPTY): debug('\n', no_hdr=True) continue d = code.copy() del d['type'] debug('[%d]' % i, CC._code_name(code['type']) + '\t' + CC._str_code(d), no_hdr=True) def scan_labels(self): """ A function that indexes the labels and jumps in the given codes. """ self.labels = {} self.jumps = {} for i in self.matcher.code_iter(): code = self.codes[i] if self.matcher.match(code, type=CC.LABEL): self.labels[code['label']] = i else: if self.matcher.match(code, type=AnyOf(CC.JUMP, CC.IF_JUMP, CC.CALL)): # functions begin with labels -- collect their calls, it might be useful later label = code['label'] elif self.matcher.match(code, type=AnyOf(CC.PUSH, CC.MOV), src=Loc.stringlit(Loc.ANY)): # collect uses of string constants label = code['src'].value else: continue if label in self.jumps: self.jumps[label].append(i) else: self.jumps[label] = [i] debug('--- label maps ---') for label in self.labels: debug('[%d]' % self.labels[label], label, ': ', str(self.jumps.get(label, None))) def find_jump_before(self, label, pos): """ Return position of last code jumping to a label *before* position pos. """ ret = bisect_left(self.jumps.get(label, []), pos) - 1 return self.jumps[label][ret] if ret >= 0 else None def mark_deleted(self, pos_or_iter, **kwargs): """ Mark code for deletion, at a single index or whole iterable. """ if isinstance(pos_or_iter, int): self.codes[pos_or_iter]['_type'] = CC._code_name(self.codes[pos_or_iter]['type']) self.codes[pos_or_iter].update(kwargs) # just for debugging, to put more indicators self.codes[pos_or_iter]['type'] = CC.DELETED else: for pos in pos_or_iter: self.mark_deleted(pos) def del_unused_results(self, **kwargs): """ Find the stack pops that are marked as popping an unused result, and delete them along with the push that causes them. """ result = 0 for pos in self.matcher.code_iter(): if self.matcher.match_at(pos, type=CC.ADD, comment=CC.S_UNUSED_RESULT): # Found an unused result, trace back to all pushes that might lead to it. # We don't need to trace arbitrary jump sequences that lead there, as for now # the sequence should be either just [push, pop] or, for bool expr evaluation: # [push 1, jump after, ..., push 0, label after, pop] debug('unused result at', pos) self.mark_deleted(pos) push_off = -1 # first, try the two-push case: find the one before the jump if self.matcher.match_at(pos-1, type=CC.LABEL): debug(' found label', self.codes[pos-1]['label'], 'before pop') push_off = -2 jump_pos = self.find_jump_before(self.codes[pos-1]['label'], pos) if jump_pos is None: debug(' jump to label not found, ignoring') elif self.matcher.match_at(jump_pos-1, type=CC.PUSH): debug(' found jumped push at', jump_pos-1) self.mark_deleted(jump_pos-1) result += 1 else: debug(' code at', jump_pos-1, 'is not a push, ignoring') # find the other push (or the only one if there was no label) if self.matcher.match_at(pos+push_off, type=CC.PUSH): debug(' found push at', pos+push_off) self.mark_deleted(pos+push_off) result += 1 else: debug(' code at', pos+push_off, 'is not a push, ignoring') return result def clear_deleted_codes(self, **kwargs): """ Really delete the codes marked DELETED. """ old_len = len(self.codes) self.codes = filter(lambda code: not self.matcher.match(code, type=CC.DELETED), self.codes) debug('pruned %d deleted codes' % (old_len - len(self.codes))) # label maps need to be recalculated after deleting self.scan_labels() return old_len - len(self.codes) def del_jumps_to_next(self, **kwargs): """ Delete jump codes that can be safely omitted (passed through). If the jump is conditional, the comparision itself is also deleted. """ result = 0 start_pos = 0 for pos in self.matcher.gen_next(start_pos, type=AnyOf(CC.JUMP, CC.IF_JUMP)): start_pos = pos label = self.codes[pos]['label'] # check if there is any 'non-noop' code between the jump and its label # (keep in mind that the label may be before the jump) start = min(pos, self.labels[label]) stop = max(pos, self.labels[label]) try: self.matcher.gen_next(start, stop, negate=True, type=self.NOOPS).next() except StopIteration: debug('skipping', self.codes[pos].get('op', 'jmp'), 'to', label, 'at', pos) result += 1 self.mark_deleted(pos) return result def del_unused_labels(self, **kwargs): """ Delete unnecessary labels (ones without jumps to them). """ # First, rescan labels to remove deleted jumps from map self.scan_labels() result = 0 for label, pos in self.labels.iteritems(): # don't consider labels starting function -- unused function should already be deleted if label[0] == '.' and label not in self.jumps: debug('deleting unused label', label, 'at', pos) self.mark_deleted(pos) result += 1 return result def reduce_push_pop(self, **kwargs): """ Optimize push-pop sequences. Detailed cases: * delete sequences [push X, pop X] * combine [push X, pop Y] into [mov X Y] -- the pop destination is always a register """ result = 0 for indexes in self.matcher.gen_seq([code_spec(type=CC.PUSH), code_spec(type=CC.POP)]): debug('push-pop sequence:', str(indexes)) result += self._do_push_pop_reduction(indexes) for indexes in self.matcher.gen_seq([code_spec(type=CC.PUSH), code_spec(type=self.NO_STACK_OPS), code_spec(type=CC.POP)]): debug('push-op-pop sequence:', str(indexes)) p_push, p_op, p_pop = indexes src, dest = self.codes[p_push]['src'], self.codes[p_pop]['dest'] # do the reduction only if op's arguments do nothing to src and dest locations if ((src.is_constant() or src not in self.codes[p_op].values()) and dest not in self.codes[p_op].values()): result += self._do_push_pop_reduction([p_push, p_pop]) return result def _do_push_pop_reduction(self, indexes): """ Child function of reduce_push_pop, that does the actual reduction. Separate function just for code reuse. `indexes` should be a two-element position list.""" p_push, p_pop = indexes src, dest = self.codes[p_push]['src'], self.codes[p_pop]['dest'] if src == dest: debug(' deleting push-pop with same attrs at', str(indexes)) self.mark_deleted(indexes) return 1 else: debug(' combining [push, pop] to mov at', str(indexes)) self.mark_deleted(p_push, _type='[push,pop]', dest=dest) self.codes[p_pop] = CC.mkcode(CC.MOV, src=src, dest=dest, comment='combined from [push,pop]') return 1 return 0 def propagate_constants(self, **kwargs): """ Propagate constants moved to registers to the place where they're used. """ # Note: some codes, e.g. division, require arguments in registers. # After deleting a [mov const, reg], hold the const value in pocket and paste it into all # reg occurences until it's assigned something else. # Note: remember to empty your pockets when jumping :) (and calling) # In particular: before a label or jump assign the value to the register anyway. # TODO this can be sometimes avoided if we consider the whole jump graph and live vars... # Also remember that division requires both arguments in registers. # For result, count when a register is replaced with a value from pocket. # TODO another level: propagate if_jumps if both operands are constants. self.prop_consts = 0 self.pocket = {} self.apply_needed = False for pos in self.matcher.code_iter(): code = self.codes[pos] if len(self.pocket): # For readability, the subsequent cases of constant propagation are in separate # functions. Any of those functions can return True to indicate that the code no # longer needs to be considered for propagation (e.g. was deleted), and the # iteration will step over to the next code. # TODO two const in IF_JUMP if (self._cp_two_const_operator(pos, code) or self._cp_apply_value_from_pocket(pos, code) or self._cp_drop_marked_registers(pos, code) or self._cp_overwrite_pocket_values(pos, code) or self._cp_empty_pocket_if_needed(pos, code)): continue self._cp_save_to_pocket(pos, code) # Turn the pocket indicators into assignments if self.apply_needed: self._insert_apply_pockets() return self.prop_consts def _add_to_apply_pocket(self, pos, a_pocket): debug(' _add_to_apply_pocket at %d:' % pos) for reg, val in a_pocket.iteritems(): debug('\t', reg.value, '->', val.value) if 'apply_pocket' in self.codes[pos]: self.codes[pos]['apply_pocket'].update(a_pocket) else: self.codes[pos]['apply_pocket'] = a_pocket.copy() self.apply_needed = True def _insert_apply_pockets(self): """ Child function of propagate_constants, used to insert assignments before pocket indicators, because they can't be inserted before when iterating through the list. """ start_pos = 0 debug('APPLY POCKETS') for pos in self.matcher.gen_next(start_pos, attrlist=['apply_pocket']): # Generate the move instructions and insert them inside codes list. moves = map(lambda (reg, val): CC.mkcode(CC.MOV, src=val, dest=reg, comment=CC.S_PROPAGATED), self.codes[pos]['apply_pocket'].iteritems()) debug('apply pocket: insert %d moves at %d' % (len(moves), pos)) del self.codes[pos]['apply_pocket'] self.codes[pos:pos] = moves start_pos = pos + len(moves) - 1 # rebuild the jump maps self.scan_labels() def _cp_two_const_operator(self, pos, code): """ Constant propagation for operators: if both operands are consts, calculate the result and propagate it instead.""" # 'lhs' and 'rhs' are both registers with values stored in pocket, or only 'rhs' is and # 'lhs' is an actual constant (e.g. propagated there in previous loop iteration, when 'rhs' # was not yet propagated). if (self.matcher.match( code, type=self.BIN_OPS, lhs=self.CONST_OR_REG, rhs=Loc.reg(Loc.ANY)) and (code['lhs'].is_constant() or code['lhs'] in self.pocket) and code['rhs'] in self.pocket): debug('two-const operator %s at %d' % (CC._code_name(code['type']), pos)) if code['type'] == CC.BOOL_OP: cmp_fun = { 'sete': operator.eq, 'setne': operator.ne, 'setg': operator.gt, 'setge': operator.ge, 'setl': operator.lt, 'setle': operator.le, }[code['op']] # The operator.* bool functions return bool, we want an int to push. op_fun = lambda x, y: int(cmp_fun(x, y)) else: op_fun = { CC.ADD: operator.add, CC.SUB: operator.sub, CC.MUL: operator.mul, CC.DIV: operator.floordiv, CC.MOD: c_modulo }[code['type']] arg1 = int(self.pocket[code['rhs']].value) if code['lhs'].is_constant(): arg2 = int(code['lhs'].value) else: arg2 = int(self.pocket[code['lhs']].value) res_val = op_fun(arg1, arg2) debug(' -> args %d, %d res %d' % (arg1, arg2, res_val)) res_reg = code['dest'] if code['type'] in [CC.DIV, CC.MOD] else code['rhs'] # Delete and re-insert value, to maintain hash properties. if res_reg in self.pocket: del self.pocket[res_reg] self.pocket[res_reg] = Loc.const(res_val) self.mark_deleted(pos, comment=CC.S_PROPAGATED, value=res_val) self.prop_consts += 1 return True def _cp_apply_value_from_pocket(self, pos, code): """ Constant propagation: apply values from pocket. To be used before assigning new values, in case of e.g. [mov $1 %eax, mov %eax %edx].""" # Only attrs 'src', 'lhs' can carry a const value. for attr in [a for a in ['src', 'lhs'] if a in code.keys()]: if not code[attr].is_reg() or code[attr] not in self.pocket: continue debug('attr %s is reg %s at %d, applying %s from pocket' % ( attr, code[attr].value, pos, self.pocket[code[attr]].value)) code[attr] = self.pocket[code[attr]] self.prop_consts += 1 def _cp_drop_marked_registers(self, pos, code): """ Constant propagation: drop each register passed in drop_reg* attribute -- to be used when a code uses that register indirectly or just needs the value to be in register. """ to_apply = {} for attr, value in code.iteritems(): if attr.startswith('drop_reg') and value in self.pocket: to_apply[value] = self.pocket[value] if len(to_apply): self._add_to_apply_pocket(pos, to_apply) def _cp_overwrite_pocket_values(self, pos, code): """ Constant propagation: if a register is being assigned, delete its entry in pocket.""" # Only attrs modifying their location are 'dest', 'rhs'. # Note: rhs needs to be reviewed first, otherwise if rhs and dest are the same we # would forget the pocket value at dest, while it is still needed by rhs. for attr in [a for a in ['rhs', 'dest'] if a in code.keys()]: if not code[attr].is_reg() or code[attr] not in self.pocket: continue value = self.pocket[code[attr]].value # NEG is a special case here: 'dest' is both source and destination -- but the # value remains constant, so remove the code and adjust value in pocket. if code['type'] == CC.NEG: new_value = value[1:] if '-' in value else '-' + value debug('NEG reg %s with const at %d, adjusting pocket value to %s' % ( code[attr].value, pos, new_value)) # Delete and re-insert value, to maintain hash properties. loc = self.pocket[code[attr]] del self.pocket[code[attr]] loc.value = new_value self.pocket[code[attr]] = loc self.mark_deleted(pos, comment=CC.S_PROPAGATED) self.prop_consts += 1 return True else: # otherwise, just forget the register's value from pocket. # but the right operand for cmpl also needs to be dropped. debug('reg %s is %s at %d, forgetting from pocket' % ( code[attr].value, attr, pos)) if attr == 'rhs': debug(' ^ but applying reg %s' % code['rhs'].value) self._add_to_apply_pocket(pos, {code['rhs']: self.pocket[code['rhs']]}) del self.pocket[code[attr]] # Special case: division -- invalidate %eax and %edx values in pocket as idivl stores # results there. if code['type'] in [CC.DIV, CC.MOD]: for reg in [Loc.reg('a'), Loc.reg('d')]: if reg in self.pocket: del self.pocket[reg] def _cp_empty_pocket_if_needed(self, pos, code): """ Constant propagation: empty the pocket in case of calls or jumps. """ # [1] On function call, empty the pocket. if len(self.pocket) and code['type'] == CC.CALL: debug('function call at %d, emptying pocket' % pos) self.pocket = {} # [2] On function exit, drop %eax and empty pocket. if len(self.pocket) and code['type'] == CC.ENDFUNC: if Loc.reg('a') in self.pocket.keys(): self._add_to_apply_pocket(pos, {Loc.reg('a'): self.pocket[Loc.reg('a')]}) debug('ENDFUNC at %d, dropping reg a' % pos) self.pocket = {} # [3] On jump instructions (both in-/out-bound) assign the pocket values anyway. if len(self.pocket) and code['type'] in [CC.JUMP, CC.IF_JUMP, CC.LABEL]: debug('%s at %d, reassigning pocket values' % (CC._code_name(code['type']), pos)) # We can't insert into a list while iterating, so save the pocket for now # TODO we could later skip at least some of pocket's values, if we check that # a value is the same at label and all jumps to it, or the register is not live. self._add_to_apply_pocket(pos, self.pocket.copy()) self.pocket = {} def _cp_save_to_pocket(self, pos, code): """ Constant propagation: when moving const to a register, stow it in the pocket instead.""" if (self.matcher.match(code, type=CC.MOV, src=self.CONST_LOCS, dest=Loc.reg(Loc.ANY)) and not self.matcher.match(code, comment=CC.S_PROPAGATED)): debug('mov const %s -> reg %s found at %d' % (code['src'].value, code['dest'].value, pos)) self.pocket[code['dest']] = code['src'] self.mark_deleted(pos, comment=CC.S_PROPAGATED)
def gen_code(self, **kwargs): if self.fsym.cls: # calling a method, the called expr must be an ATTR for case in switch(self.children[0].type.type): if case(LP.ATTR): obj_expr = self.children[0].children[0] break if case(LP.IDENT): obj_expr = None break if case(): raise InternalError('expr of invalid type `%s` to call' % str(self.children[0].type)) else: obj_expr = None # [1] Compute memory usage for arguments. argmem = CC.var_size * self.arg_count total_argmem = argmem + (CC.var_size if obj_expr else 0) # [2] Push arguments. # Arguments need to be pushed in reverse order, but evaluated in normal order -- hence # we first make enough stack space for all of them and move them in the right place after # evaluation. if self.arg_count > 1: self.add_instr(CC.SUB, lhs=Loc.const(argmem), rhs=Loc.reg('top')) for i in xrange(len(self.children) - 1): # One less -- [0] is the function! self.add_child_by_idx(i + 1) # Leaves the value on stack. self.add_instr(CC.POP, dest=Loc.reg('a')) # i-th argument should be placed in 4*i(%esp) self.add_instr(CC.MOV, src=Loc.reg('a'), dest=Loc.mem(Loc.top, i * CC.var_size)) elif self.arg_count > 0: self.add_child_by_idx( 1) # With only one argument we can just push it on stack. # [2a] When calling a method, push reference to `self` (first function's argument). if obj_expr: self.add_child_code(obj_expr) # [3] Call and pop arguments. self.add_instr(CC.CALL, label=self.fsym.call_name()) if total_argmem > 0: self.add_instr(CC.ADD, lhs=Loc.const(total_argmem), rhs=Loc.reg('top')) # [4] finish depending on how we were called: if self.has_jump_codes(kwargs): if self.fsym.ret_type.type != LP.BOOLEAN: raise InternalError( 'jump-expr codes for non-bool function %s %s at %s!' % (self.fsym.full_name(), str(self.fsym), self.tree.pos)) # [4a] bool function as part of condition evaluation -- jump basing on the result # note: comparing with 0, so on equality jump to false! self.add_instr(CC.IF_JUMP, lhs=Loc.const(0), rhs=Loc.reg('a'), op='je', label=kwargs['on_false']) self.add_instr(CC.JUMP, label=kwargs['on_true']) else: # [4b] normal expression -- push the return value on stack if needed if self.fsym.ret_type.type != LP.VOID: self.add_instr(CC.PUSH, src=Loc.reg('a')) self.check_unused_result()
def gen_code(self, **kwargs): for case in switch(self.value_type.type): if case(LP.ARRAY): self.add_child_by_idx(0) # evaluate array size self.add_instr(CC.POP, dest=Loc.reg('a')) self.add_instr( CC.PUSH, src=Loc.reg('a')) # the value needs to be saved later # Convention: array of size N is a block of memory for (N+1) variables, and the # first variable will contain array's size ( = N) self.add_instr(CC.LEA, src=Loc.mem('', offset=CC.var_size, idx=Loc.reg_a, mult=CC.var_size), dest=Loc.reg('a'), drop_reg1=Loc.reg('a')) # calc memory size init_value = self.default_asm_value( self.value_type.type.subtype) debug('init value for %s:' % self.value_type, str(init_value)) self.add_instr(CC.PUSH, src=init_value) self.add_instr(CC.PUSH, src=Loc.reg('a')) self.add_instr(CC.CALL, label=Builtins.MALLOC_FUNCTION) self.add_instr(CC.ADD, lhs=Loc.const(2 * CC.var_size), rhs=Loc.reg('top')) # Write the array size into the first index. self.add_instr( CC.POP, dest=Loc.reg('d')) # load the array size saved earlier self.add_instr(CC.MOV, src=Loc.reg('d'), dest=Loc.mem(Loc.reg_a)) # Push the memory pointer as expression result. self.add_instr(CC.PUSH, src=Loc.reg('a')) break if case(LP.OBJECT): # Allocate required space. self.add_instr(CC.PUSH, src=Loc.const(0)) # Fill the memory with 0. # Allocate space for the whole object, along with superclass members. self.add_instr(CC.PUSH, src=Loc.const(self.cls.total_var_count * CC.var_size)) self.add_instr(CC.CALL, label=Builtins.MALLOC_FUNCTION) self.add_instr(CC.ADD, lhs=Loc.const(2 * CC.var_size), rhs=Loc.reg('top')) if self.cls.has_nonzero_initializers(): # Save %ebx to store the class base pointer there. self.add_instr(CC.PUSH, src=Loc.reg('b')) self.add_instr(CC.MOV, src=Loc.reg('a'), dest=Loc.reg('b')) # Assign the non-0 default values and specified initializations. old_instantiating_class = NewCode.instantiating_class NewCode.instantiating_class = (self.cls, Loc.reg('b')) self._add_nonzero_inits(self.cls) NewCode.instantiating_class = old_instantiating_class # Restore %ebx and push the object memory pointer as expression result. self.add_instr(CC.MOV, src=Loc.reg('b'), dest=Loc.reg('a')) self.add_instr(CC.POP, dest=Loc.reg('b')) self.add_instr(CC.PUSH, src=Loc.reg('a')) break if case(): raise InternalError('invalid type for new operator: ' + str(self.value_type)) self.check_unused_result()