
com.tangosol.internal.util.invoke.lambda.RemotableLambdaGenerator Maven / Gradle / Ivy
/*
* Copyright (c) 2000, 2020, Oracle and/or its affiliates.
*
* Licensed under the Universal Permissive License v 1.0 as shown at
* http://oss.oracle.com/licenses/upl.
*/
package com.tangosol.internal.util.invoke.lambda;
import com.tangosol.internal.util.invoke.ClassIdentity;
import com.tangosol.internal.util.invoke.Lambdas;
import com.tangosol.internal.util.invoke.RemotableClassGenerator;
import com.tangosol.internal.util.invoke.Remotable;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.invoke.MethodHandleInfo;
import java.lang.invoke.SerializedLambda;
import java.util.List;
import static com.tangosol.dev.assembler.Method.toTypes;
import static org.objectweb.asm.Opcodes.ACC_FINAL;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.GETFIELD;
import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.PUTFIELD;
import static org.objectweb.asm.Opcodes.RETURN;
/**
* RemotableLambdaGenerator is a helper that transforms a locally defined
* {@link Serializable} lambda into a {@link Remotable} class.
*
* ClassDefinition's are transportable definitions absent of state that
* allow a function to be identified uniquely based upon it's instructions.
* This is particularly beneficial for recipients of many FunctionDefinitions
* that wish to forgo the cost of duplicate definitions for identical functions.
*
* The primary usage of this generator is via the following method:
*
* - {@link Lambdas#createDefinition(ClassIdentity, Serializable, ClassLoader)}
*
* Note: to output the generated class file to the file system use the JVM argument
* {@code -Dcoherence.lambda.dumpClasses=/path/to/classfiles}.
*
* @author hr/as 2015.03.30
* @since 12.2.1
*/
// Internal Notes:
// This class is tightly coupled the JDK's lambda implementation, including
// the internal implementation/conventions they chose. While this seems like
// volatile ground it is unlikely that they will change these conventions
// too drastically.
// In summary the createDefinition performs the following:
// 1. Create a new class that implements the SAM (Single Abstract Method)
// implemented by the provided function.
// 2. Create a public constructor that accepts the captured arguments.
// 3. Ensure the class extends AbstractRemotableLambda; this offers optimizations
// that can be performed when the ClassDefinition is loaded and instantiated.
// 4. Copy the function implementation (on the capturing class) to the generated
// class, thus making the generated lambda class self-contained.
// 5. Perform the appropriate type coercion from the interface type to the
// function implementation's expected type, for example below we need to
// coerce from a Number to an Integer:
//
// I { void foo(T t); }
// I i = t -> t + 1
//
public final class RemotableLambdaGenerator
extends RemotableClassGenerator
{
// ----- bytecode lambda generation methods -----------------------------
/**
* Return a generated ClassFile that entirely encapsulates the lambda,
* including implementing the required interfaces and containing the logic
* of the lambda.
*
* @param sClassName the name of the ClassFile
* @param lambdaMetadata the {@link SerializedLambda} that holds the lambda
* metadata
* @param loader the ClassLoader used to locate the lambda implementation
* (in the capturing class)
*
* @return the bytes representing a generated ClassFile entirely capsulating
* the lambda function
*/
public static byte[] createRemoteLambdaClass(String sClassName, SerializedLambda lambdaMetadata, ClassLoader loader)
{
String[] asIfaces = {lambdaMetadata.getFunctionalInterfaceClass()};
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(/*JDK8*/ 52, ACC_PUBLIC + ACC_FINAL + ACC_SYNTHETIC,
sClassName, null, ABSTRACT_REMOTE_LAMBDA_NAME, asIfaces);
// create data members
// SerializedLambda has 3 signatures as described below:
// 1. SAM/Functional Interface - signature with type bound on the interface
// 2. Implementation - actual signature of the implementation; this
// can vary between method references and lambdas. With lambdas
// this includes the captured arguments, but with method refs this
// is the signature of the method being referenced.
// 3. Instantiated - signature at the call site, potentially narrowing
// the types in the SAM signature.
//
// Function f = lambda.Value::getString
// 1. (Ljava/lang/Object;)Ljava/lang/Object;
// 2. ()Ljava/lang/String;
// 3. (Llambda/Value;)Ljava/lang/String;
//
// String s = "..."
// Remote.Function f2 = v -> s + v.name();
// 1. (Ljava/lang/Object;)Ljava/lang/Object;
// 2. (Ljava/lang/String;Llambda/Value;)Ljava/lang/String;
// 3. (Llambda/Value;)Ljava/lang/String;
//
// The use of Implementation or Instantiated signatures is conditional
// based upon the source being a lambda or a method reference
String sInvMethodName = lambdaMetadata.getImplMethodName();
String sInvMethodSig = lambdaMetadata.getImplMethodSignature();
String sSAMSig = lambdaMetadata.getFunctionalInterfaceMethodSignature();
String[] asSAMSig = toTypes(lambdaMetadata.getFunctionalInterfaceMethodSignature());
String[] asImplSig = toTypes(sInvMethodSig);
String[] asDestSig = toTypes(lambdaMetadata.getInstantiatedMethodType());
int cArgs = lambdaMetadata.getCapturedArgCount();
int nSigDelta = asDestSig.length - (asImplSig.length - cArgs);
if (nSigDelta == 0)
{
asDestSig = asImplSig;
}
else
{
assert nSigDelta == 1 : "Unexpected differences between lambda signatures: " + lambdaMetadata;
asDestSig[0] = asImplSig[0]; // return type
// assume the first argument on the dest/instantiated type is the
// receiver (the type an invokevirtual is targeted at)
// Note: the JDK implementation generates two checkcast instructions
// in the case where the receiver within the instantiatedMethodType
// is narrower than the implClass, whereas this implementation
// simply generates one checkcast assuming the narrower type
// can always be cast to the wider type
for (int i = 1, c = asDestSig.length - 1; i < c; ++i)
{
asDestSig[i + 1] = asImplSig[i + cArgs];
}
}
String[] asArgNames = new String[cArgs];
// generate fields
{
for (int i = 0; i < cArgs; ++i)
{
asArgNames[i] = "arg$" + (i + 1);
cw.visitField(Opcodes.ACC_PRIVATE + ACC_FINAL,
asArgNames[i], asImplSig[i + 1], null, null)
.visitEnd();
}
}
// (captured-args1..n)
{
String sConSig = createConstructorSig(asImplSig, cArgs);
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "", sConSig, null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, ABSTRACT_REMOTE_LAMBDA_NAME, "", Type.getMethodDescriptor(Type.VOID_TYPE), false);
for (int i = 0, c = asArgNames.length, lvIndex = 1; i < c; ++i)
{
String sArgType = asImplSig[i + 1];
int nOpcode = getLoadOpcode(sArgType);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(nOpcode, lvIndex);
mv.visitFieldInsn(PUTFIELD, sClassName, asArgNames[i], sArgType);
lvIndex += nOpcode == Opcodes.LLOAD || nOpcode == Opcodes.DLOAD
? 2 : 1;
}
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
// initialize the arguments used to construct the invocation in the
// SAM implementation; to either call the copied lambda$method or call
// the method reference
int nMethKind = lambdaMetadata.getImplMethodKind();
int iOpInvoke = toInvokeOp(nMethKind);
String sInvClassName = lambdaMetadata.getImplClass();
if (Lambdas.isLambdaMethod(sInvMethodName))
{
copyLambdaMethod(cw, lambdaMetadata, loader);
sInvClassName = sClassName;
}
// SAM implementation
{
MethodVisitor mv = cw.visitMethod(ACC_PUBLIC,
lambdaMetadata.getFunctionalInterfaceMethodName(), sSAMSig, null, null);
mv.visitCode();
// method reference to a constructor
if (nMethKind == MethodHandleInfo.REF_newInvokeSpecial)
{
mv.visitTypeInsn(NEW, sInvClassName);
mv.visitInsn(DUP);
}
// push the captured args on the stack
for (int i = 0, c = asArgNames.length; i < c; ++i)
{
mv.visitVarInsn(ALOAD, 0);
mv.visitFieldInsn(GETFIELD, sClassName, asArgNames[i], asImplSig[i + 1]);
}
// convert the interface types
assert asSAMSig.length == asDestSig.length - cArgs;
for (int i = 1, c = asSAMSig.length, lvIndex = 1; i < c; ++i)
{
String sTypeSAM = asSAMSig[i];
String sTypeDest = asDestSig[i + cArgs];
int nOpcode = getLoadOpcode(sTypeSAM);
mv.visitVarInsn(nOpcode, lvIndex);
lvIndex += nOpcode == Opcodes.LLOAD || nOpcode == Opcodes.DLOAD
? 2 : 1;
coerceType(mv, sTypeSAM, sTypeDest);
}
mv.visitMethodInsn(iOpInvoke, sInvClassName, sInvMethodName,
sInvMethodSig, iOpInvoke == INVOKEINTERFACE);
coerceType(mv, asDestSig[0], asSAMSig[0]);
mv.visitInsn(getReturnOpcode(asSAMSig[0]));
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
/**
* Return a method signature for the constructor on the generated lambda
* class.
*
* @param asArgs the captured argument types the constructor should accept
* @param cMax maximum number of arguments to accept from asArgs
*
* @return a method signature for the constructor on the generated lambda
* class
*/
private static String createConstructorSig(String[] asArgs, int cMax)
{
assert cMax <= asArgs.length;
StringBuilder sb = new StringBuilder("(");
for (int i = 0; i < cMax; ++i)
{
sb.append(asArgs[i + 1]);
}
return sb.append(")V").toString();
}
/**
* Generate a new method in the provided {@link ClassWriter} that has the
* implementation of the lambda represented by the provided {@link SerializedLambda}.
*
* @param cw ClassWriter to generate the method into
* @param lambdaMetadata the lambda metadata used to source the implementation
* @param loader the ClassLoader used to load the capturing class
*
* @throws IllegalStateException if an error was encountered during method
* generation
*/
private static String copyLambdaMethod(ClassWriter cw, SerializedLambda lambdaMetadata, ClassLoader loader)
{
String sImplClassName = lambdaMetadata.getImplClass();
String sMethodName = lambdaMetadata.getImplMethodName();
String sMethodSig = lambdaMetadata.getImplMethodSignature();
try (InputStream is = loader.getResourceAsStream(sImplClassName + ".class"))
{
ClassReader reader = new ClassReader(is);
ClassNode implClass = new ClassNode();
reader.accept(implClass, 0);
@SuppressWarnings("unchecked")
List listMethod = (List) implClass.methods;
MethodNode methMatch = null;
for (MethodNode mn : listMethod)
{
if (mn.name.equals(sMethodName) && mn.desc.equals(sMethodSig))
{
mn.instructions.resetLabels();
methMatch = mn;
break;
}
}
if (methMatch != null)
{
// copy the found method to the new ClassFile
methMatch.accept(cw.visitMethod(Opcodes.ACC_PRIVATE | ACC_STATIC,
sMethodName, sMethodSig, null, null));
return sMethodName;
}
throw new IllegalStateException("Expected lambda method not found: " +
sImplClassName + '.' + sMethodName + sMethodSig);
}
catch (IOException e)
{
throw new IllegalStateException("Unexpected error in copying lambda implementation from:" +
sImplClassName + '.' + sMethodName + sMethodSig, e);
}
}
// ----- private constants ----------------------------------------------
/**
* AbstractRemotableLambda type name.
*/
private static final String ABSTRACT_REMOTE_LAMBDA_NAME = Type.getType(AbstractRemotableLambda.class).getInternalName();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy