| #!/usr/bin/env python3 |
| # Copyright (c) 2020, 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 os |
| import subprocess |
| import sys |
| import time |
| |
| import jdk |
| import proguard |
| import toolhelper |
| import utils |
| |
| SHRINKERS = ['r8'] + proguard.getVersions() |
| |
| INPUT_PROGRAM = utils.PINNED_R8_JAR |
| |
| ANNO = 'com.android.tools.r8.com.google.common.annotations.VisibleForTesting' |
| |
| R8_OPTIONS = [ |
| 'printTimes', |
| 'passthroughDexCode', |
| 'enableClassMerging', |
| 'enableDevirtualization', |
| 'enableNonNullTracking', |
| 'enableInlining', |
| 'enableSwitchMapRemoval', |
| 'enableValuePropagation', |
| 'useSmaliSyntax', |
| 'verbose', |
| 'quiet', |
| 'invalidDebugInfoFatal', |
| 'intermediate', |
| 'enableLambdaMerging', |
| 'enableDesugaring', |
| 'enableMainDexListCheck', |
| 'enableTreeShaking', |
| 'printCfg', |
| 'ignoreMissingClasses', |
| 'forceProguardCompatibility', |
| 'enableMinification', |
| 'disableAssertions', |
| 'debugKeepRules', |
| 'debug', |
| 'minimalMainDex', |
| 'skipReadingDexCode', |
| ] |
| |
| R8_CLASSES = [ |
| 'com.android.tools.r8.code.Format11x', |
| 'com.android.tools.r8.code.MoveFrom16', |
| 'com.android.tools.r8.code.AddLong2Addr', |
| 'com.android.tools.r8.code.AgetByte', |
| 'com.android.tools.r8.code.SubDouble', |
| 'com.android.tools.r8.code.Sput', |
| 'com.android.tools.r8.code.Format10x', |
| 'com.android.tools.r8.code.RemInt', |
| 'com.android.tools.r8.code.ConstWide', |
| 'com.android.tools.r8.code.SgetWide', |
| 'com.android.tools.r8.code.OrInt2Addr', |
| 'com.android.tools.r8.code.Iget', |
| 'com.android.tools.r8.code.Instruction', |
| 'com.android.tools.r8.code.SubInt2Addr', |
| 'com.android.tools.r8.code.SwitchPayload', |
| 'com.android.tools.r8.code.Const4', |
| 'com.android.tools.r8.code.ShrIntLit8', |
| 'com.android.tools.r8.code.ConstWide16', |
| 'com.android.tools.r8.code.NegInt', |
| 'com.android.tools.r8.code.SgetBoolean', |
| 'com.android.tools.r8.code.Format22x', |
| 'com.android.tools.r8.code.InvokeVirtualRange', |
| 'com.android.tools.r8.code.Format45cc', |
| 'com.android.tools.r8.code.DivFloat2Addr', |
| 'com.android.tools.r8.code.MulIntLit16', |
| 'com.android.tools.r8.code.BytecodeStream', |
| ] |
| |
| KEEP_MAIN = \ |
| '-keep class com.android.tools.r8.R8 { void main(java.lang.String[]); }' |
| |
| BENCHMARKS = [ |
| # Baseline compile just keeps R8.main (implicitly kept for all benchmarks). |
| ('KeepBaseline', ''), |
| |
| # Mirror default keep getters/setters, but independent of hierarchy. |
| ('KeepGetters', |
| '-keepclassmembers class * { *** get*(); }'), |
| ('KeepGettersIf', |
| '-if class * { *** get*(); } -keep class <1> { *** get<2>(); }'), |
| |
| # Mirror default keep getters/setters below View (here a class with a B). |
| ('KeepSubGetters', |
| '-keepclassmembers class * extends **.*B* { *** get*(); }'), |
| ('KeepSubGettersIf', |
| '-if class * extends **.*B* -keep class <1> { *** get*(); }'), |
| |
| # General keep rule to keep annotated members. |
| ('KeepAnnoMethod', |
| '-keepclasseswithmembers class * { @%s *** *(...); }' % ANNO), |
| ('KeepAnnoMethodCond', |
| '-keepclassmembers class * { @%s *** *(...); }' % ANNO), |
| ('KeepAnnoMethodIf', |
| '-if class * { @%s *** *(...); } -keep class <1> { @%s *** <2>(...); }' \ |
| % (ANNO, ANNO)), |
| |
| # Large collection of rules mirroring AAPT conditional rules on R fields. |
| ('KeepAaptFieldIf', |
| '\n'.join([ |
| '-if class **.InternalOptions { boolean %s; }' |
| ' -keep class %s { <init>(...); }' % (f, c) |
| for (f, c) in zip(R8_OPTIONS, R8_CLASSES) * 1 #100 |
| ])), |
| |
| # If rules with predicates that will never by true, but will need |
| # consideration. The CodeSize of these should be equal to the baseline run. |
| ('KeepIfNonExistingClass', |
| '-if class **.*A*B*C*D*E*F* -keep class %s' % ANNO), |
| ('KeepIfNonExistingMember', |
| '-if class **.*A* { *** *a*b*c*d*e*f*(...); } -keep class %s' % ANNO) |
| ] |
| |
| def parse_arguments(argv): |
| parser = argparse.ArgumentParser( |
| description = 'Run keep-rule benchmarks.') |
| parser.add_argument('--ignore-java-version', |
| help='Do not check java version', |
| default=False, |
| action='store_true') |
| parser.add_argument('--shrinker', |
| help='The shrinker to use', |
| choices=SHRINKERS, |
| default=SHRINKERS[0]) |
| parser.add_argument('--runs', |
| help='Number of runs to average out time on', |
| type=int, |
| default=3) |
| parser.add_argument('--benchmark', |
| help='Benchmark to run (default all)', |
| choices=map(lambda (x,y): x, BENCHMARKS), |
| default=None) |
| options = parser.parse_args(argv) |
| return options |
| |
| class BenchmarkResult: |
| def __init__(self, name, size, runs): |
| self.name = name |
| self.size = size |
| self.runs = runs |
| |
| def isPG(shrinker): |
| return proguard.isValidVersion(shrinker) |
| |
| def shrinker_args(shrinker, keepfile, output): |
| if shrinker == 'r8': |
| return [ |
| jdk.GetJavaExecutable(), |
| '-cp', utils.R8LIB_JAR, |
| 'com.android.tools.r8.R8', |
| INPUT_PROGRAM, |
| '--lib', utils.RT_JAR, |
| '--output', output, |
| '--min-api', '10000', |
| '--pg-conf', keepfile, |
| ] |
| elif isPG(shrinker): |
| return proguard.getCmd([ |
| '-injars', INPUT_PROGRAM, |
| '-libraryjars', utils.RT_JAR, |
| '-outjars', output, |
| '-dontwarn', '**', |
| '-optimizationpasses', '2', |
| '@' + keepfile, |
| ], |
| version=shrinker) |
| else: |
| assert False, "Unexpected shrinker " + shrinker |
| |
| def dex(input, output): |
| toolhelper.run( |
| 'd8', |
| [ |
| input, |
| '--lib', utils.RT_JAR, |
| '--min-api', '10000', |
| '--output', output |
| ], |
| build=False, |
| debug=False) |
| |
| def run_shrinker(options, temp): |
| benchmarks = BENCHMARKS |
| if options.benchmark: |
| for (name, rules) in BENCHMARKS: |
| if name == options.benchmark: |
| benchmarks = [(name, rules)] |
| break |
| assert len(benchmarks) == 1, "Unexpected benchmark " + options.benchmark |
| |
| run_count = options.runs |
| benchmark_results = [] |
| for (name, rule) in benchmarks: |
| benchmark_keep = os.path.join(temp, '%s-keep.txt' % name) |
| with open(benchmark_keep, 'w') as fp: |
| fp.write(KEEP_MAIN) |
| fp.write('\n') |
| fp.write(rule) |
| |
| benchmark_runs = [] |
| benchmark_size = 0 |
| for i in range(run_count): |
| out = os.path.join(temp, '%s-out%d.jar' % (name, i)) |
| cmd = shrinker_args(options.shrinker, benchmark_keep, out) |
| utils.PrintCmd(cmd) |
| t0 = time.time() |
| subprocess.check_output(cmd) |
| t1 = time.time() |
| benchmark_runs.append(t1 - t0) |
| if isPG(options.shrinker): |
| dexout = os.path.join(temp, '%s-out%d-dex.jar' % (name, i)) |
| dex(out, dexout) |
| benchmark_size = utils.uncompressed_size(dexout) |
| else: |
| benchmark_size = utils.uncompressed_size(out) |
| benchmark_results.append( |
| BenchmarkResult(name, benchmark_size, benchmark_runs)) |
| |
| print 'Runs:', options.runs |
| for result in benchmark_results: |
| benchmark_avg = sum(result.runs) / run_count |
| print '%s(CodeSize): %d' % (result.name, result.size) |
| print '%s(RunTimeRaw): %d ms' % (result.name, 1000.0 * benchmark_avg) |
| |
| if __name__ == '__main__': |
| options = parse_arguments(sys.argv[1:]) |
| if not options.ignore_java_version: |
| utils.check_java_version() |
| with utils.TempDir() as temp: |
| run_shrinker(options, temp) |