kilim.analysis.SAMweaver Maven / Gradle / Ivy
Show all versions of kilim Show documentation
// copyright 2016 nqzero, 2014 sriram srinivasan - offered under the terms of the MIT License
package kilim.analysis;
import kilim.Constants;
import kilim.mirrors.CachedClassMirrors.ClassMirror;
import kilim.mirrors.CachedClassMirrors.MethodMirror;
import kilim.mirrors.ClassMirrorNotFoundException;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
/**
* {@code SAMweaver} generates code to support functional interfaces (also known
* as SAM, for Single Abstract Method), where the SAM method is pausable. What
* makes SAM interfaces special compared to regular interfaces is that they can
* represent a lambda function in java8. More on this later.
*
* Imagine that a class called X
uses a I
of the
* following form:
*
*
* interface I {
* int foo(double d) throws Pausable;
* }
*
*
* Since I
is a SAM, {@code CallWeaver} replaces all
* invokeinterface I.foo(double)
with a static invocation of a tiny
* wrapper method ('shim') in X like this:
*
*
* invokestatic X.$shim$2(I callee, double d, Fiber f)
*
*
* The shim method in turn turns around and calls I.foo(double)
:
*
*
* private static int X.$shim$2(I callee, double d, Fiber f) {
* int ret = callee.f(d, f);
* f.setCallee(callee); // this is the purpose of the shim
* return ret;
* }
*
*
* The purpose of {@code SAMweaver} is to generate the shim above.
*
* Why?
*
* Ordinarily, all hand-written code is modified by the weaver if it contains
* definitions or invocations of pausable methods. Lambda expressions however
* rely on the VM generating a class at run-time, which implements the
* functional interface (I
in the example). The problem is that
* this class needs to be woven to support kilim Fibers, but we don't have an
* easy portable hook to weave it at run-time.
*
* As it turns out, practically all the weaving work is already complete at
* compile time. This is because, the body of the lambda expression is already
* available to the weaver as an ordinary method in the host class
* X
, and is treated like any another pausable method. In other
* words, the transformations at the calling site and in the body of the called
* method are as usual.
*
* All that this is left for the shim to do is to capture the object's reference
* (f.setCallee
); the fiber needs it while resuming. The call to
* setCallee is redundant for ordinary hand-written implementations of SAM
* interfaces, but is necessary if the implementation happens to be generated by
* the VM as described above.
*
*
* Of course, all this applies only if the functional method is pausable.
*/
public class SAMweaver implements Constants {
String interfaceName;
String methodName;
String desc;
boolean itf;
int index = -1;
KilimContext context;
public SAMweaver(KilimContext context,String interfaceName, String methodName, String desc,
boolean itf) {
this.interfaceName = interfaceName;
this.methodName = methodName;
this.desc = desc;
this.itf = itf;
this.context = context;
}
public void setIndex(int index) {
this.index = index;
}
public boolean equals(Object obj) {
if (obj instanceof SAMweaver) {
SAMweaver that = (SAMweaver) obj;
return desc.equals(that.desc) && methodName.equals(that.methodName)
&& interfaceName.equals(that.interfaceName);
}
return false;
}
public String toString() {
return interfaceName+"."+methodName+desc + " ->" +getShimMethodName();
}
public int hashCode() {
return methodName.hashCode() ^ desc.hashCode();
}
String getShimMethodName() {
assert index >= 0;
return SAM_SHIM_PREFIX + index;
}
String getShimDesc() {
return this.desc.replace("(", "(" + TypeDesc.getInterned(this.interfaceName))
.replace(")", Constants.D_FIBER_LAST_ARG);
}
/**
* Generate a method like this:
*
*
* private static $shim$1 (I callee, ...args..., f fiber) {
* load each arg
* call interface.method
* f.setCallee(arg0)
* xret
* }
*
*
* @param cv
*/
public void accept(ClassVisitor cv) {
String shimDesc = getShimDesc();
MethodVisitor mv = cv.visitMethod(ACC_STATIC | ACC_PRIVATE, getShimMethodName(), shimDesc, null,
getExceptions());
// load arguments
int ivar = 0;
for (String argType : TypeDesc.getArgumentTypes(shimDesc)) {
int vmt = VMType.toVmType(argType);
mv.visitVarInsn(VMType.loadInsn[vmt], ivar);
ivar += VMType.category[vmt]; // register width = 2 if double or
// long, 1 otherwise
}
int fiberVar = ivar - 1;
// invoke interface
String fiberDesc = desc.replace(")", Constants.D_FIBER + ")");
mv.visitMethodInsn(INVOKEINTERFACE, interfaceName, methodName, fiberDesc, true);
// store callee object reference in fiber
mv.visitVarInsn(ALOAD, fiberVar);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKEVIRTUAL, FIBER_CLASS, "setCallee", "(" + D_OBJECT + ")V", false);
// return .. RETURN (if void) or ARETURN, IRETURN, etc.
String retDesc = TypeDesc.getReturnTypeDesc(shimDesc);
if (retDesc.charAt(0) == 'V') {
mv.visitInsn(RETURN);
} else {
int vmt = VMType.toVmType(retDesc);
mv.visitInsn(VMType.retInsn[vmt]);
}
mv.visitMaxs(0, 0); // maxLocals and maxStackDepth will be computed by asm's MethodWriter
mv.visitEnd();
}
static String [] internalName(String [] words) {
if (words==null) return words;
String [] mod = new String[words.length];
for (int ii = 0; ii < mod.length; ii++) mod[ii] = words[ii].replace('.','/');
return mod;
}
private String[] getExceptions() {
try {
ClassMirror cm = context.detector.classForName(interfaceName);
for (MethodMirror m : cm.getDeclaredMethods()) {
if (m.getName().equals(this.methodName)
&& m.getMethodDescriptor().equals(this.desc)) {
// Convert dots to slashes.
String[] ret = m.getExceptionTypes();
if (ret != null)
return internalName(ret);
break;
}
}
} catch (ClassMirrorNotFoundException cmnfe) {
}
// Should not happen at this stage. If this class weren't found, it
// would have created a
// problem much earlier on.
assert false : "Class Mirror not found for interface " + interfaceName;
return new String[0];
}
}