org.jruby.compiler.impl.BaseBodyCompiler Maven / Gradle / Ivy
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.jruby.compiler.impl;
import java.io.PrintStream;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.jcodings.Encoding;
import org.jruby.MetaClass;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyBignum;
import org.jruby.RubyBoolean;
import org.jruby.RubyClass;
import org.jruby.RubyException;
import org.jruby.RubyFixnum;
import org.jruby.RubyHash;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyMatchData;
import org.jruby.RubyModule;
import org.jruby.RubyProc;
import org.jruby.RubyRange;
import org.jruby.RubyRegexp;
import org.jruby.RubyString;
import org.jruby.RubySymbol;
import org.jruby.ast.NodeType;
import org.jruby.ast.util.ArgsUtil;
import org.jruby.compiler.ASTInspector;
import org.jruby.compiler.ArgumentsCallback;
import org.jruby.compiler.ArrayCallback;
import org.jruby.compiler.BranchCallback;
import org.jruby.compiler.CompilerCallback;
import org.jruby.compiler.InvocationCompiler;
import org.jruby.compiler.BodyCompiler;
import org.jruby.compiler.FastSwitchType;
import org.jruby.compiler.NotCompilableException;
import org.jruby.compiler.VariableCompiler;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.RaiseException;
import org.jruby.internal.runtime.GlobalVariables;
import org.jruby.internal.runtime.methods.CallConfiguration;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.javasupport.JavaUtil;
import org.jruby.runtime.Helpers;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.parser.ReOptions;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.Block;
import org.jruby.runtime.BlockBody;
import org.jruby.runtime.CallType;
import org.jruby.runtime.CompiledBlockCallback;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.runtime.builtin.InstanceVariables;
import org.jruby.runtime.invokedynamic.InvokeDynamicSupport;
import org.jruby.runtime.opto.OptoFactory;
import org.jruby.util.ByteList;
import org.jruby.util.DefinedMessage;
import org.jruby.util.JavaNameMangler;
import org.objectweb.asm.Label;
import org.objectweb.asm.Type;
import static org.objectweb.asm.Opcodes.*;
import static org.jruby.util.CodegenUtils.*;
import org.jruby.util.cli.Options;
/**
* BaseBodyCompiler encapsulates all common behavior between BodyCompiler
* implementations.
*/
public abstract class BaseBodyCompiler implements BodyCompiler {
protected SkinnyMethodAdapter method;
protected VariableCompiler variableCompiler;
protected InvocationCompiler invocationCompiler;
protected int argParamCount;
protected Label[] currentLoopLabels;
protected Label scopeStart = new Label();
protected Label scopeEnd = new Label();
protected Label redoJump;
protected boolean inNestedMethod = false;
protected boolean inRescue = false;
private int lastLine = -1;
private int lastPositionLine = -1;
protected StaticScope scope;
protected ASTInspector inspector;
protected String methodName;
protected String rubyName;
protected StandardASMCompiler script;
protected int scopeIndex;
public BaseBodyCompiler(StandardASMCompiler scriptCompiler, String methodName, String rubyName, ASTInspector inspector, StaticScope scope, int scopeIndex) {
this.script = scriptCompiler;
this.scope = scope;
this.inspector = inspector;
this.methodName = methodName;
this.rubyName = rubyName;
this.argParamCount = getActualArgsCount(scope);
method = new SkinnyMethodAdapter(script.getClassVisitor(), ACC_PUBLIC | ACC_STATIC, methodName, getSignature(), null, null);
createVariableCompiler();
invocationCompiler = OptoFactory.newInvocationCompiler(this, method);
this.scopeIndex = scopeIndex;
}
public String getNativeMethodName() {
return methodName;
}
public String getRubyName() {
return rubyName;
}
protected boolean shouldUseBoxedArgs(StaticScope scope) {
return scope.getRestArg() >= 0 || scope.getRestArg() == -2 || scope.getOptionalArgs() > 0 || scope.getRequiredArgs() > 3;
}
protected int getActualArgsCount(StaticScope scope) {
if (shouldUseBoxedArgs(scope)) {
return 1; // use IRubyObject[]
} else {
return scope.getRequiredArgs(); // specific arity
}
}
protected abstract String getSignature();
protected abstract void createVariableCompiler();
public abstract void beginMethod(CompilerCallback args, StaticScope scope);
public abstract void endBody();
public BodyCompiler chainToMethod(String methodName) {
BodyCompiler compiler = outline(methodName);
endBody();
return compiler;
}
public void beginChainedMethod() {
method.start();
method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
method.invokevirtual(p(ThreadContext.class), "getCurrentScope", sig(DynamicScope.class));
method.astore(getDynamicScopeIndex());
// if more than 4 locals, get the locals array too
if (scope.getNumberOfVariables() > 4) {
method.aload(getDynamicScopeIndex());
method.invokevirtual(p(DynamicScope.class), "getValues", sig(IRubyObject[].class));
method.astore(getVarsArrayIndex());
}
// visit a label to start scoping for local vars in this method
method.label(scopeStart);
}
public abstract BaseBodyCompiler outline(String methodName);
public StandardASMCompiler getScriptCompiler() {
return script;
}
public void lineNumber(ISourcePosition position) {
int thisLine = position.getStartLine();
// No point in updating number if last number was same value.
if (thisLine != lastLine) {
lastLine = thisLine;
} else {
return;
}
Label line = new Label();
method.label(line);
method.visitLineNumber(thisLine + 1, line);
}
public int getLastLine() {
return lastLine;
}
public void loadThreadContext() {
method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
}
public void loadSelf() {
method.aload(StandardASMCompiler.SELF_INDEX);
}
protected int getClosureIndex() {
return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.CLOSURE_OFFSET;
}
protected int getPreviousExceptionIndex() {
return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.PREVIOUS_EXCEPTION_OFFSET;
}
protected int getDynamicScopeIndex() {
return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.DYNAMIC_SCOPE_OFFSET;
}
protected int getVarsArrayIndex() {
return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.VARS_ARRAY_OFFSET;
}
protected int getFirstTempIndex() {
return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.FIRST_TEMP_OFFSET;
}
protected int getExceptionIndex() {
return StandardASMCompiler.ARGS_INDEX + argParamCount + StandardASMCompiler.EXCEPTION_OFFSET;
}
protected void loadStaticScope() {
script.getCacheCompiler().loadStaticScope(this, scopeIndex);
}
public void loadThis() {
method.aload(StandardASMCompiler.THIS);
}
public void loadRuntime() {
loadThreadContext();
if (Options.COMPILE_INVOKEDYNAMIC.load()) {
method.invokedynamic("runtime", sig(Ruby.class, ThreadContext.class), InvokeDynamicSupport.getContextFieldHandle());
} else {
method.getfield(p(ThreadContext.class), "runtime", ci(Ruby.class));
}
}
public void loadBlock() {
method.aload(getClosureIndex());
}
public void loadNil() {
loadThreadContext();
if (Options.COMPILE_INVOKEDYNAMIC.load()) {
method.invokedynamic("nil", sig(IRubyObject.class, ThreadContext.class), InvokeDynamicSupport.getContextFieldHandle());
} else {
method.getfield(p(ThreadContext.class), "nil", ci(IRubyObject.class));
}
}
public void loadNull() {
method.aconst_null();
}
public void loadObject() {
loadRuntime();
invokeRuby("getObject", sig(RubyClass.class, params()));
}
/**
* This is for utility methods used by the compiler, to reduce the amount of code generation
* necessary. All of these live in CompilerHelpers.
*/
public void invokeUtilityMethod(String methodName, String signature) {
method.invokestatic(p(Helpers.class), methodName, signature);
}
public void invokeThreadContext(String methodName, String signature) {
method.invokevirtual(StandardASMCompiler.THREADCONTEXT, methodName, signature);
}
public void invokeRuby(String methodName, String signature) {
method.invokevirtual(StandardASMCompiler.RUBY, methodName, signature);
}
public void invokeIRubyObject(String methodName, String signature) {
method.invokeinterface(StandardASMCompiler.IRUBYOBJECT, methodName, signature);
}
public void consumeCurrentValue() {
method.pop();
}
public void duplicateCurrentValue() {
method.dup();
}
public void swapValues() {
method.swap();
}
public void reverseValues(int count) {
switch (count) {
case 2:
method.swap();
break;
case 3:
method.dup_x2();
method.pop();
method.swap();
break;
case 4:
method.swap();
method.dup2_x2();
method.pop2();
method.swap();
break;
case 5:
case 6:
case 7:
case 8:
case 9:
case 10:
// up to ten, stuff into tmp locals, load in reverse order, and assign
// FIXME: There's probably a slightly smarter way, but is it important?
int[] tmpLocals = new int[count];
for (int i = 0; i < count; i++) {
tmpLocals[i] = getVariableCompiler().grabTempLocal();
getVariableCompiler().setTempLocal(tmpLocals[i]);
}
for (int i = 0; i < count; i++) {
getVariableCompiler().getTempLocal(tmpLocals[i]);
getVariableCompiler().releaseTempLocal();
}
break;
default:
throw new NotCompilableException("can't reverse more than ten values on the stack");
}
}
public void retrieveSelf() {
loadSelf();
}
public void retrieveSelfClass() {
loadSelf();
metaclass();
}
public VariableCompiler getVariableCompiler() {
return variableCompiler;
}
public InvocationCompiler getInvocationCompiler() {
return invocationCompiler;
}
public void assignConstantInCurrent(String name, CompilerCallback value) {
loadStaticScope();
method.ldc(name);
value.call(this);
method.invokevirtual(p(StaticScope.class), "setConstant", sig(IRubyObject.class, params(String.class, IRubyObject.class)));
}
public void assignConstantInModule(String name, CompilerCallback valueAndModule) {
loadThreadContext();
method.ldc(name);
valueAndModule.call(this);
invokeUtilityMethod("setConstantInModule", sig(IRubyObject.class, ThreadContext.class, String.class, IRubyObject.class, IRubyObject.class));
}
public void assignConstantInObject(String name, final CompilerCallback value) {
assignConstantInModule(name, new CompilerCallback() {
public void call(BodyCompiler context) {
value.call(context);
loadObject();
}
});
}
// TODO: Inefficient, but rarely used (constants in masgn are not common)
public void mAssignConstantInCurrent(String name) {
loadStaticScope();
method.ldc(name);
method.dup2_x1();
method.pop2();
method.invokevirtual(p(StaticScope.class), "setConstant", sig(IRubyObject.class, params(String.class, IRubyObject.class)));
}
// TODO: Inefficient, but rarely used (constants in masgn are not common)
public void mAssignConstantInModule(String name) {
loadThreadContext();
method.ldc(name);
method.dup2_x2();
method.pop2();
invokeUtilityMethod("setConstantInModule", sig(IRubyObject.class, ThreadContext.class, String.class, IRubyObject.class, IRubyObject.class));
}
// TODO: Inefficient, but rarely used (constants in masgn are not common)
public void mAssignConstantInObject(String name) {
loadObject();
loadThreadContext();
method.ldc(name);
method.dup2_x2();
method.pop2();
invokeUtilityMethod("setConstantInModule", sig(IRubyObject.class, ThreadContext.class, String.class, IRubyObject.class, IRubyObject.class));
}
public void retrieveConstant(String name) {
script.getCacheCompiler().cacheConstant(this, name);
}
public void retrieveConstantFromModule(String name) {
invokeUtilityMethod("checkIsModule", sig(RubyModule.class, IRubyObject.class));
script.getCacheCompiler().cacheConstantFrom(this, name);
}
public void retrieveConstantFromObject(String name) {
loadObject();
script.getCacheCompiler().cacheConstantFrom(this, name);
}
public void retrieveClassVariable(String name) {
loadRuntime();
loadStaticScope();
loadSelf();
method.ldc(name);
invokeUtilityMethod("fetchClassVariable", sig(IRubyObject.class, params(Ruby.class, StaticScope.class, IRubyObject.class, String.class)));
}
public void assignClassVariable(String name) {
loadRuntime();
method.swap();
loadStaticScope();
method.swap();
loadSelf();
method.swap();
method.ldc(name);
method.swap();
invokeUtilityMethod("setClassVariable", sig(IRubyObject.class, params(Ruby.class, StaticScope.class, IRubyObject.class, String.class, IRubyObject.class)));
}
public void assignClassVariable(String name, CompilerCallback value) {
loadRuntime();
loadStaticScope();
loadSelf();
method.ldc(name);
value.call(this);
invokeUtilityMethod("setClassVariable", sig(IRubyObject.class, params(Ruby.class, StaticScope.class, IRubyObject.class, String.class, IRubyObject.class)));
}
// inefficient, but uncommon to see class vars in masgn
public void declareClassVariable(String name) {
loadRuntime();
method.swap();
loadStaticScope();
method.swap();
loadSelf();
method.swap();
method.ldc(name);
method.swap();
invokeUtilityMethod("declareClassVariable", sig(IRubyObject.class, params(Ruby.class, StaticScope.class, IRubyObject.class, String.class, IRubyObject.class)));
}
public void declareClassVariable(String name, CompilerCallback value) {
loadRuntime();
loadStaticScope();
loadSelf();
method.ldc(name);
value.call(this);
invokeUtilityMethod("declareClassVariable", sig(IRubyObject.class, params(Ruby.class, StaticScope.class, IRubyObject.class, String.class, IRubyObject.class)));
}
public void createNewFloat(double value) {
script.getCacheCompiler().cacheFloat(this, value);
}
public void createNewFixnum(long value) {
script.getCacheCompiler().cacheFixnum(this, value);
}
public void createNewBignum(BigInteger value) {
loadRuntime();
script.getCacheCompiler().cacheBigInteger(this, value);
method.invokestatic(p(RubyBignum.class), "newBignum", sig(RubyBignum.class, params(Ruby.class, BigInteger.class)));
}
public void createNewString(ArrayCallback callback, int count, Encoding encoding) {
loadRuntime();
method.ldc(StandardASMCompiler.STARTING_DSTR_FACTOR * count);
if (encoding == null) {
method.invokestatic(p(RubyString.class), "newStringLight", sig(RubyString.class, Ruby.class, int.class));
} else {
script.getCacheCompiler().cacheEncoding(this, encoding);
method.invokestatic(p(RubyString.class), "newStringLight", sig(RubyString.class, Ruby.class, int.class, Encoding.class));
}
for (int i = 0; i < count; i++) {
callback.nextValue(this, null, i);
appendObject(encoding != null);
}
}
public void buildNewString(ArrayCallback callback, int count, Encoding encoding) {
loadRuntime();
method.ldc(StandardASMCompiler.STARTING_DSTR_FACTOR * count);
if (encoding == null) {
method.invokestatic(p(RubyString.class), "newStringLight", sig(RubyString.class, Ruby.class, int.class));
} else {
script.getCacheCompiler().cacheEncoding(this, encoding);
method.invokestatic(p(RubyString.class), "newStringLight", sig(RubyString.class, Ruby.class, int.class, Encoding.class));
}
for (int i = 0; i < count; i++) {
callback.nextValue(this, null, i);
}
}
public void appendByteList(ByteList value, int codeRange, boolean is19) {
script.getCacheCompiler().cacheByteList(this, value);
if (is19) {
method.ldc(codeRange);
invokeUtilityMethod("appendByteList19", sig(RubyString.class, RubyString.class, ByteList.class, int.class));
} else {
invokeUtilityMethod("appendByteList", sig(RubyString.class, RubyString.class, ByteList.class));
}
}
public void appendObject(boolean is19) {
if (is19) {
method.invokevirtual(p(RubyString.class), "append19", sig(RubyString.class, params(IRubyObject.class)));
} else {
method.invokevirtual(p(RubyString.class), "append", sig(RubyString.class, params(IRubyObject.class)));
}
}
public void shortcutAppend(boolean is19) {
if (is19) {
invokeUtilityMethod("shortcutAppend", sig(RubyString.class, RubyString.class, IRubyObject.class));
} else {
invokeUtilityMethod("shortcutAppend18", sig(RubyString.class, RubyString.class, IRubyObject.class));
}
}
public void stringToSymbol(boolean is19) {
if (is19) {
method.invokevirtual(p(RubyString.class), "intern19", sig(RubySymbol.class));
} else {
method.invokevirtual(p(RubyString.class), "intern", sig(RubySymbol.class));
}
}
public void createNewSymbol(ArrayCallback callback, int count, Encoding encoding) {
loadRuntime();
buildNewString(callback, count, encoding);
toJavaString();
invokeRuby("newSymbol", sig(RubySymbol.class, params(String.class)));
}
public void createNewString(ByteList value, int codeRange) {
script.getCacheCompiler().cacheString(this, value, codeRange);
}
public void createNewSymbol(String name, Encoding encoding) {
script.getCacheCompiler().cacheSymbol(this, name, encoding);
}
public void createNewArray(boolean lightweight) {
loadRuntime();
// put under object array already present
method.swap();
if (lightweight) {
method.invokestatic(p(RubyArray.class), "newArrayNoCopyLight", sig(RubyArray.class, params(Ruby.class, IRubyObject[].class)));
} else {
method.invokestatic(p(RubyArray.class), "newArrayNoCopy", sig(RubyArray.class, params(Ruby.class, IRubyObject[].class)));
}
}
public void createNewArray(Object[] sourceArray, ArrayCallback callback, boolean lightweight) {
loadRuntime();
buildRubyArray(sourceArray, callback, lightweight);
}
public void createNewLiteralArray(Object[] sourceArray, ArrayCallback callback, boolean lightweight) {
buildRubyLiteralArray(sourceArray, callback, lightweight);
}
public void createEmptyArray() {
loadRuntime();
invokeRuby("newArray", sig(RubyArray.class));
}
public void createObjectArray(Object[] sourceArray, ArrayCallback callback) {
buildObjectArray(StandardASMCompiler.IRUBYOBJECT, sourceArray, callback);
}
private void buildObjectArray(String type, Object[] sourceArray, ArrayCallback callback) {
if (sourceArray.length == 0) {
method.getstatic(p(IRubyObject.class), "NULL_ARRAY", ci(IRubyObject[].class));
} else if (sourceArray.length <= Helpers.MAX_SPECIFIC_ARITY_OBJECT_ARRAY) {
// if we have a specific-arity helper to construct an array for us, use that
for (int i = 0; i < sourceArray.length; i++) {
callback.nextValue(this, sourceArray, i);
}
invokeUtilityMethod("constructObjectArray", sig(IRubyObject[].class, params(IRubyObject.class, sourceArray.length)));
} else {
// brute force construction inline
method.pushInt(sourceArray.length);
method.anewarray(type);
for (int i = 0; i < sourceArray.length; i++) {
method.dup();
method.pushInt(i);
callback.nextValue(this, sourceArray, i);
method.arraystore();
}
}
}
private void buildRubyArray(Object[] sourceArray, ArrayCallback callback, boolean light) {
if (sourceArray.length == 0) {
method.invokestatic(p(RubyArray.class), "newEmptyArray", sig(RubyArray.class, Ruby.class));
} else if (sourceArray.length <= Helpers.MAX_SPECIFIC_ARITY_OBJECT_ARRAY) {
// if we have a specific-arity helper to construct an array for us, use that
for (int i = 0; i < sourceArray.length; i++) {
callback.nextValue(this, sourceArray, i);
}
invokeUtilityMethod("constructRubyArray", sig(RubyArray.class, params(Ruby.class, IRubyObject.class, sourceArray.length)));
} else {
// brute force construction
// construct array all at once
method.pushInt(sourceArray.length);
invokeUtilityMethod("anewarrayIRubyObjects", sig(IRubyObject[].class, int.class));
// iterate over elements, stuffing every ten into array in batches
int i = 0;
for (; i < sourceArray.length; i++) {
callback.nextValue(this, sourceArray, i);
if ((i + 1) % 10 == 0) {
method.pushInt(i - 9);
invokeUtilityMethod("aastoreIRubyObjects", sig(IRubyObject[].class, params(IRubyObject[].class, IRubyObject.class, 10, int.class)));
}
}
// stuff remaining into array
int remain = i % 10;
if (remain != 0) {
method.pushInt(i - remain);
invokeUtilityMethod("aastoreIRubyObjects", sig(IRubyObject[].class, params(IRubyObject[].class, IRubyObject.class, remain, int.class)));
}
// construct RubyArray wrapper
if (light) {
method.invokestatic(p(RubyArray.class), "newArrayNoCopyLight", sig(RubyArray.class, Ruby.class, IRubyObject[].class));
} else {
method.invokestatic(p(RubyArray.class), "newArrayNoCopy", sig(RubyArray.class, Ruby.class, IRubyObject[].class));
}
}
}
private void buildRubyLiteralArray(Object[] sourceArray, ArrayCallback callback, boolean light) {
if (sourceArray.length < 100) {
// don't chunk arrays smaller than 100 elements
loadRuntime();
buildRubyArray(sourceArray, callback, light);
} else {
// populate the array in a separate series of methods
SkinnyMethodAdapter oldMethod = method;
// prepare the first builder in the chain
String newMethodName = "array_builder_" + script.getAndIncrementMethodIndex() + "";
method = new SkinnyMethodAdapter(
script.getClassVisitor(),
ACC_PRIVATE | ACC_SYNTHETIC | ACC_STATIC,
newMethodName,
sig(IRubyObject[].class, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject[].class),
null,
null);
method.start();
for (int i = 0; i < sourceArray.length; i++) {
// for every hundred elements, chain to the next call
if ((i + 1) % 100 == 0) {
String nextName = "array_builder_" + script.getAndIncrementMethodIndex() + "";
method.aloadMany(0, 1, 2);
method.invokestatic(script.getClassname(), nextName,
sig(IRubyObject[].class, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject[].class));
method.areturn();
method.end();
method = new SkinnyMethodAdapter(
script.getClassVisitor(),
ACC_PRIVATE | ACC_SYNTHETIC | ACC_STATIC,
nextName,
sig(IRubyObject[].class, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject[].class),
null,
null);
method.start();
}
method.aload(2);
method.pushInt(i);
callback.nextValue(this, sourceArray, i);
method.arraystore();
}
// close out the last method in the chain
method.aload(2);
method.areturn();
method.end();
// restore original method, prepare runtime and array, and invoke the chain
method = oldMethod;
loadRuntime(); // for newArray* call below
// chain invoke
method.aload(StandardASMCompiler.THIS);
method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
method.pushInt(sourceArray.length);
method.anewarray(p(IRubyObject.class));
method.invokestatic(script.getClassname(), newMethodName,
sig(IRubyObject[].class, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject[].class));
// array construct
if (light) {
method.invokestatic(p(RubyArray.class), "newArrayNoCopyLight", sig(RubyArray.class, Ruby.class, IRubyObject[].class));
} else {
method.invokestatic(p(RubyArray.class), "newArrayNoCopy", sig(RubyArray.class, Ruby.class, IRubyObject[].class));
}
}
}
public void createEmptyHash() {
loadRuntime();
method.invokestatic(p(RubyHash.class), "newHash", sig(RubyHash.class, params(Ruby.class)));
}
public void createNewHash(Object elements, ArrayCallback callback, int keyCount) {
if (keyCount <= 10) {
createNewHashCommon(elements, callback, keyCount, "constructSmallHash", "fastASetSmallCheckString");
} else {
createNewHashCommon(elements, callback, keyCount, "constructHash", "fastASetCheckString");
}
}
public void createNewLiteralHash(Object elements, ArrayCallback callback, int keyCount) {
if (keyCount <= 10) {
createNewLiteralHashCommon(elements, callback, keyCount, "constructSmallHash", "fastASetSmallCheckString");
} else {
createNewLiteralHashCommon(elements, callback, keyCount, "constructHash", "fastASetCheckString");
}
}
public void createNewHash19(Object elements, ArrayCallback callback, int keyCount) {
if (keyCount <= 10) {
createNewHashCommon(elements, callback, keyCount, "constructSmallHash19", "fastASetSmallCheckString19");
} else {
createNewHashCommon(elements, callback, keyCount, "constructHash19", "fastASetCheckString19");
}
}
private void createNewHashCommon(Object elements, ArrayCallback callback, int keyCount,
String constructorName, String methodName) {
loadRuntime();
// use specific-arity for as much as possible
int i = 0;
for (; i < keyCount && i < Helpers.MAX_SPECIFIC_ARITY_HASH; i++) {
callback.nextValue(this, elements, i);
}
invokeUtilityMethod(constructorName, sig(RubyHash.class, params(Ruby.class, IRubyObject.class, i * 2)));
for (; i < keyCount; i++) {
method.dup();
loadRuntime();
callback.nextValue(this, elements, i);
method.invokevirtual(p(RubyHash.class), methodName, sig(void.class, params(Ruby.class, IRubyObject.class, IRubyObject.class)));
}
}
private void createNewLiteralHashCommon(Object elements, ArrayCallback callback, int keyCount,
String constructorName, String methodName) {
if (keyCount < 50) {
// small hash, use standard construction
createNewHashCommon(elements, callback, keyCount, constructorName, methodName);
} else {
// populate the hash in a separate series of methods
SkinnyMethodAdapter oldMethod = method;
// prepare the first builder in the chain
String builderMethod = "hash_builder_" + script.getAndIncrementMethodIndex() + "";
method = new SkinnyMethodAdapter(
script.getClassVisitor(),
ACC_PRIVATE | ACC_SYNTHETIC | ACC_STATIC,
builderMethod,
sig(RubyHash.class, "L" + script.getClassname() + ";", ThreadContext.class, RubyHash.class),
null,
null);
method.start();
for (int i = 0; i < keyCount; i++) {
// for every hundred keys, chain to the next call
if ((i + 1) % 100 == 0) {
String nextName = "hash_builder_" + script.getAndIncrementMethodIndex() + "";
method.aloadMany(0, 1, 2);
method.invokestatic(script.getClassname(), nextName,
sig(RubyHash.class, "L" + script.getClassname() + ";", ThreadContext.class, RubyHash.class));
method.areturn();
method.end();
method = new SkinnyMethodAdapter(
script.getClassVisitor(),
ACC_PRIVATE | ACC_SYNTHETIC | ACC_STATIC,
nextName,
sig(RubyHash.class, "L" + script.getClassname() + ";", ThreadContext.class, RubyHash.class),
null,
null);
method.start();
}
method.aload(2);
loadRuntime();
callback.nextValue(this, elements, i);
method.invokevirtual(p(RubyHash.class), methodName, sig(void.class, params(Ruby.class, IRubyObject.class, IRubyObject.class)));
}
// close out the last method in the chain
method.aload(2);
method.areturn();
method.end();
// restore original method
method = oldMethod;
// chain invoke
method.aload(StandardASMCompiler.THIS);
method.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
loadRuntime();
method.invokestatic(p(RubyHash.class), "newHash", sig(RubyHash.class, Ruby.class));
method.invokestatic(script.getClassname(), builderMethod,
sig(RubyHash.class, "L" + script.getClassname() + ";", ThreadContext.class, RubyHash.class));
}
}
public void createNewRange(CompilerCallback beginEndCallback, boolean isExclusive) {
loadRuntime();
loadThreadContext();
beginEndCallback.call(this);
if (isExclusive) {
method.invokestatic(p(RubyRange.class), "newExclusiveRange", sig(RubyRange.class, params(Ruby.class, ThreadContext.class, IRubyObject.class, IRubyObject.class)));
} else {
method.invokestatic(p(RubyRange.class), "newInclusiveRange", sig(RubyRange.class, params(Ruby.class, ThreadContext.class, IRubyObject.class, IRubyObject.class)));
}
}
public void createNewLambda(CompilerCallback closure) {
loadThreadContext();
closure.call(this);
loadSelf();
invokeUtilityMethod("newLiteralLambda", sig(RubyProc.class, ThreadContext.class, Block.class, IRubyObject.class));
}
/**
* Invoke IRubyObject.isTrue
*/
public void isTrue() {
invokeIRubyObject("isTrue", sig(Boolean.TYPE));
}
public void performBooleanBranch(BranchCallback trueBranch, BranchCallback falseBranch) {
// call isTrue on the result
isTrue();
performBooleanBranch2(trueBranch, falseBranch);
}
public void performBooleanBranch2(BranchCallback trueBranch, BranchCallback falseBranch) {
Label afterJmp = new Label();
Label falseJmp = new Label();
method.ifeq(falseJmp); // EQ == 0 (i.e. false)
trueBranch.branch(this);
method.go_to(afterJmp);
// FIXME: optimize for cases where we have no false branch
method.label(falseJmp);
falseBranch.branch(this);
method.label(afterJmp);
}
public void performBooleanGlobalBranch(String globalName, BranchCallback trueBranch, BranchCallback falseBranch) {
getScriptCompiler().getCacheCompiler().cacheGlobalBoolean(this, globalName);
performBooleanBranch2(trueBranch, falseBranch);
}
public void performBooleanConstantBranch(String globalName, BranchCallback trueBranch, BranchCallback falseBranch) {
getScriptCompiler().getCacheCompiler().cacheConstantBoolean(this, globalName);
performBooleanBranch2(trueBranch, falseBranch);
}
public void performLogicalAnd(BranchCallback longBranch) {
Label falseJmp = new Label();
// dup it since we need to return appropriately if it's false
method.dup();
// call isTrue on the result
isTrue();
method.ifeq(falseJmp); // EQ == 0 (i.e. false)
// pop the extra result and replace with the send part of the AND
method.pop();
longBranch.branch(this);
method.label(falseJmp);
}
public void performLogicalOr(BranchCallback longBranch) {
// FIXME: after jump is not in here. Will if ever be?
//Label afterJmp = new Label();
Label falseJmp = new Label();
// dup it since we need to return appropriately if it's false
method.dup();
// call isTrue on the result
isTrue();
method.ifne(falseJmp); // EQ == 0 (i.e. false)
// pop the extra result and replace with the send part of the AND
method.pop();
longBranch.branch(this);
method.label(falseJmp);
}
public void performBooleanLoopSafe(BranchCallback condition, BranchCallback body, boolean checkFirst) {
String mname = getNewRescueName();
BaseBodyCompiler nested = outline(mname);
nested.performBooleanLoopSafeInner(condition, body, checkFirst);
}
private void performBooleanLoopSafeInner(BranchCallback condition, BranchCallback body, boolean checkFirst) {
performBooleanLoop(condition, body, checkFirst);
endBody();
}
public void performBooleanLoop(BranchCallback condition, final BranchCallback body, boolean checkFirst) {
Label tryBegin = new Label();
Label tryEnd = new Label();
Label catchNext = new Label();
Label catchBreak = new Label();
Label endOfBody = new Label();
Label conditionCheck = new Label();
final Label topOfBody = new Label();
Label done = new Label();
Label normalLoopEnd = new Label();
method.trycatch(tryBegin, tryEnd, catchNext, p(JumpException.NextJump.class));
method.trycatch(tryBegin, tryEnd, catchBreak, p(JumpException.BreakJump.class));
method.label(tryBegin);
{
Label[] oldLoopLabels = currentLoopLabels;
currentLoopLabels = new Label[]{endOfBody, topOfBody, done};
// FIXME: if we terminate immediately, this appears to break while in method arguments
// we need to push a nil for the cases where we will never enter the body
if (checkFirst) {
method.go_to(conditionCheck);
}
method.label(topOfBody);
Runnable redoBody = new Runnable() { public void run() {
Runnable raiseBody = new Runnable() { public void run() {
body.branch(BaseBodyCompiler.this);
}};
Runnable raiseCatch = new Runnable() { public void run() {
loadThreadContext();
invokeUtilityMethod("unwrapRedoNextBreakOrJustLocalJump", sig(Throwable.class, RaiseException.class, ThreadContext.class));
method.athrow();
}};
method.trycatch(p(RaiseException.class), raiseBody, raiseCatch);
}};
Runnable redoCatch = new Runnable() { public void run() {
method.pop();
method.go_to(topOfBody);
}};
method.trycatch(p(JumpException.RedoJump.class), redoBody, redoCatch);
method.label(endOfBody);
// clear body or next result after each successful loop
method.pop();
method.label(conditionCheck);
// check the condition
condition.branch(this);
isTrue();
method.ifne(topOfBody); // NE == nonzero (i.e. true)
currentLoopLabels = oldLoopLabels;
}
method.label(tryEnd);
// skip catch block
method.go_to(normalLoopEnd);
// catch logic for flow-control: next, break
{
// next jump
{
method.label(catchNext);
method.pop();
// exceptionNext target is for a next that doesn't push a new value, like this one
method.go_to(conditionCheck);
}
// break jump
{
method.label(catchBreak);
loadThreadContext();
invokeUtilityMethod("breakJumpInWhile", sig(IRubyObject.class, JumpException.BreakJump.class, ThreadContext.class));
method.go_to(done);
}
}
method.label(normalLoopEnd);
loadNil();
method.label(done);
}
public void performBooleanLoopLight(BranchCallback condition, BranchCallback body, boolean checkFirst) {
Label endOfBody = new Label();
Label conditionCheck = new Label();
Label topOfBody = new Label();
Label done = new Label();
Label[] oldLoopLabels = currentLoopLabels;
currentLoopLabels = new Label[]{endOfBody, topOfBody, done};
// FIXME: if we terminate immediately, this appears to break while in method arguments
// we need to push a nil for the cases where we will never enter the body
if (checkFirst) {
method.go_to(conditionCheck);
}
method.label(topOfBody);
body.branch(this);
method.label(endOfBody);
// clear body or next result after each successful loop
method.pop();
method.label(conditionCheck);
// check the condition
condition.branch(this);
isTrue();
method.ifne(topOfBody); // NE == nonzero (i.e. true)
currentLoopLabels = oldLoopLabels;
loadNil();
method.label(done);
}
public void createNewClosure(
String file,
int line,
StaticScope scope,
int arity,
CompilerCallback body,
CompilerCallback args,
boolean hasMultipleArgsHead,
NodeType argsNodeId,
ASTInspector inspector) {
String blockInMethod = JavaNameMangler.mangleMethodName(rubyName);
if (rubyName == null || rubyName.length() == 0) {
blockInMethod = "__block__";
}
String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$" + blockInMethod;
// Done with closure compilation
loadThreadContext();
loadSelf();
int scopeIndex = script.getCacheCompiler().cacheClosure(this, closureMethodName, arity, scope, file, line, hasMultipleArgsHead, argsNodeId, inspector);
script.addBlockCallbackDescriptor(closureMethodName, file, line);
invokeUtilityMethod("createBlock", sig(Block.class,
params(ThreadContext.class, IRubyObject.class, BlockBody.class)));
// emit closure body
ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, rubyName, inspector, scope, scopeIndex);
closureCompiler.beginMethod(args, scope);
body.call(closureCompiler);
closureCompiler.endBody();
}
public void createNewClosure19(
String file,
int line,
StaticScope scope,
int arity,
CompilerCallback body,
CompilerCallback args,
boolean hasMultipleArgsHead,
NodeType argsNodeId,
String parameterList,
ASTInspector inspector) {
String blockInMethod = JavaNameMangler.mangleMethodName(rubyName);
if (rubyName == null || rubyName.length() == 0) {
blockInMethod = "__block__";
}
String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$" + blockInMethod;
// Done with closure compilation
loadThreadContext();
loadSelf();
int scopeIndex = script.getCacheCompiler().cacheClosure19(this, closureMethodName, arity, scope, file, line, hasMultipleArgsHead, argsNodeId, parameterList, inspector);
script.addBlockCallback19Descriptor(closureMethodName, file, line);
invokeUtilityMethod("createBlock19", sig(Block.class,
params(ThreadContext.class, IRubyObject.class, BlockBody.class)));
// emit closure body
ChildScopedBodyCompiler19 closureCompiler = new ChildScopedBodyCompiler19(script, closureMethodName, rubyName, inspector, scope, scopeIndex);
closureCompiler.beginMethod(args, scope);
body.call(closureCompiler);
closureCompiler.endBody();
}
public void runBeginBlock(StaticScope scope, CompilerCallback body) {
String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$__begin__";
ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, rubyName, null, scope, scopeIndex);
closureCompiler.beginMethod(null, scope);
body.call(closureCompiler);
closureCompiler.endBody();
// Done with closure compilation
loadThreadContext();
loadSelf();
String scopeNames = Helpers.encodeScope(scope);
method.ldc(scopeNames);
script.getCacheCompiler().cacheSpecialClosure(this, closureMethodName);
invokeUtilityMethod("runBeginBlock", sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, String.class, CompiledBlockCallback.class)));
}
public void createNewForLoop(int arity, CompilerCallback body, CompilerCallback args, boolean hasMultipleArgsHead, NodeType argsNodeId, ASTInspector inspector) {
String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$__for__";
ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, rubyName, inspector, scope, scopeIndex);
closureCompiler.beginMethod(args, null);
body.call(closureCompiler);
closureCompiler.endBody();
// Done with closure compilation
loadThreadContext();
loadSelf();
method.pushInt(arity);
script.getCacheCompiler().cacheSpecialClosure(this, closureMethodName);
method.ldc(Boolean.valueOf(hasMultipleArgsHead));
method.ldc(BlockBody.asArgumentType(argsNodeId));
invokeUtilityMethod("createSharedScopeBlock", sig(Block.class,
params(ThreadContext.class, IRubyObject.class, Integer.TYPE, CompiledBlockCallback.class, Boolean.TYPE, Integer.TYPE)));
}
public void createNewEndBlock(CompilerCallback body) {
String closureMethodName = "block_" + script.getAndIncrementInnerIndex() + "$RUBY$__end__";
ChildScopedBodyCompiler closureCompiler = new ChildScopedBodyCompiler(script, closureMethodName, rubyName, null, scope, scopeIndex);
closureCompiler.beginMethod(null, null);
body.call(closureCompiler);
closureCompiler.endBody();
// Done with closure compilation
loadThreadContext();
loadSelf();
method.iconst_0();
script.getCacheCompiler().cacheSpecialClosure(this, closureMethodName);
method.iconst_0(); // false
method.iconst_0(); // zero
invokeUtilityMethod("createSharedScopeBlock", sig(Block.class,
params(ThreadContext.class, IRubyObject.class, Integer.TYPE, CompiledBlockCallback.class, Boolean.TYPE, Integer.TYPE)));
loadRuntime();
invokeUtilityMethod("registerEndBlock", sig(void.class, Block.class, Ruby.class));
loadNil();
}
public void getCompiledClass() {
method.ldc(Type.getType(script.getClassname()));
}
public void println() {
method.dup();
method.getstatic(p(System.class), "out", ci(PrintStream.class));
method.swap();
method.invokevirtual(p(PrintStream.class), "println", sig(Void.TYPE, params(Object.class)));
}
public void defineAlias(CompilerCallback args) {
loadThreadContext();
loadSelf();
args.call(this);
invokeUtilityMethod("defineAlias", sig(IRubyObject.class, ThreadContext.class, IRubyObject.class, Object.class, Object.class));
}
public void literal(String value) {
method.ldc(value);
}
public void loadFalse() {
script.getCacheCompiler().cacheBoolean(this, false);
}
public void loadTrue() {
script.getCacheCompiler().cacheBoolean(this, true);
}
public void loadCurrentModule() {
loadStaticScope();
method.invokevirtual(p(StaticScope.class), "getModule", sig(RubyModule.class));
}
public void retrieveInstanceVariable(String name) {
script.getCacheCompiler().cachedGetVariable(this, name);
}
public void assignInstanceVariable(String name) {
final int tmp = getVariableCompiler().grabTempLocal();
getVariableCompiler().setTempLocal(tmp);
CompilerCallback callback = new CompilerCallback() {
public void call(BodyCompiler context) {
context.getVariableCompiler().getTempLocal(tmp);
}
};
script.getCacheCompiler().cachedSetVariable(this, name, callback);
}
public void assignInstanceVariable(String name, CompilerCallback value) {
script.getCacheCompiler().cachedSetVariable(this, name, value);
}
public void retrieveGlobalVariable(String name) {
getScriptCompiler().getCacheCompiler().cacheGlobal(this, name);
}
public void assignGlobalVariable(String name) {
loadRuntime();
method.ldc(name);
invokeUtilityMethod("setGlobalVariable", sig(IRubyObject.class, IRubyObject.class, Ruby.class, String.class));
}
public void assignGlobalVariable(String name, CompilerCallback value) {
value.call(this);
loadRuntime();
method.ldc(name);
invokeUtilityMethod("setGlobalVariable", sig(IRubyObject.class, IRubyObject.class, Ruby.class, String.class));
}
public void negateCurrentValue() {
loadRuntime();
invokeUtilityMethod("negate", sig(IRubyObject.class, IRubyObject.class, Ruby.class));
}
public void splatCurrentValue(String methodName) {
method.invokestatic(p(Helpers.class), methodName, sig(RubyArray.class, params(IRubyObject.class)));
}
public void singlifySplattedValue() {
method.invokestatic(p(Helpers.class), "aValueSplat", sig(IRubyObject.class, params(IRubyObject.class)));
}
public void singlifySplattedValue19() {
method.invokestatic(p(Helpers.class), "aValueSplat19", sig(IRubyObject.class, params(IRubyObject.class)));
}
public void aryToAry() {
method.invokestatic(p(Helpers.class), "aryToAry", sig(IRubyObject.class, params(IRubyObject.class)));
}
public void ensureRubyArray() {
invokeUtilityMethod("ensureRubyArray", sig(RubyArray.class, params(IRubyObject.class)));
}
public void ensureMultipleAssignableRubyArray(boolean masgnHasHead) {
loadRuntime();
method.pushBoolean(masgnHasHead);
invokeUtilityMethod("ensureMultipleAssignableRubyArray", sig(RubyArray.class, params(IRubyObject.class, Ruby.class, boolean.class)));
}
public void forEachInValueArray(int start, int count, Object source, ArrayCallback callback, CompilerCallback argsCallback) {
if (start < count || argsCallback != null) {
int tempLocal = getVariableCompiler().grabTempLocal();
getVariableCompiler().setTempLocal(tempLocal);
for (; start < count; start++) {
getVariableCompiler().getTempLocal(tempLocal);
switch (start) {
case 0:
invokeUtilityMethod("arrayEntryOrNilZero", sig(IRubyObject.class, RubyArray.class));
break;
case 1:
invokeUtilityMethod("arrayEntryOrNilOne", sig(IRubyObject.class, RubyArray.class));
break;
case 2:
invokeUtilityMethod("arrayEntryOrNilTwo", sig(IRubyObject.class, RubyArray.class));
break;
default:
method.pushInt(start);
invokeUtilityMethod("arrayEntryOrNil", sig(IRubyObject.class, RubyArray.class, int.class));
break;
}
callback.nextValue(this, source, start);
}
if (argsCallback != null) {
getVariableCompiler().getTempLocal(tempLocal);
loadRuntime();
method.pushInt(start);
invokeUtilityMethod("subarrayOrEmpty", sig(RubyArray.class, RubyArray.class, Ruby.class, int.class));
argsCallback.call(this);
}
getVariableCompiler().getTempLocal(tempLocal);
getVariableCompiler().releaseTempLocal();
}
}
public void forEachInValueArray(int start, int preCount, Object preSource, int postCount, Object postSource, ArrayCallback callback, CompilerCallback argsCallback) {
if (start < preCount || argsCallback != null) {
int tempLocal = getVariableCompiler().grabTempLocal();
getVariableCompiler().setTempLocal(tempLocal);
for (; start < preCount; start++) {
getVariableCompiler().getTempLocal(tempLocal);
switch (start) {
case 0:
invokeUtilityMethod("arrayEntryOrNilZero", sig(IRubyObject.class, RubyArray.class));
break;
case 1:
invokeUtilityMethod("arrayEntryOrNilOne", sig(IRubyObject.class, RubyArray.class));
break;
case 2:
invokeUtilityMethod("arrayEntryOrNilTwo", sig(IRubyObject.class, RubyArray.class));
break;
default:
method.pushInt(start);
invokeUtilityMethod("arrayEntryOrNil", sig(IRubyObject.class, RubyArray.class, int.class));
break;
}
callback.nextValue(this, preSource, start);
}
if (argsCallback != null) {
getVariableCompiler().getTempLocal(tempLocal);
loadRuntime();
method.pushInt(start);
method.pushInt(postCount);
invokeUtilityMethod("subarrayOrEmpty", sig(RubyArray.class, RubyArray.class, Ruby.class, int.class, int.class));
argsCallback.call(this);
}
for (int postStart = 0; postStart < postCount; postStart++) {
getVariableCompiler().getTempLocal(tempLocal);
method.pushInt(preCount);
method.pushInt(postCount);
switch (postStart) {
case 0:
invokeUtilityMethod("arrayPostOrNilZero", sig(IRubyObject.class, RubyArray.class, int.class, int.class));
break;
case 1:
invokeUtilityMethod("arrayPostOrNilOne", sig(IRubyObject.class, RubyArray.class, int.class, int.class));
break;
case 2:
invokeUtilityMethod("arrayPostOrNilTwo", sig(IRubyObject.class, RubyArray.class, int.class, int.class));
break;
default:
method.pushInt(postStart);
invokeUtilityMethod("arrayPostOrNil", sig(IRubyObject.class, RubyArray.class, int.class, int.class, int.class));
break;
}
callback.nextValue(this, postSource, postStart);
}
getVariableCompiler().getTempLocal(tempLocal);
getVariableCompiler().releaseTempLocal();
}
}
public void asString() {
method.invokeinterface(p(IRubyObject.class), "asString", sig(RubyString.class));
}
public void toJavaString() {
method.invokevirtual(p(Object.class), "toString", sig(String.class));
}
public void nthRef(int match) {
method.pushInt(match);
backref();
method.invokestatic(p(RubyRegexp.class), "nth_match", sig(IRubyObject.class, params(Integer.TYPE, IRubyObject.class)));
}
public void match(boolean is19) {
loadThreadContext();
method.invokevirtual(p(RubyRegexp.class), is19 ? "op_match2_19" : "op_match2", sig(IRubyObject.class, params(ThreadContext.class)));
}
public void match2(CompilerCallback value, boolean is19) {
loadThreadContext();
value.call(this);
method.invokevirtual(p(RubyRegexp.class), is19 ? "op_match19" : "op_match", sig(IRubyObject.class, params(ThreadContext.class, IRubyObject.class)));
}
public void match2Capture(CompilerCallback value, int[] scopeOffsets, boolean is19) {
loadThreadContext();
value.call(this);
method.ldc(Helpers.encodeCaptureOffsets(scopeOffsets));
invokeUtilityMethod(is19 ? "match2AndUpdateScope19" : "match2AndUpdateScope", sig(IRubyObject.class, params(IRubyObject.class, ThreadContext.class, IRubyObject.class, String.class)));
}
public void match3(boolean is19) {
loadThreadContext();
invokeUtilityMethod(is19 ? "match3_19" : "match3", sig(IRubyObject.class, RubyRegexp.class, IRubyObject.class, ThreadContext.class));
}
public void createNewRegexp(final ByteList value, final int options) {
script.getCacheCompiler().cacheRegexp(this, value, options);
}
public void createNewRegexp(CompilerCallback createStringCallback, final int options) {
boolean onceOnly = (options & ReOptions.RE_OPTION_ONCE) != 0; // for regular expressions with the /o flag
if (onceOnly) {
script.getCacheCompiler().cacheDRegexp(this, createStringCallback, options);
} else {
loadRuntime();
createStringCallback.call(this);
method.pushInt(options);
method.invokestatic(p(RubyRegexp.class), "newDRegexpEmbedded", sig(RubyRegexp.class, params(Ruby.class, RubyString.class, int.class))); //[reg]
}
}
public void createDRegexp19(ArrayCallback arrayCallback, Object[] sourceArray, int options) {
boolean onceOnly = (options & ReOptions.RE_OPTION_ONCE) != 0; // for regular expressions with the /o flag
if (onceOnly) {
script.getCacheCompiler().cacheDRegexp19(this, arrayCallback, sourceArray, options);
} else {
loadRuntime();
createObjectArray(sourceArray, arrayCallback);
method.ldc(options);
method.invokestatic(p(RubyRegexp.class), "newDRegexpEmbedded19", sig(RubyRegexp.class, params(Ruby.class, IRubyObject[].class, int.class))); //[reg]
}
}
public void pollThreadEvents() {
if (!RubyInstanceConfig.THREADLESS_COMPILE_ENABLED) {
if (Options.COMPILE_INVOKEDYNAMIC.load()) {
// switchpoint version
loadThreadContext();
method.invokedynamic(
"checkpoint",
sig(void.class, ThreadContext.class),
InvokeDynamicSupport.checkpointHandle());
} else {
loadThreadContext();
invokeThreadContext("pollThreadEvents", sig(void.class));
}
}
}
public void nullToNil() {
loadThreadContext();
invokeUtilityMethod("nullToNil", sig(IRubyObject.class, IRubyObject.class, ThreadContext.class));
}
public void isInstanceOf(Class clazz, BranchCallback trueBranch, BranchCallback falseBranch) {
method.instance_of(p(clazz));
Label falseJmp = new Label();
Label afterJmp = new Label();
method.ifeq(falseJmp); // EQ == 0 (i.e. false)
trueBranch.branch(this);
method.go_to(afterJmp);
method.label(falseJmp);
falseBranch.branch(this);
method.label(afterJmp);
}
public void isCaptured(final int number, final BranchCallback trueBranch, final BranchCallback falseBranch) {
backref();
method.dup();
isInstanceOf(RubyMatchData.class, new BranchCallback() {
public void branch(BodyCompiler context) {
method.visitTypeInsn(CHECKCAST, p(RubyMatchData.class));
method.pushInt(number);
method.invokevirtual(p(RubyMatchData.class), "group", sig(IRubyObject.class, params(int.class)));
method.invokeinterface(p(IRubyObject.class), "isNil", sig(boolean.class));
Label isNil = new Label();
Label after = new Label();
method.ifne(isNil);
trueBranch.branch(context);
method.go_to(after);
method.label(isNil);
falseBranch.branch(context);
method.label(after);
}
}, new BranchCallback() {
public void branch(BodyCompiler context) {
method.pop();
falseBranch.branch(context);
}
});
}
public void backref() {
loadRuntime();
loadThreadContext();
invokeUtilityMethod("getBackref", sig(IRubyObject.class, Ruby.class, ThreadContext.class));
}
public void backrefMethod(String methodName) {
backref();
method.invokestatic(p(RubyRegexp.class), methodName, sig(IRubyObject.class, params(IRubyObject.class)));
}
public void issueLoopBreak() {
// inside a loop, break out of it
// go to end of loop, leaving break value on stack
method.go_to(currentLoopLabels[2]);
}
public void issueLoopNext() {
// inside a loop, jump to conditional
method.go_to(currentLoopLabels[0]);
}
public void issueLoopRedo() {
// inside a loop, jump to body
method.go_to(currentLoopLabels[1]);
}
protected String getNewEnsureName() {
return "ensure_" + (script.getAndIncrementEnsureNumber()) + "$RUBY$__ensure__";
}
public void protect(BranchCallback regularCode, BranchCallback protectedCode, Class ret) {
String mname = getNewEnsureName();
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
script.getClassVisitor(),
ACC_PUBLIC | ACC_SYNTHETIC | ACC_STATIC,
mname,
sig(ret, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject.class, Block.class),
null,
null);
SkinnyMethodAdapter old_method = null;
SkinnyMethodAdapter var_old_method = null;
SkinnyMethodAdapter inv_old_method = null;
boolean oldInNestedMethod = inNestedMethod;
inNestedMethod = true;
Label[] oldLoopLabels = currentLoopLabels;
currentLoopLabels = null;
int oldArgCount = argParamCount;
argParamCount = 0; // synthetic methods always have zero arg parameters
try {
old_method = this.method;
var_old_method = getVariableCompiler().getMethodAdapter();
inv_old_method = getInvocationCompiler().getMethodAdapter();
this.method = mv;
getVariableCompiler().setMethodAdapter(mv);
getInvocationCompiler().setMethodAdapter(mv);
mv.visitCode();
mv.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
mv.invokevirtual(p(ThreadContext.class), "getCurrentScope", sig(DynamicScope.class));
mv.dup();
mv.astore(getDynamicScopeIndex());
mv.invokevirtual(p(DynamicScope.class), "getValues", sig(IRubyObject[].class));
mv.astore(getVarsArrayIndex());
Label codeBegin = new Label();
Label codeEnd = new Label();
Label ensureBegin = new Label();
Label ensureEnd = new Label();
method.label(codeBegin);
regularCode.branch(this);
method.label(codeEnd);
protectedCode.branch(this);
mv.areturn();
method.label(ensureBegin);
method.astore(getExceptionIndex());
method.label(ensureEnd);
protectedCode.branch(this);
method.aload(getExceptionIndex());
method.athrow();
method.trycatch(codeBegin, codeEnd, ensureBegin, null);
method.trycatch(ensureBegin, ensureEnd, ensureBegin, null);
mv.visitMaxs(1, 1);
mv.visitEnd();
} finally {
this.method = old_method;
getVariableCompiler().setMethodAdapter(var_old_method);
getInvocationCompiler().setMethodAdapter(inv_old_method);
inNestedMethod = oldInNestedMethod;
currentLoopLabels = oldLoopLabels;
argParamCount = oldArgCount;
}
method.aload(StandardASMCompiler.THIS);
loadThreadContext();
loadSelf();
if (this instanceof ChildScopedBodyCompiler) {
pushNull();
} else {
loadBlock();
}
method.invokestatic(
script.getClassname(),
mname,
sig(ret, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject.class, Block.class));
}
public void performEnsure(BranchCallback regularCode, BranchCallback protectedCode) {
String mname = getNewEnsureName();
BaseBodyCompiler ensure = outline(mname);
ensure.performEnsureInner(regularCode, protectedCode);
}
private void performEnsureInner(BranchCallback regularCode, BranchCallback protectedCode) {
Label codeBegin = new Label();
Label codeEnd = new Label();
Label ensureBegin = new Label();
Label ensureEnd = new Label();
method.label(codeBegin);
regularCode.branch(this);
method.label(codeEnd);
protectedCode.branch(this);
method.areturn();
method.label(ensureBegin);
method.astore(getExceptionIndex());
method.label(ensureEnd);
protectedCode.branch(this);
method.aload(getExceptionIndex());
method.athrow();
method.trycatch(codeBegin, codeEnd, ensureBegin, null);
method.trycatch(ensureBegin, ensureEnd, ensureBegin, null);
loadNil();
endBody();
}
protected String getNewRescueName() {
return "rescue_" + (script.getAndIncrementRescueNumber()) + "$RUBY$SYNTHETIC" + JavaNameMangler.mangleMethodName(getRubyName());
}
public void storeExceptionInErrorInfo() {
loadException();
loadThreadContext();
invokeUtilityMethod("storeExceptionInErrorInfo", sig(void.class, Throwable.class, ThreadContext.class));
}
public void storeNativeExceptionInErrorInfo() {
loadException();
loadThreadContext();
invokeUtilityMethod("storeNativeExceptionInErrorInfo", sig(void.class, Throwable.class, ThreadContext.class));
}
public void clearErrorInfo() {
loadThreadContext();
invokeUtilityMethod("clearErrorInfo", sig(void.class, ThreadContext.class));
}
public void rescue(BranchCallback regularCode, Class exception, BranchCallback catchCode, Class ret) {
String mname = getNewRescueName();
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
script.getClassVisitor(),
ACC_PUBLIC | ACC_SYNTHETIC | ACC_STATIC,
mname,
sig(ret, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject.class, Block.class),
null,
null);
SkinnyMethodAdapter old_method = null;
SkinnyMethodAdapter var_old_method = null;
SkinnyMethodAdapter inv_old_method = null;
Label afterMethodBody = new Label();
Label catchRetry = new Label();
Label catchRaised = new Label();
Label catchJumps = new Label();
Label exitRescue = new Label();
boolean oldWithinProtection = inNestedMethod;
inNestedMethod = true;
Label[] oldLoopLabels = currentLoopLabels;
currentLoopLabels = null;
int oldArgCount = argParamCount;
argParamCount = 0; // synthetic methods always have zero arg parameters
try {
old_method = this.method;
var_old_method = getVariableCompiler().getMethodAdapter();
inv_old_method = getInvocationCompiler().getMethodAdapter();
this.method = mv;
getVariableCompiler().setMethodAdapter(mv);
getInvocationCompiler().setMethodAdapter(mv);
mv.start();
// store previous exception for restoration if we rescue something
loadThreadContext();
invokeThreadContext("getErrorInfo", sig(IRubyObject.class));
mv.astore(getPreviousExceptionIndex());
mv.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
mv.invokevirtual(p(ThreadContext.class), "getCurrentScope", sig(DynamicScope.class));
mv.astore(getDynamicScopeIndex());
// if more than 4 vars, get values array too
if (scope.getNumberOfVariables() > 4) {
mv.aload(getDynamicScopeIndex());
mv.invokevirtual(p(DynamicScope.class), "getValues", sig(IRubyObject[].class));
mv.astore(getVarsArrayIndex());
}
Label beforeBody = new Label();
Label afterBody = new Label();
Label catchBlock = new Label();
mv.trycatch(beforeBody, afterBody, catchBlock, p(exception));
mv.label(beforeBody);
regularCode.branch(this);
mv.label(afterBody);
mv.go_to(exitRescue);
mv.label(catchBlock);
mv.astore(getExceptionIndex());
catchCode.branch(this);
mv.label(afterMethodBody);
mv.go_to(exitRescue);
// retry handling in the rescue block
mv.trycatch(catchBlock, afterMethodBody, catchRetry, p(JumpException.RetryJump.class));
mv.label(catchRetry);
mv.pop();
mv.go_to(beforeBody);
// any exceptions raised must continue to be raised, skipping $! restoration
mv.trycatch(beforeBody, afterMethodBody, catchRaised, p(RaiseException.class));
mv.label(catchRaised);
mv.athrow();
// and remaining jump exceptions should restore $!
mv.trycatch(beforeBody, afterMethodBody, catchJumps, p(JumpException.class));
mv.label(catchJumps);
loadThreadContext();
method.aload(getPreviousExceptionIndex());
invokeThreadContext("setErrorInfo", sig(IRubyObject.class, IRubyObject.class));
method.pop();
mv.athrow();
mv.label(exitRescue);
// restore the original exception
loadThreadContext();
method.aload(getPreviousExceptionIndex());
invokeThreadContext("setErrorInfo", sig(IRubyObject.class, IRubyObject.class));
method.pop();
mv.areturn();
mv.end();
} finally {
inNestedMethod = oldWithinProtection;
this.method = old_method;
getVariableCompiler().setMethodAdapter(var_old_method);
getInvocationCompiler().setMethodAdapter(inv_old_method);
currentLoopLabels = oldLoopLabels;
argParamCount = oldArgCount;
}
method.aload(StandardASMCompiler.THIS);
loadThreadContext();
loadSelf();
if (this instanceof ChildScopedBodyCompiler) {
pushNull();
} else {
loadBlock();
}
method.invokestatic(
script.getClassname(),
mname,
sig(ret, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject.class, Block.class));
}
public void performRescue(BranchCallback regularCode, BranchCallback rubyCatchCode, BranchCallback rubyElseCode, boolean needsRetry) {
String mname = getNewRescueName();
BaseBodyCompiler rescueMethod = outline(mname);
rescueMethod.performRescueInternal(regularCode, rubyCatchCode, rubyElseCode, needsRetry, false);
rescueMethod.endBody();
}
public void performRescueLight(BranchCallback regularCode, BranchCallback rubyCatchCode, BranchCallback rubyElseCode, boolean needsRetry) {
performRescueInternal(regularCode, rubyCatchCode, rubyElseCode, needsRetry, true);
}
public void performRescueInternal(BranchCallback regularCode, BranchCallback rubyCatchCode, BranchCallback rubyElseCode, boolean needsRetry, boolean light) {
Label afterRubyCatchBody = new Label();
Label catchRetry = new Label();
Label catchJumps = new Label();
Label exitRescue = new Label();
// because we must handle clearing $! properly for early returns, treat
// this method as a "nested" method, and use full exception logic for
// return, break, etc.
boolean oldInNested = inNestedMethod;
inNestedMethod = !light;
try {
// store previous exception for restoration if we rescue something
loadThreadContext();
invokeThreadContext("getErrorInfo", sig(IRubyObject.class));
method.astore(getPreviousExceptionIndex());
Label beforeBody = new Label();
Label afterBody = new Label();
Label rubyCatchBlock = new Label();
Label flowCatchBlock = new Label();
Label elseLabel = new Label();
method.visitTryCatchBlock(beforeBody, afterBody, flowCatchBlock, p(JumpException.FlowControlException.class));
method.visitTryCatchBlock(beforeBody, afterBody, rubyCatchBlock, p(Throwable.class));
method.visitLabel(beforeBody);
{
regularCode.branch(this);
}
method.label(afterBody);
if (rubyElseCode != null) {
method.go_to(elseLabel);
} else {
method.go_to(exitRescue);
}
// Handle Flow exceptions, just propagating them
method.label(flowCatchBlock);
{
// rethrow to outer flow catcher
method.athrow();
}
// Handle Ruby exceptions (RaiseException)
method.label(rubyCatchBlock);
{
// if in rescue and we do a hard return, must clear $!
boolean oldInRescue = inRescue;
inRescue = true;
try {
method.astore(getExceptionIndex());
rubyCatchCode.branch(this);
} finally {
inRescue = oldInRescue;
}
method.label(afterRubyCatchBody);
method.go_to(exitRescue);
}
// retry handling in the rescue blocks
if (needsRetry) {
method.trycatch(rubyCatchBlock, afterRubyCatchBody, catchRetry, p(JumpException.RetryJump.class));
method.label(catchRetry);
{
method.pop();
}
method.go_to(beforeBody);
}
// and remaining jump exceptions should restore $!
method.trycatch(beforeBody, afterRubyCatchBody, catchJumps, p(JumpException.FlowControlException.class));
method.label(catchJumps);
{
loadThreadContext();
method.aload(getPreviousExceptionIndex());
invokeThreadContext("setErrorInfo", sig(IRubyObject.class, IRubyObject.class));
method.pop();
method.athrow();
}
if (rubyElseCode != null) {
method.label(elseLabel);
rubyElseCode.branch(this);
}
method.label(exitRescue);
// restore the original exception
loadThreadContext();
method.aload(getPreviousExceptionIndex());
invokeThreadContext("setErrorInfo", sig(IRubyObject.class, IRubyObject.class));
method.pop();
} finally {
inNestedMethod = oldInNested;
}
}
public void wrapJavaException() {
loadRuntime();
loadException();
wrapJavaObject();
}
public void wrapJavaObject() {
method.invokestatic(p(JavaUtil.class), "convertJavaToUsableRubyObject", sig(IRubyObject.class, Ruby.class, Object.class));
}
public void stringOrNil() {
loadThreadContext();
invokeUtilityMethod("stringOrNil", sig(IRubyObject.class, ByteList.class, ThreadContext.class));
}
public void pushNull() {
method.aconst_null();
}
public void pushString(String str) {
method.ldc(str);
}
public void pushByteList(ByteList byteList) {
script.getCacheCompiler().cacheByteList(this, byteList);
}
public void pushDefinedMessage(DefinedMessage definedMessage) {
loadRuntime();
method.getstatic(p(DefinedMessage.class), definedMessage.name(), ci(DefinedMessage.class));
method.invokevirtual(p(Ruby.class), "getDefinedMessage", sig(RubyString.class, DefinedMessage.class));
}
public void isMethodBound(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
metaclass();
method.ldc(name);
method.iconst_0(); // push false
method.invokevirtual(p(RubyClass.class), "isMethodBound", sig(boolean.class, params(String.class, boolean.class)));
Label falseLabel = new Label();
Label exitLabel = new Label();
method.ifeq(falseLabel); // EQ == 0 (i.e. false)
trueBranch.branch(this);
method.go_to(exitLabel);
method.label(falseLabel);
falseBranch.branch(this);
method.label(exitLabel);
}
public void hasBlock(BranchCallback trueBranch, BranchCallback falseBranch) {
loadBlock();
method.invokevirtual(p(Block.class), "isGiven", sig(boolean.class));
Label falseLabel = new Label();
Label exitLabel = new Label();
method.ifeq(falseLabel); // EQ == 0 (i.e. false)
trueBranch.branch(this);
method.go_to(exitLabel);
method.label(falseLabel);
falseBranch.branch(this);
method.label(exitLabel);
}
public void isGlobalDefined(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
loadRuntime();
invokeRuby("getGlobalVariables", sig(GlobalVariables.class));
method.ldc(name);
method.invokevirtual(p(GlobalVariables.class), "isDefined", sig(boolean.class, params(String.class)));
Label falseLabel = new Label();
Label exitLabel = new Label();
method.ifeq(falseLabel); // EQ == 0 (i.e. false)
trueBranch.branch(this);
method.go_to(exitLabel);
method.label(falseLabel);
falseBranch.branch(this);
method.label(exitLabel);
}
public void isConstantDefined(String name) {
script.getCacheCompiler().cacheConstantDefined(this, name);
}
public void isInstanceVariableDefined(String name) {
script.getCacheCompiler().cachedGetVariableDefined(this, name);
}
public void isClassVarDefined(String name, BranchCallback trueBranch, BranchCallback falseBranch) {
method.ldc(name);
method.invokevirtual(p(RubyModule.class), "isClassVarDefined", sig(boolean.class, params(String.class)));
Label trueLabel = new Label();
Label exitLabel = new Label();
method.ifne(trueLabel);
falseBranch.branch(this);
method.go_to(exitLabel);
method.label(trueLabel);
trueBranch.branch(this);
method.label(exitLabel);
}
public Object getNewEnding() {
return new Label();
}
public void isNil(BranchCallback trueBranch, BranchCallback falseBranch) {
method.invokeinterface(p(IRubyObject.class), "isNil", sig(boolean.class));
Label falseLabel = new Label();
Label exitLabel = new Label();
method.ifeq(falseLabel); // EQ == 0 (i.e. false)
trueBranch.branch(this);
method.go_to(exitLabel);
method.label(falseLabel);
falseBranch.branch(this);
method.label(exitLabel);
}
public void isNull(BranchCallback trueBranch, BranchCallback falseBranch) {
Label falseLabel = new Label();
Label exitLabel = new Label();
method.ifnonnull(falseLabel);
trueBranch.branch(this);
method.go_to(exitLabel);
method.label(falseLabel);
falseBranch.branch(this);
method.label(exitLabel);
}
public void ifNull(Object gotoToken) {
method.ifnull((Label) gotoToken);
}
public void ifNotNull(Object gotoToken) {
method.ifnonnull((Label) gotoToken);
}
public void setEnding(Object endingToken) {
method.label((Label) endingToken);
}
public void go(Object gotoToken) {
method.go_to((Label) gotoToken);
}
public void isConstantBranch(final BranchCallback setup, final String name) {
BranchCallback catchCode = new BranchCallback() {
public void branch(BodyCompiler context) {
// restore the original exception
loadThreadContext();
method.aload(getPreviousExceptionIndex());
invokeThreadContext("setErrorInfo", sig(IRubyObject.class, IRubyObject.class));
method.pop();
pushNull();
}
};
BranchCallback regularCode = new BranchCallback() {
public void branch(BodyCompiler context) {
setup.branch(BaseBodyCompiler.this);
method.ldc(name); //[C, C, String]
invokeUtilityMethod("getDefinedConstantOrBoundMethod", sig(RubyString.class, IRubyObject.class, String.class));
}
};
String mname = getNewRescueName();
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(
script.getClassVisitor(),
ACC_PUBLIC | ACC_SYNTHETIC | ACC_STATIC,
mname,
sig(RubyString.class, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject.class, Block.class),
null,
null);
SkinnyMethodAdapter old_method = null;
SkinnyMethodAdapter var_old_method = null;
SkinnyMethodAdapter inv_old_method = null;
Label exitRescue = new Label();
boolean oldWithinProtection = inNestedMethod;
inNestedMethod = true;
Label[] oldLoopLabels = currentLoopLabels;
currentLoopLabels = null;
int oldArgCount = argParamCount;
argParamCount = 0; // synthetic methods always have zero arg parameters
try {
old_method = this.method;
var_old_method = getVariableCompiler().getMethodAdapter();
inv_old_method = getInvocationCompiler().getMethodAdapter();
this.method = mv;
getVariableCompiler().setMethodAdapter(mv);
getInvocationCompiler().setMethodAdapter(mv);
mv.start();
// store previous exception for restoration if we rescue something
loadThreadContext();
invokeThreadContext("getErrorInfo", sig(IRubyObject.class));
mv.astore(getPreviousExceptionIndex());
mv.aload(StandardASMCompiler.THREADCONTEXT_INDEX);
mv.invokevirtual(p(ThreadContext.class), "getCurrentScope", sig(DynamicScope.class));
mv.astore(getDynamicScopeIndex());
// if more than 4 vars, get values array too
if (scope.getNumberOfVariables() > 4) {
mv.aload(getDynamicScopeIndex());
mv.invokevirtual(p(DynamicScope.class), "getValues", sig(IRubyObject[].class));
mv.astore(getVarsArrayIndex());
}
Label beforeBody = new Label();
Label afterBody = new Label();
Label catchBlock = new Label();
mv.trycatch(beforeBody, afterBody, catchBlock, p(JumpException.class));
mv.label(beforeBody);
regularCode.branch(this);
mv.label(afterBody);
mv.go_to(exitRescue);
mv.label(catchBlock);
mv.astore(getExceptionIndex());
catchCode.branch(this);
mv.label(exitRescue);
mv.areturn();
mv.end();
} finally {
inNestedMethod = oldWithinProtection;
this.method = old_method;
getVariableCompiler().setMethodAdapter(var_old_method);
getInvocationCompiler().setMethodAdapter(inv_old_method);
currentLoopLabels = oldLoopLabels;
argParamCount = oldArgCount;
}
method.aload(StandardASMCompiler.THIS);
loadThreadContext();
loadSelf();
if (this instanceof ChildScopedBodyCompiler) {
pushNull();
} else {
loadBlock();
}
method.invokestatic(
script.getClassname(),
mname,
sig(RubyString.class, "L" + script.getClassname() + ";", ThreadContext.class, IRubyObject.class, Block.class));
}
public void metaclass() {
invokeIRubyObject("getMetaClass", sig(RubyClass.class));
}
public void aprintln() {
method.aprintln();
}
public void getVisibilityFor(String name) {
method.ldc(name);
method.invokevirtual(p(RubyClass.class), "searchMethod", sig(DynamicMethod.class, params(String.class)));
method.invokevirtual(p(DynamicMethod.class), "getVisibility", sig(Visibility.class));
}
public void isPrivate(Object gotoToken, int toConsume) {
method.getstatic(p(Visibility.class), "PRIVATE", ci(Visibility.class));
Label temp = new Label();
method.if_acmpne(temp);
while ((toConsume--) > 0) {
method.pop();
}
method.go_to((Label) gotoToken);
method.label(temp);
}
public void isNotProtected(Object gotoToken, int toConsume) {
method.getstatic(p(Visibility.class), "PROTECTED", ci(Visibility.class));
Label temp = new Label();
method.if_acmpeq(temp);
while ((toConsume--) > 0) {
method.pop();
}
method.go_to((Label) gotoToken);
method.label(temp);
}
public void selfIsKindOf(Object gotoToken) {
method.invokevirtual(p(RubyClass.class), "getRealClass", sig(RubyClass.class));
loadSelf();
method.invokevirtual(p(RubyModule.class), "isInstance", sig(boolean.class, params(IRubyObject.class)));
method.ifne((Label) gotoToken); // EQ != 0 (i.e. true)
}
public void notIsModuleAndClassVarDefined(String name, Object gotoToken) {
method.dup(); //[?, ?]
method.instance_of(p(RubyModule.class)); //[?, boolean]
Label falsePopJmp = new Label();
Label successJmp = new Label();
method.ifeq(falsePopJmp);
method.visitTypeInsn(CHECKCAST, p(RubyModule.class)); //[RubyModule]
method.ldc(name); //[RubyModule, String]
method.invokevirtual(p(RubyModule.class), "fastIsClassVarDefined", sig(boolean.class, params(String.class))); //[boolean]
method.ifeq((Label) gotoToken);
method.go_to(successJmp);
method.label(falsePopJmp);
method.pop();
method.go_to((Label) gotoToken);
method.label(successJmp);
}
public void ifSingleton(Object gotoToken) {
method.invokevirtual(p(RubyModule.class), "isSingleton", sig(boolean.class));
method.ifne((Label) gotoToken); // EQ == 0 (i.e. false)
}
public void getInstanceVariable(String name) {
method.ldc(name);
invokeIRubyObject("getInstanceVariables", sig(InstanceVariables.class));
method.invokeinterface(p(InstanceVariables.class), "fastGetInstanceVariable", sig(IRubyObject.class, params(String.class)));
}
public void getFrameName() {
loadThreadContext();
invokeThreadContext("getFrameName", sig(String.class));
}
public void getFrameKlazz() {
loadThreadContext();
invokeThreadContext("getFrameKlazz", sig(RubyModule.class));
}
public void superClass() {
loadSelf();
metaclass();
method.swap();
invokeUtilityMethod("findImplementerIfNecessary", sig(RubyModule.class, params(RubyModule.class, RubyModule.class)));
method.invokevirtual(p(RubyModule.class), "getSuperClass", sig(RubyClass.class));
}
public void attached() {
method.visitTypeInsn(CHECKCAST, p(MetaClass.class));
method.invokevirtual(p(MetaClass.class), "getAttached", sig(IRubyObject.class));
}
public void ifNotSuperMethodBound(Object token) {
method.swap();
method.iconst_0();
method.invokevirtual(p(RubyModule.class), "isMethodBound", sig(boolean.class, params(String.class, boolean.class)));
method.ifeq((Label) token);
}
public void concatArrays() {
method.invokevirtual(p(RubyArray.class), "concat", sig(RubyArray.class, params(IRubyObject.class)));
}
public void concatObjectArrays() {
invokeUtilityMethod("concatObjectArrays", sig(IRubyObject[].class, params(IRubyObject[].class, IRubyObject[].class)));
}
public void appendToArray() {
method.invokevirtual(p(RubyArray.class), "append", sig(RubyArray.class, params(IRubyObject.class)));
}
public void appendToObjectArray() {
invokeUtilityMethod("appendToObjectArray", sig(IRubyObject[].class, params(IRubyObject[].class, IRubyObject.class)));
}
public void splatToArguments() {
invokeUtilityMethod("splatToArguments", sig(IRubyObject[].class, IRubyObject.class));
}
public void splatToArguments19() {
invokeUtilityMethod("splatToArguments19", sig(IRubyObject[].class, IRubyObject.class));
}
public void argsCatToArguments() {
invokeUtilityMethod("argsCatToArguments", sig(IRubyObject[].class, IRubyObject[].class, IRubyObject.class));
}
public void argsCatToArguments19() {
invokeUtilityMethod("argsCatToArguments19", sig(IRubyObject[].class, IRubyObject[].class, IRubyObject.class));
}
public void convertToJavaArray() {
method.invokestatic(p(ArgsUtil.class), "convertToJavaArray", sig(IRubyObject[].class, params(IRubyObject.class)));
}
public void aliasGlobal(String newName, String oldName) {
loadRuntime();
invokeRuby("getGlobalVariables", sig(GlobalVariables.class));
method.ldc(newName);
method.ldc(oldName);
method.invokevirtual(p(GlobalVariables.class), "alias", sig(Void.TYPE, params(String.class, String.class)));
loadNil();
}
public void raiseTypeError(String msg) {
loadRuntime();
method.ldc(msg);
invokeRuby("newTypeError", sig(RaiseException.class, params(String.class)));
method.athrow();
}
public void undefMethod(CompilerCallback nameArg) {
loadThreadContext();
nameArg.call(this);
invokeUtilityMethod("undefMethod", sig(IRubyObject.class, ThreadContext.class, Object.class));
}
public void defineClass(
final String name,
final StaticScope staticScope,
final CompilerCallback superCallback,
final CompilerCallback pathCallback,
final CompilerCallback bodyCallback,
final CompilerCallback receiverCallback,
final ASTInspector inspector,
final ISourcePosition startPosition) {
String classMethodName = null;
String rubyName;
if (receiverCallback == null) {
String mangledName = JavaNameMangler.mangleMethodName(name);
classMethodName = "class_" + script.getAndIncrementMethodIndex() + "$RUBY$" + mangledName;
rubyName = mangledName;
} else {
classMethodName = "sclass_" + script.getAndIncrementMethodIndex() + "$RUBY$__singleton__";
rubyName = "__singleton__";
}
// prepare to call class definition method
method.aload(StandardASMCompiler.THIS);
loadThreadContext();
// class object
if (receiverCallback == null) {
// no receiver for singleton class
if (superCallback != null) {
// but there's a superclass provided
loadRuntime();
superCallback.call(this);
invokeUtilityMethod("prepareSuperClass", sig(RubyClass.class, params(Ruby.class, IRubyObject.class)));
} else {
method.aconst_null();
}
loadThreadContext();
loadStaticScope();
pathCallback.call(this);
invokeUtilityMethod("prepareClassNamespace", sig(RubyModule.class, params(ThreadContext.class, StaticScope.class, IRubyObject.class)));
method.swap();
method.ldc(name);
method.swap();
method.invokevirtual(p(RubyModule.class), "defineOrGetClassUnder", sig(RubyClass.class, params(String.class, RubyClass.class)));
} else {
loadRuntime();
receiverCallback.call(this);
invokeUtilityMethod("getSingletonClass", sig(RubyClass.class, params(Ruby.class, IRubyObject.class)));
}
// prepare and cache static scope using target class/module already on stack
method.dup();
loadThreadContext();
method.swap(); // [..., RubyModule, ThreadContext]
final int scopeIndex = script.getCacheCompiler().cacheStaticScope(this, staticScope); // [..., RubyModule, ThreadContext, StaticScope]
if (inspector.hasClosure() || inspector.hasScopeAwareMethods()) {
invokeThreadContext("preCompiledClass", sig(Void.TYPE, params(RubyModule.class, StaticScope.class)));
} else {
invokeThreadContext("preCompiledClassDummyScope", sig(Void.TYPE, params(RubyModule.class, StaticScope.class)));
}
method.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
method.invokestatic(script.getClassname(), classMethodName, StandardASMCompiler.getStaticMethodSignature(script.getClassname(), 0));
// define the method that forms the body of the class/module
final RootScopedBodyCompiler classBody = new ClassBodyCompiler(script, classMethodName, rubyName, inspector, staticScope, scopeIndex);
Label start = new Label();
Label end = new Label();
Label after = new Label();
Label noException = new Label();
classBody.method.trycatch(start, end, after, null);
classBody.beginMethod(null, staticScope);
if (RubyInstanceConfig.FULL_TRACE_ENABLED) classBody.traceClass(startPosition);
classBody.method.label(start);
bodyCallback.call(classBody);
classBody.method.label(end);
// finally with no exception
if (RubyInstanceConfig.FULL_TRACE_ENABLED) classBody.traceEnd(lastPositionLine);
classBody.loadThreadContext();
classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));
classBody.method.go_to(noException);
classBody.method.label(after);
// finally with exception
if (RubyInstanceConfig.FULL_TRACE_ENABLED) classBody.traceEnd(lastPositionLine);
classBody.loadThreadContext();
classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));
classBody.method.athrow();
classBody.method.label(noException);
classBody.endBody();
}
public void defineModule(final String name,
final StaticScope staticScope,
final CompilerCallback pathCallback,
final CompilerCallback bodyCallback,
final ASTInspector inspector,
final ISourcePosition startPosition) {
String mangledName = JavaNameMangler.mangleMethodName(name);
String moduleMethodName = "module__" + script.getAndIncrementMethodIndex() + "$RUBY$" + mangledName;
// prepare to call class definition method
method.aload(StandardASMCompiler.THIS);
loadThreadContext();
// prepare module object
loadThreadContext();
loadStaticScope();
pathCallback.call(this);
invokeUtilityMethod("prepareClassNamespace", sig(RubyModule.class, params(ThreadContext.class, StaticScope.class, IRubyObject.class)));
method.ldc(name);
method.invokevirtual(p(RubyModule.class), "defineOrGetModuleUnder", sig(RubyModule.class, params(String.class)));
// prepare and cache static scope using target class/module already on stack
method.dup();
loadThreadContext();
method.swap(); // [..., RubyModule, ThreadContext]
final int scopeIndex = script.getCacheCompiler().cacheStaticScope(this, staticScope); // [..., RubyModule, ThreadContext, StaticScope]
if (inspector.hasClosure() || inspector.hasScopeAwareMethods()) {
invokeThreadContext("preCompiledClass", sig(Void.TYPE, params(RubyModule.class, StaticScope.class)));
} else {
invokeThreadContext("preCompiledClassDummyScope", sig(Void.TYPE, params(RubyModule.class, StaticScope.class)));
}
method.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
method.invokestatic(script.getClassname(), moduleMethodName, StandardASMCompiler.getStaticMethodSignature(script.getClassname(), 0));
// define the method that forms the body of the class/module
final RootScopedBodyCompiler classBody = new ClassBodyCompiler(script, moduleMethodName, mangledName, inspector, staticScope, scopeIndex);
Label start = new Label();
Label end = new Label();
Label after = new Label();
Label noException = new Label();
classBody.method.trycatch(start, end, after, null);
classBody.beginMethod(null, staticScope);
if (RubyInstanceConfig.FULL_TRACE_ENABLED) classBody.traceClass(startPosition);
classBody.method.label(start);
bodyCallback.call(classBody);
classBody.method.label(end);
classBody.method.go_to(noException);
classBody.method.label(after);
if (RubyInstanceConfig.FULL_TRACE_ENABLED) classBody.traceEnd(lastPositionLine);
classBody.loadThreadContext();
classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));
classBody.method.athrow();
classBody.method.label(noException);
if (RubyInstanceConfig.FULL_TRACE_ENABLED) classBody.traceEnd(lastPositionLine);
classBody.loadThreadContext();
classBody.invokeThreadContext("postCompiledClass", sig(Void.TYPE, params()));
classBody.endBody();
}
public void unwrapPassedBlock() {
loadBlock();
invokeUtilityMethod("getBlockFromBlockPassBody", sig(Block.class, params(IRubyObject.class, Block.class)));
}
public void performBackref(char type) {
loadThreadContext();
switch (type) {
case '~':
invokeUtilityMethod("backref", sig(IRubyObject.class, params(ThreadContext.class)));
break;
case '&':
invokeUtilityMethod("backrefLastMatch", sig(IRubyObject.class, params(ThreadContext.class)));
break;
case '`':
invokeUtilityMethod("backrefMatchPre", sig(IRubyObject.class, params(ThreadContext.class)));
break;
case '\'':
invokeUtilityMethod("backrefMatchPost", sig(IRubyObject.class, params(ThreadContext.class)));
break;
case '+':
invokeUtilityMethod("backrefMatchLast", sig(IRubyObject.class, params(ThreadContext.class)));
break;
default:
throw new NotCompilableException("ERROR: backref with invalid type");
}
}
public void callZSuper(CompilerCallback closure) {
ArgumentsCallback argsCallback = new ArgumentsCallback() {
public int getArity() {
return -1;
}
public void call(BodyCompiler context) {
loadThreadContext();
invokeUtilityMethod("getArgValues", sig(IRubyObject[].class, ThreadContext.class));
}
};
getInvocationCompiler().invokeDynamicVarargs(null, null, argsCallback, CallType.SUPER, closure, false);
}
public void checkIsExceptionHandled(ArgumentsCallback rescueArgs) {
// original exception is on stack
rescueArgs.call(this);
loadThreadContext();
switch (rescueArgs.getArity()) {
case 1:
invokeUtilityMethod("isJavaExceptionHandled", sig(IRubyObject.class, Throwable.class, IRubyObject.class, ThreadContext.class));
break;
case 2:
invokeUtilityMethod("isJavaExceptionHandled", sig(IRubyObject.class, Throwable.class, IRubyObject.class, IRubyObject.class, ThreadContext.class));
break;
case 3:
invokeUtilityMethod("isJavaExceptionHandled", sig(IRubyObject.class, Throwable.class, IRubyObject.class, IRubyObject.class, IRubyObject.class, ThreadContext.class));
break;
default:
invokeUtilityMethod("isJavaExceptionHandled", sig(IRubyObject.class, Throwable.class, IRubyObject[].class, ThreadContext.class));
}
}
public void rethrowException() {
loadException();
method.athrow();
}
public void loadClass(String name) {
loadRuntime();
method.ldc(name);
invokeRuby("getClass", sig(RubyClass.class, String.class));
}
public void loadStandardError() {
loadRuntime();
invokeRuby("getStandardError", sig(RubyClass.class));
}
public void unwrapRaiseException() {
// RaiseException is on stack, get RubyException out
method.invokevirtual(p(RaiseException.class), "getException", sig(RubyException.class));
}
public void loadException() {
method.aload(getExceptionIndex());
}
public void setLinePosition(ISourcePosition position) {
lastPositionLine = position.getStartLine();
}
public void checkWhenWithSplat() {
loadThreadContext();
invokeUtilityMethod("isWhenTriggered", sig(RubyBoolean.class, IRubyObject.class, IRubyObject.class, ThreadContext.class));
}
public void issueRetryEvent() {
invokeUtilityMethod("retryJump", sig(IRubyObject.class));
}
public void defineNewMethod(String name, int methodArity, StaticScope scope,
CompilerCallback body, CompilerCallback args,
CompilerCallback receiver, ASTInspector inspector, boolean root,
String filename, int line, String parameterDesc) {
String mangledName = JavaNameMangler.mangleMethodName(name);
String newMethodName = "method__" + script.getAndIncrementMethodIndex() + "$RUBY$" + mangledName;
// prepare to call "def" utility method to handle def logic
loadThreadContext();
loadSelf();
if (receiver != null) {
receiver.call(this); // script object
}
method.aload(StandardASMCompiler.THIS);
method.ldc(name);
method.ldc(newMethodName);
int scopeIndex = script.getCacheCompiler().cacheStaticScope(this, scope);
method.pushInt(methodArity);
// arities
method.ldc(filename);
method.ldc(line);
method.getstatic(p(CallConfiguration.class), inspector.getCallConfig().name(), ci(CallConfiguration.class));
method.ldc(parameterDesc);
if (receiver != null) {
invokeUtilityMethod("defs", sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, IRubyObject.class, Object.class, String.class, String.class, StaticScope.class, int.class, String.class, int.class, CallConfiguration.class, String.class)));
} else {
invokeUtilityMethod("def", sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, Object.class, String.class, String.class, StaticScope.class, int.class, String.class, int.class, CallConfiguration.class, String.class)));
}
script.addInvokerDescriptor(name, newMethodName, methodArity, scope, inspector.getCallConfig(), filename, line);
// emit method body
BodyCompiler methodCompiler = script.startMethod(name, newMethodName, args, scope, inspector, scopeIndex);
// callbacks to fill in method body
body.call(methodCompiler);
methodCompiler.endBody();
}
public void rethrowIfSystemExit() {
loadRuntime();
method.ldc("SystemExit");
method.invokevirtual(p(Ruby.class), "fastGetClass", sig(RubyClass.class, String.class));
method.swap();
method.invokevirtual(p(RubyModule.class), "isInstance", sig(boolean.class, params(IRubyObject.class)));
method.iconst_0();
Label ifEnd = new Label();
method.if_icmpeq(ifEnd);
loadException();
method.athrow();
method.label(ifEnd);
}
public void literalSwitch(int[] cases, Object[] bodies, ArrayCallback arrayCallback, CompilerCallback defaultCallback) {
// TODO assuming case is a fixnum
method.checkcast(p(RubyFixnum.class));
method.invokevirtual(p(RubyFixnum.class), "getLongValue", sig(long.class));
method.l2i();
Map
© 2015 - 2025 Weber Informatics LLC | Privacy Policy