blob: 0a7e0633206b7de790a9122095f9ad2dc4cefb32 [file] [log] [blame]
Ian Zerny7012a722020-07-01 20:16:53 +02001#!/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
6import argparse
7import os
8import subprocess
9import sys
10import time
11
12import jdk
Ian Zerny859dd892020-07-03 11:19:03 +020013import proguard
Ian Zerny027c60f2020-07-03 07:53:10 +020014import toolhelper
Ian Zerny7012a722020-07-01 20:16:53 +020015import utils
16
Ian Zerny859dd892020-07-03 11:19:03 +020017SHRINKERS = ['r8'] + proguard.getVersions()
Ian Zerny7012a722020-07-01 20:16:53 +020018
19INPUT_PROGRAM = utils.PINNED_R8_JAR
20
21ANNO = 'com.android.tools.r8.com.google.common.annotations.VisibleForTesting'
22
23R8_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
52R8_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
81KEEP_MAIN = \
82 '-keep class com.android.tools.r8.R8 { void main(java.lang.String[]); }'
83
84BENCHMARKS = [
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
125def 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
151class BenchmarkResult:
152 def __init__(self, name, size, runs):
153 self.name = name
154 self.size = size
155 self.runs = runs
156
Ian Zerny859dd892020-07-03 11:19:03 +0200157def isPG(shrinker):
158 return proguard.isValidVersion(shrinker)
159
Ian Zerny7012a722020-07-01 20:16:53 +0200160def 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 Zerny027c60f2020-07-03 07:53:10 +0200169 '--min-api', '10000',
Ian Zerny7012a722020-07-01 20:16:53 +0200170 '--pg-conf', keepfile,
171 ]
Ian Zerny859dd892020-07-03 11:19:03 +0200172 elif isPG(shrinker):
173 return proguard.getCmd([
Ian Zerny7012a722020-07-01 20:16:53 +0200174 '-injars', INPUT_PROGRAM,
175 '-libraryjars', utils.RT_JAR,
176 '-outjars', output,
177 '-dontwarn', '**',
178 '-optimizationpasses', '2',
179 '@' + keepfile,
Ian Zerny859dd892020-07-03 11:19:03 +0200180 ],
181 version=shrinker)
Ian Zerny7012a722020-07-01 20:16:53 +0200182 else:
183 assert False, "Unexpected shrinker " + shrinker
184
Ian Zerny027c60f2020-07-03 07:53:10 +0200185def 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 Zerny7012a722020-07-01 20:16:53 +0200197def 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 Zerny859dd892020-07-03 11:19:03 +0200225 if isPG(options.shrinker):
Ian Zerny027c60f2020-07-03 07:53:10 +0200226 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 Zerny7012a722020-07-01 20:16:53 +0200231 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
240if __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)