Skip to content

Commit 60f9d6f

Browse files
rlublecopybara-github
authored andcommitted
Backoff autovalue optimization if any of the properties is an array.
This is in preparation for reusing the ValueType abstraction to optimize Java record types. PiperOrigin-RevId: 863273459
1 parent 32e8b3d commit 60f9d6f

19 files changed

+420
-188
lines changed

jre/java/javaemul/internal/ValueType.java

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package javaemul.internal;
1717

18-
import java.util.Arrays;
18+
import java.util.Objects;
1919
import java.util.StringJoiner;
2020
import javaemul.internal.annotations.DoNotAutobox;
2121
import jsinterop.annotations.JsMethod;
@@ -69,33 +69,14 @@ static boolean equals(ValueType thisObject, Object o) {
6969
for (String p : thisKeys) {
7070
Object p1 = JsUtils.getProperty(thisObject, p);
7171
Object p2 = JsUtils.getProperty(thatObject, p);
72-
if (!isAutoValueEqual(p1, p2)) {
72+
if (!Objects.equals(p1, p2)) {
7373
return false;
7474
}
7575
}
7676

7777
return true;
7878
}
7979

80-
private static boolean isAutoValueEqual(Object a, Object b) {
81-
if (a == b) {
82-
return true;
83-
}
84-
if (a == null || b == null) {
85-
return false;
86-
}
87-
88-
if (ArrayStamper.isStamped(a) && ArrayStamper.isStamped(b)) {
89-
// AutoValue only supports primitive arrays so if the array is stamped we can safely assume
90-
// this is a primitive array. So here we just check that they have same types and compare the
91-
// contents.
92-
return a.getClass() == b.getClass()
93-
&& Arrays.equals(JsUtils.<Object[]>uncheckedCast(a), JsUtils.<Object[]>uncheckedCast(b));
94-
}
95-
96-
return a.equals(b);
97-
}
98-
9980
// Package private to ensure clinit is run when called from types that use this class as a mixin.
10081
static int hashCode(ValueType thisObject) {
10182
int hashCode = 1;
@@ -104,12 +85,7 @@ static int hashCode(ValueType thisObject) {
10485
if (value == null) {
10586
continue;
10687
}
107-
// AutoValue supports primitives arrays, that needs special handling for hashCode.
108-
int h =
109-
ArrayStamper.isStamped(value)
110-
? Arrays.hashCode(JsUtils.<Object[]>uncheckedCast(value))
111-
: value.hashCode();
112-
hashCode = (1000003 * hashCode) ^ h;
88+
hashCode = (1000003 * hashCode) ^ value.hashCode();
11389
}
11490
return hashCode;
11591
}

transpiler/java/com/google/j2cl/transpiler/passes/OptimizeAutoValue.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ public <T extends TypeDescriptor> T apply(T t) {
189189

190190
private static boolean canBeInlinedTo(Type type) {
191191
if (AstUtils.isAnnotatedWithAutoValueBuilder(type.getDeclaration())) {
192+
if (!isOptimizableAutoValue(type.getDeclaration().getEnclosingTypeDeclaration())) {
193+
// If the enclosing @AutoValue is not optimizable, the builder is also not optimizable.
194+
return false;
195+
}
192196
// Note that AutoValue.Builder will generate default ctor so would be only safe to inline
193197
// the implementation if user didn't declare non-empty one.
194198
// Most complete logic for safety here would be cross-checking all generated ctors against
@@ -197,7 +201,17 @@ private static boolean canBeInlinedTo(Type type) {
197201
Method method = type.getDefaultConstructor();
198202
return method == null || method.isEmpty();
199203
}
200-
return AstUtils.isAnnotatedWithAutoValue(type.getDeclaration());
204+
return isOptimizableAutoValue(type.getDeclaration());
205+
}
206+
207+
private static boolean isOptimizableAutoValue(TypeDeclaration typeDeclaration) {
208+
return AstUtils.isAnnotatedWithAutoValue(typeDeclaration)
209+
// Do not optimize @AutoValue types that have array properties since ValueType does not
210+
// support array valued @AutoValue property semantics.
211+
&& typeDeclaration.toDescriptor().getPolymorphicMethods().stream()
212+
.filter(MethodDescriptor::isAbstract)
213+
.map(MethodDescriptor::getReturnTypeDescriptor)
214+
.noneMatch(TypeDescriptor::isArray);
201215
}
202216

203217
private static void inlineMembers(Type from, Type to) {
@@ -381,6 +395,8 @@ private void optimizeAsValueTypes(Library library) {
381395
int mask = removeJavaLangObjectMethods(autoValue);
382396
if (mask == 0) {
383397
// No method removed/needs optimization. Leave the type alone.
398+
// Note that this also applies when the type can not be inlined to as it wouldn't
399+
// have any of the generated `j.l.Object` methods.
384400
return;
385401
}
386402

transpiler/javatests/com/google/j2cl/autovalue/AutoValueTest.java

Lines changed: 9 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -193,23 +193,20 @@ public void testStrangeGetters() {
193193
abstract static class GettersAndConcreteNonGetters {
194194
abstract int getFoo();
195195

196-
@SuppressWarnings("mutable")
197-
abstract byte[] getBytes();
198-
199-
boolean hasNoBytes() {
200-
return getBytes().length == 0;
196+
boolean fooIsZero() {
197+
return getFoo() == 0;
201198
}
202199

203-
static GettersAndConcreteNonGetters create(int foo, byte[] bytes) {
204-
return new AutoValue_AutoValueTest_GettersAndConcreteNonGetters(foo, bytes);
200+
static GettersAndConcreteNonGetters create(int foo) {
201+
return new AutoValue_AutoValueTest_GettersAndConcreteNonGetters(foo);
205202
}
206203
}
207204

208205
@Test
209206
public void testGettersAndConcreteNonGetters() {
210-
GettersAndConcreteNonGetters instance = GettersAndConcreteNonGetters.create(23, new byte[] {1});
211-
assertFalse(instance.hasNoBytes());
212-
String expectedString = "GettersAndConcreteNonGetters{foo=23, bytes=[1]}";
207+
GettersAndConcreteNonGetters instance = GettersAndConcreteNonGetters.create(23);
208+
assertFalse(instance.fooIsZero());
209+
String expectedString = "GettersAndConcreteNonGetters{foo=23}";
213210
assertThat(instance.toString()).isEqualTo(expectedString);
214211
}
215212

@@ -1036,8 +1033,7 @@ public void testPrimitiveArrays() {
10361033
// EqualsTester also exercises hashCode(). We clone the arrays above to ensure that using the
10371034
// default Object.hashCode() will fail.
10381035

1039-
String expectedString =
1040-
"PrimitiveArrays{booleans=[" + booleans + "], " + "ints=[" + ints + "]}";
1036+
String expectedString = "{" + Arrays.toString(booleans) + ", " + Arrays.toString(ints) + "}";
10411037
assertThat(object1.toString()).isEqualTo(expectedString);
10421038
assertThat(object1.ints()).isSameInstanceAs(object1.ints());
10431039
}
@@ -1052,7 +1048,7 @@ public void testNullablePrimitiveArrays() {
10521048
PrimitiveArrays.create(Arrays.copyOf(booleans, booleans.length), null);
10531049
new EqualsTester().addEqualityGroup(object1, object2).addEqualityGroup(object0).testEquals();
10541050

1055-
String expectedString = "PrimitiveArrays{booleans=[" + booleans + "], " + "ints=null}";
1051+
String expectedString = "{" + Arrays.toString(booleans) + ", " + "null}";
10561052
assertThat(object1.toString()).isEqualTo(expectedString);
10571053

10581054
assertThat(object1.booleans()).isSameInstanceAs(object1.booleans());
@@ -1862,9 +1858,6 @@ public abstract static class BuilderWithUnprefixedGetters<T extends Comparable<T
18621858
@Nullable
18631859
public abstract T t();
18641860

1865-
@SuppressWarnings("mutable")
1866-
public abstract int[] ints();
1867-
18681861
public abstract int noGetter();
18691862

18701863
public abstract String oAuth();
@@ -1881,8 +1874,6 @@ public interface Builder<T extends Comparable<T>> {
18811874

18821875
Builder<T> setT(T t);
18831876

1884-
Builder<T> setInts(int[] ints);
1885-
18861877
Builder<T> setNoGetter(int x);
18871878

18881879
Builder<T> setoAuth(String x); // this ugly spelling is for compatibility
@@ -1893,8 +1884,6 @@ public interface Builder<T extends Comparable<T>> {
18931884

18941885
T t();
18951886

1896-
int[] ints();
1897-
18981887
String oAuth();
18991888

19001889
String oBrien();
@@ -1906,7 +1895,6 @@ public interface Builder<T extends Comparable<T>> {
19061895
@Test
19071896
public void testBuilderWithUnprefixedGetter() {
19081897
ImmutableList<String> names = ImmutableList.of("fred", "jim");
1909-
int[] ints = {6, 28, 496, 8128, 33550336};
19101898
int noGetter = -1;
19111899

19121900
BuilderWithUnprefixedGetters.Builder<String> builder = BuilderWithUnprefixedGetters.builder();
@@ -1917,30 +1905,17 @@ public void testBuilderWithUnprefixedGetter() {
19171905
} catch (IllegalStateException e) {
19181906
assertThat(e).hasMessageThat().isNull();
19191907
}
1920-
try {
1921-
builder.ints();
1922-
fail("Attempt to retrieve unset ints property should have failed");
1923-
} catch (IllegalStateException e) {
1924-
assertThat(e).hasMessageThat().isNull();
1925-
}
19261908

19271909
builder.setList(names);
19281910
assertThat(builder.list()).isSameInstanceAs(names);
1929-
builder.setInts(ints);
1930-
assertThat(builder.ints()).isEqualTo(ints);
19311911
builder.setoAuth("OAuth");
19321912
assertThat(builder.oAuth()).isEqualTo("OAuth");
19331913
builder.setOBrien("Flann");
19341914
assertThat(builder.oBrien()).isEqualTo("Flann");
1935-
// The array is not cloned by the getter, so the client can modify it (but shouldn't).
1936-
ints[0] = 0;
1937-
assertThat(builder.ints()[0]).isEqualTo(0);
1938-
ints[0] = 6;
19391915

19401916
BuilderWithUnprefixedGetters<String> instance = builder.setNoGetter(noGetter).build();
19411917
assertThat(instance.list()).isSameInstanceAs(names);
19421918
assertThat(instance.t()).isNull();
1943-
assertThat(instance.ints()).isEqualTo(ints);
19441919
assertThat(instance.noGetter()).isEqualTo(noGetter);
19451920
assertThat(instance.oAuth()).isEqualTo("OAuth");
19461921
assertThat(instance.oBrien()).isEqualTo("Flann");

transpiler/javatests/com/google/j2cl/readable/java/autovalue/output_closure/ArrayField$Builder.impl.java.js.txt

Lines changed: 8 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,23 @@ const j_l_Object = goog.require('java.lang.Object$impl');
44
const $Util = goog.require('nativebootstrap.Util$impl');
55

66
let ArrayField = goog.forwardDeclare('autovalue.ArrayField$impl');
7-
let IllegalStateException = goog.forwardDeclare('java.lang.IllegalStateException$impl');
8-
let Objects = goog.forwardDeclare('java.util.Objects$impl');
9-
let $Equality = goog.forwardDeclare('nativebootstrap.Equality$impl');
10-
let $Exceptions = goog.forwardDeclare('vmbootstrap.Exceptions$impl');
117

8+
/**
9+
* @abstract
10+
*/
1211
class Builder extends j_l_Object {
1312
/** @protected @nodts */
1413
constructor() {
1514
super();
16-
/**@type {Array<number>} @nodts*/
17-
this.f_arrayField__autovalue_AutoValue_ArrayField_Builder_;
18-
}
19-
/** @nodts @return {!Builder} */
20-
static $create__() {
21-
Builder.$clinit();
22-
let $instance = new Builder();
23-
$instance.$ctor__autovalue_ArrayField_Builder__void();
24-
return $instance;
2515
}
2616
/** @nodts */
2717
$ctor__autovalue_ArrayField_Builder__void() {
2818
this.$ctor__java_lang_Object__void();
2919
}
30-
/** @nodts @return {Builder} */
31-
m_setArrayField__arrayOf_int__autovalue_ArrayField_Builder(/** Array<number> */ arrayField) {
32-
Objects.m_requireNonNull__java_lang_Object__java_lang_Object(arrayField);
33-
this.f_arrayField__autovalue_AutoValue_ArrayField_Builder_ = arrayField;
34-
return this;
35-
}
36-
/** @nodts @return {ArrayField} */
37-
m_build__autovalue_ArrayField_$pp_autovalue() {
38-
if ($Equality.$same(this.f_arrayField__autovalue_AutoValue_ArrayField_Builder_, null)) {
39-
throw $Exceptions.toJs(IllegalStateException.$create__());
40-
}
41-
return ArrayField.$create__arrayOf_int(this.f_arrayField__autovalue_AutoValue_ArrayField_Builder_);
42-
}
20+
/** @abstract @nodts @return {Builder} */
21+
m_setArrayField__arrayOf_int__autovalue_ArrayField_Builder(/** Array<number> */ arrayField) {}
22+
/** @abstract @nodts @return {ArrayField} */
23+
m_build__autovalue_ArrayField_$pp_autovalue() {}
4324
/** @nodts */
4425
static $clinit() {
4526
Builder.$clinit = () =>{};
@@ -52,13 +33,7 @@ class Builder extends j_l_Object {
5233
}
5334

5435
/** @nodts */
55-
static $loadModules() {
56-
ArrayField = goog.module.get('autovalue.ArrayField$impl');
57-
IllegalStateException = goog.module.get('java.lang.IllegalStateException$impl');
58-
Objects = goog.module.get('java.util.Objects$impl');
59-
$Equality = goog.module.get('nativebootstrap.Equality$impl');
60-
$Exceptions = goog.module.get('vmbootstrap.Exceptions$impl');
61-
}
36+
static $loadModules() {}
6237
}
6338
$Util.$setClassMetadata(Builder, 'autovalue.ArrayField$Builder');
6439

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
goog.module('autovalue.ArrayField.Builder');
22

33
goog.require('autovalue.ArrayField');
4-
goog.require('java.lang.IllegalStateException');
54
goog.require('java.lang.Object');
6-
goog.require('java.util.Objects');
7-
goog.require('nativebootstrap.Equality');
85
goog.require('nativebootstrap.Util');
9-
goog.require('vmbootstrap.Exceptions');
106

117
const Builder = goog.require('autovalue.ArrayField.Builder$impl');
128
exports = Builder;

transpiler/javatests/com/google/j2cl/readable/java/autovalue/output_closure/ArrayField.impl.java.js.txt

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,27 @@
11
goog.module('autovalue.ArrayField$impl');
22

3-
const ValueType = goog.require('javaemul.internal.ValueType$impl');
3+
const j_l_Object = goog.require('java.lang.Object$impl');
44
const $Util = goog.require('nativebootstrap.Util$impl');
55

6-
class ArrayField extends ValueType {
6+
/**
7+
* @abstract
8+
*/
9+
class ArrayField extends j_l_Object {
710
/** @protected @nodts */
811
constructor() {
912
super();
10-
/**@type {Array<number>} @nodts*/
11-
this.f_arrayField__autovalue_AutoValue_ArrayField_;
12-
}
13-
/** @nodts @return {!ArrayField} */
14-
static $create__arrayOf_int(/** Array<number> */ arrayField) {
15-
ArrayField.$clinit();
16-
let $instance = new ArrayField();
17-
$instance.$ctor__autovalue_ArrayField__arrayOf_int__void(arrayField);
18-
return $instance;
1913
}
2014
/** @nodts */
21-
$ctor__autovalue_ArrayField__arrayOf_int__void(/** Array<number> */ arrayField) {
22-
this.$ctor__javaemul_internal_ValueType__void();
23-
this.f_arrayField__autovalue_AutoValue_ArrayField_ = arrayField;
24-
$J2CL_PRESERVE$(this.f_arrayField__autovalue_AutoValue_ArrayField_);
25-
}
26-
/** @nodts @return {Array<number>} */
27-
m_getArrayField__arrayOf_int() {
28-
return this.f_arrayField__autovalue_AutoValue_ArrayField_;
15+
$ctor__autovalue_ArrayField__void() {
16+
this.$ctor__java_lang_Object__void();
2917
}
18+
/** @abstract @nodts @return {Array<number>} */
19+
m_getArrayField__arrayOf_int() {}
3020
/** @nodts */
3121
static $clinit() {
3222
ArrayField.$clinit = () =>{};
3323
ArrayField.$loadModules();
34-
ValueType.$clinit();
24+
j_l_Object.$clinit();
3525
}
3626
/** @nodts @return {boolean} */
3727
static $isInstance(/** ? */ instance) {

transpiler/javatests/com/google/j2cl/readable/java/autovalue/output_closure/ArrayField.java.js.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
goog.module('autovalue.ArrayField');
22

3-
goog.require('javaemul.internal.ValueType');
3+
goog.require('java.lang.Object');
44
goog.require('nativebootstrap.Util');
55

66
const ArrayField = goog.require('autovalue.ArrayField$impl');

0 commit comments

Comments
 (0)