Skip to content

Commit

Permalink
Separate base class reification from method reification.
Browse files Browse the repository at this point in the history
The goal here is to start splitting off the method reification,
which many (most) people don't need, so we can still get the
benefit of generating a real class for every custom Ruby class
without the hassle of binding a bunch of methods.

See #4643.
headius committed Jun 6, 2017
1 parent 6e79faf commit 24c5208
Showing 1 changed file with 151 additions and 108 deletions.
259 changes: 151 additions & 108 deletions core/src/main/java/org/jruby/RubyClass.java
Original file line number Diff line number Diff line change
@@ -1420,7 +1420,7 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) {
Class reifiedParent = RubyObject.class;
if (superClass.reifiedClass != null) reifiedParent = superClass.reifiedClass;

final byte[] classBytes = new Reificator(reifiedParent).reify(javaName, javaPath);
final byte[] classBytes = new MethodReificator(reifiedParent, javaName, javaPath).reify();

final ClassDefiningClassLoader parentCL;
if (parentReified.getClassLoader() instanceof OneShotClassLoader) {
@@ -1468,28 +1468,28 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) {
}
}

private final class Reificator {
interface Reificator {
byte[] reify();
} // interface Reificator

private final Class reifiedParent;
private abstract class BaseReificator implements Reificator {

Reificator(Class<?> reifiedParent) {
protected final Class reifiedParent;
protected final String javaName;
protected final String javaPath;
protected final ClassWriter cw;

BaseReificator(Class<?> reifiedParent, String javaName, String javaPath) {
this.reifiedParent = reifiedParent;
}
this.javaName = javaName;
this.javaPath = javaPath;

byte[] reify(final String javaName, final String javaPath) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
cw.visit(RubyInstanceConfig.JAVA_VERSION, ACC_PUBLIC + ACC_SUPER, javaPath, null, p(reifiedParent), interfaces());
}

if (classAnnotations != null && !classAnnotations.isEmpty()) {
for (Map.Entry<Class,Map<String,Object>> entry : classAnnotations.entrySet()) {
Class annoType = entry.getKey();
Map<String,Object> fields = entry.getValue();

AnnotationVisitor av = cw.visitAnnotation(ci(annoType), true);
CodegenUtils.visitAnnotationFields(av, fields);
av.visitEnd();
}
}
@Override
public byte[] reify() {

// fields to hold Ruby and RubyClass references
cw.visitField(ACC_STATIC | ACC_PRIVATE, "ruby", ci(Ruby.class), null, null);
@@ -1523,7 +1523,65 @@ byte[] reify(final String javaName, final String javaPath) {
m.voidreturn();
m.end();

customReify();

cw.visitEnd();

return cw.toByteArray();
}

public abstract void customReify();

private String[] interfaces() {
final Class[] interfaces = Java.getInterfacesFromRubyClass(RubyClass.this);
final String[] interfaceNames = new String[interfaces.length + 1];
// mark this as a Reified class
interfaceNames[0] = p(Reified.class);
// add the other user-specified interfaces
for (int i = 0; i < interfaces.length; i++) {
interfaceNames[i + 1] = p(interfaces[i]);
}
return interfaceNames;
}
}

private class MethodReificator extends BaseReificator {

MethodReificator(Class<?> reifiedParent, String javaName, String javaPath) {
super(reifiedParent, javaName, javaPath);
}

@Override
public void customReify() {
addClassAnnotations();

// define fields
defineFields();

// gather a list of instance methods, so we don't accidentally make static ones that conflict
final Set<String> instanceMethods = new HashSet<String>(getMethods().size());

// define instance methods
defineInstanceMethods(instanceMethods);

// define class/static methods
defineClassMethods(instanceMethods);
}

private void addClassAnnotations() {
if (classAnnotations != null && !classAnnotations.isEmpty()) {
for (Map.Entry<Class,Map<String,Object>> entry : classAnnotations.entrySet()) {
Class annoType = entry.getKey();
Map<String,Object> fields = entry.getValue();

AnnotationVisitor av = cw.visitAnnotation(ci(annoType), true);
CodegenUtils.visitAnnotationFields(av, fields);
av.visitEnd();
}
}
}

private void defineFields() {
for (Map.Entry<String, Class> fieldSignature : getFieldSignatures().entrySet()) {
String fieldName = fieldSignature.getKey();
Class type = fieldSignature.getValue();
@@ -1540,11 +1598,83 @@ byte[] reify(final String javaName, final String javaPath) {
}
fieldVisitor.visitEnd();
}
}

// gather a list of instance methods, so we don't accidentally make static ones that conflict
final Set<String> instanceMethods = new HashSet<String>(getMethods().size());
private void defineClassMethods(Set<String> instanceMethods) {
SkinnyMethodAdapter m;
for (Map.Entry<String,DynamicMethod> methodEntry : getMetaClass().getMethods().entrySet()) {
String methodName = methodEntry.getKey();

// define instance methods
if (!JavaNameMangler.willMethodMangleOk(methodName)) continue;

String javaMethodName = JavaNameMangler.mangleMethodName(methodName);

Map<Class,Map<String,Object>> methodAnnos = getMetaClass().getMethodAnnotations().get(methodName);
List<Map<Class,Map<String,Object>>> parameterAnnos = getMetaClass().getParameterAnnotations().get(methodName);
Class[] methodSignature = getMetaClass().getMethodSignatures().get(methodName);

String signature;
if (methodSignature == null) {
final Arity arity = methodEntry.getValue().getArity();
// non-signature signature with just IRubyObject
switch (arity.getValue()) {
case 0:
signature = sig(IRubyObject.class);
if (instanceMethods.contains(javaMethodName + signature)) continue;
m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_STATIC, javaMethodName, signature, null, null);
generateMethodAnnotations(methodAnnos, m, parameterAnnos);

m.getstatic(javaPath, "rubyClass", ci(RubyClass.class));
//m.invokevirtual("org/jruby/RubyClass", "getMetaClass", sig(RubyClass.class) );
m.ldc(methodName);
m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class) );
break;
default:
signature = sig(IRubyObject.class, IRubyObject[].class);
if (instanceMethods.contains(javaMethodName + signature)) continue;
m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_VARARGS | ACC_STATIC, javaMethodName, signature, null, null);
generateMethodAnnotations(methodAnnos, m, parameterAnnos);

m.getstatic(javaPath, "rubyClass", ci(RubyClass.class));
m.ldc(methodName);
m.aload(0);
m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class) );
}
m.areturn();
}
else { // generate a real method signature for the method, with to/from coercions

// indices for temp values
Class[] params = new Class[methodSignature.length - 1];
System.arraycopy(methodSignature, 1, params, 0, params.length);
final int baseIndex = RealClassGenerator.calcBaseIndex(params, 0);
int rubyIndex = baseIndex;

signature = sig(methodSignature[0], params);
if (instanceMethods.contains(javaMethodName + signature)) continue;
m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_VARARGS | ACC_STATIC, javaMethodName, signature, null, null);
generateMethodAnnotations(methodAnnos, m, parameterAnnos);

m.getstatic(javaPath, "ruby", ci(Ruby.class));
m.astore(rubyIndex);

m.getstatic(javaPath, "rubyClass", ci(RubyClass.class));

m.ldc(methodName); // method name
RealClassGenerator.coerceArgumentsToRuby(m, params, rubyIndex);
m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class));

RealClassGenerator.coerceResultAndReturn(m, methodSignature[0]);
}

if (DEBUG_REIFY) LOG.debug("defining {}.{} as {}.{}", getName(), methodName, javaName, javaMethodName + signature);

m.end();
}
}

private void defineInstanceMethods(Set<String> instanceMethods) {
SkinnyMethodAdapter m;
for (Map.Entry<String,DynamicMethod> methodEntry : getMethods().entrySet()) {
final String methodName = methodEntry.getKey();

@@ -1653,96 +1783,9 @@ byte[] reify(final String javaName, final String javaPath) {

m.end();
}

// define class/static methods
for (Map.Entry<String,DynamicMethod> methodEntry : getMetaClass().getMethods().entrySet()) {
String methodName = methodEntry.getKey();

if (!JavaNameMangler.willMethodMangleOk(methodName)) continue;

String javaMethodName = JavaNameMangler.mangleMethodName(methodName);

Map<Class,Map<String,Object>> methodAnnos = getMetaClass().getMethodAnnotations().get(methodName);
List<Map<Class,Map<String,Object>>> parameterAnnos = getMetaClass().getParameterAnnotations().get(methodName);
Class[] methodSignature = getMetaClass().getMethodSignatures().get(methodName);

String signature;
if (methodSignature == null) {
final Arity arity = methodEntry.getValue().getArity();
// non-signature signature with just IRubyObject
switch (arity.getValue()) {
case 0:
signature = sig(IRubyObject.class);
if (instanceMethods.contains(javaMethodName + signature)) continue;
m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_STATIC, javaMethodName, signature, null, null);
generateMethodAnnotations(methodAnnos, m, parameterAnnos);

m.getstatic(javaPath, "rubyClass", ci(RubyClass.class));
//m.invokevirtual("org/jruby/RubyClass", "getMetaClass", sig(RubyClass.class) );
m.ldc(methodName);
m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class) );
break;
default:
signature = sig(IRubyObject.class, IRubyObject[].class);
if (instanceMethods.contains(javaMethodName + signature)) continue;
m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_VARARGS | ACC_STATIC, javaMethodName, signature, null, null);
generateMethodAnnotations(methodAnnos, m, parameterAnnos);

m.getstatic(javaPath, "rubyClass", ci(RubyClass.class));
m.ldc(methodName);
m.aload(0);
m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class) );
}
m.areturn();
}
else { // generate a real method signature for the method, with to/from coercions

// indices for temp values
Class[] params = new Class[methodSignature.length - 1];
System.arraycopy(methodSignature, 1, params, 0, params.length);
final int baseIndex = RealClassGenerator.calcBaseIndex(params, 0);
int rubyIndex = baseIndex;

signature = sig(methodSignature[0], params);
if (instanceMethods.contains(javaMethodName + signature)) continue;
m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_VARARGS | ACC_STATIC, javaMethodName, signature, null, null);
generateMethodAnnotations(methodAnnos, m, parameterAnnos);

m.getstatic(javaPath, "ruby", ci(Ruby.class));
m.astore(rubyIndex);

m.getstatic(javaPath, "rubyClass", ci(RubyClass.class));

m.ldc(methodName); // method name
RealClassGenerator.coerceArgumentsToRuby(m, params, rubyIndex);
m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class));

RealClassGenerator.coerceResultAndReturn(m, methodSignature[0]);
}

if (DEBUG_REIFY) LOG.debug("defining {}.{} as {}.{}", getName(), methodName, javaName, javaMethodName + signature);

m.end();
}

cw.visitEnd();

return cw.toByteArray();
}

private String[] interfaces() {
final Class[] interfaces = Java.getInterfacesFromRubyClass(RubyClass.this);
final String[] interfaceNames = new String[interfaces.length + 1];
// mark this as a Reified class
interfaceNames[0] = p(Reified.class);
// add the other user-specified interfaces
for (int i = 0; i < interfaces.length; i++) {
interfaceNames[i + 1] = p(interfaces[i]);
}
return interfaceNames;
}

} // class Reificator
} // class MethodReificator

private boolean isVarArgsSignature(final String method, final Class[] methodSignature) {
// TODO we should simply detect "java.lang.Object m1(java.lang.Object... args)"

0 comments on commit 24c5208

Please sign in to comment.