| #!/usr/bin/env python3 | |
| # Copyright (c) 2022, the R8 project authors. Please see the AUTHORS file | |
| # for details. All rights reserved. Use of this source code is governed by a | |
| # BSD-style license that can be found in the LICENSE file. | |
| import argparse | |
| import sys | |
| COMPANION_CLASS_SUFFIX = '$-CC' | |
| EXTERNAL_SYNTHETIC_SUFFIX = '$$ExternalSynthetic' | |
| SYNTHETIC_PREFIX = 'S' | |
| # Parses a list of class and method descriptors, prefixed with one or more flags | |
| # 'H' (hot), 'S' (startup), 'P' (post startup). | |
| # | |
| # Example: | |
| # | |
| # HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V | |
| # HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I | |
| # HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V | |
| # PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V | |
| # HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I | |
| # Landroidx/compose/runtime/ComposerImpl; | |
| # | |
| # See also https://developer.android.com/studio/profile/baselineprofiles. | |
| def parse_art_profile(lines): | |
| art_profile = {} | |
| flags_to_name = {'H': 'hot', 'S': 'startup', 'P': 'post_startup'} | |
| for line in lines: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| flags = {'hot': False, 'startup': False, 'post_startup': False} | |
| while line[0] in flags_to_name: | |
| flag_abbreviation = line[0] | |
| flag_name = flags_to_name.get(flag_abbreviation) | |
| flags[flag_name] = True | |
| line = line[1:] | |
| while line.startswith('['): | |
| line = line[1:] | |
| assert line.startswith('L'), line | |
| descriptor = line | |
| art_profile[descriptor] = flags | |
| return art_profile | |
| def transform_art_profile_to_r8_startup_list(art_profile, | |
| generalize_synthetics=False): | |
| r8_startup_list = {} | |
| for startup_descriptor, flags in art_profile.items(): | |
| transformed_startup_descriptor = transform_synthetic_descriptor( | |
| startup_descriptor) if generalize_synthetics else startup_descriptor | |
| r8_startup_list[transformed_startup_descriptor] = { | |
| 'conditional_startup': False, | |
| 'hot': flags['hot'], | |
| 'startup': flags['startup'], | |
| 'post_startup': flags['post_startup'] | |
| } | |
| return r8_startup_list | |
| def transform_synthetic_descriptor(descriptor): | |
| companion_class_index = descriptor.find(COMPANION_CLASS_SUFFIX) | |
| if companion_class_index >= 0: | |
| return SYNTHETIC_PREFIX + descriptor[0:companion_class_index] + ';' | |
| external_synthetic_index = descriptor.find(EXTERNAL_SYNTHETIC_SUFFIX) | |
| if external_synthetic_index >= 0: | |
| return SYNTHETIC_PREFIX + descriptor[0:external_synthetic_index] + ';' | |
| return descriptor | |
| def filter_r8_startup_list(r8_startup_list, options): | |
| filtered_r8_startup_list = {} | |
| for startup_descriptor, flags in r8_startup_list.items(): | |
| if not options.include_post_startup \ | |
| and flags.get('post_startup') \ | |
| and not flags.get('startup'): | |
| continue | |
| filtered_r8_startup_list[startup_descriptor] = flags | |
| return filtered_r8_startup_list | |
| def parse_options(argv): | |
| result = argparse.ArgumentParser( | |
| description='Utilities for converting an ART profile into an R8 startup ' | |
| 'list.') | |
| result.add_argument('--art-profile', help='Path to the ART profile') | |
| result.add_argument( | |
| '--include-post-startup', | |
| help='Include post startup classes and methods in the R8 ' | |
| 'startup list', | |
| action='store_true', | |
| default=False) | |
| result.add_argument('--out', help='Where to store the R8 startup list') | |
| options, args = result.parse_known_args(argv) | |
| return options, args | |
| def main(argv): | |
| (options, args) = parse_options(argv) | |
| with open(options.art_profile, 'r') as f: | |
| art_profile = parse_art_profile(f.read().splitlines()) | |
| r8_startup_list = transform_art_profile_to_r8_startup_list(art_profile) | |
| filtered_r8_startup_list = filter_r8_startup_list(r8_startup_list, options) | |
| if options.out is not None: | |
| with open(options.out, 'w') as f: | |
| for startup_descriptor, flags in filtered_r8_startup_list.items(): | |
| f.write(startup_descriptor) | |
| f.write('\n') | |
| else: | |
| for startup_descriptor, flags in filtered_r8_startup_list.items(): | |
| print(startup_descriptor) | |
| if __name__ == '__main__': | |
| sys.exit(main(sys.argv[1:])) |