Skip to content

Commit

Permalink
ArC - reduce allocations for intercepted methods
Browse files Browse the repository at this point in the history
- forwarding lambdas are stateless and thus may become part of immutable InterceptedMethodMetadata
- note that metadata are shared accross all invocations of an intercepted method
  • Loading branch information
mkouba committed Feb 6, 2023
1 parent 85fd385 commit 0ed5b7b
Show file tree
Hide file tree
Showing 14 changed files with 306 additions and 187 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 @@ -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 @@ -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 @@ -2,6 +2,7 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -16,41 +17,25 @@ abstract class AbstractInvocationContext implements ArcInvocationContext {

private static final Object[] EMPTY_PARAMS = new Object[0];

protected final Method method;
protected final Constructor<?> constructor;
protected final Set<Annotation> interceptorBindings;
protected final List<InterceptorInvocation> chain;
protected Object target;
protected Object[] parameters;
protected ContextDataMap contextData;

protected AbstractInvocationContext(Object target, Method method,
Constructor<?> constructor,
Object[] parameters, ContextDataMap contextData,
Set<Annotation> interceptorBindings, List<InterceptorInvocation> chain) {
protected AbstractInvocationContext(Object target, Object[] parameters, ContextDataMap contextData) {
this.target = target;
this.method = method;
this.constructor = constructor;
this.parameters = parameters != null ? parameters : EMPTY_PARAMS;
this.contextData = contextData != null ? contextData : new ContextDataMap(interceptorBindings);
this.interceptorBindings = interceptorBindings;
this.chain = chain;
this.contextData = contextData;
}

@Override
public Map<String, Object> getContextData() {
return contextData;
}

@Override
public Set<Annotation> getInterceptorBindings() {
return interceptorBindings;
}

@SuppressWarnings("unchecked")
@Override
public <T extends Annotation> T findIterceptorBinding(Class<T> annotationType) {
for (Annotation annotation : interceptorBindings) {
for (Annotation annotation : getInterceptorBindings()) {
if (annotation.annotationType().equals(annotationType)) {
return (T) annotation;
}
Expand All @@ -62,33 +47,17 @@ public <T extends Annotation> T findIterceptorBinding(Class<T> annotationType) {
@Override
public <T extends Annotation> List<T> findIterceptorBindings(Class<T> annotationType) {
List<T> found = new ArrayList<>();
for (Annotation annotation : (Set<Annotation>) interceptorBindings) {
for (Annotation annotation : (Set<Annotation>) getInterceptorBindings()) {
if (annotation.annotationType().equals(annotationType)) {
found.add((T) annotation);
}
}
return found;
}

@Override
public Method getMethod() {
return method;
}

@Override
public Object[] getParameters() {
return parameters;
}

@Override
public void setParameters(Object[] params) {
validateParameters(params);
this.parameters = params;
}

protected void validateParameters(Object[] params) {
protected void validateParameters(Executable executable, Object[] params) {
int newParametersCount = Objects.requireNonNull(params).length;
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?>[] parameterTypes = executable.getParameterTypes();
if (parameterTypes.length != newParametersCount) {
throw new IllegalArgumentException(
"Wrong number of parameters - method has " + Arrays.toString(parameterTypes) + ", attempting to set "
Expand All @@ -108,6 +77,11 @@ protected void validateParameters(Object[] params) {
}
}

@Override
public Method getMethod() {
return null;
}

@Override
public Object getTarget() {
return target;
Expand All @@ -120,7 +94,7 @@ public Object getTimer() {

@Override
public Constructor<?> getConstructor() {
return constructor;
return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,34 @@
*/
class AroundConstructInvocationContext extends LifecycleCallbackInvocationContext {

private final Constructor<?> constructor;
private final Supplier<Object> aroundConstructForward;

AroundConstructInvocationContext(Constructor<?> constructor, Object[] parameters, Set<Annotation> interceptorBindings,
List<InterceptorInvocation> chain, Supplier<Object> aroundConstructForward) {
super(null, constructor, parameters, interceptorBindings, chain);
super(null, parameters, interceptorBindings, chain);
this.aroundConstructForward = aroundConstructForward;
this.constructor = constructor;
}

protected void interceptorChainCompleted() throws Exception {
target = aroundConstructForward.get();
}

@Override
public Constructor<?> getConstructor() {
return constructor;
}

@Override
public Object[] getParameters() {
return parameters;
}

@Override
public void setParameters(Object[] params) {
validateParameters(constructor, params);
this.parameters = params;
}

}
Loading

0 comments on commit 0ed5b7b

Please sign in to comment.