def _get_global_spotmap(self): """Generate global spotmap and free values. Returns a tuple. First element is a dictionary mapping ILValue to spot for spots which do not need register allocation, like static variables or literals. The second element is a list of the free values; the variables which were not mapped in the global spotmap. """ global_spotmap = {} free_values = [] all_values = self._all_il_values() string_literal_number = 0 for value in all_values: if value in self.il_code.literals: # If literal, assign it a preassigned literal spot s = LiteralSpot(self.il_code.literals[value]) global_spotmap[value] = s elif value in self.il_code.externs: # If extern, assign assign spot and add the extern to asm code s = MemSpot(self.il_code.externs[value]) global_spotmap[value] = s self.asm_code.add_extern(self.il_code.externs[value]) elif value in self.il_code.string_literals: # Add the string literal representation to the output ASM. name = f"__strlit{string_literal_number}" string_literal_number += 1 self.asm_code.add_string_literal( name, self.il_code.string_literals[value]) global_spotmap[value] = MemSpot(name) elif (self.arguments.variables_on_stack and value in self.il_code.variables): # pragma: no cover # If all variables are allocated on the stack self.offset += value.ctype.size s = MemSpot(spots.RBP, -self.offset) global_spotmap[value] = s else: # Value is free and needs an assignment free_values.append(value) return global_spotmap, free_values
def make_asm(self, spotmap, home_spots, get_reg, asm_code): # noqa D102 addr_spot = spotmap[self.addr] value_spot = spotmap[self.val] addr_r = get_reg([], [value_spot]) self.move(addr_spot, addr_r, asm_code) indir_spot = MemSpot(addr_r) self.move(value_spot, indir_spot, asm_code)
def make_asm(self, spotmap, home_spots, get_reg, asm_code): # noqa D102 addr_spot = spotmap[self.addr] output_spot = spotmap[self.output] addr_r = get_reg([], [output_spot]) self.move(addr_spot, addr_r, asm_code) indir_spot = MemSpot(addr_r) self.move(indir_spot, output_spot, asm_code)
def _get_nondynamic_spot(self, v, num): """Get a spot for non-dynamic values. In particular, assigns a spot to all literals, string literals, variables with no storage, and variables with static storage. v - value to get a spot for, or None if the value goes in a dynamic spot like a register nnum - positive integer guaranteed never to be the same for two distinct calls to this function """ EXTERNAL = self.symbol_table.EXTERNAL INTERNAL = self.symbol_table.INTERNAL TENTATIVE = self.symbol_table.TENTATIVE if v in self.il_code.literals: return LiteralSpot(self.il_code.literals[v]) elif v in self.il_code.string_literals: name = f"__strlit{num}" self.asm_code.add_string_literal(name, self.il_code.string_literals[v]) return MemSpot(name) # Values with no storage can be referenced directly by name elif not self.symbol_table.storage.get(v, True): return MemSpot(self.symbol_table.names[v]) elif self.symbol_table.storage.get(v) == self.symbol_table.STATIC: name = self.symbol_table.names[v] if self.symbol_table.linkage_type.get(v) != EXTERNAL: name = f"{name}.{num}" if self.symbol_table.def_state.get(v) == TENTATIVE: local = (self.symbol_table.linkage_type[v] == INTERNAL) self.asm_code.add_comm(name, v.ctype.size, local) else: init_val = self.il_code.static_inits.get(v, 0) self.asm_code.add_data(name, v.ctype.size, init_val) return MemSpot(name)
def make_asm(self, spotmap, home_spots, get_reg, asm_code): # noqa D102 addr_spot = spotmap[self.addr] value_spot = spotmap[self.val] if isinstance(addr_spot, RegSpot): addr_r = addr_spot else: addr_r = get_reg([], [value_spot]) asm_code.add(asm_cmds.Mov(addr_r, addr_spot, 8)) indir_spot = MemSpot(addr_r) if isinstance(value_spot, RegSpot): temp_reg = value_spot else: temp_reg = get_reg([], [addr_r]) self.move_data(indir_spot, value_spot, self.val.ctype.size, temp_reg, asm_code)
def _make_asm(self, commands, global_spotmap): """Generate ASM code for given command list.""" # Get free values free_values = self._get_free_values(commands, global_spotmap) #print("\nfree values", free_values) #pprint.pprint(commands) # If any variable may have its address referenced, assign it a # permanent memory spot if it doesn't yet have one. move_to_mem = [] moveAllToMem = False if moveAllToMem: # test where we move all variables to memory for v in free_values: move_to_mem.append(v) #print(v, " was moved to mem because of test") else: for command in commands: refs = command.references().values() for line in refs: for v in line: if v not in refs: move_to_mem.append(v) #print(v, " was moved to mem") # In addition, move all IL values of strange size to memory because # they won't fit in a register. for v in free_values: if v.ctype.size > 4: move_to_mem.append(v) #print(v, " was moved to mem because of size") # (Not really relevant for B322) # Shivy-todo: All non-free IL values are automatically assigned distinct # memory spots. However, this is very inoptimal for structs. # Consider the following C code, where S is already declared: # # struct S array[10]; # s = array[1]; # # This code compiles to the following IL: # # READAT(array, 1) -> X # SET(X) -> s # # However, X is an unnecessary copy of `s` in memory. Ideally, # the register allocator will recognize that X is just a temporary # and assign X to the same memory location as s to avoid additional # copy operations and memory usage. This also requires that the # relevant IL commands check whether the two arguments are in the # same spot before trying to do a copy. for v in move_to_mem: if v in free_values: self.offset += v.ctype.size global_spotmap[v] = MemSpot(spots.RBP, -self.offset) free_values.remove(v) # pprint.pprint(free_values) # Perform liveliness analysis live_vars = self._get_live_vars(commands, free_values) #print(live_vars) # Generate conflict and preference graph g_bak = self._generate_graph(commands, free_values, live_vars) spilled_nodes = [] while True: g = g_bak.copy() # Remove all nodes that have been spilled for this iteration for n in spilled_nodes: g.pop(n) removed_nodes = [] merged_nodes = {} # Repeat simplification, coalescing, and freeze until freeze # does not work. while True: # Repeat simplification and coalescing until nothing # happens. while True: simplified = self._simplify_all(removed_nodes, g) merged = self._coalesce_all(merged_nodes, g) if not simplified and not merged: break if not self._freeze(g): break # If no nodes remain, we are done if not g.nodes(): break # If nodes do remain, spill one of them and retry else: # Spill node with highest number of conflicts. This node # will never be a merged node because we merge nodes # conservatively, so any recently merged node can be # simplified immediately. n = max(g.nodes(), key=lambda n: len(g.confs(n))) spilled_nodes.append(n) # Move any remaining nodes from graph into removed_nodes # This accounts for pseudonodes which cannot be removed in the # simplify phase. while g.all_nodes(): removed_nodes.append(g.pop(g.all_nodes()[0])) #print(removed_nodes) # Pop values off the stack to generate spot assignments. spotmap = self._generate_spotmap(removed_nodes, merged_nodes, g_bak) #print(spotmap) # Assign stack values to the spilled nodes for v in spilled_nodes: self.offset += v.ctype.size spotmap[v] = MemSpot(spots.RBP, -self.offset) # Merge global spotmap into this spotmap for v in global_spotmap: spotmap[v] = global_spotmap[v] if self.arguments.show_reg_alloc_perf: # pragma: no cover total_prefs = 0 matched_prefs = 0 for n1, n2 in itertools.combinations(g_bak.all_nodes(), 2): if n2 in g_bak.prefs(n1): total_prefs += 1 if spotmap[n1] == spotmap[n2]: matched_prefs += 1 print("total prefs", total_prefs) print("matched prefs", matched_prefs) print("total ILValues", len(g_bak.nodes())) print("register ILValues", len(g_bak.nodes()) - len(spilled_nodes)) # Generate assembly code self._generate_asm(commands, live_vars, spotmap)