1030 lines
40 KiB
C#
1030 lines
40 KiB
C#
/*
|
|
* Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
// [IKVM] Based on original from OpenJDK, but heavily modified to directly generate a DynamicMethod,
|
|
// instead of Java bytecode.
|
|
// Copyright (C) 2015 Jeroen Frijters
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Reflection;
|
|
using System.Reflection.Emit;
|
|
using System.Runtime.CompilerServices;
|
|
using IKVM.Attributes;
|
|
using IKVM.Internal;
|
|
using java.lang.invoke;
|
|
#if !FIRST_PASS
|
|
using BasicType = java.lang.invoke.LambdaForm.BasicType;
|
|
using Class = java.lang.Class;
|
|
using Name = java.lang.invoke.LambdaForm.Name;
|
|
using Opcodes = jdk.@internal.org.objectweb.asm.Opcodes.__Fields;
|
|
using VerifyType = sun.invoke.util.VerifyType;
|
|
using Wrapper = sun.invoke.util.Wrapper;
|
|
#endif
|
|
|
|
sealed class NativeInvokerBytecodeGenerator
|
|
{
|
|
#if FIRST_PASS
|
|
public static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType)
|
|
{
|
|
return null;
|
|
}
|
|
#else
|
|
private readonly LambdaForm lambdaForm;
|
|
private readonly MethodType invokerType;
|
|
private readonly Type delegateType;
|
|
private readonly DynamicMethod dm;
|
|
private readonly CodeEmitter ilgen;
|
|
private readonly int packedArgPos;
|
|
private readonly Type packedArgType;
|
|
private readonly CodeEmitterLocal[] locals;
|
|
private readonly List<object> constants = new List<object>();
|
|
|
|
private enum Bailout
|
|
{
|
|
NotBasicType,
|
|
UnsupportedIntrinsic,
|
|
UnsupportedArrayType,
|
|
UnsupportedRefKind,
|
|
UnsupportedConstant,
|
|
NotStaticallyInvocable,
|
|
PreconditionViolated,
|
|
}
|
|
|
|
private sealed class BailoutException : Exception
|
|
{
|
|
internal BailoutException(Bailout reason, object data)
|
|
: base("BAILOUT " + reason + ": " + data)
|
|
{
|
|
}
|
|
}
|
|
|
|
private NativeInvokerBytecodeGenerator(LambdaForm lambdaForm, MethodType invokerType)
|
|
{
|
|
if (invokerType != invokerType.basicType())
|
|
{
|
|
throw new BailoutException(Bailout.NotBasicType, invokerType);
|
|
}
|
|
this.lambdaForm = lambdaForm;
|
|
this.invokerType = invokerType;
|
|
this.delegateType = MethodHandleUtil.GetMemberWrapperDelegateType(invokerType);
|
|
MethodInfo mi = MethodHandleUtil.GetDelegateInvokeMethod(delegateType);
|
|
Type[] paramTypes = MethodHandleUtil.GetParameterTypes(typeof(object[]), mi);
|
|
// HACK the code we generate is not verifiable (known issue: locals aren't typed correctly), so we stick the DynamicMethod into mscorlib (a security critical assembly)
|
|
this.dm = new DynamicMethod(lambdaForm.debugName, mi.ReturnType, paramTypes, typeof(object).Module, true);
|
|
this.ilgen = CodeEmitter.Create(this.dm);
|
|
if (invokerType.parameterCount() > MethodHandleUtil.MaxArity)
|
|
{
|
|
this.packedArgType = paramTypes[paramTypes.Length - 1];
|
|
this.packedArgPos = paramTypes.Length - 1;
|
|
}
|
|
else
|
|
{
|
|
this.packedArgPos = Int32.MaxValue;
|
|
}
|
|
|
|
locals = new CodeEmitterLocal[lambdaForm.names.Length];
|
|
for (int i = lambdaForm._arity(); i < lambdaForm.names.Length; i++)
|
|
{
|
|
Name name = lambdaForm.names[i];
|
|
if (name.index() != i)
|
|
{
|
|
throw new BailoutException(Bailout.PreconditionViolated, "name.index() != i");
|
|
}
|
|
switch (name.typeChar())
|
|
{
|
|
case 'L':
|
|
locals[i] = ilgen.DeclareLocal(Types.Object);
|
|
break;
|
|
case 'I':
|
|
locals[i] = ilgen.DeclareLocal(Types.Int32);
|
|
break;
|
|
case 'J':
|
|
locals[i] = ilgen.DeclareLocal(Types.Int64);
|
|
break;
|
|
case 'F':
|
|
locals[i] = ilgen.DeclareLocal(Types.Single);
|
|
break;
|
|
case 'D':
|
|
locals[i] = ilgen.DeclareLocal(Types.Double);
|
|
break;
|
|
case 'V':
|
|
break;
|
|
default:
|
|
throw new BailoutException(Bailout.PreconditionViolated, "Unsupported typeChar(): " + name.typeChar());
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Low-level emit helpers.
|
|
*/
|
|
private void emitConst(object con) {
|
|
if (con == null) {
|
|
ilgen.Emit(OpCodes.Ldnull);
|
|
} else if (con is string) {
|
|
ilgen.Emit(OpCodes.Ldstr, (string)con);
|
|
} else if (con is java.lang.Integer) {
|
|
ilgen.EmitLdc_I4(((java.lang.Integer)con).intValue());
|
|
} else if (con is java.lang.Long) {
|
|
ilgen.EmitLdc_I8(((java.lang.Long)con).longValue());
|
|
} else if (con is java.lang.Float) {
|
|
ilgen.EmitLdc_R4(((java.lang.Float)con).floatValue());
|
|
} else if (con is java.lang.Double) {
|
|
ilgen.EmitLdc_R8(((java.lang.Double)con).doubleValue());
|
|
} else if (con is java.lang.Boolean) {
|
|
ilgen.EmitLdc_I4(((java.lang.Boolean)con).booleanValue() ? 1 : 0);
|
|
} else {
|
|
throw new BailoutException(Bailout.UnsupportedConstant, con);
|
|
}
|
|
}
|
|
|
|
private void emitIconstInsn(int i) {
|
|
ilgen.EmitLdc_I4(i);
|
|
}
|
|
|
|
/*
|
|
* NOTE: These load/store methods use the localsMap to find the correct index!
|
|
*/
|
|
private void emitLoadInsn(BasicType type, int index) {
|
|
// [IKVM] we don't need the localsMap (it is used to correct for long/double taking two slots)
|
|
if (locals[index] == null) {
|
|
MethodHandleUtil.LoadPackedArg(ilgen, index, 1, packedArgPos, packedArgType);
|
|
} else {
|
|
ilgen.Emit(OpCodes.Ldloc, locals[index]);
|
|
}
|
|
}
|
|
|
|
private void emitStoreInsn(BasicType type, int index) {
|
|
ilgen.Emit(OpCodes.Stloc, locals[index]);
|
|
}
|
|
|
|
private void emitAstoreInsn(int index) {
|
|
emitStoreInsn(BasicType.L_TYPE, index);
|
|
}
|
|
|
|
private byte arrayTypeCode(Wrapper elementType) {
|
|
switch (elementType.name()) {
|
|
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 BailoutException(Bailout.PreconditionViolated, "elemendType = " + elementType);
|
|
}
|
|
}
|
|
|
|
private OpCode arrayInsnOpcode(byte tcode)
|
|
{
|
|
switch (tcode)
|
|
{
|
|
case Opcodes.T_BOOLEAN:
|
|
case Opcodes.T_BYTE:
|
|
return OpCodes.Stelem_I1;
|
|
case Opcodes.T_CHAR:
|
|
case Opcodes.T_SHORT:
|
|
return OpCodes.Stelem_I2;
|
|
case Opcodes.T_INT:
|
|
return OpCodes.Stelem_I4;
|
|
case Opcodes.T_LONG:
|
|
return OpCodes.Stelem_I8;
|
|
case Opcodes.T_FLOAT:
|
|
return OpCodes.Stelem_R4;
|
|
case Opcodes.T_DOUBLE:
|
|
return OpCodes.Stelem_R8;
|
|
case 0:
|
|
return OpCodes.Stelem_Ref;
|
|
default:
|
|
throw new BailoutException(Bailout.PreconditionViolated, "tcode = " + tcode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 != BasicType.L_TYPE)
|
|
return; // nothing to do
|
|
switch (ptype.name()) {
|
|
case "L_TYPE":
|
|
if (VerifyType.isNullConversion(CoreClasses.java.lang.Object.Wrapper.ClassObject, pclass, false)) {
|
|
//if (PROFILE_LEVEL > 0)
|
|
// emitReferenceCast(Object.class, arg);
|
|
return;
|
|
}
|
|
emitReferenceCast(pclass, arg);
|
|
return;
|
|
case "I_TYPE":
|
|
if (!VerifyType.isNullConversion(java.lang.Integer.TYPE, pclass, false))
|
|
emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass));
|
|
return;
|
|
}
|
|
throw new BailoutException(Bailout.PreconditionViolated, "bad implicit conversion: tc=" + ptype + ": " + pclass);
|
|
}
|
|
|
|
/** Update localClasses type map. Return true if the information is already present. */
|
|
private void assertStaticType(Class cls, Name n) {
|
|
// [IKVM] not implemented
|
|
}
|
|
|
|
private void emitReferenceCast(Class cls, object arg) {
|
|
// [IKVM] handle the type system hole that is caused by arrays being both derived from cli.System.Array and directly from java.lang.Object
|
|
if (cls != CoreClasses.cli.System.Object.Wrapper.ClassObject)
|
|
{
|
|
TypeWrapper.FromClass(cls).EmitCheckcast(ilgen);
|
|
}
|
|
}
|
|
|
|
private sealed class AnonymousClass : TypeWrapper
|
|
{
|
|
internal static readonly Class Instance = new AnonymousClass().ClassObject;
|
|
|
|
private AnonymousClass()
|
|
: base(TypeFlags.Anonymous, Modifiers.Super | Modifiers.Final, "java.lang.invoke.LambdaForm$MH")
|
|
{
|
|
}
|
|
|
|
internal override ClassLoaderWrapper GetClassLoader()
|
|
{
|
|
return ClassLoaderWrapper.GetBootstrapClassLoader();
|
|
}
|
|
|
|
internal override Type TypeAsTBD
|
|
{
|
|
get { throw new InvalidOperationException(); }
|
|
}
|
|
|
|
internal override TypeWrapper BaseTypeWrapper
|
|
{
|
|
get { return CoreClasses.java.lang.Object.Wrapper; }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Generate customized bytecode for a given LambdaForm.
|
|
*/
|
|
public static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType)
|
|
{
|
|
try
|
|
{
|
|
MemberName memberName = new MemberName();
|
|
memberName._clazz(AnonymousClass.Instance);
|
|
memberName._name(form.debugName);
|
|
memberName._type(invokerType);
|
|
memberName._flags(MethodHandleNatives.Constants.MN_IS_METHOD | MethodHandleNatives.Constants.ACC_STATIC | (MethodHandleNatives.Constants.REF_invokeStatic << MethodHandleNatives.Constants.MN_REFERENCE_KIND_SHIFT));
|
|
memberName.vmtarget = new NativeInvokerBytecodeGenerator(form, invokerType).generateCustomizedCodeBytes();
|
|
return memberName;
|
|
}
|
|
#if DEBUG
|
|
catch (BailoutException x)
|
|
{
|
|
Console.WriteLine(x.Message);
|
|
Console.WriteLine("generateCustomizedCode: " + form + ", " + invokerType);
|
|
}
|
|
#else
|
|
catch (BailoutException)
|
|
{
|
|
}
|
|
#endif
|
|
return InvokerBytecodeGenerator.generateCustomizedCode(form, invokerType);
|
|
}
|
|
|
|
/**
|
|
* Generate an invoker method for the passed {@link LambdaForm}.
|
|
*/
|
|
private Delegate generateCustomizedCodeBytes() {
|
|
// 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.name()) {
|
|
case "SELECT_ALTERNATIVE":
|
|
//assert isSelectAlternative(i);
|
|
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 (InvokerBytecodeGenerator.isStaticallyNameable(rtype)) {
|
|
emitNewArray(name);
|
|
continue;
|
|
}
|
|
break;
|
|
case "ARRAY_LOAD":
|
|
emitArrayLoad(name);
|
|
continue;
|
|
case "IDENTITY":
|
|
//assert(name.arguments.length == 1);
|
|
emitPushArguments(name);
|
|
continue;
|
|
case "NONE":
|
|
// no intrinsic associated
|
|
break;
|
|
// [IKVM] ARRAY_STORE and ZERO appear to be unused
|
|
default:
|
|
throw new BailoutException(Bailout.UnsupportedIntrinsic, "Unknown intrinsic: "+intr);
|
|
}
|
|
|
|
MemberName member = name.function._member();
|
|
if (isStaticallyInvocable(member)) {
|
|
emitStaticInvoke(member, name);
|
|
} else {
|
|
emitInvoke(name);
|
|
}
|
|
}
|
|
|
|
// return statement
|
|
emitReturn(onStack);
|
|
|
|
ilgen.DoEmit();
|
|
return dm.CreateDelegate(delegateType, constants.ToArray());
|
|
}
|
|
|
|
void emitArrayLoad(Name name) {
|
|
OpCode arrayOpcode = OpCodes.Ldelem_Ref;
|
|
Class elementType = name.function.methodType().parameterType(0).getComponentType();
|
|
emitPushArguments(name);
|
|
if (elementType.isPrimitive()) {
|
|
Wrapper w = Wrapper.forPrimitiveType(elementType);
|
|
arrayOpcode = arrayLoadOpcode(arrayTypeCode(w));
|
|
}
|
|
ilgen.Emit(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));
|
|
EmitConstant(target);
|
|
emitReferenceCast(CoreClasses.java.lang.invoke.MethodHandle.Wrapper.ClassObject, 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);
|
|
EmitInvokeBasic(type.basicType());
|
|
}
|
|
|
|
static bool 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;
|
|
*/
|
|
if (member.isMethod()) {
|
|
// [IKVM] If we can't call the method directly, invoke it via the invokeBasic infrastructure.
|
|
return IsMethodHandleLinkTo(member)
|
|
|| IsMethodHandleInvokeBasic(member)
|
|
|| IsStaticallyInvocable(GetMethodWrapper(member));
|
|
}
|
|
if (member.isField()) {
|
|
// [IKVM] If we can't access the field directly, use the invokeBasic infrastructure.
|
|
return IsStaticallyInvocable(GetFieldWrapper(member));
|
|
}
|
|
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) {
|
|
// push arguments
|
|
emitPushArguments(name);
|
|
|
|
// invocation
|
|
if (member.isMethod()) {
|
|
if (IsMethodHandleLinkTo(member)) {
|
|
MethodType mt = member.getMethodType();
|
|
TypeWrapper[] args = new TypeWrapper[mt.parameterCount()];
|
|
for (int j = 0; j < args.Length; j++) {
|
|
args[j] = TypeWrapper.FromClass(mt.parameterType(j));
|
|
args[j].Finish();
|
|
}
|
|
TypeWrapper ret = TypeWrapper.FromClass(mt.returnType());
|
|
ret.Finish();
|
|
Compiler.MethodHandleMethodWrapper.EmitLinkToCall(ilgen, args, ret);
|
|
ret.EmitConvSignatureTypeToStackType(ilgen);
|
|
} else if (IsMethodHandleInvokeBasic(member)) {
|
|
EmitInvokeBasic(member.getMethodType());
|
|
} else {
|
|
switch (member.getReferenceKind()) {
|
|
case MethodHandleNatives.Constants.REF_invokeInterface:
|
|
case MethodHandleNatives.Constants.REF_invokeSpecial:
|
|
case MethodHandleNatives.Constants.REF_invokeStatic:
|
|
case MethodHandleNatives.Constants.REF_invokeVirtual:
|
|
break;
|
|
default:
|
|
throw new BailoutException(Bailout.UnsupportedRefKind, member);
|
|
}
|
|
MethodWrapper mw = GetMethodWrapper(member);
|
|
if (!IsStaticallyInvocable(mw)) {
|
|
throw new BailoutException(Bailout.NotStaticallyInvocable, member);
|
|
}
|
|
mw.Link();
|
|
mw.DeclaringType.Finish();
|
|
mw.ResolveMethod();
|
|
if (mw.HasCallerID) {
|
|
EmitConstant(DynamicCallerIDProvider.Instance);
|
|
ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.DynamicCallerID);
|
|
}
|
|
if (mw.IsStatic || member.getReferenceKind() == MethodHandleNatives.Constants.REF_invokeSpecial) {
|
|
mw.EmitCall(ilgen);
|
|
} else {
|
|
mw.EmitCallvirt(ilgen);
|
|
}
|
|
mw.ReturnType.EmitConvSignatureTypeToStackType(ilgen);
|
|
}
|
|
} else if (member.isField()) {
|
|
FieldWrapper fw = GetFieldWrapper(member);
|
|
if (!IsStaticallyInvocable(fw)) {
|
|
throw new BailoutException(Bailout.NotStaticallyInvocable, member);
|
|
}
|
|
fw.Link();
|
|
fw.DeclaringType.Finish();
|
|
fw.ResolveField();
|
|
switch (member.getReferenceKind()) {
|
|
case MethodHandleNatives.Constants.REF_getField:
|
|
case MethodHandleNatives.Constants.REF_getStatic:
|
|
fw.EmitGet(ilgen);
|
|
fw.FieldTypeWrapper.EmitConvSignatureTypeToStackType(ilgen);
|
|
break;
|
|
case MethodHandleNatives.Constants.REF_putField:
|
|
case MethodHandleNatives.Constants.REF_putStatic:
|
|
fw.EmitSet(ilgen);
|
|
break;
|
|
default:
|
|
throw new BailoutException(Bailout.UnsupportedRefKind, member);
|
|
}
|
|
} else {
|
|
throw new BailoutException(Bailout.NotStaticallyInvocable, member);
|
|
}
|
|
}
|
|
|
|
void emitNewArray(Name name) {
|
|
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 (Exception ex) {
|
|
throw new java.lang.InternalError(ex);
|
|
}
|
|
//assert(java.lang.reflect.Array.getLength(emptyArray) == 0);
|
|
//assert(emptyArray.getClass() == rtype); // exact typing
|
|
//mv.visitLdcInsn(constantPlaceholder(emptyArray));
|
|
EmitConstant(emptyArray);
|
|
emitReferenceCast(rtype, emptyArray);
|
|
return;
|
|
}
|
|
Class arrayElementType = rtype.getComponentType();
|
|
//assert(arrayElementType != null);
|
|
emitIconstInsn(name.arguments.Length);
|
|
OpCode xas = OpCodes.Stelem_Ref;
|
|
if (!arrayElementType.isPrimitive()) {
|
|
TypeWrapper tw = TypeWrapper.FromClass(arrayElementType);
|
|
if (tw.IsUnloadable || tw.IsGhost || tw.IsGhostArray || tw.IsNonPrimitiveValueType) {
|
|
throw new BailoutException(Bailout.UnsupportedArrayType, tw);
|
|
}
|
|
ilgen.Emit(OpCodes.Newarr, tw.TypeAsArrayType);
|
|
} else {
|
|
byte tc = arrayTypeCode(Wrapper.forPrimitiveType(arrayElementType));
|
|
xas = arrayInsnOpcode(tc);
|
|
//mv.visitIntInsn(Opcodes.NEWARRAY, tc);
|
|
ilgen.Emit(OpCodes.Newarr, TypeWrapper.FromClass(arrayElementType).TypeAsArrayType);
|
|
}
|
|
// store arguments
|
|
for (int i = 0; i < name.arguments.Length; i++) {
|
|
//mv.visitInsn(Opcodes.DUP);
|
|
ilgen.Emit(OpCodes.Dup);
|
|
emitIconstInsn(i);
|
|
emitPushArgument(name, i);
|
|
//mv.visitInsn(xas);
|
|
ilgen.Emit(xas);
|
|
}
|
|
// the array is left on the stack
|
|
assertStaticType(rtype, name);
|
|
}
|
|
|
|
/**
|
|
* 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];
|
|
|
|
CodeEmitterLabel L_fallback = ilgen.DefineLabel();
|
|
CodeEmitterLabel L_done = ilgen.DefineLabel();
|
|
|
|
// load test result
|
|
emitPushArgument(selectAlternativeName, 0);
|
|
|
|
// if_icmpne L_fallback
|
|
ilgen.EmitBrfalse(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
|
|
ilgen.EmitBr(L_done);
|
|
|
|
// L_fallback:
|
|
ilgen.MarkLabel(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:
|
|
ilgen.MarkLabel(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];
|
|
|
|
CodeEmitterLabel L_handler = ilgen.DefineLabel();
|
|
CodeEmitterLabel L_done = ilgen.DefineLabel();
|
|
|
|
Class returnType = result.function._resolvedHandle().type().returnType();
|
|
MethodType type = args.function._resolvedHandle().type()
|
|
.dropParameterTypes(0,1)
|
|
.changeReturnType(returnType);
|
|
|
|
// Normal case
|
|
ilgen.BeginExceptionBlock();
|
|
// load target
|
|
emitPushArgument(invoker, 0);
|
|
emitPushArguments(args, 1); // skip 1st argument: method handle
|
|
EmitInvokeBasic(type.basicType());
|
|
CodeEmitterLocal returnValue = null;
|
|
if (returnType != java.lang.Void.TYPE) {
|
|
returnValue = ilgen.DeclareLocal(TypeWrapper.FromClass(returnType).TypeAsLocalOrStackType);
|
|
ilgen.Emit(OpCodes.Stloc, returnValue);
|
|
}
|
|
ilgen.EmitLeave(L_done);
|
|
|
|
// Exceptional case
|
|
ilgen.BeginCatchBlock(typeof(Exception));
|
|
|
|
// [IKVM] map the exception and store it in a local and exit the handler
|
|
ilgen.EmitLdc_I4(0);
|
|
ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.mapException.MakeGenericMethod(typeof(Exception)));
|
|
CodeEmitterLocal exception = ilgen.DeclareLocal(typeof(Exception));
|
|
ilgen.Emit(OpCodes.Stloc, exception);
|
|
ilgen.EmitLeave(L_handler);
|
|
ilgen.EndExceptionBlock();
|
|
|
|
// Check exception's type
|
|
ilgen.MarkLabel(L_handler);
|
|
// load exception class
|
|
emitPushArgument(invoker, 1);
|
|
ilgen.Emit(OpCodes.Ldloc, exception);
|
|
CoreClasses.java.lang.Class.Wrapper.GetMethodWrapper("isInstance", "(Ljava.lang.Object;)Z", false).EmitCall(ilgen);
|
|
CodeEmitterLabel L_rethrow = ilgen.DefineLabel();
|
|
ilgen.EmitBrfalse(L_rethrow);
|
|
|
|
// Invoke catcher
|
|
// load catcher
|
|
emitPushArgument(invoker, 2);
|
|
ilgen.Emit(OpCodes.Ldloc, exception);
|
|
emitPushArguments(args, 1); // skip 1st argument: method handle
|
|
MethodType catcherType = type.insertParameterTypes(0, CoreClasses.java.lang.Throwable.Wrapper.ClassObject);
|
|
EmitInvokeBasic(catcherType.basicType());
|
|
if (returnValue != null) {
|
|
ilgen.Emit(OpCodes.Stloc, returnValue);
|
|
}
|
|
ilgen.EmitBr(L_done);
|
|
|
|
ilgen.MarkLabel(L_rethrow);
|
|
ilgen.Emit(OpCodes.Ldloc, exception);
|
|
ilgen.Emit(OpCodes.Call, Compiler.unmapExceptionMethod);
|
|
ilgen.Emit(OpCodes.Throw);
|
|
|
|
ilgen.MarkLabel(L_done);
|
|
if (returnValue != null) {
|
|
ilgen.Emit(OpCodes.Ldloc, returnValue);
|
|
}
|
|
|
|
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.basicType(ptype);
|
|
if (arg is Name) {
|
|
Name n = (Name)arg;
|
|
emitLoadInsn(n._type(), n.index());
|
|
emitImplicitConversion(n._type(), ptype, n);
|
|
} else if ((arg == null || arg is string) && bptype == BasicType.L_TYPE) {
|
|
emitConst(arg);
|
|
} else {
|
|
if (Wrapper.isWrapperType(ikvm.extensions.ExtensionMethods.getClass(arg)) && bptype != BasicType.L_TYPE) {
|
|
emitConst(arg);
|
|
} else {
|
|
EmitConstant(arg);
|
|
emitImplicitConversion(BasicType.L_TYPE, ptype, arg);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Store the name to its local, if necessary.
|
|
*/
|
|
private void emitStoreResult(Name name) {
|
|
if (name != null && name._type() != BasicType.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 == BasicType.V_TYPE) {
|
|
// [IKVM] unlike the JVM, the CLR doesn't like left over values on the stack
|
|
if (onStack != null && onStack._type() != BasicType.V_TYPE) {
|
|
ilgen.Emit(OpCodes.Pop);
|
|
}
|
|
} 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);
|
|
}
|
|
ilgen.Emit(OpCodes.Ret);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
bool error = false;
|
|
switch (from.name()) {
|
|
case "LONG":
|
|
switch (to.name()) {
|
|
case "FLOAT": ilgen.Emit(OpCodes.Conv_R4); break;
|
|
case "DOUBLE": ilgen.Emit(OpCodes.Conv_R8); break;
|
|
default: error = true; break;
|
|
}
|
|
break;
|
|
case "FLOAT":
|
|
switch (to.name()) {
|
|
case "LONG": ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.f2l); break;
|
|
case "DOUBLE": ilgen.Emit(OpCodes.Conv_R8); break;
|
|
default: error = true; break;
|
|
}
|
|
break;
|
|
case "DOUBLE":
|
|
switch (to.name()) {
|
|
case "LONG" : ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.d2l); break;
|
|
case "FLOAT": ilgen.Emit(OpCodes.Conv_R4); break;
|
|
default: error = true; break;
|
|
}
|
|
break;
|
|
default:
|
|
error = true;
|
|
break;
|
|
}
|
|
if (error) {
|
|
throw new BailoutException(Bailout.PreconditionViolated, "unhandled prim cast: " + from + "2" + to);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void emitI2X(Wrapper type) {
|
|
switch (type.name()) {
|
|
case "BYTE": ilgen.Emit(OpCodes.Conv_I1); break;
|
|
case "SHORT": ilgen.Emit(OpCodes.Conv_I2); break;
|
|
case "CHAR": ilgen.Emit(OpCodes.Conv_U2); break;
|
|
case "INT": /* naught */ break;
|
|
case "LONG": ilgen.Emit(OpCodes.Conv_I8); break;
|
|
case "FLOAT": ilgen.Emit(OpCodes.Conv_R4); break;
|
|
case "DOUBLE": ilgen.Emit(OpCodes.Conv_R8); break;
|
|
case "BOOLEAN":
|
|
// For compatibility with ValueConversions and explicitCastArguments:
|
|
ilgen.EmitLdc_I4(1);
|
|
ilgen.Emit(OpCodes.And);
|
|
break;
|
|
default: throw new BailoutException(Bailout.PreconditionViolated, "unknown type: " + type);
|
|
}
|
|
}
|
|
|
|
private void emitX2I(Wrapper type) {
|
|
switch (type.name()) {
|
|
case "LONG": ilgen.Emit(OpCodes.Conv_I4); break;
|
|
case "FLOAT": ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.f2i); break;
|
|
case "DOUBLE": ilgen.Emit(OpCodes.Call, ByteCodeHelperMethods.d2i); break;
|
|
default: throw new BailoutException(Bailout.PreconditionViolated, "unknown type: " + type);
|
|
}
|
|
}
|
|
|
|
private void EmitConstant(object obj)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
ilgen.Emit(OpCodes.Ldnull);
|
|
return;
|
|
}
|
|
int index = constants.IndexOf(obj);
|
|
if (index == -1)
|
|
{
|
|
index = constants.Count;
|
|
constants.Add(obj);
|
|
}
|
|
ilgen.EmitLdarg(0); // we want the bound value, not the real first parameter
|
|
ilgen.EmitLdc_I4(index);
|
|
ilgen.Emit(OpCodes.Ldelem_Ref);
|
|
}
|
|
|
|
private void EmitInvokeBasic(MethodType mt)
|
|
{
|
|
TypeWrapper[] args = new TypeWrapper[mt.parameterCount()];
|
|
for (int i = 0; i < args.Length; i++)
|
|
{
|
|
args[i] = TypeWrapper.FromClass(mt.parameterType(i));
|
|
args[i].Finish();
|
|
}
|
|
TypeWrapper ret = TypeWrapper.FromClass(mt.returnType());
|
|
ret.Finish();
|
|
Compiler.MethodHandleMethodWrapper.EmitInvokeBasic(ilgen, args, ret, false);
|
|
}
|
|
|
|
private OpCode arrayLoadOpcode(byte tcode)
|
|
{
|
|
switch (tcode)
|
|
{
|
|
case Opcodes.T_BOOLEAN:
|
|
case Opcodes.T_BYTE:
|
|
return OpCodes.Ldelem_I1;
|
|
case Opcodes.T_CHAR:
|
|
return OpCodes.Ldelem_U2;
|
|
case Opcodes.T_SHORT:
|
|
return OpCodes.Ldelem_I2;
|
|
case Opcodes.T_INT:
|
|
return OpCodes.Ldelem_I4;
|
|
case Opcodes.T_LONG:
|
|
return OpCodes.Ldelem_I8;
|
|
case Opcodes.T_FLOAT:
|
|
return OpCodes.Ldelem_R4;
|
|
case Opcodes.T_DOUBLE:
|
|
return OpCodes.Ldelem_R8;
|
|
case 0:
|
|
return OpCodes.Ldelem_Ref;
|
|
default:
|
|
throw new BailoutException(Bailout.PreconditionViolated, "tcode = " + tcode);
|
|
}
|
|
}
|
|
|
|
private static bool IsMethodHandleLinkTo(MemberName member)
|
|
{
|
|
return member.getDeclaringClass() == CoreClasses.java.lang.invoke.MethodHandle.Wrapper.ClassObject
|
|
&& member.getName().StartsWith("linkTo", StringComparison.Ordinal);
|
|
}
|
|
|
|
private static bool IsMethodHandleInvokeBasic(MemberName member)
|
|
{
|
|
return member.getDeclaringClass() == CoreClasses.java.lang.invoke.MethodHandle.Wrapper.ClassObject
|
|
&& member.getName() == "invokeBasic";
|
|
}
|
|
|
|
private static MethodWrapper GetMethodWrapper(MemberName member)
|
|
{
|
|
return TypeWrapper.FromClass(member.getDeclaringClass()).GetMethodWrapper(member.getName(), member.getSignature().Replace('/', '.'), true);
|
|
}
|
|
|
|
private static bool IsStaticallyInvocable(MethodWrapper mw)
|
|
{
|
|
if (mw == null || mw.DeclaringType.IsUnloadable || mw.DeclaringType.IsGhost || mw.DeclaringType.IsNonPrimitiveValueType || mw.IsFinalizeOrClone || mw.IsDynamicOnly)
|
|
{
|
|
return false;
|
|
}
|
|
if (mw.ReturnType.IsUnloadable || mw.ReturnType.IsGhost || mw.ReturnType.IsNonPrimitiveValueType)
|
|
{
|
|
return false;
|
|
}
|
|
foreach (TypeWrapper tw in mw.GetParameters())
|
|
{
|
|
if (tw.IsUnloadable || tw.IsGhost || tw.IsNonPrimitiveValueType)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private static FieldWrapper GetFieldWrapper(MemberName member)
|
|
{
|
|
return TypeWrapper.FromClass(member.getDeclaringClass()).GetFieldWrapper(member.getName(), member.getSignature().Replace('/', '.'));
|
|
}
|
|
|
|
private static bool IsStaticallyInvocable(FieldWrapper fw)
|
|
{
|
|
return fw != null
|
|
&& !fw.FieldTypeWrapper.IsUnloadable
|
|
&& !fw.FieldTypeWrapper.IsGhost
|
|
&& !fw.FieldTypeWrapper.IsNonPrimitiveValueType;
|
|
}
|
|
#endif
|
|
}
|