Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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