Index: tools/binary_size/libsupersize/diff.py |
diff --git a/tools/binary_size/libsupersize/diff.py b/tools/binary_size/libsupersize/diff.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3b523b7f9f25df4853cd7109c2595f7339609a9f |
--- /dev/null |
+++ b/tools/binary_size/libsupersize/diff.py |
@@ -0,0 +1,188 @@ |
+# Copyright 2017 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+"""Logic for diffing two SizeInfo objects.""" |
+ |
+import collections |
+ |
+import models |
+ |
+ |
+def _CloneSymbol(sym, size): |
+ """Returns a copy of |sym| with an updated |size|. |
+ |
+ Padding and aliases are not copied. |
+ """ |
+ return models.Symbol( |
+ sym.section_name, size, address=sym.address, name=sym.name, |
+ source_path=sym.source_path, object_path=sym.object_path, |
+ full_name=sym.full_name, flags=sym.flags) |
+ |
+ |
+def _CloneAlias(sym, diffed_alias): |
+ """Returns a copy of |sym| and making it an alias of |diffed_alias|.""" |
+ ret = _CloneSymbol(sym, diffed_alias.size_without_padding) |
+ ret.padding = diffed_alias.padding |
+ ret.aliases = diffed_alias.aliases |
+ ret.aliases.append(ret) |
+ return ret |
+ |
+ |
+def _DiffSymbol(before_sym, after_sym, diffed_symbol_by_after_aliases, |
+ padding_by_section_name): |
+ diffed_alias = None |
+ if after_sym.aliases: |
+ diffed_alias = diffed_symbol_by_after_aliases.get(id(after_sym.aliases)) |
+ |
+ if diffed_alias: |
+ ret = _CloneAlias(after_sym, diffed_alias) |
+ else: |
+ size_diff = (after_sym.size_without_padding - |
+ before_sym.size_without_padding) |
+ ret = _CloneSymbol(after_sym, size_diff) |
+ # Diffs are more stable when comparing size without padding, except when |
+ # the symbol is a padding-only symbol. |
+ if after_sym.size_without_padding == 0 and size_diff == 0: |
+ ret.padding = after_sym.padding - before_sym.padding |
+ else: |
+ padding_diff = after_sym.padding - before_sym.padding |
+ padding_by_section_name[ret.section_name] += padding_diff |
+ |
+ # If this is the first matched symbol of an alias group, initialize its |
+ # aliases list. The remaining aliases will be appended when diff'ed. |
+ if after_sym.aliases: |
+ ret.aliases = [ret] |
+ diffed_symbol_by_after_aliases[id(after_sym.aliases)] = ret |
+ return ret |
+ |
+ |
+def _CloneUnmatched(after_symbols, diffed_symbol_by_after_aliases): |
+ ret = [] |
+ for sym in after_symbols: |
+ cloned = sym |
+ if sym.IsGroup(): |
+ cloned = models.SymbolDiff( |
+ [], _CloneUnmatched(sym, diffed_symbol_by_after_aliases), [], |
+ name=sym.name, full_name=sym.full_name, section_name=sym.section_name) |
+ elif sym.aliases: |
+ diffed_alias = diffed_symbol_by_after_aliases.get(id(sym.aliases)) |
+ if diffed_alias: |
+ # At least one alias was diffed. |
+ cloned = _CloneAlias(sym, diffed_alias) |
+ ret.append(cloned) |
+ return ret |
+ |
+ |
+def _NegateAndClone(before_symbols, matched_before_aliases, |
+ negated_symbol_by_before_aliases): |
+ ret = [] |
+ for sym in before_symbols: |
+ if sym.IsGroup(): |
+ cloned = models.SymbolDiff( |
+ [], _NegateAndClone(sym, matched_before_aliases, |
+ negated_symbol_by_before_aliases), [], |
+ name=sym.name, full_name=sym.full_name, section_name=sym.section_name) |
+ else: |
+ negated_alias = None |
+ if sym.aliases: |
+ negated_alias = negated_symbol_by_before_aliases.get(id(sym.aliases)) |
+ if negated_alias: |
+ cloned = _CloneAlias(sym, negated_alias) |
+ else: |
+ all_aliases_removed = id(sym.aliases) not in matched_before_aliases |
+ # If all alises are removed, then given them negative size to reflect |
+ # the savings. |
+ if all_aliases_removed: |
+ cloned = _CloneSymbol(sym, -sym.size_without_padding) |
+ cloned.padding = -sym.padding |
+ else: |
+ # But if only a subset of aliases are removed, do not actually treat |
+ # them as aliases anymore, or else they will weigh down the PSS of |
+ # the symbols that were not removed. |
+ cloned = _CloneSymbol(sym, 0) |
+ cloned.aliases = [cloned] |
+ negated_symbol_by_before_aliases[id(sym.aliases)] = cloned |
+ else: |
+ cloned = _CloneSymbol(sym, -sym.size_without_padding) |
+ cloned.padding = -sym.padding |
+ ret.append(cloned) |
+ return ret |
+ |
+ |
+# TODO(agrieve): Diff logic does not work correctly when aliased symbols span |
+# mulitple groups. We should simplify by not allowing recursion (allow diffs |
+# only on SizeInfo, apply logic to raw_symbols, re-cluster after-the-fact. |
+def _DiffSymbolGroups(before, after): |
+ before_symbols_by_key = collections.defaultdict(list) |
+ for s in before: |
+ before_symbols_by_key[s._Key()].append(s) |
+ |
+ similar = [] |
+ diffed_symbol_by_after_aliases = {} |
+ matched_before_aliases = set() |
+ unmatched_after_syms = [] |
+ # For similar symbols, padding is zeroed out. In order to not lose the |
+ # information entirely, store it in aggregate. |
+ padding_by_section_name = collections.defaultdict(int) |
+ |
+ # Step 1: Create all delta symbols and record unmatched symbols. |
+ for after_sym in after: |
+ matching_syms = before_symbols_by_key.get(after_sym._Key()) |
+ if matching_syms: |
+ before_sym = matching_syms.pop(0) |
+ if before_sym.IsGroup() and after_sym.IsGroup(): |
+ similar.append(_DiffSymbolGroups(before_sym, after_sym)) |
+ else: |
+ if before_sym.aliases: |
+ matched_before_aliases.add(id(before_sym.aliases)) |
+ similar.append( |
+ _DiffSymbol(before_sym, after_sym, diffed_symbol_by_after_aliases, |
+ padding_by_section_name)) |
+ else: |
+ unmatched_after_syms.append(after_sym) |
+ continue |
+ |
+ # Step 2: Copy symbols only in "after" (being careful with aliases). |
+ added = _CloneUnmatched(unmatched_after_syms, diffed_symbol_by_after_aliases) |
+ |
+ # Step 3: Negate symbols only in "before" (being careful with aliases). |
+ removed = [] |
+ negated_symbol_by_before_aliases = {} |
+ for remaining_syms in before_symbols_by_key.itervalues(): |
+ removed.extend(_NegateAndClone(remaining_syms, matched_before_aliases, |
+ negated_symbol_by_before_aliases)) |
+ |
+ # Step 4: Create ** symbols to represent padding differences. |
+ for section_name, padding in padding_by_section_name.iteritems(): |
+ if padding != 0: |
+ similar.append(models.Symbol( |
+ section_name, padding, |
+ name="** aggregate padding of diff'ed symbols")) |
+ return models.SymbolDiff( |
+ added, removed, similar, name=after.name, full_name=after.full_name, |
+ section_name=after.section_name) |
+ |
+ |
+def Diff(before, after): |
+ """Diffs two SizeInfo or SymbolGroup objects. |
+ |
+ When diffing SizeInfos, a SizeInfoDiff is returned. |
+ When diffing SymbolGroups, a SymbolDiff is returned. |
+ |
+ Returns: |
+ Returns a SizeInfo when args are of type SizeInfo. |
+ Returns a SymbolDiff when args are of type SymbolGroup. |
+ """ |
+ if isinstance(after, models.SizeInfo): |
+ assert isinstance(before, models.SizeInfo) |
+ section_sizes = {k: after.section_sizes[k] - v |
+ for k, v in before.section_sizes.iteritems()} |
+ symbol_diff = _DiffSymbolGroups(before.symbols, after.symbols) |
+ return models.SizeInfoDiff(section_sizes, symbol_diff, before.metadata, |
+ after.metadata) |
+ |
+ assert (isinstance(after, models.SymbolGroup) and |
+ isinstance(before, models.SymbolGroup)) |
+ return _DiffSymbolGroups(before, after) |
+ |
+ |