Skip to content

Commit

Permalink
Showing 14 changed files with 313 additions and 8 deletions.
27 changes: 27 additions & 0 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@
import org.jruby.javasupport.JavaSupportImpl;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.invokedynamic.InvokeDynamicSupport;
import org.jruby.util.ClassDefiningClassLoader;
import org.objectweb.asm.util.TraceClassVisitor;

@@ -164,6 +165,7 @@
import java.io.InputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.invoke.MethodHandle;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
@@ -197,6 +199,9 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;

import static java.lang.invoke.MethodHandles.explicitCastArguments;
import static java.lang.invoke.MethodHandles.insertArguments;
import static java.lang.invoke.MethodType.methodType;
import static org.jruby.internal.runtime.GlobalVariable.Scope.GLOBAL;

/**
@@ -4880,6 +4885,23 @@ public DynamicMethod getBaseNewMethod() {
return baseNewMethod;
}

/**
* Get the "nullToNil" method handle filter for this runtime.
*
* @return a method handle suitable for filtering a single IRubyObject value from null to nil
*/
public MethodHandle getNullToNilHandle() {
MethodHandle nullToNil = this.nullToNil;

if (nullToNil != null) return nullToNil;

nullToNil = InvokeDynamicSupport.findStatic(Helpers.class, "nullToNil", methodType(IRubyObject.class, IRubyObject.class, IRubyObject.class));
nullToNil = insertArguments(nullToNil, 1, nilObject);
nullToNil = explicitCastArguments(nullToNil, methodType(IRubyObject.class, Object.class));

return this.nullToNil = nullToNil;
}

@Deprecated
public int getSafeLevel() {
return 0;
@@ -5238,4 +5260,9 @@ public void addToObjectSpace(boolean useObjectSpace, IRubyObject object) {
* The built-in Class#new method, so we can bind more directly to allocate and initialize.
*/
private DynamicMethod baseNewMethod;

/**
* The nullToNil filter for this runtime.
*/
private MethodHandle nullToNil;
}
15 changes: 14 additions & 1 deletion core/src/main/java/org/jruby/ir/IRBuilder.java
Original file line number Diff line number Diff line change
@@ -1023,13 +1023,26 @@ public Operand buildCall(CallNode callNode) {
Operand receiver = buildWithOrder(receiverNode, callNode.containsVariableAssignment());
Variable callResult = createTemporaryVariable();

ArrayNode argsAry;
if (
callNode.getName().equals("[]") &&
callNode.getArgsNode() instanceof ArrayNode &&
(argsAry = (ArrayNode) callNode.getArgsNode()).size() == 1 &&
argsAry.get(0) instanceof StrNode &&
!scope.maybeUsingRefinements()) {
StrNode keyNode = (StrNode) argsAry.get(0);
addInstr(ArrayDerefInstr.create(callResult, receiver, new FrozenString(keyNode.getValue(), keyNode.getCodeRange(), keyNode.getPosition().getFile(), keyNode.getLine())));
return callResult;
}

Operand[] args = setupCallArgs(callArgsNode);

Label lazyLabel = getNewLabel();
Label endLabel = getNewLabel();
if (callNode.isLazy()) {
addInstr(new BNilInstr(lazyLabel, receiver));
}

Operand[] args = setupCallArgs(callArgsNode);
Operand block = setupCallClosure(callNode.getIterNode());

CallInstr callInstr = CallInstr.create(scope, callResult, callNode.getName(), receiver, args, block);
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/IRVisitor.java
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ private void error(Object object) {
public void AliasInstr(AliasInstr aliasinstr) { error(aliasinstr); }
public void ArgScopeDepthInstr(ArgScopeDepthInstr instr) { error(instr); }
public void AttrAssignInstr(AttrAssignInstr attrassigninstr) { error(attrassigninstr); }
public void ArrayDerefInstr(ArrayDerefInstr arrayderefinstr) { error(arrayderefinstr); }
public void BacktickInstr(BacktickInstr instr) { error(instr); }
public void BEQInstr(BEQInstr beqinstr) { error(beqinstr); }
public void BFalseInstr(BFalseInstr bfalseinstr) { error(bfalseinstr); }
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/Operation.java
Original file line number Diff line number Diff line change
@@ -65,6 +65,7 @@ public enum Operation {
CALL(OpFlags.f_has_side_effect | OpFlags.f_is_call | OpFlags.f_can_raise_exception),
NORESULT_CALL(OpFlags.f_has_side_effect | OpFlags.f_is_call | OpFlags.f_can_raise_exception),
ATTR_ASSIGN(OpFlags.f_is_call | OpFlags.f_has_side_effect | OpFlags.f_can_raise_exception),
ARRAY_DEREF(OpFlags.f_is_call | OpFlags.f_has_side_effect | OpFlags.f_can_raise_exception),
CLASS_SUPER(OpFlags.f_has_side_effect | OpFlags.f_is_call | OpFlags.f_can_raise_exception),
INSTANCE_SUPER(OpFlags.f_has_side_effect | OpFlags.f_is_call | OpFlags.f_can_raise_exception),
UNRESOLVED_SUPER(OpFlags.f_has_side_effect | OpFlags.f_is_call | OpFlags.f_can_raise_exception),
85 changes: 85 additions & 0 deletions core/src/main/java/org/jruby/ir/instructions/ArrayDerefInstr.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.jruby.ir.instructions;

import org.jruby.RubyInstanceConfig;
import org.jruby.RubyString;
import org.jruby.ir.IRScope;
import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.instructions.specialized.OneOperandArgNoBlockCallInstr;
import org.jruby.ir.operands.FrozenString;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.persistence.IRReaderDecoder;
import org.jruby.ir.persistence.IRWriterEncoder;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.CallType;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import static org.jruby.ir.IRFlags.REQUIRES_FRAME;

/**
* Instruction representing Ruby code of the form: "a['str']"
* which is equivalent to: a.[]('str'). Because a Hash receiver
* would immediately freeze the string, we can freeze and dedup
* the string ahead of time and call [] directly.
*/
public class ArrayDerefInstr extends OneOperandArgNoBlockCallInstr {
private final FrozenString key;

public static ArrayDerefInstr create(Variable result, Operand obj, FrozenString arg0) {
return new ArrayDerefInstr(result, obj, arg0);
}

public ArrayDerefInstr(Variable result, Operand obj, FrozenString arg0) {
super(Operation.ARRAY_DEREF, CallType.FUNCTIONAL, result, "[]", obj, new Operand[] {arg0}, false);

key = arg0;
}

@Override
public boolean computeScopeFlags(IRScope scope) {
// CON: No native [] impls require backref/lastline for a literal String arg,
// so we don't have to deopt frame here.
super.computeScopeFlags(scope);
return true;
}

@Override
public Instr clone(CloneInfo ii) {
return new ArrayDerefInstr((Variable) getResult().cloneForInlining(ii), getReceiver().cloneForInlining(ii), key);
}

@Override
public void encode(IRWriterEncoder e) {
if (RubyInstanceConfig.IR_WRITING_DEBUG) System.out.println("Instr(" + getOperation() + "): " + this);
e.encode(getOperation());
e.encode(getResult());
e.encode(getReceiver());
e.encode(getArg1());
}

public static ArrayDerefInstr decode(IRReaderDecoder d) {
return create(d.decodeVariable(), d.decodeOperand(), (FrozenString) d.decodeOperand());
}

@Override
public Object interpret(ThreadContext context, StaticScope currScope, DynamicScope dynamicScope, IRubyObject self, Object[] temp) {
IRubyObject object = (IRubyObject) getReceiver().retrieve(context, self, currScope, dynamicScope, temp);
RubyString keyStr = (RubyString) key.retrieve(context, self, currScope, dynamicScope, temp);

return IRRuntimeHelpers.callOptimizedAref(context, self, object, keyStr, getCallSite());
}

@Override
public void visit(IRVisitor visitor) {
visitor.ArrayDerefInstr(this);
}

public FrozenString getKey() {
return key;
}
}
Original file line number Diff line number Diff line change
@@ -16,7 +16,12 @@
public class OneOperandArgNoBlockCallInstr extends CallInstr {
public OneOperandArgNoBlockCallInstr(CallType callType, Variable result, String name, Operand receiver,
Operand[] args, boolean isPotentiallyRefined) {
super(Operation.CALL_1O, callType, result, name, receiver, args, null, isPotentiallyRefined);
this(Operation.CALL_1O, callType, result, name, receiver, args, isPotentiallyRefined);
}

public OneOperandArgNoBlockCallInstr(Operation op, CallType callType, Variable result, String name, Operand receiver,
Operand[] args, boolean isPotentiallyRefined) {
super(op, callType, result, name, receiver, args, null, isPotentiallyRefined);
}

@Override
11 changes: 11 additions & 0 deletions core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java
Original file line number Diff line number Diff line change
@@ -1797,4 +1797,15 @@ public static RubyString freezeLiteralString(ThreadContext context, RubyString s

return string;
}

@JIT
public static IRubyObject callOptimizedAref(ThreadContext context, IRubyObject caller, IRubyObject target, RubyString keyStr, CallSite site) {
// FIXME: optimized builtin check for Hash#[]
if (target instanceof RubyHash) {
// call directly with cached frozen string
return ((RubyHash) target).op_aref(context, keyStr);
}

return site.call(context, caller, target, keyStr.strDup(context.runtime));
}
}
116 changes: 116 additions & 0 deletions core/src/main/java/org/jruby/ir/targets/ArrayDerefInvokeSite.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.jruby.ir.targets;

import com.headius.invokebinder.Binder;
import com.headius.invokebinder.SmartBinder;
import org.jruby.RubyClass;
import org.jruby.RubyHash;
import org.jruby.RubyString;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallType;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.callsite.CacheEntry;
import org.jruby.runtime.callsite.FunctionalCachingCallSite;
import org.jruby.util.JavaNameMangler;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;

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

import static org.jruby.util.CodegenUtils.p;
import static org.jruby.util.CodegenUtils.sig;

/**
* Created by headius on 10/23/14.
*/
public class ArrayDerefInvokeSite extends NormalInvokeSite {
public ArrayDerefInvokeSite(MethodType type) {
super(type, "[]");
}

public static final Handle BOOTSTRAP = new Handle(Opcodes.H_INVOKESTATIC, p(ArrayDerefInvokeSite.class), "bootstrap", sig(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class));

public static CallSite bootstrap(MethodHandles.Lookup lookup, String name, MethodType type) {
InvokeSite site = new ArrayDerefInvokeSite(type);

return InvokeSite.bootstrap(site, lookup);
}

public IRubyObject invoke(ThreadContext context, IRubyObject caller, IRubyObject self, IRubyObject[] args, Block block) throws Throwable {
RubyClass selfClass = pollAndGetClass(context, self);

MethodHandle mh;

CacheEntry entry = selfClass.searchWithCache(methodName);
DynamicMethod method = entry.method;

if (method.isBuiltin() && selfClass == context.runtime.getHash()) {
// fast path since we know we're working with a normal hash and have a pre-frozen string
mh = SmartBinder.from(signature)
.permute("self", "context", "arg0")
.cast(IRubyObject.class, RubyHash.class, ThreadContext.class, IRubyObject.class)
.invokeVirtual(MethodHandles.publicLookup(), "op_aref")
.handle();

SwitchPoint switchPoint = (SwitchPoint) selfClass.getInvalidator().getData();

updateInvocationTarget(mh, self, selfClass, method, switchPoint);

return ((RubyHash) self).fastARef(args[0]);
} else {
// slow path follows normal invoke logic with a strDup for the key
SwitchPoint switchPoint = (SwitchPoint) selfClass.getInvalidator().getData();

// strdup for this call
args[0] = ((RubyString) args[0]).strDup(context.runtime);

if (methodMissing(entry, caller)) {
return callMethodMissing(entry, callType, context, self, methodName, args, block);
}

mh = getHandle(self, selfClass, method);
// strdup for future calls
mh = MethodHandles.filterArguments(mh, 3, STRDUP_FILTER);

updateInvocationTarget(mh, self, selfClass, entry.method, 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;

// strdup for all calls
args[0] = ((RubyString) args[0]).strDup(context.runtime);

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);
}

private static final MethodHandle STRDUP_FILTER = Binder.from(IRubyObject.class, IRubyObject.class)
.cast(RubyString.class, RubyString.class)
.invokeVirtualQuiet(MethodHandles.publicLookup(), "strDup");
}
4 changes: 1 addition & 3 deletions core/src/main/java/org/jruby/ir/targets/Bootstrap.java
Original file line number Diff line number Diff line change
@@ -652,9 +652,7 @@ public static IRubyObject ivarGet(VariableSite site, IRubyObject self) throws Th
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));
MethodHandle nullToNil = self.getRuntime().getNullToNilHandle();

// get variable value and filter with nullToNil
MethodHandle getValue;
Original file line number Diff line number Diff line change
@@ -368,6 +368,15 @@ public org.objectweb.asm.Label newLabel() {
*/
public abstract void invokeOther(String name, int arity, boolean hasClosure, boolean isPotentiallyRefined);

/**
* Invoke the array dereferencing method ([]) on an object other than self.
*
* If this invokes against a Hash with a frozen string, it will follow an optimized path.
*
* Stack required: context, self, target, arg0
*/
public abstract void invokeArrayDeref();

/**
* Invoke a fixnum-receiving method on an object other than self.
*
24 changes: 24 additions & 0 deletions core/src/main/java/org/jruby/ir/targets/IRBytecodeAdapter6.java
Original file line number Diff line number Diff line change
@@ -317,6 +317,30 @@ public void invokeOther(String name, int arity, boolean hasClosure, boolean isPo
invoke(name, arity, hasClosure, CallType.NORMAL, isPotentiallyRefined);
}

public void invokeArrayDeref() {
SkinnyMethodAdapter adapter2;
String incomingSig = sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT, RubyString.class));

String methodName = getUniqueSiteName("[]");

adapter2 = new SkinnyMethodAdapter(
adapter.getClassVisitor(),
Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC,
methodName,
incomingSig,
null,
null);

adapter2.aloadMany(0, 1, 2, 3);
cacheCallSite(adapter2, getClassData().clsName, methodName, "[]", CallType.FUNCTIONAL, false);
adapter2.invokestatic(p(IRRuntimeHelpers.class), "callOptimizedAref", sig(IRubyObject.class, ThreadContext.class, IRubyObject.class, IRubyObject.class, RubyString.class, CallSite.class));
adapter2.areturn();
adapter2.end();

// now call it
adapter.invokestatic(getClassData().clsName, methodName, incomingSig);
}

public void invoke(String name, int arity, boolean hasClosure, CallType callType, boolean isPotentiallyRefined) {
if (arity > MAX_ARGUMENTS) throw new NotCompilableException("call to `" + name + "' has more than " + MAX_ARGUMENTS + " arguments");

Original file line number Diff line number Diff line change
@@ -148,6 +148,11 @@ public void invokeOther(String name, int arity, boolean hasClosure, boolean isPo
}
}

@Override
public void invokeArrayDeref() {
adapter.invokedynamic("aref", sig(JVM.OBJECT, params(ThreadContext.class, JVM.OBJECT, JVM.OBJECT, JVM.OBJECT, 1)), ArrayDerefInvokeSite.BOOTSTRAP);
}

public void invokeOtherOneFixnum(String name, long fixnum) {
String signature = sig(IRubyObject.class, params(ThreadContext.class, IRubyObject.class, IRubyObject.class));

6 changes: 3 additions & 3 deletions core/src/main/java/org/jruby/ir/targets/InvokeSite.java
Original file line number Diff line number Diff line change
@@ -132,7 +132,7 @@ public IRubyObject invoke(ThreadContext context, IRubyObject caller, IRubyObject

MethodHandle mh = getHandle(self, selfClass, method);

updateInvocationTarget(mh, self, selfClass, entry, switchPoint);
updateInvocationTarget(mh, self, selfClass, entry.method, switchPoint);

return method.call(context, self, selfClass, methodName, args, block);
}
@@ -230,7 +230,7 @@ MethodHandle buildNewInstanceHandle(DynamicMethod method, IRubyObject self, bool
* 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) {
MethodHandle updateInvocationTarget(MethodHandle target, IRubyObject self, RubyModule testClass, DynamicMethod method, SwitchPoint switchPoint) {
if (target == null ||
clearCount > Options.INVOKEDYNAMIC_MAXFAIL.load() ||
(!hasSeenType(testClass.id)
@@ -243,7 +243,7 @@ && seenTypesCount() + 1 > Options.INVOKEDYNAMIC_MAXPOLY.load())) {
// 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));
if (Options.INVOKEDYNAMIC_LOG_BINDING.load()) LOG.info(methodName + "\tadded to PIC " + logMethod(method));
fallback = getTarget();
} else {
// wipe out site with this new type and method
10 changes: 10 additions & 0 deletions core/src/main/java/org/jruby/ir/targets/JVMVisitor.java
Original file line number Diff line number Diff line change
@@ -444,6 +444,16 @@ public void AttrAssignInstr(AttrAssignInstr attrAssignInstr) {
attrAssignInstr.isPotentiallyRefined());
}

@Override
public void ArrayDerefInstr(ArrayDerefInstr arrayderefinstr) {
jvmMethod().loadContext();
jvmMethod().loadSelf();
visit(arrayderefinstr.getReceiver());
visit(arrayderefinstr.getKey());
jvmMethod().invokeArrayDeref();
jvmStoreLocal(arrayderefinstr.getResult());
}

@Override
public void BEQInstr(BEQInstr beqInstr) {
jvmMethod().loadContext();

0 comments on commit 2d9ff09

Please sign in to comment.