blob: 0a7e0633206b7de790a9122095f9ad2dc4cefb32 [file] [log] [blame]
#!/usr/bin/env python
# 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('--golem',
help = 'Link in third party dependencies.',
default = False,
action = 'store_true')
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 options.golem:
golem.link_third_party()
if not options.ignore_java_version:
utils.check_java_version()
with utils.TempDir() as temp:
run_shrinker(options, temp)