Skip to content

Commit 7f2cbf3

Browse files
trexemDagger Team
authored andcommitted
Share a single SwitchingProvider instance across SingleCheck and DoubleCheck providers in fastInit mode.
Previously in Dagger's fastInit mode, each scoped provider (SingleCheck, DoubleCheck) or assisted factory instantiated a dedicated SwitchingProvider instance: ```java this.fooFactoryProvider = SingleCheck.provider(new SwitchingProvider<>(testComponentImpl, 0)); ``` This change introduces `dagger.internal.SwitchingProvider` and shares a single `SwitchingProvider` instance across all scoped and assisted factory bindings within a component: 1. Introduce the `dagger.internal.SwitchingProvider<T>` interface with a parameterized `T get(int id)` method. 2. Add `provider(SwitchingProvider<?>, int)` overloads to `SingleCheck` and `DoubleCheck`. 3. In component implementations, generate a single shared `private SwitchingProvider<Object> switchingProvider` instance initialized in `initialize()` and passed to `SingleCheck.provider` and `DoubleCheck.provider`. 4. Unscoped bindings continue to instantiate `SwitchingProvider` directly as a `Provider<T>`. This reduces redundant SwitchingProvider object allocations during component initialization in fastInit mode. RELNOTES=n/a PiperOrigin-RevId: 944582946
1 parent 4fbc045 commit 7f2cbf3

130 files changed

Lines changed: 2171 additions & 949 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

dagger-compiler/main/java/dagger/internal/codegen/writing/BUILD

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ java_library(
3939
"//third_party/java/error_prone:annotations",
4040
"//third_party/java/guava/base",
4141
"//third_party/java/guava/collect",
42-
"//third_party/java/javapoet",
4342
"//third_party/java/jsr330_inject:jsr330_inject_without_dagger",
4443
],
4544
)

dagger-compiler/main/java/dagger/internal/codegen/writing/ComponentImplementation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ public final class ShardImplementation implements GeneratedImplementation {
496496

497497
private ShardImplementation(XClassName name) {
498498
this.name = name;
499-
this.switchingProviders = new SwitchingProviders(this, compilerOptions, processingEnv);
499+
this.switchingProviders = new SwitchingProviders(this, compilerOptions);
500500
if (graph.componentDescriptor().isProduction()) {
501501
claimMethodName(CANCELLATION_LISTENER_METHOD_NAME);
502502
}

dagger-compiler/main/java/dagger/internal/codegen/writing/SwitchingProviderInstanceSupplier.java

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,21 @@
1616

1717
package dagger.internal.codegen.writing;
1818

19-
import androidx.room3.compiler.codegen.XCodeBlock;
2019
import dagger.assisted.Assisted;
2120
import dagger.assisted.AssistedFactory;
2221
import dagger.assisted.AssistedInject;
23-
import dagger.internal.codegen.binding.Binding;
2422
import dagger.internal.codegen.binding.BindingGraph;
2523
import dagger.internal.codegen.binding.ContributionBinding;
2624
import dagger.internal.codegen.compileroption.CompilerOptions;
27-
import dagger.internal.codegen.model.BindingKind;
2825
import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
2926
import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
30-
import dagger.internal.codegen.xprocessing.XTypeNames;
3127

3228
/**
3329
* An object that initializes a framework-type component field for a binding using instances created
3430
* by switching providers.
3531
*/
3632
final class SwitchingProviderInstanceSupplier implements FrameworkInstanceSupplier {
3733
private final FrameworkInstanceSupplier frameworkInstanceSupplier;
38-
private final CompilerOptions compilerOptions;
3934

4035
@AssistedInject
4136
SwitchingProviderInstanceSupplier(
@@ -56,33 +51,14 @@ final class SwitchingProviderInstanceSupplier implements FrameworkInstanceSuppli
5651
compilerOptions,
5752
componentImplementation,
5853
binding,
59-
scope(binding, frameworkInstanceCreationExpression));
60-
this.compilerOptions = compilerOptions;
54+
frameworkInstanceCreationExpression);
6155
}
6256

6357
@Override
6458
public MemberSelect memberSelect() {
6559
return frameworkInstanceSupplier.memberSelect();
6660
}
6761

68-
private FrameworkInstanceCreationExpression scope(
69-
Binding binding, FrameworkInstanceCreationExpression unscoped) {
70-
// Caching assisted factory provider, so that there won't be new factory created for each
71-
// provider.get() call.
72-
if (!binding.scope().isPresent() && !binding.kind().equals(BindingKind.ASSISTED_FACTORY)) {
73-
return unscoped;
74-
}
75-
return () ->
76-
XCodeBlock.of(
77-
"%T.provider(%L)",
78-
binding.scope().isPresent()
79-
? (binding.scope().get().isReusable()
80-
? XTypeNames.SINGLE_CHECK
81-
: XTypeNames.DOUBLE_CHECK)
82-
: XTypeNames.SINGLE_CHECK,
83-
unscoped.creationExpression());
84-
}
85-
8662
@AssistedFactory
8763
static interface Factory {
8864
SwitchingProviderInstanceSupplier create(ContributionBinding binding);

dagger-compiler/main/java/dagger/internal/codegen/writing/SwitchingProviders.java

Lines changed: 80 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import static dagger.internal.codegen.xprocessing.XCodeBlocks.toParametersCodeBlock;
2828
import static dagger.internal.codegen.xprocessing.XFunSpecs.constructorBuilder;
2929
import static dagger.internal.codegen.xprocessing.XFunSpecs.methodBuilder;
30-
import static dagger.internal.codegen.xprocessing.XTypeNames.daggerProviderOf;
3130
import static javax.lang.model.element.Modifier.FINAL;
3231
import static javax.lang.model.element.Modifier.PRIVATE;
3332
import static javax.lang.model.element.Modifier.PUBLIC;
@@ -37,9 +36,9 @@
3736
import androidx.room3.compiler.codegen.XClassName;
3837
import androidx.room3.compiler.codegen.XCodeBlock;
3938
import androidx.room3.compiler.codegen.XFunSpec;
39+
import androidx.room3.compiler.codegen.XPropertySpec;
4040
import androidx.room3.compiler.codegen.XTypeName;
4141
import androidx.room3.compiler.codegen.XTypeSpec;
42-
import androidx.room3.compiler.processing.XProcessingEnv;
4342
import com.google.common.collect.ImmutableList;
4443
import com.google.common.collect.Lists;
4544
import dagger.internal.codegen.binding.ContributionBinding;
@@ -49,7 +48,6 @@
4948
import dagger.internal.codegen.writing.ComponentImplementation.ShardImplementation;
5049
import dagger.internal.codegen.writing.FrameworkFieldInitializer.FrameworkInstanceCreationExpression;
5150
import dagger.internal.codegen.xprocessing.XFunSpecs;
52-
import dagger.internal.codegen.xprocessing.XProcessingEnvs;
5351
import dagger.internal.codegen.xprocessing.XPropertySpecs;
5452
import dagger.internal.codegen.xprocessing.XTypeNames;
5553
import dagger.internal.codegen.xprocessing.XTypeSpecs;
@@ -75,18 +73,13 @@ final class SwitchingProviders {
7573

7674
private final ShardImplementation shardImplementation;
7775
private final CompilerOptions compilerOptions;
78-
private final XProcessingEnv processingEnv;
7976
private final XTypeName typeVariable;
8077
private final int maxCasesPerSwitch;
8178
private final long maxCasesPerClass;
8279

83-
SwitchingProviders(
84-
ShardImplementation shardImplementation,
85-
CompilerOptions compilerOptions,
86-
XProcessingEnv processingEnv) {
80+
SwitchingProviders(ShardImplementation shardImplementation, CompilerOptions compilerOptions) {
8781
this.shardImplementation = checkNotNull(shardImplementation);
8882
this.compilerOptions = checkNotNull(compilerOptions);
89-
this.processingEnv = checkNotNull(processingEnv);
9083
this.maxCasesPerSwitch = compilerOptions.casesPerSwitchingProviderSwitch();
9184
this.maxCasesPerClass = (long) maxCasesPerSwitch * maxCasesPerSwitch;
9285
this.typeVariable =
@@ -114,7 +107,7 @@ public XCodeBlock creationExpression() {
114107

115108
private SwitchingProviderBuilder getSwitchingProviderBuilder() {
116109
if (switchingProviderBuilders.size() % maxCasesPerClass == 0) {
117-
String name = shardImplementation.getUniqueClassName("SwitchingProvider");
110+
String name = shardImplementation.getUniqueClassName("SwitchingPImpl");
118111
SwitchingProviderBuilder switchingProviderBuilder =
119112
new SwitchingProviderBuilder(shardImplementation.name().nestedClass(name));
120113
shardImplementation.addTypeSupplier(switchingProviderBuilder::build);
@@ -130,6 +123,7 @@ private final class SwitchingProviderBuilder {
130123
private final Map<Integer, XCodeBlock> switchCases = new TreeMap<>();
131124
private final Map<Key, Integer> switchIds = new HashMap<>();
132125
private final XClassName switchingProviderType;
126+
private XPropertySpec sharedSwitchingProviderProperty;
133127

134128
SwitchingProviderBuilder(XClassName switchingProviderType) {
135129
this.switchingProviderType = checkNotNull(switchingProviderType);
@@ -144,32 +138,62 @@ private XCodeBlock getNewInstanceCodeBlock(
144138
switchCases.put(
145139
switchId, createSwitchCaseCodeBlock(key, unscopedInstanceRequestRepresentation));
146140
}
147-
return XCodeBlock.of(
148-
"new %T<%L>(%L, %L)",
149-
switchingProviderType,
150-
maybeTypeParameter(binding),
151-
shardImplementation.componentFieldsByImplementation().values().stream()
152-
.map(field -> XCodeBlock.of("%N", field))
153-
.collect(toParametersCodeBlock()),
154-
switchIds.get(key));
141+
int id = switchIds.get(key);
142+
143+
if (isWrapped(binding)) {
144+
return XCodeBlock.of(
145+
"%T.provider(%N, %L)",
146+
switchingProviderWrapper(binding),
147+
getOrCreateSharedSwitchingProvider(),
148+
id);
149+
} else {
150+
return XCodeBlock.of(
151+
"new %T<>(%L, %L)", switchingProviderType, getComponentFieldsCodeBlock(), id);
152+
}
153+
}
154+
155+
private XPropertySpec getOrCreateSharedSwitchingProvider() {
156+
if (sharedSwitchingProviderProperty == null) {
157+
String fieldName = shardImplementation.getUniqueFieldName("sharedSwitchingProvider");
158+
XCodeBlock args = XCodeBlock.of("%L, -1", getComponentFieldsCodeBlock());
159+
sharedSwitchingProviderProperty =
160+
XPropertySpecs.builder(
161+
fieldName, switchingProviderType.parametrizedBy(XTypeName.ANY_OBJECT))
162+
.addModifiers(PRIVATE)
163+
.build();
164+
shardImplementation.addField(
165+
ComponentImplementation.FieldSpecKind.FRAMEWORK_FIELD, sharedSwitchingProviderProperty);
166+
shardImplementation.addInitialization(
167+
XCodeBlock.of(
168+
"this.%N = new %T<>(%L);",
169+
sharedSwitchingProviderProperty,
170+
switchingProviderType,
171+
args));
172+
}
173+
return sharedSwitchingProviderProperty;
174+
}
175+
176+
private XClassName switchingProviderWrapper(ContributionBinding binding) {
177+
return binding.scope().isPresent() && !binding.scope().get().isReusable()
178+
? XTypeNames.DOUBLE_CHECK_SWITCHING_PROVIDER
179+
: XTypeNames.SINGLE_CHECK_SWITCHING_PROVIDER;
180+
}
181+
182+
private boolean isWrapped(ContributionBinding binding) {
183+
return binding.scope().isPresent() || binding.kind().equals(BindingKind.ASSISTED_FACTORY);
155184
}
156185

157-
private XCodeBlock maybeTypeParameter(ContributionBinding binding) {
158-
// Add the type parameter explicitly when the binding is scoped because Java can't
159-
// resolve the type when wrapped. For example, the following will error:
160-
// fooProvider = DoubleCheck.provider(new SwitchingProvider<>(1));
161-
return (binding.scope().isPresent()
162-
|| binding.kind().equals(BindingKind.ASSISTED_FACTORY)
163-
|| XProcessingEnvs.isPreJava8SourceVersion(processingEnv))
164-
? XCodeBlock.of("%T", shardImplementation.accessibleTypeName(binding.contributedType()))
165-
: XCodeBlock.of("");
186+
private XCodeBlock getComponentFieldsCodeBlock() {
187+
return shardImplementation.componentFieldsByImplementation().values().stream()
188+
.map(field -> XCodeBlock.of("%N", field))
189+
.collect(toParametersCodeBlock());
166190
}
167191

168192
private XCodeBlock createSwitchCaseCodeBlock(
169193
Key key, RequestRepresentation unscopedInstanceRequestRepresentation) {
170194
// TODO(bcorso): Try to delay calling getDependencyExpression() until we are writing out the
171195
// SwitchingProvider because calling it here makes FrameworkFieldInitializer think there's a
172-
// cycle when initializing SwitchingProviders which adds an uncessary DelegateFactory.
196+
// cycle when initializing SwitchingProviders which adds an unnecessary DelegateFactory.
173197
XCodeBlock instanceCodeBlock =
174198
unscopedInstanceRequestRepresentation
175199
.getDependencyExpression(switchingProviderType)
@@ -188,7 +212,7 @@ private XTypeSpec build() {
188212
XTypeSpecs.classBuilder(switchingProviderType)
189213
.addModifiers(PRIVATE, FINAL, STATIC)
190214
.addTypeVariable(typeVariable)
191-
.addSuperinterface(daggerProviderOf(typeVariable))
215+
.addSuperinterface(XTypeNames.SWITCHING_PROVIDER.parametrizedBy(typeVariable))
192216
.addFunctions(getMethods());
193217

194218
// The SwitchingProvider constructor lists all component parameters first and switch id last.
@@ -199,7 +223,8 @@ private XTypeSpec build() {
199223
.forEach(
200224
field -> {
201225
builder.addProperty(field);
202-
constructor.addParameter(field.getName(), field.getType()); // SUPPRESS_GET_NAME_CHECK
226+
constructor.addParameter(
227+
field.getName(), field.getType()); // SUPPRESS_GET_NAME_CHECK
203228
constructor.addStatement("this.%1N = %1N", field);
204229
});
205230

@@ -208,37 +233,52 @@ private XTypeSpec build() {
208233

209234
private ImmutableList<XFunSpec> getMethods() {
210235
ImmutableList<XCodeBlock> switchCodeBlockPartitions = switchCodeBlockPartitions();
236+
ImmutableList.Builder<XFunSpec> getMethods = ImmutableList.builder();
237+
238+
// Add the no-arg get() method for Provider interface
239+
getMethods.add(
240+
methodBuilder("get")
241+
.isOverride(true)
242+
.addModifiers(PUBLIC)
243+
.returns(typeVariable)
244+
.addStatement("return get(id)")
245+
.build());
246+
211247
if (switchCodeBlockPartitions.size() == 1) {
212248
// The case amount does not exceed maxCasesPerSwitch, so no need for extra get methods.
213-
return ImmutableList.of(
214-
methodBuilder("get")
215-
.isOverride(true)
216-
.addModifiers(PUBLIC)
217-
.addAnnotation(suppressWarnings(UNCHECKED))
218-
.returns(typeVariable)
219-
.addCode(getOnlyElement(switchCodeBlockPartitions))
220-
.build());
249+
return getMethods
250+
.add(
251+
methodBuilder("get")
252+
.isOverride(true)
253+
.addModifiers(PUBLIC)
254+
.addParameter("id", XTypeName.PRIMITIVE_INT)
255+
.addAnnotation(suppressWarnings(UNCHECKED))
256+
.returns(typeVariable)
257+
.addCode(getOnlyElement(switchCodeBlockPartitions))
258+
.build())
259+
.build();
221260
}
222261

223262
// This is the main public "get" method that will route to private getter methods.
224263
XFunSpecs.Builder routerMethod =
225264
methodBuilder("get")
226265
.isOverride(true)
227266
.addModifiers(PUBLIC)
267+
.addParameter("id", XTypeName.PRIMITIVE_INT)
228268
.returns(typeVariable)
229269
.beginControlFlow("switch (id / %L)", maxCasesPerSwitch);
230270

231-
ImmutableList.Builder<XFunSpec> getMethods = ImmutableList.builder();
232271
for (int i = 0; i < switchCodeBlockPartitions.size(); i++) {
233272
XFunSpec method =
234273
methodBuilder("get" + i)
235274
.addModifiers(PRIVATE)
275+
.addParameter("id", XTypeName.PRIMITIVE_INT)
236276
.addAnnotation(suppressWarnings(UNCHECKED))
237277
.returns(typeVariable)
238278
.addCode(switchCodeBlockPartitions.get(i))
239279
.build();
240280
getMethods.add(method);
241-
routerMethod.addStatement("case %L: return %N()", i, method);
281+
routerMethod.addStatement("case %L: return %N(id)", i, method);
242282
}
243283

244284
routerMethod

dagger-compiler/main/java/dagger/internal/codegen/xprocessing/XTypeNames.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ object XTypeNames {
8787

8888
@JvmField val DELEGATE_FACTORY = XClassName.get("dagger.internal", "DelegateFactory")
8989
@JvmField val DOUBLE_CHECK = XClassName.get("dagger.internal", "DoubleCheck")
90+
@JvmField val SWITCHING_PROVIDER = XClassName.get("dagger.internal", "SwitchingProvider")
91+
@JvmField
92+
val DOUBLE_CHECK_SWITCHING_PROVIDER =
93+
XClassName.get("dagger.internal", "DoubleCheckSwitchingProvider")
94+
@JvmField
95+
val SINGLE_CHECK_SWITCHING_PROVIDER =
96+
XClassName.get("dagger.internal", "SingleCheckSwitchingProvider")
9097
@JvmField val DAGGER_PRECONDITIONS = XClassName.get("dagger.internal", "Preconditions")
9198

9299
// TODO(b/404613325): Figure out what to do for calls like java.util.Collections.<T>emptyList()
@@ -378,8 +385,7 @@ object XTypeNames {
378385
)
379386
}
380387

381-
@JvmStatic
382-
fun XTypeName.isJavaWildcard(): Boolean = toJavaPoet() is JWildcardTypeName
388+
@JvmStatic fun XTypeName.isJavaWildcard(): Boolean = toJavaPoet() is JWildcardTypeName
383389

384390
@JvmStatic
385391
fun XTypeName.boundsHasSelfReference(): Boolean =

dagger-runtime/api/dagger.api

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,11 @@ public final class dagger/internal/DoubleCheck : dagger/Lazy, dagger/internal/Pr
9494
public static fun provider (Ljavax/inject/Provider;)Ljavax/inject/Provider;
9595
}
9696

97+
public final class dagger/internal/DoubleCheckSwitchingProvider : dagger/Lazy, dagger/internal/Provider {
98+
public fun get ()Ljava/lang/Object;
99+
public static fun provider (Ldagger/internal/SwitchingProvider;I)Ldagger/internal/Provider;
100+
}
101+
97102
public abstract interface class dagger/internal/Factory : dagger/internal/Provider {
98103
}
99104

@@ -282,6 +287,16 @@ public final class dagger/internal/SingleCheck : dagger/internal/Provider {
282287
public static fun provider (Ljavax/inject/Provider;)Ljavax/inject/Provider;
283288
}
284289

290+
public final class dagger/internal/SingleCheckSwitchingProvider : dagger/internal/Provider {
291+
public fun get ()Ljava/lang/Object;
292+
public static fun provider (Ldagger/internal/SwitchingProvider;I)Ldagger/internal/Provider;
293+
}
294+
295+
public abstract interface class dagger/internal/SwitchingProvider : dagger/internal/Provider {
296+
public abstract fun get (I)Ljava/lang/Object;
297+
public fun get ()Ljava/lang/Object;
298+
}
299+
285300
public abstract interface annotation class dagger/multibindings/ClassKey : java/lang/annotation/Annotation {
286301
public abstract fun value ()Ljava/lang/Class;
287302
}

dagger-runtime/main/java/dagger/internal/DoubleCheck.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
* A {@link Lazy} and {@link Provider} implementation that memoizes the value returned from a
2727
* delegate using the double-check idiom described in Item 71 of <i>Effective Java 2</i>.
2828
*/
29+
2930
public final class DoubleCheck<T extends @Nullable Object> implements Provider<T>, Lazy<T> {
3031
private static final Object UNINITIALIZED = new Object();
3132

@@ -80,7 +81,7 @@ public T get() {
8081
public static <T extends @Nullable Object> dagger.internal.Provider<T> provider(
8182
dagger.internal.Provider<T> delegate) {
8283
checkNotNull(delegate);
83-
if (delegate instanceof DoubleCheck) {
84+
if (delegate instanceof DoubleCheck || delegate instanceof DoubleCheckSwitchingProvider) {
8485
/* This should be a rare case, but if we have a scoped @Binds that delegates to a scoped
8586
* binding, we shouldn't cache the value again. */
8687
return delegate;
@@ -121,3 +122,4 @@ public static <P extends javax.inject.Provider<T>, T> Lazy<T> lazy(P provider) {
121122
return lazy(asDaggerProvider(provider));
122123
}
123124
}
125+

0 commit comments

Comments
 (0)