Skip to content

Commit

Permalink
Add first pass of JIT support for patterns
Browse files Browse the repository at this point in the history
This implements the following operations:

* Integer, to push a native int
* AsFixnum, to construct a RubyFixnum from an int
* ARRAY_LENGTH runtime helper to retrieve array's length as an int
* BIntInstr for branch operations against two ints

To support these, the following logic was added to manage int
local variables on the JVM stack:

* TemporaryIntVariable: similar to the prototype unboxed variables
  but stored boxed in the Object[] temp array in the interpreter.
  The JIT knows to use int when dealing with this type of var.
* int-based temps used by the pattern logic are switched to
  TemporaryIntVariable.

A few minor utility functions are added to SkinnyMethodAdapter,
including the oddly missing icmp_ge.
  • Loading branch information
headius committed Nov 29, 2023
1 parent cd3177e commit 03f2559
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 20 deletions.
4 changes: 4 additions & 0 deletions core/src/main/java/org/jruby/RubyFixnum.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ public static RubyFixnum newFixnum(Ruby runtime, long value) {
return new RubyFixnum(runtime, value);
}

public static RubyFixnum newFixnum(Ruby runtime, int value) {
return newFixnum(runtime, (long) value);
}

private static boolean isInCacheRange(long value) {
return value <= CACHE_OFFSET - 1 && value >= -CACHE_OFFSET;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,14 @@ public void pushInt(int value) {
ldc(value);
}
}

public void pushLong(long value) {
if (value == 0) {
lconst_0();
} else {
ldc(value);
}
}

public void pushBoolean(boolean bool) {
if (bool) iconst_1(); else iconst_0();
Expand Down Expand Up @@ -564,6 +572,10 @@ public void if_icmple(Label arg0) {
public void if_icmpgt(Label arg0) {
getMethodVisitor().visitJumpInsn(IF_ICMPGT, arg0);
}

public void if_icmpge(Label arg0) {
getMethodVisitor().visitJumpInsn(IF_ICMPGE, arg0);
}

public void if_icmplt(Label arg0) {
getMethodVisitor().visitJumpInsn(IF_ICMPLT, arg0);
Expand Down
47 changes: 35 additions & 12 deletions core/src/main/java/org/jruby/ir/IRBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -1381,7 +1381,7 @@ private void buildFindPattern(Label testEnd, Variable result, Variable deconstru
});
});

Variable length = addResultInstr(new RuntimeHelperCall(temp(), ARRAY_LENGTH, new Operand[]{deconstructed}));
Variable length = addResultInstr(new RuntimeHelperCall(intTemp(), ARRAY_LENGTH, new Operand[]{deconstructed}));
int fixedArgsLength = pattern.getArgs().size();
Operand argsNum = new Integer(fixedArgsLength);

Expand All @@ -1391,15 +1391,15 @@ private void buildFindPattern(Label testEnd, Variable result, Variable deconstru
jump(testEnd);
});

Variable limit = addResultInstr(new IntegerMathInstr(SUBTRACT, temp(), length, argsNum));
Variable limit = addResultInstr(new IntegerMathInstr(SUBTRACT, intTemp(), length, argsNum));
Variable i = copy(new Integer(0));

for_loop(after -> addInstr(new BIntInstr(after, BIntInstr.Op.GT, i, limit)),
after -> addInstr(new IntegerMathInstr(ADD, i, i, new Integer(1))),
(after, bottom) -> {
times(fixedArgsLength, (end_times, j) -> {
Node pat = pattern.getArgs().get(j.value);
Operand deconstructIndex = addResultInstr(new IntegerMathInstr(ADD, temp(), i, new Integer(j.value)));
Operand deconstructIndex = addResultInstr(new IntegerMathInstr(ADD, intTemp(), i, new Integer(j.value)));
Operand deconstructFixnum = as_fixnum(deconstructIndex);
Operand test = call(temp(), deconstructed, "[]", deconstructFixnum);
buildPatternMatch(result, copy(buildNil()), pat, test, false, isSinglePattern, errorString);
Expand All @@ -1416,7 +1416,7 @@ private void buildFindPattern(Label testEnd, Variable result, Variable deconstru

Node post = pattern.getPostRestArg();
if (post != null && !(post instanceof StarNode)) {
Operand deconstructIndex = addResultInstr(new IntegerMathInstr(ADD, createTemporaryVariable(), i, argsNum));
Operand deconstructIndex = addResultInstr(new IntegerMathInstr(ADD, intTemp(), i, argsNum));
Operand deconstructFixnum = as_fixnum(deconstructIndex);
Operand lengthFixnum = as_fixnum(length);
Operand test = call(temp(), deconstructed, "[]", deconstructFixnum, lengthFixnum);
Expand All @@ -1429,7 +1429,7 @@ private void buildFindPattern(Label testEnd, Variable result, Variable deconstru

private void buildArrayPattern(Label testEnd, Variable result, Variable deconstructed, ArrayPatternNode pattern,
Operand obj, boolean inAlteration, boolean isSinglePattern, Variable errorString) {
Variable restNum = addResultInstr(new CopyInstr(temp(), new Integer(0)));
Variable restNum = addResultInstr(new CopyInstr(intTemp(), new Integer(0)));

if (pattern.hasConstant()) {
Operand constant = build(pattern.getConstant());
Expand All @@ -1450,13 +1450,12 @@ private void buildArrayPattern(Label testEnd, Variable result, Variable deconstr
})
);

Operand minArgsCount = new Integer(pattern.minimumArgsNum());
Variable length = addResultInstr(new RuntimeHelperCall(createTemporaryVariable(), ARRAY_LENGTH, new Operand[]{deconstructed}));
Variable length = addResultInstr(new RuntimeHelperCall(intTemp(), ARRAY_LENGTH, new Operand[]{deconstructed}));
label("min_args_check_end", minArgsCheck -> {
BIntInstr.Op compareOp = pattern.hasRestArg() ? BIntInstr.Op.GTE : BIntInstr.Op.EQ;
addInstr(new BIntInstr(minArgsCheck, compareOp, length, minArgsCount));
addInstr(new BIntInstr(minArgsCheck, compareOp, length, new Integer(pattern.minimumArgsNum())));
fcall(errorString, buildSelf(), "sprintf",
new FrozenString("%s: %s length mismatch (given %d, expected %d)"), deconstructed, deconstructed, as_fixnum(length), as_fixnum(minArgsCount));
new FrozenString("%s: %s length mismatch (given %d, expected %d)"), deconstructed, deconstructed, as_fixnum(length), new Fixnum(pattern.minimumArgsNum()));
addInstr(new CopyInstr(result, fals()));
jump(testEnd);
});
Expand All @@ -1474,7 +1473,7 @@ private void buildArrayPattern(Label testEnd, Variable result, Variable deconstr
}

if (pattern.hasRestArg()) {
addInstr(new IntegerMathInstr(SUBTRACT, restNum, length, minArgsCount));
addInstr(new IntegerMathInstr(SUBTRACT, restNum, length, new Integer(pattern.minimumArgsNum())));

if (pattern.isNamedRestArg()) {
Variable min = copy(fix(preArgsSize));
Expand All @@ -1490,7 +1489,7 @@ private void buildArrayPattern(Label testEnd, Variable result, Variable deconstr
if (postArgs != null) {
for (int i = 0; i < postArgs.size(); i++) {
Label matchElementCheck = getNewLabel("match_post_args_element(i)_end");
Variable j = addResultInstr(new IntegerMathInstr(ADD, temp(), new Integer(i + preArgsSize), restNum));
Variable j = addResultInstr(new IntegerMathInstr(ADD, intTemp(), new Integer(i + preArgsSize), restNum));
Variable k = as_fixnum(j);
Variable elt = call(temp(), deconstructed, "[]", k);

Expand Down Expand Up @@ -1596,6 +1595,10 @@ private Variable temp() {
return createTemporaryVariable();
}

private Variable intTemp() {
return createIntVariable();
}

private Operand fals() {
return manager.getFalse();
}
Expand Down Expand Up @@ -4991,7 +4994,14 @@ public Variable copy(Operand value) {
}

public Variable copy(Variable result, Operand value) {
return addResultInstr(new CopyInstr(result == null ? createTemporaryVariable() : result, value));
if (result == null) {
if (value instanceof Integer || value instanceof TemporaryIntVariable) {
result = createIntVariable();
} else {
result = createTemporaryVariable();
}
}
return addResultInstr(new CopyInstr(result, value));
}

public Operand buildZArray(Variable result) {
Expand Down Expand Up @@ -5140,6 +5150,19 @@ private TemporaryVariable createTemporaryVariable() {
}
}

private TemporaryVariable createIntVariable() {
// BEGIN uses its parent builder to store any variables
if (variableBuilder != null) return variableBuilder.createIntVariable();

temporaryVariableIndex++;

if (scope.getScopeType() == IRScopeType.CLOSURE) {
throw new RuntimeException("primitive int variables not supported in closure");
} else {
return new TemporaryIntVariable(temporaryVariableIndex);
}
}

public LocalVariable getLocalVariable(RubySymbol name, int scopeDepth) {
return scope.getLocalVariable(name, scopeDepth);
}
Expand Down
18 changes: 18 additions & 0 deletions core/src/main/java/org/jruby/ir/IRManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,24 @@ public TemporaryLocalVariable newTemporaryLocalVariable(int index) {
return tempVar;
}

/**
* Temporarily provided for loading/storing a normal local as int on JVM; interpreter will still box as Integer.
* @param index
* @return
*/
public TemporaryLocalVariable newTemporaryIntVariable(int index) {
if (index >= temporaryLocalVariables.length-1) growTemporaryVariablePool(index);

TemporaryLocalVariable tempVar = temporaryLocalVariables[index];

if (tempVar == null || !(tempVar instanceof TemporaryIntVariable)) {
tempVar = new TemporaryIntVariable(index);
temporaryLocalVariables[index] = tempVar;
}

return tempVar;
}

/**
* For scopes that don't require a dynamic scope we can run DCE and some other passes which cannot
* be stymied by escaped bindings.
Expand Down
3 changes: 2 additions & 1 deletion core/src/main/java/org/jruby/ir/IRVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private void error(Object object) {
public void AttrAssignInstr(AttrAssignInstr attrassigninstr) { error(attrassigninstr); }
public void BFalseInstr(BFalseInstr bfalseinstr) { error(bfalseinstr); }
public void BlockGivenInstr(BlockGivenInstr blockgiveninstr) { error(blockgiveninstr); }
public void BGTEInstr(BIntInstr bneinstr) { error(bneinstr); }
public void BIntInstr(BIntInstr bIntInstr) { error(bIntInstr); }
public void BNEInstr(BNEInstr bneinstr) { error(bneinstr); }
public void BNilInstr(BNilInstr bnilinstr) { error(bnilinstr); }
public void BreakInstr(BreakInstr breakinstr) { error(breakinstr); }
Expand Down Expand Up @@ -204,6 +204,7 @@ private void error(Object object) {
public void TemporaryLocalVariable(TemporaryLocalVariable temporarylocalvariable) { error(temporarylocalvariable); }
public void TemporaryFloatVariable(TemporaryFloatVariable temporaryfloatvariable) { error(temporaryfloatvariable); }
public void TemporaryFixnumVariable(TemporaryFixnumVariable temporaryfixnumvariable) { error(temporaryfixnumvariable); }
public void TemporaryIntVariable(TemporaryIntVariable temporaryintvariable) { error(temporaryintvariable); }
public void TemporaryBooleanVariable(TemporaryBooleanVariable temporarybooleanvariable) { error(temporarybooleanvariable); }
public void UndefinedValue(UndefinedValue undefinedvalue) { error(undefinedvalue); }
public void UnexecutableNil(UnexecutableNil unexecutablenil) { error(unexecutablenil); }
Expand Down
6 changes: 5 additions & 1 deletion core/src/main/java/org/jruby/ir/instructions/BIntInstr.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ public int interpretAndGetNewIPC(ThreadContext context, DynamicScope currDynScop

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

@Override
public String[] toStringNonOperandArgs() {
return new String[] { op.toString() };
}

public Op getOp() {
return op;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,8 @@ public TemporaryLocalVariable getNewUnboxedVariable(Class type) {
varType = TemporaryVariableType.FIXNUM;
} else if (type == java.lang.Boolean.class) {
varType = TemporaryVariableType.BOOLEAN;
} else if (type == java.lang.Integer.class) {
varType = TemporaryVariableType.INT;
} else {
varType = TemporaryVariableType.LOCAL;
}
Expand All @@ -347,6 +349,10 @@ public TemporaryLocalVariable getNewTemporaryVariable(TemporaryVariableType type
case LOCAL: {
return getScope().getManager().newTemporaryLocalVariable(temporaryVariableCount - 1);
}
// FIXME: TemporaryIntegerVariable is being stored boxed since the primitive temp arrays are not wired up
case INT: {
return getScope().getManager().newTemporaryIntVariable(temporaryVariableCount - 1);
}
}

throw new RuntimeException("Invalid temporary variable being alloced in this scope: " + type);
Expand Down
71 changes: 71 additions & 0 deletions core/src/main/java/org/jruby/ir/operands/TemporaryIntVariable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
**** BEGIN LICENSE BLOCK *****
* Version: EPL 2.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v20.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Copyright (C) 2014 The JRuby Team ([email protected])
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/

package org.jruby.ir.operands;

import org.jruby.ir.IRVisitor;
import org.jruby.ir.persistence.IRReaderDecoder;
import org.jruby.ir.transformations.inlining.SimpleCloneInfo;

/**
* Represents a temporary variable for an unboxed int operand.
*
* FIXME: this does not mesh with the specialized types like TemporaryFixnum and TemporaryFloat and is added solely to
* support primitive integer operations during pattern matching.
*/
public class TemporaryIntVariable extends TemporaryLocalVariable {
public static final String PREFIX = "%i_";
public TemporaryIntVariable(int offset) {
super(offset);
}

@Override
public TemporaryVariableType getType() {
return TemporaryVariableType.INT;
}

@Override
public String getPrefix() {
return PREFIX;
}

@Override
public Variable clone(SimpleCloneInfo ii) {
return this;
}

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

public static TemporaryIntVariable decode(IRReaderDecoder d) {
return new TemporaryIntVariable(d.decodeInt());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* and we want to be able to quickly switch on type.
*/
public enum TemporaryVariableType {
LOCAL, BOOLEAN, FLOAT, FIXNUM, CLOSURE, CURRENT_MODULE;
LOCAL, BOOLEAN, FLOAT, INT, FIXNUM, CLOSURE, CURRENT_MODULE;

private static final TemporaryVariableType[] VALUES = values();

Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/org/jruby/ir/persistence/IRDumper.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.jruby.ir.operands.TemporaryBooleanVariable;
import org.jruby.ir.operands.TemporaryFixnumVariable;
import org.jruby.ir.operands.TemporaryFloatVariable;
import org.jruby.ir.operands.TemporaryIntVariable;
import org.jruby.ir.operands.TemporaryLocalVariable;
import org.jruby.ir.operands.TemporaryVariable;
import org.jruby.ir.operands.UnboxedBoolean;
Expand Down Expand Up @@ -334,6 +335,7 @@ public void StandardError(StandardError standarderror) { }
public void TemporaryVariable(TemporaryVariable temporaryvariable) { print(temporaryvariable.getId()); }
public void TemporaryLocalVariable(TemporaryLocalVariable temporarylocalvariable) { TemporaryVariable(temporarylocalvariable); }
public void TemporaryFloatVariable(TemporaryFloatVariable temporaryfloatvariable) { TemporaryVariable(temporaryfloatvariable); }
public void TemporaryIntVariable(TemporaryIntVariable temporaryintvariable) { TemporaryVariable(temporaryintvariable); }
public void TemporaryFixnumVariable(TemporaryFixnumVariable temporaryfixnumvariable) { TemporaryVariable(temporaryfixnumvariable); }
public void TemporaryBooleanVariable(TemporaryBooleanVariable temporarybooleanvariable) { TemporaryVariable(temporarybooleanvariable); }
public void UndefinedValue(UndefinedValue undefinedvalue) { }
Expand Down
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/targets/JVM.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public static String scriptToClass(String name) {
public static final Type BOOLEAN_TYPE = Type.BOOLEAN_TYPE;
public static final Type DOUBLE_TYPE = Type.DOUBLE_TYPE;
public static final Type LONG_TYPE = Type.LONG_TYPE;
public static final Type INT_TYPE = Type.INT_TYPE;
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);
Expand Down
Loading

0 comments on commit 03f2559

Please sign in to comment.