org.jruby.internal.runtime.methods.InvocationMethodFactory Maven / Gradle / Ivy
/***** BEGIN LICENSE BLOCK *****
* Version: CPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Common Public
* License Version 1.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/cpl-v10.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) 2006 The JRuby Community
* Copyright (C) 2006 Ola Bini
*
* 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 CPL, 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 CPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.internal.runtime.methods;
import java.io.PrintWriter;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
import org.jruby.Ruby;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyKernel;
import org.jruby.parser.StaticScope;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.jruby.RubyModule;
import org.jruby.RubyString;
import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JavaMethodDescriptor;
import org.jruby.anno.TypePopulator;
import org.jruby.compiler.impl.SkinnyMethodAdapter;
import org.jruby.compiler.impl.StandardASMCompiler;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.RaiseException;
import org.jruby.lexer.yacc.ISourcePosition;
import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CompiledBlockCallback;
import org.jruby.runtime.CompiledBlockCallback19;
import org.jruby.runtime.MethodFactory;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.CodegenUtils;
import static org.jruby.util.CodegenUtils.*;
import static java.lang.System.*;
import org.jruby.util.JRubyClassLoader;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Label;
import org.objectweb.asm.util.CheckClassAdapter;
/**
* In order to avoid the overhead with reflection-based method handles, this
* MethodFactory uses ASM to generate tiny invoker classes. This allows for
* better performance and more specialization per-handle than can be supported
* via reflection. It also allows optimizing away many conditionals that can
* be determined once ahead of time.
*
* When running in secured environments, this factory may not function. When
* this can be detected, MethodFactory will fall back on the reflection-based
* factory instead.
*
* @see org.jruby.internal.runtime.methods.MethodFactory
*/
public class InvocationMethodFactory extends MethodFactory implements Opcodes {
private static final boolean DEBUG = false;
/** The pathname of the super class for compiled Ruby method handles. */
private final static String COMPILED_SUPER_CLASS = p(CompiledMethod.class);
/** The outward call signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject[].class));
/** The outward call signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_BLOCK = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject[].class, Block.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_ZERO_BLOCK = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, Block.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_ZERO = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_ONE_BLOCK = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject.class, Block.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_ONE = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_TWO_BLOCK = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject.class, IRubyObject.class, Block.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_TWO = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject.class, IRubyObject.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_THREE_BLOCK = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject.class, IRubyObject.class, IRubyObject.class, Block.class));
/** The outward arity-zero call-with-block signature for compiled Ruby method handles. */
private final static String COMPILED_CALL_SIG_THREE = sig(IRubyObject.class,
params(ThreadContext.class, IRubyObject.class, RubyModule.class, String.class, IRubyObject.class, IRubyObject.class, IRubyObject.class));
private final static String BLOCK_CALL_SIG = sig(RubyKernel.IRUBY_OBJECT, params(
ThreadContext.class, RubyKernel.IRUBY_OBJECT, IRubyObject.class, Block.class));
private final static String BLOCK_CALL_SIG19 = sig(RubyKernel.IRUBY_OBJECT, params(
ThreadContext.class, IRubyObject.class, IRubyObject[].class, Block.class));
/** The super constructor signature for Java-based method handles. */
private final static String JAVA_SUPER_SIG = sig(Void.TYPE, params(RubyModule.class, Visibility.class));
/** The lvar index of "this" */
public static final int THIS_INDEX = 0;
/** The lvar index of the passed-in ThreadContext */
public static final int THREADCONTEXT_INDEX = 1;
/** The lvar index of the method-receiving object */
public static final int RECEIVER_INDEX = 2;
/** The lvar index of the RubyClass being invoked against */
public static final int CLASS_INDEX = 3;
/** The lvar index method name being invoked */
public static final int NAME_INDEX = 4;
/** The lvar index of the method args on the call */
public static final int ARGS_INDEX = 5;
/** The lvar index of the passed-in Block on the call */
public static final int BLOCK_INDEX = 6;
/** The classloader to use for code loading */
protected final JRubyClassLoader classLoader;
/** An object to sync against when loading classes, to avoid dups */
protected final Object syncObject;
/**
* Whether this factory has seen undefined methods already. This is used to
* detect likely method handle collisions when we expect to create a new
* handle for each call.
*/
private boolean seenUndefinedClasses = false;
/**
* Whether we've informed the user that we've seen undefined methods; this
* is to avoid a flood of repetitive information.
*/
private boolean haveWarnedUser = false;
/**
* Construct a new InvocationMethodFactory using the specified classloader
* to load code. If the target classloader is not an instance of
* JRubyClassLoader, it will be wrapped with one.
*
* @param classLoader The classloader to use, or to wrap if it is not a
* JRubyClassLoader instance.
*/
public InvocationMethodFactory(ClassLoader classLoader) {
// use the given classloader as our sync, regardless of whether we wrap it
this.syncObject = classLoader;
if (classLoader instanceof JRubyClassLoader) {
this.classLoader = (JRubyClassLoader)classLoader;
} else {
this.classLoader = new JRubyClassLoader(classLoader);
}
}
/**
* Use code generation to provide a method handle for a compiled Ruby method.
*
* @see org.jruby.internal.runtime.methods.MethodFactory#getCompiledMethod
*/
public DynamicMethod getCompiledMethodLazily(
RubyModule implementationClass,
String method,
Arity arity,
Visibility visibility,
StaticScope scope,
Object scriptObject,
CallConfiguration callConfig,
ISourcePosition position,
String parameterDesc) {
return new CompiledMethod.LazyCompiledMethod(
implementationClass,
method,
arity,
visibility,
scope,
scriptObject,
callConfig,
position,
parameterDesc,
new InvocationMethodFactory(classLoader));
}
private static String getCompiledCallbackName(String typePath, String method) {
return (typePath + "$" + method).replaceAll("/", "\\$");
}
/**
* Use code generation to provide a method handle for a compiled Ruby method.
*
* @see org.jruby.internal.runtime.methods.MethodFactory#getCompiledMethod
*/
public DynamicMethod getCompiledMethod(
RubyModule implementationClass,
String method,
Arity arity,
Visibility visibility,
StaticScope scope,
Object scriptObject,
CallConfiguration callConfig,
ISourcePosition position,
String parameterDesc) {
Class scriptClass = scriptObject.getClass();
String typePath = p(scriptClass);
String invokerPath = getCompiledCallbackName(typePath, method);
synchronized (syncObject) {
Class generatedClass = tryClass(invokerPath, scriptClass);
try {
if (generatedClass == null) {
if (RubyInstanceConfig.JIT_LOADING_DEBUG) {
System.err.println("no generated handle in classloader for: " + invokerPath);
}
byte[] invokerBytes = getCompiledMethodOffline(
method,
typePath,
invokerPath,
arity,
scope,
callConfig,
position.getFile(),
position.getStartLine());
generatedClass = endCallWithBytes(invokerBytes, invokerPath);
} else if (RubyInstanceConfig.JIT_LOADING_DEBUG) {
System.err.println("found generated handle in classloader: " + invokerPath);
}
CompiledMethod compiledMethod = (CompiledMethod)generatedClass.newInstance();
compiledMethod.init(implementationClass, arity, visibility, scope, scriptObject, callConfig, position, parameterDesc);
if (arity.isFixed() && arity.required() <= 3) {
Class[] params = StandardASMCompiler.getStaticMethodParams(scriptClass, scope.getRequiredArgs());
compiledMethod.setNativeCall(scriptClass, method, IRubyObject.class, params, true);
}
return compiledMethod;
} catch(Exception e) {
e.printStackTrace();
throw implementationClass.getRuntime().newLoadError(e.getMessage());
}
}
}
/**
* Use code generation to provide a method handle for a compiled Ruby method.
*
* @see org.jruby.internal.runtime.methods.MethodFactory#getCompiledMethod
*/
@Override
public byte[] getCompiledMethodOffline(
String method, String className, String invokerPath, Arity arity,
StaticScope scope, CallConfiguration callConfig, String filename, int line) {
String sup = COMPILED_SUPER_CLASS;
ClassWriter cw;
cw = createCompiledCtor(invokerPath, invokerPath, sup);
SkinnyMethodAdapter mv = null;
String signature = null;
boolean specificArity = false;
// if trace, need to at least populate a backtrace frame
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
switch (callConfig) {
case FrameNoneScopeDummy:
callConfig = CallConfiguration.FrameBacktraceScopeDummy;
break;
case FrameNoneScopeFull:
callConfig = CallConfiguration.FrameBacktraceScopeFull;
break;
case FrameNoneScopeNone:
callConfig = CallConfiguration.FrameBacktraceScopeNone;
break;
}
}
if (scope.getRestArg() >= 0 || scope.getOptionalArgs() > 0 || scope.getRequiredArgs() > 3) {
signature = COMPILED_CALL_SIG_BLOCK;
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "call", signature, null, null);
} else {
specificArity = true;
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "call", COMPILED_CALL_SIG_BLOCK, null, null);
mv.start();
mv.line(-1);
// check arity
mv.aloadMany(0, 1, 4, 5); // method, context, name, args, required
mv.pushInt(scope.getRequiredArgs());
mv.invokestatic(p(JavaMethod.class), "checkArgumentCount", sig(void.class, JavaMethod.class, ThreadContext.class, String.class, IRubyObject[].class, int.class));
mv.aloadMany(0, 1, 2, 3, 4);
for (int i = 0; i < scope.getRequiredArgs(); i++) {
mv.aload(5);
mv.ldc(i);
mv.arrayload();
}
mv.aload(6);
switch (scope.getRequiredArgs()) {
case 0:
signature = COMPILED_CALL_SIG_ZERO_BLOCK;
break;
case 1:
signature = COMPILED_CALL_SIG_ONE_BLOCK;
break;
case 2:
signature = COMPILED_CALL_SIG_TWO_BLOCK;
break;
case 3:
signature = COMPILED_CALL_SIG_THREE_BLOCK;
break;
}
mv.invokevirtual(invokerPath, "call", signature);
mv.areturn();
mv.end();
// Define a second version that doesn't take a block, so we have unique code paths for both cases.
switch (scope.getRequiredArgs()) {
case 0:
signature = COMPILED_CALL_SIG_ZERO;
break;
case 1:
signature = COMPILED_CALL_SIG_ONE;
break;
case 2:
signature = COMPILED_CALL_SIG_TWO;
break;
case 3:
signature = COMPILED_CALL_SIG_THREE;
break;
}
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "call", signature, null, null);
mv.start();
mv.line(-1);
mv.aloadMany(0, 1, 2, 3, 4);
for (int i = 1; i <= scope.getRequiredArgs(); i++) {
mv.aload(4 + i);
}
mv.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
switch (scope.getRequiredArgs()) {
case 0:
signature = COMPILED_CALL_SIG_ZERO_BLOCK;
break;
case 1:
signature = COMPILED_CALL_SIG_ONE_BLOCK;
break;
case 2:
signature = COMPILED_CALL_SIG_TWO_BLOCK;
break;
case 3:
signature = COMPILED_CALL_SIG_THREE_BLOCK;
break;
}
mv.invokevirtual(invokerPath, "call", signature);
mv.areturn();
mv.end();
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "call", signature, null, null);
}
mv.visitCode();
mv.line(-1);
// save off callNumber
mv.aload(1);
mv.getfield(p(ThreadContext.class), "callNumber", ci(int.class));
int callNumberIndex = -1;
if (specificArity) {
switch (scope.getRequiredArgs()) {
case -1:
callNumberIndex = ARGS_INDEX + 1/*args*/ + 1/*block*/ + 1;
break;
case 0:
callNumberIndex = ARGS_INDEX + 1/*block*/ + 1;
break;
default:
callNumberIndex = ARGS_INDEX + scope.getRequiredArgs() + 1/*block*/ + 1;
}
} else {
callNumberIndex = ARGS_INDEX + 1/*block*/ + 1;
}
mv.istore(callNumberIndex);
// invoke pre method stuff
if (!callConfig.isNoop() || RubyInstanceConfig.FULL_TRACE_ENABLED) {
if (specificArity) {
invokeCallConfigPre(mv, COMPILED_SUPER_CLASS, scope.getRequiredArgs(), true, callConfig);
} else {
invokeCallConfigPre(mv, COMPILED_SUPER_CLASS, -1, true, callConfig);
}
}
// pre-call trace
int traceBoolIndex = -1;
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
// load and store trace enabled flag
if (specificArity) {
switch (scope.getRequiredArgs()) {
case -1:
traceBoolIndex = ARGS_INDEX + 1/*args*/ + 1/*block*/ + 2;
break;
case 0:
traceBoolIndex = ARGS_INDEX + 1/*block*/ + 2;
break;
default:
traceBoolIndex = ARGS_INDEX + scope.getRequiredArgs() + 1/*block*/ + 2;
}
} else {
traceBoolIndex = ARGS_INDEX + 1/*block*/ + 2;
}
mv.aload(1);
mv.invokevirtual(p(ThreadContext.class), "getRuntime", sig(Ruby.class));
mv.invokevirtual(p(Ruby.class), "hasEventHooks", sig(boolean.class));
mv.istore(traceBoolIndex);
// tracing pre
invokeTraceCompiledPre(mv, COMPILED_SUPER_CLASS, traceBoolIndex, filename, line);
}
Label tryBegin = new Label();
Label tryEnd = new Label();
Label doFinally = new Label();
Label doReturnFinally = new Label();
Label doRedoFinally = new Label();
Label catchReturnJump = new Label();
Label catchRedoJump = new Label();
boolean heapScoped = callConfig.scoping() != Scoping.None;
boolean framed = callConfig.framing() != Framing.None;
if (framed || heapScoped) mv.trycatch(tryBegin, tryEnd, catchReturnJump, p(JumpException.ReturnJump.class));
if (framed) mv.trycatch(tryBegin, tryEnd, catchRedoJump, p(JumpException.RedoJump.class));
if (framed || heapScoped) mv.trycatch(tryBegin, tryEnd, doFinally, null);
if (framed || heapScoped) mv.trycatch(catchReturnJump, doReturnFinally, doFinally, null);
if (framed) mv.trycatch(catchRedoJump, doRedoFinally, doFinally, null);
if (framed || heapScoped) mv.label(tryBegin);
// main body
{
mv.aload(0);
// FIXME we want to eliminate these type casts when possible
mv.getfield(invokerPath, "$scriptObject", ci(Object.class));
mv.checkcast(className);
mv.aloadMany(THREADCONTEXT_INDEX, RECEIVER_INDEX);
if (specificArity) {
for (int i = 0; i < scope.getRequiredArgs(); i++) {
mv.aload(ARGS_INDEX + i);
}
mv.aload(ARGS_INDEX + scope.getRequiredArgs());
mv.invokestatic(className, method, StandardASMCompiler.getStaticMethodSignature(className, scope.getRequiredArgs()));
} else {
mv.aloadMany(ARGS_INDEX, BLOCK_INDEX);
mv.invokestatic(className, method, StandardASMCompiler.getStaticMethodSignature(className, 4));
}
}
if (framed || heapScoped) {
mv.label(tryEnd);
}
// normal exit, perform finally and return
{
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
invokeTraceCompiledPost(mv, COMPILED_SUPER_CLASS, traceBoolIndex);
}
if (!callConfig.isNoop()) {
invokeCallConfigPost(mv, COMPILED_SUPER_CLASS, callConfig);
}
mv.visitInsn(ARETURN);
}
// return jump handling
if (framed || heapScoped) {
mv.label(catchReturnJump);
{
mv.aload(0);
mv.swap();
mv.aload(1);
mv.swap();
mv.iload(callNumberIndex);
mv.invokevirtual(COMPILED_SUPER_CLASS, "handleReturn", sig(IRubyObject.class, ThreadContext.class, JumpException.ReturnJump.class, int.class));
mv.label(doReturnFinally);
// finally
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
invokeTraceCompiledPost(mv, COMPILED_SUPER_CLASS, traceBoolIndex);
}
if (!callConfig.isNoop()) {
invokeCallConfigPost(mv, COMPILED_SUPER_CLASS, callConfig);
}
// return result if we're still good
mv.areturn();
}
}
if (framed) {
// redo jump handling
mv.label(catchRedoJump);
{
// clear the redo
mv.pop();
// get runtime, create jump error, and throw it
mv.aload(1);
mv.invokevirtual(p(ThreadContext.class), "getRuntime", sig(Ruby.class));
mv.invokevirtual(p(Ruby.class), "newRedoLocalJumpError", sig(RaiseException.class));
mv.label(doRedoFinally);
// finally
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
invokeTraceCompiledPost(mv, COMPILED_SUPER_CLASS, traceBoolIndex);
}
if (!callConfig.isNoop()) {
invokeCallConfigPost(mv, COMPILED_SUPER_CLASS, callConfig);
}
// throw redo error if we're still good
mv.athrow();
}
}
// finally handling for abnormal exit
if (framed || heapScoped) {
mv.label(doFinally);
//call post method stuff (exception raised)
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
invokeTraceCompiledPost(mv, COMPILED_SUPER_CLASS, traceBoolIndex);
}
if (!callConfig.isNoop()) {
invokeCallConfigPost(mv, COMPILED_SUPER_CLASS, callConfig);
}
// rethrow exception
mv.athrow(); // rethrow it
}
mv.end();
return endCallOffline(cw);
}
private static class DescriptorInfo {
private int min;
private int max;
private boolean frame;
private boolean scope;
private boolean backtrace;
private boolean rest;
private boolean block;
public DescriptorInfo(List descs) {
min = Integer.MAX_VALUE;
max = 0;
frame = false;
scope = false;
backtrace = false;
rest = false;
block = false;
boolean first = true;
boolean lastBlock = false;
for (JavaMethodDescriptor desc: descs) {
// make sure we don't have some methods with blocks and others without
// the handle generation logic can't handle such cases yet
if (first) {
first = false;
} else {
if (lastBlock != desc.hasBlock) {
throw new RuntimeException("Mismatched block parameters for method " + desc.declaringClassName + "." + desc.name);
}
}
lastBlock = desc.hasBlock;
int specificArity = -1;
if (desc.hasVarArgs) {
if (desc.optional == 0 && !desc.rest && desc.required == 0) {
throw new RuntimeException("IRubyObject[] args but neither of optional or rest specified for method " + desc.declaringClassName + "." + desc.name);
}
rest = true;
if (descs.size() == 1) {
min = -1;
}
} else {
if (desc.optional == 0 && !desc.rest) {
if (desc.required == 0) {
// No required specified, check actual number of required args
if (desc.actualRequired <= 3) {
// actual required is less than 3, so we use specific arity
specificArity = desc.actualRequired;
} else {
// actual required is greater than 3, raise error (we don't support actual required > 3)
throw new RuntimeException("Invalid specific-arity number of arguments (" + desc.actualRequired + ") on method " + desc.declaringClassName + "." + desc.name);
}
} else if (desc.required >= 0 && desc.required <= 3) {
if (desc.actualRequired != desc.required) {
throw new RuntimeException("Specified required args does not match actual on method " + desc.declaringClassName + "." + desc.name);
}
specificArity = desc.required;
}
}
if (specificArity < min) {
min = specificArity;
}
if (specificArity > max) {
max = specificArity;
}
}
frame |= desc.anno.frame();
scope |= desc.anno.scope();
backtrace |= desc.anno.backtrace();
block |= desc.hasBlock;
}
}
public boolean isBacktrace() {
return backtrace;
}
public boolean isFrame() {
return frame;
}
public int getMax() {
return max;
}
public int getMin() {
return min;
}
public boolean isScope() {
return scope;
}
public boolean isRest() {
return rest;
}
public boolean isBlock() {
return block;
}
}
/**
* Use code generation to provide a method handle based on an annotated Java
* method.
*
* @see org.jruby.internal.runtime.methods.MethodFactory#getAnnotatedMethod
*/
public DynamicMethod getAnnotatedMethod(RubyModule implementationClass, List descs) {
JavaMethodDescriptor desc1 = descs.get(0);
String javaMethodName = desc1.name;
if (DEBUG) out.println("Binding multiple: " + desc1.declaringClassName + "." + javaMethodName);
synchronized (syncObject) {
try {
Class c = getAnnotatedMethodClass(descs);
DescriptorInfo info = new DescriptorInfo(descs);
if (DEBUG) out.println(" min: " + info.getMin() + ", max: " + info.getMax());
JavaMethod ic = (JavaMethod)c.getConstructor(new Class[]{RubyModule.class, Visibility.class}).newInstance(new Object[]{implementationClass, desc1.anno.visibility()});
TypePopulator.populateMethod(
ic,
Arity.optional().getValue(),
javaMethodName,
desc1.isStatic,
CallConfiguration.getCallConfig(info.isFrame(), info.isScope(), info.isBacktrace()),
desc1.anno.notImplemented());
return ic;
} catch(Exception e) {
e.printStackTrace();
throw implementationClass.getRuntime().newLoadError(e.getMessage());
}
}
}
/**
* Use code generation to provide a method handle based on an annotated Java
* method. Return the resulting generated or loaded class.
*
* @see org.jruby.internal.runtime.methods.MethodFactory#getAnnotatedMethod
*/
public Class getAnnotatedMethodClass(List descs) throws Exception {
JavaMethodDescriptor desc1 = descs.get(0);
if (!Modifier.isPublic(desc1.getDeclaringClass().getModifiers())) {
System.err.println("warning: binding non-public class" + desc1.declaringClassName + "; reflected handles won't work");
}
String javaMethodName = desc1.name;
if (DEBUG) {
if (descs.size() > 1) {
out.println("Binding multiple: " + desc1.declaringClassName + "." + javaMethodName);
} else {
out.println("Binding single: " + desc1.declaringClassName + "." + javaMethodName);
}
}
String generatedClassName = CodegenUtils.getAnnotatedBindingClassName(javaMethodName, desc1.declaringClassName, desc1.isStatic, desc1.actualRequired, desc1.optional, descs.size() > 1, desc1.anno.frame());
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
// in debug mode we append _DBG to class name to force it to regenerate (or use pre-generated debug version)
generatedClassName += "_DBG";
}
String generatedClassPath = generatedClassName.replace('.', '/');
synchronized (syncObject) {
Class c = tryClass(generatedClassName, desc1.getDeclaringClass());
DescriptorInfo info = new DescriptorInfo(descs);
if (DEBUG) out.println(" min: " + info.getMin() + ", max: " + info.getMax() + ", hasBlock: " + info.isBlock() + ", rest: " + info.isRest());
if (c == null) {
Class superClass = null;
if (info.getMin() == -1) {
// normal all-rest method
if (info.isBlock()) {
superClass = JavaMethod.JavaMethodNBlock.class;
} else {
superClass = JavaMethod.JavaMethodN.class;
}
} else {
if (info.isRest()) {
if (info.isBlock()) {
superClass = JavaMethod.BLOCK_REST_METHODS[info.getMin()][info.getMax()];
} else {
superClass = JavaMethod.REST_METHODS[info.getMin()][info.getMax()];
}
} else {
if (info.isBlock()) {
superClass = JavaMethod.BLOCK_METHODS[info.getMin()][info.getMax()];
} else {
superClass = JavaMethod.METHODS[info.getMin()][info.getMax()];
}
}
}
if (superClass == null) throw new RuntimeException("invalid multi combination");
String superClassString = p(superClass);
int dotIndex = desc1.declaringClassName.lastIndexOf('.');
ClassWriter cw = createJavaMethodCtor(generatedClassPath, desc1.declaringClassName.substring(dotIndex + 1) + "$" + desc1.name, superClassString);
addAnnotatedMethodInvoker(cw, "call", superClassString, descs);
c = endClass(cw, generatedClassName);
}
return c;
}
}
/**
* Use code generation to provide a method handle based on an annotated Java
* method.
*
* @see org.jruby.internal.runtime.methods.MethodFactory#getAnnotatedMethod
*/
public DynamicMethod getAnnotatedMethod(RubyModule implementationClass, JavaMethodDescriptor desc) {
String javaMethodName = desc.name;
synchronized (syncObject) {
try {
Class c = getAnnotatedMethodClass(Arrays.asList(desc));
JavaMethod ic = (JavaMethod)c.getConstructor(new Class[]{RubyModule.class, Visibility.class}).newInstance(new Object[]{implementationClass, desc.anno.visibility()});
TypePopulator.populateMethod(
ic,
Arity.fromAnnotation(desc.anno, desc.actualRequired).getValue(),
javaMethodName,
desc.isStatic,
CallConfiguration.getCallConfigByAnno(desc.anno),
desc.anno.notImplemented());
return ic;
} catch(Exception e) {
e.printStackTrace();
throw implementationClass.getRuntime().newLoadError(e.getMessage());
}
}
}
private static String getBlockCallbackName(String typePathString, String method) {
return (typePathString + "$" + method).replaceAll("/", "\\$");
}
public CompiledBlockCallback getBlockCallback(String method, String file, int line, Object scriptObject) {
Class typeClass = scriptObject.getClass();
String typePathString = p(typeClass);
String mname = getBlockCallbackName(typePathString, method);
try {
Class c = tryBlockCallbackClass(mname);
if (c == null) {
synchronized (syncObject) {
c = tryBlockCallbackClass(mname);
if (c == null) {
if (RubyInstanceConfig.JIT_LOADING_DEBUG) {
System.err.println("no generated handle in classloader for: " + mname);
}
byte[] bytes = getBlockCallbackOffline(method, file, line, typePathString);
c = endClassWithBytes(bytes, mname);
} else {
if (RubyInstanceConfig.JIT_LOADING_DEBUG) {
System.err.println("found generated handle in classloader for: " + mname);
}
}
}
}
CompiledBlockCallback ic = (CompiledBlockCallback) c.getConstructor(Object.class).newInstance(scriptObject);
return ic;
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage());
}
}
@Override
public byte[] getBlockCallbackOffline(String method, String file, int line, String classname) {
String mname = getBlockCallbackName(classname, method);
ClassWriter cw = createBlockCtor(mname, classname);
SkinnyMethodAdapter mv = startBlockCall(cw);
mv.line(-1);
mv.aload(0);
mv.getfield(mname, "$scriptObject", "L" + classname + ";");
mv.aloadMany(1, 2, 3, 4);
mv.invokestatic(classname, method, sig(
IRubyObject.class, "L" + classname + ";", ThreadContext.class,
IRubyObject.class, IRubyObject.class, Block.class));
mv.areturn();
mv.end();
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "getFile", sig(String.class), null, null);
mv.start();
mv.ldc(file);
mv.areturn();
mv.end();
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "getLine", sig(int.class), null, null);
mv.start();
mv.ldc(line);
mv.ireturn();
mv.end();
return endCallOffline(cw);
}
public CompiledBlockCallback19 getBlockCallback19(String method, String file, int line, Object scriptObject) {
Class typeClass = scriptObject.getClass();
String typePathString = p(typeClass);
String mname = getBlockCallbackName(typePathString, method);
try {
Class c = tryBlockCallback19Class(mname);
if (c == null) {
synchronized (syncObject) {
c = tryBlockCallback19Class(mname);
if (c == null) {
if (RubyInstanceConfig.JIT_LOADING_DEBUG) {
System.err.println("no generated handle in classloader for: " + mname);
}
byte[] bytes = getBlockCallback19Offline(method, file, line, typePathString);
c = endClassWithBytes(bytes, mname);
} else {
if (RubyInstanceConfig.JIT_LOADING_DEBUG) {
System.err.println("found generated handle in classloader for: " + mname);
}
}
}
}
CompiledBlockCallback19 ic = (CompiledBlockCallback19) c.getConstructor(Object.class).newInstance(scriptObject);
return ic;
} catch (IllegalArgumentException e) {
throw e;
} catch (Exception e) {
throw new IllegalArgumentException(e.getMessage());
}
}
@Override
public byte[] getBlockCallback19Offline(String method, String file, int line, String classname) {
String mnamePath = getBlockCallbackName(classname, method);
ClassWriter cw = createBlockCtor19(mnamePath, classname);
SkinnyMethodAdapter mv = startBlockCall19(cw);
mv.line(-1);
mv.aload(0);
mv.getfield(mnamePath, "$scriptObject", "L" + classname + ";");
mv.aloadMany(1, 2, 3, 4);
mv.invokestatic(classname, method, sig(
IRubyObject.class, "L" + classname + ";", ThreadContext.class,
IRubyObject.class, IRubyObject[].class, Block.class));
mv.areturn();
mv.end();
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "getFile", sig(String.class), null, null);
mv.start();
mv.ldc(file);
mv.areturn();
mv.end();
mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "getLine", sig(int.class), null, null);
mv.start();
mv.ldc(line);
mv.ireturn();
mv.end();
return endCallOffline(cw);
}
private SkinnyMethodAdapter startBlockCall(ClassWriter cw) {
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_SYNTHETIC | ACC_FINAL, "call", BLOCK_CALL_SIG, null, null);
mv.visitCode();
Label line = new Label();
mv.visitLineNumber(0, line);
return mv;
}
private SkinnyMethodAdapter startBlockCall19(ClassWriter cw) {
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_SYNTHETIC | ACC_FINAL, "call", BLOCK_CALL_SIG19, null, null);
mv.visitCode();
Label line = new Label();
mv.visitLineNumber(0, line);
return mv;
}
private ClassWriter createBlockCtor(String namePath, String classname) {
String ciClassname = "L" + classname + ";";
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(RubyInstanceConfig.JAVA_VERSION, ACC_PUBLIC + ACC_SUPER, namePath, null, p(CompiledBlockCallback.class), null);
cw.visitSource(namePath, null);
cw.visitField(ACC_PRIVATE | ACC_FINAL, "$scriptObject", ciClassname, null, null);
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", sig(Void.TYPE, params(Object.class)), null, null);
mv.start();
mv.line(-1);
mv.aload(0);
mv.invokespecial(p(CompiledBlockCallback.class), "", sig(void.class));
mv.aloadMany(0, 1);
mv.checkcast(classname);
mv.putfield(namePath, "$scriptObject", ciClassname);
mv.voidreturn();
mv.end();
return cw;
}
private ClassWriter createBlockCtor19(String namePath, String classname) {
String ciClassname = "L" + classname + ";";
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(RubyInstanceConfig.JAVA_VERSION, ACC_PUBLIC + ACC_SUPER, namePath, null, p(Object.class), new String[] {p(CompiledBlockCallback19.class)});
cw.visitSource(namePath, null);
cw.visitField(ACC_PRIVATE | ACC_FINAL, "$scriptObject", ciClassname, null, null);
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", sig(Void.TYPE, params(Object.class)), null, null);
mv.start();
mv.line(-1);
mv.aload(0);
mv.invokespecial(p(Object.class), "", sig(void.class));
mv.aloadMany(0, 1);
mv.checkcast(classname);
mv.putfield(namePath, "$scriptObject", ciClassname);
mv.voidreturn();
mv.end();
return cw;
}
/**
* Use code generation to provide a method handle based on an annotated Java
* method.
*
* @see org.jruby.internal.runtime.methods.MethodFactory#getAnnotatedMethod
*/
public void prepareAnnotatedMethod(RubyModule implementationClass, JavaMethod javaMethod, JavaMethodDescriptor desc) {
String javaMethodName = desc.name;
javaMethod.setArity(Arity.fromAnnotation(desc.anno, desc.actualRequired));
javaMethod.setJavaName(javaMethodName);
javaMethod.setSingleton(desc.isStatic);
javaMethod.setCallConfig(CallConfiguration.getCallConfigByAnno(desc.anno));
}
/**
* Emit code to check the arity of a call to a Java-based method.
*
* @param jrubyMethod The annotation of the called method
* @param method The code generator for the handle being created
*/
private void checkArity(JRubyMethod jrubyMethod, SkinnyMethodAdapter method, int specificArity) {
Label arityError = new Label();
Label noArityError = new Label();
switch (specificArity) {
case 0:
case 1:
case 2:
case 3:
// for zero, one, two, three arities, JavaMethod.JavaMethod*.call(...IRubyObject[] args...) will check
return;
default:
boolean checkArity = false;
if (jrubyMethod.rest()) {
if (jrubyMethod.required() > 0) {
// just confirm minimum args provided
method.aload(ARGS_INDEX);
method.arraylength();
method.ldc(jrubyMethod.required());
method.if_icmplt(arityError);
checkArity = true;
}
} else if (jrubyMethod.optional() > 0) {
if (jrubyMethod.required() > 0) {
// confirm minimum args provided
method.aload(ARGS_INDEX);
method.arraylength();
method.ldc(jrubyMethod.required());
method.if_icmplt(arityError);
}
// confirm maximum not greater than optional
method.aload(ARGS_INDEX);
method.arraylength();
method.ldc(jrubyMethod.required() + jrubyMethod.optional());
method.if_icmpgt(arityError);
checkArity = true;
} else {
// just confirm args length == required
method.aload(ARGS_INDEX);
method.arraylength();
method.ldc(jrubyMethod.required());
method.if_icmpne(arityError);
checkArity = true;
}
if (checkArity) {
method.go_to(noArityError);
// Raise an error if arity does not match requirements
method.label(arityError);
method.aload(THREADCONTEXT_INDEX);
method.invokevirtual(p(ThreadContext.class), "getRuntime", sig(Ruby.class));
method.aload(ARGS_INDEX);
method.ldc(jrubyMethod.required());
method.ldc(jrubyMethod.required() + jrubyMethod.optional());
method.invokestatic(p(Arity.class), "checkArgumentCount", sig(int.class, Ruby.class, IRubyObject[].class, int.class, int.class));
method.pop();
method.label(noArityError);
}
}
}
private ClassWriter createCompiledCtor(String namePath, String shortPath, String sup) {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
cw.visit(RubyInstanceConfig.JAVA_VERSION, ACC_PUBLIC + ACC_SUPER, namePath, null, sup, null);
cw.visitSource(shortPath, null);
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", "()V", null, null);
mv.visitCode();
mv.line(-1);
mv.aload(0);
mv.visitMethodInsn(INVOKESPECIAL, sup, "", "()V");
mv.visitLineNumber(-1, new Label());
mv.voidreturn();
mv.end();
return cw;
}
private ClassWriter createJavaMethodCtor(String namePath, String shortPath, String sup) throws Exception {
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
String sourceFile = namePath.substring(namePath.lastIndexOf('/') + 1) + ".gen";
cw.visit(RubyInstanceConfig.JAVA_VERSION, ACC_PUBLIC + ACC_SUPER, namePath, null, sup, null);
cw.visitSource(sourceFile, null);
SkinnyMethodAdapter mv = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", JAVA_SUPER_SIG, null, null);
mv.start();
mv.line(-1);
mv.aloadMany(0, 1, 2);
mv.visitMethodInsn(INVOKESPECIAL, sup, "", JAVA_SUPER_SIG);
mv.visitLineNumber(-1, new Label());
mv.voidreturn();
mv.end();
return cw;
}
private void invokeCallConfigPre(SkinnyMethodAdapter mv, String superClass, int specificArity, boolean block, CallConfiguration callConfig) {
// invoke pre method stuff
if (callConfig.isNoop()) return;
prepareForPre(mv, specificArity, block, callConfig);
mv.invokevirtual(superClass, getPreMethod(callConfig), getPreSignature(callConfig));
}
private void invokeCallConfigPost(SkinnyMethodAdapter mv, String superClass, CallConfiguration callConfig) {
if (callConfig.isNoop()) return;
mv.aload(1);
mv.invokestatic(superClass, getPostMethod(callConfig), sig(void.class, params(ThreadContext.class)));
}
private void prepareForPre(SkinnyMethodAdapter mv, int specificArity, boolean block, CallConfiguration callConfig) {
if (callConfig.isNoop()) return;
mv.aloadMany(0, THREADCONTEXT_INDEX);
switch (callConfig.framing()) {
case Full:
mv.aloadMany(RECEIVER_INDEX, NAME_INDEX); // self, name
loadBlockForPre(mv, specificArity, block);
break;
case Backtrace:
mv.aload(NAME_INDEX); // name
break;
case None:
break;
default: throw new RuntimeException("Unknown call configuration");
}
}
private String getPreMethod(CallConfiguration callConfig) {
switch (callConfig) {
case FrameFullScopeFull: return "preFrameAndScope";
case FrameFullScopeDummy: return "preFrameAndDummyScope";
case FrameFullScopeNone: return "preFrameOnly";
case FrameBacktraceScopeFull: return "preBacktraceAndScope";
case FrameBacktraceScopeDummy: return "preBacktraceDummyScope";
case FrameBacktraceScopeNone: return "preBacktraceOnly";
case FrameNoneScopeFull: return "preScopeOnly";
case FrameNoneScopeDummy: return "preNoFrameDummyScope";
case FrameNoneScopeNone: return "preNoop";
default: throw new RuntimeException("Unknown call configuration");
}
}
private String getPreSignature(CallConfiguration callConfig) {
switch (callConfig) {
case FrameFullScopeFull: return sig(void.class, params(ThreadContext.class, IRubyObject.class, String.class, Block.class));
case FrameFullScopeDummy: return sig(void.class, params(ThreadContext.class, IRubyObject.class, String.class, Block.class));
case FrameFullScopeNone: return sig(void.class, params(ThreadContext.class, IRubyObject.class, String.class, Block.class));
case FrameBacktraceScopeFull: return sig(void.class, params(ThreadContext.class, String.class));
case FrameBacktraceScopeDummy: return sig(void.class, params(ThreadContext.class, String.class));
case FrameBacktraceScopeNone: return sig(void.class, params(ThreadContext.class, String.class));
case FrameNoneScopeFull: return sig(void.class, params(ThreadContext.class));
case FrameNoneScopeDummy: return sig(void.class, params(ThreadContext.class));
case FrameNoneScopeNone: return sig(void.class);
default: throw new RuntimeException("Unknown call configuration");
}
}
public static String getPostMethod(CallConfiguration callConfig) {
switch (callConfig) {
case FrameFullScopeFull: return "postFrameAndScope";
case FrameFullScopeDummy: return "postFrameAndScope";
case FrameFullScopeNone: return "postFrameOnly";
case FrameBacktraceScopeFull: return "postBacktraceAndScope";
case FrameBacktraceScopeDummy: return "postBacktraceDummyScope";
case FrameBacktraceScopeNone: return "postBacktraceOnly";
case FrameNoneScopeFull: return "postScopeOnly";
case FrameNoneScopeDummy: return "postNoFrameDummyScope";
case FrameNoneScopeNone: return "postNoop";
default: throw new RuntimeException("Unknown call configuration");
}
}
private void loadArguments(SkinnyMethodAdapter mv, JavaMethodDescriptor desc, int specificArity) {
switch (specificArity) {
default:
case -1:
mv.aload(ARGS_INDEX);
break;
case 0:
// no args
break;
case 1:
loadArgumentWithCast(mv, 1, desc.argumentTypes[0]);
break;
case 2:
loadArgumentWithCast(mv, 1, desc.argumentTypes[0]);
loadArgumentWithCast(mv, 2, desc.argumentTypes[1]);
break;
case 3:
loadArgumentWithCast(mv, 1, desc.argumentTypes[0]);
loadArgumentWithCast(mv, 2, desc.argumentTypes[1]);
loadArgumentWithCast(mv, 3, desc.argumentTypes[2]);
break;
}
}
private void loadArgumentWithCast(SkinnyMethodAdapter mv, int argNumber, Class coerceType) {
mv.aload(ARGS_INDEX + (argNumber - 1));
if (coerceType != IRubyObject.class && coerceType != IRubyObject[].class) {
if (coerceType == RubyString.class) {
mv.invokeinterface(p(IRubyObject.class), "convertToString", sig(RubyString.class));
} else {
throw new RuntimeException("Unknown coercion target: " + coerceType);
}
}
}
/** load block argument for pre() call. Since we have fixed-arity call
* paths we need calculate where the last var holding the block is.
*
* is we don't have a block we setup NULL_BLOCK as part of our null pattern
* strategy (we do not allow null in any field which accepts block).
*/
private void loadBlockForPre(SkinnyMethodAdapter mv, int specificArity, boolean getsBlock) {
if (!getsBlock) { // No block so load null block instance
mv.getstatic(p(Block.class), "NULL_BLOCK", ci(Block.class));
return;
}
loadBlock(mv, specificArity, getsBlock);
}
/** load the block argument from the correct position. Since we have fixed-
* arity call paths we need to calculate where the last var holding the
* block is.
*
* If we don't have a block then this does nothing.
*/
private void loadBlock(SkinnyMethodAdapter mv, int specificArity, boolean getsBlock) {
if (!getsBlock) return; // No block so nothing more to do
switch (specificArity) { // load block since it accepts a block
case 0: case 1: case 2: case 3: // Fixed arities signatures
mv.aload(BLOCK_INDEX - 1 + specificArity);
break;
default: case -1:
mv.aload(BLOCK_INDEX); // Generic arity signature
break;
}
}
private void loadReceiver(String typePath, JavaMethodDescriptor desc, SkinnyMethodAdapter mv) {
// load target for invocations
if (Modifier.isStatic(desc.modifiers)) {
if (desc.hasContext) {
mv.aload(THREADCONTEXT_INDEX);
}
// load self object as IRubyObject, for recv param
mv.aload(RECEIVER_INDEX);
} else {
// load receiver as original type for virtual invocation
mv.aload(RECEIVER_INDEX);
mv.checkcast(typePath);
if (desc.hasContext) {
mv.aload(THREADCONTEXT_INDEX);
}
}
}
private Class tryClass(String name, Class targetClass) {
Class c = null;
try {
if (classLoader == null) {
c = Class.forName(name, true, classLoader);
} else {
c = classLoader.loadClass(name);
}
} catch(Exception e) {
seenUndefinedClasses = true;
return null;
}
try {
// For JRUBY-5038, try getting call method to ensure classloaders are lining up right
// If this fails, it usually means we loaded an invoker from a higher-up classloader
c.getMethod("call", new Class[]{ThreadContext.class, IRubyObject.class, RubyModule.class,
String.class, IRubyObject[].class, Block.class});
if (c != null && seenUndefinedClasses && !haveWarnedUser) {
haveWarnedUser = true;
System.err.println("WARNING: while creating new bindings for " + targetClass + ",\n" +
"found an existing binding; you may want to run a clean build.");
}
return c;
} catch(Exception e) {
e.printStackTrace();
seenUndefinedClasses = true;
return null;
}
}
private Class tryBlockCallbackClass(String name) {
try {
Class c = classLoader.loadClass(name);
// For JRUBY-5038, try getting a method to ensure classloaders are lining up right
// If this fails, it usually means we loaded an invoker from a higher-up classloader
c.getMethod("call", ThreadContext.class, IRubyObject.class, IRubyObject.class, Block.class);
return c;
} catch (Exception e) {
return null;
}
}
private Class tryBlockCallback19Class(String name) {
try {
Class c = classLoader.loadClass(name);
// For JRUBY-5038, try getting a method to ensure classloaders are lining up right
// If this fails, it usually means we loaded an invoker from a higher-up classloader
c.getMethod("call", ThreadContext.class, IRubyObject.class, IRubyObject[].class, Block.class);
return c;
} catch (Exception e) {
return null;
}
}
protected Class endCall(ClassWriter cw, String name) {
return endClass(cw, name);
}
protected Class endCallWithBytes(byte[] classBytes, String name) {
return endClassWithBytes(classBytes, name);
}
protected byte[] endCallOffline(ClassWriter cw) {
return endClassOffline(cw);
}
protected Class endClass(ClassWriter cw, String name) {
cw.visitEnd();
byte[] code = cw.toByteArray();
if (DEBUG) CheckClassAdapter.verify(new ClassReader(code), false, new PrintWriter(System.err));
return classLoader.defineClass(name, code);
}
protected Class endClassWithBytes(byte[] code, String name) {
return classLoader.defineClass(name, code);
}
protected byte[] endClassOffline(ClassWriter cw) {
cw.visitEnd();
byte[] code = cw.toByteArray();
if (DEBUG) CheckClassAdapter.verify(new ClassReader(code), false, new PrintWriter(System.err));
return code;
}
private SkinnyMethodAdapter beginMethod(ClassWriter cw, String methodName, int specificArity, boolean block) {
switch (specificArity) {
default:
case -1:
if (block) {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_BLOCK, null, null);
} else {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG, null, null);
}
case 0:
if (block) {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_ZERO_BLOCK, null, null);
} else {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_ZERO, null, null);
}
case 1:
if (block) {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_ONE_BLOCK, null, null);
} else {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_ONE, null, null);
}
case 2:
if (block) {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_TWO_BLOCK, null, null);
} else {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_TWO, null, null);
}
case 3:
if (block) {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_THREE_BLOCK, null, null);
} else {
return new SkinnyMethodAdapter(cw, ACC_PUBLIC, methodName, COMPILED_CALL_SIG_THREE, null, null);
}
}
}
private void addAnnotatedMethodInvoker(ClassWriter cw, String callName, String superClass, List descs) {
for (JavaMethodDescriptor desc: descs) {
int specificArity = -1;
if (desc.optional == 0 && !desc.rest) {
if (desc.required == 0) {
if (desc.actualRequired <= 3) {
specificArity = desc.actualRequired;
} else {
specificArity = -1;
}
} else if (desc.required >= 0 && desc.required <= 3) {
specificArity = desc.required;
}
}
boolean hasBlock = desc.hasBlock;
SkinnyMethodAdapter mv = null;
mv = beginMethod(cw, callName, specificArity, hasBlock);
mv.visitCode();
mv.line(-1);
createAnnotatedMethodInvocation(desc, mv, superClass, specificArity, hasBlock);
mv.end();
}
}
private void createAnnotatedMethodInvocation(JavaMethodDescriptor desc, SkinnyMethodAdapter method, String superClass, int specificArity, boolean block) {
String typePath = desc.declaringClassPath;
String javaMethodName = desc.name;
checkArity(desc.anno, method, specificArity);
CallConfiguration callConfig = CallConfiguration.getCallConfigByAnno(desc.anno);
if (!callConfig.isNoop()) {
invokeCallConfigPre(method, superClass, specificArity, block, callConfig);
}
int traceBoolIndex = -1;
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
// load and store trace enabled flag
switch (specificArity) {
case -1:
traceBoolIndex = ARGS_INDEX + (block ? 1 : 0) + 1;
break;
case 0:
traceBoolIndex = ARGS_INDEX + (block ? 1 : 0);
break;
default:
traceBoolIndex = ARGS_INDEX + specificArity + (block ? 1 : 0) + 1;
}
method.aload(1);
method.invokevirtual(p(ThreadContext.class), "getRuntime", sig(Ruby.class));
method.invokevirtual(p(Ruby.class), "hasEventHooks", sig(boolean.class));
method.istore(traceBoolIndex);
// call trace
invokeCCallTrace(method, traceBoolIndex);
}
Label tryBegin = new Label();
Label tryEnd = new Label();
Label doFinally = new Label();
if (!callConfig.isNoop()) {
method.trycatch(tryBegin, tryEnd, doFinally, null);
}
method.label(tryBegin);
{
loadReceiver(typePath, desc, method);
loadArguments(method, desc, specificArity);
loadBlock(method, specificArity, block);
if (Modifier.isStatic(desc.modifiers)) {
// static invocation
method.invokestatic(typePath, javaMethodName, desc.signature);
} else {
// virtual invocation
method.invokevirtual(typePath, javaMethodName, desc.signature);
}
if (desc.getReturnClass() == void.class) {
// void return type, so we need to load a nil for returning below
method.aload(THREADCONTEXT_INDEX);
method.invokevirtual(p(ThreadContext.class), "getRuntime", sig(Ruby.class));
method.invokevirtual(p(Ruby.class), "getNil", sig(IRubyObject.class));
}
}
method.label(tryEnd);
// normal finally and exit
{
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
invokeCReturnTrace(method, traceBoolIndex);
}
if (!callConfig.isNoop()) {
invokeCallConfigPost(method, superClass, callConfig);
}
// return
method.visitInsn(ARETURN);
}
// these are only needed if we have a non-noop call config
if (!callConfig.isNoop()) {
// finally handling for abnormal exit
{
method.label(doFinally);
if (RubyInstanceConfig.FULL_TRACE_ENABLED) {
invokeCReturnTrace(method, traceBoolIndex);
}
//call post method stuff (exception raised)
if (!callConfig.isNoop()) {
invokeCallConfigPost(method, superClass, callConfig);
}
// rethrow exception
method.athrow(); // rethrow it
}
}
}
private void invokeCCallTrace(SkinnyMethodAdapter method, int traceBoolIndex) {
method.aloadMany(0, 1); // method, threadContext
method.iload(traceBoolIndex); // traceEnable
method.aload(4); // invokedName
method.invokevirtual(p(JavaMethod.class), "callTrace", sig(void.class, ThreadContext.class, boolean.class, String.class));
}
private void invokeCReturnTrace(SkinnyMethodAdapter method, int traceBoolIndex) {
method.aloadMany(0, 1); // method, threadContext
method.iload(traceBoolIndex); // traceEnable
method.aload(4); // invokedName
method.invokevirtual(p(JavaMethod.class), "returnTrace", sig(void.class, ThreadContext.class, boolean.class, String.class));
}
private void invokeTraceCompiledPre(SkinnyMethodAdapter mv, String superClass, int traceBoolIndex, String filename, int line) {
mv.aloadMany(0, 1); // method, threadContext
mv.iload(traceBoolIndex); // traceEnable
mv.aload(4); // invokedName
mv.ldc(filename);
mv.ldc(line);
mv.invokevirtual(superClass, "callTraceCompiled", sig(void.class, ThreadContext.class, boolean.class, String.class, String.class, int.class));
}
private void invokeTraceCompiledPost(SkinnyMethodAdapter mv, String superClass, int traceBoolIndex) {
mv.aloadMany(0, 1); // method, threadContext
mv.iload(traceBoolIndex); // traceEnable
mv.aload(4); // invokedName
mv.invokevirtual(superClass, "returnTraceCompiled", sig(void.class, ThreadContext.class, boolean.class, String.class));
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy