static MethodHandle buildJittedHandle(InvokeSite site, DynamicMethod method, boolean blockGiven) { MethodHandle mh = null; SmartBinder binder; CompiledIRMethod compiledIRMethod = null; if (method instanceof CompiledIRMethod) { compiledIRMethod = (CompiledIRMethod) method; } else if (method instanceof InterpretedIRMethod) { DynamicMethod actualMethod = ((InterpretedIRMethod) method).getActualMethod(); if (actualMethod instanceof CompiledIRMethod) { compiledIRMethod = (CompiledIRMethod) actualMethod; } } if (compiledIRMethod != null) { // attempt IR direct binding // TODO: this will have to expand when we start specializing arities binder = SmartBinder.from(site.signature).permute("context", "self", "arg.*", "block"); if (site.arity == -1) { // already [], nothing to do mh = (MethodHandle) compiledIRMethod.getHandle(); } else if (site.arity == 0) { MethodHandle specific; if ((specific = compiledIRMethod.getHandleFor(site.arity)) != null) { mh = specific; } else { mh = (MethodHandle) compiledIRMethod.getHandle(); binder = binder.insert(2, "args", IRubyObject.NULL_ARRAY); } } else { MethodHandle specific; if ((specific = compiledIRMethod.getHandleFor(site.arity)) != null) { mh = specific; } else { mh = (MethodHandle) compiledIRMethod.getHandle(); binder = binder.collect("args", "arg.*"); } } if (!blockGiven) { binder = binder.append("block", Block.class, Block.NULL_BLOCK); } binder = binder .insert(1, "scope", StaticScope.class, compiledIRMethod.getStaticScope()) .append("class", RubyModule.class, compiledIRMethod.getImplementationClass()); mh = binder.invoke(mh).handle(); } return mh; }
public static RubyString string(MutableCallSite site, ByteList value, ThreadContext context) throws Throwable { MethodHandle handle = SmartBinder.from(STRING_SIGNATURE) .invoke(NEW_STRING_SHARED_HANDLE.apply("byteList", value)) .handle(); site.setTarget(handle); return RubyString.newStringShared(context.runtime, value); }
static MethodHandle buildGenericHandle( InvokeSite site, DynamicMethod method, RubyClass dispatchClass) { SmartBinder binder; binder = SmartBinder.from(site.signature) .permute("context", "self", "arg.*", "block") .insert( 2, new String[] {"rubyClass", "name"}, new Class[] {RubyModule.class, String.class}, dispatchClass, site.name()) .insert(0, "method", DynamicMethod.class, method); if (site.arity > 3) { binder = binder.collect("args", "arg.*"); } return binder.invokeVirtualQuiet(LOOKUP, "call").handle(); }
public Binder prepareBinder() { SmartBinder binder = SmartBinder.from(signature); // prepare arg[] if (arity == -1) { // do nothing, already have IRubyObject[] in args } else if (arity == 0) { binder = binder.insert(argOffset, "args", IRubyObject.NULL_ARRAY); } else { binder = binder.collect("args", "arg[0-9]+"); } // add block if needed if (signature.lastArgType() != Block.class) { binder = binder.append("block", Block.NULL_BLOCK); } // bind to site binder = binder.insert(0, "site", this); return binder.binder(); }
static MethodHandle buildNativeHandle(InvokeSite site, DynamicMethod method, boolean blockGiven) { MethodHandle mh = null; SmartBinder binder = null; if (method.getNativeCall() != null) { int nativeArgCount = getNativeArgCount(method, method.getNativeCall()); DynamicMethod.NativeCall nc = method.getNativeCall(); if (nc.isJava()) { // not supported yet, use DynamicMethod.call } else { if (nativeArgCount >= 0) { // native methods only support arity 3 if (nativeArgCount == site.arity) { // nothing to do binder = SmartBinder.from(lookup(), site.signature); } else { // arity mismatch...leave null and use DynamicMethod.call below } } else { // varargs if (site.arity == -1) { // ok, already passing [] binder = SmartBinder.from(lookup(), site.signature); } else if (site.arity == 0) { // no args, insert dummy binder = SmartBinder.from(lookup(), site.signature) .insert(2, "args", IRubyObject.NULL_ARRAY); } else { // 1 or more args, collect into [] binder = SmartBinder.from(lookup(), site.signature).collect("args", "arg.*"); } } if (binder != null) { // clean up non-arguments, ordering, types if (!nc.hasContext()) { binder = binder.drop("context"); } if (nc.hasBlock() && !blockGiven) { binder = binder.append("block", Block.NULL_BLOCK); } else if (!nc.hasBlock() && blockGiven) { binder = binder.drop("block"); } if (nc.isStatic()) { mh = binder .permute("context", "self", "arg.*", "block") // filter caller .cast(nc.getNativeReturn(), nc.getNativeSignature()) .invokeStaticQuiet(LOOKUP, nc.getNativeTarget(), nc.getNativeName()) .handle(); } else { mh = binder .permute("self", "context", "arg.*", "block") // filter caller, move self .castArg("self", nc.getNativeTarget()) .castVirtual( nc.getNativeReturn(), nc.getNativeTarget(), nc.getNativeSignature()) .invokeVirtualQuiet(LOOKUP, nc.getNativeName()) .handle(); } } } } return mh; }
public class Bootstrap { private static final Logger LOG = LoggerFactory.getLogger("Bootstrap"); static final Lookup LOOKUP = MethodHandles.lookup(); public static CallSite string( Lookup lookup, String name, MethodType type, String value, String encodingName) { Encoding encoding; EncodingDB.Entry entry = EncodingDB.getEncodings().get(encodingName.getBytes()); if (entry == null) entry = EncodingDB.getAliases().get(encodingName.getBytes()); if (entry == null) throw new RuntimeException("could not find encoding: " + encodingName); encoding = entry.getEncoding(); ByteList byteList = new ByteList(value.getBytes(RubyEncoding.ISO), encoding); MutableCallSite site = new MutableCallSite(type); Binder binder = Binder.from(RubyString.class, ThreadContext.class) .insert(0, arrayOf(MutableCallSite.class, ByteList.class), site, byteList); if (name.equals("frozen")) { site.setTarget(binder.invokeStaticQuiet(lookup, Bootstrap.class, "frozenString")); } else { site.setTarget(binder.invokeStaticQuiet(lookup, Bootstrap.class, "string")); } return site; } public static CallSite bytelist( Lookup lookup, String name, MethodType type, String value, String encodingName) { Encoding encoding; EncodingDB.Entry entry = EncodingDB.getEncodings().get(encodingName.getBytes()); if (entry == null) entry = EncodingDB.getAliases().get(encodingName.getBytes()); if (entry == null) throw new RuntimeException("could not find encoding: " + encodingName); encoding = entry.getEncoding(); ByteList byteList = new ByteList(value.getBytes(RubyEncoding.ISO), encoding); return new ConstantCallSite(constant(ByteList.class, byteList)); } public static CallSite array(Lookup lookup, String name, MethodType type) { MethodHandle handle = Binder.from(type) .collect(1, IRubyObject[].class) .invokeStaticQuiet(LOOKUP, Bootstrap.class, "array"); CallSite site = new ConstantCallSite(handle); return site; } public static CallSite hash(Lookup lookup, String name, MethodType type) { MethodHandle handle = Binder.from(lookup, type) .collect(1, IRubyObject[].class) .invokeStaticQuiet(LOOKUP, Bootstrap.class, "hash"); CallSite site = new ConstantCallSite(handle); return site; } public static CallSite kwargsHash(Lookup lookup, String name, MethodType type) { MethodHandle handle = Binder.from(lookup, type) .collect(2, IRubyObject[].class) .invokeStaticQuiet(LOOKUP, Bootstrap.class, "kwargsHash"); CallSite site = new ConstantCallSite(handle); return site; } public static CallSite ivar(Lookup lookup, String name, MethodType type) throws Throwable { String[] names = name.split(":"); String operation = names[0]; String varName = names[1]; VariableSite site = new VariableSite(type, varName, "noname", 0); MethodHandle handle; handle = lookup.findStatic( Bootstrap.class, operation, type.insertParameterTypes(0, VariableSite.class)); handle = handle.bindTo(site); site.setTarget(handle.asType(site.type())); return site; } public static CallSite searchConst( Lookup lookup, String name, MethodType type, int noPrivateConsts) { MutableCallSite site = new MutableCallSite(type); String[] bits = name.split(":"); String constName = bits[1]; MethodHandle handle = Binder.from(lookup, type) .append(site, constName.intern()) .append(noPrivateConsts == 0 ? false : true) .invokeStaticQuiet(LOOKUP, Bootstrap.class, bits[0]); site.setTarget(handle); return site; } public static Handle string() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "string", sig( CallSite.class, Lookup.class, String.class, MethodType.class, String.class, String.class)); } public static Handle bytelist() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "bytelist", sig( CallSite.class, Lookup.class, String.class, MethodType.class, String.class, String.class)); } public static Handle array() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "array", sig(CallSite.class, Lookup.class, String.class, MethodType.class)); } public static Handle hash() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "hash", sig(CallSite.class, Lookup.class, String.class, MethodType.class)); } public static Handle kwargsHash() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "kwargsHash", sig(CallSite.class, Lookup.class, String.class, MethodType.class)); } public static Handle invokeSuper() { return SuperInvokeSite.BOOTSTRAP; } public static Handle ivar() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "ivar", sig(CallSite.class, Lookup.class, String.class, MethodType.class)); } public static Handle searchConst() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "searchConst", sig(CallSite.class, Lookup.class, String.class, MethodType.class, int.class)); } public static RubyString string(MutableCallSite site, ByteList value, ThreadContext context) throws Throwable { MethodHandle handle = SmartBinder.from(STRING_SIGNATURE) .invoke(NEW_STRING_SHARED_HANDLE.apply("byteList", value)) .handle(); site.setTarget(handle); return RubyString.newStringShared(context.runtime, value); } public static RubyString frozenString(MutableCallSite site, ByteList value, ThreadContext context) throws Throwable { RubyString frozen = context.runtime.freezeAndDedupString(RubyString.newStringShared(context.runtime, value)); MethodHandle handle = Binder.from(RubyString.class, ThreadContext.class).dropAll().constant(frozen); site.setTarget(handle); return frozen; } private static final Signature STRING_SIGNATURE = Signature.from(RubyString.class, arrayOf(ThreadContext.class), "context"); private static final Signature NEW_STRING_SHARED_SIGNATURE = Signature.from( RubyString.class, arrayOf(ThreadContext.class, ByteList.class), "context", "byteList"); private static final SmartHandle NEW_STRING_SHARED_HANDLE = SmartBinder.from(NEW_STRING_SHARED_SIGNATURE) .invokeStaticQuiet(MethodHandles.lookup(), Bootstrap.class, "newStringShared"); @JIT private static RubyString newStringShared(ThreadContext context, ByteList byteList) { return RubyString.newStringShared(context.runtime, byteList); } public static IRubyObject array(ThreadContext context, IRubyObject[] elts) { return RubyArray.newArrayNoCopy(context.runtime, elts); } public static Handle contextValue() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "contextValue", sig(CallSite.class, Lookup.class, String.class, MethodType.class)); } public static Handle contextValueString() { return new Handle( Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "contextValueString", sig(CallSite.class, Lookup.class, String.class, MethodType.class, String.class)); } public static CallSite contextValue(Lookup lookup, String name, MethodType type) { MutableCallSite site = new MutableCallSite(type); site.setTarget(Binder.from(type).append(site).invokeStaticQuiet(lookup, Bootstrap.class, name)); return site; } public static CallSite contextValueString( Lookup lookup, String name, MethodType type, String str) { MutableCallSite site = new MutableCallSite(type); site.setTarget( Binder.from(type).append(site, str).invokeStaticQuiet(lookup, Bootstrap.class, name)); return site; } public static IRubyObject nil(ThreadContext context, MutableCallSite site) { MethodHandle constant = (MethodHandle) ((RubyNil) context.nil).constant(); if (constant == null) constant = (MethodHandle) OptoFactory.newConstantWrapper(IRubyObject.class, context.nil); site.setTarget(constant); return context.nil; } public static IRubyObject True(ThreadContext context, MutableCallSite site) { MethodHandle constant = (MethodHandle) context.runtime.getTrue().constant(); if (constant == null) constant = (MethodHandle) OptoFactory.newConstantWrapper(IRubyObject.class, context.runtime.getTrue()); site.setTarget(constant); return context.runtime.getTrue(); } public static IRubyObject False(ThreadContext context, MutableCallSite site) { MethodHandle constant = (MethodHandle) context.runtime.getFalse().constant(); if (constant == null) constant = (MethodHandle) OptoFactory.newConstantWrapper(IRubyObject.class, context.runtime.getFalse()); site.setTarget(constant); return context.runtime.getFalse(); } public static Ruby runtime(ThreadContext context, MutableCallSite site) { MethodHandle constant = (MethodHandle) context.runtime.constant(); if (constant == null) constant = (MethodHandle) OptoFactory.newConstantWrapper(Ruby.class, context.runtime); site.setTarget(constant); return context.runtime; } public static RubyEncoding encoding(ThreadContext context, MutableCallSite site, String name) { RubyEncoding rubyEncoding = IRRuntimeHelpers.retrieveEncoding(context, name); MethodHandle constant = (MethodHandle) rubyEncoding.constant(); if (constant == null) constant = (MethodHandle) OptoFactory.newConstantWrapper(RubyEncoding.class, rubyEncoding); site.setTarget(constant); return rubyEncoding; } public static IRubyObject hash(ThreadContext context, IRubyObject[] pairs) { Ruby runtime = context.runtime; RubyHash hash = RubyHash.newHash(runtime); for (int i = 0; i < pairs.length; ) { hash.fastASetCheckString(runtime, pairs[i++], pairs[i++]); } return hash; } public static IRubyObject kwargsHash(ThreadContext context, RubyHash hash, IRubyObject[] pairs) { return IRRuntimeHelpers.dupKwargsHashAndPopulateFromArray(context, hash, pairs); } static MethodHandle buildGenericHandle( InvokeSite site, DynamicMethod method, RubyClass dispatchClass) { SmartBinder binder; binder = SmartBinder.from(site.signature) .permute("context", "self", "arg.*", "block") .insert( 2, new String[] {"rubyClass", "name"}, new Class[] {RubyModule.class, String.class}, dispatchClass, site.name()) .insert(0, "method", DynamicMethod.class, method); if (site.arity > 3) { binder = binder.collect("args", "arg.*"); } return binder.invokeVirtualQuiet(LOOKUP, "call").handle(); } static MethodHandle buildJittedHandle(InvokeSite site, DynamicMethod method, boolean blockGiven) { MethodHandle mh = null; SmartBinder binder; CompiledIRMethod compiledIRMethod = null; if (method instanceof CompiledIRMethod) { compiledIRMethod = (CompiledIRMethod) method; } else if (method instanceof InterpretedIRMethod) { DynamicMethod actualMethod = ((InterpretedIRMethod) method).getActualMethod(); if (actualMethod instanceof CompiledIRMethod) { compiledIRMethod = (CompiledIRMethod) actualMethod; } } if (compiledIRMethod != null) { // attempt IR direct binding // TODO: this will have to expand when we start specializing arities binder = SmartBinder.from(site.signature).permute("context", "self", "arg.*", "block"); if (site.arity == -1) { // already [], nothing to do mh = (MethodHandle) compiledIRMethod.getHandle(); } else if (site.arity == 0) { MethodHandle specific; if ((specific = compiledIRMethod.getHandleFor(site.arity)) != null) { mh = specific; } else { mh = (MethodHandle) compiledIRMethod.getHandle(); binder = binder.insert(2, "args", IRubyObject.NULL_ARRAY); } } else { MethodHandle specific; if ((specific = compiledIRMethod.getHandleFor(site.arity)) != null) { mh = specific; } else { mh = (MethodHandle) compiledIRMethod.getHandle(); binder = binder.collect("args", "arg.*"); } } if (!blockGiven) { binder = binder.append("block", Block.class, Block.NULL_BLOCK); } binder = binder .insert(1, "scope", StaticScope.class, compiledIRMethod.getStaticScope()) .append("class", RubyModule.class, compiledIRMethod.getImplementationClass()); mh = binder.invoke(mh).handle(); } return mh; } static MethodHandle buildNativeHandle(InvokeSite site, DynamicMethod method, boolean blockGiven) { MethodHandle mh = null; SmartBinder binder = null; if (method.getNativeCall() != null) { int nativeArgCount = getNativeArgCount(method, method.getNativeCall()); DynamicMethod.NativeCall nc = method.getNativeCall(); if (nc.isJava()) { // not supported yet, use DynamicMethod.call } else { if (nativeArgCount >= 0) { // native methods only support arity 3 if (nativeArgCount == site.arity) { // nothing to do binder = SmartBinder.from(lookup(), site.signature); } else { // arity mismatch...leave null and use DynamicMethod.call below } } else { // varargs if (site.arity == -1) { // ok, already passing [] binder = SmartBinder.from(lookup(), site.signature); } else if (site.arity == 0) { // no args, insert dummy binder = SmartBinder.from(lookup(), site.signature) .insert(2, "args", IRubyObject.NULL_ARRAY); } else { // 1 or more args, collect into [] binder = SmartBinder.from(lookup(), site.signature).collect("args", "arg.*"); } } if (binder != null) { // clean up non-arguments, ordering, types if (!nc.hasContext()) { binder = binder.drop("context"); } if (nc.hasBlock() && !blockGiven) { binder = binder.append("block", Block.NULL_BLOCK); } else if (!nc.hasBlock() && blockGiven) { binder = binder.drop("block"); } if (nc.isStatic()) { mh = binder .permute("context", "self", "arg.*", "block") // filter caller .cast(nc.getNativeReturn(), nc.getNativeSignature()) .invokeStaticQuiet(LOOKUP, nc.getNativeTarget(), nc.getNativeName()) .handle(); } else { mh = binder .permute("self", "context", "arg.*", "block") // filter caller, move self .castArg("self", nc.getNativeTarget()) .castVirtual( nc.getNativeReturn(), nc.getNativeTarget(), nc.getNativeSignature()) .invokeVirtualQuiet(LOOKUP, nc.getNativeName()) .handle(); } } } } return mh; } public static int getNativeArgCount(DynamicMethod method, DynamicMethod.NativeCall nativeCall) { // if non-Java, must: // * exactly match arities or both are [] boxed // * 3 or fewer arguments return getArgCount(nativeCall.getNativeSignature(), nativeCall.isStatic()); } private static int getArgCount(Class[] args, boolean isStatic) { int length = args.length; boolean hasContext = false; if (isStatic) { if (args.length > 1 && args[0] == ThreadContext.class) { length--; hasContext = true; } // remove self object assert args.length >= 1; length--; if (args.length > 1 && args[args.length - 1] == Block.class) { length--; } if (length == 1) { if (hasContext && args[2] == IRubyObject[].class) { length = -1; } else if (args[1] == IRubyObject[].class) { length = -1; } } } else { if (args.length > 0 && args[0] == ThreadContext.class) { length--; hasContext = true; } if (args.length > 0 && args[args.length - 1] == Block.class) { length--; } if (length == 1) { if (hasContext && args[1] == IRubyObject[].class) { length = -1; } else if (args[0] == IRubyObject[].class) { length = -1; } } } return length; } public static IRubyObject ivarGet(VariableSite site, IRubyObject self) throws Throwable { RubyClass realClass = self.getMetaClass().getRealClass(); VariableAccessor accessor = realClass.getVariableAccessorForRead(site.name()); // produce nil if the variable has not been initialize MethodHandle nullToNil = findStatic( Helpers.class, "nullToNil", methodType(IRubyObject.class, IRubyObject.class, IRubyObject.class)); nullToNil = insertArguments(nullToNil, 1, self.getRuntime().getNil()); nullToNil = explicitCastArguments(nullToNil, methodType(IRubyObject.class, Object.class)); // get variable value and filter with nullToNil MethodHandle getValue; boolean direct = false; if (accessor instanceof FieldVariableAccessor) { direct = true; int offset = ((FieldVariableAccessor) accessor).getOffset(); Class cls = REIFIED_OBJECT_CLASSES[offset]; getValue = lookup().findGetter(cls, "var" + offset, Object.class); getValue = explicitCastArguments(getValue, methodType(Object.class, IRubyObject.class)); } else { getValue = findStatic( VariableAccessor.class, "getVariable", methodType(Object.class, RubyBasicObject.class, int.class)); getValue = explicitCastArguments(getValue, methodType(Object.class, IRubyObject.class, int.class)); getValue = insertArguments(getValue, 1, accessor.getIndex()); } getValue = filterReturnValue(getValue, nullToNil); // prepare fallback MethodHandle fallback = null; if (site.chainCount() + 1 > Options.INVOKEDYNAMIC_MAXPOLY.load()) { if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info( site.name() + "\tqet on type " + self.getMetaClass().id + " failed (polymorphic)" + extractSourceInfo(site)); fallback = findStatic( Bootstrap.class, "ivarGetFail", methodType(IRubyObject.class, VariableSite.class, IRubyObject.class)); fallback = fallback.bindTo(site); site.setTarget(fallback); return (IRubyObject) fallback.invokeWithArguments(self); } else { if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) { if (direct) { LOG.info( site.name() + "\tget field on type " + self.getMetaClass().id + " added to PIC" + extractSourceInfo(site)); } else { LOG.info( site.name() + "\tget on type " + self.getMetaClass().id + " added to PIC" + extractSourceInfo(site)); } } fallback = site.getTarget(); site.incrementChainCount(); } // prepare test MethodHandle test = findStatic( InvocationLinker.class, "testRealClass", methodType(boolean.class, int.class, IRubyObject.class)); test = insertArguments(test, 0, accessor.getClassId()); getValue = guardWithTest(test, getValue, fallback); if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info( site.name() + "\tget on class " + self.getMetaClass().id + " bound directly" + extractSourceInfo(site)); site.setTarget(getValue); return (IRubyObject) getValue.invokeExact(self); } public static IRubyObject ivarGetFail(VariableSite site, IRubyObject self) throws Throwable { return site.getVariable(self); } public static void ivarSet(VariableSite site, IRubyObject self, IRubyObject value) throws Throwable { RubyClass realClass = self.getMetaClass().getRealClass(); VariableAccessor accessor = realClass.getVariableAccessorForWrite(site.name()); // set variable value and fold by returning value MethodHandle setValue; boolean direct = false; if (accessor instanceof FieldVariableAccessor) { direct = true; int offset = ((FieldVariableAccessor) accessor).getOffset(); Class cls = REIFIED_OBJECT_CLASSES[offset]; setValue = findStatic(cls, "setVariableChecked", methodType(void.class, cls, Object.class)); setValue = explicitCastArguments( setValue, methodType(void.class, IRubyObject.class, IRubyObject.class)); } else { setValue = findStatic( accessor.getClass(), "setVariableChecked", methodType( void.class, RubyBasicObject.class, RubyClass.class, int.class, Object.class)); setValue = explicitCastArguments( setValue, methodType( void.class, IRubyObject.class, RubyClass.class, int.class, IRubyObject.class)); setValue = insertArguments(setValue, 1, realClass, accessor.getIndex()); } // prepare fallback MethodHandle fallback = null; if (site.chainCount() + 1 > Options.INVOKEDYNAMIC_MAXPOLY.load()) { if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info( site.name() + "\tset on type " + self.getMetaClass().id + " failed (polymorphic)" + extractSourceInfo(site)); fallback = findStatic( Bootstrap.class, "ivarSetFail", methodType(void.class, VariableSite.class, IRubyObject.class, IRubyObject.class)); fallback = fallback.bindTo(site); site.setTarget(fallback); fallback.invokeExact(self, value); } else { if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) { if (direct) { LOG.info( site.name() + "\tset field on type " + self.getMetaClass().id + " added to PIC" + extractSourceInfo(site)); } else { LOG.info( site.name() + "\tset on type " + self.getMetaClass().id + " added to PIC" + extractSourceInfo(site)); } } fallback = site.getTarget(); site.incrementChainCount(); } // prepare test MethodHandle test = findStatic( InvocationLinker.class, "testRealClass", methodType(boolean.class, int.class, IRubyObject.class)); test = insertArguments(test, 0, accessor.getClassId()); test = dropArguments(test, 1, IRubyObject.class); setValue = guardWithTest(test, setValue, fallback); if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info( site.name() + "\tset on class " + self.getMetaClass().id + " bound directly" + extractSourceInfo(site)); site.setTarget(setValue); setValue.invokeExact(self, value); } public static void ivarSetFail(VariableSite site, IRubyObject self, IRubyObject value) throws Throwable { site.setVariable(self, value); } private static MethodHandle findStatic(Class target, String name, MethodType type) { return findStatic(lookup(), target, name, type); } private static MethodHandle findStatic( Lookup lookup, Class target, String name, MethodType type) { try { return lookup.findStatic(target, name, type); } catch (Exception e) { throw new RuntimeException(e); } } public static boolean testType(RubyClass original, IRubyObject self) { // naive test return ((RubyBasicObject) self).getMetaClass() == original; } /////////////////////////////////////////////////////////////////////////// // constant lookup public static IRubyObject searchConst( ThreadContext context, StaticScope staticScope, MutableCallSite site, String constName, boolean noPrivateConsts) throws Throwable { // Lexical lookup Ruby runtime = context.getRuntime(); RubyModule object = runtime.getObject(); IRubyObject constant = (staticScope == null) ? object.getConstant(constName) : staticScope.getConstantInner(constName); // Inheritance lookup RubyModule module = null; if (constant == null) { // SSS FIXME: Is this null check case correct? module = staticScope == null ? object : staticScope.getModule(); constant = noPrivateConsts ? module.getConstantFromNoConstMissing(constName, false) : module.getConstantNoConstMissing(constName); } // Call const_missing or cache if (constant == null) { return module.callMethod(context, "const_missing", context.runtime.fastNewSymbol(constName)); } SwitchPoint switchPoint = (SwitchPoint) runtime.getConstantInvalidator(constName).getData(); // bind constant until invalidated MethodHandle target = Binder.from(site.type()).drop(0, 2).constant(constant); MethodHandle fallback = Binder.from(site.type()) .append(site, constName) .append(noPrivateConsts) .invokeStatic(LOOKUP, Bootstrap.class, "searchConst"); site.setTarget(switchPoint.guardWithTest(target, fallback)); return constant; } public static IRubyObject inheritanceSearchConst( ThreadContext context, IRubyObject cmVal, MutableCallSite site, String constName, boolean noPrivateConsts) throws Throwable { Ruby runtime = context.runtime; RubyModule module; if (cmVal instanceof RubyModule) { module = (RubyModule) cmVal; } else { throw runtime.newTypeError(cmVal + " is not a type/class"); } IRubyObject constant = noPrivateConsts ? module.getConstantFromNoConstMissing(constName, false) : module.getConstantNoConstMissing(constName); if (constant == null) { constant = UndefinedValue.UNDEFINED; } SwitchPoint switchPoint = (SwitchPoint) runtime.getConstantInvalidator(constName).getData(); // bind constant until invalidated MethodHandle target = Binder.from(site.type()).drop(0, 2).constant(constant); MethodHandle fallback = Binder.from(site.type()) .append(site, constName) .append(noPrivateConsts) .invokeStatic(LOOKUP, Bootstrap.class, "inheritanceSearchConst"); // test that module is same as before MethodHandle test = Binder.from(site.type().changeReturnType(boolean.class)) .drop(0, 1) .insert(1, module.id) .invokeStaticQuiet(LOOKUP, Bootstrap.class, "testArg0ModuleMatch"); target = guardWithTest(test, target, fallback); site.setTarget(switchPoint.guardWithTest(target, fallback)); return constant; } public static IRubyObject lexicalSearchConst( ThreadContext context, StaticScope scope, MutableCallSite site, String constName, boolean noPrivateConsts) throws Throwable { Ruby runtime = context.runtime; IRubyObject constant = scope.getConstantInner(constName); if (constant == null) { constant = UndefinedValue.UNDEFINED; } SwitchPoint switchPoint = (SwitchPoint) runtime.getConstantInvalidator(constName).getData(); // bind constant until invalidated MethodHandle target = Binder.from(site.type()).drop(0, 2).constant(constant); MethodHandle fallback = Binder.from(site.type()) .append(site, constName) .append(noPrivateConsts) .invokeStatic(LOOKUP, Bootstrap.class, "lexicalSearchConst"); site.setTarget(switchPoint.guardWithTest(target, fallback)); return constant; } /////////////////////////////////////////////////////////////////////////// // Fixnum binding public static IRubyObject instVarNullToNil(IRubyObject value, IRubyObject nil, String name) { if (value == null) { Ruby runtime = nil.getRuntime(); if (runtime.isVerbose()) { nil.getRuntime() .getWarnings() .warning( IRubyWarnings.ID.IVAR_NOT_INITIALIZED, "instance variable " + name + " not initialized"); } return nil; } return value; } public static boolean testArg0ModuleMatch(IRubyObject arg0, int id) { return arg0 instanceof RubyModule && ((RubyModule) arg0).id == id; } private static String extractSourceInfo(VariableSite site) { return " (" + site.file() + ":" + site.line() + ")"; } }
/** * Update the given call site using the new target, wrapping with appropriate guard and * argument-juggling logic. Return a handle suitable for invoking with the site's original method * type. */ MethodHandle updateInvocationTarget( MethodHandle target, IRubyObject self, RubyModule testClass, CacheEntry entry, SwitchPoint switchPoint) { if (target == null || clearCount > Options.INVOKEDYNAMIC_MAXFAIL.load() || (!hasSeenType(testClass.id) && seenTypesCount() + 1 > Options.INVOKEDYNAMIC_MAXPOLY.load())) { setTarget(target = prepareBinder().invokeVirtualQuiet(lookup(), "fail")); } else { MethodHandle fallback; MethodHandle gwt; // if we've cached no types, and the site is bound and we haven't seen this new type... if (seenTypesCount() > 0 && getTarget() != null && !hasSeenType(testClass.id)) { // stack it up into a PIC if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info(methodName + "\tadded to PIC " + logMethod(entry.method)); fallback = getTarget(); } else { // wipe out site with this new type and method String bind = boundOnce ? "rebind" : "bind"; if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info( methodName + "\ttriggered site #" + siteID + " " + bind); // + " (" + file() + ":" + line() + ")"); fallback = this.fallback; clearTypes(); } addType(testClass.id); SmartHandle test; SmartBinder selfTest = SmartBinder.from(signature.asFold(boolean.class)).permute("self"); if (self instanceof RubySymbol || self instanceof RubyFixnum || self instanceof RubyFloat || self instanceof RubyNil || self instanceof RubyBoolean.True || self instanceof RubyBoolean.False) { test = selfTest .insert(1, "selfJavaType", self.getClass()) .cast(boolean.class, Object.class, Class.class) .invoke(TEST_CLASS); } else { test = SmartBinder.from(signature.changeReturn(boolean.class)) .permute("self") .insert(0, "selfClass", RubyClass.class, testClass) .invokeStaticQuiet(Bootstrap.LOOKUP, Bootstrap.class, "testType"); } gwt = MethodHandles.guardWithTest(test.handle(), target, fallback); // wrap in switchpoint for mutation invalidation gwt = switchPoint.guardWithTest(gwt, fallback); setTarget(gwt); } return target; }