Skip to content

Commit

Permalink
Showing 31 changed files with 458 additions and 1,495 deletions.
263 changes: 154 additions & 109 deletions core/src/main/java/org/jruby/RubyClass.java
Original file line number Diff line number Diff line change
@@ -1432,7 +1432,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) {
@@ -1480,28 +1480,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);
@@ -1535,7 +1535,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();
@@ -1552,12 +1610,86 @@ 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;

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

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

String javaMethodName = JavaNameMangler.mangleMethodName(id);

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

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(id);
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(id);
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(id); // 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(), id, javaName, javaMethodName + signature);

m.end();
}
}

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

if (!JavaNameMangler.willMethodMangleOk(id)) {
@@ -1665,96 +1797,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 id = methodEntry.getKey();

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

String javaMethodName = JavaNameMangler.mangleMethodName(id);

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

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(id);
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(id);
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(id); // 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(), id, 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)"
4 changes: 1 addition & 3 deletions core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
@@ -1872,7 +1872,7 @@ public RubyClass defineOrGetClassUnder(String name, RubyClass superClazz, Object

if (allocator == null) {
if (isReifiable(runtime, superClazz)) {
if (RubyInstanceConfig.REIFY_RUBY_CLASSES) {
if (Options.REIFY_CLASSES.load()) {
allocator = REIFYING_OBJECT_ALLOCATOR;
} else if (Options.REIFY_VARIABLES.load()) {
allocator = IVAR_INSPECTING_OBJECT_ALLOCATOR;
@@ -1898,8 +1898,6 @@ private boolean isReifiable(Ruby runtime, RubyClass superClass) {

if (superClass.getAllocator() == IVAR_INSPECTING_OBJECT_ALLOCATOR) return true;

if (FIELD_ALLOCATOR_SET.contains(superClass.getAllocator())) return true;

return false;
}

115 changes: 3 additions & 112 deletions core/src/main/java/org/jruby/RubyObject.java
Original file line number Diff line number Diff line change
@@ -63,6 +63,8 @@
import static org.jruby.runtime.invokedynamic.MethodNames.EQL;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_EQUAL;
import static org.jruby.runtime.invokedynamic.MethodNames.HASH;

import org.jruby.specialized.RubyObjectSpecializer;
import org.jruby.util.cli.Options;

/**
@@ -145,107 +147,6 @@ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
}
};

public static final ObjectAllocator OBJECT_VAR0_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar0(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR1_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar1(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR2_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar2(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR3_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar3(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR4_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar4(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR5_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar5(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR6_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar6(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR7_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar7(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR8_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar8(runtime, klass);
}
};

public static final ObjectAllocator OBJECT_VAR9_ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyObjectVar9(runtime, klass);
}
};

public static final ObjectAllocator[] FIELD_ALLOCATORS = {
OBJECT_ALLOCATOR,
OBJECT_VAR0_ALLOCATOR,
OBJECT_VAR1_ALLOCATOR,
OBJECT_VAR2_ALLOCATOR,
OBJECT_VAR3_ALLOCATOR,
OBJECT_VAR4_ALLOCATOR,
OBJECT_VAR5_ALLOCATOR,
OBJECT_VAR6_ALLOCATOR,
OBJECT_VAR7_ALLOCATOR,
OBJECT_VAR8_ALLOCATOR,
OBJECT_VAR9_ALLOCATOR
};

public static final Set<ObjectAllocator> FIELD_ALLOCATOR_SET =
Collections.unmodifiableSet(new HashSet<ObjectAllocator>(Arrays.asList(FIELD_ALLOCATORS)));

public static final Class[] FIELD_ALLOCATED_CLASSES = {
RubyObject.class,
RubyObjectVar0.class,
RubyObjectVar1.class,
RubyObjectVar2.class,
RubyObjectVar3.class,
RubyObjectVar4.class,
RubyObjectVar5.class,
RubyObjectVar6.class,
RubyObjectVar7.class,
RubyObjectVar8.class,
RubyObjectVar9.class,
};

/**
* Allocator that inspects all methods for instance variables and chooses
* a concrete class to construct based on that. This allows using
@@ -261,17 +162,7 @@ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
System.err.println(klass + ";" + foundVariables);
}

int count = 0;
for (String name : foundVariables) {
klass.getVariableTableManager().getVariableAccessorForVar(name, count);
count++;
if (count >= 10) break;
}

ObjectAllocator allocator = FIELD_ALLOCATORS[count];
Class reified = FIELD_ALLOCATED_CLASSES[count];
klass.setAllocator(allocator);
klass.setReifiedClass(reified);
ObjectAllocator allocator = RubyObjectSpecializer.specializeForVariables(klass, foundVariables);

// invalidate metaclass so new allocator is picked up for specialized .new
klass.getMetaClass().invalidateCacheDescendants();
70 changes: 0 additions & 70 deletions core/src/main/java/org/jruby/RubyObjectVar0.java

This file was deleted.

81 changes: 0 additions & 81 deletions core/src/main/java/org/jruby/RubyObjectVar1.java

This file was deleted.

93 changes: 0 additions & 93 deletions core/src/main/java/org/jruby/RubyObjectVar2.java

This file was deleted.

105 changes: 0 additions & 105 deletions core/src/main/java/org/jruby/RubyObjectVar3.java

This file was deleted.

117 changes: 0 additions & 117 deletions core/src/main/java/org/jruby/RubyObjectVar4.java

This file was deleted.

129 changes: 0 additions & 129 deletions core/src/main/java/org/jruby/RubyObjectVar5.java

This file was deleted.

141 changes: 0 additions & 141 deletions core/src/main/java/org/jruby/RubyObjectVar6.java

This file was deleted.

153 changes: 0 additions & 153 deletions core/src/main/java/org/jruby/RubyObjectVar7.java

This file was deleted.

165 changes: 0 additions & 165 deletions core/src/main/java/org/jruby/RubyObjectVar8.java

This file was deleted.

177 changes: 0 additions & 177 deletions core/src/main/java/org/jruby/RubyObjectVar9.java

This file was deleted.

4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ext/ffi/AutoPointer.java
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@
import java.util.concurrent.ConcurrentMap;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyInstanceConfig;
import org.jruby.RubyModule;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
@@ -20,6 +19,7 @@
import org.jruby.runtime.callsite.CachingCallSite;
import org.jruby.runtime.callsite.FunctionalCachingCallSite;
import org.jruby.util.PhantomReferenceReaper;
import org.jruby.util.cli.Options;

import static org.jruby.runtime.Visibility.*;

@@ -38,7 +38,7 @@ public class AutoPointer extends Pointer {
public static RubyClass createAutoPointerClass(Ruby runtime, RubyModule module) {
RubyClass autoptrClass = module.defineClassUnder(AUTOPTR_CLASS_NAME,
module.getClass("Pointer"),
RubyInstanceConfig.REIFY_RUBY_CLASSES ? new ReifyingAllocator(AutoPointer.class) : AutoPointerAllocator.INSTANCE);
Options.REIFY_FFI.load() ? new ReifyingAllocator(AutoPointer.class) : AutoPointerAllocator.INSTANCE);
autoptrClass.defineAnnotatedMethods(AutoPointer.class);
autoptrClass.defineAnnotatedConstants(AutoPointer.class);
autoptrClass.setReifiedClass(AutoPointer.class);
3 changes: 2 additions & 1 deletion core/src/main/java/org/jruby/ext/ffi/MemoryPointer.java
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.cli.Options;

import static org.jruby.runtime.Visibility.PRIVATE;

@@ -19,7 +20,7 @@ public class MemoryPointer extends Pointer {
public static RubyClass createMemoryPointerClass(Ruby runtime, RubyModule module) {
RubyClass memptrClass = module.defineClassUnder("MemoryPointer",
module.getClass("Pointer"),
RubyInstanceConfig.REIFY_RUBY_CLASSES ? new ReifyingAllocator(MemoryPointer.class) : MemoryPointerAllocator.INSTANCE);
Options.REIFY_FFI.load() ? new ReifyingAllocator(MemoryPointer.class) : MemoryPointerAllocator.INSTANCE);
memptrClass.defineAnnotatedMethods(MemoryPointer.class);
memptrClass.defineAnnotatedConstants(MemoryPointer.class);
memptrClass.setReifiedClass(MemoryPointer.class);
3 changes: 2 additions & 1 deletion core/src/main/java/org/jruby/ext/ffi/Pointer.java
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.runtime.*;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.cli.Options;

import static org.jruby.runtime.Visibility.*;

@@ -23,7 +24,7 @@ public class Pointer extends AbstractMemory {
public static RubyClass createPointerClass(Ruby runtime, RubyModule module) {
RubyClass pointerClass = module.defineClassUnder("Pointer",
module.getClass(AbstractMemory.ABSTRACT_MEMORY_RUBY_CLASS),
RubyInstanceConfig.REIFY_RUBY_CLASSES ? new ReifyingAllocator(Pointer.class) : PointerAllocator.INSTANCE);
Options.REIFY_FFI.load() ? new ReifyingAllocator(Pointer.class) : PointerAllocator.INSTANCE);

pointerClass.defineAnnotatedMethods(Pointer.class);
pointerClass.defineAnnotatedConstants(Pointer.class);
4 changes: 3 additions & 1 deletion core/src/main/java/org/jruby/ext/ffi/Struct.java
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.cli.Options;

import static org.jruby.runtime.Visibility.*;

@JRubyClass(name="FFI::Struct", parent="Object")
@@ -35,7 +37,7 @@ public final IRubyObject allocate(Ruby runtime, RubyClass klass) {
public static RubyClass createStructClass(Ruby runtime, RubyModule module) {

RubyClass structClass = runtime.defineClassUnder("Struct", runtime.getObject(),
RubyInstanceConfig.REIFY_RUBY_CLASSES ? new ReifyingAllocator(Struct.class): Allocator.INSTANCE, module);
Options.REIFY_FFI.load() ? new ReifyingAllocator(Struct.class): Allocator.INSTANCE, module);
structClass.defineAnnotatedMethods(Struct.class);
structClass.defineAnnotatedConstants(Struct.class);
structClass.setReifiedClass(Struct.class);
12 changes: 0 additions & 12 deletions core/src/main/java/org/jruby/ir/targets/Bootstrap.java
Original file line number Diff line number Diff line change
@@ -50,18 +50,6 @@ public class Bootstrap {
public final static String BOOTSTRAP_BARE_SIG = sig(CallSite.class, Lookup.class, String.class, MethodType.class);
public final static String BOOTSTRAP_LONG_STRING_INT_SIG = sig(CallSite.class, Lookup.class, String.class, MethodType.class, long.class, int.class, String.class, int.class);
public final static String BOOTSTRAP_DOUBLE_STRING_INT_SIG = sig(CallSite.class, Lookup.class, String.class, MethodType.class, double.class, int.class, String.class, int.class);
public static final Class[] REIFIED_OBJECT_CLASSES = {
RubyObjectVar0.class,
RubyObjectVar1.class,
RubyObjectVar2.class,
RubyObjectVar3.class,
RubyObjectVar4.class,
RubyObjectVar5.class,
RubyObjectVar6.class,
RubyObjectVar7.class,
RubyObjectVar8.class,
RubyObjectVar9.class,
};
private static final Logger LOG = LoggerFactory.getLogger(Bootstrap.class);
static final Lookup LOOKUP = MethodHandles.lookup();

Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar0;

/**
* A variable accessor that accesses a var0 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar0 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,11 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar0;
import org.jruby.RubyObjectVar1;

/**
* A variable accessor that accesses a var1 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar1 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar2;

/**
* A variable accessor that accesses a var2 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar2 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar3;

/**
* A variable accessor that accesses a var3 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar3 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar4;

/**
* A variable accessor that accesses a var4 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar4 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar5;

/**
* A variable accessor that accesses a var5 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar5 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar6;

/**
* A variable accessor that accesses a var6 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar6 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar7;

/**
* A variable accessor that accesses a var7 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar7 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar8;

/**
* A variable accessor that accesses a var8 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar8 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -30,10 +30,9 @@
import org.jruby.ReifiedRubyObject;
import org.jruby.RubyBasicObject;
import org.jruby.RubyClass;
import org.jruby.RubyObjectVar9;

/**
* A variable accessor that accesses a var9 field directly;
* A variable accessor that accesses a get/setVariable# directly;
*/
public class VariableAccessorVar9 extends FieldVariableAccessor {
/**
Original file line number Diff line number Diff line change
@@ -575,6 +575,7 @@ synchronized final VariableAccessor allocateVariableAccessorForVar(String name,

fieldVariables += 1;

// TODO: These should be generated so they are unique to each reified width
VariableAccessor newVariableAccessor;
switch (index) {
case 0:
@@ -608,7 +609,7 @@ synchronized final VariableAccessor allocateVariableAccessorForVar(String name,
newVariableAccessor = new VariableAccessorVar9(realClass, name, newIndex, id);
break;
default:
throw new RuntimeException("unsupported var index in " + realClass + ": " + index);
newVariableAccessor = new VariableAccessor(realClass, name, newIndex, id);
}

final String[] newVariableNames = new String[newIndex + 1];
277 changes: 277 additions & 0 deletions core/src/main/java/org/jruby/specialized/RubyObjectSpecializer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,277 @@
/*
***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse 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/epl-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.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/
package org.jruby.specialized;

import me.qmx.jitescript.CodeBlock;
import me.qmx.jitescript.JDKVersion;
import me.qmx.jitescript.JiteClass;
import org.jruby.ReifiedRubyObject;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyInstanceConfig;
import org.jruby.compiler.impl.SkinnyMethodAdapter;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ClassDefiningClassLoader;
import org.jruby.util.CodegenUtils;
import org.jruby.util.OneShotClassLoader;
import org.jruby.util.collections.NonBlockingHashMapLong;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.tree.LabelNode;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.Set;

import static org.jruby.util.CodegenUtils.ci;
import static org.jruby.util.CodegenUtils.p;
import static org.jruby.util.CodegenUtils.sig;
import static org.objectweb.asm.Opcodes.*;

/**
* A code generator for Ruby objects, to map known instance variables into fields.
*/
public class RubyObjectSpecializer {

private static ObjectAllocator getClassFromSize(int size) {
return specializedFactories.get(size);
}

private static final NonBlockingHashMapLong<ObjectAllocator> specializedFactories = new NonBlockingHashMapLong<>();

private static ClassDefiningClassLoader CDCL = new OneShotClassLoader(Ruby.getClassLoader());

public static ObjectAllocator specializeForVariables(RubyClass klass, Set<String> foundVariables) {
int size = foundVariables.size();
ObjectAllocator allocator = getClassFromSize(size);

if (allocator != null) return allocator;

final String clsPath = "org/jruby/gen/RubyObject" + size;
final String clsName = clsPath.replaceAll("/", ".");

// try to load the class, in case we have parallel generation happening
Class p;

try {
p = CDCL.loadClass(clsName);
} catch (ClassNotFoundException cnfe) {
// try again under lock
synchronized (CDCL) {
try {
p = CDCL.loadClass(clsName);
} catch (ClassNotFoundException cnfe2) {
// proceed to actually generate the class
p = generateInternal(klass, foundVariables, clsPath, clsName);
}
}
}

// acquire constructor handle and store it
try {
// should only be one, the allocator we want
Class allocatorCls = p.getDeclaredClasses()[0];
allocator = (ObjectAllocator) allocatorCls.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}

klass.setAllocator(allocator);
klass.setReifiedClass(p);

return allocator;
}


private static Class generateInternal(RubyClass klass, final Set<String> names, final String clsPath, final String clsName) {
// ensure only one thread will attempt to generate and define the new class
synchronized (CDCL) {
// set up table for all names and gather count
int i = 0;
for (String name : names) {
klass.getVariableTableManager().getVariableAccessorForVar(name, i);
i++;
}
final int count = i;

// create a new one
final String[] newFields = varList(count);

final String baseName = p(ReifiedRubyObject.class);
final String allocatorPath = clsPath + "Allocator";

final JiteClass jiteClass = new JiteClass(clsPath, baseName, new String[0]) {{
// parent class constructor
defineMethod("<init>", ACC_PUBLIC, sig(void.class, Ruby.class, RubyClass.class), new CodeBlock() {{
aload(0);
aload(1);
aload(2);
invokespecial(baseName, "<init>", sig(void.class, Ruby.class, RubyClass.class));
voidreturn();
}});

// required overrides
defineMethod("getVariable", ACC_PUBLIC, sig(Object.class, int.class), new CodeBlock() {{
LabelNode parentCall = new LabelNode(new Label());

line(0);

if (count > 0) genGetSwitch(clsPath, newFields, this, 1);

line(1);

aload(0);
iload(1);
invokespecial(p(ReifiedRubyObject.class), "getVariable", sig(Object.class, int.class));
areturn();
}});

defineMethod("setVariable", ACC_PUBLIC, sig(void.class, int.class, Object.class), new CodeBlock() {{
LabelNode parentCall = new LabelNode(new Label());

line(2);

if (count > 0) genPutSwitch(clsPath, newFields, this, 1);

line(3);

aload(0);
iload(1);
aload(2);
invokespecial(p(ReifiedRubyObject.class), "setVariable", sig(void.class, int.class, Object.class));
voidreturn();
}});

for (int i = 0; i < count; i++) {
final int offset = i;

defineMethod("getVariable" + offset, ACC_PUBLIC, sig(Object.class), new CodeBlock() {{
line(4);
aload(0);
getfield(clsPath, newFields[offset], ci(Object.class));
areturn();
}});
}

for (int i = 0; i < count; i++) {
final int offset = i;

defineMethod("setVariable" + offset, ACC_PUBLIC, sig(void.class, Object.class), new CodeBlock() {{
line(5);
aload(0);
aload(1);
putfield(clsPath, newFields[offset], ci(Object.class));
voidreturn();
}});
}

// fields
for (String prop : newFields) {
defineField(prop, ACC_PUBLIC, ci(Object.class), null);
}

// allocator class
addChildClass(new JiteClass(allocatorPath, p(Object.class), Helpers.arrayOf(p(ObjectAllocator.class))) {{
defineDefaultConstructor();

defineMethod("allocate", ACC_PUBLIC, sig(IRubyObject.class, Ruby.class, RubyClass.class), new CodeBlock() {{
newobj(clsPath);
dup();
aload(1);
aload(2);
invokespecial(clsPath, "<init>", sig(void.class, Ruby.class, RubyClass.class));
areturn();
}});
}});
}};

Class specializedClass = defineClass(jiteClass);
defineClass(jiteClass.getChildClasses().get(0));
return specializedClass;
}
}

private static String[] varList(int size) {
String[] vars = new String[size];

for (int i = 0; i < size; i++) {
vars[i] = "var" + i;
}

return vars;
}

private static void genGetSwitch(String clsPath, String[] newFields, CodeBlock block, int offsetVar) {
LabelNode defaultError = new LabelNode(new Label());
int size = newFields.length;
LabelNode[] cases = new LabelNode[size];
for (int i = 0; i < size; i++) {
cases[i] = new LabelNode(new Label());
}
block.iload(offsetVar);
block.tableswitch(0, size - 1, defaultError, cases);
for (int i = 0; i < size; i++) {
block.label(cases[i]);
block.aload(0);
block.getfield(clsPath, newFields[i], ci(Object.class));
block.areturn();
}
block.label(defaultError);
}

private static void genPutSwitch(String clsPath, String[] newFields, CodeBlock block, int offsetVar) {
LabelNode defaultError = new LabelNode(new Label());
int size = newFields.length;
LabelNode[] cases = new LabelNode[size];
for (int i = 0; i < size; i++) {
cases[i] = new LabelNode(new Label());
}
block.iload(offsetVar);
block.tableswitch(0, size - 1, defaultError, cases);
for (int i = 0; i < size; i++) {
block.label(cases[i]);
block.aload(0);
block.aload(2);
block.putfield(clsPath, newFields[i], ci(Object.class));
block.voidreturn();
}
block.label(defaultError);
}

private static Class defineClass(JiteClass jiteClass) {
return CDCL.defineClass(classNameFromJiteClass(jiteClass), jiteClass.toBytes(JDKVersion.V1_7));
}

private static String classNameFromJiteClass(JiteClass jiteClass) {
return jiteClass.getClassName().replaceAll("/", ".");
}
}
3 changes: 2 additions & 1 deletion core/src/main/java/org/jruby/util/cli/Options.java
Original file line number Diff line number Diff line change
@@ -154,6 +154,7 @@ public class Options {
public static final Option<Boolean> JUMP_BACKTRACE = bool(MISCELLANEOUS, "jump.backtrace", false, "Make non-local flow jumps generate backtraces.");
public static final Option<Boolean> PROCESS_NOUNWRAP = bool(MISCELLANEOUS, "process.noUnwrap", false, "Do not unwrap process streams (issue on some recent JVMs).");
public static final Option<Boolean> REIFY_CLASSES = bool(MISCELLANEOUS, "reify.classes", false, "Before instantiation, stand up a real Java class for every Ruby class.");
public static final Option<Boolean> REIFY_FFI = bool(MISCELLANEOUS, "reify.ffi", false, "Reify FFI memory structures.");
public static final Option<Boolean> REIFY_LOGERRORS = bool(MISCELLANEOUS, "reify.logErrors", false, "Log errors during reification (reify.classes=true).");
public static final Option<Boolean> REFLECTED_HANDLES = bool(MISCELLANEOUS, "reflected.handles", false, "Use reflection for binding methods, not generated bytecode.");
public static final Option<Boolean> BACKTRACE_COLOR = bool(MISCELLANEOUS, "backtrace.color", false, "Enable colorized backtraces.");
@@ -166,7 +167,7 @@ public class Options {
public static final Option<Boolean> NATIVE_EXEC = bool(MISCELLANEOUS, "native.exec", true, "Do a true process-obliterating native exec for Kernel#exec.");
public static final Option<Boolean> ENUMERATOR_LIGHTWEIGHT = bool(MISCELLANEOUS, "enumerator.lightweight", true, "Use lightweight Enumerator#next logic when possible.");
public static final Option<Boolean> CONSISTENT_HASHING = bool(MISCELLANEOUS, "consistent.hashing", false, "Generate consistent object hashes across JVMs");
public static final Option<Boolean> REIFY_VARIABLES = bool(MISCELLANEOUS, "reify.variables", true, "Attempt to expand instance vars into Java fields");
public static final Option<Boolean> REIFY_VARIABLES = bool(MISCELLANEOUS, "reify.variables", !REIFY_CLASSES.load(), "Attempt to expand instance vars into Java fields");
public static final Option<Boolean> FCNTL_LOCKING = bool(MISCELLANEOUS, "file.flock.fcntl", true, "Use fcntl rather than flock for File#flock");
public static final Option<Boolean> VOLATILE_VARIABLES = bool(MISCELLANEOUS, "volatile.variables", true, "Always ensure volatile semantics for instance variables.");
public static final Option<Boolean> RECORD_LEXICAL_HIERARCHY = bool(MISCELLANEOUS, "record.lexical.hierarchy", false, "Maintain children static scopes to support scope dumping.");

0 comments on commit 967d75e

Please sign in to comment.