blob: 32c754e9d099786c7fe2d942a444a64572be1a0e [file] [log] [blame]
// Copyright (c) 2018, 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.
package com.android.tools.r8.regress.b111250398;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.code.Iget;
import com.android.tools.r8.code.IgetObject;
import com.android.tools.r8.code.Sget;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexField;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.FieldSubject;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
import com.google.common.collect.ImmutableList;
import java.util.Arrays;
import org.junit.Test;
// Copy of javax.inject.Provider.
interface Provider<T> {
T get();
}
// Copy of dagger.internal.SingleClass.
final class SingleCheck<T> implements Provider<T> {
private static final Object UNINITIALIZED = new Object();
private volatile Provider<T> provider;
private volatile Object instance = UNINITIALIZED;
private SingleCheck(Provider<T> provider) {
assert provider != null;
this.provider = provider;
}
@SuppressWarnings("unchecked") // cast only happens when result comes from the delegate provider
@Override
public T get() {
Object local = instance;
if (local == UNINITIALIZED) {
// provider is volatile and might become null after the check, so retrieve the provider first
Provider<T> providerReference = provider;
if (providerReference == null) {
// The provider was null, so the instance must already be set
local = instance;
} else {
local = providerReference.get();
instance = local;
// Null out the reference to the provider. We are never going to need it again, so we can
// make it eligible for GC.
provider = null;
}
}
return (T) local;
}
// This method is not relevant for the test.
/*
public static <P extends Provider<T>, T> Provider<T> provider(P provider) {
// If a scoped @Binds delegates to a scoped binding, don't cache the value again.
if (provider instanceof SingleCheck || provider instanceof DoubleCheck) {
return provider;
}
return new SingleCheck<T>(checkNotNull(provider));
}
*/
}
// Several field gets on non-volatile and volatile fields on the same class.
class A {
int t;
int f;
static int sf;
volatile int v;
static volatile int sv;
public void mf() {
t = f;
t = f;
t = f;
t = f;
t = f;
}
public void mfWithMonitor() {
t = f;
synchronized (this) {
t = f;
t = f;
}
t = f;
t = f;
}
public void msf() {
t = sf;
t = sf;
t = sf;
t = sf;
t = sf;
}
public void mv() {
t = v;
t = v;
t = v;
t = v;
t = v;
}
public void msv() {
t = sv;
t = sv;
t = sv;
t = sv;
t = sv;
}
}
// Several field gets on non-volatile and volatile fields on different class.
class B {
int t;
public void mf(A a) {
t = a.f;
t = a.f;
t = a.f;
t = a.f;
t = a.f;
}
public void msf() {
t = A.sf;
t = A.sf;
t = A.sf;
t = A.sf;
t = A.sf;
}
public void mv(A a) {
t = a.v;
t = a.v;
t = a.v;
t = a.v;
t = a.v;
}
public void msv() {
t = A.sv;
t = A.sv;
t = A.sv;
t = A.sv;
t = A.sv;
}
}
// Modified sample from http://tutorials.jenkov.com/java-concurrency/volatile.html.
class C {
private int years;
private int months;
private volatile int days;
public int totalDays() {
int total = this.days;
total += months * 30;
total += years * 365;
return total;
}
public int totalDaysTimes2() {
int total = this.days;
total += months * 30;
total += years * 365;
total += this.days;
total += months * 30;
total += years * 365;
return total;
}
public int totalDaysTimes3() {
int total = this.days;
total += months * 30;
total += years * 365;
total += this.days;
total += months * 30;
total += years * 365;
total += this.days;
total += months * 30;
total += years * 365;
return total;
}
public void update(int years, int months, int days){
this.years = years;
this.months = months;
this.days = days;
}
}
public class B111250398 extends TestBase {
private void releaseMode(InternalOptions options) {
options.debug = false;
}
private long countIget(DexCode code, DexField field) {
return Arrays.stream(code.instructions)
.filter(instruction -> instruction instanceof Iget)
.map(instruction -> (Iget) instruction)
.filter(get -> get.getField() == field)
.count();
}
private long countSget(DexCode code, DexField field) {
return Arrays.stream(code.instructions)
.filter(instruction -> instruction instanceof Sget)
.map(instruction -> (Sget) instruction)
.filter(get -> get.getField() == field)
.count();
}
private long countIgetObject(MethodSubject method, FieldSubject field) {
return Arrays.stream(method.getMethod().getCode().asDexCode().instructions)
.filter(instruction -> instruction instanceof IgetObject)
.map(instruction -> (IgetObject) instruction)
.filter(get -> get.getField() == field.getField().field)
.count();
}
private void check(CodeInspector inspector, int mfOnBGets, int msfOnBGets) {
ClassSubject classA = inspector.clazz(A.class);
assertThat(classA, isPresent());
MethodSubject mfOnA = classA.method("void", "mf", ImmutableList.of());
assertThat(mfOnA, isPresent());
MethodSubject mfWithMonitorOnA = classA.method("void", "mfWithMonitor", ImmutableList.of());
assertThat(mfWithMonitorOnA, isPresent());
MethodSubject msfOnA = classA.method("void", "msf", ImmutableList.of());
assertThat(msfOnA, isPresent());
MethodSubject mvOnA = classA.method("void", "mv", ImmutableList.of());
assertThat(mvOnA, isPresent());
MethodSubject msvOnA = classA.method("void", "msv", ImmutableList.of());
assertThat(msvOnA, isPresent());
FieldSubject fOnA = classA.field("int", "f");
assertThat(fOnA, isPresent());
FieldSubject sfOnA = classA.field("int", "sf");
assertThat(sfOnA, isPresent());
FieldSubject vOnA = classA.field("int", "v");
assertThat(vOnA, isPresent());
FieldSubject svOnA = classA.field("int", "sv");
assertThat(svOnA, isPresent());
ClassSubject classB = inspector.clazz(B.class);
assertThat(classB, isPresent());
MethodSubject mfOnB = classB.method("void", "mf", ImmutableList.of(classA.getOriginalName()));
assertThat(mfOnB, isPresent());
MethodSubject msfOnB = classB.method("void", "msf", ImmutableList.of());
assertThat(msfOnB, isPresent());
MethodSubject mvOnB = classB.method("void", "mv", ImmutableList.of(classA.getOriginalName()));
assertThat(mvOnB, isPresent());
MethodSubject msvOnB = classB.method("void", "msv", ImmutableList.of());
assertThat(msvOnB, isPresent());
// Field load of volatile fields are never eliminated.
assertEquals(5, countIget(mvOnA.getMethod().getCode().asDexCode(), vOnA.getField().field));
assertEquals(5, countSget(msvOnA.getMethod().getCode().asDexCode(), svOnA.getField().field));
assertEquals(5, countIget(mvOnB.getMethod().getCode().asDexCode(), vOnA.getField().field));
assertEquals(5, countSget(msvOnB.getMethod().getCode().asDexCode(), svOnA.getField().field));
// For fields on the same class both separate compilation (D8) and whole program
// compilation (R8) will eliminate field loads on non-volatile fields.
assertEquals(1, countIget(mfOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
assertEquals(1, countSget(msfOnA.getMethod().getCode().asDexCode(), sfOnA.getField().field));
// TODO(111380066). This could be 2 in stead of 4, but right now the optimization tracks the
// combined set of fields for all successors, and for synchronized code all blocks have
// exceptional edges for ensuring monitor exit causing the active load to be invalidated for
// both normal and exceptional successors.
assertEquals(4,
countIget(mfWithMonitorOnA.getMethod().getCode().asDexCode(), fOnA.getField().field));
// For fields on other class both separate compilation (D8) and whole program
// compilation (R8) will differ in the eliminated field loads of non-volatile fields.
assertEquals(mfOnBGets,
countIget(mfOnB.getMethod().getCode().asDexCode(), fOnA.getField().field));
assertEquals(msfOnBGets,
countSget(msfOnB.getMethod().getCode().asDexCode(), sfOnA.getField().field));
}
@Test
public void testSeparateCompilation() throws Exception {
CodeInspector inspector =
new CodeInspector(compileWithD8(readClasses(A.class, B.class), this::releaseMode));
check(inspector, 5, 5);
}
@Test
public void testWholeProgram() throws Exception {
CodeInspector inspector =
new CodeInspector(compileWithR8(readClasses(A.class, B.class), this::releaseMode));
check(inspector, 1, 1);
}
private void checkMixed(AndroidApp app) throws Exception{
CodeInspector inspector = new CodeInspector(app);
ClassSubject classC = inspector.clazz(C.class);
assertThat(classC, isPresent());
MethodSubject totalDays = classC.method("int", "totalDays", ImmutableList.of());
assertThat(totalDays, isPresent());
MethodSubject totalDaysTimes2 = classC.method("int", "totalDaysTimes2", ImmutableList.of());
assertThat(totalDaysTimes2, isPresent());
MethodSubject totalDaysTimes3 = classC.method("int", "totalDaysTimes3", ImmutableList.of());
assertThat(totalDaysTimes3, isPresent());
FieldSubject years = classC.field("int", "years");
assertThat(years, isPresent());
FieldSubject months = classC.field("int", "months");
assertThat(months, isPresent());
FieldSubject days = classC.field("int", "days");
assertThat(days, isPresent());
for (FieldSubject field : new FieldSubject[]{years, months, days}) {
assertEquals(1,
countIget(totalDays.getMethod().getCode().asDexCode(), field.getField().field));
assertEquals(2,
countIget(totalDaysTimes2.getMethod().getCode().asDexCode(), field.getField().field));
assertEquals(3,
countIget(totalDaysTimes3.getMethod().getCode().asDexCode(), field.getField().field));
}
}
@Test
public void testMixedVolatileNonVolatile() throws Exception {
AndroidApp app = readClasses(C.class);
checkMixed(compileWithD8(app, this::releaseMode));
checkMixed(compileWithR8(app, this::releaseMode));
}
private void checkDaggerSingleProviderGet(AndroidApp app) throws Exception {
CodeInspector inspector = new CodeInspector(app);
MethodSubject get =
inspector.clazz(SingleCheck.class).method("java.lang.Object", "get", ImmutableList.of());
assertThat(get, isPresent());
FieldSubject instance =
inspector.clazz(SingleCheck.class).field("java.lang.Object", "instance");
assertEquals(2, countIgetObject(get, instance));
}
@Test
public void testDaggerSingleProvider() throws Exception {
AndroidApp app = readClasses(Provider.class, SingleCheck.class);
checkDaggerSingleProviderGet(compileWithD8(app, this::releaseMode));
checkDaggerSingleProviderGet(compileWithR8(app, this::releaseMode));
}
}