diff --git a/core/src/main/java/org/jruby/Ruby.java b/core/src/main/java/org/jruby/Ruby.java index 46dd60c5572..464e17c2ca6 100644 --- a/core/src/main/java/org/jruby/Ruby.java +++ b/core/src/main/java/org/jruby/Ruby.java @@ -122,7 +122,6 @@ import org.jruby.ir.persistence.IRReader; import org.jruby.ir.persistence.IRReaderStream; import org.jruby.ir.persistence.util.IRFileExpert; -import org.jruby.javasupport.proxy.JavaProxyClassFactory; import org.jruby.management.BeanManager; import org.jruby.management.BeanManagerFactory; import org.jruby.management.Config; @@ -2986,14 +2985,6 @@ public Map> getBoundMethods() { return boundMethods; } - public void setJavaProxyClassFactory(JavaProxyClassFactory factory) { - this.javaProxyClassFactory = factory; - } - - public JavaProxyClassFactory getJavaProxyClassFactory() { - return javaProxyClassFactory; - } - private static final EnumSet interest = EnumSet.of( RubyEvent.C_CALL, @@ -5622,8 +5613,6 @@ public void warn(String message) { private FFI ffi; - private JavaProxyClassFactory javaProxyClassFactory; - /** Used to find the ProfilingService implementation to use. If profiling is disabled it's null */ private final ProfilingServiceLookup profilingServiceLookup; diff --git a/core/src/main/java/org/jruby/RubyClass.java b/core/src/main/java/org/jruby/RubyClass.java index 3c39d468803..eff907dcea0 100644 --- a/core/src/main/java/org/jruby/RubyClass.java +++ b/core/src/main/java/org/jruby/RubyClass.java @@ -31,40 +31,29 @@ package org.jruby; -import org.jruby.javasupport.JavaClass; -import org.jruby.parser.StaticScope; -import org.jruby.runtime.Arity; -import org.jruby.runtime.JavaSites; -import org.jruby.runtime.Signature; -import org.jruby.runtime.callsite.CachingCallSite; -import org.jruby.runtime.callsite.RespondToCallSite; -import org.jruby.runtime.ivars.VariableAccessor; +import static org.jruby.runtime.Visibility.PRIVATE; +import static org.jruby.runtime.Visibility.PUBLIC; import static org.jruby.util.CodegenUtils.ci; import static org.jruby.util.CodegenUtils.p; import static org.jruby.util.CodegenUtils.sig; import static org.jruby.util.RubyStringBuilder.str; import static org.jruby.util.RubyStringBuilder.types; +import static org.objectweb.asm.Opcodes.ACC_BRIDGE; +import static org.objectweb.asm.Opcodes.ACC_FINAL; import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_STATIC; import static org.objectweb.asm.Opcodes.ACC_SUPER; +import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC; import static org.objectweb.asm.Opcodes.ACC_VARARGS; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Collectors; import org.jruby.anno.JRubyClass; import org.jruby.anno.JRubyMethod; @@ -73,31 +62,41 @@ import org.jruby.internal.runtime.methods.DynamicMethod; import org.jruby.java.codegen.RealClassGenerator; import org.jruby.java.codegen.Reified; +import org.jruby.java.proxies.ConcreteJavaProxy; import org.jruby.javasupport.Java; -import org.jruby.runtime.Helpers; -import org.jruby.runtime.Block; -import org.jruby.runtime.CallSite; -import org.jruby.runtime.CallType; -import org.jruby.runtime.ClassIndex; -import org.jruby.runtime.MethodIndex; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ObjectMarshal; -import org.jruby.runtime.ThreadContext; -import static org.jruby.runtime.Visibility.*; +import org.jruby.javasupport.Java.JCtorCache; +import org.jruby.javasupport.JavaClass; +import org.jruby.javasupport.JavaConstructor; +import org.jruby.javasupport.proxy.JavaProxyClass; +import org.jruby.javasupport.proxy.ReifiedJavaProxy; +import org.jruby.javasupport.util.JavaClassConfiguration; +import org.jruby.lexer.yacc.SimpleSourcePosition; +import org.jruby.parser.StaticScope; +import org.jruby.runtime.*; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.callsite.CacheEntry; +import org.jruby.runtime.callsite.CachingCallSite; +import org.jruby.runtime.callsite.RespondToCallSite; +import org.jruby.runtime.ivars.VariableAccessor; import org.jruby.runtime.ivars.VariableAccessorField; import org.jruby.runtime.ivars.VariableTableManager; import org.jruby.runtime.marshal.MarshalStream; import org.jruby.runtime.marshal.UnmarshalStream; import org.jruby.runtime.opto.Invalidator; -import org.jruby.util.*; +import org.jruby.util.ArraySupport; +import org.jruby.util.ClassDefiningClassLoader; +import org.jruby.util.CodegenUtils; +import org.jruby.util.JavaNameMangler; +import org.jruby.util.OneShotClassLoader; +import org.jruby.util.StringSupport; import org.jruby.util.collections.ConcurrentWeakHashMap; import org.jruby.util.log.Logger; import org.jruby.util.log.LoggerFactory; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.commons.GeneratorAdapter; + /** * @@ -144,7 +143,8 @@ public void setAllocator(ObjectAllocator allocator) { * * @param cls The class on which to call the default constructor to allocate */ - public void setClassAllocator(final Class cls) { + @SuppressWarnings("unchecked") + public void setClassAllocator(final Class cls) { this.allocator = (runtime, klazz) -> { try { RubyBasicObject object = (RubyBasicObject)cls.getConstructor().newInstance(); @@ -157,7 +157,7 @@ public void setClassAllocator(final Class cls) { } }; - this.reifiedClass = cls; + this.reifiedClass = (Class) cls; } /** @@ -166,6 +166,7 @@ public void setClassAllocator(final Class cls) { * * @param clazz The class from which to grab a standard Ruby constructor */ + @SuppressWarnings("unchecked") public void setRubyClassAllocator(final Class clazz) { try { final Constructor constructor = clazz.getConstructor(Ruby.class, RubyClass.class); @@ -182,7 +183,7 @@ public void setRubyClassAllocator(final Class clazz) { } }; - this.reifiedClass = clazz; + this.reifiedClass = (Class) clazz; } catch (NoSuchMethodException nsme) { throw new RuntimeException(nsme); } @@ -196,6 +197,7 @@ public void setRubyClassAllocator(final Class clazz) { * @param clazz The class from which to grab a standard Ruby __allocate__ method. * * @note Used with `jrubyc --java` generated (interoperability) class files. + * @note Used with new concrete extension. */ public void setRubyStaticAllocator(final Class clazz) { try { @@ -211,7 +213,7 @@ public void setRubyStaticAllocator(final Class clazz) { } }; - this.reifiedClass = clazz; + this.reifiedClass = (Class) clazz; } catch (NoSuchMethodException nsme) { throw new RuntimeException(nsme); } @@ -830,18 +832,18 @@ private void dumpReifiedClass(String dumpDir, String javaPath, byte[] classBytes } } - private void generateMethodAnnotations(Map> methodAnnos, SkinnyMethodAdapter m, List>> parameterAnnos) { + private void generateMethodAnnotations(Map, Map> methodAnnos, SkinnyMethodAdapter m, List, Map>> parameterAnnos) { if (methodAnnos != null && methodAnnos.size() != 0) { - for (Map.Entry> entry : methodAnnos.entrySet()) { + for (Map.Entry, Map> entry : methodAnnos.entrySet()) { m.visitAnnotationWithFields(ci(entry.getKey()), true, entry.getValue()); } } if (parameterAnnos != null && parameterAnnos.size() != 0) { for (int i = 0; i < parameterAnnos.size(); i++) { - Map> annos = parameterAnnos.get(i); + Map, Map> annos = parameterAnnos.get(i); if (annos != null && annos.size() != 0) { - for (Iterator>> it = annos.entrySet().iterator(); it.hasNext();) { - Map.Entry> entry = it.next(); + for (Iterator, Map>> it = annos.entrySet().iterator(); it.hasNext();) { + Map.Entry, Map> entry = it.next(); m.visitParameterAnnotationWithFields(i, ci(entry.getKey()), true, entry.getValue()); } } @@ -950,12 +952,24 @@ private RubyClass initializeCommon(ThreadContext context, RubyClass superClazz, */ @JRubyMethod(name = "initialize_copy", required = 1, visibility = PRIVATE) @Override - public IRubyObject initialize_copy(IRubyObject original){ + public IRubyObject initialize_copy(IRubyObject original) { checkNotInitialized(); if (original instanceof MetaClass) throw runtime.newTypeError("can't copy singleton class"); super.initialize_copy(original); - allocator = ((RubyClass)original).allocator; + RubyClass originalClazz = (RubyClass) original; + allocator = originalClazz.allocator; + + // copy over reify options + javaClassConfiguration = originalClazz.javaClassConfiguration == null ? null : originalClazz.javaClassConfiguration.clone(); + + // copy over reified class if applicable + if (originalClazz.getJavaProxy() && originalClazz.reifiedClass != null + && !Reified.class.isAssignableFrom(originalClazz.reifiedClass)) { + reifiedClass = originalClazz.reifiedClass; + reifiedClassJava = originalClazz.reifiedClassJava; + } + return this; } @@ -1215,10 +1229,10 @@ public Object unmarshalFrom(Ruby runtime, RubyClass type, * Whether this class can be reified into a Java class. Currently only objects * that descend from Object (or descend from Ruby-based classes that descend * from Object) can be reified. - * - * @return true if the class can be reified, false otherwise + * @param java If reified from java (out param) + * @return true if the class can be reified, false otherwise. The out param indicate if it is java concrete reification */ - public boolean isReifiable() { + public boolean isReifiable(boolean[] java) { // already reified is not reifiable if (reifiedClass != null) return false; @@ -1232,12 +1246,18 @@ public boolean isReifiable() { if (reifiedSuper != null) { // super must be Object, BasicObject, or a reified user class - return reifiedSuper == RubyObject.class || - reifiedSuper == RubyBasicObject.class || - Reified.class.isAssignableFrom(reifiedSuper); + boolean result = reifiedSuper == RubyObject.class || reifiedSuper == RubyBasicObject.class + || Reified.class.isAssignableFrom(reifiedSuper); + // TODO: check & test for nested java classes + if (result && !ReifiedJavaProxy.class.isAssignableFrom(reifiedSuper)) { + return true; + } else { + java[0] = true; + return true; + } } else { // non-native, non-reified super; recurse - return realSuper.isReifiable(); + return realSuper.isReifiable(java); } } @@ -1263,7 +1283,8 @@ public void reifyWithAncestors(boolean useChildLoader) { * @param useChildLoader whether to load the class into its own child classloader */ public void reifyWithAncestors(String classDumpDir, boolean useChildLoader) { - if (isReifiable()) { + boolean[] box = { false }; + if (isReifiable(box)) { RubyClass realSuper = getSuperClass().getRealClass(); if (realSuper.reifiedClass == null) realSuper.reifyWithAncestors(classDumpDir, useChildLoader); @@ -1288,8 +1309,10 @@ public final void reify(boolean useChildLoader) { * @param classDumpDir Directory to save reified java class */ public synchronized void reify(String classDumpDir, boolean useChildLoader) { + boolean[] java_box = { false }; // re-check reifiable in case another reify call has jumped in ahead of us - if (!isReifiable()) return; + if (!isReifiable(java_box)) return; + final boolean concreteExt = java_box[0]; // calculate an appropriate name, for anonymous using inspect like format e.g. "Class:0x628fad4a" final String name = getBaseName() != null ? getName() : @@ -1298,7 +1321,7 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) { final String javaName = "rubyobj." + StringSupport.replaceAll(name, "::", "."); final String javaPath = "rubyobj/" + StringSupport.replaceAll(name, "::", "/"); - final Class parentReified = superClass.getRealClass().getReifiedClass(); + final Class parentReified = superClass.getRealClass().getReifiedClass(); if (parentReified == null) { throw getClassRuntime().newTypeError(getName() + "'s parent class is not yet reified"); } @@ -1306,7 +1329,14 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) { Class reifiedParent = RubyObject.class; if (superClass.reifiedClass != null) reifiedParent = superClass.reifiedClass; - final byte[] classBytes = new MethodReificator(reifiedParent, javaName, javaPath).reify(); + Reificator reifier; + if (concreteExt) { + reifier = new ConcreteJavaReifier(parentReified, javaName, javaPath); + } else { + reifier = new MethodReificator(reifiedParent, javaName, javaPath, null, javaPath); + } + + final byte[] classBytes = reifier.reify(); final ClassDefiningClassLoader parentCL; if (parentReified.getClassLoader() instanceof OneShotClassLoader) { @@ -1318,21 +1348,33 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) { parentCL = runtime.getJRubyClassLoader(); } } + boolean nearEnd=false; // Attempt to load the name we plan to use; skip reification if it exists already (see #1229). try { Class result = parentCL.defineClass(javaName, classBytes); dumpReifiedClass(classDumpDir, javaPath, classBytes); + //Trigger initilization @SuppressWarnings("unchecked") - java.lang.reflect.Method clinit = result.getDeclaredMethod("clinit", Ruby.class, RubyClass.class); - clinit.invoke(null, runtime, this); + java.lang.reflect.Field rt = result.getDeclaredField(BaseReificator.RUBY_FIELD); + rt.setAccessible(true); + if (rt.get(null) != runtime) throw new RuntimeException("No ruby field set!"); - setClassAllocator(result); - reifiedClass = result; + if (concreteExt) { + // setAllocator(ConcreteJavaProxy.ALLOCATOR); // this should be already set + // Allocator "set" via clinit {@see JavaProxyClass#setProxyClassReified()} + this.setInstanceVariable("@java_class", Java.wrapJavaObject(runtime, result)); + } else { + setRubyClassAllocator(result); + } + reifiedClass = result; + nearEnd = true; + JavaProxyClass.ensureStaticIntConsumed(); return; // success } catch (LinkageError error) { // fall through to failure path + JavaProxyClass.addStaticInitLookup((Object[])null); // wipe any local values not retrieved final String msg = error.getMessage(); if ( msg != null && msg.contains("duplicate class definition for name") ) { logReifyException(error, false); @@ -1342,6 +1384,8 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) { } } catch (Exception ex) { + if (nearEnd) throw (RuntimeException)ex; + JavaProxyClass.addStaticInitLookup((Object[])null); // wipe any local values not retrieved logReifyException(ex, true); } @@ -1357,84 +1401,143 @@ public synchronized void reify(String classDumpDir, boolean useChildLoader) { interface Reificator { byte[] reify(); } // interface Reificator + + private final static PositionAware defaultSimplePosition = new SimpleSourcePosition("", 0); + + public PositionAware getPositionOrDefault(DynamicMethod method) { + if (method instanceof PositionAware) { + PositionAware pos = (PositionAware) method; + return new SimpleSourcePosition(pos.getFile(), pos.getLine() + 1); // convert from 0-based to 1-based that + // the JVM requires + } else + return defaultSimplePosition; + } private abstract class BaseReificator implements Reificator { - protected final Class reifiedParent; + public final Class reifiedParent; protected final String javaName; - protected final String javaPath; + public final String javaPath; + public final String rubyName; + public final String rubyPath; + protected final JavaClassConfiguration jcc; protected final ClassWriter cw; - BaseReificator(Class reifiedParent, String javaName, String javaPath) { + public final static String RUBY_FIELD = "ruby"; + public final static String RUBY_CLASS_FIELD = "rubyClass"; + + BaseReificator(Class reifiedParent, String javaName, String javaPath, String rubyName, String rubyPath) { this.reifiedParent = reifiedParent; this.javaName = javaName; this.javaPath = javaPath; + this.rubyName = rubyName; + this.rubyPath = rubyPath; + jcc = getClassConfig(); cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); - cw.visit(RubyInstanceConfig.JAVA_VERSION, ACC_PUBLIC + ACC_SUPER, javaPath, null, p(reifiedParent), interfaces()); + cw.visit(RubyInstanceConfig.JAVA_VERSION, ACC_PUBLIC + ACC_SUPER, javaPath, null, p(reifiedParent), + interfaces()); + cw.visitSource("generated:Reificator@" + this.getClass().getName(), null); } @Override public byte[] reify() { // fields to hold Ruby and RubyClass references - cw.visitField(ACC_STATIC | ACC_PRIVATE, "ruby", ci(Ruby.class), null, null); - cw.visitField(ACC_STATIC | ACC_PRIVATE, "rubyClass", ci(RubyClass.class), null, null); - - // static initializing method - SkinnyMethodAdapter m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_STATIC, "clinit", sig(void.class, Ruby.class, RubyClass.class), null, null); - m.start(); - m.aload(0); - m.putstatic(javaPath, "ruby", ci(Ruby.class)); - m.aload(1); - m.putstatic(javaPath, "rubyClass", ci(RubyClass.class)); - m.voidreturn(); - m.end(); + cw.visitField(ACC_SYNTHETIC | ACC_FINAL | ACC_STATIC | ACC_PRIVATE, RUBY_FIELD, ci(Ruby.class), null, null); + cw.visitField(ACC_SYNTHETIC | ACC_FINAL | ACC_STATIC | ACC_PRIVATE, RUBY_CLASS_FIELD, ci(RubyClass.class), null, null); - // standard constructor that accepts Ruby, RubyClass - m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", sig(void.class, Ruby.class, RubyClass.class), null, null); - m.aload(0); - m.aload(1); - m.aload(2); - m.invokespecial(p(reifiedParent), "", sig(void.class, Ruby.class, RubyClass.class)); - m.voidreturn(); - m.end(); + reifyConstructors(); + customReify(); - // no-arg constructor using static references to Ruby and RubyClass - m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", CodegenUtils.sig(void.class), null, null); - m.aload(0); - m.getstatic(javaPath, "ruby", ci(Ruby.class)); - m.getstatic(javaPath, "rubyClass", ci(RubyClass.class)); - m.invokespecial(p(reifiedParent), "", sig(void.class, Ruby.class, RubyClass.class)); + // static initializing method, note this is after the constructors to check for alloc-ables (see Concrete Java) + SkinnyMethodAdapter m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_STATIC, "", sig(void.class), + null, null); + m.start(); + reifyClinit(m); m.voidreturn(); m.end(); - customReify(); - cw.visitEnd(); return cw.toByteArray(); } + public abstract void reifyClinit(SkinnyMethodAdapter m); + 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); + interfaceNames[0] = p(isRubyObject() ? Reified.class : ReifiedJavaProxy.class); // add the other user-specified interfaces for (int i = 0; i < interfaces.length; i++) { interfaceNames[i + 1] = p(interfaces[i]); } return interfaceNames; } + + protected boolean isRubyObject() { + return true; + } + + /** + * Loads self (if local) or the rubyObject (if a java proxy) cast to a RubyBasicObject, as everything is a RBO + * and it has a nicer interface + */ + protected void loadRubyObject(SkinnyMethodAdapter m) { + m.aload(0); // self + } + + public void rubycall(SkinnyMethodAdapter m, String signature) { + m.invokevirtual(rubyPath, "callMethod", signature); + } + + protected void reifyConstructors() { + // standard constructor that accepts Ruby, RubyClass. For use by JRuby (internally) + SkinnyMethodAdapter m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", + sig(void.class, Ruby.class, RubyClass.class), null, null); + m.aload(0); // uninitialized this + m.aload(1); // ruby + m.aload(2); // rubyclass + allocAndInitialize(m, false); + + if (jcc.javaConstructable) { + // no-arg constructor using static references to Ruby and RubyClass. For use by java + m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, "", CodegenUtils.sig(void.class), null, null); + m.aload(0); // uninitialized this + m.getstatic(javaPath, RUBY_FIELD, ci(Ruby.class)); + m.getstatic(javaPath, RUBY_CLASS_FIELD, ci(RubyClass.class)); + allocAndInitialize(m, true); + } + } + + // java can't pass args to normal ruby classes right now, only concrete (below) + protected void allocAndInitialize(SkinnyMethodAdapter m, boolean initIfAllowed) { + m.invokespecial(p(reifiedParent), "", sig(void.class, Ruby.class, RubyClass.class)); + if (jcc.callInitialize && initIfAllowed) // if we want to initialize + { + m.aload(0); // initialized this + m.ldc(jcc.javaCtorMethodName); + rubycall(m, sig(IRubyObject.class, String.class)); + } + m.voidreturn(); + m.end(); + } + + public Class[] join(Class[] base, Class... extra) { + Class[] more = ArraySupport.newCopy(base, base.length + extra.length); + ArraySupport.copy(extra, more, base.length, extra.length); + return more; + } } private class MethodReificator extends BaseReificator { - MethodReificator(Class reifiedParent, String javaName, String javaPath) { - super(reifiedParent, javaName, javaPath); + MethodReificator(Class reifiedParent, String javaName, String javaPath, String rubyName, String rubyPath) { + super(reifiedParent, javaName, javaPath, rubyName, rubyPath); } @Override @@ -1455,9 +1558,9 @@ public void customReify() { } private void addClassAnnotations() { - if (classAnnotations != null && !classAnnotations.isEmpty()) { - for (Map.Entry> entry : classAnnotations.entrySet()) { - Class annoType = entry.getKey(); + if (jcc.classAnnotations != null && !jcc.classAnnotations.isEmpty()) { + for (Map.Entry,Map> entry : jcc.classAnnotations.entrySet()) { + Class annoType = entry.getKey(); Map fields = entry.getValue(); AnnotationVisitor av = cw.visitAnnotation(ci(annoType), true); @@ -1468,17 +1571,17 @@ private void addClassAnnotations() { } private void defineFields() { - for (Map.Entry fieldSignature : getFieldSignatures().entrySet()) { + for (Map.Entry> fieldSignature : getFieldSignatures().entrySet()) { String fieldName = fieldSignature.getKey(); - Class type = fieldSignature.getValue(); - Map> fieldAnnos = getFieldAnnotations().get(fieldName); + Class type = fieldSignature.getValue(); + Map, Map> fieldAnnos = getFieldAnnotations().get(fieldName); FieldVisitor fieldVisitor = cw.visitField(ACC_PUBLIC, fieldName, ci(type), null, null); if (fieldAnnos == null) continue; - for (Map.Entry> fieldAnno : fieldAnnos.entrySet()) { - Class annoType = fieldAnno.getKey(); + for (Map.Entry, Map> fieldAnno : fieldAnnos.entrySet()) { + Class annoType = fieldAnno.getKey(); AnnotationVisitor av = fieldVisitor.visitAnnotation(ci(annoType), true); CodegenUtils.visitAnnotationFields(av, fieldAnno.getValue()); } @@ -1490,38 +1593,44 @@ private void defineClassMethods(Set instanceMethods) { SkinnyMethodAdapter m; // define class/static methods - for (Map.Entry methodEntry : getMetaClass().getMethods().entrySet()) { + for (Map.Entry methodEntry : getMetaClass().getMethods().entrySet()) { // TODO: explicitly included but not-yet defined methods? String id = methodEntry.getKey(); + if (jcc.getExcluded().contains(id)) + continue; String javaMethodName = JavaNameMangler.mangleMethodName(id); + PositionAware position = getPositionOrDefault(methodEntry.getValue()); + if (position.getLine() > 1) cw.visitSource(position.getFile(), null); - Map> methodAnnos = getMetaClass().getMethodAnnotations().get(id); - List>> parameterAnnos = getMetaClass().getParameterAnnotations().get(id); - Class[] methodSignature = getMetaClass().getMethodSignatures().get(id); + Map,Map> methodAnnos = getMetaClass().getMethodAnnotations().get(id); + List,Map>> parameterAnnos = getMetaClass().getParameterAnnotations().get(id); + Class[] methodSignature = getMetaClass().getMethodSignatures().get(id); String signature; if (methodSignature == null) { + if (!jcc.allClassMethods) continue; Signature sig = methodEntry.getValue().getSignature(); // non-signature signature with just IRubyObject if (sig.isNoArguments()) { signature = sig(IRubyObject.class); if (instanceMethods.contains(javaMethodName + signature)) continue; m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_STATIC, javaMethodName, signature, null, null); + m.line(position.getLine()); generateMethodAnnotations(methodAnnos, m, parameterAnnos); - m.getstatic(javaPath, "rubyClass", ci(RubyClass.class)); - //m.invokevirtual("org/jruby/RubyClass", "getMetaClass", sig(RubyClass.class) ); + m.getstatic(javaPath, RUBY_CLASS_FIELD, ci(RubyClass.class)); m.ldc(id); m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class)); } else { 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); + m.line(position.getLine()); generateMethodAnnotations(methodAnnos, m, parameterAnnos); - m.getstatic(javaPath, "rubyClass", ci(RubyClass.class)); + m.getstatic(javaPath, RUBY_CLASS_FIELD, ci(RubyClass.class)); m.ldc(id); - m.aload(0); + m.aload(0); // load the parameter array m.invokevirtual("org/jruby/RubyClass", "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class) ); } m.areturn(); @@ -1529,7 +1638,7 @@ private void defineClassMethods(Set instanceMethods) { else { // generate a real method signature for the method, with to/from coercions // indices for temp values - Class[] params = new Class[methodSignature.length - 1]; + 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; @@ -1537,12 +1646,13 @@ private void defineClassMethods(Set instanceMethods) { 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); + m.line(position.getLine()); generateMethodAnnotations(methodAnnos, m, parameterAnnos); - m.getstatic(javaPath, "ruby", ci(Ruby.class)); + m.getstatic(javaPath, RUBY_FIELD, ci(Ruby.class)); m.astore(rubyIndex); - m.getstatic(javaPath, "rubyClass", ci(RubyClass.class)); + m.getstatic(javaPath, RUBY_CLASS_FIELD, ci(RubyClass.class)); m.ldc(id); // method name RealClassGenerator.coerceArgumentsToRuby(m, params, rubyIndex); @@ -1557,115 +1667,519 @@ private void defineClassMethods(Set instanceMethods) { } } - private void defineInstanceMethods(Set instanceMethods) { - SkinnyMethodAdapter m; - for (Map.Entry methodEntry : getMethods().entrySet()) { + //TODO: only generate that are overrideable (javaproxyclass) + protected void defineInstanceMethods(Set instanceMethods) { + Set defined = new HashSet<>(); + for (Map.Entry methodEntry : getMethods().entrySet()) { // TODO: explicitly included but not-yet defined methods? final String id = methodEntry.getKey(); + final String callid = jcc.renamedMethods.getOrDefault(id, id); + if (defined.contains(id) || jcc.getExcluded().contains(id)) + continue; + defined.add(callid); // id we won't see again, and are only defining java methods named id + + + DynamicMethod method = methodEntry.getValue(); + if (id != callid) // identity is fine as it's the default + { + method = searchMethod(callid); + } + final Signature arity = method.getSignature(); - String javaMethodName = JavaNameMangler.mangleMethodName(id); - - Map> methodAnnos = getMethodAnnotations().get(id); - List>> parameterAnnos = getParameterAnnotations().get(id); - Class[] methodSignature = getMethodSignatures().get(id); + + PositionAware position = getPositionOrDefault(methodEntry.getValue()); + if (position.getLine() > 1) cw.visitSource(position.getFile(), null); - final String signature; - if (methodSignature == null) { // non-signature signature with just IRubyObject - Signature sig = methodEntry.getValue().getSignature(); - if (sig.isFixed()) { - switch (sig.required()) { - case 0: - signature = sig(IRubyObject.class); // return IRubyObject foo() - m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, javaMethodName, signature, null, null); - generateMethodAnnotations(methodAnnos, m, parameterAnnos); - - m.aload(0); - m.ldc(id); - m.invokevirtual(javaPath, "callMethod", sig(IRubyObject.class, String.class)); - break; - case 1: - signature = sig(IRubyObject.class, IRubyObject.class); // return IRubyObject foo(IRubyObject arg1) - m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, javaMethodName, signature, null, null); - generateMethodAnnotations(methodAnnos, m, parameterAnnos); - - m.aload(0); - m.ldc(id); - m.aload(1); // IRubyObject arg1 - m.invokevirtual(javaPath, "callMethod", sig(IRubyObject.class, String.class, IRubyObject.class)); - break; - default: - // currently we only have : - // callMethod(context, name) - // callMethod(context, name, arg1) - // so for other arities use generic: - // callMethod(context, name, args...) - final int paramCount = sig.required(); - Class[] params = new Class[paramCount]; - Arrays.fill(params, IRubyObject.class); - signature = sig(IRubyObject.class, params); - m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, javaMethodName, signature, null, null); - generateMethodAnnotations(methodAnnos, m, parameterAnnos); - - m.aload(0); - m.ldc(id); - - // generate an IRubyObject[] for the method arguments : - m.pushInt(paramCount); - m.anewarray(p(IRubyObject.class)); // new IRubyObject[size] - for (int i = 1; i <= paramCount; i++) { - m.dup(); - m.pushInt(i - 1); // array index e.g. iconst_0 - m.aload(i); // IRubyObject arg1, arg2 e.g. aload_1 - m.aastore(); // arr[ i - 1 ] = arg_i - } - m.invokevirtual(javaPath, "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class)); - } - } else { - // (generic) variable arity e.g. method(*args) - // NOTE: maybe improve to match fixed part for < -1 e.g. (IRubObject, IRubyObject, IRubyObject...) - signature = sig(IRubyObject.class, IRubyObject[].class); - m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_VARARGS, javaMethodName, signature, null, null); - generateMethodAnnotations(methodAnnos, m, parameterAnnos); + Class[] methodSignature = getMethodSignatures().get(callid); // ruby side, use callid - m.aload(0); - m.ldc(id); - m.aload(1); // IRubyObject[] arg1 - m.invokevirtual(javaPath, "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class)); + // for concrete extension, see if the method is one we are overriding, + // even if we didn't specify it manually + if (methodSignature == null) { + // TODO: should inherited search for java mangledName? + for (Class[] sig : searchInheritedSignatures(id, arity)) // id (vs callid) here as this is + // searching in java + { + String signature = defineInstanceMethod(id, callid, arity, position, sig); + if (signature != null) instanceMethods.add(signature); } - m.areturn(); - } else { // generate a real method signature for the method, with to/from coercions + } else { + String signature = defineInstanceMethod(id, callid, arity, position, methodSignature); + if (signature != null) instanceMethods.add(signature); + } + } + } + + protected String defineInstanceMethod(final String id, final String callid, final Signature sig, + PositionAware position, Class[] methodSignature) { + String javaMethodName = JavaNameMangler.mangleMethodName(id); - // indices for temp values - Class[] params = new Class[methodSignature.length - 1]; - ArraySupport.copy(methodSignature, 1, params, 0, params.length); - final int baseIndex = RealClassGenerator.calcBaseIndex(params, 1); - final int rubyIndex = baseIndex; + Map, Map> methodAnnos = getMethodAnnotations().get(callid); // ruby side, use callid + List, Map>> parameterAnnos = getParameterAnnotations().get(callid); // ruby side, use callid - signature = sig(methodSignature[0], params); - int mod = ACC_PUBLIC; - if ( isVarArgsSignature(id, methodSignature) ) mod |= ACC_VARARGS; - m = new SkinnyMethodAdapter(cw, mod, javaMethodName, signature, null, null); + final String signature; + SkinnyMethodAdapter m; + if (methodSignature == null) { // non-signature signature with just IRubyObject + if (!jcc.allMethods) return null; + if (sig.isFixed()) { + switch (sig.required()) { + case 0: + signature = sig(IRubyObject.class); // return IRubyObject foo() + m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, javaMethodName, signature, null, null); + m.line(position.getLine()); + generateMethodAnnotations(methodAnnos, m, parameterAnnos); + generateObjectBarrier(m); + + loadRubyObject(m); // self/rubyObject + m.ldc(callid); + rubycall(m, sig(IRubyObject.class, String.class)); + break; + case 1: + signature = sig(IRubyObject.class, IRubyObject.class); // return IRubyObject foo(IRubyObject arg1) + m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, javaMethodName, signature, null, null); + m.line(position.getLine()); + generateMethodAnnotations(methodAnnos, m, parameterAnnos); + generateObjectBarrier(m); + + loadRubyObject(m); // self/rubyObject + m.ldc(callid); + m.aload(1); // IRubyObject arg1 + rubycall(m, sig(IRubyObject.class, String.class, IRubyObject.class)); + break; + default: + // currently we only have : + // callMethod(context, name) + // callMethod(context, name, arg1) + // so for other arities use generic: + // callMethod(context, name, args...) + final int paramCount = sig.required(); + Class[] params = new Class[paramCount]; + Arrays.fill(params, IRubyObject.class); + signature = sig(IRubyObject.class, params); + m = new SkinnyMethodAdapter(cw, ACC_PUBLIC, javaMethodName, signature, null, null); + m.line(position.getLine()); + generateMethodAnnotations(methodAnnos, m, parameterAnnos); + generateObjectBarrier(m); + + loadRubyObject(m); // self/rubyObject + m.ldc(callid); + + // generate an IRubyObject[] for the method arguments : + m.pushInt(paramCount); + m.anewarray(p(IRubyObject.class)); // new IRubyObject[size] + for (int i = 1; i <= paramCount; i++) { + m.dup(); + m.pushInt(i - 1); // array index e.g. iconst_0 + m.aload(i); // IRubyObject arg1, arg2 e.g. aload_1 + m.aastore(); // arr[ i - 1 ] = arg_i + } + rubycall(m, sig(IRubyObject.class, String.class, IRubyObject[].class)); + } + } else { + // (generic) variable arity e.g. method(*args) + // NOTE: maybe improve to match fixed part for < -1 e.g. (IRubObject, IRubyObject, IRubyObject...) + signature = sig(IRubyObject.class, IRubyObject[].class); + m = new SkinnyMethodAdapter(cw, ACC_PUBLIC | ACC_VARARGS, javaMethodName, signature, null, null); + m.line(position.getLine()); generateMethodAnnotations(methodAnnos, m, parameterAnnos); + generateObjectBarrier(m); - m.getstatic(javaPath, "ruby", ci(Ruby.class)); - m.astore(rubyIndex); + loadRubyObject(m); // self/rubyObject + m.ldc(callid); + m.aload(1); // IRubyObject[] arg1 + rubycall(m, 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]; + ArraySupport.copy(methodSignature, 1, params, 0, params.length); + final int baseIndex = RealClassGenerator.calcBaseIndex(params, 1); + final int rubyIndex = baseIndex; + + signature = sig(methodSignature[0], params); + int mod = ACC_PUBLIC; + if ( isVarArgsSignature(callid, methodSignature) ) mod |= ACC_VARARGS; + m = new SkinnyMethodAdapter(cw, mod, javaMethodName, signature, null, null); + m.line(position.getLine()); + generateMethodAnnotations(methodAnnos, m, parameterAnnos); + generateObjectBarrier(m); + + m.getstatic(javaPath, RUBY_FIELD, ci(Ruby.class)); // runtime + m.astore(rubyIndex); + + loadRubyObject(m); // self/rubyObject + m.ldc(callid); // method name + + RealClassGenerator.coerceArgumentsToRuby(m, params, rubyIndex); + rubycall(m, sig(IRubyObject.class, String.class, IRubyObject[].class)); + RealClassGenerator.coerceResultAndReturn(m, methodSignature[0]); + + // generate any bridge methods needed as we overrode a defined one + if (!isRubyObject()) + generateSuperBridges(javaMethodName, methodSignature); + } + m.end(); - m.aload(0); // self - m.ldc(id); // method name - RealClassGenerator.coerceArgumentsToRuby(m, params, rubyIndex); - m.invokevirtual(javaPath, "callMethod", sig(IRubyObject.class, String.class, IRubyObject[].class)); + if (DEBUG_REIFY) LOG.debug("defining {}#{} (calling #{}) as {}#{}", getName(), id, callid, javaName, javaMethodName + signature); - RealClassGenerator.coerceResultAndReturn(m, methodSignature[0]); - } + return javaMethodName + signature; + } + + protected void generateSuperBridges(String javaMethodName, Class[] methodSignature) { + // Only for concrete java + } + + /** + * This method generates <clinit> by marshaling the Ruby, RubyClass, etc variables through a static map + * identified by integer in JavaProxyClass. Integers are serializable through bytecode generation so we can + * share arbitrary objects with the generated class by saving them in {@link #getExtraClinitInfo()} via + * {@link JavaProxyClass#addStaticInitLookup(Object...)} and {@link JavaProxyClass#getStaticInitLookup(int)} + */ + @Override + public void reifyClinit(SkinnyMethodAdapter m) { + // top stack layout: ..., i0, o[], i1, o[] - if (DEBUG_REIFY) LOG.debug("defining {}#{} as {}#{}", getName(), id, javaName, javaMethodName + signature); + m.pushInt(1); // rubyclass index + m.ldc(JavaProxyClass.addStaticInitLookup(getExtraClinitInfo())); + m.invokestatic(p(JavaProxyClass.class), "getStaticInitLookup", sig(Object[].class, int.class)); + m.dup_x1(); // array + m.dup_x2(); // array + m.pushInt(0); // ruby index + m.aaload(); // extract ruby + m.checkcast(p(Ruby.class)); + m.putstatic(javaPath, RUBY_FIELD, ci(Ruby.class)); + m.aaload(); // extract rubyclass + m.checkcast(p(RubyClass.class)); + m.putstatic(javaPath, RUBY_CLASS_FIELD, ci(RubyClass.class)); + extraClinitLookup(m); + } - instanceMethods.add(javaMethodName + signature); + protected Object[] getExtraClinitInfo() { + return new Object[] { runtime, RubyClass.this }; + } - m.end(); + /** + * Override to save more values from the array in {@link #reifyClinit(SkinnyMethodAdapter)} + */ + protected void extraClinitLookup(SkinnyMethodAdapter m) + { + m.pop(); + } + + protected Collection[]> searchInheritedSignatures(String id, Signature arity) { + HashMap[]> types = new HashMap<>(); + for (Class intf : Java.getInterfacesFromRubyClass(RubyClass.this)) + searchClassMethods(intf, arity, id, types); + if (types.size() == 0) types.put("", null); + return types.values(); + } + + protected Collection[]> searchClassMethods(Class clz, Signature arity, String id, + HashMap[]> options) { + if (clz.getSuperclass() != null) searchClassMethods(clz.getSuperclass(), arity, id, options); + for (Class intf : clz.getInterfaces()) + searchClassMethods(intf, arity, id, options); + for (Method method : clz.getDeclaredMethods()) { + // TODO: java <-> ruby conversion? + if (!method.getName().equals(id)) continue; + final int mod = method.getModifiers(); + if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) continue; + if (Modifier.isFinal(mod)) continue; + + if (arity != null) { + // ensure arity is reasonable (ignores java varargs) + if (arity.isFixed()) { + if (arity.required() != method.getParameterCount()) continue; + } else if (arity.required() > method.getParameterCount()) continue; + } + + // found! built a signature to return + Class[] types = join(new Class[] { method.getReturnType() }, method.getParameterTypes()); + options.put(sig(types), types); } + // Note: not stable. May flicker between different arities. TODO: sort? + return options.values(); + } + + protected void generateObjectBarrier(SkinnyMethodAdapter m) { + // For non-concrete things, we ignore, as this is a RubyObject } } // class MethodReificator + + //public or private? + public class ConcreteJavaReifier extends MethodReificator { + // names follow pattern of `this$0` from javac nested classes to hopefully be ignored by + // sane reflection tools. Also similarly marked as synthetic + public static final String RUBY_OBJECT_FIELD = "this$rubyObject"; + protected static final String RUBY_PROXY_CLASS_FIELD = "this$rubyProxyClass"; + public static final String RUBY_CTOR_CACHE_FIELD = "this$rubyCtorCache"; + + JavaConstructor[] savedSuperCtors = null; + Map> supers = new HashMap<>(); + + ConcreteJavaReifier(Class reifiedParent, String javaName, String javaPath) { + // In theory, we should operate on IRubyObject, but everything + // that we need is a ConcreteJavaProxy, and it (via RubyBasicObject) has a nicer interface to boot + super(reifiedParent, javaName, javaPath, ci(ConcreteJavaProxy.class), p(ConcreteJavaProxy.class)); + } + + @Override + public void customReify() { + super.customReify(); + + defineInterfaceMethods(); + } + + @Override + protected void loadRubyObject(SkinnyMethodAdapter m) { + m.aload(0); // self + m.getfield(javaPath, RUBY_OBJECT_FIELD, rubyName); // rubyObject + } + + @Override + public byte[] reify() { + cw.visitField(ACC_SYNTHETIC | ACC_FINAL | ACC_PRIVATE, RUBY_OBJECT_FIELD, rubyName, null, null); + cw.visitField(ACC_SYNTHETIC | ACC_FINAL | ACC_STATIC | ACC_PRIVATE, RUBY_PROXY_CLASS_FIELD, + ci(JavaProxyClass.class), null, null); + cw.visitField(ACC_SYNTHETIC | ACC_FINAL | ACC_STATIC | ACC_PRIVATE, RUBY_CTOR_CACHE_FIELD, + ci(JCtorCache.class), null, null); + return super.reify(); + } + + @Override + protected boolean isRubyObject() { + return false; + } + + // also save the ordered array of constructors + @Override + protected Object[] getExtraClinitInfo() { + return new Object[] { runtime, RubyClass.this, savedSuperCtors }; + } + + @Override + protected void extraClinitLookup(SkinnyMethodAdapter m) { + // extract cached ctors for lookup ordering + + // note: consume top of stack, lookuparray + m.newobj(p(JCtorCache.class)); + m.dup_x1(); // jccache, lookuparray, jccache + m.swap();// jccache, jccache, lookuparray + m.pushInt(2); // ctor fields = index 2 + m.aaload(); // extract ctors, -> jccache, jccache, ctor[] + m.checkcast(p(JavaConstructor[].class)); + m.invokespecial(p(JCtorCache.class), "", sig(void.class, JavaConstructor[].class)); + m.putstatic(javaPath, RUBY_CTOR_CACHE_FIELD, ci(JCtorCache.class)); + + // now create proxy class + m.getstatic(javaPath, RUBY_FIELD, ci(Ruby.class)); + m.getstatic(javaPath, RUBY_CLASS_FIELD, ci(RubyClass.class)); + m.ldc(org.objectweb.asm.Type.getType("L" + javaPath + ";")); + // if (simpleAlloc) // if simple, don't init, if complex, do init + // m.iconst_0(); // false (as int) + // else + m.iconst_1(); // true (as int) + + m.invokestatic(p(JavaProxyClass.class), "setProxyClassReified", + sig(JavaProxyClass.class, Ruby.class, RubyClass.class, Class.class, boolean.class)); + m.dup(); + m.putstatic(javaPath, RUBY_PROXY_CLASS_FIELD, ci(JavaProxyClass.class)); + + supers.forEach((name, sigs) -> { + + for (String sig : sigs) { + m.dup(); + m.ldc(name); + m.ldc(sig); + m.iconst_1(); + m.invokevirtual(p(JavaProxyClass.class), "initMethod", + sig(void.class, String.class, String.class, boolean.class)); + } + }); + m.pop(); + // Note: no end, that's in the parent call + } + + @Override + protected void generateSuperBridges(String javaMethodName, Class[] methodSignature) { + // TODO: Would be good to cache, don't look up this interface/method repeatedly + + // don't look on interfaces, just the parent + Class[] args = new Class[methodSignature.length - 1]; + ArraySupport.copy(methodSignature, 1, args, 0, methodSignature.length - 1); + Method supr = findTarget(reifiedParent, javaMethodName, methodSignature[0], args); + if (supr == null) return; + + SkinnyMethodAdapter m = new SkinnyMethodAdapter(cw, ACC_SYNTHETIC | ACC_BRIDGE | ACC_PUBLIC, + "__super$" + javaMethodName, sig(methodSignature), null, null); + GeneratorAdapter ga = RealClassGenerator.makeGenerator(m); + ga.loadThis(); + ga.loadArgs(); + m.invokespecial(p(reifiedParent), javaMethodName, sig(methodSignature)); + ga.returnValue(); + ga.endMethod(); + + if (!supers.containsKey(javaMethodName)) supers.put(javaMethodName, new ArrayList<>()); + + supers.get(javaMethodName).add(sig(methodSignature)); + } + + private Method findTarget(Class clz, String javaMethodName, Class returns, Class[] params) { + for (Method method : clz.getDeclaredMethods()) { + if (!method.getName().equals(javaMethodName)) continue; + final int mod = method.getModifiers(); + if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) continue; + if (Modifier.isAbstract(mod) || Modifier.isFinal(mod)) continue; + + // TODO: is args necessary? + if (!method.getReturnType().equals(returns)) continue; + if (!Arrays.equals(method.getParameterTypes(), params)) continue; + + return method; + } + if (clz.getSuperclass() != null) return findTarget(clz.getSuperclass(), javaMethodName, returns, params); + return null; + } + + @Override + protected Collection[]> searchInheritedSignatures(String id, Signature arity) { + HashMap[]> types = new HashMap<>(); + searchClassMethods(reifiedParent, arity, id, types); + for (Class intf : Java.getInterfacesFromRubyClass(RubyClass.this)) // this pattern is duplicated a lot. refactor? + searchClassMethods(intf, arity, id, types); + if (types.size() == 0) { + searchClassMethods(reifiedParent, null, id, types); + for (Class intf : Java.getInterfacesFromRubyClass(RubyClass.this)) + searchClassMethods(intf, null, id, types); + } + if (types.size() == 0) { + types.put("", null); + } + return types.values(); + } + + @Override + protected void reifyConstructors() { + Optional> zeroArg = Optional.empty(); + List> candidates = new ArrayList<>(); + for (Constructor constructor : reifiedParent.getDeclaredConstructors()) { + final int mod = constructor.getModifiers(); + if (!Modifier.isPublic(mod) && !Modifier.isProtected(mod)) continue; + candidates.add(constructor); + if (constructor.getParameterCount() == 0) // TODO: varargs? + { + zeroArg = Optional.of(constructor); + } + } + boolean isNestedRuby = ReifiedJavaProxy.class.isAssignableFrom(reifiedParent); + + // update the source location + DynamicMethod methodEntry = searchMethod(jcc.javaCtorMethodName); + PositionAware position = getPositionOrDefault(methodEntry); + cw.visitSource(position.getFile(), null); + int superpos = ConcreteJavaProxy.findSuperLine(runtime, methodEntry, position.getLine()); + Set generatedCtors = new HashSet<>(); + + if (candidates.size() > 0) // TODO: doc: implies javaConstructable? + { + List savedCtorsList = new ArrayList<>(candidates.size()); + for (Constructor constructor : candidates) { + savedCtorsList.add(new JavaConstructor(runtime, constructor)); + } + savedSuperCtors = savedCtorsList.toArray(new JavaConstructor[savedCtorsList.size()]); + } else { + // TODO: copy validateArgs + // TODO: no ctors = error? + throw runtime.newTypeError( + "class " + reifiedParent.getName() + " doesn't have a public or protected constructor"); + } + + if (zeroArg.isPresent()) { + // standard constructor that accepts Ruby, RubyClass. For use by JRuby (internally) + if (!jcc.allCtors) { + if (!isNestedRuby) { + generatedCtors.add(RealClassGenerator.makeConcreteConstructorProxy(cw, position, true, this, + new Class[0], isNestedRuby)); + } + + if (jcc.javaConstructable) { + generatedCtors.add(RealClassGenerator.makeConcreteConstructorProxy(cw, position, false, this, + new Class[0], isNestedRuby)); + } + } + } + + // TODO: remove rubyCtors if IRO is enabled (by default) + if (jcc.allCtors && !isNestedRuby) { + for (Constructor constructor : candidates) { + if (jcc.rubyConstructable) generatedCtors.add(RealClassGenerator.makeConcreteConstructorProxy(cw, + position, true, this, constructor.getParameterTypes(), false)); + + if (jcc.javaConstructable) generatedCtors.add(RealClassGenerator.makeConcreteConstructorProxy(cw, + position, false, this, constructor.getParameterTypes(), false)); + + } + } + + if (jcc.extraCtors != null && jcc.extraCtors.size() > 0) { + for (Class[] constructor : jcc.extraCtors) { + // TODO: support annotations in ctor params + + if (jcc.rubyConstructable && !generatedCtors + .contains(sig(void.class, join(constructor, Ruby.class, RubyClass.class)))) { + generatedCtors.add(RealClassGenerator.makeConcreteConstructorProxy(cw, position, true, this, + constructor, isNestedRuby)); + } + + if (jcc.javaConstructable && !generatedCtors.contains(sig(void.class, constructor))) { + generatedCtors.add(RealClassGenerator.makeConcreteConstructorProxy(cw, position, false, this, + constructor, isNestedRuby)); + } + } + } + if (jcc.IroCtors) { + RealClassGenerator.makeConcreteConstructorIROProxy(cw, position, this); + } else if (generatedCtors.size() == 0) { + //TODO: Warn for static classe? + throw runtime.newTypeError("class "+ this.rubyName + " doesn't have any exposed java constructors"); + } + + // generate the real (IRubyObject) ctor. All other ctor generated proxy to this one + RealClassGenerator.makeConcreteConstructorSwitch(cw, position, superpos, isNestedRuby, this, + savedSuperCtors); + } + + /** + * Generates an init barrier. NOT Thread-safe, but hopefully nobody has threads in their constructor? This is + * used to ensure that self.to_java is valid if the super ctor calls an abstract method that is re-implemented + * by ruby + */ + @Override + protected void generateObjectBarrier(SkinnyMethodAdapter m) { + // For non-concrete things, we check, as this is not a RubyObject + m.aload(0); + m.getfield(javaPath, RUBY_OBJECT_FIELD, rubyName); + m.aload(0); + m.invokevirtual(rubyPath, "ensureThis", sig(void.class, Object.class)); + } + + private void defineInterfaceMethods() { + SkinnyMethodAdapter m = new SkinnyMethodAdapter(cw, ACC_SYNTHETIC | ACC_PUBLIC, "___jruby$rubyObject", + sig(IRubyObject.class), null, null); + m.aload(0); // this + m.getfield(javaPath, RUBY_OBJECT_FIELD, rubyName); + m.areturn(); + m.end(); + + m = new SkinnyMethodAdapter(cw, ACC_SYNTHETIC | ACC_PUBLIC, "___jruby$proxyClass", + sig(JavaProxyClass.class), null, null); + m.getstatic(javaPath, RUBY_PROXY_CLASS_FIELD, ci(JavaProxyClass.class)); + m.areturn(); + m.end(); + } + + } // class ConcreteJavaReifier private boolean isVarArgsSignature(final String method, final Class[] methodSignature) { // TODO we should simply detect "java.lang.Object m1(java.lang.Object... args)" @@ -1682,17 +2196,43 @@ private void logReifyException(final Throwable failure, final boolean error) { } public void setReifiedClass(Class reifiedClass) { - this.reifiedClass = reifiedClass; + this.reifiedClass = (Class) reifiedClass; // Not always true } - public Class getReifiedClass() { + /** + * Gets a reified Ruby or Java class. + * To ensure a specific type, see {@link #getReifiedRubyClass()} or {@link #getReifiedJavaClass()} + */ + public Class getReifiedClass() { return reifiedClass; } - public static Class nearestReifiedClass(final RubyClass klass) { + /** + * Gets a reified Ruby class. Throws if this is a Java class + */ + public Class getReifiedRubyClass() { + if (reifiedClassJava == Boolean.TRUE) + // TODO: error type + throw runtime.newTypeError("Attempted to get a Ruby class for a Java class"); + else + return (Class) reifiedClass; + } + + /** + * Gets a reified Java class. Throws if this is a Ruby class + */ + public Class getReifiedJavaClass() { + if (reifiedClassJava == Boolean.FALSE) + // TODO: error type + throw runtime.newTypeError("Attempted to get a Java class for a Ruby class"); + else + return (Class) reifiedClass; + } + + public static Class nearestReifiedClass(final RubyClass klass) { RubyClass current = klass; do { - Class reified = current.getReifiedClass(); + Class reified = current.getReifiedClass(); if ( reified != null ) return reified; current = current.getSuperClass(); } @@ -1700,17 +2240,17 @@ public static Class nearestReifiedClass(final RubyClass k return null; } - public Map>>> getParameterAnnotations() { - if (parameterAnnotations == null) return Collections.EMPTY_MAP; - return parameterAnnotations; + public Map, Map>>> getParameterAnnotations() { + if (javaClassConfiguration == null || getClassConfig().parameterAnnotations == null) return Collections.EMPTY_MAP; + return javaClassConfiguration.parameterAnnotations; } - public synchronized void addParameterAnnotation(String method, int i, Class annoClass, Map value) { - if (parameterAnnotations == null) parameterAnnotations = new HashMap<>(8); - List>> paramList = parameterAnnotations.get(method); + public synchronized void addParameterAnnotation(String method, int i, Class annoClass, Map value) { + if (getClassConfig().parameterAnnotations == null) javaClassConfiguration.parameterAnnotations = new HashMap<>(8); + List,Map>> paramList = javaClassConfiguration.parameterAnnotations.get(method); if (paramList == null) { paramList = new ArrayList<>(i + 1); - parameterAnnotations.put(method, paramList); + javaClassConfiguration.parameterAnnotations.put(method, paramList); } if (paramList.size() < i + 1) { for (int j = paramList.size(); j < i + 1; j++) { @@ -1718,7 +2258,7 @@ public synchronized void addParameterAnnotation(String method, int i, Class anno } } if (annoClass != null && value != null) { - Map> annos = paramList.get(i); + Map, Map> annos = paramList.get(i); if (annos == null) { paramList.set(i, annos = new LinkedHashMap<>(4)); } @@ -1728,75 +2268,97 @@ public synchronized void addParameterAnnotation(String method, int i, Class anno } } - public Map>> getMethodAnnotations() { - if (methodAnnotations == null) return Collections.EMPTY_MAP; + public Map,Map>> getMethodAnnotations() { + if (javaClassConfiguration == null || getClassConfig().methodAnnotations == null) return Collections.EMPTY_MAP; - return methodAnnotations; + return javaClassConfiguration.methodAnnotations; } - public Map>> getFieldAnnotations() { - if (fieldAnnotations == null) return Collections.EMPTY_MAP; + public Map,Map>> getFieldAnnotations() { + if (javaClassConfiguration == null || getClassConfig().fieldAnnotations == null) return Collections.EMPTY_MAP; - return fieldAnnotations; + return javaClassConfiguration.fieldAnnotations; } - public synchronized void addMethodAnnotation(String methodName, Class annotation, Map fields) { - if (methodAnnotations == null) methodAnnotations = new HashMap<>(8); + public synchronized void addMethodAnnotation(String methodName, Class annotation, Map fields) { + if (getClassConfig().methodAnnotations == null) javaClassConfiguration.methodAnnotations = new HashMap<>(8); - Map> annos = methodAnnotations.get(methodName); + Map,Map> annos = javaClassConfiguration.methodAnnotations.get(methodName); if (annos == null) { - methodAnnotations.put(methodName, annos = new LinkedHashMap<>(4)); + javaClassConfiguration.methodAnnotations.put(methodName, annos = new LinkedHashMap<>(4)); } annos.put(annotation, fields); } - public synchronized void addFieldAnnotation(String fieldName, Class annotation, Map fields) { - if (fieldAnnotations == null) fieldAnnotations = new HashMap<>(8); + public synchronized void addFieldAnnotation(String fieldName, Class annotation, Map fields) { + if (getClassConfig().fieldAnnotations == null) javaClassConfiguration.fieldAnnotations = new HashMap<>(8); - Map> annos = fieldAnnotations.get(fieldName); + Map,Map> annos = javaClassConfiguration.fieldAnnotations.get(fieldName); if (annos == null) { - fieldAnnotations.put(fieldName, annos = new LinkedHashMap<>(4)); + javaClassConfiguration.fieldAnnotations.put(fieldName, annos = new LinkedHashMap<>(4)); } annos.put(annotation, fields); } + public Map[]> getMethodSignatures() { + if (javaClassConfiguration == null || getClassConfig().methodSignatures == null) return Collections.EMPTY_MAP; + + return javaClassConfiguration.methodSignatures.entrySet() + .stream() + .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().get(0))); + } + + public Map[]>> getAllMethodSignatures() { + if (javaClassConfiguration == null || getClassConfig().methodSignatures == null) return Collections.EMPTY_MAP; + + return javaClassConfiguration.methodSignatures; + } - public Map getMethodSignatures() { - if (methodSignatures == null) return Collections.EMPTY_MAP; + public Map> getFieldSignatures() { + if (javaClassConfiguration == null || getClassConfig().fieldSignatures == null) return Collections.EMPTY_MAP; - return methodSignatures; + return javaClassConfiguration.fieldSignatures; } - public Map getFieldSignatures() { - if (fieldSignatures == null) return Collections.EMPTY_MAP; + public synchronized void addMethodSignature(String methodName, Class[] types) { + if (getClassConfig().methodSignatures == null) javaClassConfiguration.methodSignatures = new HashMap<>(16); + + List[]> annos = javaClassConfiguration.methodSignatures.get(methodName); + if (annos == null) { + javaClassConfiguration.methodSignatures.put(methodName, annos = new ArrayList[]>(4)); + } - return fieldSignatures; + annos.add(types); } - public synchronized void addMethodSignature(String methodName, Class[] types) { - if (methodSignatures == null) methodSignatures = new HashMap<>(16); + public synchronized void addFieldSignature(String fieldName, Class type) { + if (getClassConfig().fieldSignatures == null) javaClassConfiguration.fieldSignatures = new LinkedHashMap<>(8); - methodSignatures.put(methodName, types); + javaClassConfiguration.fieldSignatures.put(fieldName, type); } - public synchronized void addFieldSignature(String fieldName, Class type) { - if (fieldSignatures == null) fieldSignatures = new LinkedHashMap<>(8); + public Map,Map> getClassAnnotations() { + if (javaClassConfiguration == null || getClassConfig().classAnnotations == null) return Collections.EMPTY_MAP; - fieldSignatures.put(fieldName, type); + return javaClassConfiguration.classAnnotations; } - public Map> getClassAnnotations() { - if (classAnnotations == null) return Collections.EMPTY_MAP; + public synchronized void addClassAnnotation(Class annotation, Map fields) { + if (getClassConfig().classAnnotations == null) javaClassConfiguration.classAnnotations = new LinkedHashMap<>(4); - return classAnnotations; + javaClassConfiguration.classAnnotations.put(annotation, fields); } - public synchronized void addClassAnnotation(Class annotation, Map fields) { - if (classAnnotations == null) classAnnotations = new LinkedHashMap<>(4); + public synchronized JavaClassConfiguration getClassConfig() { + if (javaClassConfiguration == null) javaClassConfiguration = new JavaClassConfiguration(); + + return javaClassConfiguration; + } - classAnnotations.put(annotation, fields); + public synchronized void setClassConfig(JavaClassConfiguration jcc) { + javaClassConfiguration = jcc; } @Override @@ -1808,7 +2370,7 @@ public T toJava(Class target) { Class javaClass = JavaClass.getJavaClassIfProxy(context, this); if (javaClass != null) return (T) javaClass; - Class reifiedClass = nearestReifiedClass(this); + Class reifiedClass = nearestReifiedClass(this); if ( reifiedClass != null ) return target.cast(reifiedClass); // should never fall through, since RubyObject has a reified class } @@ -2432,19 +2994,10 @@ public static CS_NAMES fromOrdinal(int ordinal) { private CallSite[] extraCallSites; - private Class reifiedClass; - - private Map>>> parameterAnnotations; - - private Map>> methodAnnotations; - - private Map>> fieldAnnotations; - - private Map methodSignatures; - - private Map fieldSignatures; - - private Map> classAnnotations; + private Class reifiedClass; + private Boolean reifiedClassJava; + + private JavaClassConfiguration javaClassConfiguration; /** A cached tuple of method, type, and generation for dumping */ private MarshalTuple cachedDumpMarshal = MarshalTuple.NULL_TUPLE; diff --git a/core/src/main/java/org/jruby/RubyModule.java b/core/src/main/java/org/jruby/RubyModule.java index 113bb0f54a9..ea8d9a271f8 100644 --- a/core/src/main/java/org/jruby/RubyModule.java +++ b/core/src/main/java/org/jruby/RubyModule.java @@ -1812,6 +1812,25 @@ public CacheEntry searchMethodEntryInner(String id) { return null; } + /** + * Searches for a method up until the superclass, but include modules. This is + * for Concrete java ctor initialization + * TODO: add a cache? + */ + public DynamicMethod searchMethodLateral(String id) { + // int token = generation; + // This flattens some of the recursion that would be otherwise be necessary. + // Used to recurse up the class hierarchy which got messy with prepend. + for (RubyModule module = this; module != null && (module == this || (module instanceof IncludedModuleWrapper)); module = module.getSuperClass()) { + // Only recurs if module is an IncludedModuleWrapper. + // This way only the recursion needs to be handled differently on + // IncludedModuleWrapper. + DynamicMethod method = module.searchMethodCommon(id); + if (method != null) return method.isNull() ? null : method; + } + return null; + } + // MRI: resolve_refined_method public CacheEntry resolveRefinedMethod(Map refinements, CacheEntry entry, String id, boolean cacheUndef) { if (entry != null && entry.method.isRefined()) { @@ -2482,6 +2501,8 @@ public IRubyObject initialize_copy(IRubyObject original) { syncConstants(originalModule); originalModule.cloneMethods(this); + + this.javaProxy = originalModule.javaProxy; return this; } diff --git a/core/src/main/java/org/jruby/compiler/impl/SkinnyMethodAdapter.java b/core/src/main/java/org/jruby/compiler/impl/SkinnyMethodAdapter.java index bb50db18f5d..6f354041fb7 100644 --- a/core/src/main/java/org/jruby/compiler/impl/SkinnyMethodAdapter.java +++ b/core/src/main/java/org/jruby/compiler/impl/SkinnyMethodAdapter.java @@ -73,17 +73,19 @@ public final class SkinnyMethodAdapter extends MethodVisitor { private final ClassVisitor cv; private final Label start; private final Label end; + private final String signature; private MethodVisitor method; private Printer printer; - public SkinnyMethodAdapter(ClassVisitor cv, int flags, String name, String signature, String something, String[] exceptions) { + public SkinnyMethodAdapter(ClassVisitor cv, int flags, String name, String signature, String genericTypeInformation, String[] exceptions) { super(ASM4); - setMethodVisitor(cv.visitMethod(flags, name, signature, something, exceptions)); + setMethodVisitor(cv.visitMethod(flags, name, signature, genericTypeInformation, exceptions)); this.cv = cv; this.name = name; this.start = new Label(); this.end = new Label(); + this.signature = signature; } public ClassVisitor getClassVisitor() { @@ -102,6 +104,10 @@ public void setMethodVisitor(MethodVisitor mv) { this.method = mv; } } + + public String getSignature() { + return signature; + } /** * Short-hand for specifying a set of aloads diff --git a/core/src/main/java/org/jruby/ext/ffi/ReifyingAllocator.java b/core/src/main/java/org/jruby/ext/ffi/ReifyingAllocator.java index c3c23fde941..6881731416d 100644 --- a/core/src/main/java/org/jruby/ext/ffi/ReifyingAllocator.java +++ b/core/src/main/java/org/jruby/ext/ffi/ReifyingAllocator.java @@ -10,10 +10,10 @@ import java.lang.reflect.InvocationTargetException; class ReifyingAllocator implements ObjectAllocator { - private final Class klass; - private final Constructor cons; + private final Class klass; + private final Constructor cons; - public ReifyingAllocator(Class klass) { + public ReifyingAllocator(Class klass) { this.klass = klass; try { this.cons = klass.getDeclaredConstructor(Ruby.class, RubyClass.class); @@ -24,8 +24,8 @@ public ReifyingAllocator(Class klass) { public IRubyObject allocate(Ruby runtime, RubyClass klazz) { try { - if (klazz.getReifiedClass() == this.klass) { - return (IRubyObject) cons.newInstance(runtime, klazz); + if (klazz.getReifiedRubyClass() == this.klass) { + return cons.newInstance(runtime, klazz); } reifyWithAncestors(klazz); @@ -44,10 +44,10 @@ private static void reifyWithAncestors(RubyClass klazz) { RubyClass realSuper = klazz.getSuperClass().getRealClass(); - if (realSuper.getReifiedClass() == null) reifyWithAncestors(realSuper); + if (realSuper.getReifiedRubyClass() == null) reifyWithAncestors(realSuper); synchronized (klazz) { klazz.reify(); - klazz.setAllocator(new ReifyingAllocator(klazz.getReifiedClass())); + klazz.setAllocator(new ReifyingAllocator(klazz.getReifiedRubyClass())); } } } diff --git a/core/src/main/java/org/jruby/internal/runtime/AbstractIRMethod.java b/core/src/main/java/org/jruby/internal/runtime/AbstractIRMethod.java index d16197cc4ab..2e6b159cc45 100644 --- a/core/src/main/java/org/jruby/internal/runtime/AbstractIRMethod.java +++ b/core/src/main/java/org/jruby/internal/runtime/AbstractIRMethod.java @@ -1,5 +1,33 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime; +import java.util.Collection; + import org.jruby.Ruby; import org.jruby.RubyModule; import org.jruby.compiler.Compilable; @@ -13,15 +41,15 @@ import org.jruby.parser.StaticScope; import org.jruby.runtime.ArgumentDescriptor; import org.jruby.runtime.Arity; +import org.jruby.runtime.Block; import org.jruby.runtime.PositionAware; import org.jruby.runtime.Signature; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; +import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.ivars.MethodData; import org.jruby.util.cli.Options; -import java.util.Collection; - public abstract class AbstractIRMethod extends DynamicMethod implements IRMethodArgs, PositionAware, Cloneable { protected final Signature signature; @@ -175,4 +203,12 @@ public boolean needsToFindImplementer() { // FIXME: This may stop working if we eliminate startup interp return !(irScope instanceof IRMethod && !irScope.getInterpreterContext().getFlags().contains(IRFlags.REQUIRES_CLASS)); } + /** + * Calls a split method (java constructor-invoked initialize) and returns the paused state. If + * this method doesn't have a super call, returns null without execution. + */ + public abstract SplitSuperState startSplitSuperCall(ThreadContext context, IRubyObject self, RubyModule klazz, + String name, IRubyObject[] args, Block block); + + public abstract void finishSplitCall(SplitSuperState state); } diff --git a/core/src/main/java/org/jruby/internal/runtime/InternalSplitState.java b/core/src/main/java/org/jruby/internal/runtime/InternalSplitState.java new file mode 100644 index 00000000000..f55a2499eaf --- /dev/null +++ b/core/src/main/java/org/jruby/internal/runtime/InternalSplitState.java @@ -0,0 +1,31 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime; + +public interface InternalSplitState { + +} diff --git a/core/src/main/java/org/jruby/internal/runtime/SplitSuperState.java b/core/src/main/java/org/jruby/internal/runtime/SplitSuperState.java new file mode 100644 index 00000000000..e6280efa930 --- /dev/null +++ b/core/src/main/java/org/jruby/internal/runtime/SplitSuperState.java @@ -0,0 +1,43 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime; + +import org.jruby.RubyArray; +import org.jruby.internal.runtime.methods.ExitableReturn; +import org.jruby.runtime.Block; + +public class SplitSuperState { + public final RubyArray callArrayArgs; + public final Block callBlockArgs; + public final T state; + + public SplitSuperState(ExitableReturn result, T state) { + callArrayArgs = result.getArguments(); + callBlockArgs = result.getBlock(); + this.state = state; + } +} diff --git a/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRMethod.java b/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRMethod.java index be33f915ee3..41b8b1abdb6 100644 --- a/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRMethod.java +++ b/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRMethod.java @@ -1,3 +1,29 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime.methods; import java.lang.invoke.MethodHandle; @@ -5,12 +31,16 @@ import org.jruby.RubyModule; import org.jruby.compiler.Compilable; import org.jruby.internal.runtime.AbstractIRMethod; +import org.jruby.internal.runtime.SplitSuperState; import org.jruby.ir.IRFlags; import org.jruby.ir.IRMethod; import org.jruby.ir.IRScope; +import org.jruby.ir.interpreter.ExitableInterpreterContext; +import org.jruby.ir.interpreter.InterpreterContext; import org.jruby.parser.StaticScope; import org.jruby.runtime.ArgumentDescriptor; import org.jruby.runtime.Block; +import org.jruby.runtime.DynamicScope; import org.jruby.runtime.Helpers; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Visibility; @@ -215,7 +245,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz return null; // not reached } } - + @Override public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { if (specificArity != 3) return call(context, self, clazz, name, new IRubyObject[] {arg0, arg1, arg2 }, Block.NULL_BLOCK); @@ -229,6 +259,69 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } } + // TODO: compile: + + @Override + public SplitSuperState startSplitSuperCall(ThreadContext context, IRubyObject self, + RubyModule clazz, String name, IRubyObject[] args, Block block) { + // TODO: check if IR method, or is it guaranteed? + InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor(); + if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this + + MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name); + + // TODO: JIT? + + ExitableReturn result = INTERPRET_METHOD(state, args, block); + + return new SplitSuperState<>(result, state); + } + + private ExitableReturn INTERPRET_METHOD(MethodSplitState state, IRubyObject[] args, Block block) { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + try { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + // TODO: explicit call protocol? + try { + this.preSplit(state.eic, state.context, state.self, state.name, block, state.implClass, state.scope); + return state.eic.getEngine().interpret(state.context, null, state.self, state.eic, state.state, + state.implClass, state.name, args, block); + } finally { + this.post(state.eic, state.context); + } + } finally { + ThreadContext.popBacktrace(state.context); + } + } + + @Override + public void finishSplitCall(SplitSuperState state) { + + // TODO: JIT? + + INTERPRET_METHOD((MethodSplitState) state.state, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); + } + + protected void post(InterpreterContext ic, ThreadContext context) { + // update call stacks (pop: ..) + context.popFrame(); + if (ic.popDynScope()) { + context.popScope(); + } + } + + // TODO: new method or make this pre? + protected void preSplit(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, + RubyModule implClass, DynamicScope scope) { + // update call stacks (push: frame, class, scope, etc.) + context.preMethodFrameOnly(implClass, name, self, block); + if (ic.pushNewDynScope()) { + context.pushScope(scope); + } + } + public boolean needsToFindImplementer() { return needsToFindImplementer; } diff --git a/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRNoProtocolMethod.java b/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRNoProtocolMethod.java index 432ecb0503a..3e5636625b3 100644 --- a/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRNoProtocolMethod.java +++ b/core/src/main/java/org/jruby/internal/runtime/methods/CompiledIRNoProtocolMethod.java @@ -1,9 +1,38 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime.methods; +import java.lang.invoke.MethodHandle; + import org.jruby.RubyModule; import org.jruby.internal.runtime.AbstractIRMethod; -import org.jruby.ir.IRFlags; -import org.jruby.ir.IRScope; +import org.jruby.internal.runtime.SplitSuperState; +import org.jruby.ir.IRMethod; +import org.jruby.ir.interpreter.ExitableInterpreterContext; import org.jruby.ir.interpreter.InterpreterContext; import org.jruby.parser.StaticScope; import org.jruby.runtime.ArgumentDescriptor; @@ -14,8 +43,6 @@ import org.jruby.runtime.Visibility; import org.jruby.runtime.builtin.IRubyObject; -import java.lang.invoke.MethodHandle; - public class CompiledIRNoProtocolMethod extends AbstractIRMethod { private final boolean needsDynamicScope; private final MethodHandle variable; @@ -96,4 +123,66 @@ public InterpreterContext ensureInstrsReady() { protected void printMethodIR() { // no-op } + // TODO: compile: + + @Override + public SplitSuperState startSplitSuperCall(ThreadContext context, IRubyObject self, + RubyModule clazz, String name, IRubyObject[] args, Block block) { + // TODO: check if IR method, or is it guaranteed? + InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor(); + if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this + + MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name); + + // TODO: JIT? + + ExitableReturn result = INTERPRET_METHOD(state, args, block); + + return new SplitSuperState<>(result, state); + } + + private ExitableReturn INTERPRET_METHOD(MethodSplitState state, IRubyObject[] args, Block block) { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + try { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + // TODO: explicit call protocol? + try { + this.preSplit(state.eic, state.context, state.self, state.name, block, state.implClass, state.scope); + return state.eic.getEngine().interpret(state.context, null, state.self, state.eic, state.state, + state.implClass, state.name, args, block); + } finally { + this.post(state.eic, state.context); + } + } finally { + ThreadContext.popBacktrace(state.context); + } + } + + @Override + public void finishSplitCall(SplitSuperState state) { + + // TODO: JIT? + + INTERPRET_METHOD((MethodSplitState) state.state, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); + } + + protected void post(InterpreterContext ic, ThreadContext context) { + // update call stacks (pop: ..) + context.popFrame(); + if (ic.popDynScope()) { + context.popScope(); + } + } + + // TODO: new method or make this pre? + protected void preSplit(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, + RubyModule implClass, DynamicScope scope) { + // update call stacks (push: frame, class, scope, etc.) + context.preMethodFrameOnly(implClass, name, self, block); + if (ic.pushNewDynScope()) { + context.pushScope(scope); + } + } } diff --git a/core/src/main/java/org/jruby/javasupport/proxy/InternalJavaProxyHelper.java b/core/src/main/java/org/jruby/internal/runtime/methods/ExitableReturn.java similarity index 73% rename from core/src/main/java/org/jruby/javasupport/proxy/InternalJavaProxyHelper.java rename to core/src/main/java/org/jruby/internal/runtime/methods/ExitableReturn.java index be986480323..2586dfeaba3 100644 --- a/core/src/main/java/org/jruby/javasupport/proxy/InternalJavaProxyHelper.java +++ b/core/src/main/java/org/jruby/internal/runtime/methods/ExitableReturn.java @@ -11,8 +11,6 @@ * implied. See the License for the specific language governing * rights and limitations under the License. * - * Copyright (C) 2006 Kresten Krab Thorup - * * 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"), @@ -26,22 +24,25 @@ * the terms of any one of the EPL, the GPL or the LGPL. ***** END LICENSE BLOCK *****/ -package org.jruby.javasupport.proxy; +package org.jruby.internal.runtime.methods; -/** - * Contains methods that are only called from generated code - * - * @author krab - */ -public class InternalJavaProxyHelper { +import org.jruby.RubyArray; +import org.jruby.runtime.Block; - public static JavaProxyClass initProxyClass(Class proxy) { - return new JavaProxyClass(proxy); +public class ExitableReturn { + public final RubyArray arguments; + public final Block block; + + public ExitableReturn(RubyArray arguments, Block block) { + this.arguments = arguments; + this.block = block; } - public static JavaProxyMethod initProxyMethod(JavaProxyClass proxyClass, - String name, String desc, boolean hasSuper) { - return proxyClass.initMethod(name, desc, hasSuper); + public RubyArray getArguments() { + return arguments; } + public Block getBlock() { + return block; + } } diff --git a/core/src/main/java/org/jruby/internal/runtime/methods/InterpretedIRMethod.java b/core/src/main/java/org/jruby/internal/runtime/methods/InterpretedIRMethod.java index acbb9c4afa1..d86baab2f44 100644 --- a/core/src/main/java/org/jruby/internal/runtime/methods/InterpretedIRMethod.java +++ b/core/src/main/java/org/jruby/internal/runtime/methods/InterpretedIRMethod.java @@ -1,3 +1,29 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime.methods; import java.io.ByteArrayOutputStream; @@ -5,7 +31,10 @@ import org.jruby.RubyModule; import org.jruby.compiler.Compilable; import org.jruby.internal.runtime.AbstractIRMethod; +import org.jruby.internal.runtime.SplitSuperState; +import org.jruby.ir.IRMethod; import org.jruby.ir.IRScope; +import org.jruby.ir.interpreter.ExitableInterpreterContext; import org.jruby.ir.interpreter.InterpreterContext; import org.jruby.ir.persistence.IRDumper; import org.jruby.ir.runtime.IRRuntimeHelpers; @@ -18,7 +47,8 @@ import org.jruby.util.log.LoggerFactory; /** - * Method for -X-C (interpreted only execution). See MixedModeIRMethod for inter/JIT method impl. + * Method for -X-C (interpreted only execution). See MixedModeIRMethod for + * inter/JIT method impl. */ public class InterpretedIRMethod extends AbstractIRMethod implements Compilable { private static final Logger LOG = LoggerFactory.getLogger(InterpretedIRMethod.class); @@ -32,7 +62,8 @@ public InterpretedIRMethod(IRScope method, Visibility visibility, RubyModule imp // regardless of compile mode (even when OFF full-builds are promoted) if (implementationClass.getRuntime().getInstanceConfig().getJitThreshold() == -1) setCallCount(-1); - // This is so profiled callsite can access the sites original method (callsites has IRScope in it). + // This is so profiled callsite can access the sites original method (callsites + // has IRScope in it). method.compilable = this; } @@ -44,7 +75,8 @@ protected void post(InterpreterContext ic, ThreadContext context) { } } - protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, RubyModule implClass) { + protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, + RubyModule implClass) { // update call stacks (push: frame, class, scope, etc.) context.preMethodFrameOnly(implClass, name, self, block); if (ic.pushNewDynScope()) { @@ -52,6 +84,16 @@ protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject sel } } + // TODO: new method or make this pre? + protected void preSplit(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, + RubyModule implClass, DynamicScope scope) { + // update call stacks (push: frame, class, scope, etc.) + context.preMethodFrameOnly(implClass, name, self, block); + if (ic.pushNewDynScope()) { + context.pushScope(scope); + } + } + @Override protected void printMethodIR() { ByteArrayOutputStream baos = IRDumper.printIR(getIRScope(), false, true); @@ -59,7 +101,8 @@ protected void printMethodIR() { } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, + Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); if (callCount >= 0) promoteToFullBuild(context); @@ -67,7 +110,8 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, + IRubyObject[] args) { if (IRRuntimeHelpers.isDebug()) doDebug(); if (callCount >= 0) promoteToFullBuild(context); @@ -75,7 +119,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject[] args, Block block) { + IRubyObject self, String name, IRubyObject[] args, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -111,7 +155,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, Block block) { + IRubyObject self, String name, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -131,7 +175,8 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); if (callCount >= 0) promoteToFullBuild(context); @@ -147,7 +192,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject arg1, Block block) { + IRubyObject self, String name, IRubyObject arg1, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -167,7 +212,8 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + IRubyObject arg1, Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); if (callCount >= 0) promoteToFullBuild(context); @@ -175,7 +221,8 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + IRubyObject arg1) { if (IRRuntimeHelpers.isDebug()) doDebug(); if (callCount >= 0) promoteToFullBuild(context); @@ -183,7 +230,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, Block block) { + IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -203,7 +250,8 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + IRubyObject arg1, IRubyObject arg2, Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); if (callCount >= 0) promoteToFullBuild(context); @@ -211,7 +259,8 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + IRubyObject arg1, IRubyObject arg2) { if (IRRuntimeHelpers.isDebug()) doDebug(); if (callCount >= 0) promoteToFullBuild(context); @@ -219,7 +268,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) { + IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -239,11 +288,56 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } + @Override + public SplitSuperState startSplitSuperCall(ThreadContext context, IRubyObject self, + RubyModule clazz, String name, IRubyObject[] args, Block block) { + // TODO: check if IR method, or is it guaranteed? + InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor(); + if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this + + MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name); + + if (IRRuntimeHelpers.isDebug()) doDebug(); // TODO? + + ExitableReturn result = INTERPRET_METHOD(state, args, block); + + return new SplitSuperState<>(result, state); + } + + private ExitableReturn INTERPRET_METHOD(MethodSplitState state, IRubyObject[] args, Block block) { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + try { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + // TODO: explicit call protocol? + try { + this.preSplit(state.eic, state.context, state.self, state.name, block, state.implClass, state.scope); + return state.eic.getEngine().interpret(state.context, null, state.self, state.eic, state.state, + state.implClass, state.name, args, block); + } finally { + this.post(state.eic, state.context); + } + } finally { + ThreadContext.popBacktrace(state.context); + } + } + + @Override + public void finishSplitCall(SplitSuperState state) { + if (IRRuntimeHelpers.isDebug()) doDebug(); // TODO? + + INTERPRET_METHOD((MethodSplitState) state.state, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); + } + protected void doDebug() { - // FIXME: This is printing out IRScope CFG but JIT may be active and it might not reflect - // currently executing. Move into JIT and into interp since they will be getting CFG from + // FIXME: This is printing out IRScope CFG but JIT may be active and it might + // not reflect + // currently executing. Move into JIT and into interp since they will be getting + // CFG from // different sources - // FIXME: This is only printing out CFG once. If we keep applying more passes then we + // FIXME: This is only printing out CFG once. If we keep applying more passes + // then we // will want to print out after those new passes. ensureInstrsReady(); LOG.info("Executing '" + getIRScope().getId() + "'"); @@ -259,7 +353,8 @@ public void completeBuild(InterpreterContext interpreterContext) { this.displayedCFG = false; } - // Unlike JIT in MixedMode this will always successfully build but if using executor pool it may take a while + // Unlike JIT in MixedMode this will always successfully build but if using + // executor pool it may take a while // and replace interpreterContext asynchronously. private void promoteToFullBuild(ThreadContext context) { tryJit(context, this); diff --git a/core/src/main/java/org/jruby/internal/runtime/methods/MethodSplitState.java b/core/src/main/java/org/jruby/internal/runtime/methods/MethodSplitState.java new file mode 100644 index 00000000000..ea88f2f0796 --- /dev/null +++ b/core/src/main/java/org/jruby/internal/runtime/methods/MethodSplitState.java @@ -0,0 +1,56 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime.methods; + +import org.jruby.RubyModule; +import org.jruby.internal.runtime.InternalSplitState; +import org.jruby.ir.interpreter.ExitableInterpreterContext; +import org.jruby.ir.interpreter.ExitableInterpreterEngineState; +import org.jruby.runtime.DynamicScope; +import org.jruby.runtime.ThreadContext; +import org.jruby.runtime.builtin.IRubyObject; + +class MethodSplitState implements InternalSplitState { + public final ExitableInterpreterContext eic; + public final ExitableInterpreterEngineState state; + public final ThreadContext context; + public final DynamicScope scope; + public final RubyModule implClass; + public final IRubyObject self; + public final String name; + + public MethodSplitState(ThreadContext context, ExitableInterpreterContext ic, RubyModule clazz, IRubyObject self, + String name) { + this.context = context; + this.eic = ic; + this.state = ic.getEngineState(); + this.scope = DynamicScope.newDynamicScope(ic.getStaticScope()); + this.implClass = clazz; + this.self = self; + this.name = name; + } +} diff --git a/core/src/main/java/org/jruby/internal/runtime/methods/MixedModeIRMethod.java b/core/src/main/java/org/jruby/internal/runtime/methods/MixedModeIRMethod.java index 2c577ca0d5e..b8b72b7fe03 100644 --- a/core/src/main/java/org/jruby/internal/runtime/methods/MixedModeIRMethod.java +++ b/core/src/main/java/org/jruby/internal/runtime/methods/MixedModeIRMethod.java @@ -1,3 +1,29 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.internal.runtime.methods; import java.io.ByteArrayOutputStream; @@ -5,7 +31,10 @@ import org.jruby.RubyModule; import org.jruby.compiler.Compilable; import org.jruby.internal.runtime.AbstractIRMethod; +import org.jruby.internal.runtime.SplitSuperState; +import org.jruby.ir.IRMethod; import org.jruby.ir.IRScope; +import org.jruby.ir.interpreter.ExitableInterpreterContext; import org.jruby.ir.interpreter.InterpreterContext; import org.jruby.ir.persistence.IRDumper; import org.jruby.ir.runtime.IRRuntimeHelpers; @@ -44,7 +73,8 @@ protected void post(InterpreterContext ic, ThreadContext context) { } } - protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, RubyModule implClass) { + protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, + RubyModule implClass) { // update call stacks (push: frame, class, scope, etc.) context.preMethodFrameOnly(implClass, name, self, block); if (ic.pushNewDynScope()) { @@ -52,6 +82,16 @@ protected void pre(InterpreterContext ic, ThreadContext context, IRubyObject sel } } + // TODO: new method or make this pre? + protected void preSplit(InterpreterContext ic, ThreadContext context, IRubyObject self, String name, Block block, + RubyModule implClass, DynamicScope scope) { + // update call stacks (push: frame, class, scope, etc.) + context.preMethodFrameOnly(implClass, name, self, block); + if (ic.pushNewDynScope()) { + context.pushScope(scope); + } + } + @Override protected void printMethodIR() { ByteArrayOutputStream baos = IRDumper.printIR(getIRScope(), false); @@ -59,10 +99,12 @@ protected void printMethodIR() { } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject[] args, + Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); - // try jit before checking actualMethod, so we use jitted version immediately if it's ready + // try jit before checking actualMethod, so we use jitted version immediately if + // it's ready if (callCount >= 0) tryJit(context, this); DynamicMethod jittedMethod = actualMethod; @@ -74,7 +116,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject[] args, Block block) { + IRubyObject self, String name, IRubyObject[] args, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -97,7 +139,8 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); - // try jit before checking actualMethod, so we use jitted version immediately if it's ready + // try jit before checking actualMethod, so we use jitted version immediately if + // it's ready if (callCount >= 0) tryJit(context, this); DynamicMethod jittedMethod = actualMethod; @@ -109,7 +152,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, Block block) { + IRubyObject self, String name, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -129,10 +172,12 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); - // try jit before checking actualMethod, so we use jitted version immediately if it's ready + // try jit before checking actualMethod, so we use jitted version immediately if + // it's ready if (callCount >= 0) tryJit(context, this); DynamicMethod jittedMethod = actualMethod; @@ -144,7 +189,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject arg1, Block block) { + IRubyObject self, String name, IRubyObject arg1, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -164,10 +209,12 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + IRubyObject arg1, Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); - // try jit before checking actualMethod, so we use jitted version immediately if it's ready + // try jit before checking actualMethod, so we use jitted version immediately if + // it's ready if (callCount >= 0) tryJit(context, this); DynamicMethod jittedMethod = actualMethod; @@ -179,7 +226,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, Block block) { + IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -199,10 +246,12 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } @Override - public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) { + public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg0, + IRubyObject arg1, IRubyObject arg2, Block block) { if (IRRuntimeHelpers.isDebug()) doDebug(); - // try jit before checking actualMethod, so we use jitted version immediately if it's ready + // try jit before checking actualMethod, so we use jitted version immediately if + // it's ready if (callCount >= 0) tryJit(context, this); DynamicMethod jittedMethod = actualMethod; @@ -214,7 +263,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz } private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext ic, RubyModule implClass, - IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) { + IRubyObject self, String name, IRubyObject arg1, IRubyObject arg2, IRubyObject arg3, Block block) { try { ThreadContext.pushBacktrace(context, name, ic.getFileName(), context.getLine()); @@ -234,11 +283,60 @@ private IRubyObject INTERPRET_METHOD(ThreadContext context, InterpreterContext i } + @Override + public SplitSuperState startSplitSuperCall(ThreadContext context, IRubyObject self, + RubyModule clazz, String name, IRubyObject[] args, Block block) { + // TODO: check if IR method, or is it guaranteed? + InterpreterContext ic = ((IRMethod) getIRScope()).builtInterperterContextForJavaConstructor(); + if (!(ic instanceof ExitableInterpreterContext)) return null; // no super call/can't split this + + MethodSplitState state = new MethodSplitState(context, (ExitableInterpreterContext) ic, clazz, self, name); + + if (IRRuntimeHelpers.isDebug()) doDebug(); // TODO? + + // TODO: JIT? + + ExitableReturn result = INTERPRET_METHOD(state, args, block); + + return new SplitSuperState<>(result, state); + } + + private ExitableReturn INTERPRET_METHOD(MethodSplitState state, IRubyObject[] args, Block block) { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + try { + ThreadContext.pushBacktrace(state.context, state.name, state.eic.getFileName(), state.context.getLine()); + + // TODO: explicit call protocol? + try { + this.preSplit(state.eic, state.context, state.self, state.name, block, state.implClass, state.scope); + return state.eic.getEngine().interpret(state.context, null, state.self, state.eic, state.state, + state.implClass, state.name, args, block); + } finally { + this.post(state.eic, state.context); + } + } finally { + ThreadContext.popBacktrace(state.context); + } + } + + @Override + public void finishSplitCall(SplitSuperState state) { + if (IRRuntimeHelpers.isDebug()) doDebug(); // TODO? + + // TODO: JIT? + + INTERPRET_METHOD((MethodSplitState) state.state, IRubyObject.NULL_ARRAY, Block.NULL_BLOCK); + } + private void doDebug() { - // FIXME: This is printing out IRScope CFG but JIT may be active and it might not reflect - // currently executing. Move into JIT and into interp since they will be getting CFG from + // FIXME: This is printing out IRScope CFG but JIT may be active and it might + // not reflect + // currently executing. Move into JIT and into interp since they will be getting + // CFG from // different sources - // FIXME: This is only printing out CFG once. If we keep applying more passes then we + // FIXME: This is only printing out CFG once. If we keep applying more passes + // then we // will want to print out after those new passes. ensureInstrsReady(); LOG.info("Executing '" + getIRScope().getId() + "'"); diff --git a/core/src/main/java/org/jruby/ir/IRMethod.java b/core/src/main/java/org/jruby/ir/IRMethod.java index 241c19bd11e..5afcc4c7b47 100644 --- a/core/src/main/java/org/jruby/ir/IRMethod.java +++ b/core/src/main/java/org/jruby/ir/IRMethod.java @@ -1,3 +1,29 @@ +/***** BEGIN LICENSE BLOCK ***** + * Version: EPL 2.0/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Eclipse Public + * License Version 2.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-v20.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.ir; import org.jruby.RubySymbol; @@ -6,19 +32,27 @@ import org.jruby.ast.InstVarNode; import org.jruby.ast.Node; import org.jruby.ast.visitor.AbstractNodeVisitor; +import org.jruby.ir.instructions.CallBase; import org.jruby.ir.instructions.GetFieldInstr; import org.jruby.ir.instructions.Instr; +import org.jruby.ir.instructions.JumpTargetInstr; +import org.jruby.ir.instructions.LabelInstr; import org.jruby.ir.instructions.PutFieldInstr; +import org.jruby.ir.interpreter.ExitableInterpreterContext; import org.jruby.ir.interpreter.InterpreterContext; +import org.jruby.ir.operands.Label; import org.jruby.ir.operands.LocalVariable; import org.jruby.ir.representations.BasicBlock; import org.jruby.parser.StaticScope; import org.jruby.runtime.ArgumentDescriptor; +import org.jruby.runtime.CallType; import org.jruby.runtime.ivars.MethodData; import org.jruby.util.ByteList; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class IRMethod extends IRScope { public final boolean isInstanceMethod; @@ -92,6 +126,69 @@ public InterpreterContext builtInterpreterContext() { return lazilyAcquireInterpreterContext(); } + /** + * initialize methods in reified Java types will try and dispatch to the Java base classes + * constructor when the Ruby in the initialize: + * + * a) The super call is still valid in terms of Java (e.g. you cannot access self/this before the super call + * b) We can detect the validity of 'a'. Limitations like super in all paths of branches is not supported (for now). + * + * In cases where no super exists or it is unsupported we will return a normal interpreter (and a warning when + * unsupported): + * + * @return appropriate interpretercontext + */ + public synchronized InterpreterContext builtInterperterContextForJavaConstructor() { + InterpreterContext interpreterContext = builtInterpreterContext(); + + if (usesSuper()) { // We know at least one super is in here somewhere + int ipc = 0; + int superIPC = -1; + CallBase superCall = null; + Map labels = new HashMap<>(); + List