def trivial_dce_pass(func): """Remove instructions from `func` that are never used as arguments to any other function. Return a bool indicating whether we deleted anything. """ blocks = list(form_blocks(func['instrs'])) # Find all the variables used as an argument to any instruction, # even once. used = set() for block in blocks: for instr in block: # Mark all the variable arguments as used. used.update(var_args(instr)) # Delete the instructions that write to unused variables. changed = False for block in blocks: # Avoid deleting *effect instructions* that do not produce a # result. The `'dest' in i` predicate is true for all the *value # functions*, which are pure and can be eliminated if their # results are never used. new_block = [i for i in block if 'dest' not in i or i['dest'] in used] # Record whether we deleted anything. changed |= len(new_block) != len(block) # Replace the block with the filtered one. block[:] = new_block # Reassemble the function. func['instrs'] = flatten(blocks) return changed
def backward_use(out, block, in_): ret = [out] used = out for i in block[-1::-1]: if 'dest' in i: used.discard(i['dest']) used.update(v for v in var_args(i)) ret.append(set(used)) assert in_ == used return ret
def use(block): """Variables that are read before they are written in the block. """ defined = set() # Locally defined. used = set() for i in block: used.update(v for v in var_args(i) if v not in defined) if 'dest' in i: defined.add(i['dest']) return used
def read_first(instrs): """Given a block of instructions, return a set of variable names that are read before they are written. """ read = set() written = set() for instr in instrs: read.update(set(var_args(instr)) - written) if 'dest' in instr: written.add(instr['dest']) return read
def reg_alloc_block(block, regs): rrindex = 0 var2reg = {} new_block = [] for instr in block: if 'dest' in instr: new_block, rrindex, new_var = eval_var(new_block, instr['dest'], regs, var2reg, rrindex) instr.update({ 'dest': new_var, }) args = var_args(instr) new_vars = [] for arg in args: new_block, rrindex, new_var = eval_var(new_block, arg, regs, var2reg, rrindex) new_vars.append(new_var) instr.update({ 'args': new_vars, }) new_block.append(instr) #print(new_block) return new_block
def drop_killed_local(block): """Delete instructions in a single block whose result is unused before the next assignment. Return a bool indicating whether anything changed. """ # A map from variable names to the last place they were assigned # since the last use. These are candidates for deletion---if a # variable is assigned while in this map, we'll delete what the maps # point to. last_def = {} # Find the indices of droppable instructions. to_drop = set() for i, instr in enumerate(block): # Check for uses. Anything we use is no longer a candidate for # deletion. for var in var_args(instr): if var in last_def: del last_def[var] # Check for definitions. This *has* to happen after the use # check, so we don't count "a = a + 1" as killing a before using # it. if 'dest' in instr: dest = instr['dest'] if dest in last_def: # Another definition since the most recent use. Drop the # last definition. to_drop.add(last_def[dest]) last_def[dest] = i # Remove the instructions marked for deletion. new_block = [instr for i, instr in enumerate(block) if i not in to_drop] changed = len(new_block) != len(block) block[:] = new_block return changed
def lvn_block(block, lookup, canonicalize, fold): """Use local value numbering to optimize a basic block. Modify the instructions in place. You can extend the basic LVN algorithm to bring interesting language semantics with these functions: - `lookup`. Arguments: a value-to-number map and a value. Return the corresponding number (or None if it does not exist). - `canonicalize`. Argument: a value. Returns an equivalent value in a canonical form. - `fold`. Arguments: a number-to-constant map and a value. Return a new constant if it can be computed directly (or None otherwise). """ # The current value of every defined variable. We'll update this # every time a variable is modified. Different variables can have # the same value number (if they represent identical computations). var2num = Numbering() # The canonical variable holding a given value. Every time we're # forced to compute a new value, we'll keep track of it here so we # can reuse it later. value2num = {} # The *canonical* variable name holding a given numbered value. # There is only one canonical variable per value number (so this is # not the inverse of var2num). num2var = {} # Track constant values for values assigned with `const`. num2const = {} # Initialize the table with numbers for input variables. These # variables are their own canonical source. for var in read_first(block): num = var2num.add(var) num2var[num] = var for instr, last_write in zip(block, last_writes(block)): # Look up the value numbers for all variable arguments, # generating new numbers for unseen variables. argvars = var_args(instr) argnums = tuple(var2num[var] for var in argvars) # Value operations are candidates for replacement. val = None if 'dest' in instr and 'args' in instr: # Construct a Value for this computation. val = canonicalize(Value(instr['op'], argnums)) # Is this value already available? num = lookup(value2num, val) if num is not None: # Mark this variable as containing the value. var2num[instr['dest']] = num # Replace the instruction with a copy or a constant. if num in num2const: # Value is a constant. instr.update({ 'op': 'const', 'value': num2const[num], }) del instr['args'] else: # Value is in a variable. instr.update({ 'op': 'id', 'args': [num2var[num]], }) continue # If this instruction produces a result, give it a number. if 'dest' in instr: newnum = var2num.add(instr['dest']) # Record constant values. if instr['op'] == 'const': num2const[newnum] = instr['value'] if last_write: # Preserve the variable name for other blocks. var = instr['dest'] else: # We must put the value in a new variable so it can be # reused by another computation in the feature (in case # the current variable name is reassigned before then). var = 'lvn.{}'.format(newnum) # Record the variable name and update the instruction. num2var[newnum] = var instr['dest'] = var if val: # Is this value foldable to a constant? const = fold(num2const, val) if const: num2const[newnum] = const instr.update({ 'op': 'const', 'value': const, }) del instr['args'] continue # If not, record the new variable as the canonical # source for the newly computed value. value2num[val] = newnum # Update argument variable names to canonical variables. if 'args' in instr: new_args = [num2var[n] for n in argnums] if instr['op'] not in TERMINATORS: instr['args'] = new_args elif instr['op'] == 'br': instr['args'] += instr['args'][1:] + new_args