Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | # Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file |
| 3 | # for details. All rights reserved. Use of this source code is governed by a |
| 4 | # BSD-style license that can be found in the LICENSE file. |
| 5 | |
| 6 | import argparse |
| 7 | import os |
| 8 | import subprocess |
| 9 | import sys |
| 10 | import time |
| 11 | |
| 12 | import jdk |
Ian Zerny | 859dd89 | 2020-07-03 11:19:03 +0200 | [diff] [blame] | 13 | import proguard |
Ian Zerny | 027c60f | 2020-07-03 07:53:10 +0200 | [diff] [blame] | 14 | import toolhelper |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 15 | import utils |
| 16 | |
Ian Zerny | 859dd89 | 2020-07-03 11:19:03 +0200 | [diff] [blame] | 17 | SHRINKERS = ['r8'] + proguard.getVersions() |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 18 | |
| 19 | INPUT_PROGRAM = utils.PINNED_R8_JAR |
| 20 | |
| 21 | ANNO = 'com.android.tools.r8.com.google.common.annotations.VisibleForTesting' |
| 22 | |
| 23 | R8_OPTIONS = [ |
| 24 | 'printTimes', |
| 25 | 'passthroughDexCode', |
| 26 | 'enableClassMerging', |
| 27 | 'enableDevirtualization', |
| 28 | 'enableNonNullTracking', |
| 29 | 'enableInlining', |
| 30 | 'enableSwitchMapRemoval', |
| 31 | 'enableValuePropagation', |
| 32 | 'useSmaliSyntax', |
| 33 | 'verbose', |
| 34 | 'quiet', |
| 35 | 'invalidDebugInfoFatal', |
| 36 | 'intermediate', |
| 37 | 'enableLambdaMerging', |
| 38 | 'enableDesugaring', |
| 39 | 'enableMainDexListCheck', |
| 40 | 'enableTreeShaking', |
| 41 | 'printCfg', |
| 42 | 'ignoreMissingClasses', |
| 43 | 'forceProguardCompatibility', |
| 44 | 'enableMinification', |
| 45 | 'disableAssertions', |
| 46 | 'debugKeepRules', |
| 47 | 'debug', |
| 48 | 'minimalMainDex', |
| 49 | 'skipReadingDexCode', |
| 50 | ] |
| 51 | |
| 52 | R8_CLASSES = [ |
| 53 | 'com.android.tools.r8.code.Format11x', |
| 54 | 'com.android.tools.r8.code.MoveFrom16', |
| 55 | 'com.android.tools.r8.code.AddLong2Addr', |
| 56 | 'com.android.tools.r8.code.AgetByte', |
| 57 | 'com.android.tools.r8.code.SubDouble', |
| 58 | 'com.android.tools.r8.code.Sput', |
| 59 | 'com.android.tools.r8.code.Format10x', |
| 60 | 'com.android.tools.r8.code.RemInt', |
| 61 | 'com.android.tools.r8.code.ConstWide', |
| 62 | 'com.android.tools.r8.code.SgetWide', |
| 63 | 'com.android.tools.r8.code.OrInt2Addr', |
| 64 | 'com.android.tools.r8.code.Iget', |
| 65 | 'com.android.tools.r8.code.Instruction', |
| 66 | 'com.android.tools.r8.code.SubInt2Addr', |
| 67 | 'com.android.tools.r8.code.SwitchPayload', |
| 68 | 'com.android.tools.r8.code.Const4', |
| 69 | 'com.android.tools.r8.code.ShrIntLit8', |
| 70 | 'com.android.tools.r8.code.ConstWide16', |
| 71 | 'com.android.tools.r8.code.NegInt', |
| 72 | 'com.android.tools.r8.code.SgetBoolean', |
| 73 | 'com.android.tools.r8.code.Format22x', |
| 74 | 'com.android.tools.r8.code.InvokeVirtualRange', |
| 75 | 'com.android.tools.r8.code.Format45cc', |
| 76 | 'com.android.tools.r8.code.DivFloat2Addr', |
| 77 | 'com.android.tools.r8.code.MulIntLit16', |
| 78 | 'com.android.tools.r8.code.BytecodeStream', |
| 79 | ] |
| 80 | |
| 81 | KEEP_MAIN = \ |
| 82 | '-keep class com.android.tools.r8.R8 { void main(java.lang.String[]); }' |
| 83 | |
| 84 | BENCHMARKS = [ |
| 85 | # Baseline compile just keeps R8.main (implicitly kept for all benchmarks). |
| 86 | ('KeepBaseline', ''), |
| 87 | |
| 88 | # Mirror default keep getters/setters, but independent of hierarchy. |
| 89 | ('KeepGetters', |
| 90 | '-keepclassmembers class * { *** get*(); }'), |
| 91 | ('KeepGettersIf', |
| 92 | '-if class * { *** get*(); } -keep class <1> { *** get<2>(); }'), |
| 93 | |
| 94 | # Mirror default keep getters/setters below View (here a class with a B). |
| 95 | ('KeepSubGetters', |
| 96 | '-keepclassmembers class * extends **.*B* { *** get*(); }'), |
| 97 | ('KeepSubGettersIf', |
| 98 | '-if class * extends **.*B* -keep class <1> { *** get*(); }'), |
| 99 | |
| 100 | # General keep rule to keep annotated members. |
| 101 | ('KeepAnnoMethod', |
| 102 | '-keepclasseswithmembers class * { @%s *** *(...); }' % ANNO), |
| 103 | ('KeepAnnoMethodCond', |
| 104 | '-keepclassmembers class * { @%s *** *(...); }' % ANNO), |
| 105 | ('KeepAnnoMethodIf', |
| 106 | '-if class * { @%s *** *(...); } -keep class <1> { @%s *** <2>(...); }' \ |
| 107 | % (ANNO, ANNO)), |
| 108 | |
| 109 | # Large collection of rules mirroring AAPT conditional rules on R fields. |
| 110 | ('KeepAaptFieldIf', |
| 111 | '\n'.join([ |
| 112 | '-if class **.InternalOptions { boolean %s; }' |
| 113 | ' -keep class %s { <init>(...); }' % (f, c) |
| 114 | for (f, c) in zip(R8_OPTIONS, R8_CLASSES) * 1 #100 |
| 115 | ])), |
| 116 | |
| 117 | # If rules with predicates that will never by true, but will need |
| 118 | # consideration. The CodeSize of these should be equal to the baseline run. |
| 119 | ('KeepIfNonExistingClass', |
| 120 | '-if class **.*A*B*C*D*E*F* -keep class %s' % ANNO), |
| 121 | ('KeepIfNonExistingMember', |
| 122 | '-if class **.*A* { *** *a*b*c*d*e*f*(...); } -keep class %s' % ANNO) |
| 123 | ] |
| 124 | |
| 125 | def parse_arguments(argv): |
| 126 | parser = argparse.ArgumentParser( |
| 127 | description = 'Run keep-rule benchmarks.') |
| 128 | parser.add_argument('--golem', |
| 129 | help = 'Link in third party dependencies.', |
| 130 | default = False, |
| 131 | action = 'store_true') |
| 132 | parser.add_argument('--ignore-java-version', |
| 133 | help='Do not check java version', |
| 134 | default=False, |
| 135 | action='store_true') |
| 136 | parser.add_argument('--shrinker', |
| 137 | help='The shrinker to use', |
| 138 | choices=SHRINKERS, |
| 139 | default=SHRINKERS[0]) |
| 140 | parser.add_argument('--runs', |
| 141 | help='Number of runs to average out time on', |
| 142 | type=int, |
| 143 | default=3) |
| 144 | parser.add_argument('--benchmark', |
| 145 | help='Benchmark to run (default all)', |
| 146 | choices=map(lambda (x,y): x, BENCHMARKS), |
| 147 | default=None) |
| 148 | options = parser.parse_args(argv) |
| 149 | return options |
| 150 | |
| 151 | class BenchmarkResult: |
| 152 | def __init__(self, name, size, runs): |
| 153 | self.name = name |
| 154 | self.size = size |
| 155 | self.runs = runs |
| 156 | |
Ian Zerny | 859dd89 | 2020-07-03 11:19:03 +0200 | [diff] [blame] | 157 | def isPG(shrinker): |
| 158 | return proguard.isValidVersion(shrinker) |
| 159 | |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 160 | def shrinker_args(shrinker, keepfile, output): |
| 161 | if shrinker == 'r8': |
| 162 | return [ |
| 163 | jdk.GetJavaExecutable(), |
| 164 | '-cp', utils.R8LIB_JAR, |
| 165 | 'com.android.tools.r8.R8', |
| 166 | INPUT_PROGRAM, |
| 167 | '--lib', utils.RT_JAR, |
| 168 | '--output', output, |
Ian Zerny | 027c60f | 2020-07-03 07:53:10 +0200 | [diff] [blame] | 169 | '--min-api', '10000', |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 170 | '--pg-conf', keepfile, |
| 171 | ] |
Ian Zerny | 859dd89 | 2020-07-03 11:19:03 +0200 | [diff] [blame] | 172 | elif isPG(shrinker): |
| 173 | return proguard.getCmd([ |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 174 | '-injars', INPUT_PROGRAM, |
| 175 | '-libraryjars', utils.RT_JAR, |
| 176 | '-outjars', output, |
| 177 | '-dontwarn', '**', |
| 178 | '-optimizationpasses', '2', |
| 179 | '@' + keepfile, |
Ian Zerny | 859dd89 | 2020-07-03 11:19:03 +0200 | [diff] [blame] | 180 | ], |
| 181 | version=shrinker) |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 182 | else: |
| 183 | assert False, "Unexpected shrinker " + shrinker |
| 184 | |
Ian Zerny | 027c60f | 2020-07-03 07:53:10 +0200 | [diff] [blame] | 185 | def dex(input, output): |
| 186 | toolhelper.run( |
| 187 | 'd8', |
| 188 | [ |
| 189 | input, |
| 190 | '--lib', utils.RT_JAR, |
| 191 | '--min-api', '10000', |
| 192 | '--output', output |
| 193 | ], |
| 194 | build=False, |
| 195 | debug=False) |
| 196 | |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 197 | def run_shrinker(options, temp): |
| 198 | benchmarks = BENCHMARKS |
| 199 | if options.benchmark: |
| 200 | for (name, rules) in BENCHMARKS: |
| 201 | if name == options.benchmark: |
| 202 | benchmarks = [(name, rules)] |
| 203 | break |
| 204 | assert len(benchmarks) == 1, "Unexpected benchmark " + options.benchmark |
| 205 | |
| 206 | run_count = options.runs |
| 207 | benchmark_results = [] |
| 208 | for (name, rule) in benchmarks: |
| 209 | benchmark_keep = os.path.join(temp, '%s-keep.txt' % name) |
| 210 | with open(benchmark_keep, 'w') as fp: |
| 211 | fp.write(KEEP_MAIN) |
| 212 | fp.write('\n') |
| 213 | fp.write(rule) |
| 214 | |
| 215 | benchmark_runs = [] |
| 216 | benchmark_size = 0 |
| 217 | for i in range(run_count): |
| 218 | out = os.path.join(temp, '%s-out%d.jar' % (name, i)) |
| 219 | cmd = shrinker_args(options.shrinker, benchmark_keep, out) |
| 220 | utils.PrintCmd(cmd) |
| 221 | t0 = time.time() |
| 222 | subprocess.check_output(cmd) |
| 223 | t1 = time.time() |
| 224 | benchmark_runs.append(t1 - t0) |
Ian Zerny | 859dd89 | 2020-07-03 11:19:03 +0200 | [diff] [blame] | 225 | if isPG(options.shrinker): |
Ian Zerny | 027c60f | 2020-07-03 07:53:10 +0200 | [diff] [blame] | 226 | dexout = os.path.join(temp, '%s-out%d-dex.jar' % (name, i)) |
| 227 | dex(out, dexout) |
| 228 | benchmark_size = utils.uncompressed_size(dexout) |
| 229 | else: |
| 230 | benchmark_size = utils.uncompressed_size(out) |
Ian Zerny | 7012a72 | 2020-07-01 20:16:53 +0200 | [diff] [blame] | 231 | benchmark_results.append( |
| 232 | BenchmarkResult(name, benchmark_size, benchmark_runs)) |
| 233 | |
| 234 | print 'Runs:', options.runs |
| 235 | for result in benchmark_results: |
| 236 | benchmark_avg = sum(result.runs) / run_count |
| 237 | print '%s(CodeSize): %d' % (result.name, result.size) |
| 238 | print '%s(RunTimeRaw): %d ms' % (result.name, 1000.0 * benchmark_avg) |
| 239 | |
| 240 | if __name__ == '__main__': |
| 241 | options = parse_arguments(sys.argv[1:]) |
| 242 | if options.golem: |
| 243 | golem.link_third_party() |
| 244 | if not options.ignore_java_version: |
| 245 | utils.check_java_version() |
| 246 | with utils.TempDir() as temp: |
| 247 | run_shrinker(options, temp) |