'Find unused registers throughout the function, and use those registers to replace stack variables.
Only functions that satisfy the following criteria can be optimized in this way:
- The function does not call any other function.
- The function does not use esp to index any stack variable.
- Prologue and epilogue of the function is identifiable.
- At least one register is not used in the entire function.
:param angr.knowledge.Function function:
:param networkx.MultiDiGraph data_graph:
:return: None'

def _register_reallocation(self, function, data_graph):

    if function.callout_sites:
        return
    if (len(function.endpoints) != 1):
        return
    startpoint_block = self.project.factory.block(function.startpoint.addr).capstone
    startpoint_insns = startpoint_block.insns
    if (len(startpoint_insns) < 3):
        return
    (insn0, insn1, insn2) = startpoint_insns[:3]
    if (not ((insn0.mnemonic == 'push') and (insn0.op_str == 'ebp'))):
        return
    if (not ((insn1.mnemonic == 'mov') and (insn1.op_str == 'ebp,    esp'))):
        return
    if ((not ((insn2.mnemonic == 'sub') and re.match('esp,    [0-9a-fx]+', insn2.op_str))) and (not ((insn2.mnemonic == 'push') and (insn2.op_str == 'eax')))):
        return
    endpoint_block = self.project.factory.block(function.endpoints[0].addr).capstone
    endpoint_insns = endpoint_block.insns
    if (len(endpoint_insns) < 3):
        return
    (insn3, insn4, insn5) = endpoint_insns[(-3):]
    if (not ((insn3.mnemonic == 'add') and re.match('esp,    [0-9a-fx]+', insn3.op_str))):
        return
    if (not ((insn4.mnemonic == 'pop') and (insn4.op_str == 'ebp'))):
        return
    if (not (insn5.mnemonic == 'ret')):
        return
    esp_offset = self.project.arch.registers['esp'][0]
    ebp_offset = self.project.arch.registers['ebp'][0]
    esp_variables = []
    for n in data_graph.nodes_iter():
        if (isinstance(n.variable, SimRegisterVariable) and (n.variable.reg == esp_offset)):
            esp_variables.append(n)
    call_insns = set()
    for (src, dst, data) in function.transition_graph.edges_iter(data=True):
        if (('type' in data) and (data['type'] == 'call')):
            src_block = function._get_block(src.addr)
            call_insns.add(src_block.instruction_addrs[(-1)])
    esp_insns = set((n.location.ins_addr for n in esp_variables))
    if (esp_insns != ({insn0.address, insn2.address, insn3.address, insn4.address, insn5.address} | call_insns)):
        return
    prologue_addr = insn0.address
    prologue_size = ((insn0.size + insn1.size) + insn2.size)
    epilogue_addr = insn3.address
    epilogue_size = ((insn3.size + insn4.size) + insn5.size)
    for (src, dst, data) in data_graph.edges_iter(data=True):
        if (isinstance(dst.variable, SimRegisterVariable) and (dst.variable.reg != ebp_offset) and (dst.variable.reg < 40)):
            if (isinstance(src.variable, SimRegisterVariable) and (src.variable.reg == ebp_offset)):
                l.debug('Found    a    lea    operation    from    ebp    at    %#x.    Function    %s    cannot    be    optimized.', dst.location.ins_addr, repr(function))
                return
    for node in data_graph.nodes_iter():
        if (isinstance(node.variable, SimRegisterVariable) and (72 <= node.variable.reg < 288)):
            l.debug('Found    a    float-point/SSE    register    access    at    %#x.    Function    %s    cannot    be    optimized.', node.location.ins_addr, repr(function))
            return
    l.debug('RegisterReallocation:    function    %s    satisfies    the    criteria.', repr(function))
    used_general_registers = set()
    for n in data_graph.nodes_iter():
        if isinstance(n.variable, SimRegisterVariable):
            if (n.variable.reg < 40):
                used_general_registers.add(n.variable.reg)
    registers = self.project.arch.registers
    all_general_registers = {registers['ebx'][0], registers['edi'][0], registers['esi'][0], registers['esp'][0], registers['ebp'][0]}
    unused_general_registers = (all_general_registers - used_general_registers)
    if (not unused_general_registers):
        l.debug('RegisterReallocation:    function    %s    does    not    have    any    free    register.', repr(function))
        return
    l.debug('RegisterReallocation:    function    %s    has    %d    free    register(s):    %s', repr(function), len(unused_general_registers), ',    '.join([self.project.arch.register_names[u] for u in unused_general_registers]))
    stack_variables = set()
    for n in data_graph.nodes_iter():
        if (isinstance(n.variable, SimStackVariable) and (n.variable.base == 'bp') and (n.variable.size == 4) and (n.variable.offset < 0)):
            stack_variables.add(n)
    filtered_stack_variables = set()
    for stack_variable in stack_variables:
        failed = False
        in_edges = data_graph.in_edges(stack_variable, data=True)
        for (src, _, data) in in_edges:
            if (('type' in data) and (data['type'] == 'mem_addr')):
                if (isinstance(src.variable, SimRegisterVariable) and (src.variable.reg == ebp_offset)):
                    pass
                elif isinstance(src.variable, SimConstantVariable):
                    pass
                else:
                    failed = True
                    break
            if (isinstance(src.variable, SimRegisterVariable) and (src.variable.reg >= 72)):
                failed = True
                break
        if failed:
            continue
        out_edges = data_graph.out_edges(stack_variable, data=True)
        for (_, dst, data) in out_edges:
            if (('type' in data) and (data['type'] == 'kill')):
                continue
            if (isinstance(dst.variable, SimRegisterVariable) and (dst.variable.reg >= 72)):
                failed = True
                break
        if failed:
            continue
        filtered_stack_variables.add(stack_variable)
    stack_variable_to_degree = defaultdict(int)
    stack_variable_sources = defaultdict(list)
    for sv in filtered_stack_variables:
        stack_variable_to_degree[sv.variable] += data_graph.in_degree(sv)
        stack_variable_to_degree[sv.variable] += data_graph.out_degree(sv)
        stack_variable_sources[sv.variable].append(sv)
    sorted_stack_variables = sorted(stack_variable_to_degree.keys(), key=(lambda sv: stack_variable_to_degree[sv]), reverse=True)
    for (reg, sv) in zip(unused_general_registers, sorted_stack_variables):
        non_initial_sources = [src for src in stack_variable_sources[sv] if (not src.initial)]
        if (not non_initial_sources):
            continue
        consumers = set()
        for src in stack_variable_sources[sv]:
            out_edges = data_graph.out_edges(src, data=True)
            for (_, dst, data) in out_edges:
                if (('type' not in data) or (data['type'] != 'kill')):
                    consumers.add(dst)
        rr = RegisterReallocation(sv, SimRegisterVariable(reg, 4), non_initial_sources, list(consumers), prologue_addr, prologue_size, epilogue_addr, epilogue_size)
        self.register_reallocations.append(rr)
        l.debug('RegisterReallocation:    %s    will    replace    %s    in    function    %s.', rr.register_variable, rr.stack_variable, repr(function))
