Skip to content

Commit

Permalink
Support optimizing AutoValue-generated code that comes in from J2KT
Browse files Browse the repository at this point in the history
On the J2KT side we'll be leaving marker annotation on the original `@AutoValue`
annotated classes to denote that it was an AutoValue type. In the Kotlin
frontend we'll then honor that annotation as being equivalent to `@AutoValue`.

In the optimization pass we primarily just need to remove the companion class
that exists for the generated subtype. The only reason it exists is due to the
static `serialVersionUID` field (which we normally expect), but the fact that
it was on the companion object means we'll have a companion class, the companion
instance field on the enclosing type, and the initialization of the companion
field. All of these need to be removed if the enclosing type is eligible for
optimization.

PiperOrigin-RevId: 718994922
  • Loading branch information
kevinoconnor7 authored and copybara-github committed Jan 23, 2025
1 parent 992d798 commit 1108a9a
Show file tree
Hide file tree
Showing 77 changed files with 686 additions and 1,371 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,13 +135,8 @@ public boolean isNoopCast() {
return getTypeDeclaration().isNoopCast();
}

/**
* Returns true if the given type descriptor is a Kotlin companion object class that can be
* optimized. In order to be optimizable, the companion object should not extend any class nor
* implement any interface.
*/
@Override
public boolean isOptimizableKotlinCompanion() {
public boolean isKotlinCompanionClass() {
// TODO(b/337362819): Uncomment this when the Java frontend is correctly settings SourceLanguage
// for Kotlin deps.
// if (getTypeDeclaration().getSourceLanguage() != KOTLIN) {
Expand All @@ -152,20 +147,26 @@ public boolean isOptimizableKotlinCompanion() {
// - The type should be a static nested final class named `Companion`
// - The enclosing class should have a static field named `Companion` of the same type.
// TODO(b/335000000): Add the ability to mark class as Kotlin companion.
if (!isClass()
|| !isFinal()
|| !getSimpleSourceName().equals("Companion")
|| getEnclosingTypeDescriptor() == null
|| getEnclosingTypeDescriptor().getDeclaredFieldDescriptors().stream()
.noneMatch(
f ->
f.getName().equals("Companion")
&& f.getTypeDescriptor().isSameBaseType(this))) {
return false;
}
return isClass()
&& isFinal()
&& getSimpleSourceName().equals("Companion")
&& getEnclosingTypeDescriptor() != null
&& getEnclosingTypeDescriptor().getDeclaredFieldDescriptors().stream()
.anyMatch(
f -> f.getName().equals("Companion") && f.getTypeDescriptor().isSameBaseType(this));
}

/**
* Returns true if the given type descriptor is a Kotlin companion object class that can be
* optimized. In order to be optimizable, the companion object should not extend any class nor
* implement any interface.
*/
@Override
public boolean isOptimizableKotlinCompanion() {
// In order to be able to optimize the companion object, it should not extend any class nor
// implement any interface.
return TypeDescriptors.isJavaLangObject(getSuperTypeDescriptor())
return isKotlinCompanionClass()
&& TypeDescriptors.isJavaLangObject(getSuperTypeDescriptor())
&& getInterfaceTypeDescriptors().isEmpty();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,11 @@ public boolean isNoopCast() {
return false;
}

/** Returns true if the given type descriptor is a Kotlin companion object class. */
public boolean isKotlinCompanionClass() {
return false;
}

/**
* Returns true if the given type descriptor is a Kotlin companion object class that can be
* optimized.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,8 @@ public ImmutableList<Supplier<NormalizationPass>> getPassFactories(BackendOption
// Compute bridge methods before optimizing autovalue, since inlining the autovalue
// classes requires inlining the bridges as well.
AddBridgeMethods::new,
() -> new OptimizeAutoValue(options.getOptimizeAutoValue()),
OptimizeKotlinCompanions::new,
() -> new OptimizeAutoValue(options.getOptimizeAutoValue()),

// Default constructors and explicit super calls should be synthesized first.
CreateImplicitConstructors::new,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.google.j2cl.transpiler.ast.TypeDeclaration
import com.google.j2cl.transpiler.ast.TypeDescriptor
import com.google.j2cl.transpiler.ast.TypeDescriptors.isJavaLangEnum
import com.google.j2cl.transpiler.ast.TypeDescriptors.isJavaLangObject
import com.google.j2cl.transpiler.backend.kotlin.KotlinSource.annotation
import com.google.j2cl.transpiler.backend.kotlin.objc.comment
import com.google.j2cl.transpiler.backend.kotlin.source.Source
import com.google.j2cl.transpiler.backend.kotlin.source.Source.Companion.block
Expand Down Expand Up @@ -65,6 +66,7 @@ internal data class TypeRenderer(val nameRenderer: NameRenderer) {
newLineSeparated(
objCNameRenderer.objCAnnotationSource(typeDeclaration),
jsInteropAnnotationRenderer.jsInteropAnnotationsSource(typeDeclaration),
autoValueAnnotationsSource(typeDeclaration),
spaceSeparated(
inheritanceModifierSource(typeDeclaration),
classModifiersSource(typeDeclaration),
Expand Down Expand Up @@ -156,6 +158,19 @@ internal data class TypeRenderer(val nameRenderer: NameRenderer) {
getConstructorInvocation(method)?.let { expressionRenderer(type).invocationSource(it) }
?: inParentheses(Source.EMPTY)

private fun autoValueAnnotationsSource(typeDeclaration: TypeDeclaration): Source =
when {
typeDeclaration.isAnnotatedWithAutoValue ->
annotation(
nameRenderer.topLevelQualifiedNameSource("javaemul.lang.annotations.WasAutoValue")
)
typeDeclaration.isAnnotatedWithAutoValueBuilder ->
annotation(
nameRenderer.topLevelQualifiedNameSource("javaemul.lang.annotations.WasAutoValue.Builder")
)
else -> Source.EMPTY
}

private companion object {
fun classModifiersSource(typeDeclaration: TypeDeclaration): Source =
Source.emptyUnless(typeDeclaration.isKtInner) { KotlinSource.INNER_KEYWORD }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ import com.google.j2cl.transpiler.frontend.kotlin.ir.getWasmInfo
import com.google.j2cl.transpiler.frontend.kotlin.ir.hasVoidReturn
import com.google.j2cl.transpiler.frontend.kotlin.ir.isAbstract
import com.google.j2cl.transpiler.frontend.kotlin.ir.isArrayType
import com.google.j2cl.transpiler.frontend.kotlin.ir.isAutoValue
import com.google.j2cl.transpiler.frontend.kotlin.ir.isAutoValueBuilder
import com.google.j2cl.transpiler.frontend.kotlin.ir.isCapturingEnclosingInstance
import com.google.j2cl.transpiler.frontend.kotlin.ir.isClassType
import com.google.j2cl.transpiler.frontend.kotlin.ir.isDeprecated
Expand Down Expand Up @@ -250,6 +252,8 @@ class KotlinEnvironment(
.setJsFunctionInterface(irClass.isJsFunction)
.setJsEnumInfo(irClass.getJsEnumInfo())
.setWasmInfo(irClass.getWasmInfo())
.setAnnotatedWithAutoValue(irClass.isAutoValue)
.setAnnotatedWithAutoValueBuilder(irClass.isAutoValueBuilder)
.apply {
val jsMemberAnnotation = irClass.getJsMemberAnnotationInfo()
setCustomizedJsNamespace(jsMemberAnnotation?.namespace)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,16 @@ val IrClass.isFinal: Boolean
val IrClass.isFromSource: Boolean
get() = source.containingFile != SourceFile.NO_SOURCE_FILE

val IrClass.isAutoValue: Boolean
get() =
hasAnnotation(FqName("com.google.auto.value.AutoValue")) ||
hasAnnotation(FqName("javaemul.lang.annotations.WasAutoValue"))

val IrClass.isAutoValueBuilder: Boolean
get() =
hasAnnotation(FqName("com.google.auto.value.AutoValue.Builder")) ||
hasAnnotation(FqName("javaemul.lang.annotations.WasAutoValue.Builder"))

fun IrDeclaration.getTopLevelEnclosingClass(): IrClass {
if (this is IrClass && isTopLevel) {
return this
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,11 @@ private boolean checkAutoValue(TypeDeclaration typeDeclaration) {
}

private boolean checkAutoValueTypeName(TypeDeclaration typeDeclaration) {
// TODO(goktug): Replace with checking the generator name passed via @Generated when J2CL starts
// modeling annotations in the AST.
// TODO(b/221280581): Replace with checking the generator name passed via @Generated when J2CL
// starts modeling annotations in the AST.
return typeDeclaration != null
&& typeDeclaration.getSimpleSourceName().matches("\\$*AutoValue_.+");
// Note: We normally expect the name to start with a $, but J2KT will replace that with __.
&& typeDeclaration.getSimpleSourceName().matches("(\\$|_)*AutoValue_.+");
}

private void checkSystemProperties(Type type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import com.google.j2cl.transpiler.ast.Field;
import com.google.j2cl.transpiler.ast.FieldAccess;
import com.google.j2cl.transpiler.ast.FieldDescriptor;
import com.google.j2cl.transpiler.ast.InitializerBlock;
import com.google.j2cl.transpiler.ast.JsInfo;
import com.google.j2cl.transpiler.ast.JsMemberType;
import com.google.j2cl.transpiler.ast.Method;
Expand Down Expand Up @@ -377,8 +378,18 @@ private static void moveCompanionInstanceFieldToCompanionClass(
return null;
}
}));
Statement initializer = Iterables.getOnlyElement(initializers);

// We may have emptied out an initializer block so we should clean those up. This doesn't
// guarantee that we're only cleaning up ones we actually touched, but if it's empty it
// shouldn't matter anyway.
enclosingType
.getMembers()
.removeIf(
m ->
m.isInitializerBlock()
&& ((InitializerBlock) m).getBody().getStatements().isEmpty());

Statement initializer = Iterables.getOnlyElement(initializers);
companion.addStaticInitializerBlock(
0,
Block.newBuilder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright 2025 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package autovalue;

import com.google.auto.value.AutoValue;

@AutoValue
public abstract class AutoValueDependency {
abstract int value();

public static Builder builder() {
return new AutoValue_AutoValueDependency.Builder();
}

@AutoValue.Builder
public abstract static class Builder {
public abstract Builder value(int value);

public abstract AutoValueDependency build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
load("//build_defs:rules.bzl", "j2wasm_library")
load(
"//transpiler/javatests/com/google/j2cl/integration:integration_test.bzl",
"integration_library",
"integration_test",
)

Expand All @@ -10,13 +11,25 @@ package(
licenses = ["notice"],
)

integration_library(
name = "dependency",
srcs = ["AutoValueDependency.java"],
deps = [
"//third_party:auto_value",
],
)

j2wasm_library(
name = "autovalue-j2wasm",
srcs = glob(
["*.java"],
exclude = ["JsArray.java"],
exclude = [
"AutoValueDependency.java",
"JsArray.java",
],
) + glob(["super-wasm/*.java"]),
deps = [
":dependency-j2wasm",
"//jre/java:javaemul_internal_annotations-j2wasm",
"//third_party:auto_value-j2wasm",
"//third_party:jsinterop-annotations-j2wasm",
Expand All @@ -27,9 +40,13 @@ j2wasm_library(
# TODO(b/264934554): Port this test to Kotlin when AutoValue is supported.
integration_test(
name = "autovalue",
srcs = glob(["*.java"]),
srcs = glob(
["*.java"],
exclude = ["AutoValueDependency.java"],
),
closure_defines = {"jre.classMetadata": "'STRIPPED'"},
deps = [
":dependency",
"//third_party:auto_value",
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static void main(String... args) {
testUnusedTypeExtending();
testClinit();
testJsInterop();
testCrossLibrary();
}

private static void testComposite() {
Expand Down Expand Up @@ -241,6 +242,10 @@ private int getField() {
return field;
}

public static int getStaticField() {
return field;
}

static {
field = 1;
}
Expand Down Expand Up @@ -268,6 +273,7 @@ static AutoValueWithBuilderAndClinit create() {
private static void testClinit() {
AutoValueWithBuilderAndClinit o = AutoValueWithBuilderAndClinit.Builder.create();
assertEquals(1, o.getField());
assertEquals(1, AutoValueWithBuilderAndClinit.getStaticField());
}

@AutoValue
Expand Down Expand Up @@ -300,4 +306,9 @@ private static void testJsInterop() {
assertEquals(0, autoValueJsType.getField());
assertEquals(1, autoValueJsType.getField2());
}

private static void testCrossLibrary() {
AutoValueDependency dependency = AutoValueDependency.builder().value(1).build();
assertEquals(1, dependency.value());
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,55 @@
goog.module('autovalue.AutoValueJsType$impl');

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

/**
* @abstract
*/
class AutoValueJsType extends j_l_Object {
class AutoValueJsType extends ValueType {
/** @protected @nodts */
constructor() {
super();
/**@type {number} @nodts*/
this.f_field__autovalue_AutoValue_AutoValueJsType_ = 0;
/**@type {number} @nodts*/
this.f_withJsMethod__autovalue_AutoValue_AutoValueJsType_ = 0;
}
/** @nodts @return {!AutoValueJsType} */
static $create__() {
AutoValueJsType.$clinit();
let $instance = new AutoValueJsType();
$instance.$ctor__autovalue_AutoValueJsType__void();
return $instance;
}
/** @nodts */
$ctor__autovalue_AutoValueJsType__void() {
this.$ctor__java_lang_Object__void();
this.$ctor__javaemul_internal_ValueType__void();
}
/** @nodts @return {!AutoValueJsType} */
static $create__int__int(/** number */ field, /** number */ withJsMethod) {
AutoValueJsType.$clinit();
let $instance = new AutoValueJsType();
$instance.$ctor__autovalue_AutoValueJsType__int__int__void(field, withJsMethod);
return $instance;
}
/** @nodts */
$ctor__autovalue_AutoValueJsType__int__int__void(/** number */ field, /** number */ withJsMethod) {
this.$ctor__autovalue_AutoValueJsType__void();
this.f_field__autovalue_AutoValue_AutoValueJsType_ = field;
this.f_withJsMethod__autovalue_AutoValue_AutoValueJsType_ = withJsMethod;
$J2CL_PRESERVE$(this.f_field__autovalue_AutoValue_AutoValueJsType_, this.f_withJsMethod__autovalue_AutoValue_AutoValueJsType_);
}
/** @return {number} */
getField() {
return this.f_field__autovalue_AutoValue_AutoValueJsType_;
}
/** @return {number} */
getField2() {
return this.f_withJsMethod__autovalue_AutoValue_AutoValueJsType_;
}
/** @abstract @return {number} */
getField() {}
/** @abstract @return {number} */
getField2() {}
/** @nodts */
static $clinit() {
AutoValueJsType.$clinit = () =>{};
AutoValueJsType.$loadModules();
j_l_Object.$clinit();
ValueType.$clinit();
}
/** @nodts @return {boolean} */
static $isInstance(/** ? */ instance) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
goog.module('autovalue.AutoValueJsType');

goog.require('java.lang.Object');
goog.require('javaemul.internal.ValueType');
goog.require('nativebootstrap.Util');

const AutoValueJsType = goog.require('autovalue.AutoValueJsType$impl');
Expand Down
Loading

0 comments on commit 1108a9a

Please sign in to comment.