Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generate all specialized width-specific RubyObject subclasses.
Browse files Browse the repository at this point in the history
This is a first step toward unifying all forms of reification.

In this commit, all the hand-implemented RubyObjectVar* and
ObjectAllocator* are instead generated as needed and then reused
statically. Since this will be a bounded number of classes (based
on the max size of inspected instance vars in user classes) it
should not use too much memory.

This now allows us to support reified instance variable fields up
to an arbitrary size.

Note that although the allocators are generated on a per-class
basis (necessary without using MethodHandle for allocation) the
variable accessors are still hand-written and only call the
appropriate getVariable# or setVariable# up to 9 as before. It is
an open TODO to generate these accessors on a per-class basis so
they inline all the way through. The alternative would be moving
toward more MethodHandle-based factories, but that limits
cross-JVM support and may result in startup impact.
headius committed Jun 7, 2017
1 parent 24c5208 commit 9e6554f
Showing 25 changed files with 292 additions and 1,369 deletions.
2 changes: 0 additions & 2 deletions core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
@@ -1700,8 +1700,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
@@ -62,6 +62,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;

/**
@@ -144,107 +146,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
@@ -260,17 +161,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();
69 changes: 0 additions & 69 deletions core/src/main/java/org/jruby/RubyObjectVar0.java

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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

This file was deleted.

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
@@ -29,10 +29,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
@@ -29,11 +29,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
@@ -29,10 +29,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
@@ -29,10 +29,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
@@ -29,10 +29,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
@@ -29,10 +29,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
@@ -29,10 +29,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
@@ -29,10 +29,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
@@ -29,10 +29,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
@@ -29,10 +29,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
@@ -574,6 +574,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:
@@ -607,7 +608,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/specialize/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("/", ".");
}
}

0 comments on commit 9e6554f

Please sign in to comment.