| OLD | NEW |
| 1 # Copyright 2017 The Chromium Authors. All rights reserved. | 1 # Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 """Logic for diffing two SizeInfo objects.""" | 4 """Logic for diffing two SizeInfo objects.""" |
| 5 | 5 |
| 6 import collections | 6 import collections |
| 7 import re | 7 import re |
| 8 | 8 |
| 9 import models | 9 import models |
| 10 | 10 |
| (...skipping 24 matching lines...) Expand all Loading... |
| 35 # Use section rather than section_name since clang & gcc use | 35 # Use section rather than section_name since clang & gcc use |
| 36 # .data.rel.ro vs .data.rel.ro.local. | 36 # .data.rel.ro vs .data.rel.ro.local. |
| 37 if '.' not in name: | 37 if '.' not in name: |
| 38 return (symbol.section, name) | 38 return (symbol.section, name) |
| 39 | 39 |
| 40 # Compiler or Linker generated symbol. | 40 # Compiler or Linker generated symbol. |
| 41 name = re.sub(r'[.0-9]', '', name) # Strip out all numbers and dots. | 41 name = re.sub(r'[.0-9]', '', name) # Strip out all numbers and dots. |
| 42 return (symbol.section, name, symbol.object_path) | 42 return (symbol.section, name, symbol.object_path) |
| 43 | 43 |
| 44 | 44 |
| 45 def _CloneSymbol(sym, size): | |
| 46 """Returns a copy of |sym| with an updated |size|. | |
| 47 | |
| 48 Padding and aliases are not copied. | |
| 49 """ | |
| 50 return models.Symbol( | |
| 51 sym.section_name, size, address=sym.address, full_name=sym.full_name, | |
| 52 template_name=sym.template_name, name=sym.name, | |
| 53 source_path=sym.source_path, object_path=sym.object_path, flags=sym.flags) | |
| 54 | |
| 55 | |
| 56 def _CloneAlias(sym, diffed_alias): | |
| 57 """Returns a copy of |sym| and making it an alias of |diffed_alias|.""" | |
| 58 ret = _CloneSymbol(sym, diffed_alias.size_without_padding) | |
| 59 ret.padding = diffed_alias.padding | |
| 60 ret.aliases = diffed_alias.aliases | |
| 61 ret.aliases.append(ret) | |
| 62 return ret | |
| 63 | |
| 64 | |
| 65 def _DiffSymbol(before_sym, after_sym, diffed_symbol_by_after_aliases, | |
| 66 padding_by_section_name): | |
| 67 diffed_alias = None | |
| 68 if after_sym.aliases: | |
| 69 diffed_alias = diffed_symbol_by_after_aliases.get(id(after_sym.aliases)) | |
| 70 | |
| 71 if diffed_alias: | |
| 72 ret = _CloneAlias(after_sym, diffed_alias) | |
| 73 else: | |
| 74 size_diff = (after_sym.size_without_padding - | |
| 75 before_sym.size_without_padding) | |
| 76 ret = _CloneSymbol(after_sym, size_diff) | |
| 77 # Diffs are more stable when comparing size without padding, except when | |
| 78 # the symbol is a padding-only symbol. | |
| 79 if after_sym.size_without_padding == 0 and size_diff == 0: | |
| 80 ret.padding = after_sym.padding - before_sym.padding | |
| 81 else: | |
| 82 padding_diff = after_sym.padding - before_sym.padding | |
| 83 padding_by_section_name[ret.section_name] += padding_diff | |
| 84 | |
| 85 # If this is the first matched symbol of an alias group, initialize its | |
| 86 # aliases list. The remaining aliases will be appended when diff'ed. | |
| 87 if after_sym.aliases: | |
| 88 ret.aliases = [ret] | |
| 89 diffed_symbol_by_after_aliases[id(after_sym.aliases)] = ret | |
| 90 return ret | |
| 91 | |
| 92 | |
| 93 def _CloneUnmatched(after_symbols, diffed_symbol_by_after_aliases): | |
| 94 ret = [None] * len(after_symbols) | |
| 95 for i, sym in enumerate(after_symbols): | |
| 96 cloned = sym | |
| 97 if sym.aliases: | |
| 98 diffed_alias = diffed_symbol_by_after_aliases.get(id(sym.aliases)) | |
| 99 if diffed_alias: | |
| 100 # At least one alias was diffed. | |
| 101 cloned = _CloneAlias(sym, diffed_alias) | |
| 102 ret[i] = cloned | |
| 103 return ret | |
| 104 | |
| 105 | |
| 106 def _NegateAndClone(before_symbols, matched_before_aliases, | |
| 107 negated_symbol_by_before_aliases): | |
| 108 ret = [None] * len(before_symbols) | |
| 109 for i, sym in enumerate(before_symbols): | |
| 110 if sym.aliases: | |
| 111 negated_alias = negated_symbol_by_before_aliases.get(id(sym.aliases)) | |
| 112 if negated_alias: | |
| 113 cloned = _CloneAlias(sym, negated_alias) | |
| 114 else: | |
| 115 all_aliases_removed = id(sym.aliases) not in matched_before_aliases | |
| 116 # If all alises are removed, then given them negative size to reflect | |
| 117 # the savings. | |
| 118 if all_aliases_removed: | |
| 119 cloned = _CloneSymbol(sym, -sym.size_without_padding) | |
| 120 cloned.padding = -sym.padding | |
| 121 else: | |
| 122 # But if only a subset of aliases are removed, do not actually treat | |
| 123 # them as aliases anymore, or else they will weigh down the PSS of | |
| 124 # the symbols that were not removed. | |
| 125 cloned = _CloneSymbol(sym, 0) | |
| 126 cloned.aliases = [cloned] | |
| 127 negated_symbol_by_before_aliases[id(sym.aliases)] = cloned | |
| 128 else: | |
| 129 cloned = _CloneSymbol(sym, -sym.size_without_padding) | |
| 130 cloned.padding = -sym.padding | |
| 131 ret[i] = cloned | |
| 132 return ret | |
| 133 | |
| 134 | |
| 135 def _DiffSymbolGroups(before, after): | 45 def _DiffSymbolGroups(before, after): |
| 136 before_symbols_by_key = collections.defaultdict(list) | 46 before_symbols_by_key = collections.defaultdict(list) |
| 137 for s in before: | 47 for s in before: |
| 138 before_symbols_by_key[_SymbolKey(s)].append(s) | 48 before_symbols_by_key[_SymbolKey(s)].append(s) |
| 139 | 49 |
| 140 similar = [] | 50 delta_symbols = [] |
| 141 diffed_symbol_by_after_aliases = {} | 51 # For changed symbols, padding is zeroed out. In order to not lose the |
| 142 matched_before_aliases = set() | |
| 143 unmatched_after_syms = [] | |
| 144 # For similar symbols, padding is zeroed out. In order to not lose the | |
| 145 # information entirely, store it in aggregate. | 52 # information entirely, store it in aggregate. |
| 146 padding_by_section_name = collections.defaultdict(int) | 53 padding_by_section_name = collections.defaultdict(int) |
| 147 | 54 |
| 148 # Step 1: Create all delta symbols and record unmatched symbols. | 55 # Create a DeltaSymbol for each after symbol. |
| 149 for after_sym in after: | 56 for after_sym in after: |
| 150 matching_syms = before_symbols_by_key.get(_SymbolKey(after_sym)) | 57 matching_syms = before_symbols_by_key.get(_SymbolKey(after_sym)) |
| 58 before_sym = None |
| 151 if matching_syms: | 59 if matching_syms: |
| 152 before_sym = matching_syms.pop(0) | 60 before_sym = matching_syms.pop(0) |
| 153 if before_sym.IsGroup() and after_sym.IsGroup(): | 61 # Padding tracked in aggregate, except for padding-only symbols. |
| 154 similar.append(_DiffSymbolGroups(before_sym, after_sym)) | 62 if before_sym.size_without_padding: |
| 155 else: | 63 padding_by_section_name[before_sym.section_name] += ( |
| 156 if before_sym.aliases: | 64 after_sym.padding_pss - before_sym.padding_pss) |
| 157 matched_before_aliases.add(id(before_sym.aliases)) | 65 delta_symbols.append(models.DeltaSymbol(before_sym, after_sym)) |
| 158 similar.append( | |
| 159 _DiffSymbol(before_sym, after_sym, diffed_symbol_by_after_aliases, | |
| 160 padding_by_section_name)) | |
| 161 else: | |
| 162 unmatched_after_syms.append(after_sym) | |
| 163 continue | |
| 164 | 66 |
| 165 # Step 2: Copy symbols only in "after" (being careful with aliases). | 67 # Create a DeltaSymbol for each unmatched before symbol. |
| 166 added = _CloneUnmatched(unmatched_after_syms, diffed_symbol_by_after_aliases) | 68 for remaining_syms in before_symbols_by_key.itervalues(): |
| 69 for before_sym in remaining_syms: |
| 70 delta_symbols.append(models.DeltaSymbol(before_sym, None)) |
| 167 | 71 |
| 168 # Step 3: Negate symbols only in "before" (being careful with aliases). | 72 # Create a DeltaSymbol to represent the zero'd out padding of matched symbols. |
| 169 removed = [] | |
| 170 negated_symbol_by_before_aliases = {} | |
| 171 for remaining_syms in before_symbols_by_key.itervalues(): | |
| 172 removed.extend(_NegateAndClone(remaining_syms, matched_before_aliases, | |
| 173 negated_symbol_by_before_aliases)) | |
| 174 | |
| 175 # Step 4: Create ** symbols to represent padding differences. | |
| 176 for section_name, padding in padding_by_section_name.iteritems(): | 73 for section_name, padding in padding_by_section_name.iteritems(): |
| 177 if padding != 0: | 74 if padding != 0: |
| 178 similar.append(models.Symbol( | 75 after_sym = models.Symbol(section_name, padding, |
| 179 section_name, padding, | 76 name="** aggregate padding of diff'ed symbols") |
| 180 name="** aggregate padding of diff'ed symbols")) | 77 after_sym.padding = padding |
| 181 return models.SymbolDiff(added, removed, similar) | 78 delta_symbols.append(models.DeltaSymbol(None, after_sym)) |
| 79 |
| 80 return models.DeltaSymbolGroup(delta_symbols) |
| 182 | 81 |
| 183 | 82 |
| 184 def Diff(before, after): | 83 def Diff(before, after): |
| 185 """Diffs two SizeInfo objects. Returns a SizeInfoDiff.""" | 84 """Diffs two SizeInfo objects. Returns a DeltaSizeInfo.""" |
| 186 assert isinstance(before, models.SizeInfo) | 85 assert isinstance(before, models.SizeInfo) |
| 187 assert isinstance(after, models.SizeInfo) | 86 assert isinstance(after, models.SizeInfo) |
| 188 section_sizes = {k: after.section_sizes.get(k, 0) - v | 87 section_sizes = {k: after.section_sizes.get(k, 0) - v |
| 189 for k, v in before.section_sizes.iteritems()} | 88 for k, v in before.section_sizes.iteritems()} |
| 190 for k, v in after.section_sizes.iteritems(): | 89 for k, v in after.section_sizes.iteritems(): |
| 191 if k not in section_sizes: | 90 if k not in section_sizes: |
| 192 section_sizes[k] = v | 91 section_sizes[k] = v |
| 193 | 92 |
| 194 symbol_diff = _DiffSymbolGroups(before.raw_symbols, after.raw_symbols) | 93 symbol_diff = _DiffSymbolGroups(before.raw_symbols, after.raw_symbols) |
| 195 return models.SizeInfoDiff(section_sizes, symbol_diff, before.metadata, | 94 return models.DeltaSizeInfo(section_sizes, symbol_diff, before.metadata, |
| 196 after.metadata) | 95 after.metadata) |
| OLD | NEW |