package org.jruby.specialized;
import me.qmx.jitescript.CodeBlock;
import me.qmx.jitescript.JDKVersion;
import me.qmx.jitescript.JiteClass;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyObject;
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.OneShotClassLoader;
import org.jruby.util.cli.Options;
import org.jruby.util.collections.NonBlockingHashMapLong;
import org.objectweb.asm.Label;
import org.objectweb.asm.tree.LabelNode;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
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;
public class RubyObjectSpecializer {
public static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static ClassAndAllocator getClassForSize(int size) {
return SPECIALIZED_CLASSES.get(size);
}
private static final NonBlockingHashMapLong<ClassAndAllocator> SPECIALIZED_CLASSES = new NonBlockingHashMapLong<>();
private static final ClassDefiningClassLoader LOADER = new OneShotClassLoader(Ruby.getClassLoader());
static class ClassAndAllocator {
final Class cls;
final ObjectAllocator allocator;
ClassAndAllocator(Class cls, ObjectAllocator allocator) {
this.cls = cls;
this.allocator = allocator;
}
}
public static ObjectAllocator specializeForVariables(RubyClass klass, Set<String> foundVariables) {
int size = foundVariables.size();
size = Math.min(size, Options.REIFY_VARIABLES_MAX.load());
ClassAndAllocator cna = null;
String className = null;
if (Options.REIFY_VARIABLES_NAME.load()) {
className = klass.getName();
if (className.startsWith("#")) {
className = "Anonymous" + Integer.toHexString(System.identityHashCode(klass));
} else {
className = className.replace("::", "/");
}
} else {
cna = getClassForSize(size);
if (cna == null) {
className = "RubyObject" + size;
}
}
if (className != null) {
final String clsPath = "org/jruby/gen/" + className;
synchronized (LOADER) {
Class specialized;
try {
specialized = LOADER.loadClass(clsPath.replace('/', '.'));
} catch (ClassNotFoundException cnfe) {
specialized = generateInternal(size, clsPath);
}
try {
ObjectAllocator allocator = (ObjectAllocator) specialized.getDeclaredClasses()[0].newInstance();
cna = new ClassAndAllocator(specialized, allocator);
if (!Options.REIFY_VARIABLES_NAME.load()) {
SPECIALIZED_CLASSES.put(size, cna);
}
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}
try {
int offset = 0;
for (String name : foundVariables) {
klass.getVariableTableManager().getVariableAccessorForVar(
name,
LOOKUP.findGetter(cna.cls, "var" + offset, Object.class),
LOOKUP.findSetter(cna.cls, "var" + offset, Object.class));
offset++;
if (offset >= size) break;
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
klass.setReifiedClass(cna.cls);
klass.setAllocator(cna.allocator);
return cna.allocator;
}
private static Class generateInternal(int size, final String clsPath) {
final String baseName = p(RubyObject.class);
final JiteClass jiteClass = new JiteClass(clsPath, baseName, new String[0]) {{
for (int i = 0; i < size; i++) {
final int offset = i;
defineField("var" + offset, ACC_PUBLIC, ci(Object.class), null);
}
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();
}});
defineMethod("getVariable", ACC_PUBLIC, sig(Object.class, int.class), new CodeBlock() {{
LabelNode parentCall = new LabelNode(new Label());
line(0);
if (size > 0) genGetSwitch(clsPath, size, this, 1);
line(1);
aload(0);
iload(1);
invokespecial(p(RubyObject.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 (size > 0) genPutSwitch(clsPath, size, this, 1);
line(3);
aload(0);
iload(1);
aload(2);
invokespecial(p(RubyObject.class), "setVariable", sig(void.class, int.class, Object.class));
voidreturn();
}});
addChildClass(new JiteClass(clsPath + "Allocator", 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 void genGetSwitch(String clsPath, int size, CodeBlock block, int offsetVar) {
LabelNode defaultError = new LabelNode(new Label());
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, "var" + i, ci(Object.class));
block.areturn();
}
block.label(defaultError);
}
private static void genPutSwitch(String clsPath, int size, CodeBlock block, int offsetVar) {
LabelNode defaultError = new LabelNode(new Label());
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, "var" + i, ci(Object.class));
block.voidreturn();
}
block.label(defaultError);
}
private static Class defineClass(JiteClass jiteClass) {
return LOADER.defineClass(classNameFromJiteClass(jiteClass), jiteClass.toBytes(JDKVersion.V1_8));
}
private static String classNameFromJiteClass(JiteClass jiteClass) {
return jiteClass.getClassName().replace('/', '.');
}
}