package org.jruby.ir.targets;

import com.headius.invokebinder.Binder;
import org.jruby.Ruby;
import org.jruby.RubyModule;
import org.jruby.RubySymbol;
import org.jruby.ir.operands.UndefinedValue;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.cli.Options;
import org.jruby.util.log.Logger;
import org.jruby.util.log.LoggerFactory;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;

import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.lang.invoke.SwitchPoint;

import static java.lang.invoke.MethodHandles.guardWithTest;
import static org.jruby.util.CodegenUtils.p;
import static org.jruby.util.CodegenUtils.sig;

Created by headius on 1/31/16.
/** * Created by headius on 1/31/16. */
public class ConstantLookupSite extends MutableCallSite { private static final Logger LOG = LoggerFactory.getLogger(ConstantLookupSite.class); private final String name; private final boolean publicOnly; private final boolean callConstMissing; private volatile RubySymbol symbolicName; private final SiteTracker tracker = new SiteTracker(); public static final Handle BOOTSTRAP = new Handle( Opcodes.H_INVOKESTATIC, p(ConstantLookupSite.class), "constLookup", sig(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, String.class, int.class, int.class), false); public ConstantLookupSite(MethodType type, String name, boolean publicOnly, boolean callConstMissing) { super(type); this.name = name; this.publicOnly = publicOnly; this.callConstMissing = callConstMissing; } public static CallSite constLookup(MethodHandles.Lookup lookup, String searchType, MethodType type, String constName, int publicOnly, int callConstMissing) { ConstantLookupSite site = new ConstantLookupSite(type, constName, publicOnly == 0 ? false : true, callConstMissing == 0 ? false : true); MethodHandle handle = Binder .from(lookup, type) .insert(0, site) .invokeVirtualQuiet(lookup, searchType); site.setTarget(handle); return site; } private RubySymbol getSymbolicName(ThreadContext context) { RubySymbol symbolicName = this.symbolicName; if (symbolicName != null) return symbolicName; return this.symbolicName = context.runtime.fastNewSymbol(name); } public IRubyObject searchConst(ThreadContext context, StaticScope staticScope) { // Lexical lookup Ruby runtime = context.getRuntime(); RubyModule object = runtime.getObject(); IRubyObject constant = (staticScope == null) ? object.getConstant(name) : staticScope.getConstantInner(name); // Inheritance lookup RubyModule module = null; if (constant == null) { // SSS FIXME: Is this null check case correct? module = staticScope == null ? object : staticScope.getModule(); constant = publicOnly ? module.getConstantFromNoConstMissing(name, false) : module.getConstantNoConstMissing(name); } // Call const_missing or cache if (constant == null) { if (callConstMissing) { return module.callMethod(context, "const_missing", getSymbolicName(context)); } else { return UndefinedValue.UNDEFINED; } } SwitchPoint switchPoint = (SwitchPoint) runtime.getConstantInvalidator(name).getData(); // bind constant until invalidated MethodHandle target = Binder.from(type()) .drop(0, 2) .constant(constant); MethodHandle fallback = Binder.from(type()) .insert(0, this) .invokeVirtualQuiet(Bootstrap.LOOKUP, "searchConst"); setTarget(switchPoint.guardWithTest(target, fallback)); if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) { LOG.info(name + "\tretrieved and cached from scope (searchConst) " + staticScope.getIRScope()); } return constant; } public IRubyObject searchModuleForConst(ThreadContext context, IRubyObject cmVal) throws Throwable { if (!(cmVal instanceof RubyModule)) throw context.runtime.newTypeError(cmVal + " is not a type/class"); RubyModule module = (RubyModule) cmVal; if (checkForBailout(module)) { return bail(context, cmVal, noCacheSMFC()); } // Inheritance lookup Ruby runtime = context.getRuntime(); IRubyObject constant = publicOnly ? module.getConstantFromNoConstMissing(name, false) : module.getConstantNoConstMissing(name); // Call const_missing or cache if (constant == null) { if (callConstMissing) { return module.callMethod(context, "const_missing", getSymbolicName(context)); } else { return UndefinedValue.UNDEFINED; } } // bind constant until invalidated bind(runtime, module, constant, SMFC()); if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) { LOG.info(name + "\tretrieved and cached from module (searchModuleForConst) " + cmVal.getMetaClass());// + " added to PIC" + extractSourceInfo(site)); } return constant; } public IRubyObject noCacheSearchModuleForConst(ThreadContext context, IRubyObject cmVal) { if (!(cmVal instanceof RubyModule)) throw context.runtime.newTypeError(cmVal + " is not a type/class"); RubyModule module = (RubyModule) cmVal; // Inheritance lookup IRubyObject constant = publicOnly ? module.getConstantFromNoConstMissing(name, false) : module.getConstantNoConstMissing(name); // Call const_missing or cache if (constant == null) { return module.callMethod(context, "const_missing", getSymbolicName(context)); } return constant; } public IRubyObject inheritanceSearchConst(ThreadContext context, IRubyObject cmVal) 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"); } if (checkForBailout(module)) { return bail(context, cmVal, noCacheISC()); } // Inheritance lookup IRubyObject constant = module.getConstantNoConstMissingSkipAutoload(name); if (constant == null) { constant = UndefinedValue.UNDEFINED; } // bind constant until invalidated bind(runtime, module, constant, ISC()); tracker.addType(module.id); if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) { LOG.info(name + "\tconstant cached from type (inheritanceSearchConst) " + cmVal.getMetaClass()); } return constant; } public IRubyObject noCacheInheritanceSearchConst(ThreadContext context, IRubyObject cmVal) { Ruby runtime = context.runtime; RubyModule module; if (cmVal instanceof RubyModule) { module = (RubyModule) cmVal; } else { throw runtime.newTypeError(cmVal + " is not a type/class"); } // Inheritance lookup IRubyObject constant = module.getConstantNoConstMissingSkipAutoload(name); if (constant == null) { constant = UndefinedValue.UNDEFINED; } return constant; } private MethodHandle getFallback(RubyModule module, MethodHandle cachingFallback) { MethodHandle fallback;// if we've cached any types and we haven't seen this new type... if (tracker.seenTypesCount() > 0 && !tracker.hasSeenType(module.id)) { // stack it up into a PIC if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) LOG.info(name + "\tconstant added to PIC"); fallback = getTarget(); } else { // wipe out site with this new type if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) LOG.info(name + "\tconstant " + (tracker.seenTypesCount() > 0 ? "rebound" : "bound")); fallback = cachingFallback; tracker.clearTypes(); } return fallback; } private boolean checkForBailout(RubyModule module) { // Invalidated too many times if (tracker.clearCount() > Options.INVOKEDYNAMIC_MAXFAIL.load()) { if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) LOG.info(name + "\tinvalidated more than " + Options.INVOKEDYNAMIC_MAXFAIL.load() + " times "); return true; } // Too many types encountered if ((!tracker.hasSeenType(module.id) && tracker.seenTypesCount() + 1 > Options.INVOKEDYNAMIC_MAXPOLY.load())) { if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) LOG.info(name + "\tencountered more than " + Options.INVOKEDYNAMIC_MAXPOLY.load() + " types "); return true; } return false; } private IRubyObject bail(ThreadContext context, IRubyObject cmVal, MethodHandle noncachingFallback) throws Throwable { setTarget(noncachingFallback); return (IRubyObject) noncachingFallback.invokeExact(context, cmVal); } private void bind(Ruby runtime, RubyModule module, IRubyObject constant, MethodHandle cachingFallback) { MethodHandle target = Binder.from(type()) .drop(0, 2) .constant(constant); // Get appropriate fallback given state of site MethodHandle fallback = getFallback(module, cachingFallback); // Test that module is same as before target = guardWithTest(module.getIdTest(), target, fallback); // Global invalidation SwitchPoint switchPoint = (SwitchPoint) runtime.getConstantInvalidator(name).getData(); target = switchPoint.guardWithTest(target, fallback); setTarget(target); } public IRubyObject lexicalSearchConst(ThreadContext context, StaticScope scope) { Ruby runtime = context.runtime; IRubyObject constant = scope.getConstantDefined(name); if (constant == null) { constant = UndefinedValue.UNDEFINED; } SwitchPoint switchPoint = (SwitchPoint) runtime.getConstantInvalidator(name).getData(); // bind constant until invalidated MethodHandle target = Binder.from(type()) .drop(0, 2) .constant(constant); MethodHandle fallback = Binder.from(type()) .insert(0, this) .invokeVirtualQuiet(Bootstrap.LOOKUP, "lexicalSearchConst"); setTarget(switchPoint.guardWithTest(target, fallback)); if (Options.INVOKEDYNAMIC_LOG_CONSTANTS.load()) { LOG.info(name + "\tretrieved and cached from scope (lexicalSearchConst) " + scope.getIRScope());// + " added to PIC" + extractSourceInfo(site)); } return constant; } private MethodHandle _SMFC; private MethodHandle SMFC() { if (_SMFC != null) return _SMFC; return _SMFC = Binder.from(type()) .insert(0, this) .invokeVirtualQuiet(Bootstrap.LOOKUP, "searchModuleForConst"); } private MethodHandle _noCacheSMFC; private MethodHandle noCacheSMFC() { if (_noCacheSMFC != null) return _noCacheSMFC; return _noCacheSMFC = Binder.from(type()) .insert(0, this) .invokeVirtualQuiet(Bootstrap.LOOKUP, "noCacheSearchModuleForConst"); } private MethodHandle _ISC; private MethodHandle ISC() { if (_ISC != null) return _ISC; return _ISC = Binder.from(type()) .insert(0, this) .invokeVirtualQuiet(Bootstrap.LOOKUP, "inheritanceSearchConst"); } private MethodHandle _noCacheISC; private MethodHandle noCacheISC() { if (_noCacheISC != null) return _noCacheISC; return _noCacheISC = Binder.from(type()) .insert(0, this) .invokeVirtualQuiet(Bootstrap.LOOKUP, "noCacheInheritanceSearchConst"); } }