Skip to content

Commit

Permalink
Merge pull request quarkusio#30816 from mkouba/arc-ic-reduce-allocations
Browse files Browse the repository at this point in the history
ArC - reduce allocations for intercepted methods
  • Loading branch information
gsmet authored Feb 6, 2023
2 parents 0fefa86 + 0d3d692 commit 25470b6
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 191 deletions.
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 @@ -650,7 +650,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

0 comments on commit 25470b6

Please sign in to comment.