diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java index f137e183fbd5..db0ef1dc5d34 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/ClassMethodRefConstant.java @@ -37,6 +37,7 @@ import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.methodhandle.MHInvokeGenericNode; +import com.oracle.truffle.espresso.resolver.LinkResolver; import com.oracle.truffle.espresso.runtime.EspressoContext; public interface ClassMethodRefConstant extends MethodRefConstant { @@ -50,10 +51,6 @@ default Tag tag() { return Tag.METHOD_REF; } - default MHInvokeGenericNode.MethodHandleInvoker invoker() { - return null; - } - final class Indexes extends MethodRefConstant.Indexes implements ClassMethodRefConstant, Resolvable { Indexes(int classIndex, int nameAndTypeIndex) { super(classIndex, nameAndTypeIndex); @@ -172,28 +169,16 @@ final class Indexes extends MethodRefConstant.Indexes implements ClassMethodRefC @Override public ResolvedConstant resolve(RuntimeConstantPool pool, int thisIndex, ObjectKlass accessingKlass) { METHODREF_RESOLVE_COUNT.inc(); - EspressoContext context = pool.getContext(); - Klass holderKlass = getResolvedHolderKlass(accessingKlass, pool); - Meta meta = context.getMeta(); - if (holderKlass.isInterface()) { - throw meta.throwExceptionWithMessage(meta.java_lang_IncompatibleClassChangeError, meta.toGuestString(getName(pool))); - } + Klass holderKlass = getResolvedHolderKlass(accessingKlass, pool); Symbol name = getName(pool); Symbol signature = getSignature(pool); - Method method = holderKlass.lookupMethod(name, signature); - if (method == null) { - throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, meta.toGuestString(holderKlass.getNameAsString() + "." + getName(pool) + signature)); - } - - MemberRefConstant.doAccessCheck(accessingKlass, holderKlass, method, meta); + Method method = LinkResolver.resolveSymbol(meta, accessingKlass, name, signature, holderKlass, false, true, true); - if (!method.isPolySignatureIntrinsic()) { - method.checkLoadingConstraints(accessingKlass.getDefiningClassLoader(), method.getDeclaringKlass().getDefiningClassLoader()); - } else if (method.isInvokeIntrinsic()) { + if (method.isInvokeIntrinsic()) { MHInvokeGenericNode.MethodHandleInvoker invoker = MHInvokeGenericNode.linkMethod(meta.getLanguage(), meta, accessingKlass, method, name, signature); return new ResolvedWithInvoker(method, invoker); } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/InterfaceMethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/InterfaceMethodRefConstant.java index 22abc17ba300..a5c47517c2ee 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/InterfaceMethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/InterfaceMethodRefConstant.java @@ -35,8 +35,7 @@ import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.EspressoError; -import com.oracle.truffle.espresso.meta.Meta; -import com.oracle.truffle.espresso.runtime.EspressoContext; +import com.oracle.truffle.espresso.resolver.LinkResolver; public interface InterfaceMethodRefConstant extends MethodRefConstant { @@ -110,30 +109,12 @@ final class Indexes extends MethodRefConstant.Indexes implements InterfaceMethod @Override public ResolvedConstant resolve(RuntimeConstantPool pool, int thisIndex, ObjectKlass accessingKlass) { METHODREF_RESOLVE_COUNT.inc(); - EspressoContext context = pool.getContext(); - Meta meta = context.getMeta(); Klass holderInterface = getResolvedHolderKlass(accessingKlass, pool); - Symbol name = getName(pool); - - // 1. If C is not an interface, interface method resolution throws an - // IncompatibleClassChangeError. - if (!holderInterface.isInterface()) { - throw meta.throwExceptionWithMessage(meta.java_lang_IncompatibleClassChangeError, "Found class " + holderInterface.getExternalName() + ", but interface was expected"); - } - Symbol signature = getSignature(pool); - Method method = ((ObjectKlass) holderInterface).resolveInterfaceMethod(name, signature); - - if (method == null) { - throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, meta.toGuestString(name)); - } - - MemberRefConstant.doAccessCheck(accessingKlass, holderInterface, method, meta); - - method.checkLoadingConstraints(accessingKlass.getDefiningClassLoader(), method.getDeclaringKlass().getDefiningClassLoader()); + Method method = LinkResolver.resolveSymbol(pool.getContext().getMeta(), accessingKlass, name, signature, holderInterface, true, true, true); return new Resolved(method); } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java index e45f65e30867..9d5f8fdcfc46 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/classfile/constantpool/MethodRefConstant.java @@ -25,6 +25,7 @@ import com.oracle.truffle.espresso.classfile.ConstantPool; import com.oracle.truffle.espresso.descriptors.Symbol; import com.oracle.truffle.espresso.descriptors.Symbol.Signature; +import com.oracle.truffle.espresso.nodes.methodhandle.MHInvokeGenericNode; import com.oracle.truffle.espresso.perf.DebugCounter; public interface MethodRefConstant extends MemberRefConstant { @@ -41,6 +42,10 @@ default Symbol getSignature(ConstantPool pool) { return (Symbol) getDescriptor(pool); } + default MHInvokeGenericNode.MethodHandleInvoker invoker() { + return null; + } + abstract class Indexes extends MemberRefConstant.Indexes implements MethodRefConstant { Indexes(int classIndex, int nameAndTypeIndex) { super(classIndex, nameAndTypeIndex); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java index 17e998dffd06..1dd2f74daff1 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/BytecodeNode.java @@ -315,7 +315,6 @@ import com.oracle.truffle.espresso.classfile.attributes.BootstrapMethodsAttribute; import com.oracle.truffle.espresso.classfile.attributes.LineNumberTableAttribute; import com.oracle.truffle.espresso.classfile.constantpool.ClassConstant; -import com.oracle.truffle.espresso.classfile.constantpool.ClassMethodRefConstant; import com.oracle.truffle.espresso.classfile.constantpool.DoubleConstant; import com.oracle.truffle.espresso.classfile.constantpool.DynamicConstant; import com.oracle.truffle.espresso.classfile.constantpool.FloatConstant; @@ -376,6 +375,10 @@ import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeVirtualQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedMethodNode; import com.oracle.truffle.espresso.perf.DebugCounter; +import com.oracle.truffle.espresso.resolver.CallKind; +import com.oracle.truffle.espresso.resolver.CallSiteType; +import com.oracle.truffle.espresso.resolver.LinkResolver; +import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.EspressoExitException; @@ -2394,141 +2397,38 @@ private int quickenArrayStore(final VirtualFrame frame, int top, int curBCI, int // endregion quickenForeign private InvokeQuickNode dispatchQuickened(int top, int curBCI, char cpi, int opcode, int statementIndex, Method resolutionSeed, boolean allowBytecodeInlining) { - Method resolved = resolutionSeed; - int resolvedOpCode = opcode; - switch (opcode) { - case INVOKESTATIC: - // Otherwise, if the resolved method is an instance method, the invokestatic - // instruction throws an IncompatibleClassChangeError. - if (!resolved.isStatic()) { - enterLinkageExceptionProfile(); - throw throwBoundary(getMethod().getMeta().java_lang_IncompatibleClassChangeError, "Expected static method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); - } - break; - case INVOKEINTERFACE: - // Otherwise, if the resolved method is static or (jdk8 or earlier) private, the - // invokeinterface instruction throws an IncompatibleClassChangeError. - if (resolved.isStatic() || - (getMethod().getContext().getJavaVersion().java8OrEarlier() && resolved.isPrivate())) { - enterLinkageExceptionProfile(); - throw throwBoundary(getMethod().getMeta().java_lang_IncompatibleClassChangeError, "Expected instance method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); - } - if (resolved.getITableIndex() < 0) { - if (resolved.isPrivate()) { - assert getJavaVersion().java9OrLater(); - // Interface private methods do not appear in itables. - resolvedOpCode = INVOKESPECIAL; - } else { - // Can happen in old classfiles that calls j.l.Object on interfaces. - resolvedOpCode = INVOKEVIRTUAL; - } - } - break; - case INVOKEVIRTUAL: - // Otherwise, if the resolved method is a class (static) method, the invokevirtual - // instruction throws an IncompatibleClassChangeError. - if (resolved.isStatic()) { - enterLinkageExceptionProfile(); - throw throwBoundary(getMethod().getMeta().java_lang_IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); - } - if (resolved.isFinalFlagSet() || resolved.getDeclaringKlass().isFinalFlagSet() || resolved.isPrivate()) { - resolvedOpCode = INVOKESPECIAL; - } - break; - case INVOKESPECIAL: - // Otherwise, if the resolved method is an instance initialization method, and the - // class in which it is declared is not the class symbolically referenced by the - // instruction, a NoSuchMethodError is thrown. - if (resolved.isConstructor()) { - if (resolved.getDeclaringKlass().getName() != getConstantPool().methodAt(cpi).getHolderKlassName(getConstantPool())) { - enterLinkageExceptionProfile(); - throw throwBoundary(getMethod().getMeta().java_lang_NoSuchMethodError, - "%s.%s%s", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); - } - } - // Otherwise, if the resolved method is a class (static) method, the invokespecial - // instruction throws an IncompatibleClassChangeError. - if (resolved.isStatic()) { - enterLinkageExceptionProfile(); - throw throwBoundary(getMethod().getMeta().java_lang_IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", - resolved.getDeclaringKlass().getName(), - resolved.getName(), - resolved.getRawSignature()); - } - // If all of the following are true, let C be the direct superclass of the current - // class: - // - // * The resolved method is not an instance initialization method (§2.9). - // - // * If the symbolic reference names a class (not an interface), then that class is - // a superclass of the current class. - // - // * The ACC_SUPER flag is set for the class file (§4.1). In Java SE 8 and - // above, the Java Virtual Machine considers the ACC_SUPER flag to be set in every - // class file, regardless of the actual value of the flag in the class file and the - // version of the class file. - if (!resolved.isConstructor()) { - ObjectKlass declaringKlass = getMethod().getDeclaringKlass(); - Klass symbolicRef = ((MethodRefConstant.Indexes) getConstantPool().methodAt(cpi)).getResolvedHolderKlass(declaringKlass, getConstantPool()); - if (!symbolicRef.isInterface() && - symbolicRef != declaringKlass && - declaringKlass.getSuperKlass() != null && - symbolicRef != declaringKlass.getSuperKlass() && - symbolicRef.isAssignableFrom(declaringKlass)) { - resolved = declaringKlass.getSuperKlass().lookupMethod(resolved.getName(), resolved.getRawSignature(), Klass.LookupMode.INSTANCE_ONLY); - } - } - break; - default: - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.unimplemented("Quickening for " + Bytecodes.nameOf(opcode)); - } + + Klass symbolicRef = ((MethodRefConstant.Indexes) getConstantPool().methodAt(cpi)).getResolvedHolderKlass(getDeclaringKlass(), getConstantPool()); + ResolvedCall resolvedCall = LinkResolver.resolveCallSite(getMeta(), getDeclaringKlass(), resolutionSeed, CallSiteType.fromOpCode(opcode), symbolicRef); + + Method resolved = resolvedCall.getResolvedMethod(); + CallKind callKind = resolvedCall.getCallKind(); // Skip inlined nodes if instrumentation is live. // Lock must be owned for correctness. assert lockIsHeld(); boolean tryBytecodeLevelInlining = this.instrumentation == null && allowBytecodeInlining; if (tryBytecodeLevelInlining) { - var node = InlinedMethodNode.createFor(resolved, top, resolvedOpCode, curBCI, statementIndex); + InlinedMethodNode node = InlinedMethodNode.createFor(resolvedCall, top, opcode, curBCI, statementIndex); if (node != null) { return node; } } - InvokeQuickNode invoke; if (resolved.isPolySignatureIntrinsic()) { - MethodHandleInvoker invoker = null; - if ((resolvedOpCode == INVOKEVIRTUAL || resolvedOpCode == INVOKESPECIAL) && (getConstantPool().resolvedMethodRefAt(getDeclaringKlass(), cpi) instanceof ClassMethodRefConstant methodRef)) { - // There might be an invoker if it's an InvokeGeneric - invoker = methodRef.invoker(); - } - invoke = new InvokeHandleNode(resolved, invoker, top, curBCI); + MethodHandleInvoker invoker = getConstantPool().resolvedMethodRefAt(getDeclaringKlass(), cpi).invoker(); + assert invoker == null || ((opcode == INVOKEVIRTUAL || opcode == INVOKESPECIAL) && resolved.isInvokeIntrinsic()); + return new InvokeHandleNode(resolved, invoker, top, curBCI); } else { // @formatter:off - switch (resolvedOpCode) { - case INVOKESTATIC : invoke = new InvokeStaticQuickNode(resolved, top, curBCI); break; - case INVOKEINTERFACE : invoke = new InvokeInterfaceQuickNode(resolved, top, curBCI); break; - case INVOKEVIRTUAL : invoke = new InvokeVirtualQuickNode(resolved, top, curBCI); break; - case INVOKESPECIAL : invoke = new InvokeSpecialQuickNode(resolved, top, curBCI); break; - default : - CompilerDirectives.transferToInterpreterAndInvalidate(); - throw EspressoError.unimplemented("Quickening for " + Bytecodes.nameOf(resolvedOpCode)); - } + return switch (callKind) { + case STATIC -> new InvokeStaticQuickNode(resolved, top, curBCI); + case ITABLE_LOOKUP -> new InvokeInterfaceQuickNode(resolved, top, curBCI); + case VTABLE_LOOKUP -> new InvokeVirtualQuickNode(resolved, top, curBCI); + case DIRECT -> new InvokeSpecialQuickNode(resolved, top, curBCI); + }; // @formatter:on } - return invoke; } @TruffleBoundary diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java index 649ff8919e8e..e3839b9e168a 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/ConditionalInlinedMethodNode.java @@ -23,11 +23,6 @@ package com.oracle.truffle.espresso.nodes.quick.invoke.inline; -import static com.oracle.truffle.espresso.bytecode.Bytecodes.INVOKEINTERFACE; -import static com.oracle.truffle.espresso.bytecode.Bytecodes.INVOKESPECIAL; -import static com.oracle.truffle.espresso.bytecode.Bytecodes.INVOKESTATIC; -import static com.oracle.truffle.espresso.bytecode.Bytecodes.INVOKEVIRTUAL; - import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.espresso.impl.Method; @@ -36,6 +31,7 @@ import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeSpecialQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeStaticQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeVirtualQuickNode; +import com.oracle.truffle.espresso.resolver.ResolvedCall; public class ConditionalInlinedMethodNode extends InlinedMethodNode { @@ -50,9 +46,9 @@ public interface Recipes { @Child protected InvokeQuickNode fallbackNode; private final InlinedMethodPredicate condition; - public ConditionalInlinedMethodNode(Method.MethodVersion inlinedMethod, int top, int opcode, int callerBCI, int statementIndex, Recipes recipes, InlinedMethodPredicate condition) { - super(inlinedMethod, top, opcode, callerBCI, statementIndex, null); - this.fallbackNode = getFallback(inlinedMethod.getMethod(), top, callerBCI, opcode); + public ConditionalInlinedMethodNode(ResolvedCall resolvedCall, int top, int opcode, int callerBCI, int statementIndex, Recipes recipes, InlinedMethodPredicate condition) { + super(resolvedCall.getResolvedMethod().getMethodVersion(), top, opcode, callerBCI, statementIndex, null); + this.fallbackNode = getFallback(resolvedCall, top, callerBCI); this.condition = condition; this.recipes = recipes; } @@ -83,13 +79,13 @@ public static InlinedMethodNode getDefinitiveNode(Recipes recipes, return replacement; } - static InvokeQuickNode getFallback(Method inlinedMethod, int top, int callerBCI, int opcode) { + static InvokeQuickNode getFallback(ResolvedCall resolvedCall, int top, int callerBCI) { // @formatter:off - switch (opcode) { - case INVOKESTATIC : return new InvokeStaticQuickNode(inlinedMethod, top, callerBCI); - case INVOKESPECIAL : return new InvokeSpecialQuickNode(inlinedMethod, top, callerBCI); - case INVOKEVIRTUAL : return new InvokeVirtualQuickNode(inlinedMethod, top, callerBCI); - case INVOKEINTERFACE : // fallback. + switch (resolvedCall.getCallKind()) { + case STATIC : return new InvokeStaticQuickNode(resolvedCall.getResolvedMethod(), top, callerBCI); + case DIRECT : return new InvokeSpecialQuickNode(resolvedCall.getResolvedMethod(), top, callerBCI); + case VTABLE_LOOKUP : return new InvokeVirtualQuickNode(resolvedCall.getResolvedMethod(), top, callerBCI); + case ITABLE_LOOKUP : // fallback. default : CompilerDirectives.transferToInterpreterAndInvalidate(); throw EspressoError.unimplemented("Conditional bytecode-level inlining only available for invokestatic, invokespecial and invokevirtual"); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java index ac65c8fcabf4..19fcbd7c598c 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/GuardedConditionalInlinedMethodNode.java @@ -29,6 +29,7 @@ import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeQuickNode; +import com.oracle.truffle.espresso.resolver.ResolvedCall; public final class GuardedConditionalInlinedMethodNode extends InlinedMethodNode { private final ConditionalInlinedMethodNode.Recipes recipes; @@ -37,11 +38,11 @@ public final class GuardedConditionalInlinedMethodNode extends InlinedMethodNode private final InlinedMethodPredicate condition; private final InlinedMethodPredicate guard; - public GuardedConditionalInlinedMethodNode(Method.MethodVersion inlinedMethod, int top, int opcode, int callerBCI, int statementIndex, + public GuardedConditionalInlinedMethodNode(ResolvedCall resolvedCall, int top, int opcode, int callerBCI, int statementIndex, ConditionalInlinedMethodNode.Recipes recipes, InlinedMethodPredicate condition, InlinedMethodPredicate guard) { - super(inlinedMethod, top, opcode, callerBCI, statementIndex, null); - this.fallbackNode = insert(getFallback(inlinedMethod.getMethod(), top, callerBCI, opcode)); + super(resolvedCall.getResolvedMethod().getMethodVersion(), top, opcode, callerBCI, statementIndex, null); + this.fallbackNode = insert(getFallback(resolvedCall, top, callerBCI)); this.condition = condition; this.guard = guard; this.recipes = recipes; diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java index 87ada9b02c0b..92352673cfc9 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/InlinedMethodNode.java @@ -28,7 +28,6 @@ import com.oracle.truffle.api.CompilerDirectives; import com.oracle.truffle.api.frame.VirtualFrame; import com.oracle.truffle.api.source.SourceSection; -import com.oracle.truffle.espresso.bytecode.Bytecodes; import com.oracle.truffle.espresso.impl.Method; import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.nodes.BytecodeNode; @@ -37,6 +36,8 @@ import com.oracle.truffle.espresso.nodes.quick.invoke.InvokeQuickNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.bodies.InlinedFieldAccessNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.bodies.InlinedSubstitutionBodyNode; +import com.oracle.truffle.espresso.resolver.CallKind; +import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.substitutions.JavaSubstitution; import com.oracle.truffle.espresso.substitutions.Substitutions; @@ -107,17 +108,18 @@ public SourceSection getSourceSection() { @Child BodyNode body; - public static InlinedMethodNode createFor(Method resolutionSeed, int top, int opcode, int curBCI, int statementIndex) { - if (!isInlineCandidate(resolutionSeed, opcode)) { + public static InlinedMethodNode createFor(ResolvedCall resolvedCall, int top, int opcode, int curBCI, int statementIndex) { + if (!isInlineCandidate(resolvedCall)) { return null; } + Method resolutionSeed = resolvedCall.getResolvedMethod(); if (resolutionSeed.isInlinableGetter()) { - return InlinedFieldAccessNode.createGetter(resolutionSeed, top, opcode, curBCI, statementIndex); + return InlinedFieldAccessNode.createGetter(resolvedCall, top, opcode, curBCI, statementIndex); } if (resolutionSeed.isInlinableSetter()) { - return InlinedFieldAccessNode.createSetter(resolutionSeed, top, opcode, curBCI, statementIndex); + return InlinedFieldAccessNode.createSetter(resolvedCall, top, opcode, curBCI, statementIndex); } - if (isUnconditionalInlineCandidate(opcode)) { + if (isUnconditionalInlineCandidate(resolvedCall)) { // Try to inline trivial substitutions. JavaSubstitution.Factory factory = Substitutions.lookupSubstitution(resolutionSeed); if (factory != null && factory.inlineInBytecode()) { @@ -188,16 +190,18 @@ public final BaseQuickNode revertToGeneric(BytecodeNode parent) { return parent.generifyInlinedMethodNode(top, opcode, getCallerBCI(), statementIndex, method.getMethod()); } - public static boolean isInlineCandidate(Method resolutionSeed, int opcode) { + public static boolean isInlineCandidate(ResolvedCall resolvedCall) { + Method resolutionSeed = resolvedCall.getResolvedMethod(); if (resolutionSeed.isSynchronized()) { return false; } - if (opcode == Bytecodes.INVOKESTATIC || opcode == Bytecodes.INVOKESPECIAL) { + CallKind kind = resolvedCall.getCallKind(); + if (kind.isDirectCall()) { return true; } - if (opcode == Bytecodes.INVOKEVIRTUAL) { + if (kind == CallKind.VTABLE_LOOKUP) { // InvokeVirtual can be bytecode-level inlined if there are no overrides yet. - // final methods are already translated to INVOKESPECIAL + // final methods are already translated to DIRECT calls if (resolutionSeed.getContext().getClassHierarchyOracle().isLeafMethod(resolutionSeed).isValid()) { return true; } @@ -205,7 +209,7 @@ public static boolean isInlineCandidate(Method resolutionSeed, int opcode) { return false; } - public static boolean isUnconditionalInlineCandidate(int opcode) { - return opcode == Bytecodes.INVOKESTATIC || opcode == Bytecodes.INVOKESPECIAL; + public static boolean isUnconditionalInlineCandidate(ResolvedCall resolvedCall) { + return resolvedCall.getCallKind().isDirectCall(); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java index e4bdc624d5a9..7da66f1d8e17 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/nodes/quick/invoke/inline/bodies/InlinedFieldAccessNode.java @@ -38,6 +38,7 @@ import com.oracle.truffle.espresso.nodes.quick.invoke.inline.GuardedConditionalInlinedMethodNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedMethodNode; import com.oracle.truffle.espresso.nodes.quick.invoke.inline.InlinedMethodPredicate; +import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoContext; public abstract class InlinedFieldAccessNode extends InlinedMethodNode.BodyNode implements InlinedMethodPredicate { @@ -94,18 +95,18 @@ protected InlinedFieldAccessNode(Method.MethodVersion method, char fieldCpi) { this.field = getInlinedField(method, fieldCpi); } - public static InlinedMethodNode createGetter(Method inlinedMethod, int top, int opCode, int curBCI, int statementIndex) { - Method.MethodVersion methodVersion = inlinedMethod.getMethodVersion(); + public static InlinedMethodNode createGetter(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex) { + Method.MethodVersion methodVersion = resolvedCall.getResolvedMethod().getMethodVersion(); char fieldCpi = InlinedFieldAccessNode.getFieldCpi(false, methodVersion); ConditionalInlinedMethodNode.Recipes recipes = new FieldAccessRecipes(() -> new InlinedGetterNode(methodVersion, fieldCpi)); - return create(methodVersion, top, opCode, curBCI, statementIndex, recipes, fieldCpi); + return create(resolvedCall, top, opCode, curBCI, statementIndex, recipes, fieldCpi); } - public static InlinedMethodNode createSetter(Method inlinedMethod, int top, int opCode, int curBCI, int statementIndex) { - Method.MethodVersion methodVersion = inlinedMethod.getMethodVersion(); + public static InlinedMethodNode createSetter(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex) { + Method.MethodVersion methodVersion = resolvedCall.getResolvedMethod().getMethodVersion(); char fieldCpi = InlinedFieldAccessNode.getFieldCpi(true, methodVersion); ConditionalInlinedMethodNode.Recipes recipes = new FieldAccessRecipes(() -> new InlinedSetterNode(methodVersion, fieldCpi)); - return create(methodVersion, top, opCode, curBCI, statementIndex, recipes, fieldCpi); + return create(resolvedCall, top, opCode, curBCI, statementIndex, recipes, fieldCpi); } /** @@ -122,12 +123,13 @@ public static InlinedMethodNode createSetter(Method inlinedMethod, int top, int * to a generic invoke if the method is no longer a leaf. * */ - private static InlinedMethodNode create(Method.MethodVersion inlinedMethod, int top, int opCode, int curBCI, int statementIndex, + private static InlinedMethodNode create(ResolvedCall resolvedCall, int top, int opCode, int curBCI, int statementIndex, ConditionalInlinedMethodNode.Recipes recipes, char fieldCpi) { - assert isInlineCandidate(inlinedMethod.getMethod(), opCode); + assert isInlineCandidate(resolvedCall); + Method.MethodVersion inlinedMethod = resolvedCall.getResolvedMethod().getMethodVersion(); boolean isDefinitive = isResolutionSuccessAt(inlinedMethod, fieldCpi); if (isDefinitive) { - if (isUnconditionalInlineCandidate(opCode)) { + if (isUnconditionalInlineCandidate(resolvedCall)) { return ConditionalInlinedMethodNode.getDefinitiveNode(recipes, inlinedMethod, top, opCode, curBCI, statementIndex); } else { return GuardedConditionalInlinedMethodNode.getDefinitiveNode(recipes, InlinedMethodPredicate.LEAF_ASSUMPTION_CHECK, @@ -135,10 +137,10 @@ private static InlinedMethodNode create(Method.MethodVersion inlinedMethod, int } } InlinedMethodPredicate condition = (context, version, frame, node) -> isResolutionSuccessAt(version, fieldCpi); - if (isUnconditionalInlineCandidate(opCode)) { - return new ConditionalInlinedMethodNode(inlinedMethod, top, opCode, curBCI, statementIndex, recipes, condition); + if (isUnconditionalInlineCandidate(resolvedCall)) { + return new ConditionalInlinedMethodNode(resolvedCall, top, opCode, curBCI, statementIndex, recipes, condition); } else { - return new GuardedConditionalInlinedMethodNode(inlinedMethod, top, opCode, curBCI, statementIndex, recipes, condition, InlinedMethodPredicate.LEAF_ASSUMPTION_CHECK); + return new GuardedConditionalInlinedMethodNode(resolvedCall, top, opCode, curBCI, statementIndex, recipes, condition, InlinedMethodPredicate.LEAF_ASSUMPTION_CHECK); } } diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallKind.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallKind.java new file mode 100644 index 000000000000..2282ece60b4f --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallKind.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +package com.oracle.truffle.espresso.resolver; + +/** + * Indicates what dispatch behavior should be performed for a call-site. + *

+ * In opposition to {@link CallSiteType}, these are not 1-to-1 mappings from the call-site + * bytecodes, as, for example, a {@link CallSiteType#Virtual} may resolve to a {@link #DIRECT} + * dispatch when the symbolic resolution points to a {@code final} method. + */ +public enum CallKind { + /** + * A static call: No receiver and no call-time resolution. + */ + STATIC(Constants.IS_STATIC | Constants.IS_DIRECT_CALL), + /** + * A direct call: A receiver but no call-time resolution. + */ + DIRECT(Constants.IS_DIRECT_CALL), + /** + * A virtual call: A receiver and a call-time lookup in the vtable. + */ + VTABLE_LOOKUP(Constants.HAS_LOOKUP), + /** + * An interface call: A receiver and a call-time lookup in the itable. + */ + ITABLE_LOOKUP(Constants.HAS_LOOKUP); + + private final byte tags; + + CallKind(int tags) { + this.tags = (byte) tags; + } + + public boolean isStatic() { + return (tags & Constants.IS_STATIC) != 0; + } + + public boolean isDirectCall() { + return (tags & Constants.IS_DIRECT_CALL) != 0; + } + + public boolean hasLookup() { + return (tags & Constants.HAS_LOOKUP) != 0; + } + + private static final class Constants { + static final byte IS_STATIC = 0b0001; + static final byte IS_DIRECT_CALL = 0b0010; + static final byte HAS_LOOKUP = 0b0100; + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallSiteType.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallSiteType.java new file mode 100644 index 000000000000..e1dce80531cb --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/CallSiteType.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +package com.oracle.truffle.espresso.resolver; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.espresso.bytecode.Bytecodes; +import com.oracle.truffle.espresso.classfile.Constants; +import com.oracle.truffle.espresso.meta.EspressoError; + +/** + * Describes the type of call-site resolution that should happen for a given call-site. For + * call-sites in the bytecodes, they correspond 1-to-1 to {@link Bytecodes#INVOKESTATIC}, + * {@link Bytecodes#INVOKESPECIAL}, {@link Bytecodes#INVOKEVIRTUAL}, + * {@link Bytecodes#INVOKEINTERFACE}. + */ +public enum CallSiteType { + Static, + Special, + Virtual, + Interface; + + public static CallSiteType fromOpCode(int opcode) { + switch (opcode) { + case Bytecodes.INVOKESTATIC: + return Static; + case Bytecodes.INVOKESPECIAL: + return Special; + case Bytecodes.INVOKEVIRTUAL: + return Virtual; + case Bytecodes.INVOKEINTERFACE: + return Interface; + default: + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere(Bytecodes.nameOf(opcode)); + } + } + + public static CallSiteType fromRefKind(int refKind) { + switch (refKind) { + case Constants.REF_invokeVirtual: + return Virtual; + case Constants.REF_invokeStatic: + return Static; + case Constants.REF_invokeSpecial: // fallthrough + case Constants.REF_newInvokeSpecial: + return Special; + case Constants.REF_invokeInterface: + return Interface; + default: + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere("refKind: " + refKind); + } + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/LinkResolver.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/LinkResolver.java new file mode 100644 index 000000000000..f2ae618cd9d2 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/LinkResolver.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +package com.oracle.truffle.espresso.resolver; + +import static com.oracle.truffle.espresso.meta.EspressoError.cat; + +import java.util.Locale; + +import com.oracle.truffle.api.CompilerDirectives; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.espresso.classfile.constantpool.MemberRefConstant; +import com.oracle.truffle.espresso.descriptors.Symbol; +import com.oracle.truffle.espresso.descriptors.Symbol.Name; +import com.oracle.truffle.espresso.descriptors.Symbol.Signature; +import com.oracle.truffle.espresso.impl.Klass; +import com.oracle.truffle.espresso.impl.Method; +import com.oracle.truffle.espresso.impl.ObjectKlass; +import com.oracle.truffle.espresso.meta.EspressoError; +import com.oracle.truffle.espresso.meta.Meta; + +public final class LinkResolver { + /** + * Symbolically resolves a method. + * + * @param meta + * @param accessingKlass The class requesting resolution. + * @param name The name of the method. + * @param signature The signature of the method. + * @param symbolicHolder The holder of the method, as described in the constant pool. + * @param accessCheck Whether to perform access checks on the resolved method. + * @param loadingConstraints Whether to check loading constraints on the resolved method. + */ + public static Method resolveSymbol(Meta meta, ObjectKlass accessingKlass, + Symbol name, Symbol signature, Klass symbolicHolder, + boolean interfaceLookup, + boolean accessCheck, boolean loadingConstraints) { + return LinkResolverImpl.resolveSymbol(meta, accessingKlass, name, signature, symbolicHolder, interfaceLookup, accessCheck, loadingConstraints); + } + + /** + * Resolve a call-site given the symbolic resolution of the method in the constant pool. + * + * @param meta + * @param currentKlass The class in which the call site to resolve appears. + * @param symbolicResolution The result of the symbolic resolution of the method declared in the + * call site. + * @param callSiteType The {@link CallSiteType} representing the call site to resolve. + * @param symbolicHolder The declared holder for symbolic resolution. May differ from + * {@code symbolicResolution.getDeclaringKlass()}. + */ + public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method symbolicResolution, CallSiteType callSiteType, Klass symbolicHolder) { + return LinkResolverImpl.resolveCallSite(meta, currentKlass, symbolicResolution, callSiteType, symbolicHolder); + } + + // Only static + private LinkResolver() { + } +} + +final class LinkResolverImpl { + + private static final String AN_INTERFACE = "an interface"; + private static final String A_CLASS = "a class"; + + @TruffleBoundary + public static Method resolveSymbol(Meta meta, ObjectKlass accessingKlass, Symbol name, Symbol signature, Klass symbolicHolder, + boolean interfaceLookup, + boolean accessCheck, boolean loadingConstraints) { + Method resolved; + if (interfaceLookup != symbolicHolder.isInterface()) { + String expected = interfaceLookup ? AN_INTERFACE : A_CLASS; + String found = interfaceLookup ? A_CLASS : AN_INTERFACE; + meta.throwExceptionWithMessage(meta.java_lang_IncompatibleClassChangeError, cat("Resolution failure for ", symbolicHolder.getExternalName(), ".\n", + "Is ", found, ", but ", expected, " was expected.")); + } + + if (symbolicHolder.isInterface()) { + resolved = ((ObjectKlass) symbolicHolder).resolveInterfaceMethod(name, signature); + } else { + resolved = symbolicHolder.lookupMethod(name, signature); + } + if (resolved == null) { + throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, symbolicHolder.getNameAsString() + "." + name + signature); + } + if (accessCheck) { + MemberRefConstant.doAccessCheck(accessingKlass, symbolicHolder, resolved, meta); + } + if (loadingConstraints && !resolved.isPolySignatureIntrinsic()) { + resolved.checkLoadingConstraints(accessingKlass.getDefiningClassLoader(), resolved.getDeclaringKlass().getDefiningClassLoader()); + } + return resolved; + } + + public static ResolvedCall resolveCallSite(Meta meta, Klass currentKlass, Method symbolicResolution, CallSiteType callSiteType, Klass symbolicHolder) { + Method resolved = symbolicResolution; + CallKind callKind; + switch (callSiteType) { + case Static: + // Otherwise, if the resolved method is an instance method, the invokestatic + // instruction throws an IncompatibleClassChangeError. + if (!resolved.isStatic()) { + throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected static method '%s.%s%s'", + resolved.getDeclaringKlass().getName(), + resolved.getName(), + resolved.getRawSignature()); + } + callKind = CallKind.STATIC; + break; + case Interface: + // Otherwise, if the resolved method is static or (jdk8 or earlier) private, the + // invokeinterface instruction throws an IncompatibleClassChangeError. + if (resolved.isStatic() || + (meta.getJavaVersion().java8OrEarlier() && resolved.isPrivate())) { + throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected instance method '%s.%s%s'", + resolved.getDeclaringKlass().getName(), + resolved.getName(), + resolved.getRawSignature()); + } + if (resolved.getITableIndex() < 0) { + if (resolved.isPrivate()) { + assert meta.getJavaVersion().java9OrLater(); + // Interface private methods do not appear in itables. + callKind = CallKind.DIRECT; + } else { + assert resolved.getVTableIndex() >= 0; + // Can happen in old classfiles that calls j.l.Object on interfaces. + callKind = CallKind.VTABLE_LOOKUP; + } + } else { + callKind = CallKind.ITABLE_LOOKUP; + } + break; + case Virtual: + // Otherwise, if the resolved method is a class (static) method, the invokevirtual + // instruction throws an IncompatibleClassChangeError. + if (resolved.isStatic()) { + throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", + resolved.getDeclaringKlass().getName(), + resolved.getName(), + resolved.getRawSignature()); + } + if (resolved.isFinalFlagSet() || resolved.getDeclaringKlass().isFinalFlagSet() || resolved.isPrivate()) { + callKind = CallKind.DIRECT; + } else { + callKind = CallKind.VTABLE_LOOKUP; + } + break; + case Special: + // Otherwise, if the resolved method is an instance initialization method, and the + // class in which it is declared is not the class symbolically referenced by the + // instruction, a NoSuchMethodError is thrown. + if (resolved.isConstructor()) { + if (resolved.getDeclaringKlass().getName() != symbolicHolder.getName()) { + throw throwBoundary(meta, meta.java_lang_NoSuchMethodError, + "%s.%s%s", + resolved.getDeclaringKlass().getName(), + resolved.getName(), + resolved.getRawSignature()); + } + } + // Otherwise, if the resolved method is a class (static) method, the invokespecial + // instruction throws an IncompatibleClassChangeError. + if (resolved.isStatic()) { + throw throwBoundary(meta, meta.java_lang_IncompatibleClassChangeError, "Expected instance not static method '%s.%s%s'", + resolved.getDeclaringKlass().getName(), + resolved.getName(), + resolved.getRawSignature()); + } + // If all of the following are true, let C be the direct superclass of the current + // class: + // + // * The resolved method is not an instance initialization method (§2.9). + // + // * If the symbolic reference names a class (not an interface), then that class is + // a superclass of the current class. + // + // * The ACC_SUPER flag is set for the class file (§4.1). In Java SE 8 and + // above, the Java Virtual Machine considers the ACC_SUPER flag to be set in every + // class file, regardless of the actual value of the flag in the class file and the + // version of the class file. + if (!resolved.isConstructor()) { + if (!symbolicHolder.isInterface() && + symbolicHolder != currentKlass && + currentKlass.getSuperKlass() != null && + symbolicHolder != currentKlass.getSuperKlass() && + symbolicHolder.isAssignableFrom(currentKlass)) { + resolved = currentKlass.getSuperKlass().lookupMethod(resolved.getName(), resolved.getRawSignature(), Klass.LookupMode.INSTANCE_ONLY); + } + } + callKind = CallKind.DIRECT; + break; + default: + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.unimplemented("Resolution for " + callSiteType); + } + return new ResolvedCall(callKind, resolved); + } + + @TruffleBoundary + private static RuntimeException throwBoundary(Meta meta, ObjectKlass exceptionKlass, String messageFormat, Object... args) { + throw meta.throwExceptionWithMessage(exceptionKlass, String.format(Locale.ENGLISH, messageFormat, args)); + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/ResolvedCall.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/ResolvedCall.java new file mode 100644 index 000000000000..11a037b3cdb8 --- /dev/null +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/resolver/ResolvedCall.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024, 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. + * + * 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. + */ + +package com.oracle.truffle.espresso.resolver; + +import java.util.Objects; + +import com.oracle.truffle.espresso.impl.Method; + +public final class ResolvedCall { + private final CallKind callKind; + private final Method resolved; + + public ResolvedCall(CallKind callKind, Method resolved) { + this.callKind = Objects.requireNonNull(callKind); + this.resolved = Objects.requireNonNull(resolved); + } + + public Object call(Object... args) { + return switch (callKind) { + case STATIC -> + resolved.invokeDirectStatic(args); + case DIRECT -> + resolved.invokeDirect(args); + case VTABLE_LOOKUP -> + resolved.invokeDirectVirtual(args); + case ITABLE_LOOKUP -> + resolved.invokeDirectInterface(args); + }; + } + + public Method getResolvedMethod() { + return resolved; + } + + public CallKind getCallKind() { + return callKind; + } +} diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java index 2e0b74a487d5..093bb246b067 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_java_lang_invoke_MethodHandleNatives.java @@ -31,9 +31,11 @@ import static com.oracle.truffle.espresso.classfile.Constants.REF_invokeSpecial; import static com.oracle.truffle.espresso.classfile.Constants.REF_invokeStatic; import static com.oracle.truffle.espresso.classfile.Constants.REF_invokeVirtual; +import static com.oracle.truffle.espresso.classfile.Constants.REF_newInvokeSpecial; import static com.oracle.truffle.espresso.classfile.Constants.REF_putField; import static com.oracle.truffle.espresso.classfile.Constants.REF_putStatic; import static com.oracle.truffle.espresso.meta.EspressoError.cat; +import static com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics.PolySigIntrinsics.InvokeGeneric; import static com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics.PolySigIntrinsics.None; import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.ALL_KINDS; import static com.oracle.truffle.espresso.substitutions.Target_java_lang_invoke_MethodHandleNatives.Constants.CONSTANTS; @@ -74,6 +76,9 @@ import com.oracle.truffle.espresso.meta.EspressoError; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.EspressoNode; +import com.oracle.truffle.espresso.resolver.CallSiteType; +import com.oracle.truffle.espresso.resolver.LinkResolver; +import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoContext; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.MethodHandleIntrinsics; @@ -97,19 +102,13 @@ public final class Target_java_lang_invoke_MethodHandleNatives { // Actual planting Method target = Method.getHostReflectiveMethodRoot(ref, meta); plantResolvedMethod(self, target, target.getRefKind(), meta); - // Finish the job - meta.java_lang_invoke_MemberName_clazz.setObject(self, target.getDeclaringKlass().mirror()); } else if (targetKlass.getType() == Type.java_lang_reflect_Field) { // Actual planting Field field = Field.getReflectiveFieldRoot(ref, meta); plantResolvedField(self, field, getRefKind(meta.java_lang_invoke_MemberName_flags.getInt(self)), meta, language); - // Finish the job - Klass fieldKlass = meta.java_lang_reflect_Field_class.getObject(ref).getMirrorKlass(meta); - meta.java_lang_invoke_MemberName_clazz.setObject(self, fieldKlass.mirror()); } else if (targetKlass.getType() == Type.java_lang_reflect_Constructor) { Method target = Method.getHostReflectiveConstructorRoot(ref, meta); plantResolvedMethod(self, target, target.getRefKind(), meta); - meta.java_lang_invoke_MemberName_clazz.setObject(self, target.getDeclaringKlass().mirror()); } else { CompilerDirectives.transferToInterpreterAndInvalidate(); throw EspressoError.shouldNotReachHere("invalid argument for MemberName.init: " + ref.getKlass()); @@ -427,124 +426,137 @@ StaticObject doCached( @Cached BranchProfile isMethodProfile, @Cached BranchProfile isFieldProfile, @Cached BranchProfile isConstructorProfile, - @Cached BranchProfile noMethodNameProfile, - @Cached BranchProfile isSignaturePolymorphicIntrinsicProfile, - @Cached BranchProfile isInvokeStaticOrInterfaceProfile, - @Cached BranchProfile isInvokeVirtualOrSpecialProfile, + @Cached BranchProfile iaeProfile, + @Cached BranchProfile internalErrorProfile, + @Cached BranchProfile linkageErrorProfile, @Cached BranchProfile isHandleMethodProfile) { + if (StaticObject.isNull(memberName)) { + internalErrorProfile.enter(); + throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "Member Name is null."); + } + // JDK code should have already checked that 'caller' has access to 'memberName.clazz'. + if (meta.HIDDEN_VMTARGET.getHiddenObject(memberName) != null) { return memberName; // Already planted } StaticObject clazz = meta.java_lang_invoke_MemberName_clazz.getObject(memberName); - if (StaticObject.isNull(clazz)) { - return StaticObject.NULL; + StaticObject type = meta.java_lang_invoke_MemberName_type.getObject(memberName); + StaticObject guestName = meta.java_lang_invoke_MemberName_name.getObject(memberName); + + if (StaticObject.isNull(guestName) || StaticObject.isNull(type) || StaticObject.isNull(clazz)) { + iaeProfile.enter(); + throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "Nothing to resolve."); } + // Extract resolution information from member name. Klass resolutionKlass = clazz.getMirrorKlass(meta); + final int flags = meta.java_lang_invoke_MemberName_flags.getInt(memberName); + if (Integer.bitCount(flags & ALL_KINDS) != 1) { + // Ensure the flags field is not ill-formed. + iaeProfile.enter(); + throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "Invalid MemberName flag format."); + } + ByteSequence desc = asSignature(type, meta); + Symbol name = lookupName(meta, meta.toHostString(guestName), (Constants.flagHas(flags, MN_IS_FIELD)) ? meta.java_lang_NoSuchFieldException : meta.java_lang_NoSuchMethodException); + ObjectKlass callerKlass = StaticObject.isNull(caller) ? null : (ObjectKlass) caller.getMirrorKlass(meta); - int flags = meta.java_lang_invoke_MemberName_flags.getInt(memberName); + boolean doAccessChecks = callerKlass != null; + boolean doConstraintsChecks = (callerKlass != null && ((lookupMode & LM_UNCONDITIONAL) == 0)); int refKind = getRefKind(flags); - StaticObject name = meta.java_lang_invoke_MemberName_name.getObject(memberName); - if (StaticObject.isNull(name)) { - return StaticObject.NULL; - } - Symbol methodName; - try { - methodName = meta.getNames().lookup(meta.toHostString(name)); - } catch (EspressoError e) { - methodName = null; + if (Constants.flagHas(flags, MN_IS_FIELD)) { + isFieldProfile.enter(); + Symbol t = lookupType(meta, desc); + plantFieldMemberName(memberName, resolutionKlass, name, t, refKind, callerKlass, doAccessChecks, doConstraintsChecks, meta, getLanguage()); + return memberName; } - if (methodName == null) { - noMethodNameProfile.enter(); - if ((flags & ALL_KINDS) == MN_IS_FIELD) { - throw meta.throwException(meta.java_lang_NoSuchFieldException); - } else { - throw meta.throwException(meta.java_lang_NoSuchMethodException); + + isMethodProfile.enter(); + if (Constants.flagHas(flags, MN_IS_CONSTRUCTOR)) { + isConstructorProfile.enter(); + if (name != Name._init_) { + linkageErrorProfile.enter(); + throw meta.throwException(meta.java_lang_LinkageError); } + // Ignores refKind + refKind = REF_invokeSpecial; + } else if (!Constants.flagHas(flags, MN_IS_METHOD)) { + internalErrorProfile.enter(); + throw meta.throwExceptionWithMessage(meta.java_lang_InternalError, "Unrecognized MemberName format"); } + // Check if we got a polymorphic signature method, in which case we may need to force + // the creation of a new signature symbol. + PolySigIntrinsics mhMethodId = getPolysignatureIntrinsicID(isHandleMethodProfile, flags, resolutionKlass, refKind, name); + + if (mhMethodId == InvokeGeneric) { + // Can not resolve InvokeGeneric, as we would miss the invoker and appendix. + internalErrorProfile.enter(); + throw meta.throwException(meta.java_lang_InternalError); + } + + Symbol sig = lookupSignature(meta, desc, mhMethodId); + Method m = LinkResolver.resolveSymbol(meta, callerKlass, name, sig, resolutionKlass, resolutionKlass.isInterface(), doAccessChecks, doConstraintsChecks); + ResolvedCall resolvedCall = LinkResolver.resolveCallSite(meta, callerKlass, m, CallSiteType.fromRefKind(refKind), resolutionKlass); + + plantResolvedMethod(memberName, resolvedCall, meta); + + return memberName; + } + + private static PolySigIntrinsics getPolysignatureIntrinsicID(BranchProfile isHandleMethodProfile, int flags, Klass resolutionKlass, int refKind, Symbol name) { PolySigIntrinsics mhMethodId = None; - if (((flags & ALL_KINDS) == MN_IS_METHOD) && + if (Constants.flagHas(flags, MN_IS_METHOD) && (resolutionKlass.getType() == Type.java_lang_invoke_MethodHandle || resolutionKlass.getType() == Type.java_lang_invoke_VarHandle)) { isHandleMethodProfile.enter(); if (refKind == REF_invokeVirtual || refKind == REF_invokeSpecial || refKind == REF_invokeStatic) { - PolySigIntrinsics iid = MethodHandleIntrinsics.getId(methodName, resolutionKlass); + PolySigIntrinsics iid = MethodHandleIntrinsics.getId(name, resolutionKlass); if (iid != None && ((refKind == REF_invokeStatic) == (iid.isStaticPolymorphicSignature()))) { mhMethodId = iid; } } } + return mhMethodId; + } + } - ObjectKlass callerKlass = StaticObject.isNull(caller) ? null : (ObjectKlass) caller.getMirrorKlass(meta); - - boolean doAccessChecks = callerKlass != null; - boolean doConstraintsChecks = (callerKlass != null && ((lookupMode & LM_UNCONDITIONAL) == 0)); + @TruffleBoundary + private static Symbol lookupName(Meta meta, String name, ObjectKlass exceptionKlass) { + Symbol methodName; + try { + methodName = meta.getNames().lookup(name); + } catch (EspressoError e) { + methodName = null; + } + if (methodName == null) { + throw meta.throwExceptionWithMessage(exceptionKlass, name); + } + return methodName; + } - StaticObject type = meta.java_lang_invoke_MemberName_type.getObject(memberName); - if (StaticObject.isNull(type)) { - return StaticObject.NULL; - } - ByteSequence desc = asSignature(type, meta); - switch (flags & ALL_KINDS) { - case MN_IS_CONSTRUCTOR: - isConstructorProfile.enter(); - Symbol constructorSignature; - if (mhMethodId != None) { - constructorSignature = meta.getSignatures().symbolifyValidSignature(desc); - } else { - constructorSignature = meta.getSignatures().lookupValidSignature(desc); - } - if (constructorSignature == null) { - throw meta.throwException(meta.java_lang_NoSuchMethodException); - } - plantMethodMemberName(memberName, resolutionKlass, methodName, constructorSignature, refKind, callerKlass, doAccessChecks, doConstraintsChecks, meta); - meta.HIDDEN_VMINDEX.setHiddenObject(memberName, -3_000_000L); - break; - case MN_IS_METHOD: - isMethodProfile.enter(); - Symbol sig; - if (mhMethodId != None) { - sig = meta.getSignatures().symbolifyValidSignature(desc); - } else { - sig = meta.getSignatures().lookupValidSignature(desc); - } - if (sig == null) { - throw meta.throwException(meta.java_lang_NoSuchMethodException); - } - if (refKind == REF_invokeStatic || refKind == REF_invokeInterface) { - // This branch will also handle MH.linkTo* methods. - isInvokeStaticOrInterfaceProfile.enter(); - plantMethodMemberName(memberName, resolutionKlass, methodName, sig, refKind, callerKlass, doAccessChecks, doConstraintsChecks, meta); - } else if (mhMethodId != None) { - isSignaturePolymorphicIntrinsicProfile.enter(); - assert (!mhMethodId.isStaticPolymorphicSignature()) : "Should have been handled by refKind == REF_invokeStatic"; - EspressoError.guarantee(mhMethodId.isSignaturePolymorphicIntrinsic(), "Should never need to resolve invokeGeneric MemberName"); - plantInvokeBasic(memberName, resolutionKlass, methodName, sig, callerKlass, doAccessChecks, meta); - } else if (refKind == REF_invokeVirtual || refKind == REF_invokeSpecial) { - isInvokeVirtualOrSpecialProfile.enter(); - plantMethodMemberName(memberName, resolutionKlass, methodName, sig, refKind, callerKlass, doAccessChecks, doConstraintsChecks, meta); - } - flags = meta.java_lang_invoke_MemberName_flags.getInt(memberName); - refKind = getRefKind(flags); - meta.HIDDEN_VMINDEX.setHiddenObject(memberName, (refKind == REF_invokeInterface || refKind == REF_invokeVirtual) ? 1_000_000L : -1_000_000L); - break; - case MN_IS_FIELD: - isFieldProfile.enter(); - Symbol t = meta.getLanguage().getTypes().lookup(desc); - if (t == null) { - throw meta.throwException(meta.java_lang_NoSuchFieldException); - } - plantFieldMemberName(memberName, resolutionKlass, methodName, t, refKind, callerKlass, doConstraintsChecks, meta, getLanguage()); - break; - default: - throw meta.throwExceptionWithMessage(meta.java_lang_LinkageError, "Member name resolution failed"); - } + @TruffleBoundary + private static Symbol lookupType(Meta meta, ByteSequence desc) { + Symbol t = meta.getLanguage().getTypes().lookup(desc); + if (t == null) { + throw meta.throwException(meta.java_lang_NoSuchFieldException); + } + return t; + } - return memberName; + @TruffleBoundary + private static Symbol lookupSignature(Meta meta, ByteSequence desc, PolySigIntrinsics iid) { + Symbol signature; + if (iid != None) { + signature = meta.getSignatures().symbolifyValidSignature(desc); + } else { + signature = meta.getSignatures().lookupValidSignature(desc); + } + if (signature == null) { + throw meta.throwException(meta.java_lang_NoSuchMethodException); } + return signature; } private static ByteSequence asSignature(StaticObject typeObject, Meta meta) { @@ -567,69 +579,56 @@ private static ByteSequence methodTypeAsSignature(StaticObject methodType, Meta return Method.getSignatureFromGuestDescription(ptypes, rtype, meta); } - // region MemberName planting - - private static void plantInvokeBasic(StaticObject memberName, Klass resolutionKlass, Symbol name, Symbol sig, ObjectKlass callerKlass, boolean accessCheck, Meta meta) { - assert (name == Name.invokeBasic); - Method target = resolutionKlass.lookupMethod(name, sig); - if (accessCheck) { - MemberRefConstant.doAccessCheck(callerKlass, target.getDeclaringKlass(), target, meta); + private static long refKindToVMIndex(int refKind) { + switch (refKind) { + case REF_invokeStatic: + return Constants.STATIC_INDEX; + case REF_invokeVirtual: + return Constants.VIRTUAL_INDEX; + case REF_invokeInterface: + return Constants.INTERFACE_INDEX; + case REF_invokeSpecial: // fallthrough + case REF_newInvokeSpecial: + return Constants.SPECIAL_INDEX; } - meta.HIDDEN_VMTARGET.setHiddenObject(memberName, target); - meta.java_lang_invoke_MemberName_flags.setInt(memberName, getMethodFlags(target, REF_invokeSpecial)); + CompilerDirectives.transferToInterpreterAndInvalidate(); + throw EspressoError.shouldNotReachHere(); } - private static void plantMethodMemberName(StaticObject memberName, - Klass resolutionKlass, Symbol name, Symbol sig, - int refKind, - ObjectKlass callerKlass, - boolean accessCheck, boolean constraintsChecks, - Meta meta) { - // TODO this needs to be the correct lookup (static, interface, virtual etc.) - Method target; - if (refKind == REF_invokeVirtual && resolutionKlass.isInterface()) { - throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, cat("Found interface ", resolutionKlass.getName(), ", but class was expected")); - } - if (refKind == REF_invokeInterface || (refKind == REF_invokeSpecial && resolutionKlass.isInterface())) { - target = doInterfaceMethodLookup(resolutionKlass, name, sig, meta); - } else { - target = doMethodLookup(resolutionKlass, name, sig); - } - if (target == null) { - throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, cat("Failed lookup for method ", resolutionKlass.getName(), "#", name, ":", sig)); - } - if (target.isStatic() != (refKind == REF_invokeStatic)) { - String expected = (refKind == REF_invokeStatic) ? "Static" : "Instance"; - String actual = (refKind == REF_invokeStatic) ? "Instance" : "Static"; - throw meta.throwExceptionWithMessage(meta.java_lang_IncompatibleClassChangeError, - cat(expected, " method lookup resulted in ", actual, " resolution for method ", resolutionKlass.getName(), "#", name, ":", sig)); - } - if (accessCheck) { - MemberRefConstant.doAccessCheck(callerKlass, target.getDeclaringKlass(), target, meta); - } - if (constraintsChecks) { - target.checkLoadingConstraints(callerKlass.getDefiningClassLoader(), resolutionKlass.getDefiningClassLoader()); - } - plantResolvedMethod(memberName, target, refKind, meta); - } + // region MemberName planting // Exposed to StackWalk public static void plantResolvedMethod(StaticObject memberName, Method target, int refKind, Meta meta) { + int methodFlags = getMethodFlags(target, refKind); + plant(memberName, target, meta, methodFlags); + } + + public static void plantResolvedMethod(StaticObject memberName, ResolvedCall resolvedCall, Meta meta) { + int methodFlags = getMethodFlags(resolvedCall); + plant(memberName, resolvedCall.getResolvedMethod(), meta, methodFlags); + } + + private static void plant(StaticObject memberName, Method target, Meta meta, int methodFlags) { meta.HIDDEN_VMTARGET.setHiddenObject(memberName, target); - meta.java_lang_invoke_MemberName_flags.setInt(memberName, getMethodFlags(target, refKind)); + meta.HIDDEN_VMINDEX.setHiddenObject(memberName, refKindToVMIndex(getRefKind(methodFlags))); + meta.java_lang_invoke_MemberName_flags.setInt(memberName, methodFlags); meta.java_lang_invoke_MemberName_clazz.setObject(memberName, target.getDeclaringKlass().mirror()); } private static void plantFieldMemberName(StaticObject memberName, Klass resolutionKlass, Symbol name, Symbol type, int refKind, - Klass callerKlass, + ObjectKlass callerKlass, + boolean accessCheck, boolean constraintsCheck, Meta meta, EspressoLanguage language) { Field field = doFieldLookup(resolutionKlass, name, type); if (field == null) { throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchFieldError, cat("Failed lookup for field ", resolutionKlass.getName(), "#", name, ":", type)); } + if (accessCheck) { + MemberRefConstant.doAccessCheck(callerKlass, field.getDeclaringKlass(), field, meta); + } if (constraintsCheck) { field.checkLoadingConstraints(callerKlass.getDefiningClassLoader(), resolutionKlass.getDefiningClassLoader()); } @@ -643,6 +642,34 @@ private static void plantResolvedField(StaticObject memberName, Field field, int meta.java_lang_invoke_MemberName_clazz.setObject(memberName, field.getDeclaringKlass().mirror()); } + private static int getMethodFlags(ResolvedCall call) { + int flags = call.getResolvedMethod().getMethodModifiers(); + if (call.getResolvedMethod().isCallerSensitive()) { + flags |= MN_CALLER_SENSITIVE; + } + if (call.getResolvedMethod().isConstructor() || call.getResolvedMethod().isClassInitializer()) { + flags |= MN_IS_CONSTRUCTOR; + flags |= (REF_newInvokeSpecial << MN_REFERENCE_KIND_SHIFT); + return flags; + } + flags |= MN_IS_METHOD; + switch (call.getCallKind()) { + case STATIC: + flags |= (REF_invokeStatic << MN_REFERENCE_KIND_SHIFT); + break; + case DIRECT: + flags |= (REF_invokeSpecial << MN_REFERENCE_KIND_SHIFT); + break; + case VTABLE_LOOKUP: + flags |= (REF_invokeVirtual << MN_REFERENCE_KIND_SHIFT); + break; + case ITABLE_LOOKUP: + flags |= (REF_invokeInterface << MN_REFERENCE_KIND_SHIFT); + break; + } + return flags; + } + private static int getMethodFlags(Method target, int refKind) { int res = target.getMethodModifiers(); if (refKind == REF_invokeInterface) { @@ -687,31 +714,6 @@ private static int getFieldFlags(int refKind, Field fd) { return res; } - private static Method doMethodLookup(Klass resolutionKlass, Symbol name, Symbol sig) { - if (CompilerDirectives.isPartialEvaluationConstant(resolutionKlass)) { - return lookupMethod(resolutionKlass, name, sig); - } else { - return lookupMethodBoundary(resolutionKlass, name, sig); - } - } - - @TruffleBoundary - private static Method doInterfaceMethodLookup(Klass resolutionKlass, Symbol name, Symbol sig, Meta meta) { - if (!resolutionKlass.isInterface()) { - throw meta.throwExceptionWithMessage(meta.java_lang_IncompatibleClassChangeError, meta.toGuestString(name)); - } - return ((ObjectKlass) resolutionKlass).resolveInterfaceMethod(name, sig); - } - - private static Method lookupMethod(Klass resolutionKlass, Symbol name, Symbol sig) { - return resolutionKlass.lookupMethod(name, sig); - } - - @TruffleBoundary - private static Method lookupMethodBoundary(Klass resolutionKlass, Symbol name, Symbol sig) { - return lookupMethod(resolutionKlass, name, sig); - } - private static Field doFieldLookup(Klass resolutionKlass, Symbol name, Symbol sig) { if (CompilerDirectives.isPartialEvaluationConstant(resolutionKlass)) { return lookupField(resolutionKlass, name, sig); @@ -752,6 +754,13 @@ public static final class Constants { private Constants() { } // static only + // VM_Index spoofs + static final long NONE_INDEX = -3_000_000L; + static final long VIRTUAL_INDEX = 1_000_000L; + static final long INTERFACE_INDEX = 2_000_000L; + static final long STATIC_INDEX = -1_000_000L; + static final long SPECIAL_INDEX = -2_000_000L; + public static final int MN_IS_METHOD = 0x00010000; // method (not constructor) public static final int MN_IS_CONSTRUCTOR = 0x00020000; // constructor public static final int MN_IS_FIELD = 0x00040000; // field @@ -788,6 +797,10 @@ private Constants() { static final List> CONSTANTS; static final int CONSTANTS_BEFORE_16; + public static boolean flagHas(int flags, int status) { + return (flags & status) != 0; + } + static { CONSTANTS = new ArrayList<>(); CONSTANTS.add(Pair.create("MN_IS_METHOD", MN_IS_METHOD)); diff --git a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java index 35c32a944a3c..57d8a113f9b5 100644 --- a/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java +++ b/espresso/src/com.oracle.truffle.espresso/src/com/oracle/truffle/espresso/substitutions/Target_sun_reflect_NativeMethodAccessorImpl.java @@ -36,10 +36,13 @@ import com.oracle.truffle.espresso.descriptors.Symbol.Type; import com.oracle.truffle.espresso.impl.Klass; import com.oracle.truffle.espresso.impl.Method; -import com.oracle.truffle.espresso.impl.ObjectKlass; import com.oracle.truffle.espresso.meta.JavaKind; import com.oracle.truffle.espresso.meta.Meta; import com.oracle.truffle.espresso.nodes.interop.ToEspressoNode; +import com.oracle.truffle.espresso.resolver.CallKind; +import com.oracle.truffle.espresso.resolver.CallSiteType; +import com.oracle.truffle.espresso.resolver.LinkResolver; +import com.oracle.truffle.espresso.resolver.ResolvedCall; import com.oracle.truffle.espresso.runtime.EspressoException; import com.oracle.truffle.espresso.runtime.staticobject.StaticObject; @@ -253,8 +256,11 @@ abstract static class Invoke0 extends SubstitutionNode { } } - static @JavaType(Object.class) StaticObject invoke0(@JavaType(java.lang.reflect.Method.class) StaticObject guestMethod, @JavaType(Object.class) StaticObject receiver, - @JavaType(Object[].class) StaticObject args, @Inject EspressoLanguage language, @Inject Meta meta, ToEspressoNode.DynamicToEspresso toEspressoNode) { + static @JavaType(Object.class) StaticObject invoke0( + @JavaType(java.lang.reflect.Method.class) StaticObject guestMethod, @JavaType(Object.class) StaticObject receiver, + @JavaType(Object[].class) StaticObject args, + EspressoLanguage language, Meta meta, + ToEspressoNode.DynamicToEspresso toEspressoNode) { Method reflectedMethod = Method.getHostReflectiveMethodRoot(guestMethod, meta); Klass klass = meta.java_lang_reflect_Method_clazz.getObject(guestMethod).getMirrorKlass(meta); @@ -268,8 +274,11 @@ abstract static class Invoke0 extends SubstitutionNode { return callMethodReflectively(language, meta, receiver, args, reflectedMethod, klass, parameterTypes, toEspressoNode); } - public static @JavaType(Object.class) StaticObject callMethodReflectively(EspressoLanguage language, Meta meta, @JavaType(Object.class) StaticObject receiver, - @JavaType(Object[].class) StaticObject args, Method m, + public static @JavaType(Object.class) StaticObject callMethodReflectively( + EspressoLanguage language, Meta meta, + @JavaType(Object.class) StaticObject receiver, + @JavaType(Object[].class) StaticObject args, + Method m, Klass klass, @JavaType(Class[].class) StaticObject parameterTypes, ToEspressoNode.DynamicToEspresso toEspressoNode) { // Klass should be initialized if method is static, and could be delayed until method // invocation, according to specs. However, JCK tests that it is indeed always initialized @@ -287,12 +296,22 @@ abstract static class Invoke0 extends SubstitutionNode { } } - Method method; // actual method to invoke - + CallSiteType callSiteType; if (reflectedMethod.isStatic()) { - // Ignore receiver argument;. - method = reflectedMethod; + callSiteType = CallSiteType.Static; + } else if (reflectedMethod.getDeclaringKlass().isInterface()) { + callSiteType = CallSiteType.Interface; + } else if (reflectedMethod.isConstructor()) { + callSiteType = CallSiteType.Special; } else { + callSiteType = CallSiteType.Virtual; + } + ResolvedCall resolvedCall = LinkResolver.resolveCallSite( + meta, + null, // No current class. + reflectedMethod, callSiteType, klass); + + if (resolvedCall.getCallKind() != CallKind.STATIC) { if (StaticObject.isNull(receiver)) { throw meta.throwNullPointerException(); } @@ -301,51 +320,31 @@ abstract static class Invoke0 extends SubstitutionNode { if (!klass.isAssignableFrom(receiver.getKlass())) { throw meta.throwExceptionWithMessage(meta.java_lang_IllegalArgumentException, "object is not an instance of declaring class"); } + } - // target klass is receiver's klass - Klass targetKlass = receiver.getKlass(); - // no need to resolve if method is private or - if (reflectedMethod.isPrivate() || Name._init_.equals(reflectedMethod.getName())) { - method = reflectedMethod; - } else { - // resolve based on the receiver - if (reflectedMethod.getDeclaringKlass().isInterface()) { - // resolve interface call - // Match resolution errors with those thrown due to reflection inlining - // Linktime resolution & IllegalAccessCheck already done by Class.getMethod() - method = reflectedMethod; - assert targetKlass instanceof ObjectKlass; - method = ((ObjectKlass) targetKlass).itableLookup(method.getDeclaringKlass(), method.getITableIndex()); - if (method != null) { - // Check for abstract methods as well - if (!method.hasCode()) { - // new default: 65315 - throw meta.throwExceptionWithCause(meta.java_lang_reflect_InvocationTargetException, Meta.initException(meta.java_lang_AbstractMethodError)); - } - } - } else { - // if the method can be overridden, we resolve using the vtable index. - method = reflectedMethod; - // VTable is live, use it - method = targetKlass.vtableLookup(method.getVTableIndex()); - if (method != null) { - // Check for abstract methods as well - if (method.isAbstract()) { - // new default: 65315 - throw meta.throwExceptionWithCause(meta.java_lang_reflect_InvocationTargetException, Meta.initException(meta.java_lang_AbstractMethodError)); - } - } - } - } + Object[] adjustedArgs = makeArgs(resolvedCall, parameterTypes, receiver, args, language, meta, toEspressoNode); + + Object result; + try { + result = resolvedCall.call(adjustedArgs); + } catch (EspressoException e) { + throw meta.throwExceptionWithCause(meta.java_lang_reflect_InvocationTargetException, e.getGuestException()); } - // Comment from HotSpot: - // I believe this is a ShouldNotGetHere case which requires - // an internal vtable bug. If you ever get this please let Karen know. - if (method == null) { - throw meta.throwExceptionWithMessage(meta.java_lang_NoSuchMethodError, "please let Karen know"); + if (reflectedMethod.getReturnKind() == JavaKind.Void) { + return StaticObject.NULL; + } + if (reflectedMethod.getReturnKind().isPrimitive()) { + return Meta.box(meta, result); } + // Result is not void nor primitive, pass through. + return (StaticObject) result; + } + + private static Object[] makeArgs(ResolvedCall resolvedCall, StaticObject parameterTypes, + StaticObject receiver, StaticObject args, + EspressoLanguage language, Meta meta, ToEspressoNode.DynamicToEspresso toEspressoNode) { boolean isForeignArray = args.isForeignObject(); Object rawForeign = null; InteropLibrary interop = null; @@ -363,7 +362,7 @@ abstract static class Invoke0 extends SubstitutionNode { argsLen = StaticObject.isNull(args) ? 0 : args.length(language); } - final Symbol[] signature = method.getParsedSignature(); + final Symbol[] signature = resolvedCall.getResolvedMethod().getParsedSignature(); // Check number of arguments. if (Signatures.parameterCount(signature) != argsLen) { @@ -372,12 +371,12 @@ abstract static class Invoke0 extends SubstitutionNode { int argsOffset = 0; int adjustedArgsLen = argsLen; - if (!reflectedMethod.isStatic()) { + if (!resolvedCall.getCallKind().isStatic()) { adjustedArgsLen += 1; argsOffset = 1; } Object[] adjustedArgs = new Object[adjustedArgsLen]; - if (!reflectedMethod.isStatic()) { + if (!resolvedCall.getCallKind().isStatic()) { adjustedArgs[0] = receiver; } for (int i = 0; i < argsLen; ++i) { @@ -398,23 +397,7 @@ abstract static class Invoke0 extends SubstitutionNode { adjustedArgs[i + argsOffset] = checkAndWiden(meta, (StaticObject) arg, paramKlass); } } - - Object result; - try { - result = method.invokeDirect(adjustedArgs); - } catch (EspressoException e) { - throw meta.throwExceptionWithCause(meta.java_lang_reflect_InvocationTargetException, e.getGuestException()); - } - - if (reflectedMethod.getReturnKind() == JavaKind.Void) { - return StaticObject.NULL; - } - if (reflectedMethod.getReturnKind().isPrimitive()) { - return Meta.box(meta, result); - } - - // Result is not void nor primitive, pass through. - return (StaticObject) result; + return adjustedArgs; } public static class SharedNativeMetohdAccessorImpl extends SubstitutionNamesProvider {