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; }
public GuardBuilder filter(int index, Filter filter) throws Exception { MethodHandle methodHandle = filter.methodHandle( methodType(binder.type().parameterType(index), binder.type().parameterType(index))); binder = binder.filter(index, methodHandle); return this; }
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 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 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 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 MethodHandle getTestBlockBody() { if (testBlockBody != null) return testBlockBody; return testBlockBody = Binder.from(boolean.class, ThreadContext.class, Block.class) .drop(0) .append(this) .invoke(TEST_BLOCK_BODY); }
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; }
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; }
@Override public StrategicLink linkGetMethod( StrategyChain chain, Object receiver, String methodName, Binder binder, Binder guardBinder) throws NoSuchMethodException, IllegalAccessException { StrategicLink link = super.linkGetMethod(chain, receiver, methodName, binder, guardBinder); if (link != null) { MethodHandle target = link.getTarget(); return new StrategicLink( Binder.from(target.type()).filterReturn(FILTER_HANDLE).invoke(target), link.getGuard()); } return null; }
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; }
@Override public StrategicLink linkCall( StrategyChain chain, Object receiver, Object self, Object[] args, Binder binder, Binder guardBinder) throws NoSuchMethodException, IllegalAccessException { // TODO Auto-generated method stub StrategicLink link = super.linkCall(chain, receiver, self, args, binder, guardBinder); if (link != null) { MethodHandle target = link.getTarget(); return new StrategicLink( Binder.from(target.type()).filterReturn(FILTER_HANDLE).invoke(target), link.getGuard()); } return null; }
public GuardBuilder spread(Class... spreadTypes) { binder = binder.spread(spreadTypes); return this; }
public GuardBuilder drop(int index) { binder = binder.drop(index); return this; }
/** The executable body portion of a closure. */ public abstract class BlockBody { protected final Signature signature; protected volatile MethodHandle testBlockBody; public BlockBody(Signature signature) { this.signature = signature; } public Signature getSignature() { return signature; } public void setEvalType(EvalType evalType) {} public boolean canCallDirect() { return false; } public MethodHandle getTestBlockBody() { if (testBlockBody != null) return testBlockBody; return testBlockBody = Binder.from(boolean.class, ThreadContext.class, Block.class) .drop(0) .append(this) .invoke(TEST_BLOCK_BODY); } private static final MethodHandle TEST_BLOCK_BODY = Binder.from(boolean.class, Block.class, BlockBody.class) .invokeStaticQuiet(MethodHandles.lookup(), BlockBody.class, "testBlockBody"); public static boolean testBlockBody(Block block, BlockBody body) { return block.getBody() == body; } protected IRubyObject callDirect( ThreadContext context, Block block, IRubyObject[] args, Block blockArg) { throw new RuntimeException( "callDirect not implemented in base class. We should never get here."); } protected IRubyObject yieldDirect( ThreadContext context, Block block, IRubyObject[] args, IRubyObject self) { throw new RuntimeException( "yieldDirect not implemented in base class. We should never get here."); } public IRubyObject call(ThreadContext context, Block block, IRubyObject[] args) { if (canCallDirect()) { return callDirect(context, block, args, Block.NULL_BLOCK); } else { return yield(context, block, prepareArgumentsForCall(context, args, block.type), null); } } public IRubyObject call(ThreadContext context, Block block, IRubyObject[] args, Block blockArg) { if (canCallDirect()) { return callDirect(context, block, args, blockArg); } else { return yield( context, block, prepareArgumentsForCall(context, args, block.type), null, blockArg); } } public final IRubyObject yield(ThreadContext context, Block block, IRubyObject value) { if (canCallDirect()) { return yieldDirect(context, block, new IRubyObject[] {value}, null); } else { return doYield(context, block, value); } } public final IRubyObject yield( ThreadContext context, Block block, IRubyObject[] args, IRubyObject self) { if (canCallDirect()) { return yieldDirect(context, block, args, self); } else { IRubyObject[] preppedValue = RubyProc.prepareArgs(context, block.type, this, args); return doYield(context, block, preppedValue, self); } } /** * Subclass specific yield implementation. * * <p>Should not be called directly. Gets called by {@link #yield(ThreadContext, Block, * org.jruby.runtime.builtin.IRubyObject)} after ensuring that any common yield logic is taken * care of. */ protected abstract IRubyObject doYield(ThreadContext context, Block block, IRubyObject value); /** * Subclass specific yield implementation. * * <p>Should not be called directly. Gets called by {@link #yield(ThreadContext, Block, * org.jruby.runtime.builtin.IRubyObject[], org.jruby.runtime.builtin.IRubyObject)} after ensuring * that all common yield logic is taken care of. */ protected abstract IRubyObject doYield( ThreadContext context, Block block, IRubyObject[] args, IRubyObject self); // FIXME: This should be unified with the final versions above // Here to allow incremental replacement. Overriden by subclasses which support it. public IRubyObject yield( ThreadContext context, Block block, IRubyObject[] args, IRubyObject self, Block blockArg) { return yield(context, block, args, self); } // FIXME: This should be unified with the final versions above // Here to allow incremental replacement. Overriden by subclasses which support it. public IRubyObject yield(ThreadContext context, Block block, IRubyObject value, Block blockArg) { return yield(context, block, value); } public IRubyObject call(ThreadContext context, Block block) { IRubyObject[] args = IRubyObject.NULL_ARRAY; if (canCallDirect()) { return callDirect(context, block, args, Block.NULL_BLOCK); } else { return yield(context, block, prepareArgumentsForCall(context, args, block.type), null); } } public IRubyObject call(ThreadContext context, Block block, Block unusedBlock) { return call(context, block); } public IRubyObject yieldSpecific(ThreadContext context, Block block) { if (canCallDirect()) { return yieldDirect(context, block, null, null); } else { return yield(context, block, null); } } public IRubyObject call(ThreadContext context, Block block, IRubyObject arg0) { IRubyObject[] args = new IRubyObject[] {arg0}; if (canCallDirect()) { return callDirect(context, block, args, Block.NULL_BLOCK); } else { return yield(context, block, prepareArgumentsForCall(context, args, block.type), null); } } public IRubyObject call(ThreadContext context, Block block, IRubyObject arg0, Block unusedBlock) { return call(context, block, arg0); } public IRubyObject yieldSpecific(ThreadContext context, Block block, IRubyObject arg0) { if (canCallDirect()) { return yieldDirect(context, block, new IRubyObject[] {arg0}, null); } else { return yield(context, block, arg0); } } public IRubyObject call(ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1) { IRubyObject[] args = new IRubyObject[] {arg0, arg1}; if (canCallDirect()) { return callDirect(context, block, args, Block.NULL_BLOCK); } else { return yield(context, block, prepareArgumentsForCall(context, args, block.type), null); } } public IRubyObject call( ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1, Block unusedBlock) { return call(context, block, arg0, arg1); } public IRubyObject yieldSpecific( ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1) { if (canCallDirect()) { return yieldDirect(context, block, new IRubyObject[] {arg0, arg1}, null); } else { return yield(context, block, new IRubyObject[] {arg0, arg1}, null); } } public IRubyObject call( ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { IRubyObject[] args = new IRubyObject[] {arg0, arg1, arg2}; if (canCallDirect()) { return callDirect(context, block, args, Block.NULL_BLOCK); } else { return yield(context, block, prepareArgumentsForCall(context, args, block.type), null); } } public IRubyObject call( ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block unusedBlock) { return call(context, block, arg0, arg1, arg2); } public IRubyObject yieldSpecific( ThreadContext context, Block block, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { if (canCallDirect()) { return yieldDirect(context, block, new IRubyObject[] {arg0, arg1, arg2}, null); } else { return yield(context, block, new IRubyObject[] {arg0, arg1, arg2}, null); } } public abstract StaticScope getStaticScope(); public abstract void setStaticScope(StaticScope newScope); /** * What is the arity of this block? * * @return the arity */ @Deprecated public Arity arity() { return signature.arity(); } /** * Is the current block a real yield'able block instead a null one * * @return true if this is a valid block or false otherwise */ public boolean isGiven() { return true; } /** Get the filename for this block */ public abstract String getFile(); /** get The line number for this block */ public abstract int getLine(); public IRubyObject[] prepareArgumentsForCall( ThreadContext context, IRubyObject[] args, Block.Type type) { if (type == Block.Type.LAMBDA) { signature.checkArity(context.runtime, args); } else { // SSS FIXME: How is it even possible to "call" a NORMAL block? // I thought only procs & lambdas can be called, and blocks are yielded to. if (args.length == 1) { // Convert value to arg-array, unwrapping where necessary args = IRRuntimeHelpers.convertValueIntoArgArray( context, args[0], signature, type == Block.Type.NORMAL && args[0] instanceof RubyArray); } else if (getSignature().arityValue() == 1 && !getSignature().restKwargs()) { // discard excess arguments args = args.length == 0 ? context.runtime.getSingleNilArray() : new IRubyObject[] {args[0]}; } } return args; } public ArgumentDescriptor[] getArgumentDescriptors() { return ArgumentDescriptor.EMPTY_ARRAY; } public static final BlockBody NULL_BODY = new NullBlockBody(); }
public GuardBuilder printType() { binder = binder.printType(); return this; }
public GuardBuilder permute(int... reorder) { binder = binder.permute(reorder); return this; }
public GuardBuilder fold(MethodHandle function) { binder = binder.fold(function); return this; }
public GuardBuilder convert(Class returnType, Class... argTypes) { binder = binder.convert(returnType, argTypes); return this; }
public GuardBuilder insert(int index, Object... values) { binder = binder.insert(index, values); return this; }
public GuardBuilder collect(int index, Class type) { binder = binder.collect(index, type); return this; }
public GuardBuilder varargs(int index, Class type) { binder = binder.varargs(index, type); return this; }
public GuardBuilder printType(PrintStream ps) { binder = binder.printType(ps); return this; }
/** Created by headius on 10/23/14. */ public abstract class InvokeSite extends MutableCallSite { final Signature signature; final Signature fullSignature; final int arity; protected final String methodName; final MethodHandle fallback; private final Set<Integer> seenTypes = new HashSet<Integer>(); private int clearCount; private static final AtomicLong SITE_ID = new AtomicLong(1); private final long siteID = SITE_ID.getAndIncrement(); private final int argOffset; private boolean boundOnce; CacheEntry cache = CacheEntry.NULL_CACHE; private static final Logger LOG = LoggerFactory.getLogger("InvokeSite"); public String name() { return methodName; } public final CallType callType; public InvokeSite(MethodType type, String name, CallType callType) { super(type); this.methodName = name; this.callType = callType; Signature startSig; if (callType == CallType.SUPER) { // super calls receive current class argument, so offsets and signature are different startSig = JRubyCallSite.STANDARD_SUPER_SIG; argOffset = 4; } else { startSig = JRubyCallSite.STANDARD_SITE_SIG; argOffset = 3; } int arity; if (type.parameterType(type.parameterCount() - 1) == Block.class) { arity = type.parameterCount() - (argOffset + 1); if (arity == 1 && type.parameterType(argOffset) == IRubyObject[].class) { arity = -1; startSig = startSig.appendArg("args", IRubyObject[].class); } else { for (int i = 0; i < arity; i++) { startSig = startSig.appendArg("arg" + i, IRubyObject.class); } } startSig = startSig.appendArg("block", Block.class); fullSignature = signature = startSig; } else { arity = type.parameterCount() - argOffset; if (arity == 1 && type.parameterType(argOffset) == IRubyObject[].class) { arity = -1; startSig = startSig.appendArg("args", IRubyObject[].class); } else { for (int i = 0; i < arity; i++) { startSig = startSig.appendArg("arg" + i, IRubyObject.class); } } signature = startSig; fullSignature = startSig.appendArg("block", Block.class); } this.arity = arity; this.fallback = prepareBinder().invokeVirtualQuiet(Bootstrap.LOOKUP, "invoke"); } public static CallSite bootstrap(InvokeSite site, MethodHandles.Lookup lookup) { site.setInitialTarget(site.fallback); return site; } public IRubyObject invoke( ThreadContext context, IRubyObject caller, IRubyObject self, IRubyObject[] args, Block block) throws Throwable { RubyClass selfClass = pollAndGetClass(context, self); SwitchPoint switchPoint = (SwitchPoint) selfClass.getInvalidator().getData(); CacheEntry entry = selfClass.searchWithCache(methodName); DynamicMethod method = entry.method; if (methodMissing(entry, caller)) { return callMethodMissing(entry, callType, context, self, methodName, args, block); } MethodHandle mh = getHandle(selfClass, this, method); updateInvocationTarget(mh, self, selfClass, entry, switchPoint); return method.call(context, self, selfClass, methodName, args, block); } /** Failover version uses a monomorphic cache and DynamicMethod.call, as in non-indy. */ public IRubyObject fail( ThreadContext context, IRubyObject caller, IRubyObject self, IRubyObject[] args, Block block) throws Throwable { RubyClass selfClass = pollAndGetClass(context, self); String name = methodName; CacheEntry entry = cache; if (entry.typeOk(selfClass)) { return entry.method.call(context, self, selfClass, name, args, block); } entry = selfClass.searchWithCache(name); if (methodMissing(entry, caller)) { return callMethodMissing(entry, callType, context, self, name, args, block); } cache = entry; return entry.method.call(context, self, selfClass, name, args, block); } 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(); } MethodHandle getHandle(RubyClass dispatchClass, InvokeSite site, DynamicMethod method) throws Throwable { boolean blockGiven = signature.lastArgType() == Block.class; MethodHandle mh = Bootstrap.buildNativeHandle(site, method, blockGiven); if (mh == null) mh = Bootstrap.buildIndyHandle(site, method, method.getImplementationClass()); if (mh == null) mh = Bootstrap.buildJittedHandle(site, method, blockGiven); if (mh == null) mh = Bootstrap.buildGenericHandle(site, method, dispatchClass); assert mh != null : "we should have a method handle of some sort by now"; return mh; } /** * 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; } public RubyClass pollAndGetClass(ThreadContext context, IRubyObject self) { context.callThreadPoll(); RubyClass selfType = ((RubyBasicObject) self).getMetaClass(); return selfType; } @Override public void setTarget(MethodHandle target) { super.setTarget(target); boundOnce = true; } public void setInitialTarget(MethodHandle target) { super.setTarget(target); } public synchronized boolean hasSeenType(int typeCode) { return seenTypes.contains(typeCode); } public synchronized void addType(int typeCode) { seenTypes.add(typeCode); } public synchronized int seenTypesCount() { return seenTypes.size(); } public synchronized void clearTypes() { seenTypes.clear(); clearCount++; } public abstract boolean methodMissing(CacheEntry entry, IRubyObject caller); public IRubyObject callMethodMissing( CacheEntry entry, CallType callType, ThreadContext context, IRubyObject self, String name, IRubyObject[] args, Block block) { return Helpers.selectMethodMissing(context, self, entry.method.getVisibility(), name, callType) .call(context, self, self.getMetaClass(), name, args, block); } private static String logMethod(DynamicMethod method) { return "[#" + method.getSerialNumber() + " " + method.getImplementationClass() + "]"; } @JIT public static boolean testMetaclass(RubyClass metaclass, IRubyObject self) { return metaclass == ((RubyBasicObject) self).getMetaClass(); } @JIT public static boolean testClass(Object object, Class clazz) { return object.getClass() == clazz; } private static final MethodHandle TEST_CLASS = Binder.from(boolean.class, Object.class, Class.class) .invokeStaticQuiet(lookup(), InvokeSite.class, "testClass"); }
public GuardBuilder drop(int index, int count) { binder = binder.drop(index, count); return this; }
public GuardBuilder filter(int index, MethodHandle... functions) { binder = binder.filter(index, functions); return this; }
public GuardBuilder insert(int index, int value) { binder = binder.insert(index, value); return this; }
public GuardBuilder insert(int index, Class[] types, Object... values) { binder = binder.insert(index, types, values); return this; }