Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ArC - reduce allocations for intercepted methods #30816

Merged
merged 3 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
import io.quarkus.arc.impl.CreationalContextImpl;
import io.quarkus.arc.impl.InterceptedMethodMetadata;
import io.quarkus.arc.impl.InterceptedStaticMethods;
import io.quarkus.arc.impl.InterceptedStaticMethods.InterceptedStaticMethod;
import io.quarkus.arc.processor.AnnotationLiteralProcessor;
import io.quarkus.arc.processor.BeanProcessor;
import io.quarkus.arc.processor.DotNames;
Expand Down Expand Up @@ -77,7 +76,7 @@ public class InterceptedStaticMethodsProcessor {
private static final Logger LOGGER = Logger.getLogger(InterceptedStaticMethodsProcessor.class);

static final MethodDescriptor INTERCEPTED_STATIC_METHODS_REGISTER = MethodDescriptor
.ofMethod(InterceptedStaticMethods.class, "register", void.class, String.class, InterceptedStaticMethod.class);
.ofMethod(InterceptedStaticMethods.class, "register", void.class, String.class, InterceptedMethodMetadata.class);
static final MethodDescriptor INTERCEPTED_STATIC_METHODS_AROUND_INVOKE = MethodDescriptor
.ofMethod(InterceptedStaticMethods.class, "aroundInvoke", Object.class, String.class, Object[].class);

Expand Down Expand Up @@ -322,23 +321,19 @@ private String implementInit(IndexView index, ClassCreator initializer,
}
}

// Create forwarding function
ResultHandle forwardingFunc = createForwardingFunction(init, interceptedStaticMethod.getTarget(), method);

// Now create metadata for the given intercepted method
ResultHandle metadataHandle = init.newInstance(MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR,
chainHandle, methodHandle, bindingsHandle);
chainHandle, methodHandle, bindingsHandle, forwardingFunc);

// Needed when running on native image
reflectiveMethods.produce(new ReflectiveMethodBuildItem(method));

// Create forwarding function
ResultHandle forwardingFunc = createForwardingFunction(init, interceptedStaticMethod.getTarget(), method);

ResultHandle staticMethodHandle = init.newInstance(
MethodDescriptor.ofConstructor(InterceptedStaticMethod.class, Function.class, InterceptedMethodMetadata.class),
forwardingFunc, metadataHandle);

// Call InterceptedStaticMethods.register()
init.invokeStaticMethod(INTERCEPTED_STATIC_METHODS_REGISTER, init.load(interceptedStaticMethod.getHash()),
staticMethodHandle);
metadataHandle);
init.returnValue(null);
return name;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,21 @@ Integer computeAlternativePriority(AnnotationTarget target, List<StereotypeInfo>
return alternativePriorities != null ? alternativePriorities.compute(target, stereotypes) : null;
}

Set<MethodInfo> getObserverAndProducerMethods() {
Set<MethodInfo> ret = new HashSet<>();
for (ObserverInfo observer : observers) {
if (!observer.isSynthetic()) {
ret.add(observer.getObserverMethod());
}
}
for (BeanInfo bean : beans) {
if (bean.isProducerMethod()) {
ret.add(bean.getTarget().get().asMethod());
}
}
return ret;
}

private void buildContextPut(String key, Object value) {
if (buildContext != null) {
buildContext.putInternal(key, value);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,7 +626,7 @@ private Map<MethodInfo, DecorationInfo> initDecoratedMethods() {
ClassInfo classInfo = target.get().asClass();
addDecoratedMethods(candidates, classInfo, classInfo, bound,
new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom,
beanDeployment.getBeanArchiveIndex()));
beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods()));

Map<MethodInfo, DecorationInfo> decoratedMethods = new HashMap<>(candidates.size());
for (Entry<MethodKey, DecorationInfo> entry : candidates.entrySet()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,7 @@ public final class MethodDescriptors {

public static final MethodDescriptor INVOCATION_CONTEXTS_PERFORM_AROUND_INVOKE = MethodDescriptor.ofMethod(
InvocationContexts.class,
"performAroundInvoke",
Object.class, Object.class, Method.class, Function.class, Object[].class, List.class,
Set.class);
"performAroundInvoke", Object.class, Object.class, Object[].class, InterceptedMethodMetadata.class);

public static final MethodDescriptor INVOCATION_CONTEXTS_AROUND_CONSTRUCT = MethodDescriptor.ofMethod(
InvocationContexts.class,
Expand Down Expand Up @@ -230,7 +228,7 @@ public final class MethodDescriptors {

public static final MethodDescriptor INTERCEPTED_METHOD_METADATA_CONSTRUCTOR = MethodDescriptor.ofConstructor(
InterceptedMethodMetadata.class,
List.class, Method.class, Set.class);
List.class, Method.class, Set.class, Function.class);

public static final MethodDescriptor CREATIONAL_CTX_HAS_DEPENDENT_INSTANCES = MethodDescriptor.ofMethod(
CreationalContextImpl.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ static Set<MethodInfo> addInterceptedMethodCandidates(BeanDeployment beanDeploym
return addInterceptedMethodCandidates(beanDeployment, classInfo, classInfo, candidates, Set.copyOf(classLevelBindings),
bytecodeTransformerConsumer, transformUnproxyableClasses,
new SubclassSkipPredicate(beanDeployment.getAssignabilityCheck()::isAssignableFrom,
beanDeployment.getBeanArchiveIndex()),
beanDeployment.getBeanArchiveIndex(), beanDeployment.getObserverAndProducerMethods()),
false, new HashSet<>());
}

Expand Down Expand Up @@ -519,14 +519,17 @@ static class SubclassSkipPredicate implements Predicate<MethodInfo> {

private final BiFunction<Type, Type, Boolean> assignableFromFun;
private final IndexView beanArchiveIndex;
private final Set<MethodInfo> producersAndObservers;
private ClassInfo clazz;
private ClassInfo originalClazz;
private List<MethodInfo> regularMethods;
private Set<MethodInfo> bridgeMethods = new HashSet<>();

public SubclassSkipPredicate(BiFunction<Type, Type, Boolean> assignableFromFun, IndexView beanArchiveIndex) {
public SubclassSkipPredicate(BiFunction<Type, Type, Boolean> assignableFromFun, IndexView beanArchiveIndex,
Set<MethodInfo> producersAndObservers) {
this.assignableFromFun = assignableFromFun;
this.beanArchiveIndex = beanArchiveIndex;
this.producersAndObservers = producersAndObservers;
}

void startProcessing(ClassInfo clazz, ClassInfo originalClazz) {
Expand Down Expand Up @@ -554,6 +557,13 @@ public boolean test(MethodInfo method) {
// Skip bridge methods that have a corresponding "implementation method" on the same class
// The algorithm we use to detect these methods is best effort, i.e. there might be use cases where the detection fails
return hasImplementation(method);
} else if (method.isSynthetic()) {
// Skip non-bridge synthetic methods
return true;
}
if (Modifier.isPrivate(method.flags()) && !producersAndObservers.contains(method)) {
// Skip a private method that is not and observer or producer
return true;
}
if (method.hasAnnotation(DotNames.POST_CONSTRUCT) || method.hasAnnotation(DotNames.PRE_DESTROY)) {
// @PreDestroy and @PostConstruct methods declared on the bean are NOT candidates for around invoke interception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -74,12 +73,6 @@ public class SubclassGenerator extends AbstractGenerator {

protected static final String FIELD_NAME_PREDESTROYS = "arc$preDestroys";
protected static final String FIELD_NAME_CONSTRUCTED = "arc$constructed";
protected static final FieldDescriptor FIELD_METADATA_METHOD = FieldDescriptor.of(InterceptedMethodMetadata.class, "method",
Method.class);
protected static final FieldDescriptor FIELD_METADATA_CHAIN = FieldDescriptor.of(InterceptedMethodMetadata.class, "chain",
List.class);
protected static final FieldDescriptor FIELD_METADATA_BINDINGS = FieldDescriptor.of(InterceptedMethodMetadata.class,
"bindings", Set.class);

private final Predicate<DotName> applicationClassPredicate;
private final Set<String> existingClasses;
Expand Down Expand Up @@ -349,6 +342,7 @@ public String apply(List<BindingKey> keys) {
}

MethodDescriptor methodDescriptor = MethodDescriptor.of(method);
MethodDescriptor originalMethodDescriptor = MethodDescriptor.of(method);
InterceptionInfo interception = bean.getInterceptedMethods().get(method);
DecorationInfo decoration = bean.getDecoratedMethods().get(method);
MethodDescriptor forwardDescriptor = forwardingMethods.get(methodDescriptor);
Expand Down Expand Up @@ -392,10 +386,58 @@ public String apply(List<BindingKey> keys) {
initMetadataMethodFinal.getMethodParam(1), initMetadataMethodFinal.load(bindingKey));
});

DecoratorInfo decorator = decoration != null ? decoration.decorators.get(0) : null;
ResultHandle decoratorHandle = null;
if (decorator != null) {
decoratorHandle = initMetadataMethod.readInstanceField(FieldDescriptor.of(subclass.getClassName(),
decorator.getIdentifier(), Object.class.getName()), initMetadataMethod.getThis());
}

// Instantiate the forwarding function
// Function<InvocationContext, Object> forward = ctx -> super.foo((java.lang.String)ctx.getParameters()[0])
FunctionCreator func = initMetadataMethod.createFunction(Function.class);
BytecodeCreator funcBytecode = func.getBytecode();
ResultHandle ctxHandle = funcBytecode.getMethodParam(0);
ResultHandle[] superParamHandles;
if (parameters.isEmpty()) {
superParamHandles = new ResultHandle[0];
} else {
superParamHandles = new ResultHandle[parameters.size()];
ResultHandle ctxParamsHandle = funcBytecode.invokeInterfaceMethod(
MethodDescriptor.ofMethod(InvocationContext.class, "getParameters", Object[].class),
ctxHandle);
// autoboxing is handled inside Gizmo
for (int i = 0; i < superParamHandles.length; i++) {
superParamHandles[i] = funcBytecode.readArrayValue(ctxParamsHandle, i);
}
}
// If a decorator is bound then invoke the method upon the decorator instance instead of the generated forwarding method
if (decorator != null) {
AssignableResultHandle funDecoratorInstance = funcBytecode.createVariable(Object.class);
funcBytecode.assign(funDecoratorInstance, decoratorHandle);
String declaringClass = decorator.getBeanClass().toString();
if (decorator.isAbstract()) {
String baseName = DecoratorGenerator.createBaseName(decorator.getTarget().get().asClass());
String targetPackage = DotNames.packageName(decorator.getProviderType().name());
declaringClass = generatedNameFromTarget(targetPackage, baseName,
DecoratorGenerator.ABSTRACT_IMPL_SUFFIX);
}
MethodDescriptor virtualMethodDescriptor = MethodDescriptor.ofMethod(declaringClass,
originalMethodDescriptor.getName(),
originalMethodDescriptor.getReturnType(), originalMethodDescriptor.getParameterTypes());
funcBytecode
.returnValue(funcBytecode.invokeVirtualMethod(virtualMethodDescriptor, funDecoratorInstance,
superParamHandles));
} else {
ResultHandle superResult = funcBytecode.invokeVirtualMethod(forwardDescriptor, initMetadataMethod.getThis(),
superParamHandles);
funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull());
}

// Now create metadata for the given intercepted method
ResultHandle methodMetadataHandle = initMetadataMethod.newInstance(
MethodDescriptors.INTERCEPTED_METHOD_METADATA_CONSTRUCTOR,
chainHandle, methodHandle, bindingsHandle);
chainHandle, methodHandle, bindingsHandle, func.getInstance());

FieldDescriptor metadataField = FieldDescriptor.of(subclass.getClassName(), "arc$" + methodIdx++,
InterceptedMethodMetadata.class.getName());
Expand Down Expand Up @@ -769,52 +811,6 @@ private void createInterceptedMethod(ClassOutput classOutput, BeanInfo bean, Met
notConstructed.returnValue(notConstructed.invokeVirtualMethod(forwardMethod, notConstructed.getThis(), params));
}

ResultHandle decoratorHandle = null;
if (decorator != null) {
decoratorHandle = interceptedMethod.readInstanceField(FieldDescriptor.of(subclass.getClassName(),
decorator.getIdentifier(), Object.class.getName()), interceptedMethod.getThis());
}

// Forwarding function
// Function<InvocationContext, Object> forward = ctx -> super.foo((java.lang.String)ctx.getParameters()[0])
FunctionCreator func = interceptedMethod.createFunction(Function.class);
BytecodeCreator funcBytecode = func.getBytecode();
ResultHandle ctxHandle = funcBytecode.getMethodParam(0);
ResultHandle[] superParamHandles;
if (parameters.isEmpty()) {
superParamHandles = new ResultHandle[0];
} else {
superParamHandles = new ResultHandle[parameters.size()];
ResultHandle ctxParamsHandle = funcBytecode.invokeInterfaceMethod(
MethodDescriptor.ofMethod(InvocationContext.class, "getParameters", Object[].class),
ctxHandle);
// autoboxing is handled inside Gizmo
for (int i = 0; i < superParamHandles.length; i++) {
superParamHandles[i] = funcBytecode.readArrayValue(ctxParamsHandle, i);
}
}
// If a decorator is bound then invoke the method upon the decorator instance instead of the generated forwarding method
if (decorator != null) {
AssignableResultHandle funDecoratorInstance = funcBytecode.createVariable(Object.class);
funcBytecode.assign(funDecoratorInstance, decoratorHandle);
String declaringClass = decorator.getBeanClass().toString();
if (decorator.isAbstract()) {
String baseName = DecoratorGenerator.createBaseName(decorator.getTarget().get().asClass());
String targetPackage = DotNames.packageName(decorator.getProviderType().name());
declaringClass = generatedNameFromTarget(targetPackage, baseName, DecoratorGenerator.ABSTRACT_IMPL_SUFFIX);
}
MethodDescriptor methodDescriptor = MethodDescriptor.ofMethod(
declaringClass, originalMethodDescriptor.getName(),
originalMethodDescriptor.getReturnType(), originalMethodDescriptor.getParameterTypes());
funcBytecode
.returnValue(funcBytecode.invokeVirtualMethod(methodDescriptor, funDecoratorInstance, superParamHandles));

} else {
ResultHandle superResult = funcBytecode.invokeVirtualMethod(forwardMethod, interceptedMethod.getThis(),
superParamHandles);
funcBytecode.returnValue(superResult != null ? superResult : funcBytecode.loadNull());
}

for (Type declaredException : method.exceptions()) {
interceptedMethod.addException(declaredException.name().toString());
}
Expand Down Expand Up @@ -857,10 +853,7 @@ private void createInterceptedMethod(ClassOutput classOutput, BeanInfo bean, Met
// InvocationContexts.performAroundInvoke(...)
ResultHandle methodMetadataHandle = tryCatch.readInstanceField(metadataField, tryCatch.getThis());
ResultHandle ret = tryCatch.invokeStaticMethod(MethodDescriptors.INVOCATION_CONTEXTS_PERFORM_AROUND_INVOKE,
tryCatch.getThis(),
tryCatch.readInstanceField(FIELD_METADATA_METHOD, methodMetadataHandle), func.getInstance(), paramsHandle,
tryCatch.readInstanceField(FIELD_METADATA_CHAIN, methodMetadataHandle),
tryCatch.readInstanceField(FIELD_METADATA_BINDINGS, methodMetadataHandle));
tryCatch.getThis(), paramsHandle, methodMetadataHandle);
tryCatch.returnValue(ret);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

Expand All @@ -24,7 +25,8 @@ public class SubclassSkipPredicateTest {
public void testPredicate() throws IOException {
IndexView index = Basics.index(Base.class, Submarine.class, Long.class, Number.class);
AssignabilityCheck assignabilityCheck = new AssignabilityCheck(index, null);
SubclassSkipPredicate predicate = new SubclassSkipPredicate(assignabilityCheck::isAssignableFrom, null);
SubclassSkipPredicate predicate = new SubclassSkipPredicate(assignabilityCheck::isAssignableFrom, null,
Collections.emptySet());

ClassInfo submarineClass = index.getClassByName(DotName.createSimple(Submarine.class.getName()));
predicate.startProcessing(submarineClass, submarineClass);
Expand Down
Loading