/*
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package java.lang.invoke;
import java.io.*;
import java.util.*;
import java.lang.reflect.
Modifier;
import jdk.internal.org.objectweb.asm.*;
import static java.lang.invoke.
LambdaForm.*;
import static java.lang.invoke.
LambdaForm.
BasicType.*;
import static java.lang.invoke.
MethodHandleStatics.*;
import static java.lang.invoke.
MethodHandleNatives.
Constants.*;
import sun.invoke.util.
VerifyAccess;
import sun.invoke.util.
VerifyType;
import sun.invoke.util.
Wrapper;
import sun.reflect.misc.
ReflectUtil;
/**
* Code generation backend for LambdaForm.
* <p>
* @author John Rose, JSR 292 EG
*/
class
InvokerBytecodeGenerator {
/** Define class names for convenience. */
private static final
String MH = "java/lang/invoke/MethodHandle";
private static final
String MHI = "java/lang/invoke/MethodHandleImpl";
private static final
String LF = "java/lang/invoke/LambdaForm";
private static final
String LFN = "java/lang/invoke/LambdaForm$Name";
private static final
String CLS = "java/lang/Class";
private static final
String OBJ = "java/lang/Object";
private static final
String OBJARY = "[Ljava/lang/Object;";
private static final
String MH_SIG = "L" +
MH + ";";
private static final
String LF_SIG = "L" +
LF + ";";
private static final
String LFN_SIG = "L" +
LFN + ";";
private static final
String LL_SIG = "(L" +
OBJ + ";)L" +
OBJ + ";";
private static final
String LLV_SIG = "(L" +
OBJ + ";L" +
OBJ + ";)V";
private static final
String CLL_SIG = "(L" +
CLS + ";L" +
OBJ + ";)L" +
OBJ + ";";
/** Name of its super class*/
private static final
String superName =
OBJ;
/** Name of new class */
private final
String className;
/** Name of the source file (for stack trace printing). */
private final
String sourceFile;
private final
LambdaForm lambdaForm;
private final
String invokerName;
private final
MethodType invokerType;
/** Info about local variables in compiled lambda form */
private final int[]
localsMap; // index
private final
BasicType[]
localTypes; // basic type
private final
Class<?>[]
localClasses; // type
/** ASM bytecode generation. */
private
ClassWriter cw;
private
MethodVisitor mv;
private static final
MemberName.
Factory MEMBERNAME_FACTORY =
MemberName.
getFactory();
private static final
Class<?>
HOST_CLASS =
LambdaForm.class;
/** Main constructor; other constructors delegate to this one. */
private
InvokerBytecodeGenerator(
LambdaForm lambdaForm, int
localsMapSize,
String className,
String invokerName,
MethodType invokerType) {
if (
invokerName.
contains(".")) {
int
p =
invokerName.
indexOf(".");
className =
invokerName.
substring(0,
p);
invokerName =
invokerName.
substring(
p+1);
}
if (
DUMP_CLASS_FILES) {
className =
makeDumpableClassName(
className);
}
this.
className =
LF + "$" +
className;
this.
sourceFile = "LambdaForm$" +
className;
this.
lambdaForm =
lambdaForm;
this.
invokerName =
invokerName;
this.
invokerType =
invokerType;
this.
localsMap = new int[
localsMapSize+1];
// last entry of localsMap is count of allocated local slots
this.
localTypes = new
BasicType[
localsMapSize+1];
this.
localClasses = new
Class<?>[
localsMapSize+1];
}
/** For generating LambdaForm interpreter entry points. */
private
InvokerBytecodeGenerator(
String className,
String invokerName,
MethodType invokerType) {
this(null,
invokerType.
parameterCount(),
className,
invokerName,
invokerType);
// Create an array to map name indexes to locals indexes.
localTypes[
localTypes.length - 1] =
V_TYPE;
for (int
i = 0;
i <
localsMap.length;
i++) {
localsMap[
i] =
invokerType.
parameterSlotCount() -
invokerType.
parameterSlotDepth(
i);
if (
i <
invokerType.
parameterCount())
localTypes[
i] =
basicType(
invokerType.
parameterType(
i));
}
}
/** For generating customized code for a single LambdaForm. */
private
InvokerBytecodeGenerator(
String className,
LambdaForm form,
MethodType invokerType) {
this(
form,
form.
names.length,
className,
form.
debugName,
invokerType);
// Create an array to map name indexes to locals indexes.
Name[]
names =
form.
names;
for (int
i = 0,
index = 0;
i <
localsMap.length;
i++) {
localsMap[
i] =
index;
if (
i <
names.length) {
BasicType type =
names[
i].
type();
index +=
type.
basicTypeSlots();
localTypes[
i] =
type;
}
}
}
/** instance counters for dumped classes */
private final static
HashMap<
String,
Integer>
DUMP_CLASS_FILES_COUNTERS;
/** debugging flag for saving generated class files */
private final static
File DUMP_CLASS_FILES_DIR;
static {
if (
DUMP_CLASS_FILES) {
DUMP_CLASS_FILES_COUNTERS = new
HashMap<>();
try {
File dumpDir = new
File("DUMP_CLASS_FILES");
if (!
dumpDir.
exists()) {
dumpDir.
mkdirs();
}
DUMP_CLASS_FILES_DIR =
dumpDir;
System.
out.
println("Dumping class files to "+
DUMP_CLASS_FILES_DIR+"/...");
} catch (
Exception e) {
throw
newInternalError(
e);
}
} else {
DUMP_CLASS_FILES_COUNTERS = null;
DUMP_CLASS_FILES_DIR = null;
}
}
static void
maybeDump(final
String className, final byte[]
classFile) {
if (
DUMP_CLASS_FILES) {
java.security.
AccessController.
doPrivileged(
new java.security.
PrivilegedAction<
Void>() {
public
Void run() {
try {
String dumpName =
className;
//dumpName = dumpName.replace('/', '-');
File dumpFile = new
File(
DUMP_CLASS_FILES_DIR,
dumpName+".class");
System.
out.
println("dump: " +
dumpFile);
dumpFile.
getParentFile().
mkdirs();
FileOutputStream file = new
FileOutputStream(
dumpFile);
file.
write(
classFile);
file.
close();
return null;
} catch (
IOException ex) {
throw
newInternalError(
ex);
}
}
});
}
}
private static
String makeDumpableClassName(
String className) {
Integer ctr;
synchronized (
DUMP_CLASS_FILES_COUNTERS) {
ctr =
DUMP_CLASS_FILES_COUNTERS.
get(
className);
if (
ctr == null)
ctr = 0;
DUMP_CLASS_FILES_COUNTERS.
put(
className,
ctr+1);
}
String sfx =
ctr.
toString();
while (
sfx.
length() < 3)
sfx = "0"+
sfx;
className +=
sfx;
return
className;
}
class
CpPatch {
final int
index;
final
String placeholder;
final
Object value;
CpPatch(int
index,
String placeholder,
Object value) {
this.
index =
index;
this.
placeholder =
placeholder;
this.
value =
value;
}
public
String toString() {
return "CpPatch/index="+
index+",placeholder="+
placeholder+",value="+
value;
}
}
Map<
Object,
CpPatch>
cpPatches = new
HashMap<>();
int
cph = 0; // for counting constant placeholders
String constantPlaceholder(
Object arg) {
String cpPlaceholder = "CONSTANT_PLACEHOLDER_" +
cph++;
if (
DUMP_CLASS_FILES)
cpPlaceholder += " <<" +
debugString(
arg) + ">>"; // debugging aid
if (
cpPatches.
containsKey(
cpPlaceholder)) {
throw new
InternalError("observed CP placeholder twice: " +
cpPlaceholder);
}
// insert placeholder in CP and remember the patch
int
index =
cw.
newConst((
Object)
cpPlaceholder); // TODO check if aready in the constant pool
cpPatches.
put(
cpPlaceholder, new
CpPatch(
index,
cpPlaceholder,
arg));
return
cpPlaceholder;
}
Object[]
cpPatches(byte[]
classFile) {
int
size =
getConstantPoolSize(
classFile);
Object[]
res = new
Object[
size];
for (
CpPatch p :
cpPatches.
values()) {
if (
p.
index >=
size)
throw new
InternalError("in cpool["+
size+"]: "+
p+"\n"+
Arrays.
toString(
Arrays.
copyOf(
classFile, 20)));
res[
p.
index] =
p.
value;
}
return
res;
}
private static
String debugString(
Object arg) {
if (
arg instanceof
MethodHandle) {
MethodHandle mh = (
MethodHandle)
arg;
MemberName member =
mh.
internalMemberName();
if (
member != null)
return
member.
toString();
return
mh.
debugString();
}
return
arg.
toString();
}
/**
* Extract the number of constant pool entries from a given class file.
*
* @param classFile the bytes of the class file in question.
* @return the number of entries in the constant pool.
*/
private static int
getConstantPoolSize(byte[]
classFile) {
// The first few bytes:
// u4 magic;
// u2 minor_version;
// u2 major_version;
// u2 constant_pool_count;
return ((
classFile[8] & 0xFF) << 8) | (
classFile[9] & 0xFF);
}
/**
* Extract the MemberName of a newly-defined method.
*/
private
MemberName loadMethod(byte[]
classFile) {
Class<?>
invokerClass =
loadAndInitializeInvokerClass(
classFile,
cpPatches(
classFile));
return
resolveInvokerMember(
invokerClass,
invokerName,
invokerType);
}
/**
* Define a given class as anonymous class in the runtime system.
*/
private static
Class<?>
loadAndInitializeInvokerClass(byte[]
classBytes,
Object[]
patches) {
Class<?>
invokerClass =
UNSAFE.
defineAnonymousClass(
HOST_CLASS,
classBytes,
patches);
UNSAFE.
ensureClassInitialized(
invokerClass); // Make sure the class is initialized; VM might complain.
return
invokerClass;
}
private static
MemberName resolveInvokerMember(
Class<?>
invokerClass,
String name,
MethodType type) {
MemberName member = new
MemberName(
invokerClass,
name,
type,
REF_invokeStatic);
//System.out.println("resolveInvokerMember => "+member);
//for (Method m : invokerClass.getDeclaredMethods()) System.out.println(" "+m);
try {
member =
MEMBERNAME_FACTORY.
resolveOrFail(
REF_invokeStatic,
member,
HOST_CLASS,
ReflectiveOperationException.class);
} catch (
ReflectiveOperationException e) {
throw
newInternalError(
e);
}
//System.out.println("resolveInvokerMember => "+member);
return
member;
}
/**
* Set up class file generation.
*/
private void
classFilePrologue() {
final int
NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC
cw = new
ClassWriter(
ClassWriter.
COMPUTE_MAXS +
ClassWriter.
COMPUTE_FRAMES);
cw.
visit(
Opcodes.
V1_8,
NOT_ACC_PUBLIC +
Opcodes.
ACC_FINAL +
Opcodes.
ACC_SUPER,
className, null,
superName, null);
cw.
visitSource(
sourceFile, null);
String invokerDesc =
invokerType.
toMethodDescriptorString();
mv =
cw.
visitMethod(
Opcodes.
ACC_STATIC,
invokerName,
invokerDesc, null, null);
}
/**
* Tear down class file generation.
*/
private void
classFileEpilogue() {
mv.
visitMaxs(0, 0);
mv.
visitEnd();
}
/*
* Low-level emit helpers.
*/
private void
emitConst(
Object con) {
if (
con == null) {
mv.
visitInsn(
Opcodes.
ACONST_NULL);
return;
}
if (
con instanceof
Integer) {
emitIconstInsn((int)
con);
return;
}
if (
con instanceof
Long) {
long
x = (long)
con;
if (
x == (short)
x) {
emitIconstInsn((int)
x);
mv.
visitInsn(
Opcodes.
I2L);
return;
}
}
if (
con instanceof
Float) {
float
x = (float)
con;
if (
x == (short)
x) {
emitIconstInsn((int)
x);
mv.
visitInsn(
Opcodes.
I2F);
return;
}
}
if (
con instanceof
Double) {
double
x = (double)
con;
if (
x == (short)
x) {
emitIconstInsn((int)
x);
mv.
visitInsn(
Opcodes.
I2D);
return;
}
}
if (
con instanceof
Boolean) {
emitIconstInsn((boolean)
con ? 1 : 0);
return;
}
// fall through:
mv.
visitLdcInsn(
con);
}
private void
emitIconstInsn(int
i) {
int
opcode;
switch (
i) {
case 0:
opcode =
Opcodes.
ICONST_0; break;
case 1:
opcode =
Opcodes.
ICONST_1; break;
case 2:
opcode =
Opcodes.
ICONST_2; break;
case 3:
opcode =
Opcodes.
ICONST_3; break;
case 4:
opcode =
Opcodes.
ICONST_4; break;
case 5:
opcode =
Opcodes.
ICONST_5; break;
default:
if (
i == (byte)
i) {
mv.
visitIntInsn(
Opcodes.
BIPUSH,
i & 0xFF);
} else if (
i == (short)
i) {
mv.
visitIntInsn(
Opcodes.
SIPUSH, (char)
i);
} else {
mv.
visitLdcInsn(
i);
}
return;
}
mv.
visitInsn(
opcode);
}
/*
* NOTE: These load/store methods use the localsMap to find the correct index!
*/
private void
emitLoadInsn(
BasicType type, int
index) {
int
opcode =
loadInsnOpcode(
type);
mv.
visitVarInsn(
opcode,
localsMap[
index]);
}
private int
loadInsnOpcode(
BasicType type) throws
InternalError {
switch (
type) {
case
I_TYPE: return
Opcodes.
ILOAD;
case
J_TYPE: return
Opcodes.
LLOAD;
case
F_TYPE: return
Opcodes.
FLOAD;
case
D_TYPE: return
Opcodes.
DLOAD;
case
L_TYPE: return
Opcodes.
ALOAD;
default:
throw new
InternalError("unknown type: " +
type);
}
}
private void
emitAloadInsn(int
index) {
emitLoadInsn(
L_TYPE,
index);
}
private void
emitStoreInsn(
BasicType type, int
index) {
int
opcode =
storeInsnOpcode(
type);
mv.
visitVarInsn(
opcode,
localsMap[
index]);
}
private int
storeInsnOpcode(
BasicType type) throws
InternalError {
switch (
type) {
case
I_TYPE: return
Opcodes.
ISTORE;
case
J_TYPE: return
Opcodes.
LSTORE;
case
F_TYPE: return
Opcodes.
FSTORE;
case
D_TYPE: return
Opcodes.
DSTORE;
case
L_TYPE: return
Opcodes.
ASTORE;
default:
throw new
InternalError("unknown type: " +
type);
}
}
private void
emitAstoreInsn(int
index) {
emitStoreInsn(
L_TYPE,
index);
}
private byte
arrayTypeCode(
Wrapper elementType) {
switch (
elementType) {
case
BOOLEAN: return
Opcodes.
T_BOOLEAN;
case
BYTE: return
Opcodes.
T_BYTE;
case
CHAR: return
Opcodes.
T_CHAR;
case
SHORT: return
Opcodes.
T_SHORT;
case
INT: return
Opcodes.
T_INT;
case
LONG: return
Opcodes.
T_LONG;
case
FLOAT: return
Opcodes.
T_FLOAT;
case
DOUBLE: return
Opcodes.
T_DOUBLE;
case
OBJECT: return 0; // in place of Opcodes.T_OBJECT
default: throw new
InternalError();
}
}
private int
arrayInsnOpcode(byte
tcode, int
aaop) throws
InternalError {
assert(
aaop ==
Opcodes.
AASTORE ||
aaop ==
Opcodes.
AALOAD);
int
xas;
switch (
tcode) {
case
Opcodes.
T_BOOLEAN:
xas =
Opcodes.
BASTORE; break;
case
Opcodes.
T_BYTE:
xas =
Opcodes.
BASTORE; break;
case
Opcodes.
T_CHAR:
xas =
Opcodes.
CASTORE; break;
case
Opcodes.
T_SHORT:
xas =
Opcodes.
SASTORE; break;
case
Opcodes.
T_INT:
xas =
Opcodes.
IASTORE; break;
case
Opcodes.
T_LONG:
xas =
Opcodes.
LASTORE; break;
case
Opcodes.
T_FLOAT:
xas =
Opcodes.
FASTORE; break;
case
Opcodes.
T_DOUBLE:
xas =
Opcodes.
DASTORE; break;
case 0:
xas =
Opcodes.
AASTORE; break;
default: throw new
InternalError();
}
return
xas -
Opcodes.
AASTORE +
aaop;
}
private void
freeFrameLocal(int
oldFrameLocal) {
int
i =
indexForFrameLocal(
oldFrameLocal);
if (
i < 0) return;
BasicType type =
localTypes[
i];
int
newFrameLocal =
makeLocalTemp(
type);
mv.
visitVarInsn(
loadInsnOpcode(
type),
oldFrameLocal);
mv.
visitVarInsn(
storeInsnOpcode(
type),
newFrameLocal);
assert(
localsMap[
i] ==
oldFrameLocal);
localsMap[
i] =
newFrameLocal;
assert(
indexForFrameLocal(
oldFrameLocal) < 0);
}
private int
indexForFrameLocal(int
frameLocal) {
for (int
i = 0;
i <
localsMap.length;
i++) {
if (
localsMap[
i] ==
frameLocal &&
localTypes[
i] !=
V_TYPE)
return
i;
}
return -1;
}
private int
makeLocalTemp(
BasicType type) {
int
frameLocal =
localsMap[
localsMap.length - 1];
localsMap[
localsMap.length - 1] =
frameLocal +
type.
basicTypeSlots();
return
frameLocal;
}
/**
* Emit a boxing call.
*
* @param wrapper primitive type class to box.
*/
private void
emitBoxing(
Wrapper wrapper) {
String owner = "java/lang/" +
wrapper.
wrapperType().
getSimpleName();
String name = "valueOf";
String desc = "(" +
wrapper.
basicTypeChar() + ")L" +
owner + ";";
mv.
visitMethodInsn(
Opcodes.
INVOKESTATIC,
owner,
name,
desc, false);
}
/**
* Emit an unboxing call (plus preceding checkcast).
*
* @param wrapper wrapper type class to unbox.
*/
private void
emitUnboxing(
Wrapper wrapper) {
String owner = "java/lang/" +
wrapper.
wrapperType().
getSimpleName();
String name =
wrapper.
primitiveSimpleName() + "Value";
String desc = "()" +
wrapper.
basicTypeChar();
emitReferenceCast(
wrapper.
wrapperType(), null);
mv.
visitMethodInsn(
Opcodes.
INVOKEVIRTUAL,
owner,
name,
desc, false);
}
/**
* Emit an implicit conversion for an argument which must be of the given pclass.
* This is usually a no-op, except when pclass is a subword type or a reference other than Object or an interface.
*
* @param ptype type of value present on stack
* @param pclass type of value required on stack
* @param arg compile-time representation of value on stack (Node, constant) or null if none
*/
private void
emitImplicitConversion(
BasicType ptype,
Class<?>
pclass,
Object arg) {
assert(
basicType(
pclass) ==
ptype); // boxing/unboxing handled by caller
if (
pclass ==
ptype.
basicTypeClass() &&
ptype !=
L_TYPE)
return; // nothing to do
switch (
ptype) {
case
L_TYPE:
if (
VerifyType.
isNullConversion(
Object.class,
pclass, false)) {
if (
PROFILE_LEVEL > 0)
emitReferenceCast(
Object.class,
arg);
return;
}
emitReferenceCast(
pclass,
arg);
return;
case
I_TYPE:
if (!
VerifyType.
isNullConversion(int.class,
pclass, false))
emitPrimCast(
ptype.
basicTypeWrapper(),
Wrapper.
forPrimitiveType(
pclass));
return;
}
throw
newInternalError("bad implicit conversion: tc="+
ptype+": "+
pclass);
}
/** Update localClasses type map. Return true if the information is already present. */
private boolean
assertStaticType(
Class<?>
cls,
Name n) {
int
local =
n.
index();
Class<?>
aclass =
localClasses[
local];
if (
aclass != null && (
aclass ==
cls ||
cls.
isAssignableFrom(
aclass))) {
return true; // type info is already present
} else if (
aclass == null ||
aclass.
isAssignableFrom(
cls)) {
localClasses[
local] =
cls; // type info can be improved
}
return false;
}
private void
emitReferenceCast(
Class<?>
cls,
Object arg) {
Name writeBack = null; // local to write back result
if (
arg instanceof
Name) {
Name n = (
Name)
arg;
if (
assertStaticType(
cls,
n))
return; // this cast was already performed
if (
lambdaForm.
useCount(
n) > 1) {
// This guy gets used more than once.
writeBack =
n;
}
}
if (
isStaticallyNameable(
cls)) {
String sig =
getInternalName(
cls);
mv.
visitTypeInsn(
Opcodes.
CHECKCAST,
sig);
} else {
mv.
visitLdcInsn(
constantPlaceholder(
cls));
mv.
visitTypeInsn(
Opcodes.
CHECKCAST,
CLS);
mv.
visitInsn(
Opcodes.
SWAP);
mv.
visitMethodInsn(
Opcodes.
INVOKESTATIC,
MHI, "castReference",
CLL_SIG, false);
if (
Object[].class.
isAssignableFrom(
cls))
mv.
visitTypeInsn(
Opcodes.
CHECKCAST,
OBJARY);
else if (
PROFILE_LEVEL > 0)
mv.
visitTypeInsn(
Opcodes.
CHECKCAST,
OBJ);
}
if (
writeBack != null) {
mv.
visitInsn(
Opcodes.
DUP);
emitAstoreInsn(
writeBack.
index());
}
}
/**
* Emits an actual return instruction conforming to the given return type.
*/
private void
emitReturnInsn(
BasicType type) {
int
opcode;
switch (
type) {
case
I_TYPE:
opcode =
Opcodes.
IRETURN; break;
case
J_TYPE:
opcode =
Opcodes.
LRETURN; break;
case
F_TYPE:
opcode =
Opcodes.
FRETURN; break;
case
D_TYPE:
opcode =
Opcodes.
DRETURN; break;
case
L_TYPE:
opcode =
Opcodes.
ARETURN; break;
case
V_TYPE:
opcode =
Opcodes.
RETURN; break;
default:
throw new
InternalError("unknown return type: " +
type);
}
mv.
visitInsn(
opcode);
}
private static
String getInternalName(
Class<?>
c) {
if (
c ==
Object.class) return
OBJ;
else if (
c ==
Object[].class) return
OBJARY;
else if (
c ==
Class.class) return
CLS;
else if (
c ==
MethodHandle.class) return
MH;
assert(
VerifyAccess.
isTypeVisible(
c,
Object.class)) :
c.
getName();
return
c.
getName().
replace('.', '/');
}
/**
* Generate customized bytecode for a given LambdaForm.
*/
static
MemberName generateCustomizedCode(
LambdaForm form,
MethodType invokerType) {
InvokerBytecodeGenerator g = new
InvokerBytecodeGenerator("MH",
form,
invokerType);
return
g.
loadMethod(
g.
generateCustomizedCodeBytes());
}
/** Generates code to check that actual receiver and LambdaForm matches */
private boolean
checkActualReceiver() {
// Expects MethodHandle on the stack and actual receiver MethodHandle in slot #0
mv.
visitInsn(
Opcodes.
DUP);
mv.
visitVarInsn(
Opcodes.
ALOAD,
localsMap[0]);
mv.
visitMethodInsn(
Opcodes.
INVOKESTATIC,
MHI, "assertSame",
LLV_SIG, false);
return true;
}
/**
* Generate an invoker method for the passed {@link LambdaForm}.
*/
private byte[]
generateCustomizedCodeBytes() {
classFilePrologue();
// Suppress this method in backtraces displayed to the user.
mv.
visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
// Mark this method as a compiled LambdaForm
mv.
visitAnnotation("Ljava/lang/invoke/LambdaForm$Compiled;", true);
if (
lambdaForm.
forceInline) {
// Force inlining of this invoker method.
mv.
visitAnnotation("Ljava/lang/invoke/ForceInline;", true);
} else {
mv.
visitAnnotation("Ljava/lang/invoke/DontInline;", true);
}
if (
lambdaForm.
customized != null) {
// Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute
// receiver MethodHandle (at slot #0) with an embedded constant and use it instead.
// It enables more efficient code generation in some situations, since embedded constants
// are compile-time constants for JIT compiler.
mv.
visitLdcInsn(
constantPlaceholder(
lambdaForm.
customized));
mv.
visitTypeInsn(
Opcodes.
CHECKCAST,
MH);
assert(
checkActualReceiver()); // expects MethodHandle on top of the stack
mv.
visitVarInsn(
Opcodes.
ASTORE,
localsMap[0]);
}
// iterate over the form's names, generating bytecode instructions for each
// start iterating at the first name following the arguments
Name onStack = null;
for (int
i =
lambdaForm.
arity;
i <
lambdaForm.
names.length;
i++) {
Name name =
lambdaForm.
names[
i];
emitStoreResult(
onStack);
onStack =
name; // unless otherwise modified below
MethodHandleImpl.
Intrinsic intr =
name.
function.
intrinsicName();
switch (
intr) {
case
SELECT_ALTERNATIVE:
assert
isSelectAlternative(
i);
if (
PROFILE_GWT) {
assert(
name.
arguments[0] instanceof
Name &&
nameRefersTo((
Name)
name.
arguments[0],
MethodHandleImpl.class, "profileBoolean"));
mv.
visitAnnotation("Ljava/lang/invoke/InjectedProfile;", true);
}
onStack =
emitSelectAlternative(
name,
lambdaForm.
names[
i+1]);
i++; // skip MH.invokeBasic of the selectAlternative result
continue;
case
GUARD_WITH_CATCH:
assert
isGuardWithCatch(
i);
onStack =
emitGuardWithCatch(
i);
i =
i+2; // Jump to the end of GWC idiom
continue;
case
NEW_ARRAY:
Class<?>
rtype =
name.
function.
methodType().
returnType();
if (
isStaticallyNameable(
rtype)) {
emitNewArray(
name);
continue;
}
break;
case
ARRAY_LOAD:
emitArrayLoad(
name);
continue;
case
ARRAY_STORE:
emitArrayStore(
name);
continue;
case
IDENTITY:
assert(
name.
arguments.length == 1);
emitPushArguments(
name);
continue;
case
ZERO:
assert(
name.
arguments.length == 0);
emitConst(
name.
type.
basicTypeWrapper().
zero());
continue;
case
NONE:
// no intrinsic associated
break;
default:
throw
newInternalError("Unknown intrinsic: "+
intr);
}
MemberName member =
name.
function.
member();
if (
isStaticallyInvocable(
member)) {
emitStaticInvoke(
member,
name);
} else {
emitInvoke(
name);
}
}
// return statement
emitReturn(
onStack);
classFileEpilogue();
bogusMethod(
lambdaForm);
final byte[]
classFile =
cw.
toByteArray();
maybeDump(
className,
classFile);
return
classFile;
}
void
emitArrayLoad(
Name name) {
emitArrayOp(
name,
Opcodes.
AALOAD); }
void
emitArrayStore(
Name name) {
emitArrayOp(
name,
Opcodes.
AASTORE); }
void
emitArrayOp(
Name name, int
arrayOpcode) {
assert
arrayOpcode ==
Opcodes.
AALOAD ||
arrayOpcode ==
Opcodes.
AASTORE;
Class<?>
elementType =
name.
function.
methodType().
parameterType(0).
getComponentType();
assert
elementType != null;
emitPushArguments(
name);
if (
elementType.
isPrimitive()) {
Wrapper w =
Wrapper.
forPrimitiveType(
elementType);
arrayOpcode =
arrayInsnOpcode(
arrayTypeCode(
w),
arrayOpcode);
}
mv.
visitInsn(
arrayOpcode);
}
/**
* Emit an invoke for the given name.
*/
void
emitInvoke(
Name name) {
assert(!
isLinkerMethodInvoke(
name)); // should use the static path for these
if (true) {
// push receiver
MethodHandle target =
name.
function.
resolvedHandle;
assert(
target != null) :
name.
exprString();
mv.
visitLdcInsn(
constantPlaceholder(
target));
emitReferenceCast(
MethodHandle.class,
target);
} else {
// load receiver
emitAloadInsn(0);
emitReferenceCast(
MethodHandle.class, null);
mv.
visitFieldInsn(
Opcodes.
GETFIELD,
MH, "form",
LF_SIG);
mv.
visitFieldInsn(
Opcodes.
GETFIELD,
LF, "names",
LFN_SIG);
// TODO more to come
}
// push arguments
emitPushArguments(
name);
// invocation
MethodType type =
name.
function.
methodType();
mv.
visitMethodInsn(
Opcodes.
INVOKEVIRTUAL,
MH, "invokeBasic",
type.
basicType().
toMethodDescriptorString(), false);
}
static private
Class<?>[]
STATICALLY_INVOCABLE_PACKAGES = {
// Sample classes from each package we are willing to bind to statically:
java.lang.
Object.class,
java.util.
Arrays.class,
sun.misc.
Unsafe.class
//MethodHandle.class already covered
};
static boolean
isStaticallyInvocable(
Name name) {
return
isStaticallyInvocable(
name.
function.
member());
}
static boolean
isStaticallyInvocable(
MemberName member) {
if (
member == null) return false;
if (
member.
isConstructor()) return false;
Class<?>
cls =
member.
getDeclaringClass();
if (
cls.
isArray() ||
cls.
isPrimitive())
return false; // FIXME
if (
cls.
isAnonymousClass() ||
cls.
isLocalClass())
return false; // inner class of some sort
if (
cls.
getClassLoader() !=
MethodHandle.class.
getClassLoader())
return false; // not on BCP
if (
ReflectUtil.
isVMAnonymousClass(
cls)) // FIXME: switch to supported API once it is added
return false;
MethodType mtype =
member.
getMethodOrFieldType();
if (!
isStaticallyNameable(
mtype.
returnType()))
return false;
for (
Class<?>
ptype :
mtype.
parameterArray())
if (!
isStaticallyNameable(
ptype))
return false;
if (!
member.
isPrivate() &&
VerifyAccess.
isSamePackage(
MethodHandle.class,
cls))
return true; // in java.lang.invoke package
if (
member.
isPublic() &&
isStaticallyNameable(
cls))
return true;
return false;
}
static boolean
isStaticallyNameable(
Class<?>
cls) {
if (
cls ==
Object.class)
return true;
while (
cls.
isArray())
cls =
cls.
getComponentType();
if (
cls.
isPrimitive())
return true; // int[].class, for example
if (
ReflectUtil.
isVMAnonymousClass(
cls)) // FIXME: switch to supported API once it is added
return false;
// could use VerifyAccess.isClassAccessible but the following is a safe approximation
if (
cls.
getClassLoader() !=
Object.class.
getClassLoader())
return false;
if (
VerifyAccess.
isSamePackage(
MethodHandle.class,
cls))
return true;
if (!
Modifier.
isPublic(
cls.
getModifiers()))
return false;
for (
Class<?>
pkgcls :
STATICALLY_INVOCABLE_PACKAGES) {
if (
VerifyAccess.
isSamePackage(
pkgcls,
cls))
return true;
}
return false;
}
void
emitStaticInvoke(
Name name) {
emitStaticInvoke(
name.
function.
member(),
name);
}
/**
* Emit an invoke for the given name, using the MemberName directly.
*/
void
emitStaticInvoke(
MemberName member,
Name name) {
assert(
member.
equals(
name.
function.
member()));
Class<?>
defc =
member.
getDeclaringClass();
String cname =
getInternalName(
defc);
String mname =
member.
getName();
String mtype;
byte
refKind =
member.
getReferenceKind();
if (
refKind ==
REF_invokeSpecial) {
// in order to pass the verifier, we need to convert this to invokevirtual in all cases
assert(
member.
canBeStaticallyBound()) :
member;
refKind =
REF_invokeVirtual;
}
if (
member.
getDeclaringClass().
isInterface() &&
refKind ==
REF_invokeVirtual) {
// Methods from Object declared in an interface can be resolved by JVM to invokevirtual kind.
// Need to convert it back to invokeinterface to pass verification and make the invocation works as expected.
refKind =
REF_invokeInterface;
}
// push arguments
emitPushArguments(
name);
// invocation
if (
member.
isMethod()) {
mtype =
member.
getMethodType().
toMethodDescriptorString();
mv.
visitMethodInsn(
refKindOpcode(
refKind),
cname,
mname,
mtype,
member.
getDeclaringClass().
isInterface());
} else {
mtype =
MethodType.
toFieldDescriptorString(
member.
getFieldType());
mv.
visitFieldInsn(
refKindOpcode(
refKind),
cname,
mname,
mtype);
}
// Issue a type assertion for the result, so we can avoid casts later.
if (
name.
type ==
L_TYPE) {
Class<?>
rtype =
member.
getInvocationType().
returnType();
assert(!
rtype.
isPrimitive());
if (
rtype !=
Object.class && !
rtype.
isInterface()) {
assertStaticType(
rtype,
name);
}
}
}
void
emitNewArray(
Name name) throws
InternalError {
Class<?>
rtype =
name.
function.
methodType().
returnType();
if (
name.
arguments.length == 0) {
// The array will be a constant.
Object emptyArray;
try {
emptyArray =
name.
function.
resolvedHandle.
invoke();
} catch (
Throwable ex) {
throw
newInternalError(
ex);
}
assert(java.lang.reflect.
Array.
getLength(
emptyArray) == 0);
assert(
emptyArray.
getClass() ==
rtype); // exact typing
mv.
visitLdcInsn(
constantPlaceholder(
emptyArray));
emitReferenceCast(
rtype,
emptyArray);
return;
}
Class<?>
arrayElementType =
rtype.
getComponentType();
assert(
arrayElementType != null);
emitIconstInsn(
name.
arguments.length);
int
xas =
Opcodes.
AASTORE;
if (!
arrayElementType.
isPrimitive()) {
mv.
visitTypeInsn(
Opcodes.
ANEWARRAY,
getInternalName(
arrayElementType));
} else {
byte
tc =
arrayTypeCode(
Wrapper.
forPrimitiveType(
arrayElementType));
xas =
arrayInsnOpcode(
tc,
xas);
mv.
visitIntInsn(
Opcodes.
NEWARRAY,
tc);
}
// store arguments
for (int
i = 0;
i <
name.
arguments.length;
i++) {
mv.
visitInsn(
Opcodes.
DUP);
emitIconstInsn(
i);
emitPushArgument(
name,
i);
mv.
visitInsn(
xas);
}
// the array is left on the stack
assertStaticType(
rtype,
name);
}
int
refKindOpcode(byte
refKind) {
switch (
refKind) {
case
REF_invokeVirtual: return
Opcodes.
INVOKEVIRTUAL;
case
REF_invokeStatic: return
Opcodes.
INVOKESTATIC;
case
REF_invokeSpecial: return
Opcodes.
INVOKESPECIAL;
case
REF_invokeInterface: return
Opcodes.
INVOKEINTERFACE;
case
REF_getField: return
Opcodes.
GETFIELD;
case
REF_putField: return
Opcodes.
PUTFIELD;
case
REF_getStatic: return
Opcodes.
GETSTATIC;
case
REF_putStatic: return
Opcodes.
PUTSTATIC;
}
throw new
InternalError("refKind="+
refKind);
}
/**
* Check if MemberName is a call to a method named {@code name} in class {@code declaredClass}.
*/
private boolean
memberRefersTo(
MemberName member,
Class<?>
declaringClass,
String name) {
return
member != null &&
member.
getDeclaringClass() ==
declaringClass &&
member.
getName().
equals(
name);
}
private boolean
nameRefersTo(
Name name,
Class<?>
declaringClass,
String methodName) {
return
name.
function != null &&
memberRefersTo(
name.
function.
member(),
declaringClass,
methodName);
}
/**
* Check if MemberName is a call to MethodHandle.invokeBasic.
*/
private boolean
isInvokeBasic(
Name name) {
if (
name.
function == null)
return false;
if (
name.
arguments.length < 1)
return false; // must have MH argument
MemberName member =
name.
function.
member();
return
memberRefersTo(
member,
MethodHandle.class, "invokeBasic") &&
!
member.
isPublic() && !
member.
isStatic();
}
/**
* Check if MemberName is a call to MethodHandle.linkToStatic, etc.
*/
private boolean
isLinkerMethodInvoke(
Name name) {
if (
name.
function == null)
return false;
if (
name.
arguments.length < 1)
return false; // must have MH argument
MemberName member =
name.
function.
member();
return
member != null &&
member.
getDeclaringClass() ==
MethodHandle.class &&
!
member.
isPublic() &&
member.
isStatic() &&
member.
getName().
startsWith("linkTo");
}
/**
* Check if i-th name is a call to MethodHandleImpl.selectAlternative.
*/
private boolean
isSelectAlternative(int
pos) {
// selectAlternative idiom:
// t_{n}:L=MethodHandleImpl.selectAlternative(...)
// t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
if (
pos+1 >=
lambdaForm.
names.length) return false;
Name name0 =
lambdaForm.
names[
pos];
Name name1 =
lambdaForm.
names[
pos+1];
return
nameRefersTo(
name0,
MethodHandleImpl.class, "selectAlternative") &&
isInvokeBasic(
name1) &&
name1.
lastUseIndex(
name0) == 0 && // t_{n+1}:?=MethodHandle.invokeBasic(t_{n}, ...)
lambdaForm.
lastUseIndex(
name0) ==
pos+1; // t_{n} is local: used only in t_{n+1}
}
/**
* Check if i-th name is a start of GuardWithCatch idiom.
*/
private boolean
isGuardWithCatch(int
pos) {
// GuardWithCatch idiom:
// t_{n}:L=MethodHandle.invokeBasic(...)
// t_{n+1}:L=MethodHandleImpl.guardWithCatch(*, *, *, t_{n});
// t_{n+2}:?=MethodHandle.invokeBasic(t_{n+1})
if (
pos+2 >=
lambdaForm.
names.length) return false;
Name name0 =
lambdaForm.
names[
pos];
Name name1 =
lambdaForm.
names[
pos+1];
Name name2 =
lambdaForm.
names[
pos+2];
return
nameRefersTo(
name1,
MethodHandleImpl.class, "guardWithCatch") &&
isInvokeBasic(
name0) &&
isInvokeBasic(
name2) &&
name1.
lastUseIndex(
name0) == 3 && // t_{n+1}:L=MethodHandleImpl.guardWithCatch(*, *, *, t_{n});
lambdaForm.
lastUseIndex(
name0) ==
pos+1 && // t_{n} is local: used only in t_{n+1}
name2.
lastUseIndex(
name1) == 1 && // t_{n+2}:?=MethodHandle.invokeBasic(t_{n+1})
lambdaForm.
lastUseIndex(
name1) ==
pos+2; // t_{n+1} is local: used only in t_{n+2}
}
/**
* Emit bytecode for the selectAlternative idiom.
*
* The pattern looks like (Cf. MethodHandleImpl.makeGuardWithTest):
* <blockquote><pre>{@code
* Lambda(a0:L,a1:I)=>{
* t2:I=foo.test(a1:I);
* t3:L=MethodHandleImpl.selectAlternative(t2:I,(MethodHandle(int)int),(MethodHandle(int)int));
* t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I}
* }</pre></blockquote>
*/
private
Name emitSelectAlternative(
Name selectAlternativeName,
Name invokeBasicName) {
assert
isStaticallyInvocable(
invokeBasicName);
Name receiver = (
Name)
invokeBasicName.
arguments[0];
Label L_fallback = new
Label();
Label L_done = new
Label();
// load test result
emitPushArgument(
selectAlternativeName, 0);
// if_icmpne L_fallback
mv.
visitJumpInsn(
Opcodes.
IFEQ,
L_fallback);
// invoke selectAlternativeName.arguments[1]
Class<?>[]
preForkClasses =
localClasses.
clone();
emitPushArgument(
selectAlternativeName, 1); // get 2nd argument of selectAlternative
emitAstoreInsn(
receiver.
index()); // store the MH in the receiver slot
emitStaticInvoke(
invokeBasicName);
// goto L_done
mv.
visitJumpInsn(
Opcodes.
GOTO,
L_done);
// L_fallback:
mv.
visitLabel(
L_fallback);
// invoke selectAlternativeName.arguments[2]
System.
arraycopy(
preForkClasses, 0,
localClasses, 0,
preForkClasses.length);
emitPushArgument(
selectAlternativeName, 2); // get 3rd argument of selectAlternative
emitAstoreInsn(
receiver.
index()); // store the MH in the receiver slot
emitStaticInvoke(
invokeBasicName);
// L_done:
mv.
visitLabel(
L_done);
// for now do not bother to merge typestate; just reset to the dominator state
System.
arraycopy(
preForkClasses, 0,
localClasses, 0,
preForkClasses.length);
return
invokeBasicName; // return what's on stack
}
/**
* Emit bytecode for the guardWithCatch idiom.
*
* The pattern looks like (Cf. MethodHandleImpl.makeGuardWithCatch):
* <blockquote><pre>{@code
* guardWithCatch=Lambda(a0:L,a1:L,a2:L,a3:L,a4:L,a5:L,a6:L,a7:L)=>{
* t8:L=MethodHandle.invokeBasic(a4:L,a6:L,a7:L);
* t9:L=MethodHandleImpl.guardWithCatch(a1:L,a2:L,a3:L,t8:L);
* t10:I=MethodHandle.invokeBasic(a5:L,t9:L);t10:I}
* }</pre></blockquote>
*
* It is compiled into bytecode equivalent of the following code:
* <blockquote><pre>{@code
* try {
* return a1.invokeBasic(a6, a7);
* } catch (Throwable e) {
* if (!a2.isInstance(e)) throw e;
* return a3.invokeBasic(ex, a6, a7);
* }}
*/
private
Name emitGuardWithCatch(int
pos) {
Name args =
lambdaForm.
names[
pos];
Name invoker =
lambdaForm.
names[
pos+1];
Name result =
lambdaForm.
names[
pos+2];
Label L_startBlock = new
Label();
Label L_endBlock = new
Label();
Label L_handler = new
Label();
Label L_done = new
Label();
Class<?>
returnType =
result.
function.
resolvedHandle.
type().
returnType();
MethodType type =
args.
function.
resolvedHandle.
type()
.
dropParameterTypes(0,1)
.
changeReturnType(
returnType);
mv.
visitTryCatchBlock(
L_startBlock,
L_endBlock,
L_handler, "java/lang/Throwable");
// Normal case
mv.
visitLabel(
L_startBlock);
// load target
emitPushArgument(
invoker, 0);
emitPushArguments(
args, 1); // skip 1st argument: method handle
mv.
visitMethodInsn(
Opcodes.
INVOKEVIRTUAL,
MH, "invokeBasic",
type.
basicType().
toMethodDescriptorString(), false);
mv.
visitLabel(
L_endBlock);
mv.
visitJumpInsn(
Opcodes.
GOTO,
L_done);
// Exceptional case
mv.
visitLabel(
L_handler);
// Check exception's type
mv.
visitInsn(
Opcodes.
DUP);
// load exception class
emitPushArgument(
invoker, 1);
mv.
visitInsn(
Opcodes.
SWAP);
mv.
visitMethodInsn(
Opcodes.
INVOKEVIRTUAL, "java/lang/Class", "isInstance", "(Ljava/lang/Object;)Z", false);
Label L_rethrow = new
Label();
mv.
visitJumpInsn(
Opcodes.
IFEQ,
L_rethrow);
// Invoke catcher
// load catcher
emitPushArgument(
invoker, 2);
mv.
visitInsn(
Opcodes.
SWAP);
emitPushArguments(
args, 1); // skip 1st argument: method handle
MethodType catcherType =
type.
insertParameterTypes(0,
Throwable.class);
mv.
visitMethodInsn(
Opcodes.
INVOKEVIRTUAL,
MH, "invokeBasic",
catcherType.
basicType().
toMethodDescriptorString(), false);
mv.
visitJumpInsn(
Opcodes.
GOTO,
L_done);
mv.
visitLabel(
L_rethrow);
mv.
visitInsn(
Opcodes.
ATHROW);
mv.
visitLabel(
L_done);
return
result;
}
private void
emitPushArguments(
Name args) {
emitPushArguments(
args, 0);
}
private void
emitPushArguments(
Name args, int
start) {
for (int
i =
start;
i <
args.
arguments.length;
i++) {
emitPushArgument(
args,
i);
}
}
private void
emitPushArgument(
Name name, int
paramIndex) {
Object arg =
name.
arguments[
paramIndex];
Class<?>
ptype =
name.
function.
methodType().
parameterType(
paramIndex);
emitPushArgument(
ptype,
arg);
}
private void
emitPushArgument(
Class<?>
ptype,
Object arg) {
BasicType bptype =
basicType(
ptype);
if (
arg instanceof
Name) {
Name n = (
Name)
arg;
emitLoadInsn(
n.
type,
n.
index());
emitImplicitConversion(
n.
type,
ptype,
n);
} else if ((
arg == null ||
arg instanceof
String) &&
bptype ==
L_TYPE) {
emitConst(
arg);
} else {
if (
Wrapper.
isWrapperType(
arg.
getClass()) &&
bptype !=
L_TYPE) {
emitConst(
arg);
} else {
mv.
visitLdcInsn(
constantPlaceholder(
arg));
emitImplicitConversion(
L_TYPE,
ptype,
arg);
}
}
}
/**
* Store the name to its local, if necessary.
*/
private void
emitStoreResult(
Name name) {
if (
name != null &&
name.
type !=
V_TYPE) {
// non-void: actually assign
emitStoreInsn(
name.
type,
name.
index());
}
}
/**
* Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type.
*/
private void
emitReturn(
Name onStack) {
// return statement
Class<?>
rclass =
invokerType.
returnType();
BasicType rtype =
lambdaForm.
returnType();
assert(
rtype ==
basicType(
rclass)); // must agree
if (
rtype ==
V_TYPE) {
// void
mv.
visitInsn(
Opcodes.
RETURN);
// it doesn't matter what rclass is; the JVM will discard any value
} else {
LambdaForm.
Name rn =
lambdaForm.
names[
lambdaForm.
result];
// put return value on the stack if it is not already there
if (
rn !=
onStack) {
emitLoadInsn(
rtype,
lambdaForm.
result);
}
emitImplicitConversion(
rtype,
rclass,
rn);
// generate actual return statement
emitReturnInsn(
rtype);
}
}
/**
* Emit a type conversion bytecode casting from "from" to "to".
*/
private void
emitPrimCast(
Wrapper from,
Wrapper to) {
// Here's how.
// - indicates forbidden
// <-> indicates implicit
// to ----> boolean byte short char int long float double
// from boolean <-> - - - - - - -
// byte - <-> i2s i2c <-> i2l i2f i2d
// short - i2b <-> i2c <-> i2l i2f i2d
// char - i2b i2s <-> <-> i2l i2f i2d
// int - i2b i2s i2c <-> i2l i2f i2d
// long - l2i,i2b l2i,i2s l2i,i2c l2i <-> l2f l2d
// float - f2i,i2b f2i,i2s f2i,i2c f2i f2l <-> f2d
// double - d2i,i2b d2i,i2s d2i,i2c d2i d2l d2f <->
if (
from ==
to) {
// no cast required, should be dead code anyway
return;
}
if (
from.
isSubwordOrInt()) {
// cast from {byte,short,char,int} to anything
emitI2X(
to);
} else {
// cast from {long,float,double} to anything
if (
to.
isSubwordOrInt()) {
// cast to {byte,short,char,int}
emitX2I(
from);
if (
to.
bitWidth() < 32) {
// targets other than int require another conversion
emitI2X(
to);
}
} else {
// cast to {long,float,double} - this is verbose
boolean
error = false;
switch (
from) {
case
LONG:
switch (
to) {
case
FLOAT:
mv.
visitInsn(
Opcodes.
L2F); break;
case
DOUBLE:
mv.
visitInsn(
Opcodes.
L2D); break;
default:
error = true; break;
}
break;
case
FLOAT:
switch (
to) {
case
LONG :
mv.
visitInsn(
Opcodes.
F2L); break;
case
DOUBLE:
mv.
visitInsn(
Opcodes.
F2D); break;
default:
error = true; break;
}
break;
case
DOUBLE:
switch (
to) {
case
LONG :
mv.
visitInsn(
Opcodes.
D2L); break;
case
FLOAT:
mv.
visitInsn(
Opcodes.
D2F); break;
default:
error = true; break;
}
break;
default:
error = true;
break;
}
if (
error) {
throw new
IllegalStateException("unhandled prim cast: " +
from + "2" +
to);
}
}
}
}
private void
emitI2X(
Wrapper type) {
switch (
type) {
case
BYTE:
mv.
visitInsn(
Opcodes.
I2B); break;
case
SHORT:
mv.
visitInsn(
Opcodes.
I2S); break;
case
CHAR:
mv.
visitInsn(
Opcodes.
I2C); break;
case
INT: /* naught */ break;
case
LONG:
mv.
visitInsn(
Opcodes.
I2L); break;
case
FLOAT:
mv.
visitInsn(
Opcodes.
I2F); break;
case
DOUBLE:
mv.
visitInsn(
Opcodes.
I2D); break;
case
BOOLEAN:
// For compatibility with ValueConversions and explicitCastArguments:
mv.
visitInsn(
Opcodes.
ICONST_1);
mv.
visitInsn(
Opcodes.
IAND);
break;
default: throw new
InternalError("unknown type: " +
type);
}
}
private void
emitX2I(
Wrapper type) {
switch (
type) {
case
LONG:
mv.
visitInsn(
Opcodes.
L2I); break;
case
FLOAT:
mv.
visitInsn(
Opcodes.
F2I); break;
case
DOUBLE:
mv.
visitInsn(
Opcodes.
D2I); break;
default: throw new
InternalError("unknown type: " +
type);
}
}
/**
* Generate bytecode for a LambdaForm.vmentry which calls interpretWithArguments.
*/
static
MemberName generateLambdaFormInterpreterEntryPoint(
String sig) {
assert(
isValidSignature(
sig));
String name = "interpret_"+
signatureReturn(
sig).
basicTypeChar();
MethodType type =
signatureType(
sig); // sig includes leading argument
type =
type.
changeParameterType(0,
MethodHandle.class);
InvokerBytecodeGenerator g = new
InvokerBytecodeGenerator("LFI",
name,
type);
return
g.
loadMethod(
g.
generateLambdaFormInterpreterEntryPointBytes());
}
private byte[]
generateLambdaFormInterpreterEntryPointBytes() {
classFilePrologue();
// Suppress this method in backtraces displayed to the user.
mv.
visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
// Don't inline the interpreter entry.
mv.
visitAnnotation("Ljava/lang/invoke/DontInline;", true);
// create parameter array
emitIconstInsn(
invokerType.
parameterCount());
mv.
visitTypeInsn(
Opcodes.
ANEWARRAY, "java/lang/Object");
// fill parameter array
for (int
i = 0;
i <
invokerType.
parameterCount();
i++) {
Class<?>
ptype =
invokerType.
parameterType(
i);
mv.
visitInsn(
Opcodes.
DUP);
emitIconstInsn(
i);
emitLoadInsn(
basicType(
ptype),
i);
// box if primitive type
if (
ptype.
isPrimitive()) {
emitBoxing(
Wrapper.
forPrimitiveType(
ptype));
}
mv.
visitInsn(
Opcodes.
AASTORE);
}
// invoke
emitAloadInsn(0);
mv.
visitFieldInsn(
Opcodes.
GETFIELD,
MH, "form", "Ljava/lang/invoke/LambdaForm;");
mv.
visitInsn(
Opcodes.
SWAP); // swap form and array; avoid local variable
mv.
visitMethodInsn(
Opcodes.
INVOKEVIRTUAL,
LF, "interpretWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false);
// maybe unbox
Class<?>
rtype =
invokerType.
returnType();
if (
rtype.
isPrimitive() &&
rtype != void.class) {
emitUnboxing(
Wrapper.
forPrimitiveType(
rtype));
}
// return statement
emitReturnInsn(
basicType(
rtype));
classFileEpilogue();
bogusMethod(
invokerType);
final byte[]
classFile =
cw.
toByteArray();
maybeDump(
className,
classFile);
return
classFile;
}
/**
* Generate bytecode for a NamedFunction invoker.
*/
static
MemberName generateNamedFunctionInvoker(
MethodTypeForm typeForm) {
MethodType invokerType =
NamedFunction.
INVOKER_METHOD_TYPE;
String invokerName = "invoke_" +
shortenSignature(
basicTypeSignature(
typeForm.
erasedType()));
InvokerBytecodeGenerator g = new
InvokerBytecodeGenerator("NFI",
invokerName,
invokerType);
return
g.
loadMethod(
g.
generateNamedFunctionInvokerImpl(
typeForm));
}
private byte[]
generateNamedFunctionInvokerImpl(
MethodTypeForm typeForm) {
MethodType dstType =
typeForm.
erasedType();
classFilePrologue();
// Suppress this method in backtraces displayed to the user.
mv.
visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true);
// Force inlining of this invoker method.
mv.
visitAnnotation("Ljava/lang/invoke/ForceInline;", true);
// Load receiver
emitAloadInsn(0);
// Load arguments from array
for (int
i = 0;
i <
dstType.
parameterCount();
i++) {
emitAloadInsn(1);
emitIconstInsn(
i);
mv.
visitInsn(
Opcodes.
AALOAD);
// Maybe unbox
Class<?>
dptype =
dstType.
parameterType(
i);
if (
dptype.
isPrimitive()) {
Class<?>
sptype =
dstType.
basicType().
wrap().
parameterType(
i);
Wrapper dstWrapper =
Wrapper.
forBasicType(
dptype);
Wrapper srcWrapper =
dstWrapper.
isSubwordOrInt() ?
Wrapper.
INT :
dstWrapper; // narrow subword from int
emitUnboxing(
srcWrapper);
emitPrimCast(
srcWrapper,
dstWrapper);
}
}
// Invoke
String targetDesc =
dstType.
basicType().
toMethodDescriptorString();
mv.
visitMethodInsn(
Opcodes.
INVOKEVIRTUAL,
MH, "invokeBasic",
targetDesc, false);
// Box primitive types
Class<?>
rtype =
dstType.
returnType();
if (
rtype != void.class &&
rtype.
isPrimitive()) {
Wrapper srcWrapper =
Wrapper.
forBasicType(
rtype);
Wrapper dstWrapper =
srcWrapper.
isSubwordOrInt() ?
Wrapper.
INT :
srcWrapper; // widen subword to int
// boolean casts not allowed
emitPrimCast(
srcWrapper,
dstWrapper);
emitBoxing(
dstWrapper);
}
// If the return type is void we return a null reference.
if (
rtype == void.class) {
mv.
visitInsn(
Opcodes.
ACONST_NULL);
}
emitReturnInsn(
L_TYPE); // NOTE: NamedFunction invokers always return a reference value.
classFileEpilogue();
bogusMethod(
dstType);
final byte[]
classFile =
cw.
toByteArray();
maybeDump(
className,
classFile);
return
classFile;
}
/**
* Emit a bogus method that just loads some string constants. This is to get the constants into the constant pool
* for debugging purposes.
*/
private void
bogusMethod(
Object...
os) {
if (
DUMP_CLASS_FILES) {
mv =
cw.
visitMethod(
Opcodes.
ACC_STATIC, "dummy", "()V", null, null);
for (
Object o :
os) {
mv.
visitLdcInsn(
o.
toString());
mv.
visitInsn(
Opcodes.
POP);
}
mv.
visitInsn(
Opcodes.
RETURN);
mv.
visitMaxs(0, 0);
mv.
visitEnd();
}
}
}