Ejemplo n.º 1
0
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() + ")";
  }
}
Ejemplo n.º 2
0
/** This file implements a seekable IO file. */
public class ChannelStream implements Stream, Finalizable {

  private static final Logger LOG = LoggerFactory.getLogger("ChannelStream");

  private static final boolean DEBUG = false;

  /**
   * The size of the read/write buffer allocated for this stream.
   *
   * <p>This size has been scaled back from its original 16k because although the larger buffer size
   * results in raw File.open times being rather slow (due to the cost of instantiating a relatively
   * large buffer). We should try to find a happy medium, or potentially pool buffers, or perhaps
   * even choose a value based on platform(??), but for now I am reducing it along with changes for
   * the "large read" patch from JRUBY-2657.
   */
  public static final int BUFSIZE = 4 * 1024;

  /**
   * The size at which a single read should turn into a chunkier bulk read. Currently, this size is
   * about 4x a normal buffer size.
   *
   * <p>This size was not really arrived at experimentally, and could potentially be increased.
   * However, it seems like a "good size" and we should probably only adjust it if it turns out we
   * would perform better with a larger buffer for large bulk reads.
   */
  private static final int BULK_READ_SIZE = 16 * 1024;

  private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);

  /**
   * A cached EOFException. Since EOFException is only used by us internally, we create a single
   * instance to avoid stack trace generation. Comment out the initialization of this field to cause
   * a new one each time.
   */
  private static EOFException eofException = new EOFException();

  private volatile Ruby runtime;
  protected ModeFlags modes;
  protected boolean sync = false;

  protected volatile ByteBuffer buffer; // r/w buffer
  protected boolean reading; // are we reading or writing?
  private ChannelDescriptor descriptor;
  private boolean blocking = true;
  protected int ungotc = -1;
  private volatile boolean closedExplicitly = false;

  private volatile boolean eof = false;
  private volatile boolean autoclose = true;

  private ChannelStream(Ruby runtime, ChannelDescriptor descriptor, boolean autoclose) {
    this.runtime = runtime;
    this.descriptor = descriptor;
    this.modes = descriptor.getOriginalModes();
    buffer = ByteBuffer.allocate(BUFSIZE);
    buffer.flip();
    this.reading = true;
    this.autoclose = autoclose;
    runtime.addInternalFinalizer(this);
  }

  private ChannelStream(
      Ruby runtime, ChannelDescriptor descriptor, ModeFlags modes, boolean autoclose) {
    this(runtime, descriptor, autoclose);
    this.modes = modes;
  }

  public Ruby getRuntime() {
    return runtime;
  }

  public void checkReadable() throws IOException {
    if (!modes.isReadable()) throw new IOException("not opened for reading");
  }

  public void checkWritable() throws IOException {
    if (!modes.isWritable()) throw new IOException("not opened for writing");
  }

  public void checkPermissionsSubsetOf(ModeFlags subsetModes) {
    subsetModes.isSubsetOf(modes);
  }

  public ModeFlags getModes() {
    return modes;
  }

  public boolean isSync() {
    return sync;
  }

  public void setSync(boolean sync) {
    this.sync = sync;
  }

  public void setBinmode() {
    // No-op here, no binmode handling needed.
  }

  public boolean isBinmode() {
    return false;
  }

  public boolean isAutoclose() {
    return autoclose;
  }

  public void setAutoclose(boolean autoclose) {
    this.autoclose = autoclose;
  }

  /**
   * Implement IO#wait as per io/wait in MRI. waits until input available or timed out and returns
   * self, or nil when EOF reached.
   *
   * <p>The default implementation loops while ready returns 0.
   */
  public void waitUntilReady() throws IOException, InterruptedException {
    while (ready() == 0) {
      Thread.sleep(10);
    }
  }

  public boolean readDataBuffered() {
    return reading && (ungotc != -1 || buffer.hasRemaining());
  }

  public boolean writeDataBuffered() {
    return !reading && buffer.position() > 0;
  }

  private final int refillBuffer() throws IOException {
    buffer.clear();
    int n = ((ReadableByteChannel) descriptor.getChannel()).read(buffer);
    buffer.flip();
    return n;
  }

  public synchronized ByteList fgets(ByteList separatorString)
      throws IOException, BadDescriptorException {
    checkReadable();
    ensureRead();

    if (separatorString == null) {
      return readall();
    }

    final ByteList separator =
        (separatorString == PARAGRAPH_DELIMETER) ? PARAGRAPH_SEPARATOR : separatorString;

    descriptor.checkOpen();

    if (feof()) {
      return null;
    }

    int c = read();

    if (c == -1) {
      return null;
    }

    // unread back
    buffer.position(buffer.position() - 1);

    ByteList buf = new ByteList(40);

    byte first = separator.getUnsafeBytes()[separator.getBegin()];

    LineLoop:
    while (true) {
      ReadLoop:
      while (true) {
        byte[] bytes = buffer.array();
        int offset = buffer.position();
        int max = buffer.limit();

        // iterate over remainder of buffer until we find a match
        for (int i = offset; i < max; i++) {
          c = bytes[i];
          if (c == first) {
            // terminate and advance buffer when we find our char
            buf.append(bytes, offset, i - offset);
            if (i >= max) {
              buffer.clear();
            } else {
              buffer.position(i + 1);
            }
            break ReadLoop;
          }
        }

        // no match, append remainder of buffer and continue with next block
        buf.append(bytes, offset, buffer.remaining());
        int read = refillBuffer();
        if (read == -1) break LineLoop;
      }

      // found a match above, check if remaining separator characters match, appending as we go
      for (int i = 0; i < separator.getRealSize(); i++) {
        if (c == -1) {
          break LineLoop;
        } else if (c != separator.getUnsafeBytes()[separator.getBegin() + i]) {
          buf.append(c);
          continue LineLoop;
        }
        buf.append(c);
        if (i < separator.getRealSize() - 1) {
          c = read();
        }
      }
      break;
    }

    if (separatorString == PARAGRAPH_DELIMETER) {
      while (c == separator.getUnsafeBytes()[separator.getBegin()]) {
        c = read();
      }
      ungetc(c);
    }

    return buf;
  }

  public synchronized int getline(ByteList dst, byte terminator)
      throws IOException, BadDescriptorException {
    checkReadable();
    ensureRead();
    descriptor.checkOpen();

    int totalRead = 0;
    boolean found = false;
    if (ungotc != -1) {
      dst.append((byte) ungotc);
      found = ungotc == terminator;
      ungotc = -1;
      ++totalRead;
    }
    while (!found) {
      final byte[] bytes = buffer.array();
      final int begin = buffer.arrayOffset() + buffer.position();
      final int end = begin + buffer.remaining();
      int len = 0;
      for (int i = begin; i < end && !found; ++i) {
        found = bytes[i] == terminator;
        ++len;
      }
      if (len > 0) {
        dst.append(buffer, len);
        totalRead += len;
      }
      if (!found) {
        int n = refillBuffer();
        if (n <= 0) {
          if (n < 0 && totalRead < 1) {
            return -1;
          }
          break;
        }
      }
    }
    return totalRead;
  }

  public synchronized int getline(ByteList dst, byte terminator, long limit)
      throws IOException, BadDescriptorException {
    checkReadable();
    ensureRead();
    descriptor.checkOpen();

    int totalRead = 0;
    boolean found = false;
    if (ungotc != -1) {
      dst.append((byte) ungotc);
      found = ungotc == terminator;
      ungotc = -1;
      limit--;
      ++totalRead;
    }
    while (!found) {
      final byte[] bytes = buffer.array();
      final int begin = buffer.arrayOffset() + buffer.position();
      final int end = begin + buffer.remaining();
      int len = 0;
      for (int i = begin; i < end && limit-- > 0 && !found; ++i) {
        found = bytes[i] == terminator;
        ++len;
      }
      if (limit < 1) found = true;

      if (len > 0) {
        dst.append(buffer, len);
        totalRead += len;
      }
      if (!found) {
        int n = refillBuffer();
        if (n <= 0) {
          if (n < 0 && totalRead < 1) {
            return -1;
          }
          break;
        }
      }
    }
    return totalRead;
  }

  /**
   * @deprecated readall do busy loop for the IO which has NONBLOCK bit. You should implement the
   *     logic by yourself with fread().
   */
  @Deprecated
  public synchronized ByteList readall() throws IOException, BadDescriptorException {
    final long fileSize =
        descriptor.isSeekable() && descriptor.getChannel() instanceof FileChannel
            ? ((FileChannel) descriptor.getChannel()).size()
            : 0;
    //
    // Check file size - special files in /proc have zero size and need to be
    // handled by the generic read path.
    //
    if (fileSize > 0) {
      ensureRead();

      FileChannel channel = (FileChannel) descriptor.getChannel();
      final long left = fileSize - channel.position() + bufferedInputBytesRemaining();
      if (left <= 0) {
        eof = true;
        return null;
      }

      if (left > Integer.MAX_VALUE) {
        if (getRuntime() != null) {
          throw getRuntime().newIOError("File too large");
        } else {
          throw new IOException("File too large");
        }
      }

      ByteList result = new ByteList((int) left);
      ByteBuffer buf = ByteBuffer.wrap(result.getUnsafeBytes(), result.begin(), (int) left);

      //
      // Copy any buffered data (including ungetc byte)
      //
      copyBufferedBytes(buf);

      //
      // Now read unbuffered directly from the file
      //
      while (buf.hasRemaining()) {
        final int MAX_READ_CHUNK = 1 * 1024 * 1024;
        //
        // When reading into a heap buffer, the jvm allocates a temporary
        // direct ByteBuffer of the requested size.  To avoid allocating
        // a huge direct buffer when doing ludicrous reads (e.g. 1G or more)
        // we split the read up into chunks of no more than 1M
        //
        ByteBuffer tmp = buf.duplicate();
        if (tmp.remaining() > MAX_READ_CHUNK) {
          tmp.limit(tmp.position() + MAX_READ_CHUNK);
        }
        int n = channel.read(tmp);
        if (n <= 0) {
          break;
        }
        buf.position(tmp.position());
      }
      eof = true;
      result.length(buf.position());
      return result;
    } else if (descriptor.isNull()) {
      return new ByteList(0);
    } else {
      checkReadable();

      ByteList byteList = new ByteList();
      ByteList read = fread(BUFSIZE);

      if (read == null) {
        eof = true;
        return byteList;
      }

      while (read != null) {
        byteList.append(read);
        read = fread(BUFSIZE);
      }

      return byteList;
    }
  }

  /**
   * Copies bytes from the channel buffer into a destination <tt>ByteBuffer</tt>
   *
   * @param dst A <tt>ByteBuffer</tt> to place the data in.
   * @return The number of bytes copied.
   */
  private final int copyBufferedBytes(ByteBuffer dst) {
    final int bytesToCopy = dst.remaining();

    if (ungotc != -1 && dst.hasRemaining()) {
      dst.put((byte) ungotc);
      ungotc = -1;
    }

    if (buffer.hasRemaining() && dst.hasRemaining()) {

      if (dst.remaining() >= buffer.remaining()) {
        //
        // Copy out any buffered bytes
        //
        dst.put(buffer);

      } else {
        //
        // Need to clamp source (buffer) size to avoid overrun
        //
        ByteBuffer tmp = buffer.duplicate();
        tmp.limit(tmp.position() + dst.remaining());
        dst.put(tmp);
        buffer.position(tmp.position());
      }
    }

    return bytesToCopy - dst.remaining();
  }

  /**
   * Copies bytes from the channel buffer into a destination <tt>ByteBuffer</tt>
   *
   * @param dst A <tt>ByteBuffer</tt> to place the data in.
   * @return The number of bytes copied.
   */
  private final int copyBufferedBytes(byte[] dst, int off, int len) {
    int bytesCopied = 0;

    if (ungotc != -1 && len > 0) {
      dst[off++] = (byte) ungotc;
      ungotc = -1;
      ++bytesCopied;
    }

    final int n = Math.min(len - bytesCopied, buffer.remaining());
    buffer.get(dst, off, n);
    bytesCopied += n;

    return bytesCopied;
  }

  /**
   * Copies bytes from the channel buffer into a destination <tt>ByteBuffer</tt>
   *
   * @param dst A <tt>ByteList</tt> to place the data in.
   * @param len The maximum number of bytes to copy.
   * @return The number of bytes copied.
   */
  private final int copyBufferedBytes(ByteList dst, int len) {
    int bytesCopied = 0;

    dst.ensure(Math.min(len, bufferedInputBytesRemaining()));

    if (bytesCopied < len && ungotc != -1) {
      ++bytesCopied;
      dst.append((byte) ungotc);
      ungotc = -1;
    }

    //
    // Copy out any buffered bytes
    //
    if (bytesCopied < len && buffer.hasRemaining()) {
      int n = Math.min(buffer.remaining(), len - bytesCopied);
      dst.append(buffer, n);
      bytesCopied += n;
    }

    return bytesCopied;
  }

  /**
   * Returns a count of how many bytes are available in the read buffer
   *
   * @return The number of bytes that can be read without reading the underlying stream.
   */
  private final int bufferedInputBytesRemaining() {
    return reading ? (buffer.remaining() + (ungotc != -1 ? 1 : 0)) : 0;
  }

  /**
   * Tests if there are bytes remaining in the read buffer.
   *
   * @return <tt>true</tt> if there are bytes available in the read buffer.
   */
  private final boolean hasBufferedInputBytes() {
    return reading && (buffer.hasRemaining() || ungotc != -1);
  }

  /**
   * Returns a count of how many bytes of space is available in the write buffer.
   *
   * @return The number of bytes that can be written to the buffer without flushing to the
   *     underlying stream.
   */
  private final int bufferedOutputSpaceRemaining() {
    return !reading ? buffer.remaining() : 0;
  }

  /**
   * Tests if there is space available in the write buffer.
   *
   * @return <tt>true</tt> if there are bytes available in the write buffer.
   */
  private final boolean hasBufferedOutputSpace() {
    return !reading && buffer.hasRemaining();
  }

  /**
   * Closes IO handler resources.
   *
   * @throws IOException
   * @throws BadDescriptorException
   */
  public void fclose() throws IOException, BadDescriptorException {
    try {
      synchronized (this) {
        closedExplicitly = true;
        close(); // not closing from finalize
      }
    } finally {
      Ruby localRuntime = getRuntime();

      // Make sure we remove finalizers while not holding self lock,
      // otherwise there is a possibility for a deadlock!
      if (localRuntime != null) localRuntime.removeInternalFinalizer(this);

      // clear runtime so it doesn't get stuck in memory (JRUBY-2933)
      runtime = null;
    }
  }

  /**
   * Internal close.
   *
   * @throws IOException
   * @throws BadDescriptorException
   */
  private void close() throws IOException, BadDescriptorException {
    // finish and close ourselves
    finish(true);
  }

  private void finish(boolean close) throws BadDescriptorException, IOException {
    try {
      flushWrite();

      if (DEBUG) LOG.info("Descriptor for fileno {} closed by stream", descriptor.getFileno());
    } finally {
      buffer = EMPTY_BUFFER;

      // clear runtime so it doesn't get stuck in memory (JRUBY-2933)
      runtime = null;

      // finish descriptor
      descriptor.finish(close);
    }
  }

  /**
   * @throws IOException
   * @throws BadDescriptorException
   */
  public synchronized int fflush() throws IOException, BadDescriptorException {
    checkWritable();
    try {
      flushWrite();
    } catch (EOFException eofe) {
      return -1;
    }
    return 0;
  }

  /**
   * Flush the write buffer to the channel (if needed)
   *
   * @throws IOException
   */
  private void flushWrite() throws IOException, BadDescriptorException {
    if (reading || !modes.isWritable() || buffer.position() == 0) return; // Don't bother

    int len = buffer.position();
    buffer.flip();
    int n = descriptor.write(buffer);

    if (n != len) {
      // TODO: check the return value here
    }
    buffer.clear();
  }

  /**
   * Flush the write buffer to the channel (if needed)
   *
   * @throws IOException
   */
  private boolean flushWrite(final boolean block) throws IOException, BadDescriptorException {
    if (reading || !modes.isWritable() || buffer.position() == 0) return false; // Don't bother
    int len = buffer.position();
    int nWritten = 0;
    buffer.flip();

    // For Sockets, only write as much as will fit.
    if (descriptor.getChannel() instanceof SelectableChannel) {
      SelectableChannel selectableChannel = (SelectableChannel) descriptor.getChannel();
      synchronized (selectableChannel.blockingLock()) {
        boolean oldBlocking = selectableChannel.isBlocking();
        try {
          if (oldBlocking != block) {
            selectableChannel.configureBlocking(block);
          }
          nWritten = descriptor.write(buffer);
        } finally {
          if (oldBlocking != block) {
            selectableChannel.configureBlocking(oldBlocking);
          }
        }
      }
    } else {
      nWritten = descriptor.write(buffer);
    }
    if (nWritten != len) {
      buffer.compact();
      return false;
    }
    buffer.clear();
    return true;
  }

  public InputStream newInputStream() {
    InputStream in = descriptor.getBaseInputStream();
    return in == null ? new InputStreamAdapter(this) : in;
  }

  public OutputStream newOutputStream() {
    return new OutputStreamAdapter(this);
  }

  public void clearerr() {
    eof = false;
  }

  /**
   * @throws IOException
   * @throws BadDescriptorException
   */
  public boolean feof() throws IOException, BadDescriptorException {
    checkReadable();

    if (eof) {
      return true;
    } else {
      return false;
    }
  }

  /** @throws IOException */
  public synchronized long fgetpos()
      throws IOException, PipeException, InvalidValueException, BadDescriptorException {
    // Correct position for read / write buffering (we could invalidate, but expensive)
    if (descriptor.isSeekable()) {
      FileChannel fileChannel = (FileChannel) descriptor.getChannel();
      long pos = fileChannel.position();
      // Adjust for buffered data
      if (reading) {
        pos -= buffer.remaining();
        return pos - (pos > 0 && ungotc != -1 ? 1 : 0);
      } else {
        return pos + buffer.position();
      }
    } else if (descriptor.isNull()) {
      return 0;
    } else {
      throw new PipeException();
    }
  }

  /**
   * Implementation of libc "lseek", which seeks on seekable streams, raises EPIPE if the fd is
   * assocated with a pipe, socket, or FIFO, and doesn't do anything for other cases (like stdio).
   *
   * @throws IOException
   * @throws InvalidValueException
   */
  public synchronized void lseek(long offset, int type)
      throws IOException, InvalidValueException, PipeException, BadDescriptorException {
    if (descriptor.isSeekable()) {
      FileChannel fileChannel = (FileChannel) descriptor.getChannel();
      ungotc = -1;
      int adj = 0;
      if (reading) {
        // for SEEK_CUR, need to adjust for buffered data
        adj = buffer.remaining();
        buffer.clear();
        buffer.flip();
      } else {
        flushWrite();
      }
      try {
        switch (type) {
          case SEEK_SET:
            fileChannel.position(offset);
            break;
          case SEEK_CUR:
            fileChannel.position(fileChannel.position() - adj + offset);
            break;
          case SEEK_END:
            fileChannel.position(fileChannel.size() + offset);
            break;
        }
      } catch (IllegalArgumentException e) {
        throw new InvalidValueException();
      } catch (IOException ioe) {
        throw ioe;
      }
    } else if (descriptor.getChannel() instanceof SelectableChannel) {
      // TODO: It's perhaps just a coincidence that all the channels for
      // which we should raise are instanceof SelectableChannel, since
      // stdio is not...so this bothers me slightly. -CON
      throw new PipeException();
    } else {
    }
  }

  public synchronized void sync() throws IOException, BadDescriptorException {
    flushWrite();
  }

  /**
   * Ensure buffer is ready for reading, flushing remaining writes if required
   *
   * @throws IOException
   */
  private void ensureRead() throws IOException, BadDescriptorException {
    if (reading) return;
    flushWrite();
    buffer.clear();
    buffer.flip();
    reading = true;
  }

  /**
   * Ensure buffer is ready for reading, flushing remaining writes if required
   *
   * @throws IOException
   */
  private void ensureReadNonBuffered() throws IOException, BadDescriptorException {
    if (reading) {
      if (buffer.hasRemaining()) {
        Ruby localRuntime = getRuntime();
        if (localRuntime != null) {
          throw localRuntime.newIOError("sysread for buffered IO");
        } else {
          throw new IOException("sysread for buffered IO");
        }
      }
    } else {
      // libc flushes writes on any read from the actual file, so we flush here
      flushWrite();
      buffer.clear();
      buffer.flip();
      reading = true;
    }
  }

  private void resetForWrite() throws IOException {
    if (descriptor.isSeekable()) {
      FileChannel fileChannel = (FileChannel) descriptor.getChannel();
      if (buffer.hasRemaining()) { // we have read ahead, and need to back up
        fileChannel.position(fileChannel.position() - buffer.remaining());
      }
    }
    // FIXME: Clearing read buffer here...is this appropriate?
    buffer.clear();
    reading = false;
  }

  /**
   * Ensure buffer is ready for writing.
   *
   * @throws IOException
   */
  private void ensureWrite() throws IOException {
    if (!reading) return;
    resetForWrite();
  }

  public synchronized ByteList read(int number) throws IOException, BadDescriptorException {
    checkReadable();
    ensureReadNonBuffered();

    ByteList byteList = new ByteList(number);

    // TODO this should entry into error handling somewhere
    int bytesRead = descriptor.read(number, byteList);

    if (bytesRead == -1) {
      eof = true;
    }

    return byteList;
  }

  private ByteList bufferedRead(int number) throws IOException, BadDescriptorException {
    checkReadable();
    ensureRead();

    int resultSize = 0;

    // 128K seems to be the minimum at which the stat+seek is faster than reallocation
    final int BULK_THRESHOLD = 128 * 1024;
    if (number >= BULK_THRESHOLD
        && descriptor.isSeekable()
        && descriptor.getChannel() instanceof FileChannel) {
      //
      // If it is a file channel, then we can pre-allocate the output buffer
      // to the total size of buffered + remaining bytes in file
      //
      FileChannel fileChannel = (FileChannel) descriptor.getChannel();
      resultSize =
          (int)
              Math.min(
                  fileChannel.size() - fileChannel.position() + bufferedInputBytesRemaining(),
                  number);
    } else {
      //
      // Cannot discern the total read length - allocate at least enough for the buffered data
      //
      resultSize = Math.min(bufferedInputBytesRemaining(), number);
    }

    ByteList result = new ByteList(resultSize);
    bufferedRead(result, number);
    return result;
  }

  private int bufferedRead(ByteList dst, int number) throws IOException, BadDescriptorException {

    int bytesRead = 0;

    //
    // Copy what is in the buffer, if there is some buffered data
    //
    bytesRead += copyBufferedBytes(dst, number);

    boolean done = false;
    //
    // Avoid double-copying for reads that are larger than the buffer size
    //
    while ((number - bytesRead) >= BUFSIZE) {
      //
      // limit each iteration to a max of BULK_READ_SIZE to avoid over-size allocations
      //
      final int bytesToRead = Math.min(BULK_READ_SIZE, number - bytesRead);
      final int n = descriptor.read(bytesToRead, dst);
      if (n == -1) {
        eof = true;
        done = true;
        break;
      } else if (n == 0) {
        done = true;
        break;
      }
      bytesRead += n;
    }

    //
    // Complete the request by filling the read buffer first
    //
    while (!done && bytesRead < number) {
      int read = refillBuffer();

      if (read == -1) {
        eof = true;
        break;
      } else if (read == 0) {
        break;
      }

      // append what we read into our buffer and allow the loop to continue
      final int len = Math.min(buffer.remaining(), number - bytesRead);
      dst.append(buffer, len);
      bytesRead += len;
    }

    if (bytesRead == 0 && number != 0) {
      if (eof) {
        throw newEOFException();
      }
    }

    return bytesRead;
  }

  private EOFException newEOFException() {
    if (eofException != null) {
      return eofException;
    } else {
      return new EOFException();
    }
  }

  private int bufferedRead(ByteBuffer dst, boolean partial)
      throws IOException, BadDescriptorException {
    checkReadable();
    ensureRead();

    boolean done = false;
    int bytesRead = 0;

    //
    // Copy what is in the buffer, if there is some buffered data
    //
    bytesRead += copyBufferedBytes(dst);

    //
    // Avoid double-copying for reads that are larger than the buffer size, or
    // the destination is a direct buffer.
    //
    while ((bytesRead < 1 || !partial) && (dst.remaining() >= BUFSIZE || dst.isDirect())) {
      ByteBuffer tmpDst = dst;
      if (!dst.isDirect()) {
        //
        // We limit reads to BULK_READ_SIZED chunks to avoid NIO allocating
        // a huge temporary native buffer, when doing reads into a heap buffer
        // If the dst buffer is direct, then no need to limit.
        //
        int bytesToRead = Math.min(BULK_READ_SIZE, dst.remaining());
        if (bytesToRead < dst.remaining()) {
          tmpDst = dst.duplicate();
          tmpDst.limit(tmpDst.position() + bytesToRead);
        }
      }
      int n = descriptor.read(tmpDst);
      if (n == -1) {
        eof = true;
        done = true;
        break;
      } else if (n == 0) {
        done = true;
        break;
      } else {
        bytesRead += n;
      }
    }

    //
    // Complete the request by filling the read buffer first
    //
    while (!done && dst.hasRemaining() && (bytesRead < 1 || !partial)) {
      int read = refillBuffer();

      if (read == -1) {
        eof = true;
        done = true;
        break;
      } else if (read == 0) {
        done = true;
        break;
      } else {
        // append what we read into our buffer and allow the loop to continue
        bytesRead += copyBufferedBytes(dst);
      }
    }

    if (eof && bytesRead == 0 && dst.remaining() != 0) {
      throw newEOFException();
    }

    return bytesRead;
  }

  private int bufferedRead() throws IOException, BadDescriptorException {
    ensureRead();

    if (!buffer.hasRemaining()) {
      int len = refillBuffer();
      if (len == -1) {
        eof = true;
        return -1;
      } else if (len == 0) {
        return -1;
      }
    }
    return buffer.get() & 0xFF;
  }

  /**
   * @throws IOException
   * @throws BadDescriptorException
   */
  private int bufferedWrite(ByteList buf) throws IOException, BadDescriptorException {
    checkWritable();
    ensureWrite();

    // Ruby ignores empty syswrites
    if (buf == null || buf.length() == 0) return 0;

    if (buf.length() > buffer.capacity()) { // Doesn't fit in buffer. Write immediately.
      flushWrite(); // ensure nothing left to write

      int n = descriptor.write(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length()));
      if (n != buf.length()) {
        // TODO: check the return value here
      }
    } else {
      if (buf.length() > buffer.remaining()) flushWrite();

      buffer.put(buf.getUnsafeBytes(), buf.begin(), buf.length());
    }

    if (isSync()) flushWrite();

    return buf.getRealSize();
  }

  /**
   * @throws IOException
   * @throws BadDescriptorException
   */
  private int bufferedWrite(ByteBuffer buf) throws IOException, BadDescriptorException {
    checkWritable();
    ensureWrite();

    // Ruby ignores empty syswrites
    if (buf == null || !buf.hasRemaining()) return 0;

    final int nbytes = buf.remaining();
    if (nbytes >= buffer.capacity()) { // Doesn't fit in buffer. Write immediately.
      flushWrite(); // ensure nothing left to write

      descriptor.write(buf);
      // TODO: check the return value here
    } else {
      if (nbytes > buffer.remaining()) flushWrite();

      buffer.put(buf);
    }

    if (isSync()) flushWrite();

    return nbytes - buf.remaining();
  }

  /**
   * @throws IOException
   * @throws BadDescriptorException
   */
  private int bufferedWrite(int c) throws IOException, BadDescriptorException {
    checkWritable();
    ensureWrite();

    if (!buffer.hasRemaining()) flushWrite();

    buffer.put((byte) c);

    if (isSync()) flushWrite();

    return 1;
  }

  public synchronized void ftruncate(long newLength)
      throws IOException, BadDescriptorException, InvalidValueException {
    Channel ch = descriptor.getChannel();
    if (!(ch instanceof FileChannel)) {
      throw new InvalidValueException();
    }
    invalidateBuffer();
    FileChannel fileChannel = (FileChannel) ch;
    long position = fileChannel.position();
    if (newLength > fileChannel.size()) {
      // truncate can't lengthen files, so we save position, seek/write, and go back
      int difference = (int) (newLength - fileChannel.size());

      fileChannel.position(fileChannel.size());
      // FIXME: This worries me a bit, since it could allocate a lot with a large newLength
      fileChannel.write(ByteBuffer.allocate(difference));
    } else {
      fileChannel.truncate(newLength);
    }
    fileChannel.position(position);
  }

  /**
   * Invalidate buffer before a position change has occurred (e.g. seek), flushing writes if
   * required, and correcting file position if reading
   *
   * @throws IOException
   */
  private void invalidateBuffer() throws IOException, BadDescriptorException {
    if (!reading) flushWrite();
    int posOverrun = buffer.remaining(); // how far ahead we are when reading
    buffer.clear();
    if (reading) {
      buffer.flip();
      // if the read buffer is ahead, back up
      FileChannel fileChannel = (FileChannel) descriptor.getChannel();
      if (posOverrun != 0) fileChannel.position(fileChannel.position() - posOverrun);
    }
  }

  /** Ensure close (especially flush) when we're finished with. */
  @Override
  public void finalize() throws Throwable {
    super.finalize();

    if (closedExplicitly) return;

    if (DEBUG) {
      LOG.info("finalize() for not explicitly closed stream");
    }

    // FIXME: I got a bunch of NPEs when I didn't check for nulls here...HOW?!
    if (descriptor != null && descriptor.isOpen()) {
      // tidy up
      finish(autoclose);
    }
  }

  public int ready() throws IOException {
    if (descriptor.getChannel() instanceof SelectableChannel) {
      int ready_stat = 0;
      java.nio.channels.Selector sel =
          SelectorFactory.openWithRetryFrom(
              null, ((SelectableChannel) descriptor.getChannel()).provider());
      SelectableChannel selchan = (SelectableChannel) descriptor.getChannel();
      synchronized (selchan.blockingLock()) {
        boolean is_block = selchan.isBlocking();
        try {
          selchan.configureBlocking(false);
          selchan.register(sel, java.nio.channels.SelectionKey.OP_READ);
          ready_stat = sel.selectNow();
          sel.close();
        } catch (Throwable ex) {
        } finally {
          if (sel != null) {
            try {
              sel.close();
            } catch (Exception e) {
            }
          }
          selchan.configureBlocking(is_block);
        }
      }
      return ready_stat;
    } else {
      return newInputStream().available();
    }
  }

  public synchronized void fputc(int c) throws IOException, BadDescriptorException {
    bufferedWrite(c);
  }

  public int ungetc(int c) {
    if (c == -1) {
      return -1;
    }

    // putting a bit back, so we're not at EOF anymore
    eof = false;

    // save the ungot
    ungotc = c;

    return c;
  }

  public synchronized int fgetc() throws IOException, BadDescriptorException {
    if (eof) {
      return -1;
    }

    checkReadable();

    int c = read();

    if (c == -1) {
      eof = true;
      return c;
    }

    return c & 0xff;
  }

  public synchronized int fwrite(ByteList string) throws IOException, BadDescriptorException {
    return bufferedWrite(string);
  }

  public synchronized int write(ByteBuffer buf) throws IOException, BadDescriptorException {
    return bufferedWrite(buf);
  }

  public synchronized int writenonblock(ByteList buf) throws IOException, BadDescriptorException {
    checkWritable();
    ensureWrite();

    // Ruby ignores empty syswrites
    if (buf == null || buf.length() == 0) return 0;

    if (buffer.position() != 0 && !flushWrite(false)) return 0;

    if (descriptor.getChannel() instanceof SelectableChannel) {
      SelectableChannel selectableChannel = (SelectableChannel) descriptor.getChannel();
      synchronized (selectableChannel.blockingLock()) {
        boolean oldBlocking = selectableChannel.isBlocking();
        try {
          if (oldBlocking) {
            selectableChannel.configureBlocking(false);
          }
          return descriptor.write(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length()));
        } finally {
          if (oldBlocking) {
            selectableChannel.configureBlocking(oldBlocking);
          }
        }
      }
    } else {
      // can't set nonblocking, so go ahead with it...not much else we can do
      return descriptor.write(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length()));
    }
  }

  public synchronized ByteList fread(int number) throws IOException, BadDescriptorException {
    try {
      if (number == 0) {
        if (eof) {
          return null;
        } else {
          return new ByteList(0);
        }
      }

      return bufferedRead(number);
    } catch (EOFException e) {
      eof = true;
      return null;
    }
  }

  public synchronized ByteList readnonblock(int number)
      throws IOException, BadDescriptorException, EOFException {
    assert number >= 0;

    if (number == 0) {
      return null;
    }

    if (descriptor.getChannel() instanceof SelectableChannel) {
      SelectableChannel selectableChannel = (SelectableChannel) descriptor.getChannel();
      synchronized (selectableChannel.blockingLock()) {
        boolean oldBlocking = selectableChannel.isBlocking();
        try {
          selectableChannel.configureBlocking(false);
          return readpartial(number);
        } finally {
          selectableChannel.configureBlocking(oldBlocking);
        }
      }
    } else if (descriptor.getChannel() instanceof FileChannel) {
      return fread(number);
    } else {
      return null;
    }
  }

  public synchronized ByteList readpartial(int number)
      throws IOException, BadDescriptorException, EOFException {
    assert number >= 0;

    if (number == 0) {
      return null;
    }
    if (descriptor.getChannel() instanceof FileChannel) {
      return fread(number);
    }

    if (hasBufferedInputBytes()) {
      // already have some bytes buffered, just return those
      return bufferedRead(Math.min(bufferedInputBytesRemaining(), number));
    } else {
      // otherwise, we try an unbuffered read to get whatever's available
      return read(number);
    }
  }

  public synchronized int read(ByteBuffer dst)
      throws IOException, BadDescriptorException, EOFException {
    return read(dst, !(descriptor.getChannel() instanceof FileChannel));
  }

  public synchronized int read(ByteBuffer dst, boolean partial)
      throws IOException, BadDescriptorException, EOFException {
    assert dst.hasRemaining();

    return bufferedRead(dst, partial);
  }

  public synchronized int read() throws IOException, BadDescriptorException {
    try {
      descriptor.checkOpen();

      if (ungotc >= 0) {
        int c = ungotc;
        ungotc = -1;
        return c;
      }

      return bufferedRead();
    } catch (EOFException e) {
      eof = true;
      return -1;
    }
  }

  public ChannelDescriptor getDescriptor() {
    return descriptor;
  }

  public void setBlocking(boolean block) throws IOException {
    if (!(descriptor.getChannel() instanceof SelectableChannel)) {
      return;
    }
    synchronized (((SelectableChannel) descriptor.getChannel()).blockingLock()) {
      blocking = block;
      try {
        ((SelectableChannel) descriptor.getChannel()).configureBlocking(block);
      } catch (IllegalBlockingModeException e) {
        // ignore this; select() will set the correct mode when it is finished
      }
    }
  }

  public boolean isBlocking() {
    return blocking;
  }

  public synchronized void freopen(Ruby runtime, String path, ModeFlags modes)
      throws DirectoryAsFileException, IOException, InvalidValueException, PipeException,
          BadDescriptorException {
    // flush first
    flushWrite();

    // reset buffer
    buffer.clear();
    if (reading) {
      buffer.flip();
    }

    this.modes = modes;

    if (descriptor.isOpen()) {
      descriptor.close();
    }

    if (path.equals("/dev/null") || path.equalsIgnoreCase("nul:") || path.equalsIgnoreCase("nul")) {
      descriptor = descriptor.reopen(new NullChannel(), modes);
    } else {
      String cwd = runtime.getCurrentDirectory();
      JRubyFile theFile = JRubyFile.create(cwd, path);

      if (theFile.isDirectory() && modes.isWritable()) throw new DirectoryAsFileException();

      if (modes.isCreate()) {
        if (theFile.exists() && modes.isExclusive()) {
          throw runtime.newErrnoEEXISTError("File exists - " + path);
        }
        theFile.createNewFile();
      } else {
        if (!theFile.exists()) {
          throw runtime.newErrnoENOENTError("file not found - " + path);
        }
      }

      // We always open this rw since we can only open it r or rw.
      RandomAccessFile file = new RandomAccessFile(theFile, modes.toJavaModeString());

      if (modes.isTruncate()) file.setLength(0L);

      descriptor = descriptor.reopen(file, modes);

      try {
        if (modes.isAppendable()) lseek(0, SEEK_END);
      } catch (PipeException pe) {
        // ignore, it's a pipe or fifo
      }
    }
  }

  public static Stream open(Ruby runtime, ChannelDescriptor descriptor) {
    return maybeWrapWithLineEndingWrapper(
        new ChannelStream(runtime, descriptor, true), descriptor.getOriginalModes());
  }

  public static Stream fdopen(Ruby runtime, ChannelDescriptor descriptor, ModeFlags modes)
      throws InvalidValueException {
    // check these modes before constructing, so we don't finalize the partially-initialized stream
    descriptor.checkNewModes(modes);
    return maybeWrapWithLineEndingWrapper(
        new ChannelStream(runtime, descriptor, modes, true), modes);
  }

  public static Stream open(Ruby runtime, ChannelDescriptor descriptor, boolean autoclose) {
    return maybeWrapWithLineEndingWrapper(
        new ChannelStream(runtime, descriptor, autoclose), descriptor.getOriginalModes());
  }

  public static Stream fdopen(
      Ruby runtime, ChannelDescriptor descriptor, ModeFlags modes, boolean autoclose)
      throws InvalidValueException {
    // check these modes before constructing, so we don't finalize the partially-initialized stream
    descriptor.checkNewModes(modes);
    return maybeWrapWithLineEndingWrapper(
        new ChannelStream(runtime, descriptor, modes, autoclose), modes);
  }

  private static Stream maybeWrapWithLineEndingWrapper(Stream stream, ModeFlags modes) {
    if (Platform.IS_WINDOWS
        && stream.getDescriptor().getChannel() instanceof FileChannel
        && !modes.isBinary()) {
      return new CRLFStreamWrapper(stream);
    }
    return stream;
  }

  public static Stream fopen(Ruby runtime, String path, ModeFlags modes)
      throws FileNotFoundException, DirectoryAsFileException, FileExistsException, IOException,
          InvalidValueException, PipeException, BadDescriptorException {
    ChannelDescriptor descriptor =
        ChannelDescriptor.open(
            runtime.getCurrentDirectory(), path, modes, runtime.getClassLoader());
    Stream stream = fdopen(runtime, descriptor, modes);

    return stream;
  }

  public Channel getChannel() {
    return getDescriptor().getChannel();
  }

  private static final class InputStreamAdapter extends java.io.InputStream {
    private final ChannelStream stream;

    public InputStreamAdapter(ChannelStream stream) {
      this.stream = stream;
    }

    @Override
    public int read() throws IOException {
      synchronized (stream) {
        // If it can be pulled direct from the buffer, don't go via the slow path
        if (stream.hasBufferedInputBytes()) {
          try {
            return stream.read();
          } catch (BadDescriptorException ex) {
            throw new IOException(ex.getMessage());
          }
        }
      }

      byte[] b = new byte[1];
      // java.io.InputStream#read must return an unsigned value;
      return read(b, 0, 1) == 1 ? b[0] & 0xff : -1;
    }

    @Override
    public int read(byte[] bytes, int off, int len) throws IOException {
      if (bytes == null) {
        throw new NullPointerException("null destination buffer");
      }
      if ((len | off | (off + len) | (bytes.length - (off + len))) < 0) {
        throw new IndexOutOfBoundsException();
      }
      if (len == 0) {
        return 0;
      }

      try {
        synchronized (stream) {
          final int available = stream.bufferedInputBytesRemaining();
          if (available >= len) {
            return stream.copyBufferedBytes(bytes, off, len);
          } else if (stream.getDescriptor().getChannel() instanceof SelectableChannel) {
            SelectableChannel ch = (SelectableChannel) stream.getDescriptor().getChannel();
            synchronized (ch.blockingLock()) {
              boolean oldBlocking = ch.isBlocking();
              try {
                if (!oldBlocking) {
                  ch.configureBlocking(true);
                }
                return stream.bufferedRead(ByteBuffer.wrap(bytes, off, len), true);
              } finally {
                if (!oldBlocking) {
                  ch.configureBlocking(oldBlocking);
                }
              }
            }
          } else {
            return stream.bufferedRead(ByteBuffer.wrap(bytes, off, len), true);
          }
        }
      } catch (BadDescriptorException ex) {
        throw new IOException(ex.getMessage());
      } catch (EOFException ex) {
        return -1;
      }
    }

    @Override
    public int available() throws IOException {
      synchronized (stream) {
        return !stream.eof ? stream.bufferedInputBytesRemaining() : 0;
      }
    }

    @Override
    public void close() throws IOException {
      try {
        synchronized (stream) {
          stream.fclose();
        }
      } catch (BadDescriptorException ex) {
        throw new IOException(ex.getMessage());
      }
    }
  }

  private static final class OutputStreamAdapter extends java.io.OutputStream {
    private final ChannelStream stream;

    public OutputStreamAdapter(ChannelStream stream) {
      this.stream = stream;
    }

    @Override
    public void write(int i) throws IOException {
      synchronized (stream) {
        if (!stream.isSync() && stream.hasBufferedOutputSpace()) {
          stream.buffer.put((byte) i);
          return;
        }
      }
      byte[] b = {(byte) i};
      write(b, 0, 1);
    }

    @Override
    public void write(byte[] bytes, int off, int len) throws IOException {
      if (bytes == null) {
        throw new NullPointerException("null source buffer");
      }
      if ((len | off | (off + len) | (bytes.length - (off + len))) < 0) {
        throw new IndexOutOfBoundsException();
      }

      try {
        synchronized (stream) {
          if (!stream.isSync() && stream.bufferedOutputSpaceRemaining() >= len) {
            stream.buffer.put(bytes, off, len);

          } else if (stream.getDescriptor().getChannel() instanceof SelectableChannel) {
            SelectableChannel ch = (SelectableChannel) stream.getDescriptor().getChannel();
            synchronized (ch.blockingLock()) {
              boolean oldBlocking = ch.isBlocking();
              try {
                if (!oldBlocking) {
                  ch.configureBlocking(true);
                }
                stream.bufferedWrite(ByteBuffer.wrap(bytes, off, len));
              } finally {
                if (!oldBlocking) {
                  ch.configureBlocking(oldBlocking);
                }
              }
            }
          } else {
            stream.bufferedWrite(ByteBuffer.wrap(bytes, off, len));
          }
        }
      } catch (BadDescriptorException ex) {
        throw new IOException(ex.getMessage());
      }
    }

    @Override
    public void close() throws IOException {
      try {
        synchronized (stream) {
          stream.fclose();
        }
      } catch (BadDescriptorException ex) {
        throw new IOException(ex.getMessage());
      }
    }

    @Override
    public void flush() throws IOException {
      try {
        synchronized (stream) {
          stream.flushWrite(true);
        }
      } catch (BadDescriptorException ex) {
        throw new IOException(ex.getMessage());
      }
    }
  }
}
Ejemplo n.º 3
0
public class MixedModeIRMethod extends DynamicMethod
    implements IRMethodArgs, PositionAware, Compilable<DynamicMethod> {
  private static final Logger LOG = LoggerFactory.getLogger("InterpretedIRMethod");

  private Signature signature;
  private boolean displayedCFG = false; // FIXME: Remove when we find nicer way of logging CFG

  protected final IRScope method;

  protected static class DynamicMethodBox {
    public DynamicMethod actualMethod;
    public int callCount = 0;
  }

  protected DynamicMethodBox box = new DynamicMethodBox();

  public MixedModeIRMethod(IRScope method, Visibility visibility, RubyModule implementationClass) {
    super(implementationClass, visibility, CallConfiguration.FrameNoneScopeNone, method.getName());
    this.method = method;
    getStaticScope().determineModule();
    this.signature = getStaticScope().getSignature();

    // disable JIT if JIT is disabled
    if (!implementationClass.getRuntime().getInstanceConfig().getCompileMode().shouldJIT()) {
      this.box.callCount = -1;
    }
  }

  public IRScope getIRScope() {
    return method;
  }

  public DynamicMethod getActualMethod() {
    return box.actualMethod;
  }

  public StaticScope getStaticScope() {
    return method.getStaticScope();
  }

  public ArgumentDescriptor[] getArgumentDescriptors() {
    ensureInstrsReady(); // Make sure method is minimally built before returning this info
    return ((IRMethod) method).getArgumentDescriptors();
  }

  public Signature getSignature() {
    return signature;
  }

  @Override
  public Arity getArity() {
    return signature.arity();
  }

  protected void post(InterpreterContext ic, ThreadContext context) {
    // update call stacks (pop: ..)
    context.popFrame();
    if (ic.popDynScope()) {
      context.popScope();
    }
  }

  protected void pre(
      InterpreterContext ic,
      ThreadContext context,
      IRubyObject self,
      String name,
      Block block,
      RubyModule implClass) {
    // update call stacks (push: frame, class, scope, etc.)
    context.preMethodFrameOnly(implClass, name, self, block);
    if (ic.pushNewDynScope()) {
      context.pushScope(DynamicScope.newDynamicScope(ic.getStaticScope()));
    }
  }

  // FIXME: for subclasses we should override this method since it can be simple get
  // FIXME: to avoid cost of synch call in lazilyacquire we can save the ic here
  public InterpreterContext ensureInstrsReady() {
    if (method instanceof IRMethod) {
      return ((IRMethod) method).lazilyAcquireInterpreterContext();
    }
    return method.getInterpreterContext();
  }

  @Override
  public IRubyObject call(
      ThreadContext context,
      IRubyObject self,
      RubyModule clazz,
      String name,
      IRubyObject[] args,
      Block block) {
    if (IRRuntimeHelpers.isDebug()) doDebug();

    DynamicMethodBox box = this.box;
    if (box.callCount >= 0) tryJit(context, box);
    DynamicMethod jittedMethod = box.actualMethod;

    if (jittedMethod != null) {
      return jittedMethod.call(context, self, clazz, name, args, block);
    } else {
      return INTERPRET_METHOD(
          context,
          ensureInstrsReady(),
          getImplementationClass().getMethodLocation(),
          self,
          name,
          args,
          block);
    }
  }

  private IRubyObject INTERPRET_METHOD(
      ThreadContext context,
      InterpreterContext ic,
      RubyModule implClass,
      IRubyObject self,
      String name,
      IRubyObject[] args,
      Block block) {
    try {
      ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

      if (ic.hasExplicitCallProtocol()) {
        return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
      } else {
        try {
          this.pre(ic, context, self, name, block, implClass);
          return ic.engine.interpret(context, self, ic, implClass, name, args, block, null);
        } finally {
          this.post(ic, context);
        }
      }
    } finally {
      ThreadContext.popBacktrace(context);
    }
  }

  @Override
  public IRubyObject call(
      ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
    if (IRRuntimeHelpers.isDebug()) doDebug();

    DynamicMethodBox box = this.box;
    if (box.callCount >= 0) tryJit(context, box);
    DynamicMethod jittedMethod = box.actualMethod;

    if (jittedMethod != null) {
      return jittedMethod.call(context, self, clazz, name, block);
    } else {
      return INTERPRET_METHOD(
          context,
          ensureInstrsReady(),
          getImplementationClass().getMethodLocation(),
          self,
          name,
          block);
    }
  }

  private IRubyObject INTERPRET_METHOD(
      ThreadContext context,
      InterpreterContext ic,
      RubyModule implClass,
      IRubyObject self,
      String name,
      Block block) {
    try {
      ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

      if (ic.hasExplicitCallProtocol()) {
        return ic.engine.interpret(context, self, ic, implClass, name, block, null);
      } else {
        try {
          this.pre(ic, context, self, name, block, implClass);
          return ic.engine.interpret(context, self, ic, implClass, name, block, null);
        } finally {
          this.post(ic, context);
        }
      }
    } finally {
      ThreadContext.popBacktrace(context);
    }
  }

  @Override
  public IRubyObject call(
      ThreadContext context,
      IRubyObject self,
      RubyModule clazz,
      String name,
      IRubyObject arg0,
      Block block) {
    if (IRRuntimeHelpers.isDebug()) doDebug();

    DynamicMethodBox box = this.box;
    if (box.callCount >= 0) tryJit(context, box);
    DynamicMethod jittedMethod = box.actualMethod;

    if (jittedMethod != null) {
      return jittedMethod.call(context, self, clazz, name, arg0, block);
    } else {
      return INTERPRET_METHOD(
          context,
          ensureInstrsReady(),
          getImplementationClass().getMethodLocation(),
          self,
          name,
          arg0,
          block);
    }
  }

  private IRubyObject INTERPRET_METHOD(
      ThreadContext context,
      InterpreterContext ic,
      RubyModule implClass,
      IRubyObject self,
      String name,
      IRubyObject arg1,
      Block block) {
    try {
      ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

      if (ic.hasExplicitCallProtocol()) {
        return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
      } else {
        try {
          this.pre(ic, context, self, name, block, implClass);
          return ic.engine.interpret(context, self, ic, implClass, name, arg1, block, null);
        } finally {
          this.post(ic, context);
        }
      }
    } finally {
      ThreadContext.popBacktrace(context);
    }
  }

  @Override
  public IRubyObject call(
      ThreadContext context,
      IRubyObject self,
      RubyModule clazz,
      String name,
      IRubyObject arg0,
      IRubyObject arg1,
      Block block) {
    if (IRRuntimeHelpers.isDebug()) doDebug();

    DynamicMethodBox box = this.box;
    if (box.callCount >= 0) tryJit(context, box);
    DynamicMethod jittedMethod = box.actualMethod;

    if (jittedMethod != null) {
      return jittedMethod.call(context, self, clazz, name, arg0, arg1, block);
    } else {
      return INTERPRET_METHOD(
          context,
          ensureInstrsReady(),
          getImplementationClass().getMethodLocation(),
          self,
          name,
          arg0,
          arg1,
          block);
    }
  }

  private IRubyObject INTERPRET_METHOD(
      ThreadContext context,
      InterpreterContext ic,
      RubyModule implClass,
      IRubyObject self,
      String name,
      IRubyObject arg1,
      IRubyObject arg2,
      Block block) {
    try {
      ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

      if (ic.hasExplicitCallProtocol()) {
        return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
      } else {
        try {
          this.pre(ic, context, self, name, block, implClass);
          return ic.engine.interpret(context, self, ic, implClass, name, arg1, arg2, block, null);
        } finally {
          this.post(ic, context);
        }
      }
    } finally {
      ThreadContext.popBacktrace(context);
    }
  }

  @Override
  public IRubyObject call(
      ThreadContext context,
      IRubyObject self,
      RubyModule clazz,
      String name,
      IRubyObject arg0,
      IRubyObject arg1,
      IRubyObject arg2,
      Block block) {
    if (IRRuntimeHelpers.isDebug()) doDebug();

    DynamicMethodBox box = this.box;
    if (box.callCount >= 0) tryJit(context, box);
    DynamicMethod jittedMethod = box.actualMethod;

    if (jittedMethod != null) {
      return jittedMethod.call(context, self, clazz, name, arg0, arg1, arg2, block);
    } else {
      return INTERPRET_METHOD(
          context,
          ensureInstrsReady(),
          getImplementationClass().getMethodLocation(),
          self,
          name,
          arg0,
          arg1,
          arg2,
          block);
    }
  }

  private IRubyObject INTERPRET_METHOD(
      ThreadContext context,
      InterpreterContext ic,
      RubyModule implClass,
      IRubyObject self,
      String name,
      IRubyObject arg1,
      IRubyObject arg2,
      IRubyObject arg3,
      Block block) {
    try {
      ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine());

      if (ic.hasExplicitCallProtocol()) {
        return ic.engine.interpret(
            context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
      } else {
        try {
          this.pre(ic, context, self, name, block, implClass);
          return ic.engine.interpret(
              context, self, ic, implClass, name, arg1, arg2, arg3, block, null);
        } finally {
          this.post(ic, context);
        }
      }
    } finally {
      ThreadContext.popBacktrace(context);
    }
  }

  protected void doDebug() {
    // FIXME: This is printing out IRScope CFG but JIT may be active and it might not reflect
    // currently executing.  Move into JIT and into interp since they will be getting CFG from
    // different sources
    // FIXME: This is only printing out CFG once.  If we keep applying more passes then we
    // will want to print out after those new passes.
    ensureInstrsReady();
    LOG.info("Executing '" + method.getName() + "'");
    if (!displayedCFG) {
      LOG.info(method.debugOutput());
      displayedCFG = true;
    }
  }

  public DynamicMethod getMethodForCaching() {
    DynamicMethod method = box.actualMethod;
    if (method instanceof CompiledIRMethod) {
      return method;
    }
    return this;
  }

  @Override
  public void completeBuild(DynamicMethod newMethod) {
    this.box.actualMethod = newMethod;
    this.box.actualMethod.serialNumber = this.serialNumber;
    this.box.callCount = -1;
    getImplementationClass().invalidateCacheDescendants();
  }

  protected void tryJit(ThreadContext context, DynamicMethodBox box) {
    if (context.runtime.isBooting()) return; // don't JIT during runtime boot

    if (box.callCount++ >= Options.JIT_THRESHOLD.load())
      context.runtime.getJITCompiler().buildThresholdReached(context, this);
  }

  public String getClassName(ThreadContext context) {
    String className;
    if (implementationClass.isSingleton()) {
      MetaClass metaClass = (MetaClass) implementationClass;
      RubyClass realClass = metaClass.getRealClass();
      // if real class is Class
      if (realClass == context.runtime.getClassClass()) {
        // use the attached class's name
        className = ((RubyClass) metaClass.getAttached()).getName();
      } else {
        // use the real class name
        className = realClass.getName();
      }
    } else {
      // use the class name
      className = implementationClass.getName();
    }
    return className;
  }

  public void setActualMethod(CompiledIRMethod method) {
    this.box.actualMethod = method;
  }

  protected void dupBox(MixedModeIRMethod orig) {
    this.box = orig.box;
  }

  @Override
  public DynamicMethod dup() {
    MixedModeIRMethod x = new MixedModeIRMethod(method, visibility, implementationClass);
    x.box = box;

    return x;
  }

  public String getFile() {
    return method.getFileName();
  }

  public int getLine() {
    return method.getLineNumber();
  }

  public void setCallCount(int callCount) {
    box.callCount = callCount;
  }
}
Ejemplo n.º 4
0
public class Interpreter {

  private static final Logger LOG = LoggerFactory.getLogger("Interpreter");

  public static IRubyObject interpret(Ruby runtime, Node rootNode, IRubyObject self) {
    IRScope scope = new IRBuilder().buildRoot((RootNode) rootNode);
    scope.prepareForInterpretation();
    //        scope.runCompilerPass(new CallSplitter());

    return interpretTop(runtime, scope, self);
  }

  private static int interpInstrsCount = 0;

  public static boolean isDebug() {
    return RubyInstanceConfig.IR_DEBUG;
  }

  public static IRubyObject interpretTop(Ruby runtime, IRScope scope, IRubyObject self) {
    assert scope instanceof IRScript : "Must be an IRScript scope at Top!!!";

    IRScript root = (IRScript) scope;

    // We get the live object ball rolling here.  This give a valid value for the top
    // of this lexical tree.  All new scope can then retrieve and set based on lexical parent.
    if (root.getStaticScope().getModule() == null) { // If an eval this may already be setup.
      root.getStaticScope().setModule(runtime.getObject());
    }

    RubyModule currModule = root.getStaticScope().getModule();
    IRMethod rootMethod = root.getRootClass().getRootMethod();
    InterpretedIRMethod method = new InterpretedIRMethod(rootMethod, currModule);
    ThreadContext context = runtime.getCurrentContext();

    IRubyObject rv = method.call(context, self, currModule, "", IRubyObject.NULL_ARRAY);
    if (isDebug()) LOG.debug("-- Interpreted instructions: {}", interpInstrsCount);

    return rv;
  }

  public static IRubyObject interpret(ThreadContext context, CFG cfg, InterpreterContext interp) {
    Ruby runtime = context.getRuntime();
    boolean inClosure = (cfg.getScope() instanceof IRClosure);
    boolean passThroughBreak = false;

    try {
      interp.setMethodExitLabel(
          cfg.getExitBB().getLabel()); // used by return and break instructions!

      Instr[] instrs = cfg.prepareForInterpretation();
      int n = instrs.length;
      int ipc = 0;
      Instr lastInstr = null;
      while (ipc < n) {
        interpInstrsCount++;
        lastInstr = instrs[ipc];

        if (isDebug()) LOG.debug("I: {}", lastInstr);

        try {
          Label jumpTarget = lastInstr.interpret(interp);
          ipc = (jumpTarget == null) ? ipc + 1 : jumpTarget.getTargetPC();
        }
        // SSS FIXME: This only catches Ruby exceptions
        // What about Java exceptions?
        catch (org.jruby.exceptions.RaiseException re) {
          ipc = cfg.getRescuerPC(lastInstr);

          if (ipc == -1) throw re; // No one rescued exception, pass it on!

          interp.setException(re.getException());
        }
      }

      // If a closure, and lastInstr was a return, have to return from the nearest method!
      IRubyObject rv = (IRubyObject) interp.getReturnValue();

      if (lastInstr instanceof ReturnInstr && inClosure && !interp.inLambda()) {
        throw new IRReturnJump(((ReturnInstr) lastInstr).methodToReturnFrom, rv);
      }
      // If a closure, and lastInstr was a break, have to return from the nearest closure!
      else if (lastInstr instanceof BREAK_Instr) {
        if (!inClosure) throw runtime.newLocalJumpError(Reason.BREAK, rv, "unexpected break");

        passThroughBreak = true;
        RuntimeHelpers.breakJump(context, rv);
      }

      return rv;
    } catch (JumpException.BreakJump bj) {
      if (passThroughBreak) throw bj;
      return (IRubyObject) bj.getValue();
    } catch (IRReturnJump rj) {
      // - If we are in a lambda, stop propagating
      // - If not in a lambda
      //   - if in a closure, pass it along
      //   - if not in a closure, we got this return jump from a closure further up the call stack.
      //     So, continue popping the call stack till we get to the right method
      if (!interp.inLambda() && (inClosure || (rj.methodToReturnFrom != cfg.getScope())))
        throw rj; // pass it along

      return (IRubyObject) rj.returnValue;
    } finally {
      if (interp.getFrame() != null) {
        context.popFrame();
        interp.setFrame(null);
      }

      if (interp.hasAllocatedDynamicScope()) context.postMethodScopeOnly();
    }
  }

  public static IRubyObject INTERPRET_METHOD(
      ThreadContext context,
      CFG cfg,
      InterpreterContext interp,
      String name,
      RubyModule implClass,
      boolean isTraceable) {
    Ruby runtime = interp.getRuntime();
    boolean syntheticMethod = name == null || name.equals("");

    try {
      String className = implClass.getName();
      if (!syntheticMethod)
        ThreadContext.pushBacktrace(context, className, name, context.getFile(), context.getLine());
      if (isTraceable) methodPreTrace(runtime, context, name, implClass);
      return interpret(context, cfg, interp);
    } finally {
      if (isTraceable) {
        try {
          methodPostTrace(runtime, context, name, implClass);
        } finally {
          if (!syntheticMethod) ThreadContext.popBacktrace(context);
        }
      } else {
        if (!syntheticMethod) ThreadContext.popBacktrace(context);
      }
    }
  }

  private static void methodPreTrace(
      Ruby runtime, ThreadContext context, String name, RubyModule implClass) {
    if (runtime.hasEventHooks()) context.trace(RubyEvent.CALL, name, implClass);
  }

  private static void methodPostTrace(
      Ruby runtime, ThreadContext context, String name, RubyModule implClass) {
    if (runtime.hasEventHooks()) context.trace(RubyEvent.RETURN, name, implClass);
  }
}
Ejemplo n.º 5
0
Archivo: CFG.java Proyecto: cwgem/jruby
/**
 * Represents the base build of a CFG. All information here is accessed via delegation from the CFG
 * itself so this is meant as an internal organizational structure for a build.
 */
public class CFG {
  public enum EdgeType {
    REGULAR, // Any non-special edge.  Not really used.
    EXCEPTION, // Edge to exception handling basic blocks
    FALL_THROUGH, // Edge which is the natural fall through choice on a branch
    EXIT // Edge to dummy exit BB
  }

  private static final Logger LOG = LoggerFactory.getLogger("CFG");

  private IRExecutionScope scope;
  private Map<Label, BasicBlock> bbMap = new HashMap<Label, BasicBlock>();

  // Map of bb -> first bb of the rescue block that initiates exception handling for all exceptions
  // thrown within this bb
  private Map<BasicBlock, BasicBlock> rescuerMap = new HashMap<BasicBlock, BasicBlock>();

  // Map of bb -> first bb of the ensure block that protects this bb
  private Map<BasicBlock, BasicBlock> ensurerMap = new HashMap<BasicBlock, BasicBlock>();

  private List<ExceptionRegion> outermostERs = new ArrayList<ExceptionRegion>();

  private BasicBlock entryBB = null;
  private BasicBlock exitBB = null;
  private DirectedGraph<BasicBlock> graph = new DirectedGraph<BasicBlock>();

  private int nextBBId = 0; // Next available basic block id

  LinkedList<BasicBlock> postOrderList = null; // Post order traversal list of the cfg

  public CFG(IRExecutionScope scope) {
    this.scope = scope;
  }

  public int getNextBBID() {
    nextBBId++;
    return nextBBId;
  }

  public int getMaxNodeID() {
    return nextBBId;
  }

  public boolean bbIsProtected(BasicBlock b) {
    // No need to look in ensurerMap because (_bbEnsurerMap(b) != null) => (_bbResucerMap(b) !=
    // null)
    return getRescuerBBFor(b) != null;
  }

  public BasicBlock getBBForLabel(Label label) {
    return bbMap.get(label);
  }

  public BasicBlock getEnsurerBBFor(BasicBlock block) {
    return ensurerMap.get(block);
  }

  public BasicBlock getEntryBB() {
    return entryBB;
  }

  public BasicBlock getExitBB() {
    return exitBB;
  }

  public List<ExceptionRegion> getOutermostExceptionRegions() {
    return outermostERs;
  }

  public LinkedList<BasicBlock> postOrderList() {
    if (postOrderList == null) postOrderList = buildPostOrderList();
    return postOrderList;
  }

  public ListIterator<BasicBlock> getPostOrderTraverser() {
    return postOrderList().listIterator();
  }

  public ListIterator<BasicBlock> getReversePostOrderTraverser() {
    return postOrderList().listIterator(size());
  }

  public IRExecutionScope getScope() {
    return scope;
  }

  public int size() {
    return graph.size();
  }

  public Collection<BasicBlock> getBasicBlocks() {
    return graph.allData();
  }

  public Collection<BasicBlock> getSortedBasicBlocks() {
    return graph.getSortedData();
  }

  public Iterable<BasicBlock> getIncomingSources(BasicBlock block) {
    return graph.vertexFor(block).getIncomingSourcesData();
  }

  public Iterable<Edge<BasicBlock>> getIncomingEdges(BasicBlock block) {
    return graph.vertexFor(block).getIncomingEdges();
  }

  public BasicBlock getIncomingSource(BasicBlock block) {
    return graph.vertexFor(block).getIncomingSourceData();
  }

  public BasicBlock getIncomingSourceOfType(BasicBlock block, Object type) {
    return graph.vertexFor(block).getIncomingSourceDataOfType(type);
  }

  public Edge<BasicBlock> getIncomingEdgeOfType(BasicBlock block, Object type) {
    return graph.vertexFor(block).getIncomingEdgeOfType(type);
  }

  public Edge<BasicBlock> getOutgoingEdgeOfType(BasicBlock block, Object type) {
    return graph.vertexFor(block).getOutgoingEdgeOfType(type);
  }

  public BasicBlock getOutgoingDestination(BasicBlock block) {
    return graph.vertexFor(block).getOutgoingDestinationData();
  }

  public BasicBlock getOutgoingDestinationOfType(BasicBlock block, Object type) {
    return graph.vertexFor(block).getOutgoingDestinationDataOfType(type);
  }

  public Iterable<BasicBlock> getOutgoingDestinations(BasicBlock block) {
    return graph.vertexFor(block).getOutgoingDestinationsData();
  }

  public Iterable<BasicBlock> getOutgoingDestinationsOfType(BasicBlock block, Object type) {
    return graph.vertexFor(block).getOutgoingDestinationsDataOfType(type);
  }

  public Iterable<BasicBlock> getOutgoingDestinationsNotOfType(BasicBlock block, Object type) {
    return graph.vertexFor(block).getOutgoingDestinationsDataNotOfType(type);
  }

  public Set<Edge<BasicBlock>> getOutgoingEdges(BasicBlock block) {
    return graph.vertexFor(block).getOutgoingEdges();
  }

  public Iterable<Edge<BasicBlock>> getOutgoingEdgesNotOfType(BasicBlock block, Object type) {
    return graph.vertexFor(block).getOutgoingEdgesNotOfType(type);
  }

  public BasicBlock getRescuerBBFor(BasicBlock block) {
    return rescuerMap.get(block);
  }

  public void addEdge(BasicBlock source, BasicBlock destination, Object type) {
    graph.vertexFor(source).addEdgeTo(destination, type);
  }

  /* Add 'b' as a global ensure block that protects all unprotected blocks in this scope */
  public void addGlobalEnsureBlock(BasicBlock geb) {
    addEdge(geb, getExitBB(), EdgeType.EXIT);

    for (BasicBlock basicBlock : getBasicBlocks()) {
      if (basicBlock != geb && !bbIsProtected(basicBlock)) {
        addEdge(basicBlock, geb, EdgeType.EXCEPTION);
        setRescuerBB(basicBlock, geb);
        setEnsurerBB(basicBlock, geb);
      }
    }
  }

  public void putBBForLabel(Label label, BasicBlock block) {
    bbMap.put(label, block);
  }

  public void setEnsurerBB(BasicBlock block, BasicBlock ensureBlock) {
    ensurerMap.put(block, ensureBlock);
  }

  public void setRescuerBB(BasicBlock block, BasicBlock exceptionBlock) {
    rescuerMap.put(block, exceptionBlock);
  }

  /** Build the Control Flow Graph */
  public DirectedGraph<BasicBlock> build(List<Instr> instrs) {
    // Map of label & basic blocks which are waiting for a bb with that label
    Map<Label, List<BasicBlock>> forwardRefs = new HashMap<Label, List<BasicBlock>>();

    // Map of return address variable and all possible targets (required to connect up ensure blocks
    // with their targets)
    Map<Variable, Set<Label>> retAddrMap = new HashMap<Variable, Set<Label>>();
    Map<Variable, BasicBlock> retAddrTargetMap = new HashMap<Variable, BasicBlock>();

    // List of bbs that have a 'return' instruction
    List<BasicBlock> returnBBs = new ArrayList<BasicBlock>();

    // List of bbs that have a 'throw' instruction
    List<BasicBlock> exceptionBBs = new ArrayList<BasicBlock>();

    // Stack of nested rescue regions
    Stack<ExceptionRegion> nestedExceptionRegions = new Stack<ExceptionRegion>();

    // List of all rescued regions
    List<ExceptionRegion> allExceptionRegions = new ArrayList<ExceptionRegion>();

    // Dummy entry basic block (see note at end to see why)
    entryBB = createBB(nestedExceptionRegions);

    // First real bb
    BasicBlock firstBB = createBB(nestedExceptionRegions);

    // Build the rest!
    BasicBlock currBB = firstBB;
    BasicBlock newBB = null;
    boolean bbEnded = false;
    boolean nextBBIsFallThrough = true;
    for (Instr i : instrs) {
      Operation iop = i.getOperation();
      if (iop == Operation.LABEL) {
        Label l = ((LabelInstr) i).label;
        newBB = createBB(l, nestedExceptionRegions);
        // Jump instruction bbs dont add an edge to the succeeding bb by default
        if (nextBBIsFallThrough) graph.addEdge(currBB, newBB, EdgeType.FALL_THROUGH);
        currBB = newBB;
        bbEnded = false;
        nextBBIsFallThrough = true;

        // Add forward reference edges
        List<BasicBlock> frefs = forwardRefs.get(l);
        if (frefs != null) {
          for (BasicBlock b : frefs) {
            graph.addEdge(b, newBB, EdgeType.REGULAR);
          }
        }
      } else if (bbEnded && (iop != Operation.EXC_REGION_END)) {
        newBB = createBB(nestedExceptionRegions);
        // Jump instruction bbs dont add an edge to the succeeding bb by default
        if (nextBBIsFallThrough)
          graph.addEdge(currBB, newBB, EdgeType.FALL_THROUGH); // currBB cannot be null!
        currBB = newBB;
        bbEnded = false;
        nextBBIsFallThrough = true;
      }

      if (i instanceof ExceptionRegionStartMarkerInstr) {
        // SSS: Do we need this anymore?
        //                currBB.addInstr(i);
        ExceptionRegionStartMarkerInstr ersmi = (ExceptionRegionStartMarkerInstr) i;
        ExceptionRegion rr =
            new ExceptionRegion(ersmi.firstRescueBlockLabel, ersmi.ensureBlockLabel);
        rr.addBB(currBB);
        allExceptionRegions.add(rr);

        if (nestedExceptionRegions.empty()) {
          outermostERs.add(rr);
        } else {
          nestedExceptionRegions.peek().addNestedRegion(rr);
        }

        nestedExceptionRegions.push(rr);
      } else if (i instanceof ExceptionRegionEndMarkerInstr) {
        // SSS: Do we need this anymore?
        //                currBB.addInstr(i);
        nestedExceptionRegions.pop().setEndBB(currBB);
      } else if (iop.endsBasicBlock()) {
        bbEnded = true;
        currBB.addInstr(i);
        Label tgt;
        nextBBIsFallThrough = false;
        if (i instanceof BranchInstr) {
          tgt = ((BranchInstr) i).getJumpTarget();
          nextBBIsFallThrough = true;
        } else if (i instanceof JumpInstr) {
          tgt = ((JumpInstr) i).getJumpTarget();
        } else if (iop.isReturn()) { // BREAK, RETURN, CLOSURE_RETURN
          tgt = null;
          returnBBs.add(currBB);
        } else if (i instanceof ThrowExceptionInstr) {
          tgt = null;
          exceptionBBs.add(currBB);
        } else if (i instanceof JumpIndirectInstr) {
          tgt = null;
          Set<Label> retAddrs = retAddrMap.get(((JumpIndirectInstr) i).getJumpTarget());
          for (Label l : retAddrs) {
            addEdge(currBB, l, forwardRefs);
          }
          // Record the target bb for the retaddr var for any set_addr instrs that appear later and
          // use the same retaddr var
          retAddrTargetMap.put(((JumpIndirectInstr) i).getJumpTarget(), currBB);
        } else {
          throw new RuntimeException(
              "Unhandled case in CFG builder for basic block ending instr: " + i);
        }

        if (tgt != null) addEdge(currBB, tgt, forwardRefs);
      } else if (iop != Operation.LABEL) {
        currBB.addInstr(i);
      }

      if (i instanceof SetReturnAddressInstr) {
        Variable v = i.getResult();
        Label tgtLbl = ((SetReturnAddressInstr) i).getReturnAddr();
        BasicBlock tgtBB = retAddrTargetMap.get(v);
        // If we have the target bb, add the edge
        // If not, record it for fixup later
        if (tgtBB != null) {
          addEdge(tgtBB, tgtLbl, forwardRefs);
        } else {
          Set<Label> addrs = retAddrMap.get(v);
          if (addrs == null) {
            addrs = new HashSet<Label>();
            retAddrMap.put(v, addrs);
          }
          addrs.add(tgtLbl);
        }
      } else if (i instanceof CallInstr) { // Build CFG for the closure if there exists one
        Operand closureArg = ((CallInstr) i).getClosureArg();
        if (closureArg instanceof MetaObject) {
          ((IRClosure) ((MetaObject) closureArg).scope).buildCFG();
        }
      }
    }

    // Process all rescued regions
    for (ExceptionRegion rr : allExceptionRegions) {
      BasicBlock firstRescueBB = bbMap.get(rr.getFirstRescueBlockLabel());

      // 1. Tell the region that firstRescueBB is its protector!
      rr.setFirstRescueBB(firstRescueBB);

      // 2. Record a mapping from the region's exclusive basic blocks to the first bb that will
      // start exception handling for all their exceptions.
      // 3. Add an exception edge from every exclusive bb of the region to firstRescueBB
      BasicBlock ensureBlockBB =
          rr.getEnsureBlockLabel() == null ? null : bbMap.get(rr.getEnsureBlockLabel());
      for (BasicBlock b : rr.getExclusiveBBs()) {
        rescuerMap.put(b, firstRescueBB);
        graph.addEdge(b, firstRescueBB, EdgeType.EXCEPTION);
        if (ensureBlockBB != null) {
          ensurerMap.put(b, ensureBlockBB);
          // SSS FIXME: This is a conservative edge because when a rescue block is present
          // that catches an exception, control never reaches the ensure block directly.
          // Only when we get an error or threadkill even, or when breaks propagate upward
          // do we need to hit an ensure directly.  This edge is present to account for that
          // control-flow scneario.
          graph.addEdge(b, ensureBlockBB, EdgeType.EXCEPTION);
        }
      }
    }

    buildExitBasicBlock(
        nestedExceptionRegions,
        firstBB,
        returnBBs,
        exceptionBBs,
        nextBBIsFallThrough,
        currBB,
        entryBB);

    optimize(); // remove useless cfg edges & orphaned bbs

    return graph;
  }

  private void addEdge(
      BasicBlock src, Label targetLabel, Map<Label, List<BasicBlock>> forwardRefs) {
    BasicBlock target = bbMap.get(targetLabel);

    if (target != null) {
      graph.addEdge(src, target, EdgeType.REGULAR);
      return;
    }

    // Add a forward reference from target -> source
    List<BasicBlock> forwardReferences = forwardRefs.get(targetLabel);

    if (forwardReferences == null) {
      forwardReferences = new ArrayList<BasicBlock>();
      forwardRefs.put(targetLabel, forwardReferences);
    }

    forwardReferences.add(src);
  }

  /**
   * Create special empty exit BasicBlock that all BasicBlocks will eventually flow into. All Edges
   * to this 'dummy' BasicBlock will get marked with an edge type of EXIT.
   *
   * <p>Special BasicBlocks worth noting: 1. Exceptions, Returns, Entry(why?) -> ExitBB 2. Returns
   * -> ExitBB
   */
  private BasicBlock buildExitBasicBlock(
      Stack<ExceptionRegion> nestedExceptionRegions,
      BasicBlock firstBB,
      List<BasicBlock> returnBBs,
      List<BasicBlock> exceptionBBs,
      boolean nextIsFallThrough,
      BasicBlock currBB,
      BasicBlock entryBB) {
    exitBB = createBB(nestedExceptionRegions);

    graph.addEdge(entryBB, exitBB, EdgeType.EXIT);
    graph.addEdge(entryBB, firstBB, EdgeType.FALL_THROUGH);

    for (BasicBlock rb : returnBBs) {
      graph.addEdge(rb, exitBB, EdgeType.EXIT);
    }

    for (BasicBlock rb : exceptionBBs) {
      graph.addEdge(rb, exitBB, EdgeType.EXIT);
    }

    if (nextIsFallThrough) graph.addEdge(currBB, exitBB, EdgeType.EXIT);

    return exitBB;
  }

  private BasicBlock createBB(Label label, Stack<ExceptionRegion> nestedExceptionRegions) {
    BasicBlock basicBlock = new BasicBlock(this, label);
    bbMap.put(label, basicBlock);
    graph.vertexFor(basicBlock);

    if (!nestedExceptionRegions.empty()) nestedExceptionRegions.peek().addBB(basicBlock);

    return basicBlock;
  }

  private BasicBlock createBB(Stack<ExceptionRegion> nestedExceptionRegions) {
    return createBB(scope.getNewLabel(), nestedExceptionRegions);
  }

  public void removeEdge(Edge edge) {
    graph.removeEdge(edge);
  }

  private void deleteOrphanedBlocks(DirectedGraph<BasicBlock> graph) {
    // System.out.println("\nGraph:\n" + getGraph().toString());
    // System.out.println("\nInstructions:\n" + toStringInstrs());

    // FIXME: Quick and dirty implementation
    while (true) {
      BasicBlock bbToRemove = null;
      for (BasicBlock b : graph.allData()) {
        if (b == entryBB) continue; // Skip entry bb!

        // Every other bb should have at least one incoming edge
        if (graph.vertexFor(b).getIncomingEdges().isEmpty()) {
          bbToRemove = b;
          break;
        }
      }
      if (bbToRemove == null) break;

      removeBB(bbToRemove);
    }
  }

  void removeBB(BasicBlock b) {
    graph.removeVertexFor(b);
    bbMap.remove(b.getLabel());
    rescuerMap.remove(b);
    ensurerMap.remove(b);
    // SSS FIXME: Patch up rescued regions as well??
  }

  private void optimize() {
    // SSS FIXME: Can't we not add some of these exception edges in the first place??
    // Remove exception edges from blocks that couldn't possibly thrown an exception!
    List<Edge> toRemove = new ArrayList<Edge>();
    for (BasicBlock b : graph.allData()) {
      boolean noExceptions = true;
      for (Instr i : b.getInstrs()) {
        if (i.canRaiseException()) {
          noExceptions = false;
          break;
        }
      }

      if (noExceptions) {
        for (Edge<BasicBlock> e : graph.vertexFor(b).getOutgoingEdgesOfType(EdgeType.EXCEPTION)) {
          BasicBlock source = e.getSource().getData();
          BasicBlock destination = e.getDestination().getData();
          toRemove.add(e);

          if (rescuerMap.get(source) == destination) rescuerMap.remove(source);
          if (ensurerMap.get(source) == destination) ensurerMap.remove(source);
        }
      }
    }

    if (!toRemove.isEmpty()) {
      for (Edge edge : toRemove) {
        graph.removeEdge(edge);
      }
    }

    deleteOrphanedBlocks(graph);
  }

  public String toStringGraph() {
    return graph.toString();
  }

  public String toStringInstrs() {
    StringBuilder buf = new StringBuilder();

    for (BasicBlock b : getSortedBasicBlocks()) {
      buf.append(b.toStringInstrs());
    }
    buf.append("\n\n------ Rescue block map ------\n");
    for (BasicBlock bb : rescuerMap.keySet()) {
      buf.append("BB ")
          .append(bb.getID())
          .append(" --> BB ")
          .append(rescuerMap.get(bb).getID())
          .append("\n");
    }
    buf.append("\n\n------ Ensure block map ------\n");
    for (BasicBlock bb : ensurerMap.keySet()) {
      buf.append("BB ")
          .append(bb.getID())
          .append(" --> BB ")
          .append(ensurerMap.get(bb).getID())
          .append("\n");
    }

    List<IRClosure> closures = scope.getClosures();
    if (!closures.isEmpty()) {
      buf.append("\n\n------ Closures encountered in this scope ------\n");
      for (IRClosure c : closures) {
        buf.append(c.toStringBody());
      }
      buf.append("------------------------------------------------\n");
    }

    return buf.toString();
  }

  void removeEdge(BasicBlock a, BasicBlock b) {
    throw new UnsupportedOperationException("Not yet implemented");
  }

  private LinkedList<BasicBlock> buildPostOrderList() {
    LinkedList<BasicBlock> list = new LinkedList<BasicBlock>();
    BasicBlock root = getEntryBB();
    Stack<BasicBlock> stack = new Stack<BasicBlock>();
    stack.push(root);
    BitSet bbSet = new BitSet(1 + getMaxNodeID());
    bbSet.set(root.getID());

    // Non-recursive post-order traversal (the added flag is required to handle cycles and common
    // ancestors)
    while (!stack.empty()) {
      // Check if all children of the top of the stack have been added
      BasicBlock b = stack.peek();
      boolean allChildrenDone = true;
      for (BasicBlock dst : getOutgoingDestinations(b)) {
        int dstID = dst.getID();
        if (!bbSet.get(dstID)) {
          allChildrenDone = false;
          stack.push(dst);
          bbSet.set(dstID);
        }
      }

      // If all children have been added previously, we are ready with 'b' in this round!
      if (allChildrenDone) {
        stack.pop();
        list.add(b);
      }
    }

    // Sanity check!
    for (BasicBlock b : getBasicBlocks()) {
      if (!bbSet.get(b.getID())) {
        printError("BB " + b.getID() + " missing from po list!");
        break;
      }
    }

    return list;
  }

  private void printError(String message) {
    LOG.error(message + "\nGraph:\n" + this + "\nInstructions:\n" + toStringInstrs());
  }
}
Ejemplo n.º 6
0
/**
 * Implementation of Ruby's <code>Thread</code> class. Each Ruby thread is mapped to an underlying
 * Java Virtual Machine thread.
 *
 * <p>Thread encapsulates the behavior of a thread of execution, including the main thread of the
 * Ruby script. In the descriptions that follow, the parameter <code>aSymbol</code> refers to a
 * symbol, which is either a quoted string or a <code>Symbol</code> (such as <code>:name</code>).
 *
 * <p>Note: For CVS history, see ThreadClass.java.
 */
@JRubyClass(name = "Thread")
public class RubyThread extends RubyObject implements ExecutionContext {

  private static final Logger LOG = LoggerFactory.getLogger("RubyThread");

  /** The thread-like think that is actually executing */
  private ThreadLike threadImpl;

  /** Normal thread-local variables */
  private transient Map<IRubyObject, IRubyObject> threadLocalVariables;

  /** Context-local variables, internal-ish thread locals */
  private final Map<Object, IRubyObject> contextVariables = new WeakHashMap<Object, IRubyObject>();

  /** Whether this thread should try to abort the program on exception */
  private boolean abortOnException;

  /** The final value resulting from the thread's execution */
  private IRubyObject finalResult;

  /**
   * The exception currently being raised out of the thread. We reference it here to continue
   * propagating it while handling thread shutdown logic and abort_on_exception.
   */
  private RaiseException exitingException;

  /** The ThreadGroup to which this thread belongs */
  private RubyThreadGroup threadGroup;

  /** Per-thread "current exception" */
  private IRubyObject errorInfo;

  /** Weak reference to the ThreadContext for this thread. */
  private volatile WeakReference<ThreadContext> contextRef;

  private static final boolean DEBUG = false;

  /** Thread statuses */
  public static enum Status {
    RUN,
    SLEEP,
    ABORTING,
    DEAD
  }

  /** Current status in an atomic reference */
  private final AtomicReference<Status> status = new AtomicReference<Status>(Status.RUN);

  /** Mail slot for cross-thread events */
  private volatile ThreadService.Event mail;

  /** The current task blocking a thread, to allow interrupting it in an appropriate way */
  private volatile BlockingTask currentBlockingTask;

  /** The list of locks this thread currently holds, so they can be released on exit */
  private final List<Lock> heldLocks = new ArrayList<Lock>();

  /** Whether or not this thread has been disposed of */
  private volatile boolean disposed = false;

  /** The thread's initial priority, for use in thread pooled mode */
  private int initialPriority;

  protected RubyThread(Ruby runtime, RubyClass type) {
    super(runtime, type);

    finalResult = runtime.getNil();
    errorInfo = runtime.getNil();
  }

  public void receiveMail(ThreadService.Event event) {
    synchronized (this) {
      // if we're already aborting, we can receive no further mail
      if (status.get() == Status.ABORTING) return;

      mail = event;
      switch (event.type) {
        case KILL:
          status.set(Status.ABORTING);
      }

      // If this thread is sleeping or stopped, wake it
      notify();
    }

    // interrupt the target thread in case it's blocking or waiting
    // WARNING: We no longer interrupt the target thread, since this usually means
    // interrupting IO and with NIO that means the channel is no longer usable.
    // We either need a new way to handle waking a target thread that's waiting
    // on IO, or we need to accept that we can't wake such threads and must wait
    // for them to complete their operation.
    // threadImpl.interrupt();

    // new interrupt, to hopefully wake it out of any blocking IO
    this.interrupt();
  }

  public synchronized void checkMail(ThreadContext context) {
    ThreadService.Event myEvent = mail;
    mail = null;
    if (myEvent != null) {
      switch (myEvent.type) {
        case RAISE:
          receivedAnException(context, myEvent.exception);
        case KILL:
          throwThreadKill();
      }
    }
  }

  public IRubyObject getErrorInfo() {
    return errorInfo;
  }

  public IRubyObject setErrorInfo(IRubyObject errorInfo) {
    this.errorInfo = errorInfo;
    return errorInfo;
  }

  public void setContext(ThreadContext context) {
    this.contextRef = new WeakReference<ThreadContext>(context);
  }

  public ThreadContext getContext() {
    return contextRef.get();
  }

  public Thread getNativeThread() {
    return threadImpl.nativeThread();
  }

  /**
   * Perform pre-execution tasks once the native thread is running, but we have not yet called the
   * Ruby code for the thread.
   */
  public void beforeStart() {
    // store initial priority, for restoring pooled threads to normal
    initialPriority = threadImpl.getPriority();

    // set to "normal" priority
    threadImpl.setPriority(Thread.NORM_PRIORITY);
  }

  /** Dispose of the current thread by tidying up connections to other stuff */
  public synchronized void dispose() {
    if (!disposed) {
      disposed = true;

      // remove from parent thread group
      threadGroup.remove(this);

      // unlock all locked locks
      unlockAll();

      // reset thread priority to initial if pooling
      if (Options.THREADPOOL_ENABLED.load()) {
        threadImpl.setPriority(initialPriority);
      }

      // mark thread as DEAD
      beDead();

      // unregister from runtime's ThreadService
      getRuntime().getThreadService().unregisterThread(this);
    }
  }

  public static RubyClass createThreadClass(Ruby runtime) {
    // FIXME: In order for Thread to play well with the standard 'new' behavior,
    // it must provide an allocator that can create empty object instances which
    // initialize then fills with appropriate data.
    RubyClass threadClass =
        runtime.defineClass(
            "Thread", runtime.getObject(), ObjectAllocator.NOT_ALLOCATABLE_ALLOCATOR);
    runtime.setThread(threadClass);

    threadClass.index = ClassIndex.THREAD;
    threadClass.setReifiedClass(RubyThread.class);

    threadClass.defineAnnotatedMethods(RubyThread.class);

    RubyThread rubyThread = new RubyThread(runtime, threadClass);
    // TODO: need to isolate the "current" thread from class creation
    rubyThread.threadImpl = new NativeThread(rubyThread, Thread.currentThread());
    runtime.getThreadService().setMainThread(Thread.currentThread(), rubyThread);

    // set to default thread group
    runtime.getDefaultThreadGroup().addDirectly(rubyThread);

    threadClass.setMarshal(ObjectMarshal.NOT_MARSHALABLE_MARSHAL);

    return threadClass;
  }

  /**
   * <code>Thread.new</code>
   *
   * <p>Thread.new( <i>[ arg ]*</i> ) {| args | block } -> aThread
   *
   * <p>Creates a new thread to execute the instructions given in block, and begins running it. Any
   * arguments passed to Thread.new are passed into the block.
   *
   * <pre>
   * x = Thread.new { sleep .1; print "x"; print "y"; print "z" }
   * a = Thread.new { print "a"; print "b"; sleep .2; print "c" }
   * x.join # Let the threads finish before
   * a.join # main thread exits...
   * </pre>
   *
   * <i>produces:</i> abxyzc
   */
  @JRubyMethod(
      name = {"new", "fork"},
      rest = true,
      meta = true)
  public static IRubyObject newInstance(IRubyObject recv, IRubyObject[] args, Block block) {
    return startThread(recv, args, true, block);
  }

  /**
   * Basically the same as Thread.new . However, if class Thread is subclassed, then calling start
   * in that subclass will not invoke the subclass's initialize method.
   */
  @JRubyMethod(rest = true, meta = true, compat = RUBY1_8)
  public static RubyThread start(IRubyObject recv, IRubyObject[] args, Block block) {
    return startThread(recv, args, false, block);
  }

  @JRubyMethod(rest = true, name = "start", meta = true, compat = RUBY1_9)
  public static RubyThread start19(IRubyObject recv, IRubyObject[] args, Block block) {
    Ruby runtime = recv.getRuntime();
    // The error message may appear incongruous here, due to the difference
    // between JRuby's Thread model and MRI's.
    // We mimic MRI's message in the name of compatibility.
    if (!block.isGiven())
      throw runtime.newArgumentError("tried to create Proc object without a block");
    return startThread(recv, args, false, block);
  }

  public static RubyThread adopt(IRubyObject recv, Thread t) {
    return adoptThread(recv, t, Block.NULL_BLOCK);
  }

  private static RubyThread adoptThread(final IRubyObject recv, Thread t, Block block) {
    final Ruby runtime = recv.getRuntime();
    final RubyThread rubyThread = new RubyThread(runtime, (RubyClass) recv);

    rubyThread.threadImpl = new NativeThread(rubyThread, t);
    ThreadContext context = runtime.getThreadService().registerNewThread(rubyThread);
    runtime.getThreadService().associateThread(t, rubyThread);

    context.preAdoptThread();

    // set to default thread group
    runtime.getDefaultThreadGroup().addDirectly(rubyThread);

    return rubyThread;
  }

  @JRubyMethod(rest = true, visibility = PRIVATE)
  public IRubyObject initialize(ThreadContext context, IRubyObject[] args, Block block) {
    Ruby runtime = getRuntime();
    if (!block.isGiven()) throw runtime.newThreadError("must be called with a block");

    try {
      RubyRunnable runnable = new RubyRunnable(this, args, context.getFrames(0), block);
      if (RubyInstanceConfig.POOLING_ENABLED) {
        FutureThread futureThread = new FutureThread(this, runnable);
        threadImpl = futureThread;

        addToCorrectThreadGroup(context);

        threadImpl.start();

        // JRUBY-2380, associate future early so it shows up in Thread.list right away, in case it
        // doesn't run immediately
        runtime.getThreadService().associateThread(futureThread.getFuture(), this);
      } else {
        Thread thread = new Thread(runnable);
        thread.setDaemon(true);
        thread.setName(
            "Ruby" + thread.getName() + ": " + context.getFile() + ":" + (context.getLine() + 1));
        threadImpl = new NativeThread(this, thread);

        addToCorrectThreadGroup(context);

        // JRUBY-2380, associate thread early so it shows up in Thread.list right away, in case it
        // doesn't run immediately
        runtime.getThreadService().associateThread(thread, this);

        threadImpl.start();
      }

      // We yield here to hopefully permit the target thread to schedule
      // MRI immediately schedules it, so this is close but not exact
      Thread.yield();

      return this;
    } catch (OutOfMemoryError oome) {
      if (oome.getMessage().equals("unable to create new native thread")) {
        throw runtime.newThreadError(oome.getMessage());
      }
      throw oome;
    } catch (SecurityException ex) {
      throw runtime.newThreadError(ex.getMessage());
    }
  }

  private static RubyThread startThread(
      final IRubyObject recv, final IRubyObject[] args, boolean callInit, Block block) {
    RubyThread rubyThread = new RubyThread(recv.getRuntime(), (RubyClass) recv);

    if (callInit) {
      rubyThread.callInit(args, block);
    } else {
      // for Thread::start, which does not call the subclass's initialize
      rubyThread.initialize(recv.getRuntime().getCurrentContext(), args, block);
    }

    return rubyThread;
  }

  public synchronized void cleanTerminate(IRubyObject result) {
    finalResult = result;
  }

  public synchronized void beDead() {
    status.set(Status.DEAD);
  }

  public void pollThreadEvents() {
    pollThreadEvents(getRuntime().getCurrentContext());
  }

  public void pollThreadEvents(ThreadContext context) {
    if (mail != null) checkMail(context);
  }

  private static void throwThreadKill() {
    throw new ThreadKill();
  }

  /**
   * Returns the status of the global ``abort on exception'' condition. The default is false. When
   * set to true, will cause all threads to abort (the process will exit(0)) if an exception is
   * raised in any thread. See also Thread.abort_on_exception= .
   */
  @JRubyMethod(name = "abort_on_exception", meta = true)
  public static RubyBoolean abort_on_exception_x(IRubyObject recv) {
    Ruby runtime = recv.getRuntime();
    return runtime.isGlobalAbortOnExceptionEnabled() ? runtime.getTrue() : runtime.getFalse();
  }

  @JRubyMethod(name = "abort_on_exception=", required = 1, meta = true)
  public static IRubyObject abort_on_exception_set_x(IRubyObject recv, IRubyObject value) {
    recv.getRuntime().setGlobalAbortOnExceptionEnabled(value.isTrue());
    return value;
  }

  @JRubyMethod(name = "current", meta = true)
  public static RubyThread current(IRubyObject recv) {
    return recv.getRuntime().getCurrentContext().getThread();
  }

  @JRubyMethod(name = "main", meta = true)
  public static RubyThread main(IRubyObject recv) {
    return recv.getRuntime().getThreadService().getMainThread();
  }

  @JRubyMethod(name = "pass", meta = true)
  public static IRubyObject pass(IRubyObject recv) {
    Ruby runtime = recv.getRuntime();
    ThreadService ts = runtime.getThreadService();
    boolean critical = ts.getCritical();

    ts.setCritical(false);

    Thread.yield();

    ts.setCritical(critical);

    return recv.getRuntime().getNil();
  }

  @JRubyMethod(name = "list", meta = true)
  public static RubyArray list(IRubyObject recv) {
    RubyThread[] activeThreads = recv.getRuntime().getThreadService().getActiveRubyThreads();

    return recv.getRuntime().newArrayNoCopy(activeThreads);
  }

  private void addToCorrectThreadGroup(ThreadContext context) {
    // JRUBY-3568, inherit threadgroup or use default
    IRubyObject group = context.getThread().group();
    if (!group.isNil()) {
      ((RubyThreadGroup) group).addDirectly(this);
    } else {
      context.runtime.getDefaultThreadGroup().addDirectly(this);
    }
  }

  private IRubyObject getSymbolKey(IRubyObject originalKey) {
    if (originalKey instanceof RubySymbol) {
      return originalKey;
    } else if (originalKey instanceof RubyString) {
      return getRuntime().newSymbol(originalKey.asJavaString());
    } else if (originalKey instanceof RubyFixnum) {
      getRuntime().getWarnings().warn(ID.FIXNUMS_NOT_SYMBOLS, "Do not use Fixnums as Symbols");
      throw getRuntime().newArgumentError(originalKey + " is not a symbol");
    } else {
      throw getRuntime().newTypeError(originalKey + " is not a symbol");
    }
  }

  private synchronized Map<IRubyObject, IRubyObject> getThreadLocals() {
    if (threadLocalVariables == null) {
      threadLocalVariables = new HashMap<IRubyObject, IRubyObject>();
    }
    return threadLocalVariables;
  }

  private void clearThreadLocals() {
    threadLocalVariables = null;
  }

  public final Map<Object, IRubyObject> getContextVariables() {
    return contextVariables;
  }

  public boolean isAlive() {
    return threadImpl.isAlive() && status.get() != Status.ABORTING;
  }

  @JRubyMethod(name = "[]", required = 1)
  public IRubyObject op_aref(IRubyObject key) {
    IRubyObject value;
    if ((value = getThreadLocals().get(getSymbolKey(key))) != null) {
      return value;
    }
    return getRuntime().getNil();
  }

  @JRubyMethod(name = "[]=", required = 2)
  public IRubyObject op_aset(IRubyObject key, IRubyObject value) {
    key = getSymbolKey(key);

    getThreadLocals().put(key, value);
    return value;
  }

  @JRubyMethod(name = "abort_on_exception")
  public RubyBoolean abort_on_exception() {
    return abortOnException ? getRuntime().getTrue() : getRuntime().getFalse();
  }

  @JRubyMethod(name = "abort_on_exception=", required = 1)
  public IRubyObject abort_on_exception_set(IRubyObject val) {
    abortOnException = val.isTrue();
    return val;
  }

  @JRubyMethod(name = "alive?")
  public RubyBoolean alive_p() {
    return isAlive() ? getRuntime().getTrue() : getRuntime().getFalse();
  }

  @JRubyMethod(name = "join", optional = 1)
  public IRubyObject join(IRubyObject[] args) {
    Ruby runtime = getRuntime();
    long timeoutMillis = Long.MAX_VALUE;

    if (args.length > 0 && !args[0].isNil()) {
      if (args.length > 1) {
        throw getRuntime().newArgumentError(args.length, 1);
      }
      // MRI behavior: value given in seconds; converted to Float; less
      // than or equal to zero returns immediately; returns nil
      timeoutMillis = (long) (1000.0D * args[0].convertToFloat().getValue());
      if (timeoutMillis <= 0) {
        // TODO: not sure that we should skip calling join() altogether.
        // Thread.join() has some implications for Java Memory Model, etc.
        if (threadImpl.isAlive()) {
          return getRuntime().getNil();
        } else {
          return this;
        }
      }
    }

    if (isCurrent()) {
      throw getRuntime().newThreadError("thread " + identityString() + " tried to join itself");
    }

    try {
      if (runtime.getThreadService().getCritical()) {
        // If the target thread is sleeping or stopped, wake it
        synchronized (this) {
          notify();
        }

        // interrupt the target thread in case it's blocking or waiting
        // WARNING: We no longer interrupt the target thread, since this usually means
        // interrupting IO and with NIO that means the channel is no longer usable.
        // We either need a new way to handle waking a target thread that's waiting
        // on IO, or we need to accept that we can't wake such threads and must wait
        // for them to complete their operation.
        // threadImpl.interrupt();
      }

      RubyThread currentThread = getRuntime().getCurrentContext().getThread();
      final long timeToWait = Math.min(timeoutMillis, 200);

      // We need this loop in order to be able to "unblock" the
      // join call without actually calling interrupt.
      long start = System.currentTimeMillis();
      while (true) {
        currentThread.pollThreadEvents();
        threadImpl.join(timeToWait);
        if (!threadImpl.isAlive()) {
          break;
        }
        if (System.currentTimeMillis() - start > timeoutMillis) {
          break;
        }
      }
    } catch (InterruptedException ie) {
      ie.printStackTrace();
      assert false : ie;
    } catch (ExecutionException ie) {
      ie.printStackTrace();
      assert false : ie;
    }

    if (exitingException != null) {
      // Set $! in the current thread before exiting
      getRuntime().getGlobalVariables().set("$!", (IRubyObject) exitingException.getException());
      throw exitingException;
    }

    if (threadImpl.isAlive()) {
      return getRuntime().getNil();
    } else {
      return this;
    }
  }

  @JRubyMethod
  public IRubyObject value() {
    join(new IRubyObject[0]);
    synchronized (this) {
      return finalResult;
    }
  }

  @JRubyMethod
  public IRubyObject group() {
    if (threadGroup == null) {
      return getRuntime().getNil();
    }

    return threadGroup;
  }

  void setThreadGroup(RubyThreadGroup rubyThreadGroup) {
    threadGroup = rubyThreadGroup;
  }

  @JRubyMethod(name = "inspect")
  @Override
  public synchronized IRubyObject inspect() {
    // FIXME: There's some code duplication here with RubyObject#inspect
    StringBuilder part = new StringBuilder();
    String cname = getMetaClass().getRealClass().getName();
    part.append("#<").append(cname).append(":");
    part.append(identityString());
    part.append(' ');
    part.append(status.toString().toLowerCase());
    part.append('>');
    return getRuntime().newString(part.toString());
  }

  @JRubyMethod(name = "key?", required = 1)
  public RubyBoolean key_p(IRubyObject key) {
    key = getSymbolKey(key);

    return getRuntime().newBoolean(getThreadLocals().containsKey(key));
  }

  @JRubyMethod(name = "keys")
  public RubyArray keys() {
    IRubyObject[] keys = new IRubyObject[getThreadLocals().size()];

    return RubyArray.newArrayNoCopy(getRuntime(), getThreadLocals().keySet().toArray(keys));
  }

  @JRubyMethod(name = "critical=", required = 1, meta = true, compat = CompatVersion.RUBY1_8)
  public static IRubyObject critical_set(IRubyObject receiver, IRubyObject value) {
    receiver.getRuntime().getThreadService().setCritical(value.isTrue());

    return value;
  }

  @JRubyMethod(name = "critical", meta = true, compat = CompatVersion.RUBY1_8)
  public static IRubyObject critical(IRubyObject receiver) {
    return receiver.getRuntime().newBoolean(receiver.getRuntime().getThreadService().getCritical());
  }

  @JRubyMethod(name = "stop", meta = true)
  public static IRubyObject stop(ThreadContext context, IRubyObject receiver) {
    RubyThread rubyThread = context.getThread();

    synchronized (rubyThread) {
      rubyThread.checkMail(context);
      try {
        // attempt to decriticalize all if we're the critical thread
        receiver.getRuntime().getThreadService().setCritical(false);

        rubyThread.status.set(Status.SLEEP);
        rubyThread.wait();
      } catch (InterruptedException ie) {
        rubyThread.checkMail(context);
        rubyThread.status.set(Status.RUN);
      }
    }

    return receiver.getRuntime().getNil();
  }

  @JRubyMethod(required = 1, meta = true)
  public static IRubyObject kill(IRubyObject receiver, IRubyObject rubyThread, Block block) {
    if (!(rubyThread instanceof RubyThread))
      throw receiver.getRuntime().newTypeError(rubyThread, receiver.getRuntime().getThread());
    return ((RubyThread) rubyThread).kill();
  }

  @JRubyMethod(meta = true)
  public static IRubyObject exit(IRubyObject receiver, Block block) {
    RubyThread rubyThread =
        receiver.getRuntime().getThreadService().getCurrentContext().getThread();

    synchronized (rubyThread) {
      rubyThread.status.set(Status.ABORTING);
      rubyThread.mail = null;
      receiver.getRuntime().getThreadService().setCritical(false);
      throw new ThreadKill();
    }
  }

  @JRubyMethod(name = "stop?")
  public RubyBoolean stop_p() {
    // not valid for "dead" state
    return getRuntime().newBoolean(status.get() == Status.SLEEP || status.get() == Status.DEAD);
  }

  @JRubyMethod(name = "wakeup")
  public synchronized RubyThread wakeup() {
    if (!threadImpl.isAlive() && status.get() == Status.DEAD) {
      throw getRuntime().newThreadError("killed thread");
    }

    status.set(Status.RUN);
    notifyAll();

    return this;
  }

  @JRubyMethod(name = "priority")
  public RubyFixnum priority() {
    return RubyFixnum.newFixnum(getRuntime(), threadImpl.getPriority());
  }

  @JRubyMethod(name = "priority=", required = 1)
  public IRubyObject priority_set(IRubyObject priority) {
    // FIXME: This should probably do some translation from Ruby priority levels to Java priority
    // levels (until we have green threads)
    int iPriority = RubyNumeric.fix2int(priority);

    if (iPriority < Thread.MIN_PRIORITY) {
      iPriority = Thread.MIN_PRIORITY;
    } else if (iPriority > Thread.MAX_PRIORITY) {
      iPriority = Thread.MAX_PRIORITY;
    }

    if (threadImpl.isAlive()) {
      threadImpl.setPriority(iPriority);
    }

    return RubyFixnum.newFixnum(getRuntime(), iPriority);
  }

  @JRubyMethod(optional = 3)
  public IRubyObject raise(IRubyObject[] args, Block block) {
    Ruby runtime = getRuntime();
    ThreadContext context = runtime.getCurrentContext();
    if (this == context.getThread()) {
      return RubyKernel.raise(context, runtime.getKernel(), args, block);
    }

    debug(this, "before raising");
    RubyThread currentThread = getRuntime().getCurrentContext().getThread();

    debug(this, "raising");
    IRubyObject exception = prepareRaiseException(runtime, args, block);

    runtime
        .getThreadService()
        .deliverEvent(
            new ThreadService.Event(
                currentThread, this, ThreadService.Event.Type.RAISE, exception));

    return this;
  }

  /**
   * This is intended to be used to raise exceptions in Ruby threads from non- Ruby threads like
   * Timeout's thread.
   *
   * @param args Same args as for Thread#raise
   * @param block Same as for Thread#raise
   */
  public void internalRaise(IRubyObject[] args) {
    Ruby runtime = getRuntime();

    IRubyObject exception = prepareRaiseException(runtime, args, Block.NULL_BLOCK);

    receiveMail(new ThreadService.Event(this, this, ThreadService.Event.Type.RAISE, exception));
  }

  private IRubyObject prepareRaiseException(Ruby runtime, IRubyObject[] args, Block block) {
    if (args.length == 0) {
      IRubyObject lastException = errorInfo;
      if (lastException.isNil()) {
        return new RaiseException(runtime, runtime.getRuntimeError(), "", false).getException();
      }
      return lastException;
    }

    IRubyObject exception;
    ThreadContext context = getRuntime().getCurrentContext();

    if (args.length == 1) {
      if (args[0] instanceof RubyString) {
        return runtime.getRuntimeError().newInstance(context, args, block);
      }

      if (!args[0].respondsTo("exception")) {
        return runtime.newTypeError("exception class/object expected").getException();
      }
      exception = args[0].callMethod(context, "exception");
    } else {
      if (!args[0].respondsTo("exception")) {
        return runtime.newTypeError("exception class/object expected").getException();
      }

      exception = args[0].callMethod(context, "exception", args[1]);
    }

    if (!runtime.getException().isInstance(exception)) {
      return runtime.newTypeError("exception object expected").getException();
    }

    if (args.length == 3) {
      ((RubyException) exception).set_backtrace(args[2]);
    }

    return exception;
  }

  @JRubyMethod(name = "run")
  public synchronized IRubyObject run() {
    return wakeup();
  }

  /**
   * We can never be sure if a wait will finish because of a Java "spurious wakeup". So if we
   * explicitly wakeup and we wait less than requested amount we will return false. We will return
   * true if we sleep right amount or less than right amount via spurious wakeup.
   */
  public synchronized boolean sleep(long millis) throws InterruptedException {
    assert this == getRuntime().getCurrentContext().getThread();
    boolean result = true;

    synchronized (this) {
      pollThreadEvents();
      try {
        status.set(Status.SLEEP);
        if (millis == -1) {
          wait();
        } else {
          wait(millis);
        }
      } finally {
        result = (status.get() != Status.RUN);
        pollThreadEvents();
        status.set(Status.RUN);
      }
    }

    return result;
  }

  @JRubyMethod(name = "status")
  public synchronized IRubyObject status() {
    if (threadImpl.isAlive()) {
      // TODO: no java stringity
      return getRuntime().newString(status.toString().toLowerCase());
    } else if (exitingException != null) {
      return getRuntime().getNil();
    } else {
      return getRuntime().getFalse();
    }
  }

  public static interface BlockingTask {
    public void run() throws InterruptedException;

    public void wakeup();
  }

  public static final class SleepTask implements BlockingTask {
    private final Object object;
    private final long millis;
    private final int nanos;

    public SleepTask(Object object, long millis, int nanos) {
      this.object = object;
      this.millis = millis;
      this.nanos = nanos;
    }

    public void run() throws InterruptedException {
      synchronized (object) {
        object.wait(millis, nanos);
      }
    }

    public void wakeup() {
      synchronized (object) {
        object.notify();
      }
    }
  }

  public void executeBlockingTask(BlockingTask task) throws InterruptedException {
    enterSleep();
    try {
      currentBlockingTask = task;
      pollThreadEvents();
      task.run();
    } finally {
      exitSleep();
      currentBlockingTask = null;
      pollThreadEvents();
    }
  }

  public void enterSleep() {
    status.set(Status.SLEEP);
  }

  public void exitSleep() {
    status.set(Status.RUN);
  }

  @JRubyMethod(name = {"kill", "exit", "terminate"})
  public IRubyObject kill() {
    // need to reexamine this
    RubyThread currentThread = getRuntime().getCurrentContext().getThread();

    // If the killee thread is the same as the killer thread, just die
    if (currentThread == this) throwThreadKill();

    debug(this, "trying to kill");

    currentThread.pollThreadEvents();

    getRuntime()
        .getThreadService()
        .deliverEvent(new ThreadService.Event(currentThread, this, ThreadService.Event.Type.KILL));

    debug(this, "succeeded with kill");

    return this;
  }

  private static void debug(RubyThread thread, String message) {
    if (DEBUG) LOG.debug(Thread.currentThread() + "(" + thread.status + "): " + message);
  }

  @JRubyMethod(
      name = {"kill!", "exit!", "terminate!"},
      compat = RUBY1_8)
  public IRubyObject kill_bang() {
    throw getRuntime()
        .newNotImplementedError(
            "Thread#kill!, exit!, and terminate! are not safe and not supported");
  }

  @JRubyMethod(name = "safe_level")
  public IRubyObject safe_level() {
    throw getRuntime().newNotImplementedError("Thread-specific SAFE levels are not supported");
  }

  @JRubyMethod(compat = CompatVersion.RUBY1_9)
  public IRubyObject backtrace(ThreadContext context) {
    return getContext().createCallerBacktrace(context.runtime, 0);
  }

  public StackTraceElement[] javaBacktrace() {
    if (threadImpl instanceof NativeThread) {
      return ((NativeThread) threadImpl).getThread().getStackTrace();
    }

    // Future-based threads can't get a Java trace
    return new StackTraceElement[0];
  }

  private boolean isCurrent() {
    return threadImpl.isCurrent();
  }

  public void exceptionRaised(RaiseException exception) {
    assert isCurrent();

    RubyException rubyException = exception.getException();
    Ruby runtime = rubyException.getRuntime();
    if (runtime.getSystemExit().isInstance(rubyException)) {
      runtime
          .getThreadService()
          .getMainThread()
          .raise(new IRubyObject[] {rubyException}, Block.NULL_BLOCK);
    } else if (abortOnException(runtime)) {
      RubyException systemExit;

      if (!runtime.is1_9()) {
        runtime.printError(rubyException);

        systemExit = RubySystemExit.newInstance(runtime, 1);
        systemExit.message = rubyException.message;
        systemExit.set_backtrace(rubyException.backtrace());
      } else {
        systemExit = rubyException;
      }

      runtime
          .getThreadService()
          .getMainThread()
          .raise(new IRubyObject[] {systemExit}, Block.NULL_BLOCK);
      return;
    } else if (runtime.getDebug().isTrue()) {
      runtime.printError(exception.getException());
    }
    exitingException = exception;
  }

  /**
   * For handling all non-Ruby exceptions bubbling out of threads
   *
   * @param exception
   */
  @SuppressWarnings("deprecation")
  public void exceptionRaised(Throwable exception) {
    if (exception instanceof RaiseException) {
      exceptionRaised((RaiseException) exception);
      return;
    }

    assert isCurrent();

    Ruby runtime = getRuntime();
    if (abortOnException(runtime) && exception instanceof Error) {
      // re-propagate on main thread
      runtime.getThreadService().getMainThread().getNativeThread().stop(exception);
    } else {
      // just rethrow on this thread, let system handlers report it
      UnsafeFactory.getUnsafe().throwException(exception);
    }
  }

  private boolean abortOnException(Ruby runtime) {
    return (runtime.isGlobalAbortOnExceptionEnabled() || abortOnException);
  }

  public static RubyThread mainThread(IRubyObject receiver) {
    return receiver.getRuntime().getThreadService().getMainThread();
  }

  private volatile Selector currentSelector;

  @Deprecated
  public boolean selectForAccept(RubyIO io) {
    return select(io, SelectionKey.OP_ACCEPT);
  }

  private synchronized Selector getSelector(SelectableChannel channel) throws IOException {
    return SelectorFactory.openWithRetryFrom(getRuntime(), channel.provider());
  }

  public boolean select(RubyIO io, int ops) {
    return select(io.getChannel(), io, ops);
  }

  public boolean select(RubyIO io, int ops, long timeout) {
    return select(io.getChannel(), io, ops, timeout);
  }

  public boolean select(Channel channel, RubyIO io, int ops) {
    return select(channel, io, ops, -1);
  }

  public boolean select(Channel channel, RubyIO io, int ops, long timeout) {
    if (channel instanceof SelectableChannel) {
      SelectableChannel selectable = (SelectableChannel) channel;

      synchronized (selectable.blockingLock()) {
        boolean oldBlocking = selectable.isBlocking();

        SelectionKey key = null;
        try {
          selectable.configureBlocking(false);

          if (io != null) io.addBlockingThread(this);
          currentSelector = getRuntime().getSelectorPool().get(selectable.provider());

          key = selectable.register(currentSelector, ops);

          beforeBlockingCall();
          int result;
          if (timeout < 0) {
            result = currentSelector.select();
          } else if (timeout == 0) {
            result = currentSelector.selectNow();
          } else {
            result = currentSelector.select(timeout);
          }

          // check for thread events, in case we've been woken up to die
          pollThreadEvents();

          if (result == 1) {
            Set<SelectionKey> keySet = currentSelector.selectedKeys();

            if (keySet.iterator().next() == key) {
              return true;
            }
          }

          return false;
        } catch (IOException ioe) {
          throw getRuntime().newIOErrorFromException(ioe);
        } finally {
          // Note: I don't like ignoring these exceptions, but it's
          // unclear how likely they are to happen or what damage we
          // might do by ignoring them. Note that the pieces are separate
          // so that we can ensure one failing does not affect the others
          // running.

          // clean up the key in the selector
          try {
            if (key != null) key.cancel();
            if (currentSelector != null) currentSelector.selectNow();
          } catch (Exception e) {
            // ignore
          }

          // shut down and null out the selector
          try {
            if (currentSelector != null) {
              getRuntime().getSelectorPool().put(currentSelector);
            }
          } catch (Exception e) {
            // ignore
          } finally {
            currentSelector = null;
          }

          // remove this thread as a blocker against the given IO
          if (io != null) io.removeBlockingThread(this);

          // go back to previous blocking state on the selectable
          try {
            selectable.configureBlocking(oldBlocking);
          } catch (Exception e) {
            // ignore
          }

          // clear thread state from blocking call
          afterBlockingCall();
        }
      }
    } else {
      // can't select, just have to do a blocking call
      return true;
    }
  }

  public void interrupt() {
    Selector activeSelector = currentSelector;
    if (activeSelector != null) {
      activeSelector.wakeup();
    }
    BlockingIO.Condition iowait = blockingIO;
    if (iowait != null) {
      iowait.cancel();
    }

    BlockingTask task = currentBlockingTask;
    if (task != null) {
      task.wakeup();
    }
  }

  private volatile BlockingIO.Condition blockingIO = null;

  public boolean waitForIO(ThreadContext context, RubyIO io, int ops) {
    Channel channel = io.getChannel();

    if (!(channel instanceof SelectableChannel)) {
      return true;
    }
    try {
      io.addBlockingThread(this);
      blockingIO = BlockingIO.newCondition(channel, ops);
      boolean ready = blockingIO.await();

      // check for thread events, in case we've been woken up to die
      pollThreadEvents();
      return ready;
    } catch (IOException ioe) {
      throw context.runtime.newRuntimeError("Error with selector: " + ioe);
    } catch (InterruptedException ex) {
      // FIXME: not correct exception
      throw context.runtime.newRuntimeError("Interrupted");
    } finally {
      blockingIO = null;
      io.removeBlockingThread(this);
    }
  }

  public void beforeBlockingCall() {
    pollThreadEvents();
    enterSleep();
  }

  public void afterBlockingCall() {
    exitSleep();
    pollThreadEvents();
  }

  private void receivedAnException(ThreadContext context, IRubyObject exception) {
    RubyModule kernelModule = getRuntime().getKernel();
    debug(this, "before propagating exception");
    kernelModule.callMethod(context, "raise", exception);
  }

  public boolean wait_timeout(IRubyObject o, Double timeout) throws InterruptedException {
    if (timeout != null) {
      long delay_ns = (long) (timeout.doubleValue() * 1000000000.0);
      long start_ns = System.nanoTime();
      if (delay_ns > 0) {
        long delay_ms = delay_ns / 1000000;
        int delay_ns_remainder = (int) (delay_ns % 1000000);
        executeBlockingTask(new SleepTask(o, delay_ms, delay_ns_remainder));
      }
      long end_ns = System.nanoTime();
      return (end_ns - start_ns) <= delay_ns;
    } else {
      executeBlockingTask(new SleepTask(o, 0, 0));
      return true;
    }
  }

  @Override
  public boolean equals(Object obj) {
    if (obj == null) {
      return false;
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    final RubyThread other = (RubyThread) obj;
    if (this.threadImpl != other.threadImpl
        && (this.threadImpl == null || !this.threadImpl.equals(other.threadImpl))) {
      return false;
    }
    return true;
  }

  @Override
  public int hashCode() {
    int hash = 3;
    hash = 97 * hash + (this.threadImpl != null ? this.threadImpl.hashCode() : 0);
    return hash;
  }

  public String toString() {
    return threadImpl.toString();
  }

  /**
   * Acquire the given lock, holding a reference to it for cleanup on thread termination.
   *
   * @param lock the lock to acquire, released on thread termination
   */
  public void lock(Lock lock) {
    assert Thread.currentThread() == getNativeThread();
    lock.lock();
    heldLocks.add(lock);
  }

  /**
   * Acquire the given lock interruptibly, holding a reference to it for cleanup on thread
   * termination.
   *
   * @param lock the lock to acquire, released on thread termination
   * @throws InterruptedException if the lock acquisition is interrupted
   */
  public void lockInterruptibly(Lock lock) throws InterruptedException {
    assert Thread.currentThread() == getNativeThread();
    lock.lockInterruptibly();
    heldLocks.add(lock);
  }

  /**
   * Try to acquire the given lock, adding it to a list of held locks for cleanup on thread
   * termination if it is acquired. Return immediately if the lock cannot be acquired.
   *
   * @param lock the lock to acquire, released on thread termination
   */
  public boolean tryLock(Lock lock) {
    assert Thread.currentThread() == getNativeThread();
    boolean locked = lock.tryLock();
    if (locked) {
      heldLocks.add(lock);
    }
    return locked;
  }

  /**
   * Release the given lock and remove it from the list of locks to be released on thread
   * termination.
   *
   * @param lock the lock to release and dereferences
   */
  public void unlock(Lock lock) {
    assert Thread.currentThread() == getNativeThread();
    lock.unlock();
    heldLocks.remove(lock);
  }

  /** Release all locks held. */
  public void unlockAll() {
    assert Thread.currentThread() == getNativeThread();
    for (Lock lock : heldLocks) {
      lock.unlock();
    }
  }

  private String identityString() {
    return "0x" + Integer.toHexString(System.identityHashCode(this));
  }
}
Ejemplo n.º 7
0
public class InterpretedIRBlockBody extends IRBlockBody implements Compilable<InterpreterContext> {
  private static final Logger LOG = LoggerFactory.getLogger("InterpretedIRBlockBody");
  protected boolean pushScope;
  protected boolean reuseParentScope;
  private boolean displayedCFG = false; // FIXME: Remove when we find nicer way of logging CFG
  private int callCount = 0;
  private InterpreterContext interpreterContext;
  private InterpreterContext fullInterpreterContext;

  public InterpretedIRBlockBody(IRClosure closure, Signature signature) {
    super(closure, signature);
    this.pushScope = true;
    this.reuseParentScope = false;

    // JIT currently JITs blocks along with their method and no on-demand by themselves.  We only
    // promote to full build here if we are -X-C.
    if (closure.getManager().getInstanceConfig().getCompileMode().shouldJIT()
        || Options.JIT_THRESHOLD.load() == -1) {
      callCount = -1;
    }
  }

  @Override
  public void setCallCount(int callCount) {
    this.callCount = callCount;
  }

  @Override
  public void completeBuild(InterpreterContext interpreterContext) {
    this.fullInterpreterContext = interpreterContext;
    // This enables IR & CFG to be dumped in debug mode
    // when this updated code starts executing.
    this.displayedCFG = false;
  }

  @Override
  public IRScope getIRScope() {
    return closure;
  }

  @Override
  public ArgumentDescriptor[] getArgumentDescriptors() {
    return closure.getArgumentDescriptors();
  }

  public InterpreterContext ensureInstrsReady() {
    if (IRRuntimeHelpers.isDebug() && !displayedCFG) {
      LOG.info(
          "Executing '"
              + closure
              + "' (pushScope="
              + pushScope
              + ", reuseParentScope="
              + reuseParentScope);
      LOG.info(closure.debugOutput());
      displayedCFG = true;
    }

    if (interpreterContext == null) {
      if (Options.IR_PRINT.load()) {
        ByteArrayOutputStream baos = IRDumper.printIR(closure, false);

        LOG.info(
            "Printing simple IR for " + closure.getName(), "\n" + new String(baos.toByteArray()));
      }

      interpreterContext = closure.getInterpreterContext();
      fullInterpreterContext = interpreterContext;
    }
    return interpreterContext;
  }

  @Override
  public String getClassName(ThreadContext context) {
    return null;
  }

  @Override
  public String getName() {
    return null;
  }

  @Override
  public boolean canCallDirect() {
    return interpreterContext != null && interpreterContext.hasExplicitCallProtocol();
  }

  @Override
  protected IRubyObject callDirect(
      ThreadContext context, Block block, IRubyObject[] args, Block blockArg) {
    context.setCurrentBlockType(Block.Type.PROC);
    InterpreterContext ic = ensureInstrsReady(); // so we get debugging output
    return Interpreter.INTERPRET_BLOCK(
        context, block, null, ic, args, block.getBinding().getMethod(), blockArg);
  }

  @Override
  protected IRubyObject yieldDirect(
      ThreadContext context, Block block, IRubyObject[] args, IRubyObject self) {
    context.setCurrentBlockType(Block.Type.NORMAL);
    InterpreterContext ic = ensureInstrsReady(); // so we get debugging output
    return Interpreter.INTERPRET_BLOCK(
        context, block, self, ic, args, block.getBinding().getMethod(), Block.NULL_BLOCK);
  }

  @Override
  protected IRubyObject commonYieldPath(
      ThreadContext context,
      Block block,
      Block.Type type,
      IRubyObject[] args,
      IRubyObject self,
      Block blockArg) {
    if (callCount >= 0) promoteToFullBuild(context);

    InterpreterContext ic = ensureInstrsReady();

    // Update interpreter context for next time this block is executed
    // This ensures that if we had determined canCallDirect() is false
    // based on the old IC, we continue to execute with it.
    interpreterContext = fullInterpreterContext;

    Binding binding = block.getBinding();
    Visibility oldVis = binding.getFrame().getVisibility();
    Frame prevFrame = context.preYieldNoScope(binding);

    // SSS FIXME: Maybe, we should allocate a NoVarsScope/DummyScope for for-loop bodies because the
    // static-scope here
    // probably points to the parent scope? To be verified and fixed if necessary. There is no harm
    // as it is now. It
    // is just wasteful allocation since the scope is not used at all.
    DynamicScope actualScope = binding.getDynamicScope();
    if (ic.pushNewDynScope()) {
      context.pushScope(block.allocScope(actualScope));
    } else if (ic.reuseParentDynScope()) {
      // Reuse! We can avoid the push only if surrounding vars aren't referenced!
      context.pushScope(actualScope);
    }

    self = IRRuntimeHelpers.updateBlockState(block, self);

    try {
      return Interpreter.INTERPRET_BLOCK(
          context, block, self, ic, args, binding.getMethod(), blockArg);
    } finally {
      // IMPORTANT: Do not clear eval-type in case this is reused in bindings!
      // Ex: eval("...", foo.instance_eval { binding })
      // The dyn-scope used for binding needs to have its eval-type set to INSTANCE_EVAL
      binding.getFrame().setVisibility(oldVis);
      if (ic.popDynScope()) {
        context.postYield(binding, prevFrame);
      } else {
        context.postYieldNoScope(prevFrame);
      }
    }
  }

  // Unlike JIT in MixedMode this will always successfully build but if using executor pool it may
  // take a while
  // and replace interpreterContext asynchronously.
  protected void promoteToFullBuild(ThreadContext context) {
    if (context.runtime.isBooting()) return; // don't Promote to full build during runtime boot

    if (callCount++ >= Options.JIT_THRESHOLD.load())
      context.runtime.getJITCompiler().buildThresholdReached(context, this);
  }

  public RubyModule getImplementationClass() {
    return null;
  }
}
Ejemplo n.º 8
0
public class Interpreter {
  private static class IRCallSite {
    IRScope s;
    int v; // scope version
    CallBase call;
    long count;
    InterpretedIRMethod tgtM;

    public IRCallSite() {}

    public IRCallSite(IRCallSite cs) {
      this.s = cs.s;
      this.v = cs.v;
      this.call = cs.call;
      this.count = 0;
    }

    public int hashCode() {
      return (int) this.call.callSiteId;
    }
  }

  private static class CallSiteProfile {
    IRCallSite cs;
    HashMap<IRScope, Counter> counters;

    public CallSiteProfile(IRCallSite cs) {
      this.cs = new IRCallSite(cs);
      this.counters = new HashMap<IRScope, Counter>();
    }
  }

  private static IRCallSite callerSite = new IRCallSite();

  private static final Logger LOG = LoggerFactory.getLogger("Interpreter");

  private static int versionCount = 1;
  private static HashMap<IRScope, Integer> scopeVersionMap = new HashMap<IRScope, Integer>();

  private static int inlineCount = 0;
  private static int interpInstrsCount = 0;
  private static int codeModificationsCount = 0;
  private static int numCyclesWithNoModifications = 0;
  private static int globalThreadPollCount = 0;
  private static HashMap<IRScope, Counter> scopeThreadPollCounts = new HashMap<IRScope, Counter>();
  private static HashMap<Long, CallSiteProfile> callProfile = new HashMap<Long, CallSiteProfile>();
  private static HashMap<Operation, Counter> opStats = new HashMap<Operation, Counter>();

  private static IRScope getEvalContainerScope(Ruby runtime, StaticScope evalScope) {
    // SSS FIXME: Weirdness here.  We cannot get the containing IR scope from evalScope because of
    // static-scope wrapping
    // that is going on
    // 1. In all cases, DynamicScope.getEvalScope wraps the executing static scope in a new local
    // scope.
    // 2. For instance-eval (module-eval, class-eval) scenarios, there is an extra scope that is
    // added to
    //    the stack in ThreadContext.java:preExecuteUnder
    // I dont know what rule to apply when.  However, in both these cases, since there is no
    // IR-scope associated,
    // I have used the hack below where I first unwrap once and see if I get a non-null IR scope.
    // If that doesn't
    // work, I unwarp once more and I am guaranteed to get the IR scope I want.
    IRScope containingIRScope = ((IRStaticScope) evalScope.getEnclosingScope()).getIRScope();
    if (containingIRScope == null)
      containingIRScope =
          ((IRStaticScope) evalScope.getEnclosingScope().getEnclosingScope()).getIRScope();
    return containingIRScope;
  }

  public static IRubyObject interpretCommonEval(
      Ruby runtime,
      String file,
      int lineNumber,
      String backtraceName,
      RootNode rootNode,
      IRubyObject self,
      Block block) {
    // SSS FIXME: Is this required here since the IR version cannot change from eval-to-eval? This
    // is much more of a global setting.
    boolean is_1_9 = runtime.is1_9();
    if (is_1_9) IRBuilder.setRubyVersion("1.9");

    StaticScope ss = rootNode.getStaticScope();
    IRScope containingIRScope = getEvalContainerScope(runtime, ss);
    IREvalScript evalScript =
        IRBuilder.createIRBuilder(runtime, runtime.getIRManager())
            .buildEvalRoot(ss, containingIRScope, file, lineNumber, rootNode);
    evalScript.prepareForInterpretation(false);
    //        evalScript.runCompilerPass(new CallSplitter());
    ThreadContext context = runtime.getCurrentContext();
    runBeginEndBlocks(
        evalScript.getBeginBlocks(), context, self, null); // FIXME: No temp vars yet right?
    IRubyObject rv =
        evalScript.call(
            context,
            self,
            evalScript.getStaticScope().getModule(),
            rootNode.getScope(),
            block,
            backtraceName);
    runBeginEndBlocks(evalScript.getEndBlocks(), context, self, null); // FIXME: No temp vars right?
    return rv;
  }

  public static IRubyObject interpretSimpleEval(
      Ruby runtime,
      String file,
      int lineNumber,
      String backtraceName,
      Node node,
      IRubyObject self) {
    return interpretCommonEval(
        runtime, file, lineNumber, backtraceName, (RootNode) node, self, Block.NULL_BLOCK);
  }

  public static IRubyObject interpretBindingEval(
      Ruby runtime,
      String file,
      int lineNumber,
      String backtraceName,
      Node node,
      IRubyObject self,
      Block block) {
    return interpretCommonEval(
        runtime, file, lineNumber, backtraceName, (RootNode) node, self, block);
  }

  public static void runBeginEndBlocks(
      List<IRClosure> beBlocks, ThreadContext context, IRubyObject self, Object[] temp) {
    if (beBlocks == null) return;

    for (IRClosure b : beBlocks) {
      // SSS FIXME: Should I piggyback on WrappedIRClosure.retrieve or just copy that code here?
      b.prepareForInterpretation(false);
      Block blk =
          (Block)
              (new WrappedIRClosure(b)).retrieve(context, self, context.getCurrentScope(), temp);
      blk.yield(context, null);
    }
  }

  public static IRubyObject interpret(Ruby runtime, Node rootNode, IRubyObject self) {
    if (runtime.is1_9()) IRBuilder.setRubyVersion("1.9");

    IRScriptBody root =
        (IRScriptBody)
            IRBuilder.createIRBuilder(runtime, runtime.getIRManager())
                .buildRoot((RootNode) rootNode);

    // We get the live object ball rolling here.  This give a valid value for the top
    // of this lexical tree.  All new scope can then retrieve and set based on lexical parent.
    if (root.getStaticScope().getModule() == null) { // If an eval this may already be setup.
      root.getStaticScope().setModule(runtime.getObject());
    }

    RubyModule currModule = root.getStaticScope().getModule();

    // Scope state for root?
    IRStaticScopeFactory.newIRLocalScope(null).setModule(currModule);
    ThreadContext context = runtime.getCurrentContext();

    try {
      runBeginEndBlocks(
          root.getBeginBlocks(), context, self, null); // FIXME: No temp vars yet...not needed?
      InterpretedIRMethod method = new InterpretedIRMethod(root, currModule);
      IRubyObject rv = method.call(context, self, currModule, "(root)", IRubyObject.NULL_ARRAY);
      runBeginEndBlocks(
          root.getEndBlocks(), context, self, null); // FIXME: No temp vars yet...not needed?
      if ((IRRuntimeHelpers.isDebug() || IRRuntimeHelpers.inProfileMode())
          && interpInstrsCount > 10000) {
        LOG.info("-- Interpreted instructions: {}", interpInstrsCount);
        /*
        for (Operation o: opStats.keySet()) {
            System.out.println(o + " = " + opStats.get(o).count);
        }
        */
      }
      return rv;
    } catch (IRBreakJump bj) {
      throw IRException.BREAK_LocalJumpError.getException(context.runtime);
    }
  }

  private static void analyzeProfile() {
    versionCount++;

    // if (inlineCount == 2) return;

    if (codeModificationsCount == 0) numCyclesWithNoModifications++;
    else numCyclesWithNoModifications = 0;

    codeModificationsCount = 0;

    if (numCyclesWithNoModifications < 3) return;

    // We are now good to go -- start analyzing the profile

    // System.out.println("-------------------start analysis-----------------------");

    final HashMap<IRScope, Long> scopeCounts = new HashMap<IRScope, Long>();
    final ArrayList<IRCallSite> callSites = new ArrayList<IRCallSite>();
    HashMap<IRCallSite, Long> callSiteCounts = new HashMap<IRCallSite, Long>();
    // System.out.println("# call sites: " + callProfile.keySet().size());
    long total = 0;
    for (Long id : callProfile.keySet()) {
      Long c;

      CallSiteProfile csp = callProfile.get(id);
      IRCallSite cs = csp.cs;

      if (cs.v != scopeVersionMap.get(cs.s).intValue()) {
        // System.out.println("Skipping callsite: <" + cs.s + "," + cs.v + "> with compiled version:
        // " + scopeVersionMap.get(cs.s));
        continue;
      }

      Set<IRScope> calledScopes = csp.counters.keySet();
      cs.count = 0;
      for (IRScope s : calledScopes) {
        c = scopeCounts.get(s);
        if (c == null) {
          c = new Long(0);
          scopeCounts.put(s, c);
        }

        long x = csp.counters.get(s).count;
        c += x;
        cs.count += x;
      }

      CallBase call = cs.call;
      if (calledScopes.size() == 1 && !call.inliningBlocked()) {
        CallSite runtimeCS = call.getCallSite();
        if (runtimeCS != null && (runtimeCS instanceof CachingCallSite)) {
          CachingCallSite ccs = (CachingCallSite) runtimeCS;
          CacheEntry ce = ccs.getCache();

          if (!(ce.method instanceof InterpretedIRMethod)) {
            // System.out.println("NOT IR-M!");
            continue;
          } else {
            callSites.add(cs);
            cs.tgtM = (InterpretedIRMethod) ce.method;
          }
        }
      }

      total += cs.count;
    }

    Collections.sort(
        callSites,
        new java.util.Comparator<IRCallSite>() {
          @Override
          public int compare(IRCallSite a, IRCallSite b) {
            if (a.count == b.count) return 0;
            return (a.count < b.count) ? 1 : -1;
          }
        });

    // Find top N call sites
    double freq = 0.0;
    int i = 0;
    boolean noInlining = true;
    Set<IRScope> inlinedScopes = new HashSet<IRScope>();
    for (IRCallSite ircs : callSites) {
      double contrib = (ircs.count * 100.0) / total;

      // 1% is arbitrary
      if (contrib < 1.0) break;

      i++;
      freq += contrib;

      // This check is arbitrary
      if (i == 100 || freq > 99.0) break;

      // System.out.println("Considering: " + ircs.call + " with id: " + ircs.call.callSiteId +
      // " in scope " + ircs.s + " with count " + ircs.count + "; contrib " + contrib + "; freq: " +
      // freq);

      // Now inline here!
      CallBase call = ircs.call;

      IRScope hs = ircs.s;
      boolean isHotClosure = hs instanceof IRClosure;
      IRScope hc = isHotClosure ? hs : null;
      hs = isHotClosure ? hs.getLexicalParent() : hs;

      IRScope tgtMethod = ircs.tgtM.getIRMethod();

      Instr[] instrs = tgtMethod.getInstrsForInterpretation();
      // Dont inline large methods -- 500 is arbitrary
      // Can be null if a previously inlined method hasn't been rebuilt
      if ((instrs == null) || instrs.length > 500) {
        // if (instrs == null) System.out.println("no instrs!");
        // else System.out.println("large method with " + instrs.length + " instrs. skipping!");
        continue;
      }

      RubyModule implClass = ircs.tgtM.getImplementationClass();
      int classToken = implClass.getGeneration();
      String n = tgtMethod.getName();
      boolean inlineCall = true;
      if (isHotClosure) {
        Operand clArg = call.getClosureArg(null);
        inlineCall =
            (clArg instanceof WrappedIRClosure) && (((WrappedIRClosure) clArg).getClosure() == hc);
      }

      if (inlineCall) {
        noInlining = false;
        long start = new java.util.Date().getTime();
        hs.inlineMethod(tgtMethod, implClass, classToken, null, call);
        inlinedScopes.add(hs);
        long end = new java.util.Date().getTime();
        // System.out.println("Inlined " + tgtMethod + " in " + hs +
        //     " @ instr " + call + " in time (ms): "
        //     + (end-start) + " # instrs: " + instrs.length);

        inlineCount++;
      } else {
        // System.out.println("--no inlining--");
      }
    }

    for (IRScope x : inlinedScopes) {
      // update version count for 'hs'
      scopeVersionMap.put(x, versionCount);
      // System.out.println("Updating version of " + x + " to " + versionCount);
      // System.out.println("--- pre-inline-instrs ---");
      // System.out.println(x.getCFG().toStringInstrs());
      // System.out.println("--- post-inline-instrs ---");
      // System.out.println(x.getCFG().toStringInstrs());
    }

    // reset
    codeModificationsCount = 0;
    callProfile = new HashMap<Long, CallSiteProfile>();

    // Every 1M thread polls, discard stats by reallocating the thread-poll count map
    if (globalThreadPollCount % 1000000 == 0) {
      globalThreadPollCount = 0;
    }
  }

  private static void outputProfileStats() {
    ArrayList<IRScope> scopes = new ArrayList<IRScope>(scopeThreadPollCounts.keySet());
    Collections.sort(
        scopes,
        new java.util.Comparator<IRScope>() {
          @Override
          public int compare(IRScope a, IRScope b) {
            // In non-methods and non-closures, we may not have any thread poll instrs.
            int aden = a.getThreadPollInstrsCount();
            if (aden == 0) aden = 1;
            int bden = b.getThreadPollInstrsCount();
            if (bden == 0) bden = 1;

            // Use estimated instr count to order scopes -- rather than raw thread-poll count
            float aCount =
                scopeThreadPollCounts.get(a).count
                    * (1.0f * a.getInstrsForInterpretation().length / aden);
            float bCount =
                scopeThreadPollCounts.get(b).count
                    * (1.0f * b.getInstrsForInterpretation().length / bden);
            if (aCount == bCount) return 0;
            return (aCount < bCount) ? 1 : -1;
          }
        });

    /*
    LOG.info("------------------------");
    LOG.info("Stats after " + globalThreadPollCount + " thread polls:");
    LOG.info("------------------------");
    LOG.info("# instructions: " + interpInstrsCount);
    LOG.info("# code modifications in this period : " + codeModificationsCount);
    LOG.info("------------------------");
    */
    int i = 0;
    float f1 = 0.0f;
    for (IRScope s : scopes) {
      long n = scopeThreadPollCounts.get(s).count;
      float p1 = ((n * 1000) / globalThreadPollCount) / 10.0f;
      String msg =
          i
              + ". "
              + s
              + " [file:"
              + s.getFileName()
              + ":"
              + s.getLineNumber()
              + "] = "
              + n
              + "; ("
              + p1
              + "%)";
      if (s instanceof IRClosure) {
        IRMethod m = s.getNearestMethod();
        // if (m != null) LOG.info(msg + " -- nearest enclosing method: " + m);
        // else LOG.info(msg + " -- no enclosing method --");
      } else {
        // LOG.info(msg);
      }

      i++;
      f1 += p1;

      // Top 20 or those that account for 95% of thread poll events.
      if (i == 20 || f1 >= 95.0) break;
    }

    // reset code modification counter
    codeModificationsCount = 0;

    // Every 1M thread polls, discard stats by reallocating the thread-poll count map
    if (globalThreadPollCount % 1000000 == 0) {
      // System.out.println("---- resetting thread-poll counters ----");
      scopeThreadPollCounts = new HashMap<IRScope, Counter>();
      globalThreadPollCount = 0;
    }
  }

  private static Integer initProfiling(IRScope scope) {
    /* SSS: Not being used currently
    tpCount = scopeThreadPollCounts.get(scope);
    if (tpCount == null) {
        tpCount = new Counter();
        scopeThreadPollCounts.put(scope, tpCount);
    }
    */

    Integer scopeVersion = scopeVersionMap.get(scope);
    if (scopeVersion == null) {
      scopeVersionMap.put(scope, versionCount);
      scopeVersion = new Integer(versionCount);
    }

    if (callerSite.call != null) {
      Long id = callerSite.call.callSiteId;
      CallSiteProfile csp = callProfile.get(id);
      if (csp == null) {
        csp = new CallSiteProfile(callerSite);
        callProfile.put(id, csp);
      }

      Counter csCount = csp.counters.get(scope);
      if (csCount == null) {
        csCount = new Counter();
        csp.counters.put(scope, csCount);
      }
      csCount.count++;
    }

    return scopeVersion;
  }

  private static void setResult(
      Object[] temp, DynamicScope currDynScope, Variable resultVar, Object result) {
    if (resultVar instanceof TemporaryVariable) {
      temp[((TemporaryVariable) resultVar).offset] = result;
    } else {
      LocalVariable lv = (LocalVariable) resultVar;
      currDynScope.setValue((IRubyObject) result, lv.getLocation(), lv.getScopeDepth());
    }
  }

  private static void setResult(
      Object[] temp, DynamicScope currDynScope, Instr instr, Object result) {
    if (instr instanceof ResultInstr) {
      setResult(temp, currDynScope, ((ResultInstr) instr).getResult(), result);
    }
  }

  private static Object retrieveOp(
      Operand r,
      ThreadContext context,
      IRubyObject self,
      DynamicScope currDynScope,
      Object[] temp) {
    Object res;
    if (r instanceof Self) {
      return self;
    } else if (r instanceof TemporaryVariable) {
      res = temp[((TemporaryVariable) r).offset];
      return res == null ? context.nil : res;
    } else if (r instanceof LocalVariable) {
      LocalVariable lv = (LocalVariable) r;
      res = currDynScope.getValue(lv.getLocation(), lv.getScopeDepth());
      return res == null ? context.nil : res;
    } else {
      return r.retrieve(context, self, currDynScope, temp);
    }
  }

  private static void updateCallSite(Instr instr, IRScope scope, Integer scopeVersion) {
    if (instr instanceof CallBase) {
      callerSite.s = scope;
      callerSite.v = scopeVersion;
      callerSite.call = (CallBase) instr;
    }
  }

  private static void receiveArg(
      ThreadContext context,
      Instr i,
      Operation operation,
      IRubyObject[] args,
      int kwArgHashCount,
      DynamicScope currDynScope,
      Object[] temp,
      Object exception,
      Block block) {
    Object result = null;
    ResultInstr instr = (ResultInstr) i;
    switch (operation) {
      case RECV_PRE_REQD_ARG:
        int argIndex = ((ReceivePreReqdArgInstr) instr).getArgIndex();
        result =
            ((argIndex + kwArgHashCount) < args.length)
                ? args[argIndex]
                : context.nil; // SSS FIXME: This check is only required for closures, not methods
        break;
      case RECV_CLOSURE:
        result =
            (block == Block.NULL_BLOCK)
                ? context.nil
                : context.runtime.newProc(Block.Type.PROC, block);
        break;
      case RECV_OPT_ARG:
        result = ((ReceiveOptArgInstr) instr).receiveOptArg(args, kwArgHashCount);
        break;
      case RECV_POST_REQD_ARG:
        result = ((ReceivePostReqdArgInstr) instr).receivePostReqdArg(args, kwArgHashCount);
        // For blocks, missing arg translates to nil
        result = result == null ? context.nil : result;
        break;
      case RECV_REST_ARG:
        result =
            ((ReceiveRestArgInstr) instr).receiveRestArg(context.runtime, args, kwArgHashCount);
        break;
      case RECV_KW_ARG:
        result = ((ReceiveKeywordArgInstr) instr).receiveKWArg(context, kwArgHashCount, args);
        break;
      case RECV_KW_REST_ARG:
        result = ((ReceiveKeywordRestArgInstr) instr).receiveKWArg(context, kwArgHashCount, args);
        break;
      case RECV_EXCEPTION:
        {
          ReceiveExceptionInstr rei = (ReceiveExceptionInstr) instr;
          result =
              (exception instanceof RaiseException && rei.checkType)
                  ? ((RaiseException) exception).getException()
                  : exception;
          break;
        }
    }

    setResult(temp, currDynScope, instr.getResult(), result);
  }

  private static void processCall(
      ThreadContext context,
      Instr instr,
      Operation operation,
      IRScope scope,
      DynamicScope currDynScope,
      Object[] temp,
      IRubyObject self,
      Block block,
      Block.Type blockType) {
    Object result = null;
    switch (operation) {
      case RUNTIME_HELPER:
        {
          RuntimeHelperCall rhc = (RuntimeHelperCall) instr;
          result = rhc.callHelper(context, currDynScope, self, temp, scope, blockType);
          setResult(temp, currDynScope, rhc.getResult(), result);
          break;
        }
      case CALL_1F:
        {
          OneFixnumArgNoBlockCallInstr call = (OneFixnumArgNoBlockCallInstr) instr;
          IRubyObject r =
              (IRubyObject) retrieveOp(call.getReceiver(), context, self, currDynScope, temp);
          result = call.getCallSite().call(context, self, r, call.getFixnumArg());
          setResult(temp, currDynScope, call.getResult(), result);
          break;
        }
      case CALL_1O:
        {
          OneOperandArgNoBlockCallInstr call = (OneOperandArgNoBlockCallInstr) instr;
          IRubyObject r =
              (IRubyObject) retrieveOp(call.getReceiver(), context, self, currDynScope, temp);
          IRubyObject o = (IRubyObject) call.getArg1().retrieve(context, self, currDynScope, temp);
          result = call.getCallSite().call(context, self, r, o);
          setResult(temp, currDynScope, call.getResult(), result);
          break;
        }
      case CALL_0O:
        {
          ZeroOperandArgNoBlockCallInstr call = (ZeroOperandArgNoBlockCallInstr) instr;
          IRubyObject r =
              (IRubyObject) retrieveOp(call.getReceiver(), context, self, currDynScope, temp);
          result = call.getCallSite().call(context, self, r);
          setResult(temp, currDynScope, call.getResult(), result);
          break;
        }
      case NORESULT_CALL_1O:
        {
          OneOperandArgNoBlockNoResultCallInstr call =
              (OneOperandArgNoBlockNoResultCallInstr) instr;
          IRubyObject r =
              (IRubyObject) retrieveOp(call.getReceiver(), context, self, currDynScope, temp);
          IRubyObject o = (IRubyObject) call.getArg1().retrieve(context, self, currDynScope, temp);
          call.getCallSite().call(context, self, r, o);
          break;
        }
      case NORESULT_CALL:
        instr.interpret(context, currDynScope, self, temp, block);
        break;
      case CALL:
      default:
        result = instr.interpret(context, currDynScope, self, temp, block);
        setResult(temp, currDynScope, instr, result);
        break;
    }
  }

  private static IRubyObject interpret(
      ThreadContext context,
      IRubyObject self,
      IRScope scope,
      Visibility visibility,
      RubyModule implClass,
      IRubyObject[] args,
      Block block,
      Block.Type blockType) {
    Instr[] instrs = scope.getInstrsForInterpretation();

    // The base IR may not have been processed yet
    if (instrs == null) instrs = scope.prepareForInterpretation(blockType == Block.Type.LAMBDA);

    int numTempVars = scope.getTemporaryVariableSize();
    Object[] temp = numTempVars > 0 ? new Object[numTempVars] : null;
    int n = instrs.length;
    int ipc = 0;
    Instr instr = null;
    Object exception = null;
    int kwArgHashCount =
        (scope.receivesKeywordArgs() && args[args.length - 1] instanceof RubyHash) ? 1 : 0;
    DynamicScope currDynScope = context.getCurrentScope();

    // Counter tpCount = null;

    // Init profiling this scope
    boolean debug = IRRuntimeHelpers.isDebug();
    boolean profile = IRRuntimeHelpers.inProfileMode();
    Integer scopeVersion = profile ? initProfiling(scope) : 0;

    // Enter the looooop!
    while (ipc < n) {
      instr = instrs[ipc];
      ipc++;
      Operation operation = instr.getOperation();
      if (debug) {
        LOG.info("I: {}", instr);
        interpInstrsCount++;
      } else if (profile) {
        if (operation.modifiesCode()) codeModificationsCount++;
        interpInstrsCount++;
        /*
        Counter cnt = opStats.get(operation);
        if (cnt == null) {
            cnt = new Counter();
            opStats.put(operation, cnt);
        }
        cnt.count++;
        */
      }

      try {
        switch (operation.opClass) {
          case ARG_OP:
            {
              receiveArg(
                  context,
                  instr,
                  operation,
                  args,
                  kwArgHashCount,
                  currDynScope,
                  temp,
                  exception,
                  block);
              break;
            }
          case BRANCH_OP:
            {
              if (operation == Operation.JUMP) {
                ipc = ((JumpInstr) instr).getJumpTarget().getTargetPC();
              } else {
                ipc = instr.interpretAndGetNewIPC(context, currDynScope, self, temp, ipc);
              }
              break;
            }
          case CALL_OP:
            {
              if (profile) updateCallSite(instr, scope, scopeVersion);
              processCall(
                  context, instr, operation, scope, currDynScope, temp, self, block, blockType);
              break;
            }
          case BOOK_KEEPING_OP:
            {
              switch (operation) {
                case PUSH_FRAME:
                  {
                    context.preMethodFrameAndClass(
                        implClass, scope.getName(), self, block, scope.getStaticScope());
                    context.setCurrentVisibility(visibility);
                    break;
                  }
                case PUSH_BINDING:
                  {
                    // SSS NOTE: Method scopes only!
                    //
                    // Blocks are a headache -- so, these instrs. are only added to IRMethods.
                    // Blocks have more complicated logic for pushing a dynamic scope (see
                    // InterpretedIRBlockBody)
                    // Changed by DPR
                    currDynScope =
                        DynamicScope.newDynamicScope(
                            scope.getStaticScope(), context.getCurrentScope().getDepth());
                    context.pushScope(currDynScope);
                    break;
                  }
                case CHECK_ARITY:
                  ((CheckArityInstr) instr).checkArity(context.runtime, args.length);
                  break;
                case POP_FRAME:
                  context.popFrame();
                  context.popRubyClass();
                  break;
                case POP_BINDING:
                  context.popScope();
                  break;
                case THREAD_POLL:
                  if (profile) {
                    // SSS: Not being used currently
                    // tpCount.count++;
                    globalThreadPollCount++;

                    // 20K is arbitrary
                    // Every 20K profile counts, spit out profile stats
                    if (globalThreadPollCount % 20000 == 0) {
                      analyzeProfile();
                      // outputProfileStats();
                    }
                  }
                  context.callThreadPoll();
                  break;
                case LINE_NUM:
                  context.setLine(((LineNumberInstr) instr).lineNumber);
                  break;
                case RECORD_END_BLOCK:
                  ((RecordEndBlockInstr) instr).interpret();
                  break;
              }
              break;
            }
          case OTHER_OP:
            {
              Object result = null;
              switch (operation) {
                  // --------- Return flavored instructions --------
                case BREAK:
                  {
                    BreakInstr bi = (BreakInstr) instr;
                    IRubyObject rv =
                        (IRubyObject)
                            bi.getReturnValue().retrieve(context, self, currDynScope, temp);
                    // This also handles breaks in lambdas -- by converting them to a return
                    return IRRuntimeHelpers.initiateBreak(
                        context, scope, bi.getScopeToReturnTo().getScopeId(), rv, blockType);
                  }
                case RETURN:
                  {
                    return (IRubyObject)
                        retrieveOp(
                            ((ReturnBase) instr).getReturnValue(),
                            context,
                            self,
                            currDynScope,
                            temp);
                  }
                case NONLOCAL_RETURN:
                  {
                    NonlocalReturnInstr ri = (NonlocalReturnInstr) instr;
                    IRubyObject rv =
                        (IRubyObject)
                            retrieveOp(ri.getReturnValue(), context, self, currDynScope, temp);
                    ipc = n;
                    // If not in a lambda, check if this was a non-local return
                    if (!IRRuntimeHelpers.inLambda(blockType)) {
                      IRRuntimeHelpers.initiateNonLocalReturn(
                          context, scope, ri.methodToReturnFrom, rv);
                    }
                    return rv;
                  }

                  // ---------- Common instruction ---------
                case COPY:
                  {
                    CopyInstr c = (CopyInstr) instr;
                    result = retrieveOp(c.getSource(), context, self, currDynScope, temp);
                    setResult(temp, currDynScope, c.getResult(), result);
                    break;
                  }

                case GET_FIELD:
                  {
                    GetFieldInstr gfi = (GetFieldInstr) instr;
                    IRubyObject object =
                        (IRubyObject) gfi.getSource().retrieve(context, self, currDynScope, temp);
                    VariableAccessor a = gfi.getAccessor(object);
                    result = a == null ? null : (IRubyObject) a.get(object);
                    if (result == null) {
                      result = context.nil;
                    }
                    setResult(temp, currDynScope, gfi.getResult(), result);
                    break;
                  }

                case SEARCH_CONST:
                  {
                    SearchConstInstr sci = (SearchConstInstr) instr;
                    result = sci.getCachedConst();
                    if (!sci.isCached(context, result))
                      result = sci.cache(context, currDynScope, self, temp);
                    setResult(temp, currDynScope, sci.getResult(), result);
                    break;
                  }

                  // ---------- All the rest ---------
                default:
                  result = instr.interpret(context, currDynScope, self, temp, block);
                  setResult(temp, currDynScope, instr, result);
                  break;
              }

              break;
            }
        }
      } catch (Throwable t) {
        // Unrescuable:
        //    IRReturnJump, ThreadKill, RubyContinuation, MainExitException, etc.
        //    These cannot be rescued -- only run ensure blocks
        //
        // Others:
        //    IRBreakJump, Ruby exceptions, errors, and other java exceptions.
        //    These can be rescued -- run rescue blocks

        if (debug)
          LOG.info(
              "in scope: "
                  + scope
                  + ", caught Java throwable: "
                  + t
                  + "; excepting instr: "
                  + instr);
        ipc = (t instanceof Unrescuable) ? scope.getEnsurerPC(instr) : scope.getRescuerPC(instr);
        if (debug) LOG.info("ipc for rescuer/ensurer: " + ipc);

        if (ipc == -1) {
          Helpers.throwException((Throwable) t);
        } else {
          exception = t;
        }
      }
    }

    // Control should never get here!
    // SSS FIXME: But looks like BEGIN/END blocks get here -- needs fixing
    return null;
  }

  public static IRubyObject INTERPRET_EVAL(
      ThreadContext context,
      IRubyObject self,
      IRScope scope,
      RubyModule clazz,
      IRubyObject[] args,
      String name,
      Block block,
      Block.Type blockType) {
    try {
      ThreadContext.pushBacktrace(context, name, scope.getFileName(), context.getLine());
      return interpret(context, self, scope, null, clazz, args, block, blockType);
    } finally {
      ThreadContext.popBacktrace(context);
    }
  }

  public static IRubyObject INTERPRET_BLOCK(
      ThreadContext context,
      IRubyObject self,
      IRScope scope,
      IRubyObject[] args,
      String name,
      Block block,
      Block.Type blockType) {
    try {
      ThreadContext.pushBacktrace(context, name, scope.getFileName(), context.getLine());
      return interpret(context, self, scope, null, null, args, block, blockType);
    } finally {
      ThreadContext.popBacktrace(context);
    }
  }

  public static IRubyObject INTERPRET_METHOD(
      ThreadContext context,
      InterpretedIRMethod irMethod,
      IRubyObject self,
      String name,
      IRubyObject[] args,
      Block block,
      Block.Type blockType,
      boolean isTraceable) {
    Ruby runtime = context.runtime;
    IRScope scope = irMethod.getIRMethod();
    RubyModule implClass = irMethod.getImplementationClass();
    Visibility viz = irMethod.getVisibility();
    boolean syntheticMethod = name == null || name.equals("");

    try {
      if (!syntheticMethod)
        ThreadContext.pushBacktrace(context, name, scope.getFileName(), context.getLine());
      if (isTraceable) methodPreTrace(runtime, context, name, implClass);
      return interpret(context, self, scope, viz, implClass, args, block, blockType);
    } finally {
      if (isTraceable) {
        try {
          methodPostTrace(runtime, context, name, implClass);
        } finally {
          if (!syntheticMethod) ThreadContext.popBacktrace(context);
        }
      } else {
        if (!syntheticMethod) ThreadContext.popBacktrace(context);
      }
    }
  }

  private static void methodPreTrace(
      Ruby runtime, ThreadContext context, String name, RubyModule implClass) {
    if (runtime.hasEventHooks()) context.trace(RubyEvent.CALL, name, implClass);
  }

  private static void methodPostTrace(
      Ruby runtime, ThreadContext context, String name, RubyModule implClass) {
    if (runtime.hasEventHooks()) context.trace(RubyEvent.RETURN, name, implClass);
  }
}
Ejemplo n.º 9
0
// This class represents JVM as the target of compilation
// and outputs bytecode
public class JVM {
  private static final Logger LOG = LoggerFactory.getLogger("IRBuilder");

  Stack<ClassData> clsStack = new Stack();
  ClassWriter writer;

  public JVM() {}

  public static final int CMP_EQ = 0;

  public byte[] code() {
    return writer.toByteArray();
  }

  public ClassVisitor cls() {
    return clsData().cls;
  }

  public ClassData clsData() {
    return clsStack.peek();
  }

  public MethodData methodData() {
    return clsData().methodData();
  }

  public void pushclass(String clsName) {
    PrintWriter pw = new PrintWriter(System.out);
    clsStack.push(
        new ClassData(
            clsName, new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS)));
    pw.flush();
  }

  public void pushscript(String clsName, String filename) {
    PrintWriter pw = new PrintWriter(System.out);
    writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
    clsStack.push(new ClassData(clsName, writer));

    cls()
        .visit(
            RubyInstanceConfig.JAVA_VERSION,
            ACC_PUBLIC + ACC_SUPER,
            clsName,
            null,
            p(Object.class),
            null);
    cls().visitSource(filename, null);
    pw.flush();
  }

  public void popclass() {
    clsStack.pop();
  }

  public IRBytecodeAdapter method() {
    return clsData().method();
  }

  public void pushmethod(String name, int arity) {
    clsData().pushmethod(name, arity);
    method().startMethod();

    // locals for ThreadContext and self
    methodData().local("$context", JVM.THREADCONTEXT_TYPE);
    methodData().local("$scope", JVM.STATICSCOPE_TYPE);
    methodData().local("$self"); // , JVM.OBJECT_TYPE);
    for (int i = 0; i < arity; i++) {
      // incoming arguments
      methodData().local("$argument" + i);
    }
    methodData().local("$block", Type.getType(Block.class));

    // TODO: this should go into the PushBinding instruction
    methodData().local("$dynamicScope");
  }

  public void popmethod() {
    clsData().popmethod();
  }

  public static String scriptToClass(String name) {
    if (name.equals("-e")) {
      return "DashE";
    } else {
      return JavaNameMangler.mangledFilenameForStartupClasspath(name);
    }
  }

  public void declareField(String field) {
    if (!clsData().fieldSet.contains(field)) {
      cls().visitField(ACC_PROTECTED, field, ci(Object.class), null, null);
      clsData().fieldSet.add(field);
    }
  }

  public static final Class OBJECT = IRubyObject.class;
  public static final Class BLOCK = Block.class;
  public static final Class THREADCONTEXT = ThreadContext.class;
  public static final Class STATICSCOPE = StaticScope.class;
  public static final Type OBJECT_TYPE = Type.getType(OBJECT);
  public static final Type BLOCK_TYPE = Type.getType(BLOCK);
  public static final Type THREADCONTEXT_TYPE = Type.getType(THREADCONTEXT);
  public static final Type STATICSCOPE_TYPE = Type.getType(STATICSCOPE);
}
Ejemplo n.º 10
0
public class IREvalScript extends IRClosure {
  private static final Logger LOG = LoggerFactory.getLogger("IREvalScript");

  private IRScope nearestNonEvalScope;
  private int nearestNonEvalScopeDepth;
  private List<IRClosure> beginBlocks;
  private List<IRClosure> endBlocks;

  public IREvalScript(
      IRManager manager,
      IRScope lexicalParent,
      String fileName,
      int lineNumber,
      StaticScope staticScope) {
    super(manager, lexicalParent, fileName, lineNumber, staticScope, "EVAL_");

    int n = 0;
    IRScope s = lexicalParent;
    while (s instanceof IREvalScript) {
      n++;
      s = s.getLexicalParent();
    }

    this.nearestNonEvalScope = s;
    this.nearestNonEvalScopeDepth = n;
    this.nearestNonEvalScope.initEvalScopeVariableAllocator(false);
  }

  @Override
  public Label getNewLabel() {
    return getNewLabel("EV" + closureId + "_LBL");
  }

  @Override
  public IRScopeType getScopeType() {
    return IRScopeType.EVAL_SCRIPT;
  }

  @Override
  public Operand[] getBlockArgs() {
    return new Operand[0];
  }

  /* Record a begin block -- not all scope implementations can handle them */
  @Override
  public void recordBeginBlock(IRClosure beginBlockClosure) {
    if (beginBlocks == null) beginBlocks = new ArrayList<IRClosure>();
    beginBlockClosure.setBeginEndBlock();
    beginBlocks.add(beginBlockClosure);
  }

  /* Record an end block -- not all scope implementations can handle them */
  @Override
  public void recordEndBlock(IRClosure endBlockClosure) {
    if (endBlocks == null) endBlocks = new ArrayList<IRClosure>();
    endBlockClosure.setBeginEndBlock();
    endBlocks.add(endBlockClosure);
  }

  public List<IRClosure> getBeginBlocks() {
    return beginBlocks;
  }

  public List<IRClosure> getEndBlocks() {
    return endBlocks;
  }

  public IRubyObject call(
      ThreadContext context,
      IRubyObject self,
      RubyModule clazz,
      DynamicScope evalScope,
      Block block,
      String backtraceName) {
    if (IRRuntimeHelpers.isDebug()) {
      LOG.info("Graph:\n" + cfg().toStringGraph());
      LOG.info("CFG:\n" + cfg().toStringInstrs());
    }

    // FIXME: Do not push new empty arg array in every time
    return Interpreter.INTERPRET_EVAL(
        context, self, this, clazz, new IRubyObject[] {}, backtraceName, block, null);
  }

  @Override
  public LocalVariable lookupExistingLVar(String name) {
    return nearestNonEvalScope.evalScopeVars.get(name);
  }

  @Override
  public LocalVariable findExistingLocalVariable(String name, int scopeDepth) {
    // Look in the nearest non-eval scope's shared eval scope vars first.
    // If you dont find anything there, look in the nearest non-eval scope's regular vars.
    LocalVariable lvar = lookupExistingLVar(name);
    if (lvar != null || scopeDepth == 0) return lvar;
    else
      return nearestNonEvalScope.findExistingLocalVariable(
          name, scopeDepth - nearestNonEvalScopeDepth - 1);
  }

  @Override
  public LocalVariable getLocalVariable(String name, int scopeDepth) {
    LocalVariable lvar = findExistingLocalVariable(name, scopeDepth);
    if (lvar == null) lvar = getNewLocalVariable(name, scopeDepth);
    // Create a copy of the variable usable at the right depth
    if (lvar.getScopeDepth() != scopeDepth) lvar = lvar.cloneForDepth(scopeDepth);

    return lvar;
  }

  @Override
  public LocalVariable getImplicitBlockArg() {
    return getLocalVariable(Variable.BLOCK, nearestNonEvalScopeDepth);
  }

  @Override
  public LocalVariable getNewLocalVariable(String name, int depth) {
    assert depth == nearestNonEvalScopeDepth
        : "Local variable depth in IREvalScript:getNewLocalVariable must be "
            + nearestNonEvalScopeDepth
            + ".  Got "
            + depth;
    LocalVariable lvar =
        new ClosureLocalVariable(this, name, 0, nearestNonEvalScope.evalScopeVars.size());
    nearestNonEvalScope.evalScopeVars.put(name, lvar);
    // CON: unsure how to get static scope to reflect this name as in IRClosure and IRMethod
    return lvar;
  }

  @Override
  public LocalVariable getNewFlipStateVariable() {
    return getLocalVariable("%flip_" + allocateNextPrefixedName("%flip"), 0);
  }

  @Override
  public int getUsedVariablesCount() {
    return 1 + nearestNonEvalScope.evalScopeVars.size() + getPrefixCountSize("%flip");
  }

  @Override
  public boolean isScriptScope() {
    return true;
  }

  @Override
  public boolean isTopLocalVariableScope() {
    return false;
  }

  @Override
  public boolean isFlipScope() {
    return true;
  }
}
Ejemplo n.º 11
0
/** 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");
}
Ejemplo n.º 12
0
/** @author headius */
public class ASTInspector {

  private static final Logger LOG = LoggerFactory.getLogger("ASTInspector");

  private final boolean dump;
  private final String name;

  public ASTInspector() {
    dump = false;
    name = null;
  }

  public ASTInspector(String name, boolean dump) {
    this.name = name;
    this.dump = dump;
  }

  private static final boolean DEBUG = false;

  enum Flag {
    BLOCK_ARG(0x1), // block argument to the method
    CLOSURE(0x2), // closure present
    CLASS(0x4), // class present
    METHOD(0x8), // method table mutations, def, defs, undef, alias
    EVAL(0x10), // likely call to eval
    FRAME_AWARE(0x20), // makes calls that are aware of the frame
    FRAME_SELF(0x40), // makes calls that are aware of the frame's self
    FRAME_VISIBILITY(0x80), // makes calls that are aware of the frame's visibility
    FRAME_BLOCK(0x100), // makes calls that are aware of the frame's block
    FRAME_NAME(0x200), // makes calls that are aware of the frame's name
    BACKREF(0x400), // makes calls that set or get backref
    LASTLINE(0x800), // makes calls that set or get lastline
    FRAME_CLASS(0x1000), // makes calls that are aware of the frame's class
    OPT_ARGS(0x2000), // optional arguments to the method
    REST_ARG(0x4000), // rest arg to the method
    SCOPE_AWARE(0x8000), // makes calls that are aware of the scope
    ZSUPER(0x10000), // makes a zero-argument super call
    CONSTANT(0x20000), // accesses or sets constants
    CLASS_VAR(0x40000), // accesses or sets class variables
    SUPER(0x80000), // makes normal super call
    RETRY(0x100000); // contains a retry

    private Flag(int value) {
      flag = value;
    }

    public final int flag;
  }

  public static final Flag BLOCK_ARG = Flag.BLOCK_ARG;
  public static final Flag CLOSURE = Flag.CLOSURE;
  public static final Flag CLASS = Flag.CLASS;
  public static final Flag METHOD = Flag.METHOD;
  public static final Flag EVAL = Flag.EVAL;
  public static final Flag FRAME_AWARE = Flag.FRAME_AWARE;
  public static final Flag FRAME_SELF = Flag.FRAME_SELF;
  public static final Flag FRAME_VISIBILITY = Flag.FRAME_VISIBILITY;
  public static final Flag FRAME_BLOCK = Flag.FRAME_BLOCK;
  public static final Flag FRAME_NAME = Flag.FRAME_NAME;
  public static final Flag BACKREF = Flag.BACKREF;
  public static final Flag LASTLINE = Flag.LASTLINE;
  public static final Flag FRAME_CLASS = Flag.FRAME_CLASS;
  public static final Flag OPT_ARGS = Flag.OPT_ARGS;
  public static final Flag REST_ARG = Flag.REST_ARG;
  public static final Flag SCOPE_AWARE = Flag.SCOPE_AWARE;
  public static final Flag ZSUPER = Flag.ZSUPER;
  public static final Flag CONSTANT = Flag.CONSTANT;
  public static final Flag CLASS_VAR = Flag.CLASS_VAR;
  public static final Flag SUPER = Flag.SUPER;
  public static final Flag RETRY = Flag.RETRY;

  private static final String[] MODIFIER_NAMES = {
    "BLOCK",
    "CLOSURE",
    "CLASS",
    "METHOD",
    "EVAL",
    "FRAME_AWARE",
    "FRAME_SELF",
    "FRAME_VISIBILITY",
    "FRAME_BLOCK",
    "FRAME_NAME",
    "BACKREF",
    "LASTLINE",
    "FRAME_CLASS",
    "OPT_ARGS",
    "REST_ARG",
    "SCOPE_AWARE",
    "ZSUPER",
    "CONSTANT",
    "CLASS_VAR",
    "SUPER",
    "RETRY"
  };

  private int flags;

  // pragmas
  private boolean noFrame;

  public static final Set<String> FRAME_AWARE_METHODS =
      Collections.synchronizedSet(new HashSet<String>());
  public static final Set<String> SCOPE_AWARE_METHODS =
      Collections.synchronizedSet(new HashSet<String>());

  public static void addFrameAwareMethods(String... methods) {
    if (DEBUG) LOG.debug("Adding frame-aware method names: {}", Arrays.toString(methods));
    FRAME_AWARE_METHODS.addAll(Arrays.asList(methods));
  }

  public static void addScopeAwareMethods(String... methods) {
    if (DEBUG) LOG.debug("Adding scope-aware method names: {}", Arrays.toString(methods));
    SCOPE_AWARE_METHODS.addAll(Arrays.asList(methods));
  }

  public static final Set<String> PRAGMAS = Collections.synchronizedSet(new HashSet<String>());

  static {
    FRAME_AWARE_METHODS.add("eval");
    FRAME_AWARE_METHODS.add("module_eval");
    FRAME_AWARE_METHODS.add("class_eval");
    FRAME_AWARE_METHODS.add("instance_eval");
    FRAME_AWARE_METHODS.add("binding");
    FRAME_AWARE_METHODS.add("public");
    FRAME_AWARE_METHODS.add("private");
    FRAME_AWARE_METHODS.add("protected");
    FRAME_AWARE_METHODS.add("module_function");
    FRAME_AWARE_METHODS.add("block_given?");
    FRAME_AWARE_METHODS.add("iterator?");

    SCOPE_AWARE_METHODS.addAll(RubyModule.SCOPE_CAPTURING_METHODS);

    PRAGMAS.add("__NOFRAME__");
  }

  public void disable() {
    if (dump) LOG.debug("[ASTInspector] {} DISABLED", name);
    flags = 0xFFFFFFFF;
  }

  public CallConfiguration getCallConfig() {
    if (!noFrame()
        && (hasFrameAwareMethods() || hasClosure() || RubyInstanceConfig.FULL_TRACE_ENABLED)) {
      // We're doing normal framed compilation or the method needs a frame
      if (hasClosure() || hasScopeAwareMethods()) {
        // The method also needs a scope, do both
        return CallConfiguration.FrameFullScopeFull;
      } else {
        // The method doesn't need a scope or static scope; frame only
        return CallConfiguration.FrameFullScopeNone;
      }
    } else {
      if (hasClosure() || hasScopeAwareMethods()) {
        return CallConfiguration.FrameNoneScopeFull;
      } else {
        return CallConfiguration.FrameNoneScopeNone;
      }
    }
  }

  /**
   * Perform an inspection of a subtree or set of subtrees separate from the parent inspection, to
   * make independent decisions based on that subtree(s).
   *
   * @param nodes The child nodes to walk with a new inspector
   * @return The new inspector resulting from the walk
   */
  public ASTInspector subInspect(Node... nodes) {
    ASTInspector newInspector = new ASTInspector(name, dump);

    for (Node node : nodes) {
      newInspector.inspect(node);
    }

    return newInspector;
  }

  public boolean getFlag(Flag modifier) {
    return (flags & modifier.flag) != 0;
  }

  public boolean getFlag(Flag... modifiers) {
    int mask = 0;
    for (Flag flag : modifiers) {
      mask |= flag.flag;
    }
    return (flags & mask) != 0;
  }

  public void setFlag(Flag modifier) {
    if (dump) {
      LOG.info("[ASTInspector] " + name + "\n\tset flag " + modifier);
    }
    flags |= modifier.flag;
  }

  public void setFlag(Node node, Flag modifier) {
    if (dump) {
      LOG.info(
          "[ASTInspector] "
              + name
              + "\n\tset flag "
              + modifier
              + " because of "
              + node.getNodeType()
              + " at "
              + node.getPosition());
    }
    flags |= modifier.flag;
  }

  /**
   * Integrate the results of a separate inspection into the state of this inspector.
   *
   * @param other The other inspector whose state to integrate.
   */
  public void integrate(ASTInspector other) {
    flags |= other.flags;
  }

  public void inspect(Node node) {
    if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
      disable();
      // we still inspect since some nodes change state as a result (JRUBY-6836)
    }

    if (node == null) return;

    switch (node.getNodeType()) {
      case ALIASNODE:
        setFlag(node, METHOD);
        break;
      case ANDNODE:
        AndNode andNode = (AndNode) node;
        inspect(andNode.getFirstNode());
        inspect(andNode.getSecondNode());
        break;
      case ARGSCATNODE:
        ArgsCatNode argsCatNode = (ArgsCatNode) node;
        inspect(argsCatNode.getFirstNode());
        inspect(argsCatNode.getSecondNode());
        break;
      case ARGSPUSHNODE:
        ArgsPushNode argsPushNode = (ArgsPushNode) node;
        inspect(argsPushNode.getFirstNode());
        inspect(argsPushNode.getSecondNode());
        break;
      case ARGUMENTNODE:
        break;
      case ARRAYNODE:
      case BLOCKNODE:
      case DREGEXPNODE:
      case DSTRNODE:
      case DSYMBOLNODE:
      case DXSTRNODE:
      case LISTNODE:
        ListNode listNode = (ListNode) node;
        for (int i = 0; i < listNode.size(); i++) {
          inspect(listNode.get(i));
        }
        break;
      case ARGSNODE:
        ArgsNode argsNode = (ArgsNode) node;
        if (argsNode.getBlock() != null) setFlag(node, BLOCK_ARG);
        if (argsNode.getOptArgs() != null) {
          setFlag(node, OPT_ARGS);
          inspect(argsNode.getOptArgs());
        }
        if (argsNode.getRestArg() == -2 || argsNode.getRestArg() >= 0) setFlag(node, REST_ARG);
        break;
      case ATTRASSIGNNODE:
        AttrAssignNode attrAssignNode = (AttrAssignNode) node;
        inspect(attrAssignNode.getArgsNode());
        inspect(attrAssignNode.getReceiverNode());
        break;
      case BACKREFNODE:
        setFlag(node, BACKREF);
        break;
      case BEGINNODE:
        inspect(((BeginNode) node).getBodyNode());
        break;
      case BIGNUMNODE:
        break;
      case BINARYOPERATORNODE:
        BinaryOperatorNode binaryOperatorNode = (BinaryOperatorNode) node;
        inspect(binaryOperatorNode.getFirstNode());
        inspect(binaryOperatorNode.getSecondNode());
        break;
      case BLOCKARGNODE:
        break;
      case BLOCKPASSNODE:
        BlockPassNode blockPassNode = (BlockPassNode) node;
        inspect(blockPassNode.getArgsNode());
        inspect(blockPassNode.getBodyNode());
        break;
      case BREAKNODE:
        inspect(((BreakNode) node).getValueNode());
        break;
      case CALLNODE:
        CallNode callNode = (CallNode) node;
        inspect(callNode.getReceiverNode());
        // check for Proc.new, an especially magic method
        if (callNode.getName() == "new"
            && callNode.getReceiverNode() instanceof ConstNode
            && ((ConstNode) callNode.getReceiverNode()).getName() == "Proc") {
          // Proc.new needs the caller's block to instantiate a proc
          setFlag(node, FRAME_BLOCK);
        }
        if (callNode.getArgsNode() == null && callNode.getIterNode() == null) {
          switch (callNode.getReceiverNode().getNodeType()) {
              // no unary methods on literal numbers, symbols, or strings have frame/scope effects
            case FIXNUMNODE:
            case FLOATNODE:
            case BIGNUMNODE:
            case STRNODE:
            case SYMBOLNODE:
              return;
          }
        }
      case FCALLNODE:
        inspect(((IArgumentNode) node).getArgsNode());
        inspect(((BlockAcceptingNode) node).getIterNode());
      case VCALLNODE:
        INameNode nameNode = (INameNode) node;
        if (FRAME_AWARE_METHODS.contains(nameNode.getName())) {
          setFlag(node, FRAME_AWARE);
          if (nameNode.getName().indexOf("eval") != -1) {
            setFlag(node, EVAL);
          }
        }
        if (SCOPE_AWARE_METHODS.contains(nameNode.getName())) {
          setFlag(node, SCOPE_AWARE);
        }
        break;
      case CASENODE:
        CaseNode caseNode = (CaseNode) node;
        inspect(caseNode.getCaseNode());
        if (caseNode.getCases().size() > Options.COMPILE_OUTLINE_CASECOUNT.load()) {
          // if more than N cases, disable; we'll compile them as separate bodies
          // see BaseBodyCompiler#compiledSequencedConditional and ASTCompiler#compileCase
          disable();
          return;
        } else {
          for (Node when : caseNode.getCases().childNodes()) {
            inspect(when);
          }
          inspect(caseNode.getElseNode());
        }
        break;
      case CLASSNODE:
        setFlag(node, CLASS);
        ClassNode classNode = (ClassNode) node;
        inspect(classNode.getCPath());
        inspect(classNode.getSuperNode());
        break;
      case CLASSVARNODE:
        setFlag(node, CLASS_VAR);
        break;
      case CONSTDECLNODE:
        inspect(((AssignableNode) node).getValueNode());
        setFlag(node, CONSTANT);
        break;
      case CLASSVARASGNNODE:
        inspect(((AssignableNode) node).getValueNode());
        setFlag(node, CLASS_VAR);
        break;
      case CLASSVARDECLNODE:
        inspect(((AssignableNode) node).getValueNode());
        setFlag(node, CLASS_VAR);
        break;
      case COLON2NODE:
        inspect(((Colon2Node) node).getLeftNode());
        break;
      case COLON3NODE:
        break;
      case CONSTNODE:
        setFlag(node, CONSTANT);
        break;
      case DEFNNODE:
      case DEFSNODE:
        setFlag(node, METHOD);
        setFlag(node, FRAME_VISIBILITY);
        setFlag(node, SCOPE_AWARE);
        break;
      case DEFINEDNODE:
        switch (((DefinedNode) node).getExpressionNode().getNodeType()) {
          case CLASSVARASGNNODE:
          case CLASSVARDECLNODE:
          case CONSTDECLNODE:
          case DASGNNODE:
          case GLOBALASGNNODE:
          case LOCALASGNNODE:
          case MULTIPLEASGNNODE:
          case OPASGNNODE:
          case OPELEMENTASGNNODE:
          case DVARNODE:
          case FALSENODE:
          case TRUENODE:
          case LOCALVARNODE:
          case INSTVARNODE:
          case BACKREFNODE:
          case SELFNODE:
          case VCALLNODE:
          case YIELDNODE:
          case GLOBALVARNODE:
          case CONSTNODE:
          case FCALLNODE:
          case CLASSVARNODE:
            // ok, we have fast paths
            inspect(((DefinedNode) node).getExpressionNode());
            break;
          default:
            // long, slow way causes disabling
            // we still inspect because some nodes may change state (JRUBY-6836)
            inspect(((DefinedNode) node).getExpressionNode());
            disable();
        }
        break;
      case DOTNODE:
        DotNode dotNode = (DotNode) node;
        inspect(dotNode.getBeginNode());
        inspect(dotNode.getEndNode());
        break;
      case DASGNNODE:
        inspect(((AssignableNode) node).getValueNode());
        break;
      case DVARNODE:
        break;
      case ENSURENODE:
        inspect(((EnsureNode) node).getBodyNode());
        inspect(((EnsureNode) node).getEnsureNode());
        disable();
        break;
      case ENCODINGNODE:
        break;
      case EVSTRNODE:
        inspect(((EvStrNode) node).getBody());
        break;
      case FALSENODE:
        break;
      case FIXNUMNODE:
        break;
      case FLIPNODE:
        inspect(((FlipNode) node).getBeginNode());
        inspect(((FlipNode) node).getEndNode());
        break;
      case FLOATNODE:
        break;
      case FORNODE:
        setFlag(node, CLOSURE);
        setFlag(node, SCOPE_AWARE);
        inspect(((ForNode) node).getIterNode());
        inspect(((ForNode) node).getBodyNode());
        inspect(((ForNode) node).getVarNode());
        break;
      case GLOBALASGNNODE:
        GlobalAsgnNode globalAsgnNode = (GlobalAsgnNode) node;
        if (globalAsgnNode.getName().equals("$_")) {
          setFlag(node, LASTLINE);
        } else if (globalAsgnNode.getName().equals("$~")) {
          setFlag(node, BACKREF);
        }
        inspect(globalAsgnNode.getValueNode());
        break;
      case GLOBALVARNODE:
        {
          String name = ((GlobalVarNode) node).getName();
          if (name.equals("$_") || name.equals("$LAST_READ_LINE")) {
            setFlag(node, LASTLINE);
          } else if (name.equals("$~")
              || name.equals("$`")
              || name.equals("$'")
              || name.equals("$+")
              || name.equals("$LAST_MATCH_INFO")
              || name.equals("$PREMATCH")
              || name.equals("$POSTMATCH")
              || name.equals("$LAST_PAREN_MATCH")) {
            setFlag(node, BACKREF);
          }
          break;
        }
      case HASHNODE:
        HashNode hashNode = (HashNode) node;
        inspect(hashNode.getListNode());
        break;
      case IFNODE:
        IfNode ifNode = (IfNode) node;
        inspect(ifNode.getCondition());
        inspect(ifNode.getThenBody());
        inspect(ifNode.getElseBody());
        break;
      case INSTASGNNODE:
        inspect(((AssignableNode) node).getValueNode());
        break;
      case INSTVARNODE:
        break;
      case ISCOPINGNODE:
        IScopingNode iscopingNode = (IScopingNode) node;
        inspect(iscopingNode.getCPath());
        break;
      case ITERNODE:
        setFlag(node, CLOSURE);
        break;
      case LAMBDANODE:
        setFlag(node, CLOSURE);
        break;
      case LOCALASGNNODE:
        LocalAsgnNode localAsgnNode = (LocalAsgnNode) node;
        if (PRAGMAS.contains(localAsgnNode.getName())) {
          if (localAsgnNode.getName().equals("__NOFRAME__")) {
            noFrame = localAsgnNode.getValueNode() instanceof TrueNode;
          }
          break;
        }
        inspect(localAsgnNode.getValueNode());
        break;
      case LOCALVARNODE:
        break;
      case MATCHNODE:
        inspect(((MatchNode) node).getRegexpNode());
        setFlag(node, BACKREF);
        break;
      case MATCH2NODE:
        Match2Node match2Node = (Match2Node) node;
        inspect(match2Node.getReceiverNode());
        inspect(match2Node.getValueNode());
        setFlag(node, BACKREF);
        if (match2Node instanceof Match2CaptureNode) {
          // additionally need scope, to set local vars
          // FIXME: this can be done without heap scope
          setFlag(node, SCOPE_AWARE);
        }
        break;
      case MATCH3NODE:
        Match3Node match3Node = (Match3Node) node;
        inspect(match3Node.getReceiverNode());
        inspect(match3Node.getValueNode());
        setFlag(node, BACKREF);
        break;
      case MODULENODE:
        setFlag(node, CLASS);
        inspect(((ModuleNode) node).getCPath());
        break;
      case MULTIPLEASGN19NODE:
        MultipleAsgn19Node multipleAsgn19Node = (MultipleAsgn19Node) node;
        inspect(multipleAsgn19Node.getPre());
        inspect(multipleAsgn19Node.getPost());
        inspect(multipleAsgn19Node.getRest());
        inspect(multipleAsgn19Node.getValueNode());
        break;
      case MULTIPLEASGNNODE:
        MultipleAsgnNode multipleAsgnNode = (MultipleAsgnNode) node;
        inspect(multipleAsgnNode.getArgsNode());
        inspect(multipleAsgnNode.getHeadNode());
        inspect(multipleAsgnNode.getValueNode());
        break;
      case NEWLINENODE:
        inspect(((NewlineNode) node).getNextNode());
        break;
      case NEXTNODE:
        inspect(((NextNode) node).getValueNode());
        break;
      case NILNODE:
        break;
      case NOTNODE:
        inspect(((NotNode) node).getConditionNode());
        break;
      case NTHREFNODE:
        break;
      case OPASGNANDNODE:
        OpAsgnAndNode opAsgnAndNode = (OpAsgnAndNode) node;
        inspect(opAsgnAndNode.getFirstNode());
        inspect(opAsgnAndNode.getSecondNode());
        break;
      case OPASGNNODE:
        OpAsgnNode opAsgnNode = (OpAsgnNode) node;
        inspect(opAsgnNode.getReceiverNode());
        inspect(opAsgnNode.getValueNode());
        break;
      case OPASGNORNODE:
        switch (((OpAsgnOrNode) node).getFirstNode().getNodeType()) {
          case CLASSVARASGNNODE:
          case CLASSVARDECLNODE:
          case CONSTDECLNODE:
          case DASGNNODE:
          case GLOBALASGNNODE:
          case LOCALASGNNODE:
          case MULTIPLEASGNNODE:
          case OPASGNNODE:
          case OPELEMENTASGNNODE:
          case DVARNODE:
          case FALSENODE:
          case TRUENODE:
          case LOCALVARNODE:
          case INSTVARNODE:
          case BACKREFNODE:
          case SELFNODE:
          case VCALLNODE:
          case YIELDNODE:
          case GLOBALVARNODE:
          case CONSTNODE:
          case FCALLNODE:
          case CLASSVARNODE:
            // ok, we have fast paths
            inspect(((OpAsgnOrNode) node).getSecondNode());
            break;
          default:
            // long, slow way causes disabling for defined
            inspect(((OpAsgnOrNode) node).getFirstNode());
            inspect(((OpAsgnOrNode) node).getSecondNode());
            disable();
        }
        break;
      case OPELEMENTASGNNODE:
        OpElementAsgnNode opElementAsgnNode = (OpElementAsgnNode) node;
        inspect(opElementAsgnNode.getArgsNode());
        inspect(opElementAsgnNode.getReceiverNode());
        inspect(opElementAsgnNode.getValueNode());
        break;
      case OPTARGNODE:
        inspect(((OptArgNode) node).getValue());
        break;
      case ORNODE:
        OrNode orNode = (OrNode) node;
        inspect(orNode.getFirstNode());
        inspect(orNode.getSecondNode());
        break;
      case POSTEXENODE:
        PostExeNode postExeNode = (PostExeNode) node;
        setFlag(node, CLOSURE);
        setFlag(node, SCOPE_AWARE);
        inspect(postExeNode.getBodyNode());
        inspect(postExeNode.getVarNode());
        break;
      case PREEXENODE:
        PreExeNode preExeNode = (PreExeNode) node;
        setFlag(node, CLOSURE);
        setFlag(node, SCOPE_AWARE);
        inspect(preExeNode.getBodyNode());
        inspect(preExeNode.getVarNode());
        break;
      case REDONODE:
        break;
      case REGEXPNODE:
        break;
      case ROOTNODE:
        inspect(((RootNode) node).getBodyNode());
        if (((RootNode) node).getBodyNode() instanceof BlockNode) {
          BlockNode blockNode = (BlockNode) ((RootNode) node).getBodyNode();
          if (blockNode.size() > 500) {
            // method has more than 500 lines; we'll need to split it
            // and therefore need to use a heap-based scope
            setFlag(node, SCOPE_AWARE);
          }
        }
        break;
      case RESCUEBODYNODE:
        RescueBodyNode rescueBody = (RescueBodyNode) node;
        inspect(rescueBody.getExceptionNodes());
        inspect(rescueBody.getBodyNode());
        inspect(rescueBody.getOptRescueNode());
        break;
      case RESCUENODE:
        RescueNode rescueNode = (RescueNode) node;
        inspect(rescueNode.getBodyNode());
        inspect(rescueNode.getElseNode());
        inspect(rescueNode.getRescueNode());
        disable();
        break;
      case RETRYNODE:
        setFlag(node, RETRY);
        break;
      case RETURNNODE:
        inspect(((ReturnNode) node).getValueNode());
        break;
      case SCLASSNODE:
        setFlag(node, CLASS);
        setFlag(node, FRAME_AWARE);
        SClassNode sclassNode = (SClassNode) node;
        inspect(sclassNode.getReceiverNode());
        break;
      case SCOPENODE:
        break;
      case SELFNODE:
        break;
      case SPLATNODE:
        inspect(((SplatNode) node).getValue());
        break;
      case STARNODE:
        break;
      case STRNODE:
        break;
      case SUPERNODE:
        SuperNode superNode = (SuperNode) node;
        inspect(superNode.getArgsNode());
        inspect(superNode.getIterNode());
        setFlag(node, SUPER);
        break;
      case SVALUENODE:
        inspect(((SValueNode) node).getValue());
        break;
      case SYMBOLNODE:
        break;
      case TOARYNODE:
        inspect(((ToAryNode) node).getValue());
        break;
      case TRUENODE:
        break;
      case UNDEFNODE:
        setFlag(node, METHOD);
        break;
      case UNTILNODE:
        UntilNode untilNode = (UntilNode) node;
        ASTInspector untilInspector =
            subInspect(untilNode.getConditionNode(), untilNode.getBodyNode());
        // a while node could receive non-local flow control from any of these:
        // * a closure within the loop
        // * an eval within the loop
        // * a block-arg-based proc called within the loop
        if (untilInspector.getFlag(CLOSURE) || untilInspector.getFlag(EVAL)) {
          untilNode.containsNonlocalFlow = true;

          // we set scope-aware to true to force heap-based locals
          setFlag(node, SCOPE_AWARE);
        }
        integrate(untilInspector);
        break;
      case VALIASNODE:
        break;
      case WHENNODE:
        {
          inspect(((WhenNode) node).getBodyNode());
          inspect(((WhenNode) node).getExpressionNodes());
          inspect(((WhenNode) node).getNextCase());
          // if any elements are not literals or are regexp, set backref
          Node expr = ((WhenNode) node).getExpressionNodes();
          if (!(expr instanceof ILiteralNode) || expr.getNodeType() == NodeType.REGEXPNODE) {
            setFlag(node, BACKREF);
          }
          break;
        }
      case WHILENODE:
        WhileNode whileNode = (WhileNode) node;
        ASTInspector whileInspector =
            subInspect(whileNode.getConditionNode(), whileNode.getBodyNode());
        // a while node could receive non-local flow control from any of these:
        // * a closure within the loop
        // * an eval within the loop
        // * a block-arg-based proc called within the loop
        // * any case that disables optimization, like rescues and ensures
        if (whileInspector.getFlag(CLOSURE) || whileInspector.getFlag(EVAL) || getFlag(BLOCK_ARG)) {
          whileNode.containsNonlocalFlow = true;

          // we set scope-aware to true to force heap-based locals
          setFlag(node, SCOPE_AWARE);
        }
        integrate(whileInspector);
        break;
      case XSTRNODE:
        break;
      case YIELDNODE:
        inspect(((YieldNode) node).getArgsNode());
        break;
      case ZARRAYNODE:
        break;
      case ZEROARGNODE:
        break;
      case ZSUPERNODE:
        setFlag(node, SCOPE_AWARE);
        setFlag(node, ZSUPER);
        inspect(((ZSuperNode) node).getIterNode());
        break;
      default:
        // encountered a node we don't recognize, set everything to true to disable optz
        assert false : "All nodes should be accounted for in AST inspector: " + node;
        disable();
    }
  }

  public boolean hasClass() {
    return getFlag(CLASS);
  }

  public boolean hasClosure() {
    return getFlag(CLOSURE);
  }

  /**
   * Whether the tree under inspection contains any method-table mutations, including def, defs,
   * undef, and alias.
   *
   * @return True if there are mutations, false otherwise
   */
  public boolean hasMethod() {
    return getFlag(METHOD);
  }

  public boolean hasFrameAwareMethods() {
    return getFlag(
        FRAME_AWARE,
        FRAME_BLOCK,
        FRAME_CLASS,
        FRAME_NAME,
        FRAME_SELF,
        FRAME_VISIBILITY,
        CLOSURE,
        EVAL,
        ZSUPER,
        SUPER,
        BACKREF,
        LASTLINE);
  }

  public boolean hasScopeAwareMethods() {
    return getFlag(SCOPE_AWARE);
  }

  public boolean hasBlockArg() {
    return getFlag(BLOCK_ARG);
  }

  public boolean hasOptArgs() {
    return getFlag(OPT_ARGS);
  }

  public boolean hasRestArg() {
    return getFlag(REST_ARG);
  }

  public boolean hasConstant() {
    return getFlag(CONSTANT);
  }

  public boolean hasClassVar() {
    return getFlag(CLASS_VAR);
  }

  public boolean noFrame() {
    return noFrame;
  }
}
Ejemplo n.º 13
0
public class JITCompiler implements JITCompilerMBean {
  private static final Logger LOG = LoggerFactory.getLogger("JITCompiler");

  public static final boolean USE_CACHE = true;
  public static final String RUBY_JIT_PREFIX = "rubyjit";

  public static final String CLASS_METHOD_DELIMITER = "$$";

  public static class JITCounts {
    private final AtomicLong compiledCount = new AtomicLong(0);
    private final AtomicLong successCount = new AtomicLong(0);
    private final AtomicLong failCount = new AtomicLong(0);
    private final AtomicLong abandonCount = new AtomicLong(0);
    private final AtomicLong compileTime = new AtomicLong(0);
    private final AtomicLong averageCompileTime = new AtomicLong(0);
    private final AtomicLong codeSize = new AtomicLong(0);
    private final AtomicLong averageCodeSize = new AtomicLong(0);
    private final AtomicLong largestCodeSize = new AtomicLong(0);
  }

  private final JITCounts counts = new JITCounts();
  private final ExecutorService executor =
      new ThreadPoolExecutor(
          2, // always two threads
          2,
          0, // never stop
          TimeUnit.SECONDS,
          new LinkedBlockingQueue<Runnable>(),
          new DaemonThreadFactory("JRubyJIT", Thread.MIN_PRIORITY));

  private final Ruby runtime;
  private final RubyInstanceConfig config;

  public JITCompiler(Ruby runtime) {
    this.runtime = runtime;
    this.config = runtime.getInstanceConfig();

    runtime.getBeanManager().register(this);
  }

  public long getSuccessCount() {
    return counts.successCount.get();
  }

  public long getCompileCount() {
    return counts.compiledCount.get();
  }

  public long getFailCount() {
    return counts.failCount.get();
  }

  public long getCompileTime() {
    return counts.compileTime.get() / 1000;
  }

  public long getAbandonCount() {
    return counts.abandonCount.get();
  }

  public long getCodeSize() {
    return counts.codeSize.get();
  }

  public long getAverageCodeSize() {
    return counts.averageCodeSize.get();
  }

  public long getAverageCompileTime() {
    return counts.averageCompileTime.get() / 1000;
  }

  public long getLargestCodeSize() {
    return counts.largestCodeSize.get();
  }

  public void tryJIT(
      DefaultMethod method, ThreadContext context, String className, String methodName) {
    if (!config.getCompileMode().shouldJIT()) return;

    if (method.incrementCallCount() < config.getJitThreshold()) return;

    jitThresholdReached(method, config, context, className, methodName);
  }

  public void tearDown() {
    if (executor != null) {
      try {
        executor.shutdown();
      } catch (SecurityException se) {
        // ignore, can't shut down executor
      }
    }
  }

  private void jitThresholdReached(
      final DefaultMethod method,
      final RubyInstanceConfig config,
      ThreadContext context,
      final String className,
      final String methodName) {
    // Disable any other jit tasks from entering queue
    method.setCallCount(-1);

    final Ruby runtime = context.runtime;

    Runnable jitTask = new JITTask(className, method, methodName);

    // if background JIT is enabled and threshold is > 0 and we have an executor...
    if (config.getJitBackground() && config.getJitThreshold() > 0 && executor != null) {
      // JIT in background
      try {
        executor.submit(jitTask);
      } catch (RejectedExecutionException ree) {
        // failed to submit, just run it directly
        jitTask.run();
      }
    } else {
      // just run directly
      jitTask.run();
    }
  }

  private class JITTask implements Runnable {
    private final String className;
    private final DefaultMethod method;
    private final String methodName;

    public JITTask(String className, DefaultMethod method, String methodName) {
      this.className = className;
      this.method = method;
      this.methodName = methodName;
    }

    public void run() {
      try {
        // The cache is full. Abandon JIT for this method and bail out.
        ClassCache classCache = config.getClassCache();
        if (classCache.isFull()) {
          counts.abandonCount.incrementAndGet();
          return;
        }

        // Check if the method has been explicitly excluded
        if (config.getExcludedMethods().size() > 0) {
          String excludeModuleName = className;
          if (method.getImplementationClass().isSingleton()) {
            IRubyObject possibleRealClass =
                ((MetaClass) method.getImplementationClass()).getAttached();
            if (possibleRealClass instanceof RubyModule) {
              excludeModuleName = "Meta:" + ((RubyModule) possibleRealClass).getName();
            }
          }

          if ((config.getExcludedMethods().contains(excludeModuleName)
              || config.getExcludedMethods().contains(excludeModuleName + "#" + methodName)
              || config.getExcludedMethods().contains(methodName))) {
            method.setCallCount(-1);
            log(method, methodName, "skipping method: " + excludeModuleName + "#" + methodName);
            return;
          }
        }

        String key = SexpMaker.create(methodName, method.getArgsNode(), method.getBodyNode());
        JITClassGenerator generator =
            new JITClassGenerator(className, methodName, key, runtime, method, counts);

        Class<Script> sourceClass =
            (Class<Script>)
                config.getClassCache().cacheClassByKey(generator.digestString, generator);

        if (sourceClass == null) {
          // class could not be found nor generated; give up on JIT and bail out
          counts.failCount.incrementAndGet();
          return;
        }

        // successfully got back a jitted method
        counts.successCount.incrementAndGet();

        // finally, grab the script
        Script jitCompiledScript = sourceClass.newInstance();

        // set root scope
        jitCompiledScript.setRootScope(method.getStaticScope());

        // add to the jitted methods set
        Set<Script> jittedMethods = runtime.getJittedMethods();
        jittedMethods.add(jitCompiledScript);

        // logEvery n methods based on configuration
        if (config.getJitLogEvery() > 0) {
          int methodCount = jittedMethods.size();
          if (methodCount % config.getJitLogEvery() == 0) {
            log(method, methodName, "live compiled methods: " + methodCount);
          }
        }

        if (config.isJitLogging()) {
          log(method, className + "." + methodName, "done jitting");
        }

        method.switchToJitted(jitCompiledScript, generator.callConfig());
        return;
      } catch (Throwable t) {
        if (runtime.getDebug().isTrue()) {
          t.printStackTrace();
        }
        if (config.isJitLoggingVerbose()) {
          log(method, className + "." + methodName, "could not compile", t.getMessage());
        }

        counts.failCount.incrementAndGet();
        return;
      }
    }
  }

  public static String getHashForString(String str) {
    return getHashForBytes(RubyEncoding.encodeUTF8(str));
  }

  public static String getHashForBytes(byte[] bytes) {
    try {
      MessageDigest sha1 = MessageDigest.getInstance("SHA1");
      sha1.update(bytes);
      byte[] digest = sha1.digest();
      StringBuilder builder = new StringBuilder();
      for (int i = 0; i < digest.length; i++) {
        builder.append(Integer.toString((digest[i] & 0xff) + 0x100, 16).substring(1));
      }
      return builder.toString().toUpperCase(Locale.ENGLISH);
    } catch (NoSuchAlgorithmException nsae) {
      throw new RuntimeException(nsae);
    }
  }

  public static void saveToCodeCache(
      Ruby ruby, byte[] bytecode, String packageName, File cachedClassFile) {
    String codeCache = RubyInstanceConfig.JIT_CODE_CACHE;
    File codeCacheDir = new File(codeCache);
    if (!codeCacheDir.exists()) {
      ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " does not exist");
    } else if (!codeCacheDir.isDirectory()) {
      ruby.getWarnings()
          .warn("jruby.jit.codeCache directory " + codeCacheDir + " is not a directory");
    } else if (!codeCacheDir.canWrite()) {
      ruby.getWarnings().warn("jruby.jit.codeCache directory " + codeCacheDir + " is not writable");
    } else {
      if (!new File(codeCache, packageName).isDirectory()) {
        boolean createdDirs = new File(codeCache, packageName).mkdirs();
        if (!createdDirs) {
          ruby.getWarnings()
              .warn("could not create JIT cache dir: " + new File(codeCache, packageName));
        }
      }
      // write to code cache
      FileOutputStream fos = null;
      try {
        if (RubyInstanceConfig.JIT_LOADING_DEBUG)
          LOG.info("writing jitted code to to " + cachedClassFile);
        fos = new FileOutputStream(cachedClassFile);
        fos.write(bytecode);
      } catch (Exception e) {
        e.printStackTrace();
        // ignore
      } finally {
        try {
          fos.close();
        } catch (Exception e) {
        }
      }
    }
  }

  public static class JITClassGenerator implements ClassCache.ClassGenerator {
    public JITClassGenerator(
        String className,
        String methodName,
        String key,
        Ruby ruby,
        DefaultMethod method,
        JITCounts counts) {
      this.packageName = JITCompiler.RUBY_JIT_PREFIX;
      if (RubyInstanceConfig.JAVA_VERSION == Opcodes.V1_7) {
        // recent Java 7 seems to have a bug that leaks definitions across cousin classloaders
        // so we force the class name to be unique to this runtime
        digestString = getHashForString(key) + Math.abs(ruby.hashCode());
      } else {
        digestString = getHashForString(key);
      }
      this.className =
          packageName
              + "/"
              + className.replace('.', '/')
              + CLASS_METHOD_DELIMITER
              + JavaNameMangler.mangleMethodName(methodName)
              + "_"
              + digestString;
      this.name = this.className.replaceAll("/", ".");
      this.bodyNode = method.getBodyNode();
      this.argsNode = method.getArgsNode();
      this.methodName = methodName;
      filename = calculateFilename(argsNode, bodyNode);
      staticScope = method.getStaticScope();
      asmCompiler = new StandardASMCompiler(this.className, filename);
      this.ruby = ruby;
      this.counts = counts;
    }

    @SuppressWarnings("unchecked")
    protected void compile() {
      if (bytecode != null) return;

      // check if we have a cached compiled version on disk
      String codeCache = RubyInstanceConfig.JIT_CODE_CACHE;
      File cachedClassFile = new File(codeCache + "/" + className + ".class");

      if (codeCache != null && cachedClassFile.exists()) {
        FileInputStream fis = null;
        try {
          if (RubyInstanceConfig.JIT_LOADING_DEBUG)
            LOG.info("loading cached code from: " + cachedClassFile);
          fis = new FileInputStream(cachedClassFile);
          bytecode = new byte[(int) fis.getChannel().size()];
          fis.read(bytecode);
          name = new ClassReader(bytecode).getClassName();
          return;
        } catch (Exception e) {
          // ignore and proceed to compile
        } finally {
          try {
            fis.close();
          } catch (Exception e) {
          }
        }
      }

      // Time the compilation
      long start = System.nanoTime();

      asmCompiler.startScript(staticScope);
      final ASTCompiler compiler = ruby.getInstanceConfig().newCompiler();

      CompilerCallback args =
          new CompilerCallback() {
            public void call(BodyCompiler context) {
              compiler.compileArgs(argsNode, context, true);
            }
          };

      ASTInspector inspector = new ASTInspector();
      if (ruby.getInstanceConfig().isJitDumping()) {
        inspector = new ASTInspector(className, true);
      }
      // check args first, since body inspection can depend on args
      inspector.inspect(argsNode);
      inspector.inspect(bodyNode);

      BodyCompiler methodCompiler;
      if (bodyNode != null) {
        // we have a body, do a full-on method
        methodCompiler = asmCompiler.startFileMethod(args, staticScope, inspector);
        compiler.compileBody(bodyNode, methodCompiler, true);
      } else {
        // If we don't have a body, check for required or opt args
        // if opt args, they could have side effects
        // if required args, need to raise errors if too few args passed
        // otherwise, method does nothing, make it a nop
        if (argsNode != null
            && (argsNode.getRequiredArgsCount() > 0 || argsNode.getOptionalArgsCount() > 0)) {
          methodCompiler = asmCompiler.startFileMethod(args, staticScope, inspector);
          methodCompiler.loadNil();
        } else {
          methodCompiler = asmCompiler.startFileMethod(null, staticScope, inspector);
          methodCompiler.loadNil();
          jitCallConfig = CallConfiguration.FrameNoneScopeNone;
        }
      }
      methodCompiler.endBody();
      asmCompiler.endScript(false, false);

      // if we haven't already decided on a do-nothing call
      if (jitCallConfig == null) {
        jitCallConfig = inspector.getCallConfig();
      }

      bytecode = asmCompiler.getClassByteArray();
      if (ruby.getInstanceConfig().isJitDumping()) {
        TraceClassVisitor tcv = new TraceClassVisitor(new PrintWriter(System.out));
        new ClassReader(bytecode).accept(tcv, 0);
      }

      if (bytecode.length > ruby.getInstanceConfig().getJitMaxSize()) {
        bytecode = null;
        throw new NotCompilableException(
            "JITed method size exceeds configured max of "
                + ruby.getInstanceConfig().getJitMaxSize());
      }

      if (codeCache != null) {
        JITCompiler.saveToCodeCache(ruby, bytecode, packageName, cachedClassFile);
      }

      counts.compiledCount.incrementAndGet();
      counts.compileTime.addAndGet(System.nanoTime() - start);
      counts.codeSize.addAndGet(bytecode.length);
      counts.averageCompileTime.set(counts.compileTime.get() / counts.compiledCount.get());
      counts.averageCodeSize.set(counts.codeSize.get() / counts.compiledCount.get());
      synchronized (counts) {
        if (counts.largestCodeSize.get() < bytecode.length) {
          counts.largestCodeSize.set(bytecode.length);
        }
      }
    }

    public void generate() {
      compile();
    }

    public byte[] bytecode() {
      return bytecode;
    }

    public String name() {
      return name;
    }

    public CallConfiguration callConfig() {
      compile();
      return jitCallConfig;
    }

    @Override
    public String toString() {
      return methodName
          + "() at "
          + bodyNode.getPosition().getFile()
          + ":"
          + bodyNode.getPosition().getLine();
    }

    private final StandardASMCompiler asmCompiler;
    private final StaticScope staticScope;
    private final Node bodyNode;
    private final ArgsNode argsNode;
    private final Ruby ruby;
    private final String packageName;
    private final String className;
    private final String filename;
    private final String methodName;
    private final JITCounts counts;
    private final String digestString;

    private CallConfiguration jitCallConfig;
    private byte[] bytecode;
    private String name;
  }

  public Block newCompiledClosure(ThreadContext context, IterNode iterNode, IRubyObject self) {
    Binding binding = context.currentBinding(self);
    NodeType argsNodeId = getArgumentTypeWackyHack(iterNode);

    boolean hasMultipleArgsHead = false;
    if (iterNode.getVarNode() instanceof MultipleAsgnNode) {
      hasMultipleArgsHead = ((MultipleAsgnNode) iterNode.getVarNode()).getHeadNode() != null;
    }

    BlockBody body =
        new CompiledBlock(
            Arity.procArityOf(iterNode.getVarNode()),
            iterNode.getScope(),
            compileBlock(
                context,
                new StandardASMCompiler("blahfooblah" + System.currentTimeMillis(), "blahfooblah"),
                iterNode),
            hasMultipleArgsHead,
            BlockBody.asArgumentType(argsNodeId));
    return new Block(body, binding);
  }

  public BlockBody newCompiledBlockBody(
      ThreadContext context, IterNode iterNode, Arity arity, int argumentType) {
    NodeType argsNodeId = getArgumentTypeWackyHack(iterNode);

    boolean hasMultipleArgsHead = false;
    if (iterNode.getVarNode() instanceof MultipleAsgnNode) {
      hasMultipleArgsHead = ((MultipleAsgnNode) iterNode.getVarNode()).getHeadNode() != null;
    }
    return new CompiledBlock(
        Arity.procArityOf(iterNode.getVarNode()),
        iterNode.getScope(),
        compileBlock(
            context,
            new StandardASMCompiler("blahfooblah" + System.currentTimeMillis(), "blahfooblah"),
            iterNode),
        hasMultipleArgsHead,
        BlockBody.asArgumentType(argsNodeId));
  }

  // ENEBO: Some of this logic should be put back into the Nodes themselves, but the more
  // esoteric features of 1.9 make this difficult to know how to do this yet.
  public BlockBody newCompiledBlockBody19(ThreadContext context, IterNode iterNode) {
    final ArgsNode argsNode = (ArgsNode) iterNode.getVarNode();

    boolean hasMultipleArgsHead = false;
    if (iterNode.getVarNode() instanceof MultipleAsgnNode) {
      hasMultipleArgsHead = ((MultipleAsgnNode) iterNode.getVarNode()).getHeadNode() != null;
    }

    NodeType argsNodeId = BlockBody.getArgumentTypeWackyHack(iterNode);

    return new CompiledBlock19(
        ((ArgsNode) iterNode.getVarNode()).getArity(),
        iterNode.getScope(),
        compileBlock19(
            context,
            new StandardASMCompiler("blahfooblah" + System.currentTimeMillis(), "blahfooblah"),
            iterNode),
        hasMultipleArgsHead,
        BlockBody.asArgumentType(argsNodeId),
        Helpers.encodeParameterList(argsNode).split(";"));
  }

  public CompiledBlockCallback compileBlock(
      ThreadContext context, StandardASMCompiler asmCompiler, final IterNode iterNode) {
    final ASTCompiler astCompiler = new ASTCompiler();
    final StaticScope scope = iterNode.getScope();

    asmCompiler.startScript(scope);

    // create the closure class and instantiate it
    final CompilerCallback closureBody =
        new CompilerCallback() {

          public void call(BodyCompiler context) {
            if (iterNode.getBodyNode() != null) {
              astCompiler.compile(iterNode.getBodyNode(), context, true);
            } else {
              context.loadNil();
            }
          }
        };

    // create the closure class and instantiate it
    final CompilerCallback closureArgs =
        new CompilerCallback() {
          public void call(BodyCompiler context) {
            if (iterNode.getVarNode() != null) {
              astCompiler.compileAssignment(iterNode.getVarNode(), context);
            } else {
              context.consumeCurrentValue();
            }

            if (iterNode.getBlockVarNode() != null) {
              astCompiler.compileAssignment(iterNode.getBlockVarNode(), context);
            } else {
              context.consumeCurrentValue();
            }
          }
        };

    ASTInspector inspector = new ASTInspector();
    inspector.inspect(iterNode.getBodyNode());
    inspector.inspect(iterNode.getVarNode());

    int scopeIndex = asmCompiler.getCacheCompiler().reserveStaticScope();
    ChildScopedBodyCompiler closureCompiler =
        new ChildScopedBodyCompiler(
            asmCompiler, "__file__", asmCompiler.getClassname(), inspector, scope, scopeIndex);

    closureCompiler.beginMethod(closureArgs, scope);

    closureBody.call(closureCompiler);

    closureCompiler.endBody();

    // __file__ method with [] args; no-op
    SkinnyMethodAdapter method =
        new SkinnyMethodAdapter(
            asmCompiler.getClassVisitor(),
            ACC_PUBLIC,
            "__file__",
            getMethodSignature(4),
            null,
            null);
    method.start();

    method.aload(SELF_INDEX);
    method.areturn();
    method.end();

    // __file__ method to call static version
    method =
        new SkinnyMethodAdapter(
            asmCompiler.getClassVisitor(),
            ACC_PUBLIC,
            "__file__",
            getMethodSignature(1),
            null,
            null);
    method.start();

    // invoke static __file__
    method.aload(THIS);
    method.aload(THREADCONTEXT_INDEX);
    method.aload(SELF_INDEX);
    method.aload(ARGS_INDEX);
    method.aload(ARGS_INDEX + 1); // block
    method.invokestatic(
        asmCompiler.getClassname(),
        "__file__",
        getStaticMethodSignature(asmCompiler.getClassname(), 1));

    method.areturn();
    method.end();

    asmCompiler.endScript(false, false);

    byte[] bytes = asmCompiler.getClassByteArray();
    Class blockClass =
        new JRubyClassLoader(context.runtime.getJRubyClassLoader())
            .defineClass(asmCompiler.getClassname(), bytes);
    try {
      final AbstractScript script = (AbstractScript) blockClass.newInstance();
      script.setRootScope(scope);

      return new CompiledBlockCallback() {

        @Override
        public IRubyObject call(
            ThreadContext context, IRubyObject self, IRubyObject args, Block block) {
          return script.__file__(context, self, args, block);
        }

        @Override
        public String getFile() {
          return "blah";
        }

        @Override
        public int getLine() {
          return -1;
        }
      };
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  public CompiledBlockCallback19 compileBlock19(
      ThreadContext context, StandardASMCompiler asmCompiler, final IterNode iterNode) {
    final ASTCompiler19 astCompiler = new ASTCompiler19();
    final StaticScope scope = iterNode.getScope();

    asmCompiler.startScript(scope);

    final ArgsNode argsNode = (ArgsNode) iterNode.getVarNode();

    // create the closure class and instantiate it
    final CompilerCallback closureBody =
        new CompilerCallback() {
          public void call(BodyCompiler context) {
            if (iterNode.getBodyNode() != null) {
              astCompiler.compile(iterNode.getBodyNode(), context, true);
            } else {
              context.loadNil();
            }
          }
        };

    // create the closure class and instantiate it
    final CompilerCallback closureArgs =
        new CompilerCallback() {
          public void call(BodyCompiler context) {
            // FIXME: This is temporary since the variable compilers assume we want
            // args already on stack for assignment. We just pop and continue with
            // 1.9 args logic.
            context.consumeCurrentValue(); // args value
            context.consumeCurrentValue(); // passed block
            if (iterNode.getVarNode() != null) {
              if (iterNode instanceof LambdaNode) {
                final int required = argsNode.getRequiredArgsCount();
                final int opt = argsNode.getOptionalArgsCount();
                final int rest = argsNode.getRestArg();
                context.getVariableCompiler().checkMethodArity(required, opt, rest);
                astCompiler.compileMethodArgs(argsNode, context, true);
              } else {
                astCompiler.compileMethodArgs(argsNode, context, true);
              }
            }
          }
        };

    ASTInspector inspector = new ASTInspector();
    inspector.inspect(iterNode.getBodyNode());
    inspector.inspect(iterNode.getVarNode());

    NodeType argsNodeId = BlockBody.getArgumentTypeWackyHack(iterNode);

    int scopeIndex = asmCompiler.getCacheCompiler().reserveStaticScope();
    ChildScopedBodyCompiler closureCompiler =
        new ChildScopedBodyCompiler19(
            asmCompiler, "__file__", asmCompiler.getClassname(), inspector, scope, scopeIndex);

    closureCompiler.beginMethod(argsNodeId == null ? null : closureArgs, scope);

    closureBody.call(closureCompiler);

    closureCompiler.endBody();

    // __file__ method to call static version
    SkinnyMethodAdapter method =
        new SkinnyMethodAdapter(
            asmCompiler.getClassVisitor(),
            ACC_PUBLIC,
            "__file__",
            getMethodSignature(4),
            null,
            null);
    method.start();

    // invoke static __file__
    method.aload(THIS);
    method.aload(THREADCONTEXT_INDEX);
    method.aload(SELF_INDEX);
    method.aload(ARGS_INDEX);
    method.aload(ARGS_INDEX + 1); // block
    method.invokestatic(
        asmCompiler.getClassname(),
        "__file__",
        asmCompiler.getStaticMethodSignature(asmCompiler.getClassname(), 4));

    method.areturn();
    method.end();

    asmCompiler.endScript(false, false);

    byte[] bytes = asmCompiler.getClassByteArray();
    Class blockClass =
        new JRubyClassLoader(context.runtime.getJRubyClassLoader())
            .defineClass(asmCompiler.getClassname(), bytes);
    try {
      final AbstractScript script = (AbstractScript) blockClass.newInstance();
      script.setRootScope(scope);

      return new CompiledBlockCallback19() {

        @Override
        public IRubyObject call(
            ThreadContext context, IRubyObject self, IRubyObject[] args, Block block) {
          return script.__file__(context, self, args, block);
        }

        @Override
        public String getFile() {
          return iterNode.getPosition().getFile();
        }

        @Override
        public int getLine() {
          return iterNode.getPosition().getLine();
        }
      };
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  static void log(DefaultMethod method, String name, String message, String... reason) {
    String className = method.getImplementationClass().getBaseName();

    if (className == null) className = "<anon class>";

    StringBuilder builder =
        new StringBuilder(message + ":" + className + "." + name + " at " + method.getPosition());

    if (reason.length > 0) {
      builder.append(" because of: \"");
      for (int i = 0; i < reason.length; i++) {
        builder.append(reason[i]);
      }
      builder.append('"');
    }

    LOG.info(builder.toString());
  }

  private static String calculateFilename(ArgsNode argsNode, Node bodyNode) {
    if (bodyNode != null) return bodyNode.getPosition().getFile();
    if (argsNode != null) return argsNode.getPosition().getFile();

    return "__eval__";
  }
}
Ejemplo n.º 14
0
/**
 * ChannelDescriptor provides an abstraction similar to the concept of a "file descriptor" on any
 * POSIX system. In our case, it's a numbered object (fileno) enclosing a Channel (@see
 * java.nio.channels.Channel), FileDescriptor (@see java.io.FileDescriptor), and flags under which
 * the original open occured (@see org.jruby.util.io.ModeFlags). Several operations you would
 * normally expect to use with a POSIX file descriptor are implemented here and used by higher-level
 * classes to implement higher-level IO behavior.
 *
 * <p>Note that the channel specified when constructing a ChannelDescriptor will be
 * reference-counted; that is, until all known references to it through this class have gone away,
 * it will be left open. This is to support operations like "dup" which must produce two independent
 * ChannelDescriptor instances that can be closed separately without affecting the other.
 *
 * <p>At present there's no way to simulate the behavior on some platforms where POSIX dup also
 * allows independent positioning information.
 */
public class ChannelDescriptor {
  private static final Logger LOG = LoggerFactory.getLogger("ChannelDescriptor");

  /** Whether to log debugging information */
  private static final boolean DEBUG = false;

  /** The java.nio.channels.Channel this descriptor wraps. */
  private Channel channel;
  /**
   * The file number (equivalent to the int file descriptor value in POSIX) for this descriptor.
   * This is generated new for most ChannelDescriptor instances, except when they need to masquerade
   * as another fileno.
   */
  private int internalFileno;
  /** The java.io.FileDescriptor object for this descriptor. */
  private FileDescriptor fileDescriptor;
  /** The original org.jruby.util.io.ModeFlags with which the specified channel was opened. */
  private ModeFlags originalModes;
  /**
   * The reference count for the provided channel. Only counts references through ChannelDescriptor
   * instances.
   */
  private AtomicInteger refCounter;

  /**
   * Used to work-around blocking problems with STDIN. In most cases <code>null</code>. See {@link
   * ChannelDescriptor#ChannelDescriptor(java.io.InputStream, ModeFlags, java.io.FileDescriptor)}
   * for more details. You probably should not use it.
   */
  private InputStream baseInputStream;

  /**
   * Process streams get Channel.newChannel()ed into FileChannel but are not actually seekable. So
   * instead of just the isSeekable check doing instanceof FileChannel, we must also add this
   * boolean to check, which we set to false when it's known that the incoming channel is from a
   * process.
   *
   * <p>FIXME: This is gross, and it's NIO's fault for not providing a nice way to tell if a channel
   * is "really" seekable.
   */
  private boolean canBeSeekable = true;

  /**
   * If the incoming channel is already in append mode (i.e. it will do the requisite seeking), we
   * don't want to do our own additional seeks.
   */
  private boolean isInAppendMode = false;

  /** Whether the current channe is writable or not. */
  private boolean readableChannel;

  /** Whether the current channel is readable or not. */
  private boolean writableChannel;

  /** Whether the current channel is seekable or not. */
  private boolean seekableChannel;

  /**
   * Construct a new ChannelDescriptor with the specified channel, file number, mode flags, file
   * descriptor object and reference counter. This constructor is only used when constructing a new
   * copy of an existing ChannelDescriptor with an existing reference count, to allow the two
   * instances to safely share and appropriately close a given channel.
   *
   * @param channel The channel for the new descriptor, which will be shared with another
   * @param fileno The new file number for the new descriptor
   * @param originalModes The mode flags to use as the "origina" set for this descriptor
   * @param fileDescriptor The java.io.FileDescriptor object to associate with this
   *     ChannelDescriptor
   * @param refCounter The reference counter from another ChannelDescriptor being duped.
   * @param canBeSeekable If the underlying channel can be considered seekable.
   * @param isInAppendMode If the underlying channel is already in append mode.
   */
  private ChannelDescriptor(
      Channel channel,
      int fileno,
      ModeFlags originalModes,
      FileDescriptor fileDescriptor,
      AtomicInteger refCounter,
      boolean canBeSeekable,
      boolean isInAppendMode) {
    this.refCounter = refCounter;
    this.channel = channel;
    this.internalFileno = fileno;
    this.originalModes = originalModes;
    this.fileDescriptor = fileDescriptor;
    this.canBeSeekable = canBeSeekable;
    this.isInAppendMode = isInAppendMode;

    this.readableChannel = channel instanceof ReadableByteChannel;
    this.writableChannel = channel instanceof WritableByteChannel;
    this.seekableChannel = channel instanceof FileChannel;

    registerDescriptor(this);
  }

  private ChannelDescriptor(
      Channel channel, int fileno, ModeFlags originalModes, FileDescriptor fileDescriptor) {
    this(channel, fileno, originalModes, fileDescriptor, new AtomicInteger(1), true, false);
  }

  /**
   * Construct a new ChannelDescriptor with the given channel, file number, mode flags, and file
   * descriptor object. The channel will be kept open until all ChannelDescriptor references to it
   * have been closed.
   *
   * @param channel The channel for the new descriptor
   * @param originalModes The mode flags for the new descriptor
   * @param fileDescriptor The java.io.FileDescriptor object for the new descriptor
   */
  public ChannelDescriptor(
      Channel channel, ModeFlags originalModes, FileDescriptor fileDescriptor) {
    this(channel, getNewFileno(), originalModes, fileDescriptor, new AtomicInteger(1), true, false);
  }

  /**
   * Construct a new ChannelDescriptor with the given channel, file number, mode flags, and file
   * descriptor object. The channel will be kept open until all ChannelDescriptor references to it
   * have been closed.
   *
   * @param channel The channel for the new descriptor
   * @param originalModes The mode flags for the new descriptor
   * @param fileDescriptor The java.io.FileDescriptor object for the new descriptor
   */
  public ChannelDescriptor(
      Channel channel,
      ModeFlags originalModes,
      FileDescriptor fileDescriptor,
      boolean isInAppendMode) {
    this(
        channel,
        getNewFileno(),
        originalModes,
        fileDescriptor,
        new AtomicInteger(1),
        true,
        isInAppendMode);
  }

  /**
   * Construct a new ChannelDescriptor with the given channel, file number, mode flags, and file
   * descriptor object. The channel will be kept open until all ChannelDescriptor references to it
   * have been closed.
   *
   * @param channel The channel for the new descriptor
   * @param originalModes The mode flags for the new descriptor
   */
  public ChannelDescriptor(Channel channel, ModeFlags originalModes) {
    this(
        channel,
        getNewFileno(),
        originalModes,
        getDescriptorFromChannel(channel),
        new AtomicInteger(1),
        true,
        false);
  }

  /**
   * Special constructor to create the ChannelDescriptor out of the stream, file number, mode flags,
   * and file descriptor object. The channel will be created from the provided stream. The channel
   * will be kept open until all ChannelDescriptor references to it have been closed. <b>Note:</b>
   * in most cases, you should not use this constructor, it's reserved mostly for STDIN.
   *
   * @param baseInputStream The stream to create the channel for the new descriptor
   * @param originalModes The mode flags for the new descriptor
   * @param fileDescriptor The java.io.FileDescriptor object for the new descriptor
   */
  public ChannelDescriptor(
      InputStream baseInputStream, ModeFlags originalModes, FileDescriptor fileDescriptor) {
    // The reason why we need the stream is to be able to invoke available() on it.
    // STDIN in Java is non-interruptible, non-selectable, and attempt to read
    // on such stream might lead to thread being blocked without *any* way to unblock it.
    // That's where available() comes it, so at least we could check whether
    // anything is available to be read without blocking.
    this(
        Channels.newChannel(baseInputStream),
        getNewFileno(),
        originalModes,
        fileDescriptor,
        new AtomicInteger(1),
        true,
        false);
    this.baseInputStream = baseInputStream;
  }

  /**
   * Special constructor to create the ChannelDescriptor out of the stream, file number, mode flags,
   * and file descriptor object. The channel will be created from the provided stream. The channel
   * will be kept open until all ChannelDescriptor references to it have been closed. <b>Note:</b>
   * in most cases, you should not use this constructor, it's reserved mostly for STDIN.
   *
   * @param baseInputStream The stream to create the channel for the new descriptor
   * @param originalModes The mode flags for the new descriptor
   */
  public ChannelDescriptor(InputStream baseInputStream, ModeFlags originalModes) {
    // The reason why we need the stream is to be able to invoke available() on it.
    // STDIN in Java is non-interruptible, non-selectable, and attempt to read
    // on such stream might lead to thread being blocked without *any* way to unblock it.
    // That's where available() comes it, so at least we could check whether
    // anything is available to be read without blocking.
    this(
        Channels.newChannel(baseInputStream),
        getNewFileno(),
        originalModes,
        new FileDescriptor(),
        new AtomicInteger(1),
        true,
        false);
    this.baseInputStream = baseInputStream;
  }

  /**
   * Construct a new ChannelDescriptor with the given channel, file number, and file descriptor
   * object. The channel will be kept open until all ChannelDescriptor references to it have been
   * closed. The channel's capabilities will be used to determine the "original" set of mode flags.
   *
   * @param channel The channel for the new descriptor
   * @param fileDescriptor The java.io.FileDescriptor object for the new descriptor
   */
  public ChannelDescriptor(Channel channel, FileDescriptor fileDescriptor)
      throws InvalidValueException {
    this(channel, getModesFromChannel(channel), fileDescriptor);
  }

  @Deprecated
  public ChannelDescriptor(Channel channel, int fileno, FileDescriptor fileDescriptor)
      throws InvalidValueException {
    this(channel, getModesFromChannel(channel), fileDescriptor);
  }

  /**
   * Construct a new ChannelDescriptor with the given channel, file number, and file descriptor
   * object. The channel will be kept open until all ChannelDescriptor references to it have been
   * closed. The channel's capabilities will be used to determine the "original" set of mode flags.
   * This version generates a new fileno.
   *
   * @param channel The channel for the new descriptor
   */
  public ChannelDescriptor(Channel channel) throws InvalidValueException {
    this(channel, getModesFromChannel(channel), getDescriptorFromChannel(channel));
  }

  /**
   * Get this descriptor's file number.
   *
   * @return the fileno for this descriptor
   */
  public int getFileno() {
    return internalFileno;
  }

  /**
   * Get the FileDescriptor object associated with this descriptor. This is not guaranteed to be a
   * "valid" descriptor in the terms of the Java implementation, but is provided for completeness
   * and for cases where it is possible to get a valid FileDescriptor for a given channel.
   *
   * @return the java.io.FileDescriptor object associated with this descriptor
   */
  public FileDescriptor getFileDescriptor() {
    return fileDescriptor;
  }

  /**
   * The channel associated with this descriptor. The channel will be reference counted through
   * ChannelDescriptor and kept open until all ChannelDescriptor objects have been closed.
   * References that leave ChannelDescriptor through this method will not be counted.
   *
   * @return the java.nio.channels.Channel associated with this descriptor
   */
  public Channel getChannel() {
    return channel;
  }

  /**
   * This is intentionally non-public, since it should not be really used outside of very limited
   * use case (handling of STDIN). See {@link
   * ChannelDescriptor#ChannelDescriptor(java.io.InputStream, ModeFlags, java.io.FileDescriptor)}
   * for more info.
   */
  /*package-protected*/ InputStream getBaseInputStream() {
    return baseInputStream;
  }

  /**
   * Whether the channel associated with this descriptor is seekable (i.e. whether it is instanceof
   * FileChannel).
   *
   * @return true if the associated channel is seekable, false otherwise
   */
  public boolean isSeekable() {
    return canBeSeekable && seekableChannel;
  }

  /**
   * Set the channel to be explicitly seekable or not, for streams that appear to be seekable with
   * the instanceof FileChannel check.
   *
   * @param canBeSeekable Whether the channel is seekable or not.
   */
  public void setCanBeSeekable(boolean canBeSeekable) {
    this.canBeSeekable = canBeSeekable;
  }

  /**
   * Whether the channel associated with this descriptor is a NullChannel, for which many operations
   * are simply noops.
   */
  public boolean isNull() {
    return channel instanceof NullChannel;
  }

  /**
   * Whether the channel associated with this descriptor is writable (i.e. whether it is instanceof
   * WritableByteChannel).
   *
   * @return true if the associated channel is writable, false otherwise
   */
  public boolean isWritable() {
    return writableChannel;
  }

  /**
   * Whether the channel associated with this descriptor is readable (i.e. whether it is instanceof
   * ReadableByteChannel).
   *
   * @return true if the associated channel is readable, false otherwise
   */
  public boolean isReadable() {
    return readableChannel;
  }

  /**
   * Whether the channel associated with this descriptor is open.
   *
   * @return true if the associated channel is open, false otherwise
   */
  public boolean isOpen() {
    return channel.isOpen();
  }

  /**
   * Check whether the isOpen returns true, raising a BadDescriptorException if it returns false.
   *
   * @throws org.jruby.util.io.BadDescriptorException if isOpen returns false
   */
  public void checkOpen() throws BadDescriptorException {
    if (!isOpen()) {
      throw new BadDescriptorException();
    }
  }

  /**
   * Get the original mode flags for the descriptor.
   *
   * @return the original mode flags for the descriptor
   */
  public ModeFlags getOriginalModes() {
    return originalModes;
  }

  /**
   * Check whether a specified set of mode flags is a superset of this descriptor's original set of
   * mode flags.
   *
   * @param newModes The modes to confirm as superset
   * @throws org.jruby.util.io.InvalidValueException if the modes are not a superset
   */
  public void checkNewModes(ModeFlags newModes) throws InvalidValueException {
    if (!newModes.isSubsetOf(originalModes)) {
      throw new InvalidValueException();
    }
  }

  /**
   * Mimics the POSIX dup(2) function, returning a new descriptor that references the same open
   * channel.
   *
   * @return A duplicate ChannelDescriptor based on this one
   */
  public ChannelDescriptor dup() {
    synchronized (refCounter) {
      refCounter.incrementAndGet();

      int newFileno = getNewFileno();

      if (DEBUG) LOG.info("Reopen fileno {}, refs now: {}", newFileno, refCounter.get());

      return new ChannelDescriptor(
          channel,
          newFileno,
          originalModes,
          fileDescriptor,
          refCounter,
          canBeSeekable,
          isInAppendMode);
    }
  }

  /**
   * Mimics the POSIX dup2(2) function, returning a new descriptor that references the same open
   * channel but with a specified fileno.
   *
   * @param fileno The fileno to use for the new descriptor
   * @return A duplicate ChannelDescriptor based on this one
   */
  public ChannelDescriptor dup2(int fileno) {
    synchronized (refCounter) {
      refCounter.incrementAndGet();

      if (DEBUG) LOG.info("Reopen fileno {}, refs now: {}", fileno, refCounter.get());

      return new ChannelDescriptor(
          channel,
          fileno,
          originalModes,
          fileDescriptor,
          refCounter,
          canBeSeekable,
          isInAppendMode);
    }
  }

  /**
   * Mimics the POSIX dup2(2) function, returning a new descriptor that references the same open
   * channel but with a specified fileno. This differs from the fileno version by making the target
   * descriptor into a new reference to the current descriptor's channel, closing what it originally
   * pointed to and preserving its original fileno.
   *
   * @param other the descriptor to dup this one into
   */
  public void dup2Into(ChannelDescriptor other) throws BadDescriptorException, IOException {
    synchronized (refCounter) {
      refCounter.incrementAndGet();

      if (DEBUG) LOG.info("Reopen fileno {}, refs now: {}", internalFileno, refCounter.get());

      other.close();

      other.channel = channel;
      other.originalModes = originalModes;
      other.fileDescriptor = fileDescriptor;
      other.refCounter = refCounter;
      other.canBeSeekable = canBeSeekable;
      other.readableChannel = readableChannel;
      other.writableChannel = writableChannel;
      other.seekableChannel = seekableChannel;
    }
  }

  public ChannelDescriptor reopen(Channel channel, ModeFlags modes) {
    return new ChannelDescriptor(channel, internalFileno, modes, fileDescriptor);
  }

  public ChannelDescriptor reopen(RandomAccessFile file, ModeFlags modes) throws IOException {
    return new ChannelDescriptor(file.getChannel(), internalFileno, modes, file.getFD());
  }

  /**
   * Perform a low-level seek operation on the associated channel if it is instanceof FileChannel,
   * or raise PipeException if it is not a FileChannel. Calls checkOpen to confirm the target
   * channel is open. This is equivalent to the lseek(2) POSIX function, and like that function it
   * bypasses any buffer flushing or invalidation as in ChannelStream.fseek.
   *
   * @param offset the offset value to use
   * @param whence whence to seek
   * @throws java.io.IOException If there is an exception while seeking
   * @throws org.jruby.util.io.InvalidValueException If the value specified for offset or whence is
   *     invalid
   * @throws org.jruby.util.io.PipeException If the target channel is not seekable
   * @throws org.jruby.util.io.BadDescriptorException If the target channel is already closed.
   * @return the new offset into the FileChannel.
   */
  public long lseek(long offset, int whence)
      throws IOException, InvalidValueException, PipeException, BadDescriptorException {
    if (seekableChannel) {
      checkOpen();

      FileChannel fileChannel = (FileChannel) channel;
      try {
        long pos;
        switch (whence) {
          case Stream.SEEK_SET:
            pos = offset;
            fileChannel.position(pos);
            break;
          case Stream.SEEK_CUR:
            pos = fileChannel.position() + offset;
            fileChannel.position(pos);
            break;
          case Stream.SEEK_END:
            pos = fileChannel.size() + offset;
            fileChannel.position(pos);
            break;
          default:
            throw new InvalidValueException();
        }
        return pos;
      } catch (IllegalArgumentException e) {
        throw new InvalidValueException();
      } catch (IOException ioe) {
        // "invalid seek" means it's an ESPIPE, so we rethrow as a PipeException()
        if (ioe.getMessage().equals("Illegal seek")) {
          throw new PipeException();
        }
        throw ioe;
      }
    } else {
      throw new PipeException();
    }
  }

  /**
   * Perform a low-level read of the specified number of bytes into the specified byte list. The
   * incoming bytes will be appended to the byte list. This is equivalent to the read(2) POSIX
   * function, and like that function it ignores read and write buffers defined elsewhere.
   *
   * @param number the number of bytes to read
   * @param byteList the byte list on which to append the incoming bytes
   * @return the number of bytes actually read
   * @throws java.io.IOException if there is an exception during IO
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed.
   * @see org.jruby.util.ByteList
   */
  public int read(int number, ByteList byteList) throws IOException, BadDescriptorException {
    checkOpen();

    byteList.ensure(byteList.length() + number);
    int bytesRead =
        read(
            ByteBuffer.wrap(
                byteList.getUnsafeBytes(), byteList.begin() + byteList.length(), number));
    if (bytesRead > 0) {
      byteList.length(byteList.length() + bytesRead);
    }
    return bytesRead;
  }

  /**
   * Perform a low-level read of the remaining number of bytes into the specified byte buffer. The
   * incoming bytes will be used to fill the remaining space in the target byte buffer. This is
   * equivalent to the read(2) POSIX function, and like that function it ignores read and write
   * buffers defined elsewhere.
   *
   * @param buffer the java.nio.ByteBuffer in which to put the incoming bytes
   * @return the number of bytes actually read
   * @throws java.io.IOException if there is an exception during IO
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed
   * @see java.nio.ByteBuffer
   */
  public int read(ByteBuffer buffer) throws IOException, BadDescriptorException {
    checkOpen();

    // TODO: It would be nice to throw a better error for this
    if (!isReadable()) {
      throw new BadDescriptorException();
    }
    ReadableByteChannel readChannel = (ReadableByteChannel) channel;
    int bytesRead = 0;
    bytesRead = readChannel.read(buffer);

    return bytesRead;
  }

  /**
   * Write the bytes in the specified byte list to the associated channel.
   *
   * @param buffer the byte list containing the bytes to be written
   * @return the number of bytes actually written
   * @throws java.io.IOException if there is an exception during IO
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed
   */
  public int internalWrite(ByteBuffer buffer) throws IOException, BadDescriptorException {
    checkOpen();

    // TODO: It would be nice to throw a better error for this
    if (!isWritable()) {
      throw new BadDescriptorException();
    }

    WritableByteChannel writeChannel = (WritableByteChannel) channel;

    // if appendable, we always seek to the end before writing
    if (isSeekable() && originalModes.isAppendable()) {
      // if already in append mode, we don't do our own seeking
      if (!isInAppendMode) {
        FileChannel fileChannel = (FileChannel) channel;
        fileChannel.position(fileChannel.size());
      }
    }

    return writeChannel.write(buffer);
  }

  /**
   * Write the bytes in the specified byte list to the associated channel.
   *
   * @param buffer the byte list containing the bytes to be written
   * @return the number of bytes actually written
   * @throws java.io.IOException if there is an exception during IO
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed
   */
  public int write(ByteBuffer buffer) throws IOException, BadDescriptorException {
    checkOpen();

    return internalWrite(buffer);
  }

  /**
   * Write the bytes in the specified byte list to the associated channel.
   *
   * @param buf the byte list containing the bytes to be written
   * @return the number of bytes actually written
   * @throws java.io.IOException if there is an exception during IO
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed
   */
  public int write(ByteList buf) throws IOException, BadDescriptorException {
    checkOpen();

    return internalWrite(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin(), buf.length()));
  }

  /**
   * Write the bytes in the specified byte list to the associated channel.
   *
   * @param buf the byte list containing the bytes to be written
   * @param offset the offset to start at. this is relative to the begin variable in the but
   * @param len the amount of bytes to write. this should not be longer than the buffer
   * @return the number of bytes actually written
   * @throws java.io.IOException if there is an exception during IO
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed
   */
  public int write(ByteList buf, int offset, int len) throws IOException, BadDescriptorException {
    checkOpen();

    return internalWrite(ByteBuffer.wrap(buf.getUnsafeBytes(), buf.begin() + offset, len));
  }

  /**
   * Write the byte represented by the specified int to the associated channel.
   *
   * @param c The byte to write
   * @return 1 if the byte was written, 0 if not and -1 if there was an error (@see
   *     java.nio.channels.WritableByteChannel.write(java.nio.ByteBuffer))
   * @throws java.io.IOException If there was an exception during IO
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed
   */
  public int write(int c) throws IOException, BadDescriptorException {
    checkOpen();

    ByteBuffer buf = ByteBuffer.allocate(1);
    buf.put((byte) c);
    buf.flip();

    return internalWrite(buf);
  }

  /**
   * Open a new descriptor using the given working directory, file path, mode flags, and file
   * permission. This is equivalent to the open(2) POSIX function. See
   * org.jruby.util.io.ChannelDescriptor.open(String, String, ModeFlags, int, POSIX) for the version
   * that also sets file permissions.
   *
   * @param cwd the "current working directory" to use when opening the file
   * @param path the file path to open
   * @param flags the mode flags to use for opening the file
   * @return a new ChannelDescriptor based on the specified parameters
   */
  public static ChannelDescriptor open(String cwd, String path, ModeFlags flags)
      throws FileNotFoundException, DirectoryAsFileException, FileExistsException, IOException {
    return open(cwd, path, flags, 0, null, null);
  }

  /**
   * Open a new descriptor using the given working directory, file path, mode flags, and file
   * permission. This is equivalent to the open(2) POSIX function. See
   * org.jruby.util.io.ChannelDescriptor.open(String, String, ModeFlags, int, POSIX) for the version
   * that also sets file permissions.
   *
   * @param cwd the "current working directory" to use when opening the file
   * @param path the file path to open
   * @param flags the mode flags to use for opening the file
   * @param classLoader a ClassLoader to use for classpath: resources
   * @return a new ChannelDescriptor based on the specified parameters
   */
  public static ChannelDescriptor open(
      String cwd, String path, ModeFlags flags, ClassLoader classLoader)
      throws FileNotFoundException, DirectoryAsFileException, FileExistsException, IOException {
    return open(cwd, path, flags, 0, null, classLoader);
  }

  /**
   * Open a new descriptor using the given working directory, file path, mode flags, and file
   * permission. This is equivalent to the open(2) POSIX function.
   *
   * @param cwd the "current working directory" to use when opening the file
   * @param path the file path to open
   * @param flags the mode flags to use for opening the file
   * @param perm the file permissions to use when creating a new file (currently unobserved)
   * @param posix a POSIX api implementation, used for setting permissions; if null, permissions are
   *     ignored
   * @return a new ChannelDescriptor based on the specified parameters
   */
  public static ChannelDescriptor open(
      String cwd, String path, ModeFlags flags, int perm, POSIX posix)
      throws FileNotFoundException, DirectoryAsFileException, FileExistsException, IOException {
    return open(cwd, path, flags, perm, posix, null);
  }

  /**
   * Open a new descriptor using the given working directory, file path, mode flags, and file
   * permission. This is equivalent to the open(2) POSIX function.
   *
   * @param cwd the "current working directory" to use when opening the file
   * @param path the file path to open
   * @param flags the mode flags to use for opening the file
   * @param perm the file permissions to use when creating a new file (currently unobserved)
   * @param posix a POSIX api implementation, used for setting permissions; if null, permissions are
   *     ignored
   * @param classLoader a ClassLoader to use for classpath: resources
   * @return a new ChannelDescriptor based on the specified parameters
   */
  public static ChannelDescriptor open(
      String cwd, String path, ModeFlags flags, int perm, POSIX posix, ClassLoader classLoader)
      throws FileNotFoundException, DirectoryAsFileException, FileExistsException, IOException {
    if (path.equals("/dev/null") || path.equalsIgnoreCase("nul:") || path.equalsIgnoreCase("nul")) {
      Channel nullChannel = new NullChannel();
      // FIXME: don't use RubyIO for this
      return new ChannelDescriptor(nullChannel, flags);
    }

    if (path.startsWith("classpath:/") && classLoader != null) {
      path = path.substring("classpath:/".length());
      InputStream is = classLoader.getResourceAsStream(path);
      // FIXME: don't use RubyIO for this
      return new ChannelDescriptor(Channels.newChannel(is), flags);
    }

    return JRubyFile.createResource(posix, cwd, path).openDescriptor(flags, perm);
  }

  /**
   * Close this descriptor. If in closing the last ChannelDescriptor reference to the associate
   * channel is closed, the channel itself will be closed.
   *
   * @throws org.jruby.util.io.BadDescriptorException if the associated channel is already closed
   * @throws java.io.IOException if there is an exception during IO
   */
  public void close() throws BadDescriptorException, IOException {
    // tidy up
    finish(true);
  }

  void finish(boolean close) throws BadDescriptorException, IOException {
    synchronized (refCounter) {
      // if refcount is at or below zero, we're no longer valid
      if (refCounter.get() <= 0) {
        throw new BadDescriptorException();
      }

      // if channel is already closed, we're no longer valid
      if (!channel.isOpen()) {
        throw new BadDescriptorException();
      }

      // otherwise decrement and possibly close as normal
      if (close) {
        int count = refCounter.decrementAndGet();

        if (DEBUG) LOG.info("Descriptor for fileno {} refs: {}", internalFileno, count);

        if (count <= 0) {
          // if we're the last referrer, close the channel
          try {
            channel.close();
          } finally {
            unregisterDescriptor(internalFileno);
          }
        }
      }
    }
  }

  /**
   * Build a set of mode flags using the specified channel's actual capabilities.
   *
   * @param channel the channel to examine for capabilities
   * @return the mode flags
   * @throws org.jruby.util.io.InvalidValueException
   */
  private static ModeFlags getModesFromChannel(Channel channel) throws InvalidValueException {
    ModeFlags modes;
    if (channel instanceof ReadableByteChannel) {
      if (channel instanceof WritableByteChannel) {
        modes = new ModeFlags(RDWR);
      } else {
        modes = new ModeFlags(RDONLY);
      }
    } else if (channel instanceof WritableByteChannel) {
      modes = new ModeFlags(WRONLY);
    } else {
      // FIXME: I don't like this
      modes = new ModeFlags(RDWR);
    }

    return modes;
  }

  // FIXME shouldn't use static; would interfere with other runtimes in the same JVM
  protected static final AtomicInteger internalFilenoIndex = new AtomicInteger(2);

  public static int getNewFileno() {
    return internalFilenoIndex.incrementAndGet();
  }

  private static void registerDescriptor(ChannelDescriptor descriptor) {
    filenoDescriptorMap.put(descriptor.getFileno(), descriptor);
  }

  public static void unregisterDescriptor(int aFileno) {
    filenoDescriptorMap.remove(aFileno);
  }

  public static ChannelDescriptor getDescriptorByFileno(int aFileno) {
    return filenoDescriptorMap.get(aFileno);
  }

  public static Map<Integer, ChannelDescriptor> getFilenoDescriptorMapReadOnly() {
    return Collections.unmodifiableMap(filenoDescriptorMap);
  }

  private static final Map<Integer, ChannelDescriptor> filenoDescriptorMap =
      new ConcurrentHashMap<Integer, ChannelDescriptor>();

  private static final Class SEL_CH_IMPL;
  private static final Method SEL_CH_IMPL_GET_FD;
  private static final Class FILE_CHANNEL_IMPL;
  private static final Field FILE_CHANNEL_IMPL_FD;
  private static final Field FILE_DESCRIPTOR_FD;

  static {
    Method getFD;
    Class selChImpl;
    try {
      selChImpl = Class.forName("sun.nio.ch.SelChImpl");
      try {
        getFD = selChImpl.getMethod("getFD");
        getFD.setAccessible(true);
      } catch (Exception e) {
        getFD = null;
      }
    } catch (Exception e) {
      selChImpl = null;
      getFD = null;
    }
    SEL_CH_IMPL = selChImpl;
    SEL_CH_IMPL_GET_FD = getFD;

    Field fd;
    Class fileChannelImpl;
    try {
      fileChannelImpl = Class.forName("sun.nio.ch.FileChannelImpl");
      try {
        fd = fileChannelImpl.getDeclaredField("fd");
        fd.setAccessible(true);
      } catch (Exception e) {
        fd = null;
      }
    } catch (Exception e) {
      fileChannelImpl = null;
      fd = null;
    }
    FILE_CHANNEL_IMPL = fileChannelImpl;
    FILE_CHANNEL_IMPL_FD = fd;

    Field ffd;
    try {
      ffd = FileDescriptor.class.getDeclaredField("fd");
      ffd.setAccessible(true);
    } catch (Exception e) {
      ffd = null;
    }
    FILE_DESCRIPTOR_FD = ffd;
  }

  public static FileDescriptor getDescriptorFromChannel(Channel channel) {
    if (SEL_CH_IMPL_GET_FD != null && SEL_CH_IMPL.isInstance(channel)) {
      // Pipe Source and Sink, Sockets, and other several other selectable channels
      try {
        return (FileDescriptor) SEL_CH_IMPL_GET_FD.invoke(channel);
      } catch (Exception e) {
        // return bogus below
      }
    } else if (FILE_CHANNEL_IMPL_FD != null && FILE_CHANNEL_IMPL.isInstance(channel)) {
      // FileChannels
      try {
        return (FileDescriptor) FILE_CHANNEL_IMPL_FD.get(channel);
      } catch (Exception e) {
        // return bogus below
      }
    } else if (FILE_DESCRIPTOR_FD != null) {
      FileDescriptor unixFD = new FileDescriptor();

      // UNIX sockets, from jnr-unixsocket
      try {
        if (channel instanceof UnixSocketChannel) {
          FILE_DESCRIPTOR_FD.set(unixFD, ((UnixSocketChannel) channel).getFD());
          return unixFD;
        } else if (channel instanceof UnixServerSocketChannel) {
          FILE_DESCRIPTOR_FD.set(unixFD, ((UnixServerSocketChannel) channel).getFD());
          return unixFD;
        }
      } catch (Exception e) {
        // return bogus below
      }
    }
    return new FileDescriptor();
  }
}
Ejemplo n.º 15
0
/**
 * Right now, this class abstracts the following execution scopes: Method, Closure, Module, Class,
 * MetaClass Top-level Script, and Eval Script
 *
 * <p>In the compiler-land, IR versions of these scopes encapsulate only as much information as is
 * required to convert Ruby code into equivalent Java code.
 *
 * <p>But, in the non-compiler land, there will be a corresponding java object for some of these
 * scopes which encapsulates the runtime semantics and data needed for implementing them. In the
 * case of Module, Class, MetaClass, and Method, they also happen to be instances of the
 * corresponding Ruby classes -- so, in addition to providing code that help with this specific ruby
 * implementation, they also have code that let them behave as ruby instances of their corresponding
 * classes.
 *
 * <p>Examples: - the runtime class object might have refs. to the runtime method objects. - the
 * runtime method object might have a slot for a heap frame (for when it has closures that need
 * access to the method's local variables), it might have version information, it might have
 * references to other methods that were optimized with the current version number, etc. - the
 * runtime closure object will have a slot for a heap frame (for when it has closures within) and
 * might get reified as a method in the java land (but inaccessible in ruby land). So, passing
 * closures in Java land might be equivalent to passing around the method handles.
 *
 * <p>and so on ...
 */
public abstract class IRScope {
  private static final Logger LOG = LoggerFactory.getLogger("IRScope");

  private static Integer globalScopeCount = 0;

  /** Unique global scope id */
  private int scopeId;

  /** Name */
  private String name;

  /** File within which this scope has been defined */
  private final String fileName;

  /** Starting line for this scope's definition */
  private final int lineNumber;

  /** Lexical parent scope */
  private IRScope lexicalParent;

  /** Parser static-scope that this IR scope corresponds to */
  private StaticScope staticScope;

  /** Live version of module within whose context this method executes */
  private RubyModule containerModule;

  /** List of IR instructions for this method */
  private List<Instr> instrList;

  /** Control flow graph representation of this method's instructions */
  private CFG cfg;

  /** List of (nested) closures in this scope */
  private List<IRClosure> nestedClosures;

  /** Local variables defined in this scope */
  private Set<Variable> definedLocalVars;

  /** Local variables used in this scope */
  private Set<Variable> usedLocalVars;

  /** Is %block implicit block arg unused? */
  private boolean hasUnusedImplicitBlockArg;

  /** %current_module and %current_scope variables */
  private TemporaryVariable currentModuleVar;

  private TemporaryVariable currentScopeVar;

  /** Map of name -> dataflow problem */
  private Map<String, DataFlowProblem> dfProbs;

  private Instr[] linearizedInstrArray;
  private List<BasicBlock> linearizedBBList;
  protected int temporaryVariableIndex;

  /** Keeps track of types of prefix indexes for variables and labels */
  private Map<String, Integer> nextVarIndex;

  // Index values to guarantee we don't assign same internal index twice
  private int nextClosureIndex;

  // List of all scopes this scope contains lexically.  This is not used
  // for execution, but is used during dry-runs for debugging.
  List<IRScope> lexicalChildren;

  protected static class LocalVariableAllocator {
    public int nextSlot;
    public Map<String, LocalVariable> varMap;

    public LocalVariableAllocator() {
      varMap = new HashMap<String, LocalVariable>();
      nextSlot = 0;
    }

    public final LocalVariable getVariable(String name) {
      return varMap.get(name);
    }

    public final void putVariable(String name, LocalVariable var) {
      varMap.put(name, var);
      nextSlot++;
    }
  }

  LocalVariableAllocator localVars;
  LocalVariableAllocator evalScopeVars;

  /** Have scope flags been computed? */
  private boolean flagsComputed;

  /* *****************************************************************************************************
   * Does this execution scope (applicable only to methods) receive a block and use it in such a way that
   * all of the caller's local variables need to be materialized into a heap binding?
   * Ex:
   *    def foo(&b)
   *      eval 'puts a', b
   *    end
   *
   *    def bar
   *      a = 1
   *      foo {} # prints out '1'
   *    end
   *
   * Here, 'foo' can access all of bar's variables because it captures the caller's closure.
   *
   * There are 2 scenarios when this can happen (even this is conservative -- but, good enough for now)
   * 1. This method receives an explicit block argument (in this case, the block can be stored, passed around,
   *    eval'ed against, called, etc.).
   *    CAVEAT: This is conservative ... it may not actually be stored & passed around, evaled, called, ...
   * 2. This method has a 'super' call (ZSuper AST node -- ZSuperInstr IR instruction)
   *    In this case, the parent (in the inheritance hierarchy) can access the block and store it, etc.  So, in reality,
   *    rather than assume that the parent will always do this, we can query the parent, if we can precisely identify
   *    the parent method (which in the face of Ruby's dynamic hierarchy, we cannot).  So, be pessimistic.
   *
   * This logic was extracted from an email thread on the JRuby mailing list -- Yehuda Katz & Charles Nutter
   * contributed this analysis above.
   * ********************************************************************************************************/
  private boolean canCaptureCallersBinding;

  /* ****************************************************************************
   * Does this scope define code, i.e. does it (or anybody in the downward call chain)
   * do class_eval, module_eval? In the absence of any other information, we default
   * to yes -- which basically leads to pessimistic but safe optimizations.  But, for
   * library and internal methods, this might be false.
   * **************************************************************************** */
  private boolean canModifyCode;

  /* ****************************************************************************
   * Does this scope require a binding to be materialized?
   * Yes if any of the following holds true:
   * - calls 'Proc.new'
   * - calls 'eval'
   * - calls 'call' (could be a call on a stored block which could be local!)
   * - calls 'send' and we cannot resolve the message (method name) that is being sent!
   * - calls methods that can access the caller's binding
   * - calls a method which we cannot resolve now!
   * - has a call whose closure requires a binding
   * **************************************************************************** */
  private boolean bindingHasEscaped;

  /** Does this scope call any eval */
  private boolean usesEval;

  /** Does this scope receive keyword args? */
  private boolean receivesKeywordArgs;

  /** Does this scope have a break instr? */
  protected boolean hasBreakInstrs;

  /** Can this scope receive breaks */
  protected boolean canReceiveBreaks;

  /** Does this scope have a non-local return instr? */
  protected boolean hasNonlocalReturns;

  /** Can this scope receive a non-local return? */
  public boolean canReceiveNonlocalReturns;

  /**
   * Since backref ($~) and lastline ($_) vars are allocated space on the dynamic scope, this is an
   * useful flag to compute.
   */
  private boolean usesBackrefOrLastline;

  /** Does this scope call any zsuper */
  private boolean usesZSuper;

  /** Does this scope have loops? */
  private boolean hasLoops;

  /** # of thread poll instrs added to this scope */
  private int threadPollInstrsCount;

  /**
   * Does this scope have explicit call protocol instructions? If yes, there are IR instructions for
   * managing bindings/frames, etc. If not, this has to be managed implicitly as in the current
   * runtime For now, only dyn-scopes are managed explicitly. Others will come in time
   */
  private boolean hasExplicitCallProtocol;

  /** Should we re-run compiler passes -- yes after we've inlined, for example */
  private boolean relinearizeCFG;

  private IRManager manager;

  // Used by cloning code
  protected IRScope(IRScope s, IRScope lexicalParent) {
    this.lexicalParent = lexicalParent;
    this.manager = s.manager;
    this.fileName = s.fileName;
    this.lineNumber = s.lineNumber;
    this.staticScope = s.staticScope;
    this.threadPollInstrsCount = s.threadPollInstrsCount;
    this.nextClosureIndex = s.nextClosureIndex;
    this.temporaryVariableIndex = s.temporaryVariableIndex;
    this.hasLoops = s.hasLoops;
    this.hasUnusedImplicitBlockArg = s.hasUnusedImplicitBlockArg;
    this.instrList = null;
    this.nestedClosures = new ArrayList<IRClosure>();
    this.dfProbs = new HashMap<String, DataFlowProblem>();
    this.nextVarIndex = new HashMap<String, Integer>(); // SSS FIXME: clone!
    this.cfg = null;
    this.linearizedInstrArray = null;
    this.linearizedBBList = null;

    this.flagsComputed = s.flagsComputed;
    this.canModifyCode = s.canModifyCode;
    this.canCaptureCallersBinding = s.canCaptureCallersBinding;
    this.receivesKeywordArgs = s.receivesKeywordArgs;
    this.hasBreakInstrs = s.hasBreakInstrs;
    this.hasNonlocalReturns = s.hasNonlocalReturns;
    this.canReceiveBreaks = s.canReceiveBreaks;
    this.canReceiveNonlocalReturns = s.canReceiveNonlocalReturns;
    this.bindingHasEscaped = s.bindingHasEscaped;
    this.usesEval = s.usesEval;
    this.usesBackrefOrLastline = s.usesBackrefOrLastline;
    this.usesZSuper = s.usesZSuper;
    this.hasExplicitCallProtocol = s.hasExplicitCallProtocol;

    this.localVars = new LocalVariableAllocator(); // SSS FIXME: clone!
    this.localVars.nextSlot = s.localVars.nextSlot;
    this.relinearizeCFG = false;

    setupLexicalContainment();
  }

  public IRScope(
      IRManager manager,
      IRScope lexicalParent,
      String name,
      String fileName,
      int lineNumber,
      StaticScope staticScope) {
    this.manager = manager;
    this.lexicalParent = lexicalParent;
    this.name = name;
    this.fileName = fileName;
    this.lineNumber = lineNumber;
    this.staticScope = staticScope;
    this.threadPollInstrsCount = 0;
    this.nextClosureIndex = 0;
    this.temporaryVariableIndex = -1;
    this.instrList = new ArrayList<Instr>();
    this.nestedClosures = new ArrayList<IRClosure>();
    this.dfProbs = new HashMap<String, DataFlowProblem>();
    this.nextVarIndex = new HashMap<String, Integer>();
    this.cfg = null;
    this.linearizedInstrArray = null;
    this.linearizedBBList = null;
    this.hasLoops = false;
    this.hasUnusedImplicitBlockArg = false;

    this.flagsComputed = false;
    this.receivesKeywordArgs = false;
    this.hasBreakInstrs = false;
    this.hasNonlocalReturns = false;
    this.canReceiveBreaks = false;
    this.canReceiveNonlocalReturns = false;

    // These flags are true by default!
    this.canModifyCode = true;
    this.canCaptureCallersBinding = true;
    this.bindingHasEscaped = true;
    this.usesEval = true;
    this.usesBackrefOrLastline = true;
    this.usesZSuper = true;

    this.hasExplicitCallProtocol = false;

    this.localVars = new LocalVariableAllocator();
    synchronized (globalScopeCount) {
      this.scopeId = globalScopeCount++;
    }
    this.relinearizeCFG = false;

    setupLexicalContainment();
  }

  private final void setupLexicalContainment() {
    if (manager.isDryRun()) {
      lexicalChildren = new ArrayList<IRScope>();
      if (lexicalParent != null) lexicalParent.addChildScope(this);
    }
  }

  @Override
  public int hashCode() {
    return scopeId;
  }

  protected void addChildScope(IRScope scope) {
    lexicalChildren.add(scope);
  }

  public List<IRScope> getLexicalScopes() {
    return lexicalChildren;
  }

  public void addClosure(IRClosure c) {
    nestedClosures.add(c);
  }

  public Instr getLastInstr() {
    return instrList.get(instrList.size() - 1);
  }

  public void addInstrAtBeginning(Instr i) {
    instrList.add(0, i);
  }

  public void addInstr(Instr i) {
    // SSS FIXME: If more instructions set these flags, there may be
    // a better way to do this by encoding flags in its own object
    // and letting every instruction update it.
    if (i instanceof ThreadPollInstr) threadPollInstrsCount++;
    else if (i instanceof BreakInstr) this.hasBreakInstrs = true;
    else if (i instanceof NonlocalReturnInstr) this.hasNonlocalReturns = true;
    else if (i instanceof DefineMetaClassInstr) this.canReceiveNonlocalReturns = true;
    else if (i instanceof ReceiveKeywordArgInstr || i instanceof ReceiveKeywordRestArgInstr)
      this.receivesKeywordArgs = true;
    instrList.add(i);
  }

  public LocalVariable getNewFlipStateVariable() {
    return getLocalVariable("%flip_" + allocateNextPrefixedName("%flip"), 0);
  }

  public void initFlipStateVariable(Variable v, Operand initState) {
    // Add it to the beginning
    instrList.add(0, new CopyInstr(v, initState));
  }

  public boolean isForLoopBody() {
    return false;
  }

  public Label getNewLabel(String prefix) {
    return new Label(prefix + "_" + allocateNextPrefixedName(prefix));
  }

  public Label getNewLabel() {
    return getNewLabel("LBL");
  }

  public List<IRClosure> getClosures() {
    return nestedClosures;
  }

  public IRManager getManager() {
    return manager;
  }

  /** Returns the lexical scope that contains this scope definition */
  public IRScope getLexicalParent() {
    return lexicalParent;
  }

  public StaticScope getStaticScope() {
    return staticScope;
  }

  public IRMethod getNearestMethod() {
    IRScope current = this;

    while (current != null && !(current instanceof IRMethod)) {
      current = current.getLexicalParent();
    }

    return (IRMethod) current;
  }

  public IRScope getNearestFlipVariableScope() {
    IRScope current = this;

    while (current != null && !current.isFlipScope()) {
      current = current.getLexicalParent();
    }

    return current;
  }

  public IRScope getNearestTopLocalVariableScope() {
    IRScope current = this;

    while (current != null && !current.isTopLocalVariableScope()) {
      current = current.getLexicalParent();
    }

    return current;
  }

  /**
   * Returns the nearest scope which we can extract a live module from. If this returns null (like
   * for evals), then it means it cannot be statically determined.
   */
  public IRScope getNearestModuleReferencingScope() {
    IRScope current = this;

    while (!(current instanceof IRModuleBody)) {
      // When eval'ing, we dont have a lexical view of what module we are nested in
      // because binding_eval, class_eval, module_eval, instance_eval can switch
      // around the lexical scope for evaluation to be something else.
      if (current == null || current instanceof IREvalScript) return null;

      current = current.getLexicalParent();
    }

    return current;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) { // This is for IRClosure ;(
    this.name = name;
  }

  public String getFileName() {
    return fileName;
  }

  public int getLineNumber() {
    return lineNumber;
  }

  /** Returns the top level scope */
  public IRScope getTopLevelScope() {
    IRScope current = this;

    for (; current != null && !current.isScriptScope(); current = current.getLexicalParent()) {}

    return current;
  }

  public boolean isNestedInClosure(IRClosure closure) {
    for (IRScope s = this; s != null && !s.isTopLocalVariableScope(); s = s.getLexicalParent()) {
      if (s == closure) return true;
    }

    return false;
  }

  public void setHasLoopsFlag(boolean f) {
    hasLoops = true;
  }

  public boolean hasLoops() {
    return hasLoops;
  }

  public boolean hasExplicitCallProtocol() {
    return hasExplicitCallProtocol;
  }

  public void setExplicitCallProtocolFlag(boolean flag) {
    this.hasExplicitCallProtocol = flag;
  }

  public void setCodeModificationFlag(boolean f) {
    canModifyCode = f;
  }

  public boolean receivesKeywordArgs() {
    return this.receivesKeywordArgs;
  }

  public boolean modifiesCode() {
    return canModifyCode;
  }

  public boolean bindingHasEscaped() {
    return bindingHasEscaped;
  }

  public boolean usesBackrefOrLastline() {
    return usesBackrefOrLastline;
  }

  public boolean usesEval() {
    return usesEval;
  }

  public boolean usesZSuper() {
    return usesZSuper;
  }

  public boolean canCaptureCallersBinding() {
    return canCaptureCallersBinding;
  }

  public boolean canReceiveNonlocalReturns() {
    if (this.canReceiveNonlocalReturns) {
      return true;
    }

    boolean canReceiveNonlocalReturns = false;
    for (IRClosure cl : getClosures()) {
      if (cl.hasNonlocalReturns || cl.canReceiveNonlocalReturns()) {
        canReceiveNonlocalReturns = true;
      }
    }
    return canReceiveNonlocalReturns;
  }

  public CFG buildCFG() {
    cfg = new CFG(this);
    cfg.build(instrList);
    // Clear out instruction list after CFG has been built.
    this.instrList = null;
    return cfg;
  }

  protected void setCFG(CFG cfg) {
    this.cfg = cfg;
  }

  public CFG getCFG() {
    return cfg;
  }

  private void setupLabelPCs(HashMap<Label, Integer> labelIPCMap) {
    for (BasicBlock b : linearizedBBList) {
      Label l = b.getLabel();
      l.setTargetPC(labelIPCMap.get(l));
    }
  }

  private Instr[] prepareInstructionsForInterpretation() {
    checkRelinearization();

    if (linearizedInstrArray != null) return linearizedInstrArray; // Already prepared

    try {
      buildLinearization(); // FIXME: compiler passes should have done this
      depends(linearization());
    } catch (RuntimeException e) {
      LOG.error("Error linearizing cfg: ", e);
      CFG c = cfg();
      LOG.error("\nGraph:\n" + c.toStringGraph());
      LOG.error("\nInstructions:\n" + c.toStringInstrs());
      throw e;
    }

    // Set up IPCs
    HashMap<Label, Integer> labelIPCMap = new HashMap<Label, Integer>();
    List<Instr> newInstrs = new ArrayList<Instr>();
    int ipc = 0;
    for (BasicBlock b : linearizedBBList) {
      labelIPCMap.put(b.getLabel(), ipc);
      List<Instr> bbInstrs = b.getInstrs();
      int bbInstrsLength = bbInstrs.size();
      for (int i = 0; i < bbInstrsLength; i++) {
        Instr instr = bbInstrs.get(i);

        if (instr instanceof Specializeable) {
          instr = ((Specializeable) instr).specializeForInterpretation();
          bbInstrs.set(i, instr);
        }

        if (!(instr instanceof ReceiveSelfInstr)) {
          newInstrs.add(instr);
          ipc++;
        }
      }
    }

    // Set up label PCs
    setupLabelPCs(labelIPCMap);

    // Exit BB ipc
    cfg().getExitBB().getLabel().setTargetPC(ipc + 1);

    linearizedInstrArray = newInstrs.toArray(new Instr[newInstrs.size()]);
    return linearizedInstrArray;
  }

  private void runCompilerPasses() {
    // SSS FIXME: Why is this again?  Document this weirdness!
    // Forcibly clear out the shared eval-scope variable allocator each time this method executes
    initEvalScopeVariableAllocator(true);

    // SSS FIXME: We should configure different optimization levels
    // and run different kinds of analysis depending on time budget.  Accordingly, we need to set
    // IR levels/states (basic, optimized, etc.) and the
    // ENEBO: If we use a MT optimization mechanism we cannot mutate CFG
    // while another thread is using it.  This may need to happen on a clone()
    // and we may need to update the method to return the new method.  Also,
    // if this scope is held in multiple locations how do we update all references?
    for (CompilerPass pass : getManager().getCompilerPasses(this)) {
      pass.run(this);
    }
  }

  /** Run any necessary passes to get the IR ready for interpretation */
  public synchronized Instr[] prepareForInterpretation(boolean isLambda) {
    if (isLambda) {
      // Add a global ensure block to catch uncaught breaks
      // and throw a LocalJumpError.
      if (((IRClosure) this).addGEBForUncaughtBreaks()) {
        this.relinearizeCFG = true;
      }
    }

    checkRelinearization();

    if (linearizedInstrArray != null) return linearizedInstrArray;

    // Build CFG and run compiler passes, if necessary
    if (getCFG() == null) runCompilerPasses();

    // Linearize CFG, etc.
    return prepareInstructionsForInterpretation();
  }

  /* SSS FIXME: Do we need to synchronize on this?  Cache this info in a scope field? */
  /** Run any necessary passes to get the IR ready for compilation */
  public Tuple<Instr[], Map<Integer, Label[]>> prepareForCompilation() {
    // Build CFG and run compiler passes, if necessary
    if (getCFG() == null) runCompilerPasses();

    // Add this always since we dont re-JIT a previously
    // JIT-ted closure.  But, check if there are other
    // smarts available to us and eliminate adding this
    // code to every closure there is.
    //
    // Add a global ensure block to catch uncaught breaks
    // and throw a LocalJumpError.
    if (this instanceof IRClosure && ((IRClosure) this).addGEBForUncaughtBreaks()) {
      this.relinearizeCFG = true;
    }

    try {
      buildLinearization(); // FIXME: compiler passes should have done this
      depends(linearization());
    } catch (RuntimeException e) {
      LOG.error("Error linearizing cfg: ", e);
      CFG c = cfg();
      LOG.error("\nGraph:\n" + c.toStringGraph());
      LOG.error("\nInstructions:\n" + c.toStringInstrs());
      throw e;
    }

    // Set up IPCs
    // FIXME: Would be nice to collapse duplicate labels; for now, using Label[]
    HashMap<Integer, Label[]> ipcLabelMap = new HashMap<Integer, Label[]>();
    List<Instr> newInstrs = new ArrayList<Instr>();
    int ipc = 0;
    for (BasicBlock b : linearizedBBList) {
      Label l = b.getLabel();
      ipcLabelMap.put(ipc, catLabels(ipcLabelMap.get(ipc), l));
      for (Instr i : b.getInstrs()) {
        if (!(i instanceof ReceiveSelfInstr)) {
          newInstrs.add(i);
          ipc++;
        }
      }
    }

    return new Tuple<Instr[], Map<Integer, Label[]>>(
        newInstrs.toArray(new Instr[newInstrs.size()]), ipcLabelMap);
  }

  private List<Object[]> buildJVMExceptionTable() {
    List<Object[]> etEntries = new ArrayList<Object[]>();
    for (BasicBlock b : linearizedBBList) {
      // We need handlers for:
      // - Unrescuable    (handled by ensures),
      // - Throwable      (handled by rescues)
      // in that order since Throwable < Unrescuable
      BasicBlock rBB = cfg().getRescuerBBFor(b);
      BasicBlock eBB = cfg().getEnsurerBBFor(b);
      if ((eBB != null) && (rBB == eBB || rBB == null)) {
        // 1. same rescue and ensure handler ==> just spit out one entry with a Throwable class
        // 2. only ensure handler            ==> just spit out one entry with a Throwable class

        etEntries.add(new Object[] {b.getLabel(), eBB.getLabel(), Throwable.class});
      } else if (rBB != null) {
        // Unrescuable comes before Throwable
        if (eBB != null)
          etEntries.add(new Object[] {b.getLabel(), eBB.getLabel(), Unrescuable.class});
        etEntries.add(new Object[] {b.getLabel(), rBB.getLabel(), Throwable.class});
      }
    }

    // SSS FIXME: This could be optimized by compressing entries for adjacent BBs that have
    // identical handlers
    // This could be optimized either during generation or as another pass over the table.  But, if
    // the JVM
    // does that already, do we need to bother with it?
    return etEntries;
  }

  private static Label[] catLabels(Label[] labels, Label cat) {
    if (labels == null) return new Label[] {cat};
    Label[] newLabels = new Label[labels.length + 1];
    System.arraycopy(labels, 0, newLabels, 0, labels.length);
    newLabels[labels.length] = cat;
    return newLabels;
  }

  private boolean computeScopeFlags(boolean receivesClosureArg, List<Instr> instrs) {
    for (Instr i : instrs) {
      Operation op = i.getOperation();
      if (op == Operation.RECV_CLOSURE) {
        receivesClosureArg = true;
      } else if (op == Operation.ZSUPER) {
        this.canCaptureCallersBinding = true;
        this.usesZSuper = true;
      } else if (i instanceof CallBase) {
        CallBase call = (CallBase) i;

        if (call.targetRequiresCallersBinding()) this.bindingHasEscaped = true;

        if (call.canBeEval()) {
          this.usesEval = true;

          // If this method receives a closure arg, and this call is an eval that has more than 1
          // argument,
          // it could be using the closure as a binding -- which means it could be using pretty much
          // any
          // variable from the caller's binding!
          if (receivesClosureArg && (call.getCallArgs().length > 1)) {
            this.canCaptureCallersBinding = true;
          }
        }
      } else if (op == Operation.GET_GLOBAL_VAR) {
        GlobalVariable gv = (GlobalVariable) ((GetGlobalVariableInstr) i).getSource();
        String gvName = gv.getName();
        if (gvName.equals("$_")
            || gvName.equals("$~")
            || gvName.equals("$`")
            || gvName.equals("$'")
            || gvName.equals("$+")
            || gvName.equals("$LAST_READ_LINE")
            || gvName.equals("$LAST_MATCH_INFO")
            || gvName.equals("$PREMATCH")
            || gvName.equals("$POSTMATCH")
            || gvName.equals("$LAST_PAREN_MATCH")) {
          this.usesBackrefOrLastline = true;
        }
      } else if (op == Operation.PUT_GLOBAL_VAR) {
        GlobalVariable gv = (GlobalVariable) ((PutGlobalVarInstr) i).getTarget();
        String gvName = gv.getName();
        if (gvName.equals("$_") || gvName.equals("$~")) usesBackrefOrLastline = true;
      } else if (op == Operation.MATCH || op == Operation.MATCH2 || op == Operation.MATCH3) {
        this.usesBackrefOrLastline = true;
      } else if (op == Operation.BREAK) {
        this.hasBreakInstrs = true;
      } else if (i instanceof NonlocalReturnInstr) {
        this.hasNonlocalReturns = true;
      } else if (i instanceof DefineMetaClassInstr) {
        // SSS: Inner-classes are defined with closures and
        // a return in the closure can force a return from this method
        // For now conservatively assume that a scope with inner-classes
        // can receive non-local returns. (Alternatively, have to inspect
        // all lexically nested scopes, not just closures in computeScopeFlags())
        this.canReceiveNonlocalReturns = true;
      }
    }

    return receivesClosureArg;
  }

  //
  // This can help use eliminate writes to %block that are not used since this is
  // a special local-variable, not programmer-defined local-variable
  public void computeScopeFlags() {
    if (flagsComputed) {
      return;
    }

    // init
    canModifyCode = true;
    canCaptureCallersBinding = false;
    usesZSuper = false;
    usesEval = false;
    usesBackrefOrLastline = false;
    // NOTE: bindingHasEscaped is the crucial flag and it effectively is
    // unconditionally true whenever it has a call that receives a closure.
    // See CallInstr.computeRequiresCallersBindingFlag
    bindingHasEscaped =
        (this instanceof IREvalScript); // for eval scopes, bindings are considered escaped ...
    hasBreakInstrs = false;
    hasNonlocalReturns = false;
    canReceiveBreaks = false;
    canReceiveNonlocalReturns = false;

    // recompute flags -- we could be calling this method different times
    // definitely once after ir generation and local optimizations propagates constants locally
    // but potentially at a later time after doing ssa generation and constant propagation
    if (cfg == null) {
      computeScopeFlags(false, getInstrs());
    } else {
      boolean receivesClosureArg = false;
      for (BasicBlock b : cfg.getBasicBlocks()) {
        receivesClosureArg = computeScopeFlags(receivesClosureArg, b.getInstrs());
      }
    }

    // Compute flags for nested closures (recursively) and set derived flags.
    for (IRClosure cl : getClosures()) {
      cl.computeScopeFlags();
      if (cl.hasBreakInstrs || cl.canReceiveBreaks) {
        canReceiveBreaks = true;
      }
      if (cl.hasNonlocalReturns || cl.canReceiveNonlocalReturns) {
        canReceiveNonlocalReturns = true;
      }
      if (cl.usesZSuper()) {
        usesZSuper = true;
      }
    }

    flagsComputed = true;
  }

  public abstract String getScopeName();

  @Override
  public String toString() {
    return getScopeName() + " " + getName() + "[" + getFileName() + ":" + getLineNumber() + "]";
  }

  public String toStringInstrs() {
    StringBuilder b = new StringBuilder();

    int i = 0;
    for (Instr instr : instrList) {
      if (i > 0) b.append("\n");

      b.append("  ").append(i).append('\t').append(instr);

      i++;
    }

    if (!nestedClosures.isEmpty()) {
      b.append("\n\n------ Closures encountered in this scope ------\n");
      for (IRClosure c : nestedClosures) b.append(c.toStringBody());
      b.append("------------------------------------------------\n");
    }

    return b.toString();
  }

  public String toPersistableString() {
    StringBuilder b = new StringBuilder();

    b.append("Scope:<");
    b.append(name);
    b.append(">");
    for (Instr instr : instrList) {
      b.append("\n");
      b.append(instr);
    }

    return b.toString();
  }

  public String toStringVariables() {
    Map<Variable, Integer> ends = new HashMap<Variable, Integer>();
    Map<Variable, Integer> starts = new HashMap<Variable, Integer>();
    SortedSet<Variable> variables = new TreeSet<Variable>();

    for (int i = instrList.size() - 1; i >= 0; i--) {
      Instr instr = instrList.get(i);

      if (instr instanceof ResultInstr) {
        Variable var = ((ResultInstr) instr).getResult();
        variables.add(var);
        starts.put(var, i);
      }

      for (Operand operand : instr.getOperands()) {
        if (operand != null
            && operand instanceof Variable
            && ends.get((Variable) operand) == null) {
          ends.put((Variable) operand, i);
          variables.add((Variable) operand);
        }
      }
    }

    StringBuilder sb = new StringBuilder();
    int i = 0;
    for (Variable var : variables) {
      Integer end = ends.get(var);
      if (end != null) { // Variable is actually used somewhere and not dead
        if (i > 0) sb.append("\n");
        i++;
        sb.append("    ").append(var).append(": ").append(starts.get(var)).append("-").append(end);
      }
    }

    return sb.toString();
  }

  /**
   * --------------------------------------- SSS FIXME: What is this method for? @Interp public void
   * calculateParameterCounts() { for (int i = instrList.size() - 1; i >= 0; i--) { Instr instr =
   * instrList.get(i); } } ------------------------------------------ *
   */
  public LocalVariable getSelf() {
    return Self.SELF;
  }

  public Variable getCurrentModuleVariable() {
    // SSS: Used in only 3 cases in generated IR:
    // -> searching a constant in the inheritance hierarchy
    // -> searching a super-method in the inheritance hierarchy
    // -> looking up 'StandardError' (which can be eliminated by creating a special operand type for
    // this)
    if (currentModuleVar == null)
      currentModuleVar = getNewTemporaryVariable(Variable.CURRENT_MODULE);
    return currentModuleVar;
  }

  public Variable getCurrentScopeVariable() {
    // SSS: Used in only 1 case in generated IR:
    // -> searching a constant in the lexical scope hierarchy
    if (currentScopeVar == null) currentScopeVar = getNewTemporaryVariable(Variable.CURRENT_SCOPE);
    return currentScopeVar;
  }

  public abstract LocalVariable getImplicitBlockArg();

  public void markUnusedImplicitBlockArg() {
    hasUnusedImplicitBlockArg = true;
  }

  public LocalVariable findExistingLocalVariable(String name, int depth) {
    return localVars.getVariable(name);
  }

  /**
   * Find or create a local variable. By default, scopes are assumed to only check current depth.
   * Blocks/Closures override this because they have special nesting rules.
   */
  public LocalVariable getLocalVariable(String name, int scopeDepth) {
    LocalVariable lvar = findExistingLocalVariable(name, scopeDepth);
    if (lvar == null) {
      lvar = new LocalVariable(name, scopeDepth, localVars.nextSlot);
      localVars.putVariable(name, lvar);
    }

    return lvar;
  }

  public LocalVariable getNewLocalVariable(String name, int depth) {
    throw new RuntimeException(
        "getNewLocalVariable should be called for: " + this.getClass().getName());
  }

  protected void initEvalScopeVariableAllocator(boolean reset) {
    if (reset || evalScopeVars == null) evalScopeVars = new LocalVariableAllocator();
  }

  public TemporaryVariable getNewTemporaryVariable() {
    temporaryVariableIndex++;
    return new TemporaryVariable(temporaryVariableIndex);
  }

  public TemporaryVariable getNewTemporaryVariable(String name) {
    temporaryVariableIndex++;
    return new TemporaryVariable(name, temporaryVariableIndex);
  }

  public void resetTemporaryVariables() {
    temporaryVariableIndex = -1;
  }

  public int getTemporaryVariableSize() {
    return temporaryVariableIndex + 1;
  }

  // Generate a new variable for inlined code
  public Variable getNewInlineVariable(String inlinePrefix, Variable v) {
    if (v instanceof LocalVariable) {
      LocalVariable lv = (LocalVariable) v;
      return getLocalVariable(inlinePrefix + lv.getName(), lv.getScopeDepth());
    } else {
      return getNewTemporaryVariable();
    }
  }

  public int getThreadPollInstrsCount() {
    return threadPollInstrsCount;
  }

  public int getLocalVariablesCount() {
    return localVars.nextSlot;
  }

  public int getUsedVariablesCount() {
    // System.out.println("For " + this + ", # lvs: " + nextLocalVariableSlot);
    // # local vars, # flip vars
    //
    // SSS FIXME: When we are opting local var access,
    // no need to allocate local var space except when we have been asked to!
    return getLocalVariablesCount() + getPrefixCountSize("%flip");
  }

  public void setUpUseDefLocalVarMaps() {
    definedLocalVars = new java.util.HashSet<Variable>();
    usedLocalVars = new java.util.HashSet<Variable>();
    for (BasicBlock bb : cfg().getBasicBlocks()) {
      for (Instr i : bb.getInstrs()) {
        for (Variable v : i.getUsedVariables()) {
          if (v instanceof LocalVariable) usedLocalVars.add(v);
        }

        if (i instanceof ResultInstr) {
          Variable v = ((ResultInstr) i).getResult();

          if (v instanceof LocalVariable) definedLocalVars.add(v);
        }
      }
    }

    for (IRClosure cl : getClosures()) {
      cl.setUpUseDefLocalVarMaps();
    }
  }

  public boolean usesLocalVariable(Variable v) {
    if (usedLocalVars == null) setUpUseDefLocalVarMaps();
    if (usedLocalVars.contains(v)) return true;

    for (IRClosure cl : getClosures()) {
      if (cl.usesLocalVariable(v)) return true;
    }

    return false;
  }

  public boolean definesLocalVariable(Variable v) {
    if (definedLocalVars == null) setUpUseDefLocalVarMaps();
    if (definedLocalVars.contains(v)) return true;

    for (IRClosure cl : getClosures()) {
      if (cl.definesLocalVariable(v)) return true;
    }

    return false;
  }

  public void setDataFlowSolution(String name, DataFlowProblem p) {
    dfProbs.put(name, p);
  }

  public DataFlowProblem getDataFlowSolution(String name) {
    return dfProbs.get(name);
  }

  // This should only be used to do pre-cfg opts and to build the CFG.
  // Everyone else should use the CFG.
  public List<Instr> getInstrs() {
    if (cfg != null)
      throw new RuntimeException("Please use the CFG to access this scope's instructions.");
    return instrList;
  }

  public Instr[] getInstrsForInterpretation() {
    return linearizedInstrArray;
  }

  public void resetLinearizationData() {
    linearizedBBList = null;
    relinearizeCFG = false;
  }

  public void checkRelinearization() {
    if (relinearizeCFG) resetLinearizationData();
  }

  public List<BasicBlock> buildLinearization() {
    checkRelinearization();

    if (linearizedBBList != null) return linearizedBBList; // Already linearized

    linearizedBBList = CFGLinearizer.linearize(cfg);

    return linearizedBBList;
  }

  // SSS FIXME: Extremely inefficient
  public int getRescuerPC(Instr excInstr) {
    depends(cfg());

    for (BasicBlock b : linearizedBBList) {
      for (Instr i : b.getInstrs()) {
        if (i == excInstr) {
          BasicBlock rescuerBB = cfg().getRescuerBBFor(b);
          return (rescuerBB == null) ? -1 : rescuerBB.getLabel().getTargetPC();
        }
      }
    }

    // SSS FIXME: Cannot happen! Throw runtime exception
    LOG.error("Fell through looking for rescuer ipc for " + excInstr);
    return -1;
  }

  // SSS FIXME: Extremely inefficient
  public int getEnsurerPC(Instr excInstr) {
    depends(cfg());

    for (BasicBlock b : linearizedBBList) {
      for (Instr i : b.getInstrs()) {
        if (i == excInstr) {
          BasicBlock ensurerBB = cfg.getEnsurerBBFor(b);
          return (ensurerBB == null) ? -1 : ensurerBB.getLabel().getTargetPC();
        }
      }
    }

    // SSS FIXME: Cannot happen! Throw runtime exception
    LOG.error("Fell through looking for ensurer ipc for " + excInstr);
    return -1;
  }

  public List<BasicBlock> linearization() {
    depends(cfg());

    assert linearizedBBList != null : "You have not run linearization";

    return linearizedBBList;
  }

  protected void depends(Object obj) {
    assert obj != null
        : "Unsatisfied dependency and this depends() was set "
            + "up wrong.  Use depends(build()) not depends(build).";
  }

  public CFG cfg() {
    assert cfg != null : "Trying to access build before build started";
    return cfg;
  }

  public void splitCalls() {
    // FIXME: (Enebo) We are going to make a SplitCallInstr so this logic can be separate
    // from unsplit calls.  Comment out until new SplitCall is created.
    //        for (BasicBlock b: getNodes()) {
    //            List<Instr> bInstrs = b.getInstrs();
    //            for (ListIterator<Instr> it = ((ArrayList<Instr>)b.getInstrs()).listIterator();
    // it.hasNext(); ) {
    //                Instr i = it.next();
    //                // Only user calls, not Ruby & JRuby internal calls
    //                if (i.operation == Operation.CALL) {
    //                    CallInstr call = (CallInstr)i;
    //                    Operand   r    = call.getReceiver();
    //                    Operand   m    = call.getMethodAddr();
    //                    Variable  mh   = _scope.getNewTemporaryVariable();
    //                    MethodLookupInstr mli = new MethodLookupInstr(mh, m, r);
    //                    // insert method lookup at the right place
    //                    it.previous();
    //                    it.add(mli);
    //                    it.next();
    //                    // update call address
    //                    call.setMethodAddr(mh);
    //                }
    //            }
    //        }
    //
    //        List<IRClosure> nestedClosures = _scope.getClosures();
    //        if (!nestedClosures.isEmpty()) {
    //            for (IRClosure c : nestedClosures) {
    //                c.getCFG().splitCalls();
    //            }
    //        }
  }

  public void resetDFProblemsState() {
    dfProbs = new HashMap<String, DataFlowProblem>();
    for (IRClosure c : nestedClosures) c.resetDFProblemsState();
  }

  public void resetState() {
    relinearizeCFG = true;
    linearizedInstrArray = null;
    cfg.resetState();

    // reset flags
    flagsComputed = false;
    canModifyCode = true;
    canCaptureCallersBinding = true;
    bindingHasEscaped = true;
    usesEval = true;
    usesZSuper = true;
    hasBreakInstrs = false;
    hasNonlocalReturns = false;
    canReceiveBreaks = false;
    canReceiveNonlocalReturns = false;

    // Reset dataflow problems state
    resetDFProblemsState();
  }

  public void inlineMethod(
      IRScope method, RubyModule implClass, int classToken, BasicBlock basicBlock, CallBase call) {
    // Inline
    depends(cfg());
    new CFGInliner(cfg).inlineMethod(method, implClass, classToken, basicBlock, call);

    // Reset state
    resetState();

    // Re-run opts
    for (CompilerPass pass : getManager().getInliningCompilerPasses(this)) {
      pass.run(this);
    }
  }

  public void buildCFG(List<Instr> instrList) {
    CFG newBuild = new CFG(this);
    newBuild.build(instrList);
    cfg = newBuild;
  }

  public void resetCFG() {
    cfg = null;
  }

  /* Record a begin block -- not all scope implementations can handle them */
  public void recordBeginBlock(IRClosure beginBlockClosure) {
    throw new RuntimeException("BEGIN blocks cannot be added to: " + this.getClass().getName());
  }

  /* Record an end block -- not all scope implementations can handle them */
  public void recordEndBlock(IRClosure endBlockClosure) {
    throw new RuntimeException("END blocks cannot be added to: " + this.getClass().getName());
  }

  // Enebo: We should just make n primitive int and not take the hash hit
  protected int allocateNextPrefixedName(String prefix) {
    int index = getPrefixCountSize(prefix);

    nextVarIndex.put(prefix, index + 1);

    return index;
  }

  protected void resetVariableCounter(String prefix) {
    nextVarIndex.remove(prefix);
  }

  protected int getPrefixCountSize(String prefix) {
    Integer index = nextVarIndex.get(prefix);

    if (index == null) return 0;

    return index.intValue();
  }

  public RubyModule getContainerModule() {
    //        System.out.println("GET: container module of " + getName() + " with hc " + hashCode()
    // + " to " + containerModule.getName());
    return containerModule;
  }

  public int getNextClosureId() {
    nextClosureIndex++;

    return nextClosureIndex;
  }

  /**
   * Does this scope represent a module body? (SSS FIXME: what about script or eval script bodies?)
   */
  public boolean isModuleBody() {
    return false;
  }

  /** Is this IRClassBody but not IRMetaClassBody? */
  public boolean isNonSingletonClassBody() {
    return false;
  }

  public boolean isFlipScope() {
    return true;
  }

  public boolean isTopLocalVariableScope() {
    return true;
  }

  /** Is this an eval script or a regular file script? */
  public boolean isScriptScope() {
    return false;
  }
}
Ejemplo n.º 16
0
/**
 * Class used to launch the interpreter. This is the main class as defined in the jruby.mf manifest.
 * It is very basic and does not support yet the same array of switches as the C interpreter. Usage:
 * java -jar jruby.jar [switches] [rubyfile.rb] [arguments] -e 'command' one line of script. Several
 * -e's allowed. Omit [programfile]
 *
 * @author jpetersen
 */
public class Main {
  private static final Logger LOG = LoggerFactory.getLogger("Main");

  public Main(RubyInstanceConfig config) {
    this(config, false);
  }

  public Main(final InputStream in, final PrintStream out, final PrintStream err) {
    this(new RubyInstanceConfig(in, out, err));
  }

  public Main() {
    this(new RubyInstanceConfig());
  }

  private Main(RubyInstanceConfig config, boolean hardExit) {
    this.config = config;
    config.setHardExit(hardExit);
  }

  private Main(boolean hardExit) {
    // used only from main(String[]), so we process dotfile here
    processDotfile();
    this.config = new RubyInstanceConfig();
    config.setHardExit(hardExit);
  }

  private static List<String> getDotfileDirectories() {
    ArrayList<String> searchList = new ArrayList<String>();
    for (String homeProp : new String[] {"user.dir", "user.home"}) {
      String home = SafePropertyAccessor.getProperty(homeProp);
      if (home != null) searchList.add(home);
    }

    // JVM sometimes picks odd location for user.home based on a registry entry
    // (see http://bugs.sun.com/view_bug.do?bug_id=4787931).  Add extra check in
    // case that entry is wrong. Add before user.home in search list.
    if (Platform.IS_WINDOWS) {
      String homeDrive = System.getenv("HOMEDRIVE");
      String homePath = System.getenv("HOMEPATH");
      if (homeDrive != null && homePath != null) {
        searchList.add(1, (homeDrive + homePath).replace('\\', '/'));
      }
    }

    return searchList;
  }

  public static void processDotfile() {
    for (String home : getDotfileDirectories()) {
      File dotfile = new File(home + "/.jrubyrc");
      if (dotfile.exists()) loadJRubyProperties(dotfile);
    }
  }

  private static void loadJRubyProperties(File dotfile) {
    FileInputStream fis = null;

    try {
      // update system properties with long form jruby properties from .jrubyrc
      Properties sysProps = System.getProperties();
      Properties newProps = new Properties();
      // load properties and re-set as jruby.*
      fis = new FileInputStream(dotfile);
      newProps.load(fis);
      for (Map.Entry entry : newProps.entrySet()) {
        sysProps.put("jruby." + entry.getKey(), entry.getValue());
      }
    } catch (IOException ioe) {
      LOG.debug("exception loading " + dotfile, ioe);
    } catch (SecurityException se) {
      LOG.debug("exception loading " + dotfile, se);
    } finally {
      if (fis != null)
        try {
          fis.close();
        } catch (Exception e) {
        }
    }
  }

  public static class Status {
    private boolean isExit = false;
    private int status = 0;

    /**
     * Creates a status object with the specified value and with explicit exit flag. An exit flag
     * means that Kernel.exit() has been explicitly invoked during the run.
     *
     * @param status The status value.
     */
    Status(int status) {
      this.isExit = true;
      this.status = status;
    }

    /** Creates a status object with 0 value and no explicit exit flag. */
    Status() {}

    public boolean isExit() {
      return isExit;
    }

    public int getStatus() {
      return status;
    }
  }

  /**
   * This is the command-line entry point for JRuby, and should ONLY be used by Java when starting
   * up JRuby from a command-line. Use other mechanisms when embedding JRuby into another
   * application.
   *
   * @param args command-line args, provided by the JVM.
   */
  public static void main(String[] args) {
    doGCJCheck();

    Main main;

    if (DripMain.DRIP_RUNTIME != null) {
      main = new Main(DripMain.DRIP_CONFIG, true);
    } else {
      main = new Main(true);
    }

    try {
      Status status = main.run(args);
      if (status.isExit()) {
        System.exit(status.getStatus());
      }
    } catch (RaiseException rj) {
      System.exit(handleRaiseException(rj));
    } catch (Throwable t) {
      // print out as a nice Ruby backtrace
      System.err.println(ThreadContext.createRawBacktraceStringFromThrowable(t));
      while ((t = t.getCause()) != null) {
        System.err.println("Caused by:");
        System.err.println(ThreadContext.createRawBacktraceStringFromThrowable(t));
      }
      System.exit(1);
    }
  }

  public Status run(String[] args) {
    try {
      if (Options.TRUFFLE_PRINT_RUNTIME.load()) {
        config.getError().println("jruby: using " + Truffle.getRuntime().getName());
      }

      config.processArguments(args);
      return internalRun();
    } catch (MainExitException mee) {
      return handleMainExit(mee);
    } catch (OutOfMemoryError oome) {
      return handleOutOfMemory(oome);
    } catch (StackOverflowError soe) {
      return handleStackOverflow(soe);
    } catch (UnsupportedClassVersionError ucve) {
      return handleUnsupportedClassVersion(ucve);
    } catch (ThreadKill kill) {
      return new Status();
    }
  }

  @Deprecated
  public Status run() {
    return internalRun();
  }

  private Status internalRun() {
    doShowVersion();
    doShowCopyright();

    if (!config.getShouldRunInterpreter()) {
      doPrintUsage(false);
      doPrintProperties();
      return new Status();
    }

    InputStream in = config.getScriptSource();
    String filename = config.displayedFileName();

    doProcessArguments(in);

    Ruby _runtime;

    if (DripMain.DRIP_RUNTIME != null) {
      // use drip's runtime, reinitializing config
      _runtime = DripMain.DRIP_RUNTIME;
      _runtime.reinitialize(true);
    } else {
      _runtime = Ruby.newInstance(config);
    }

    final Ruby runtime = _runtime;
    final AtomicBoolean didTeardown = new AtomicBoolean();

    if (config.isHardExit()) {
      // we're the command-line JRuby, and should set a shutdown hook for
      // teardown.
      Runtime.getRuntime()
          .addShutdownHook(
              new Thread() {
                public void run() {
                  if (didTeardown.compareAndSet(false, true)) {
                    runtime.tearDown();
                  }
                }
              });
    }

    try {
      doSetContextClassLoader(runtime);

      if (in == null) {
        // no script to run, return success
        return new Status();
      } else if (config.isXFlag() && !config.hasShebangLine()) {
        // no shebang was found and x option is set
        throw new MainExitException(1, "jruby: no Ruby script found in input (LoadError)");
      } else if (config.getShouldCheckSyntax()) {
        // check syntax only and exit
        return doCheckSyntax(runtime, in, filename);
      } else {
        // proceed to run the script
        return doRunFromMain(runtime, in, filename);
      }
    } finally {
      if (didTeardown.compareAndSet(false, true)) {
        runtime.tearDown();
      }
    }
  }

  private Status handleUnsupportedClassVersion(UnsupportedClassVersionError ucve) {
    config
        .getError()
        .println("Error: Some library (perhaps JRuby) was built with a later JVM version.");
    config
        .getError()
        .println(
            "Please use libraries built with the version you intend to use or an earlier one.");
    if (config.isVerbose()) {
      config.getError().println("Exception trace follows:");
      ucve.printStackTrace();
    } else {
      config.getError().println("Specify -w for full UnsupportedClassVersionError stack trace");
    }
    return new Status(1);
  }

  /** Print a nicer stack size error since Rubyists aren't used to seeing this. */
  private Status handleStackOverflow(StackOverflowError soe) {
    String memoryMax = getRuntimeFlagValue("-Xss");

    if (memoryMax != null) {
      config
          .getError()
          .println(
              "Error: Your application used more stack memory than the safety cap of "
                  + memoryMax
                  + ".");
    } else {
      config
          .getError()
          .println("Error: Your application used more stack memory than the default safety cap.");
    }
    config.getError().println("Specify -J-Xss####k to increase it (#### = cap size in KB).");

    if (config.isVerbose()) {
      config.getError().println("Exception trace follows:");
      soe.printStackTrace(config.getError());
    } else {
      config.getError().println("Specify -w for full StackOverflowError stack trace");
    }

    return new Status(1);
  }

  /** Print a nicer memory error since Rubyists aren't used to seeing this. */
  private Status handleOutOfMemory(OutOfMemoryError oome) {
    System.gc(); // try to clean up a bit of space, hopefully, so we can report this error

    String oomeMessage = oome.getMessage();

    if (oomeMessage.contains("PermGen")) { // report permgen memory error
      config.getError().println("Error: Your application exhausted PermGen area of the heap.");
      config
          .getError()
          .println("Specify -J-XX:MaxPermSize=###M to increase it (### = PermGen size in MB).");

    } else { // report heap memory error

      String memoryMax = getRuntimeFlagValue("-Xmx");

      if (memoryMax != null) {
        config
            .getError()
            .println(
                "Error: Your application used more memory than the safety cap of "
                    + memoryMax
                    + ".");
      } else {
        config
            .getError()
            .println("Error: Your application used more memory than the default safety cap.");
      }
      config.getError().println("Specify -J-Xmx####m to increase it (#### = cap size in MB).");
    }

    if (config.isVerbose()) {
      config.getError().println("Exception trace follows:");
      oome.printStackTrace(config.getError());
    } else {
      config.getError().println("Specify -w for full OutOfMemoryError stack trace");
    }

    return new Status(1);
  }

  private String getRuntimeFlagValue(String prefix) {
    RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();

    for (String param : runtime.getInputArguments()) {
      if (param.startsWith(prefix)) {
        return param.substring(prefix.length()).toUpperCase();
      }
    }

    return null;
  }

  private Status handleMainExit(MainExitException mee) {
    if (!mee.isAborted()) {
      config.getOutput().println(mee.getMessage());
      if (mee.isUsageError()) {
        doPrintUsage(true);
      }
    }
    return new Status(mee.getStatus());
  }

  private Status doRunFromMain(Ruby runtime, InputStream in, String filename) {
    long now = -1;
    try {
      doCheckSecurityManager();

      runtime.runFromMain(in, filename);

      runtime.getTruffleBridge().shutdown();

    } catch (RaiseException rj) {
      return new Status(handleRaiseException(rj));
    }
    return new Status();
  }

  private Status doCheckSyntax(Ruby runtime, InputStream in, String filename)
      throws RaiseException {
    // check primary script
    boolean status = checkStreamSyntax(runtime, in, filename);

    // check other scripts specified on argv
    for (String arg : config.getArgv()) {
      status = status && checkFileSyntax(runtime, arg);
    }

    return new Status(status ? 0 : -1);
  }

  private boolean checkFileSyntax(Ruby runtime, String filename) {
    File file = new File(filename);
    if (file.exists()) {
      try {
        return checkStreamSyntax(runtime, new FileInputStream(file), filename);
      } catch (FileNotFoundException fnfe) {
        config.getError().println("File not found: " + filename);
        return false;
      }
    } else {
      return false;
    }
  }

  private boolean checkStreamSyntax(Ruby runtime, InputStream in, String filename) {
    try {
      runtime.parseFromMain(in, filename);
      config.getOutput().println("Syntax OK");
      return true;
    } catch (RaiseException re) {
      if (re.getException().getMetaClass().getBaseName().equals("SyntaxError")) {
        config
            .getError()
            .println("SyntaxError in " + re.getException().message(runtime.getCurrentContext()));
      } else {
        throw re;
      }
      return false;
    }
  }

  private void doSetContextClassLoader(Ruby runtime) {
    // set thread context JRuby classloader here, for the main thread
    try {
      Thread.currentThread().setContextClassLoader(runtime.getJRubyClassLoader());
    } catch (SecurityException se) {
      // can't set TC classloader
      if (runtime.getInstanceConfig().isVerbose()) {
        config
            .getError()
            .println(
                "WARNING: Security restrictions disallowed setting context classloader for main thread.");
      }
    }
  }

  private void doProcessArguments(InputStream in) {
    config.processArguments(config.parseShebangOptions(in));
  }

  private void doPrintProperties() {
    if (config.getShouldPrintProperties()) {
      config.getOutput().print(OutputStrings.getPropertyHelp());
    }
  }

  private void doPrintUsage(boolean force) {
    if (config.getShouldPrintUsage() || force) {
      config.getOutput().print(OutputStrings.getBasicUsageHelp());
    }
  }

  private void doShowCopyright() {
    if (config.isShowCopyright()) {
      config.getOutput().println(OutputStrings.getCopyrightString());
    }
  }

  private void doShowVersion() {
    if (config.isShowVersion()) {
      config.getOutput().println(OutputStrings.getVersionString());
    }
  }

  private static void doGCJCheck() {
    // Ensure we're not running on GCJ, since it's not supported and leads to weird errors
    if (Platform.IS_GCJ) {
      System.err.println("Fatal: GCJ (GNU Compiler for Java) is not supported by JRuby.");
      System.exit(1);
    }
  }

  private void doCheckSecurityManager() {
    if (Main.class.getClassLoader() == null && System.getSecurityManager() != null) {
      System.err.println(
          "Warning: security manager and JRuby running from boot classpath.\n"
              + "Run from jruby.jar or set env VERIFY_JRUBY=true to enable security.");
    }
  }

  private static int handleRaiseException(RaiseException rj) {
    RubyException raisedException = rj.getException();
    Ruby runtime = raisedException.getRuntime();
    if (runtime.getSystemExit().isInstance(raisedException)) {
      IRubyObject status = raisedException.callMethod(runtime.getCurrentContext(), "status");
      if (status != null && !status.isNil()) {
        return RubyNumeric.fix2int(status);
      } else {
        return 0;
      }
    } else {
      runtime
          .getErrorStream()
          .print(
              runtime
                  .getInstanceConfig()
                  .getTraceType()
                  .printBacktrace(raisedException, runtime.getPosix().isatty(FileDescriptor.err)));
      return 1;
    }
  }

  private final RubyInstanceConfig config;
}