Skip to content

Commit

Permalink
Showing 33 changed files with 751 additions and 220 deletions.
23 changes: 15 additions & 8 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -65,7 +65,6 @@
import org.jruby.parser.StaticScope;
import org.jruby.runtime.JavaSites;
import org.jruby.runtime.invokedynamic.InvokeDynamicSupport;
import org.jruby.runtime.opto.ConstantInvalidator;
import org.jruby.util.MRIRecursionGuard;
import org.jruby.util.StrptimeParser;
import org.jruby.util.StrptimeToken;
@@ -176,13 +175,10 @@
import java.lang.invoke.MethodHandle;
import java.lang.ref.WeakReference;
import java.net.BindException;
import java.net.PortUnreachableException;
import java.nio.channels.ClosedChannelException;
import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
@@ -3004,13 +3000,24 @@ public void loadExtension(String extName, BasicLibraryService extension, boolean

public void addBoundMethod(String className, String methodName, String rubyName) {
Map<String, String> javaToRuby = boundMethods.get(className);
if (javaToRuby == null) {
javaToRuby = new HashMap<String, String>();
boundMethods.put(className, javaToRuby);
}
if (javaToRuby == null) boundMethods.put(className, javaToRuby = new HashMap<>());
javaToRuby.put(methodName, rubyName);
}

public void addBoundMethodsPacked(String className, String packedTuples) {
String[] names = Helpers.SEMICOLON_PATTERN.split(packedTuples);
for (int i = 0; i < names.length; i += 2) {
addBoundMethod(className, names[i], names[i+1]);
}
}

public void addSimpleBoundMethodsPacked(String className, String packedNames) {
String[] names = Helpers.SEMICOLON_PATTERN.split(packedNames);
for (String name : names) {
addBoundMethod(className, name, name);
}
}

public Map<String, Map<String, String>> getBoundMethods() {
return boundMethods;
}
20 changes: 10 additions & 10 deletions core/src/main/java/org/jruby/RubyBasicObject.java
Original file line number Diff line number Diff line change
@@ -1744,33 +1744,33 @@ public IRubyObject send(ThreadContext context, IRubyObject[] args, Block block)
}

@JRubyMethod(name = "instance_eval",
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject instance_eval19(ThreadContext context, Block block) {
return specificEval(context, getInstanceEvalClass(), block, EvalType.INSTANCE_EVAL);
}
@JRubyMethod(name = "instance_eval",
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject instance_eval19(ThreadContext context, IRubyObject arg0, Block block) {
return specificEval(context, getInstanceEvalClass(), arg0, block, EvalType.INSTANCE_EVAL);
}
@JRubyMethod(name = "instance_eval",
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject instance_eval19(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
return specificEval(context, getInstanceEvalClass(), arg0, arg1, block, EvalType.INSTANCE_EVAL);
}
@JRubyMethod(name = "instance_eval",
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject instance_eval19(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
return specificEval(context, getInstanceEvalClass(), arg0, arg1, arg2, block, EvalType.INSTANCE_EVAL);
}

@JRubyMethod(name = "instance_exec", optional = 3, rest = true,
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject instance_exec19(ThreadContext context, IRubyObject[] args, Block block) {
if (!block.isGiven()) {
throw context.runtime.newLocalJumpErrorNoBlock();
8 changes: 4 additions & 4 deletions core/src/main/java/org/jruby/RubyKernel.java
Original file line number Diff line number Diff line change
@@ -788,8 +788,8 @@ public static RubyBinding binding(ThreadContext context, IRubyObject recv, Block
}

@JRubyMethod(name = "binding", module = true, visibility = PRIVATE,
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public static RubyBinding binding19(ThreadContext context, IRubyObject recv, Block block) {
return RubyBinding.newBinding(context.runtime, context.currentBinding());
}
@@ -1002,8 +1002,8 @@ public static IRubyObject eval(ThreadContext context, IRubyObject recv, IRubyObj
}

@JRubyMethod(name = "eval", required = 1, optional = 3, module = true, visibility = PRIVATE,
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public static IRubyObject eval19(ThreadContext context, IRubyObject recv, IRubyObject[] args, Block block) {
return evalCommon(context, recv, args, evalBinding19);
}
99 changes: 57 additions & 42 deletions core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
@@ -76,6 +76,7 @@
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.internal.runtime.methods.Framing;
import org.jruby.internal.runtime.methods.JavaMethod;
import org.jruby.internal.runtime.methods.NativeCallMethod;
import org.jruby.internal.runtime.methods.ProcMethod;
import org.jruby.internal.runtime.methods.Scoping;
import org.jruby.internal.runtime.methods.SynchronizedDynamicMethod;
@@ -120,7 +121,6 @@
import static org.jruby.anno.FrameField.BLOCK;
import static org.jruby.anno.FrameField.CLASS;
import static org.jruby.anno.FrameField.FILENAME;
import static org.jruby.anno.FrameField.JUMPTARGET;
import static org.jruby.anno.FrameField.LASTLINE;
import static org.jruby.anno.FrameField.LINE;
import static org.jruby.anno.FrameField.METHODNAME;
@@ -1645,29 +1645,44 @@ public synchronized void defineAliases(List<String> aliases, String oldName) {
private DynamicMethod searchForAliasMethod(Ruby runtime, String name) {
DynamicMethod method = deepMethodSearch(name, runtime);

// if (method instanceof JavaMethod) {
// // JRUBY-2435: Aliasing eval and other "special" methods should display a warning
// // We warn because we treat certain method names as "special" for purposes of
// // optimization. Hopefully this will be enough to convince people not to alias
// // them.
// CallConfiguration callerReq = ((JavaMethod)method).getCallerRequirement();
//
// if (callerReq.framing() != Framing.None ||
// callerReq.scoping() != Scoping.None) {String baseName = getBaseName();
// char refChar = '#';
// String simpleName = getSimpleName();
//
// if (baseName == null && this instanceof MetaClass) {
// IRubyObject attached = ((MetaClass)this).getAttached();
// if (attached instanceof RubyModule) {
// simpleName = ((RubyModule)attached).getSimpleName();
// refChar = '.';
// }
// }
//
// runtime.getWarnings().warn(simpleName + refChar + name + " accesses caller's state and should not be aliased");
// }
// }
if (method instanceof NativeCallMethod) {
// JRUBY-2435: Aliasing eval and other "special" methods should display a warning
// We warn because we treat certain method names as "special" for purposes of
// optimization. Hopefully this will be enough to convince people not to alias
// them.

DynamicMethod.NativeCall nativeCall = ((NativeCallMethod) method).getNativeCall();

// native-backed but not a direct call, ok
if (nativeCall == null) return method;

Method javaMethod = nativeCall.getMethod();
JRubyMethod anno = javaMethod.getAnnotation(JRubyMethod.class);

if (anno == null) return method;

if (anno.reads().length > 0 || anno.writes().length > 0) {

MethodIndex.addMethodReadFields(name, anno.reads());
MethodIndex.addMethodWriteFields(name, anno.writes());

if (runtime.isVerbose()) {
String baseName = getBaseName();
char refChar = '#';
String simpleName = getSimpleName();

if (baseName == null && this instanceof MetaClass) {
IRubyObject attached = ((MetaClass) this).getAttached();
if (attached instanceof RubyModule) {
simpleName = ((RubyModule) attached).getSimpleName();
refChar = '.';
}
}

runtime.getWarnings().warning(simpleName + refChar + name + " accesses caller method's state and should not be aliased");
}
}
}

return method;
}
@@ -2861,26 +2876,26 @@ public RubyModule undef_method(ThreadContext context, IRubyObject[] args) {
}

@JRubyMethod(name = {"module_eval", "class_eval"},
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject module_eval(ThreadContext context, Block block) {
return specificEval(context, this, block, EvalType.MODULE_EVAL);
}
@JRubyMethod(name = {"module_eval", "class_eval"},
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject module_eval(ThreadContext context, IRubyObject arg0, Block block) {
return specificEval(context, this, arg0, block, EvalType.MODULE_EVAL);
}
@JRubyMethod(name = {"module_eval", "class_eval"},
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject module_eval(ThreadContext context, IRubyObject arg0, IRubyObject arg1, Block block) {
return specificEval(context, this, arg0, arg1, block, EvalType.MODULE_EVAL);
}
@JRubyMethod(name = {"module_eval", "class_eval"},
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject module_eval(ThreadContext context, IRubyObject arg0, IRubyObject arg1, IRubyObject arg2, Block block) {
return specificEval(context, this, arg0, arg1, arg2, block, EvalType.MODULE_EVAL);
}
@@ -2890,8 +2905,8 @@ public IRubyObject module_eval(ThreadContext context, IRubyObject[] args, Block
}

@JRubyMethod(name = {"module_exec", "class_exec"},
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject module_exec(ThreadContext context, Block block) {
if (block.isGiven()) {
return yieldUnder(context, this, IRubyObject.NULL_ARRAY, block, EvalType.MODULE_EVAL);
@@ -2901,8 +2916,8 @@ public IRubyObject module_exec(ThreadContext context, Block block) {
}

@JRubyMethod(name = {"module_exec", "class_exec"}, rest = true,
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE})
reads = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE},
writes = {LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, CLASS, FILENAME, SCOPE})
public IRubyObject module_exec(ThreadContext context, IRubyObject[] args, Block block) {
if (block.isGiven()) {
return yieldUnder(context, this, args, block, EvalType.MODULE_EVAL);
@@ -4465,14 +4480,14 @@ private static void define(RubyModule module, JavaMethodDescriptor desc, final S
CallConfiguration needs = CallConfiguration.valueOf(AnnotationHelper.getCallerCallConfigNameByAnno(jrubyMethod));

if (needs.framing() == Framing.Full) {
Collection<String> frameAwareMethods = new ArrayList<>(4); // added to a Set - thus no need for another Set
AnnotationHelper.addMethodNamesToSet(frameAwareMethods, simpleName, names, aliases);
MethodIndex.FRAME_AWARE_METHODS.addAll(frameAwareMethods);
Map<String, JRubyMethod> frameAwareMethods = new HashMap<>(4); // added to a Set - thus no need for another Set
AnnotationHelper.addMethodNamesToMap(frameAwareMethods, null, simpleName, names, aliases);
MethodIndex.FRAME_AWARE_METHODS.addAll(frameAwareMethods.keySet());
}
if (needs.scoping() == Scoping.Full) {
Collection<String> scopeAwareMethods = new ArrayList<>(4); // added to a Set - thus no need for another Set
AnnotationHelper.addMethodNamesToSet(scopeAwareMethods, simpleName, names, aliases);
MethodIndex.SCOPE_AWARE_METHODS.addAll(scopeAwareMethods);
Map<String, JRubyMethod> scopeAwareMethods = new HashMap<>(4); // added to a Set - thus no need for another Set
AnnotationHelper.addMethodNamesToMap(scopeAwareMethods, null, simpleName, names, aliases);
MethodIndex.SCOPE_AWARE_METHODS.addAll(scopeAwareMethods.keySet());
}

RubyModule singletonClass;
128 changes: 95 additions & 33 deletions core/src/main/java/org/jruby/anno/AnnotationBinder.java
Original file line number Diff line number Diff line change
@@ -36,15 +36,14 @@
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.*;
import javax.lang.model.type.TypeMirror;
@@ -159,8 +158,8 @@ public void processType(TypeElement cd) {
Map<CharSequence, List<ExecutableElement>> annotatedMethods = new HashMap<>();
Map<CharSequence, List<ExecutableElement>> staticAnnotatedMethods = new HashMap<>();

Set<String> frameAwareMethods = new HashSet<>(4, 1);
Set<String> scopeAwareMethods = new HashSet<>(4, 1);
Map<Set<FrameField>, List<String>> readGroups = new HashMap<>();
Map<Set<FrameField>, List<String>> writeGroups = new HashMap<>();

int methodCount = 0;
for (ExecutableElement method : ElementFilter.methodsIn(cd.getEnclosedElements())) {
@@ -199,20 +198,7 @@ public void processType(TypeElement cd) {
methodDescs.add(method);

// check for frame field reads or writes
boolean frame = false;
boolean scope = false;

for (FrameField field : anno.reads()) {
frame |= field.needsFrame();
scope |= field.needsScope();
}
for (FrameField field : anno.writes()) {
frame |= field.needsFrame();
scope |= field.needsScope();
}

if (frame) AnnotationHelper.addMethodNamesToSet(frameAwareMethods, anno, method.getSimpleName().toString());
if (scope) AnnotationHelper.addMethodNamesToSet(scopeAwareMethods, anno, method.getSimpleName().toString());
AnnotationHelper.groupFrameFields(readGroups, writeGroups, anno, method.getSimpleName().toString());
}

if (methodCount == 0) {
@@ -222,28 +208,74 @@ public void processType(TypeElement cd) {

classNames.add(getActualQualifiedName(cd));

List<ExecutableElement> simpleNames = new ArrayList<>();
Map<CharSequence, List<ExecutableElement>> complexNames = new HashMap<>();

processMethodDeclarations(staticAnnotatedMethods);
for (Map.Entry<CharSequence, List<ExecutableElement>> entry : staticAnnotatedMethods.entrySet()) {
ExecutableElement decl = entry.getValue().get(0);
if (!decl.getAnnotation(JRubyMethod.class).omit()) addCoreMethodMapping(entry.getKey(), decl, out);
JRubyMethod anno = decl.getAnnotation(JRubyMethod.class);

if (anno.omit()) continue;

CharSequence rubyName = entry.getKey();

if (decl.getSimpleName().equals(rubyName)) {
simpleNames.add(decl);
continue;
}

List<ExecutableElement> complex = complexNames.get(rubyName);
if (complex == null) complexNames.put(rubyName, complex = new ArrayList<ExecutableElement>());
complex.add(decl);
}

processMethodDeclarations(annotatedMethods);
for (Map.Entry<CharSequence, List<ExecutableElement>> entry : annotatedMethods.entrySet()) {
ExecutableElement decl = entry.getValue().get(0);
if (!decl.getAnnotation(JRubyMethod.class).omit()) addCoreMethodMapping(entry.getKey(), decl, out);
JRubyMethod anno = decl.getAnnotation(JRubyMethod.class);

if (anno.omit()) continue;

CharSequence rubyName = entry.getKey();

if (decl.getSimpleName().equals(rubyName) && decl.getAnnotation(JRubyMethod.class).name().length <= 1) {
simpleNames.add(decl);
continue;
}

List<ExecutableElement> complex = complexNames.get(rubyName);
if (complex == null) complexNames.put(rubyName, complex = new ArrayList<ExecutableElement>());
complex.add(decl);
}

addCoreMethodMapping(cd, complexNames);

addSimpleMethodMappings(cd, simpleNames);

out.println(" }");

// write out a static initializer for frame names, so it only fires once
out.println(" static {");
if (!frameAwareMethods.isEmpty()) {
out.println(" MethodIndex.addFrameAwareMethods(" + join(frameAwareMethods) + ");");

if (!readGroups.isEmpty()) {
for (Map.Entry<Set<FrameField>, List<String>> reads : readGroups.entrySet()) {
Set<FrameField> key = reads.getKey();
FrameField[] frameFields = key.toArray(new FrameField[key.size()]);

out.println(" MethodIndex.addMethodReadFieldsPacked(" + FrameField.pack(frameFields) + ", \"" + join(reads.getValue()) + "\");");
}
}
if (!scopeAwareMethods.isEmpty()) {
out.println(" MethodIndex.addScopeAwareMethods(" + join(scopeAwareMethods) + ");");

if (!writeGroups.isEmpty()) {
for (Map.Entry<Set<FrameField>, List<String>> writes : writeGroups.entrySet()) {
Set<FrameField> key = writes.getKey();
FrameField[] frameFields = key.toArray(new FrameField[key.size()]);

out.println(" MethodIndex.addMethodWriteFieldsPacked(" + FrameField.pack(frameFields) + ", \"" + join(writes.getValue()) + "\");");
}
}

out.println(" }");

out.println("}");
@@ -263,11 +295,9 @@ public void processType(TypeElement cd) {

private static StringBuilder join(final Iterable<String> names) {
final StringBuilder str = new StringBuilder();
boolean first = true;
for (String name : names) {
if (!first) str.append(',');
first = false;
str.append('"').append(name).append('"');
if (str.length() > 0) str.append(';');
str.append(name);
}
return str;
}
@@ -378,14 +408,46 @@ public void processMethodDeclarationMulti(ExecutableElement method) {
}
}

private void addCoreMethodMapping(CharSequence rubyName, ExecutableElement decl, PrintStream out) {
private void addCoreMethodMapping(TypeElement cls, Map<CharSequence, List<ExecutableElement>> complexNames) {
StringBuilder encoded = new StringBuilder();

for (Map.Entry<CharSequence, List<ExecutableElement>> entry : complexNames.entrySet()) {

for (Iterator<ExecutableElement> iterator = entry.getValue().iterator(); iterator.hasNext(); ) {
if (encoded.length() > 0) encoded.append(";");

ExecutableElement elt = iterator.next();
encoded
.append(elt.getSimpleName())
.append(";")
.append(entry.getKey());
}
}

if (encoded.length() == 0) return;

out.println(new StringBuilder(50)
.append(" runtime.addBoundMethod(")
.append('"').append(((TypeElement)decl.getEnclosingElement()).getQualifiedName()).append('"')
.append(" runtime.addBoundMethodsPacked(")
.append('"').append(cls.getQualifiedName()).append('"')
.append(',')
.append('"').append(decl.getSimpleName()).append('"')
.append('"').append(encoded).append('"')
.append(");").toString());
}

private void addSimpleMethodMappings(TypeElement cls, List<ExecutableElement> simpleNames) {
StringBuilder encoded = new StringBuilder();
for (ExecutableElement elt : simpleNames) {
if (encoded.length() > 0) encoded.append(";");
encoded.append(elt.getSimpleName());
}

if (encoded.length() == 0) return;

out.println(new StringBuilder(50)
.append(" runtime.addSimpleBoundMethodsPacked(")
.append('"').append(cls.getQualifiedName()).append('"')
.append(',')
.append('"').append(rubyName).append('"')
.append('"').append(encoded).append('"')
.append(");").toString());
}

45 changes: 37 additions & 8 deletions core/src/main/java/org/jruby/anno/AnnotationHelper.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package org.jruby.anno;

import java.util.Collection;
import javax.lang.model.element.ExecutableElement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
@@ -15,19 +20,19 @@ public class AnnotationHelper {

private AnnotationHelper() { /* no instances */ }

public static void addMethodNamesToSet(Set<String> set, JRubyMethod jrubyMethod, String simpleName) {
addMethodNamesToSet(set, simpleName, jrubyMethod.name(), jrubyMethod.alias());
public static void addMethodNamesToMap(Map<String, JRubyMethod> map, JRubyMethod jrubyMethod, String simpleName) {
addMethodNamesToMap(map, jrubyMethod, simpleName, jrubyMethod.name(), jrubyMethod.alias());
}

public static void addMethodNamesToSet(final Collection<String> set, final String simpleName,
final String[] names, final String[] aliases) {
if ( names.length == 0 ) set.add(simpleName);
public static void addMethodNamesToMap(final Map<String, JRubyMethod> map, JRubyMethod anno, final String simpleName,
final String[] names, final String[] aliases) {
if ( names.length == 0 ) map.put(simpleName, anno);
else {
for ( String name : names ) set.add(name);
for ( String name : names ) map.put(name, anno);
}

if ( aliases.length > 0 ) {
for ( String alias : aliases ) set.add(alias);
for ( String alias : aliases ) map.put(alias, anno);
}
}

@@ -70,5 +75,29 @@ public static String getCallConfigName(boolean frame, boolean scope) {
}
return scope ? "FrameNoneScopeFull" : "FrameNoneScopeNone";
}

public static void groupFrameFields(Map<Set<FrameField>, List<String>> readGroups, Map<Set<FrameField>, List<String>> writeGroups, JRubyMethod anno, String simpleName) {
if (anno.reads().length > 0) {
Set<FrameField> reads = new HashSet<>(Arrays.asList(anno.reads()));
List<String> nameList = readGroups.get(reads);
if (nameList == null) readGroups.put(reads, nameList = new ArrayList<>());
if (anno.name().length == 0) {
nameList.add(simpleName);
} else {
nameList.addAll(Arrays.asList(anno.name()));
}
}

if (anno.writes().length > 0) {
Set<FrameField> writes = new HashSet<>(Arrays.asList(anno.writes()));
List<String> nameList = writeGroups.get(writes);
if (nameList == null) writeGroups.put(writes, nameList = new ArrayList<>());
if (anno.name().length == 0) {
nameList.add(simpleName);
} else {
nameList.addAll(Arrays.asList(anno.name()));
}
}
}
}

56 changes: 54 additions & 2 deletions core/src/main/java/org/jruby/anno/FrameField.java
Original file line number Diff line number Diff line change
@@ -28,8 +28,21 @@
***** END LICENSE BLOCK *****/
package org.jruby.anno;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

public enum FrameField {
LASTLINE, BACKREF, VISIBILITY, BLOCK, SELF, METHODNAME, LINE, JUMPTARGET, CLASS, FILENAME, SCOPE;
LASTLINE,
BACKREF,
VISIBILITY,
BLOCK,
SELF,
METHODNAME,
LINE,
CLASS,
FILENAME,
SCOPE;

public boolean needsFrame() {
switch (this) {
@@ -39,15 +52,54 @@ public boolean needsFrame() {
case BLOCK:
case SELF:
case METHODNAME:
case JUMPTARGET:
case CLASS:
return true;
default:
return false;
}
}

public static boolean needsFrame(int bits) {
return (bits &
(LASTLINE.bit |
BACKREF.bit |
VISIBILITY.bit |
BLOCK.bit |
SELF.bit |
METHODNAME.bit |
CLASS.bit)) != 0;
}

public static boolean needsScope(int bits) {
return (bits & SCOPE.bit) != 0;
}

private final int bit;

FrameField() {
this.bit = 1 << ordinal();
}

public boolean needsScope() {
return this == SCOPE;
}

public static int pack(FrameField[] frameFields) {
int bits = 0;
for (FrameField frameField : frameFields) {
bits |= frameField.bit;
}
return bits;
}

public static Set<FrameField> unpack(int bits) {
Set<FrameField> frameFields = Collections.EMPTY_SET;
for (FrameField frameField : values()) {
if ((bits & frameField.bit) != 0) {
if (frameFields == Collections.EMPTY_SET) frameFields = new HashSet<>();
frameFields.add(frameField);
}
}
return frameFields;
}
}
158 changes: 108 additions & 50 deletions core/src/main/java/org/jruby/anno/IndyBinder.java
Original file line number Diff line number Diff line change
@@ -56,11 +56,10 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import static org.jruby.util.CodegenUtils.*;
import static org.objectweb.asm.Opcodes.*;
@@ -182,8 +181,8 @@ public void processType(TypeElement cd) {
Map<CharSequence, List<ExecutableElement>> annotatedMethods = new HashMap<>();
Map<CharSequence, List<ExecutableElement>> staticAnnotatedMethods = new HashMap<>();

Set<String> frameAwareMethods = null; // lazy init - there's usually none
Set<String> scopeAwareMethods = null; // lazy init - there's usually none
Map<Set<FrameField>, List<String>> readGroups = null; // lazy init - there's usually none
Map<Set<FrameField>, List<String>> writeGroups = null; // lazy init - there's usually none

int methodCount = 0;
for (ExecutableElement method : ElementFilter.methodsIn(cd.getEnclosedElements())) {
@@ -222,25 +221,7 @@ public void processType(TypeElement cd) {
methodDescs.add(method);

// check for caller frame field reads or writes
boolean frame = false;
boolean scope = false;
for (FrameField field : anno.reads()) {
frame |= field.needsFrame();
scope |= field.needsScope();
}
for (FrameField field : anno.writes()) {
frame |= field.needsFrame();
scope |= field.needsScope();
}

if (frame) {
if (frameAwareMethods == null) frameAwareMethods = new HashSet<>(4, 1);
AnnotationHelper.addMethodNamesToSet(frameAwareMethods, method.getSimpleName().toString(), names, anno.alias());
}
if (scope) {
if (scopeAwareMethods == null) scopeAwareMethods = new HashSet<>(4, 1);
AnnotationHelper.addMethodNamesToSet(scopeAwareMethods, method.getSimpleName().toString(), names, anno.alias());
}
AnnotationHelper.groupFrameFields(readGroups, writeGroups, anno, method.getSimpleName().toString());
}

if (methodCount == 0) {
@@ -251,17 +232,52 @@ public void processType(TypeElement cd) {
classNames.add(getActualQualifiedName(cd));

processMethodDeclarations(staticAnnotatedMethods);

List<ExecutableElement> simpleNames = new ArrayList<>();
Map<CharSequence, List<ExecutableElement>> complexNames = new HashMap<>();

for (Map.Entry<CharSequence, List<ExecutableElement>> entry : staticAnnotatedMethods.entrySet()) {
ExecutableElement decl = entry.getValue().get(0);
if (!decl.getAnnotation(JRubyMethod.class).omit()) addCoreMethodMapping(entry.getKey(), decl);
JRubyMethod anno = decl.getAnnotation(JRubyMethod.class);

if (anno.omit()) continue;

CharSequence rubyName = entry.getKey();

if (decl.getSimpleName().equals(rubyName) && anno.name().length <= 1) {
simpleNames.add(decl);
continue;
}

List<ExecutableElement> complex = complexNames.get(rubyName);
if (complex == null) complexNames.put(rubyName, complex = new ArrayList<ExecutableElement>());
complex.add(decl);
}

processMethodDeclarations(annotatedMethods);

for (Map.Entry<CharSequence, List<ExecutableElement>> entry : annotatedMethods.entrySet()) {
ExecutableElement decl = entry.getValue().get(0);
if (!decl.getAnnotation(JRubyMethod.class).omit()) addCoreMethodMapping(entry.getKey(), decl);
JRubyMethod anno = decl.getAnnotation(JRubyMethod.class);

if (anno.omit()) continue;

CharSequence rubyName = entry.getKey();

if (decl.getSimpleName().equals(rubyName) && anno.name().length <= 1) {
simpleNames.add(decl);
continue;
}

List<ExecutableElement> complex = complexNames.get(rubyName);
if (complex == null) complexNames.put(rubyName, complex = new ArrayList<ExecutableElement>());
complex.add(decl);
}

addCoreMethodMapping(cd, complexNames);

addSimpleMethodMappings(cd, simpleNames);

mv.voidreturn();
mv.end();

@@ -270,29 +286,40 @@ public void processType(TypeElement cd) {

mv.start();

if (frameAwareMethods != null && ! frameAwareMethods.isEmpty()) {
mv.ldc(frameAwareMethods.size());
mv.anewarray("java/lang/String");
int index = 0;
for (CharSequence name : frameAwareMethods) {
mv.dup();
mv.ldc(index++);
mv.ldc(name);
mv.aastore();
if (!readGroups.isEmpty()) {
for (Map.Entry<Set<FrameField>, List<String>> reads : readGroups.entrySet()) {
Set<FrameField> key = reads.getKey();
FrameField[] frameFields = key.toArray(new FrameField[key.size()]);

mv.pushInt(FrameField.pack(frameFields));

StringBuilder builder = new StringBuilder();
for (CharSequence name : reads.getValue()) {
if (builder.length() > 0) builder.append(";");
builder.append(name);
}

mv.ldc(builder.toString());
mv.invokestatic("org/jruby/runtime/MethodIndex", "addMethodReadFields", "(ILjava/lang/String;)V");
}
mv.invokestatic("org/jruby/runtime/MethodIndex", "addFrameAwareMethods", "([Ljava/lang/String;)V");
}
if (scopeAwareMethods != null && ! scopeAwareMethods.isEmpty()) {
mv.ldc(frameAwareMethods.size());
mv.anewarray("java/lang/String");
int index = 0;
for (CharSequence name : scopeAwareMethods) {
mv.dup();
mv.ldc(index++);
mv.ldc(name);
mv.aastore();
if (!writeGroups.isEmpty()) {
for (Map.Entry<Set<FrameField>, List<String>> writes : readGroups.entrySet()) {
Set<FrameField> key = writes.getKey();
FrameField[] frameFields = key.toArray(new FrameField[key.size()]);

mv.pushInt(FrameField.pack(frameFields));

StringBuilder builder = new StringBuilder();
for (CharSequence name : writes.getValue()) {
if (builder.length() > 0) builder.append(";");
builder.append(name);
}

mv.ldc(builder.toString());

mv.invokestatic("org/jruby/runtime/MethodIndex", "addMethodWriteFields", "(I[Ljava/lang/String;)V");
}
mv.invokestatic("org/jruby/runtime/MethodIndex", "addScopeAwareMethods", "([Ljava/lang/String;)V");
}

mv.voidreturn();
@@ -451,12 +478,43 @@ public void adaptHandle(ExecutableElementDescriptor executableElementDescriptor,
mv.invokestatic("org/jruby/internal/runtime/methods/InvokeDynamicMethodFactory", "adaptHandle", Method.getMethod("java.util.concurrent.Callable adaptHandle(java.lang.invoke.MethodHandle, org.jruby.Ruby, int, int, int, boolean, java.lang.String, java.lang.Class, boolean, boolean, boolean, boolean, org.jruby.RubyModule)").getDescriptor());
}

private void addCoreMethodMapping(CharSequence rubyName, ExecutableElement decl) {
private void addCoreMethodMapping(TypeElement cls, Map<CharSequence, List<ExecutableElement>> complexNames) {
StringBuilder encoded = new StringBuilder();

for (Map.Entry<CharSequence, List<ExecutableElement>> entry : complexNames.entrySet()) {

for (Iterator<ExecutableElement> iterator = entry.getValue().iterator(); iterator.hasNext(); ) {
if (encoded.length() > 0) encoded.append(";");

ExecutableElement elt = iterator.next();
encoded
.append(elt.getSimpleName())
.append(";")
.append(entry.getKey());
}
}

if (encoded.length() == 0) return;

mv.aload(RUNTIME);
mv.ldc(cls.getQualifiedName().toString());
mv.ldc(encoded.toString());
mv.invokevirtual("org/jruby/Ruby", "addBoundMethodsPacked", "(Ljava/lang/String;Ljava/lang/String;)V");
}

private void addSimpleMethodMappings(TypeElement cls, List<ExecutableElement> simpleNames) {
StringBuilder encoded = new StringBuilder();
for (ExecutableElement elt : simpleNames) {
if (encoded.length() > 0) encoded.append(";");
encoded.append(elt.getSimpleName());
}

if (encoded.length() == 0) return;

mv.aload(RUNTIME);
mv.ldc(((TypeElement) decl.getEnclosingElement()).getQualifiedName().toString());
mv.ldc(decl.getSimpleName().toString());
mv.ldc(rubyName.toString());
mv.invokevirtual("org/jruby/Ruby", "addBoundMethod", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
mv.ldc(cls.getSimpleName().toString());
mv.ldc(encoded);
mv.invokevirtual("org/jruby/Ruby", "addSimpleBoundMethodsPacked", "(Ljava/lang/String;Ljava/lang/String;)V");
}

private static CharSequence getActualQualifiedName(TypeElement td) {
Original file line number Diff line number Diff line change
@@ -408,6 +408,7 @@ public static class NativeCall {
private final Class[] nativeSignature;
private final boolean statik;
private final boolean java;
private Method reflected;

public NativeCall(Class nativeTarget, String nativeName, Class nativeReturn, Class[] nativeSignature, boolean statik) {
this(nativeTarget, nativeName, nativeReturn, nativeSignature, statik, false);
@@ -460,8 +461,10 @@ public boolean hasBlock() {
* @return the reflected method corresponding to this NativeCall
*/
public Method getMethod() {
Method reflected = this.reflected;
if (reflected != null) return reflected;
try {
return nativeTarget.getDeclaredMethod(nativeName, nativeSignature);
return this.reflected = nativeTarget.getDeclaredMethod(nativeName, nativeSignature);
} catch (Exception e) {
throw new RuntimeException(e);
}
Original file line number Diff line number Diff line change
@@ -294,6 +294,12 @@ private void promoteToFullBuild(ThreadContext context) {
if (runtime.isBooting() && !Options.JIT_KERNEL.load()) return; // don't Promote to full build during runtime boot

if (callCount++ >= Options.JIT_THRESHOLD.load()) runtime.getJITCompiler().buildThresholdReached(context, this);

if (Options.IR_PRINT.load()) {
ByteArrayOutputStream baos = IRDumper.printIR(method, true, true);

LOG.info("Printing full IR for " + method.getName() + ":\n" + new String(baos.toByteArray()));
}
}

public String getClassName(ThreadContext context) {
29 changes: 26 additions & 3 deletions core/src/main/java/org/jruby/ir/IRFlags.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jruby.ir;

import java.util.EnumSet;

public enum IRFlags {
// Does this scope require a binding to be materialized?
// Yes if any of the following holds true:
@@ -49,13 +51,34 @@ public enum IRFlags {
RECEIVES_CLOSURE_ARG, // This scope (or parent receives a closure
RECEIVES_KEYWORD_ARGS, // receives keyword args
REQUIRES_DYNSCOPE, // does this scope require a dynamic scope?
USES_BACKREF_OR_LASTLINE, // Since backref ($~) and lastline ($_) vars are allocated space on the dynamic scope
USES_EVAL, // calls eval
USES_ZSUPER, // has zsuper instr
REQUIRES_FRAME, // callee may read/write caller's frame elements

REQUIRES_LASTLINE,
REQUIRES_BACKREF,
REQUIRES_VISIBILITY, // callee may read/write caller's visibility
REQUIRES_BLOCK,
REQUIRES_SELF,
REQUIRES_METHODNAME,
REQUIRES_LINE,
REQUIRES_CLASS,
REQUIRES_FILENAME,
REQUIRES_SCOPE,

DYNSCOPE_ELIMINATED, // local var load/stores have been converted to tmp var accesses
REUSE_PARENT_DYNSCOPE, // for closures -- reuse parent's dynscope
CODE_COVERAGE, // Was marked as needing code coverage (only used by lazy methods and converting closures->methods)
CODE_COVERAGE; // Was marked as needing code coverage (only used by lazy methods and converting closures->methods)

public static final EnumSet<IRFlags> REQUIRE_ALL_FRAME_FIELDS =
EnumSet.of(
REQUIRES_LASTLINE,
REQUIRES_BACKREF,
REQUIRES_VISIBILITY,
REQUIRES_BLOCK,
REQUIRES_SELF,
REQUIRES_METHODNAME,
REQUIRES_LINE,
REQUIRES_CLASS,
REQUIRES_FILENAME,
REQUIRES_SCOPE);
}
52 changes: 44 additions & 8 deletions core/src/main/java/org/jruby/ir/IRScope.java
Original file line number Diff line number Diff line change
@@ -176,7 +176,8 @@ public IRScope(IRManager manager, IRScope lexicalParent, String name,
flags.add(CAN_CAPTURE_CALLERS_BINDING);
flags.add(BINDING_HAS_ESCAPED);
flags.add(USES_EVAL);
flags.add(USES_BACKREF_OR_LASTLINE);
flags.add(REQUIRES_BACKREF);
flags.add(REQUIRES_LASTLINE);
flags.add(REQUIRES_DYNSCOPE);
flags.add(USES_ZSUPER);

@@ -398,10 +399,6 @@ public boolean bindingHasEscaped() {
return flags.contains(BINDING_HAS_ESCAPED);
}

public boolean usesBackrefOrLastline() {
return flags.contains(USES_BACKREF_OR_LASTLINE);
}

public boolean usesEval() {
return flags.contains(USES_EVAL);
}
@@ -650,15 +647,26 @@ public EnumSet<IRFlags> getFlags() {
}

private void initScopeFlags() {
// CON: why isn't this just clear?
flags.remove(CAN_CAPTURE_CALLERS_BINDING);
flags.remove(CAN_RECEIVE_BREAKS);
flags.remove(CAN_RECEIVE_NONLOCAL_RETURNS);
flags.remove(HAS_BREAK_INSTRS);
flags.remove(HAS_NONLOCAL_RETURNS);
flags.remove(USES_ZSUPER);
flags.remove(USES_EVAL);
flags.remove(USES_BACKREF_OR_LASTLINE);
flags.remove(REQUIRES_DYNSCOPE);

flags.remove(REQUIRES_LASTLINE);
flags.remove(REQUIRES_BACKREF);
flags.remove(REQUIRES_VISIBILITY);
flags.remove(REQUIRES_BLOCK);
flags.remove(REQUIRES_SELF);
flags.remove(REQUIRES_METHODNAME);
flags.remove(REQUIRES_LINE);
flags.remove(REQUIRES_CLASS);
flags.remove(REQUIRES_FILENAME);
flags.remove(REQUIRES_SCOPE);
}

private void bindingEscapedScopeFlagsCheck() {
@@ -1171,9 +1179,13 @@ public boolean needsFrame() {
switch (flag) {
case BINDING_HAS_ESCAPED:
case CAN_CAPTURE_CALLERS_BINDING:
case REQUIRES_FRAME:
case REQUIRES_LASTLINE:
case REQUIRES_BACKREF:
case REQUIRES_VISIBILITY:
case USES_BACKREF_OR_LASTLINE:
case REQUIRES_BLOCK:
case REQUIRES_SELF:
case REQUIRES_METHODNAME:
case REQUIRES_CLASS:
case USES_EVAL:
case USES_ZSUPER:
requireFrame = true;
@@ -1183,6 +1195,30 @@ public boolean needsFrame() {
return requireFrame;
}

public boolean needsOnlyBackref() {
boolean backrefSeen = false;
for (IRFlags flag : getFlags()) {
switch (flag) {
case BINDING_HAS_ESCAPED:
case CAN_CAPTURE_CALLERS_BINDING:
case REQUIRES_LASTLINE:
case REQUIRES_VISIBILITY:
case REQUIRES_BLOCK:
case REQUIRES_SELF:
case REQUIRES_METHODNAME:
case REQUIRES_CLASS:
case USES_EVAL:
case USES_ZSUPER:
return false;
case REQUIRES_BACKREF:
backrefSeen = true;
break;
}
}

return backrefSeen;
}

public boolean reuseParentScope() {
return getFlags().contains(IRFlags.REUSE_PARENT_DYNSCOPE);
}
2 changes: 2 additions & 0 deletions core/src/main/java/org/jruby/ir/IRVisitor.java
Original file line number Diff line number Diff line change
@@ -90,6 +90,7 @@ private void error(Object object) {
public void PopBindingInstr(PopBindingInstr popbindinginstr) { error(popbindinginstr); }
public void PopBlockFrameInstr(PopBlockFrameInstr instr) { error(instr); }
public void PopMethodFrameInstr(PopMethodFrameInstr instr) { error(instr); }
public void PopBackrefFrameInstr(PopBackrefFrameInstr instr) { error(instr); }
public void PrepareBlockArgsInstr(PrepareBlockArgsInstr instr) { error(instr); }
public void PrepareFixedBlockArgsInstr(PrepareFixedBlockArgsInstr instr) { error(instr); }
public void PrepareSingleBlockArgInstr(PrepareSingleBlockArgInstr instr) { error(instr); }
@@ -103,6 +104,7 @@ private void error(Object object) {
public void PushBlockFrameInstr(PushBlockFrameInstr instr) { error(instr); }
public void PushMethodBindingInstr(PushMethodBindingInstr instr) { error(instr); }
public void PushMethodFrameInstr(PushMethodFrameInstr instr) { error(instr); }
public void PushBackrefFrameInstr(PushBackrefFrameInstr instr) { error(instr); }
public void RaiseArgumentErrorInstr(RaiseArgumentErrorInstr raiseargumenterrorinstr) { error(raiseargumenterrorinstr); }
public void RaiseRequiredKeywordArgumentErrorInstr(RaiseRequiredKeywordArgumentError instr) { error(instr); }
public void ReifyClosureInstr(ReifyClosureInstr reifyclosureinstr) { error(reifyclosureinstr); }
2 changes: 2 additions & 0 deletions core/src/main/java/org/jruby/ir/Operation.java
Original file line number Diff line number Diff line change
@@ -211,8 +211,10 @@ public enum Operation {
/** Other JRuby internal primitives for optimizations */
MODULE_GUARD(OpFlags.f_is_jump_or_branch), /* a guard acts as a branch */
PUSH_METHOD_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
PUSH_BACKREF_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
PUSH_METHOD_BINDING(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
POP_METHOD_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
POP_BACKREF_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
PUSH_BLOCK_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
PUSH_BLOCK_BINDING(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
POP_BLOCK_FRAME(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
16 changes: 0 additions & 16 deletions core/src/main/java/org/jruby/ir/instructions/AttrAssignInstr.java
Original file line number Diff line number Diff line change
@@ -31,22 +31,6 @@ public AttrAssignInstr(Operand obj, String attr, Operand[] args, boolean isPoten
super(Operation.ATTR_ASSIGN, obj instanceof Self ? CallType.FUNCTIONAL : CallType.NORMAL, attr, obj, args, null, isPotentiallyRefined);
}

@Override
public boolean computeScopeFlags(IRScope scope) {
// SSS FIXME: For now, forcibly require a frame for scopes having attr-assign instructions. However, we
// can avoid this by passing in the frame self explicitly to Helpers.invoke(..) rather than try to get
// it off context.getFrameSelf()
boolean modifiesScope = super.computeScopeFlags(scope);

if (targetRequiresCallersFrame()) {
// This can be narrowed further by filtering out cases with literals other than String and Regexp
scope.getFlags().add(REQUIRES_FRAME);
modifiesScope = true;
}

return modifiesScope;
}

@Override
public Instr clone(CloneInfo ii) {
return new AttrAssignInstr(getReceiver().cloneForInlining(ii), getName(), cloneCallArgs(ii), isPotentiallyRefined());
Original file line number Diff line number Diff line change
@@ -66,7 +66,7 @@ public void visit(IRVisitor visitor) {

@Override
public boolean computeScopeFlags(IRScope scope) {
scope.getFlags().add(IRFlags.REQUIRES_FRAME);
scope.getFlags().add(IRFlags.REQUIRES_BACKREF);

return true;
}
104 changes: 85 additions & 19 deletions core/src/main/java/org/jruby/ir/instructions/CallBase.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.jruby.ir.instructions;

import org.jruby.anno.FrameField;
import org.jruby.ir.IRFlags;
import org.jruby.ir.IRScope;
import org.jruby.ir.Operation;
import org.jruby.ir.operands.*;
@@ -13,12 +15,16 @@
import org.jruby.runtime.callsite.RefinedCachingCallSite;
import org.jruby.util.ArraySupport;

import java.util.Collections;
import java.util.EnumSet;
import java.util.Map;
import java.util.Set;

import static org.jruby.ir.IRFlags.*;

public abstract class CallBase extends NOperandInstr implements ClosureAcceptingInstr {
private static long callSiteCounter = 1;
private static final EnumSet<FrameField> ALL = EnumSet.allOf(FrameField.class);

public transient final long callSiteId;
private final CallType callType;
@@ -35,6 +41,8 @@ public abstract class CallBase extends NOperandInstr implements ClosureAccepting
private transient boolean[] splatMap;
private transient boolean procNew;
private boolean potentiallyRefined;
private transient Set<FrameField> frameReads;
private transient Set<FrameField> frameWrites;

protected CallBase(Operation op, CallType callType, String name, Operand receiver, Operand[] args, Operand closure,
boolean potentiallyRefined) {
@@ -54,6 +62,8 @@ protected CallBase(Operation op, CallType callType, String name, Operand receive
dontInline = false;
procNew = false;
this.potentiallyRefined = potentiallyRefined;

captureFrameReadsAndWrites();
}

@Override
@@ -184,28 +194,48 @@ public boolean isPotentiallyRefined() {
public boolean computeScopeFlags(IRScope scope) {
boolean modifiedScope = false;

EnumSet<IRFlags> flags = scope.getFlags();
if (targetRequiresCallersBinding()) {
modifiedScope = true;
scope.getFlags().add(BINDING_HAS_ESCAPED);
flags.add(BINDING_HAS_ESCAPED);
}

if (targetRequiresCallersFrame()) {
for (FrameField read : frameReads) {
modifiedScope = true;
scope.getFlags().add(REQUIRES_FRAME);

switch (read) {
case LASTLINE: flags.add(IRFlags.REQUIRES_LASTLINE); break;
case BACKREF: flags.add(IRFlags.REQUIRES_BACKREF); break;
case VISIBILITY: flags.add(IRFlags.REQUIRES_VISIBILITY); break;
case BLOCK: flags.add(IRFlags.REQUIRES_BLOCK); break;
case SELF: flags.add(IRFlags.REQUIRES_SELF); break;
case METHODNAME: flags.add(IRFlags.REQUIRES_METHODNAME); break;
case LINE: flags.add(IRFlags.REQUIRES_LINE); break;
case CLASS: flags.add(IRFlags.REQUIRES_CLASS); break;
case FILENAME: flags.add(IRFlags.REQUIRES_FILENAME); break;
case SCOPE: flags.add(IRFlags.REQUIRES_SCOPE); break;
}
}

if (canBeEval()) flags.add(IRFlags.USES_EVAL);

// literal closures can be used to capture surrounding binding
if (hasLiteralClosure()) flags.addAll(IRFlags.REQUIRE_ALL_FRAME_FIELDS);

if (procNew) flags.add(IRFlags.REQUIRES_BLOCK);

if (canBeEval()) {
modifiedScope = true;
scope.getFlags().add(USES_EVAL);
flags.add(USES_EVAL);

// If eval contains a return then a nonlocal may pass through (e.g. def foo; eval "return 1"; end).
scope.getFlags().add(CAN_RECEIVE_NONLOCAL_RETURNS);
flags.add(CAN_RECEIVE_NONLOCAL_RETURNS);

// If this method receives a closure arg, and this call is an eval that has more than 1 argument,
// it could be using the closure as a binding -- which means it could be using pretty much any
// variable from the caller's binding!
if (scope.getFlags().contains(RECEIVES_CLOSURE_ARG) && argsCount > 1) {
scope.getFlags().add(CAN_CAPTURE_CALLERS_BINDING);
if (flags.contains(RECEIVES_CLOSURE_ARG) && argsCount > 1) {
flags.add(CAN_CAPTURE_CALLERS_BINDING);
}
}

@@ -215,16 +245,16 @@ public boolean computeScopeFlags(IRScope scope) {
// FIXME: We need to decouple static-scope and dyn-scope.
String mname = getName();
if (mname.equals("local_variables")) {
scope.getFlags().add(REQUIRES_DYNSCOPE);
} else if (potentiallySend(mname) && argsCount >= 1) {
flags.add(REQUIRES_DYNSCOPE);
} else if (potentiallySend(mname, argsCount)) {
Operand meth = getArg1();
if (meth instanceof StringLiteral && "local_variables".equals(((StringLiteral)meth).getString())) {
scope.getFlags().add(REQUIRES_DYNSCOPE);
flags.add(REQUIRES_DYNSCOPE);
}
}

// Refined scopes require dynamic scope in order to get the static scope
if (potentiallyRefined) scope.getFlags().add(REQUIRES_DYNSCOPE);
if (potentiallyRefined) flags.add(REQUIRES_DYNSCOPE);

return modifiedScope;
}
@@ -267,7 +297,7 @@ private boolean computeEvalFlag() {
}

// Calls to 'send' where the first arg is either unknown or is eval or send (any others?)
if (potentiallySend(mname) && argsCount >= 1) {
if (potentiallySend(mname, argsCount)) {
Operand meth = getArg1();
if (!(meth instanceof StringLiteral)) return true; // We don't know

@@ -290,7 +320,7 @@ private boolean computeRequiresCallersBindingFlag() {
String mname = getName();
if (MethodIndex.SCOPE_AWARE_METHODS.contains(mname)) {
return true;
} else if (potentiallySend(mname) && argsCount >= 1) {
} else if (potentiallySend(mname, argsCount)) {
Operand meth = getArg1();
if (!(meth instanceof StringLiteral)) return true; // We don't know -- could be anything

@@ -341,22 +371,58 @@ private boolean computeRequiresCallersFrameFlag() {
if (procNew) return true;

String mname = getName();
if (MethodIndex.FRAME_AWARE_METHODS.contains(mname)) {
if (frameReads.size() > 0 || frameWrites.size() > 0) {
// Known frame-aware methods.
return true;

} else if (potentiallySend(mname) && argsCount >= 1) {
} else if (potentiallySend(mname, argsCount)) {
Operand meth = getArg1();
if (!(meth instanceof StringLiteral)) return true; // We don't know -- could be anything
String name;
if (meth instanceof Stringable) {
name = ((Stringable) meth).getString();
} else {
return true; // We don't know -- could be anything
}

return MethodIndex.FRAME_AWARE_METHODS.contains(((StringLiteral) meth).getString());
frameReads = MethodIndex.METHOD_FRAME_READS.getOrDefault(name, Collections.EMPTY_SET);
frameWrites = MethodIndex.METHOD_FRAME_WRITES.getOrDefault(name, Collections.EMPTY_SET);

if (frameReads.size() > 0 || frameWrites.size() > 0) {
return true;
}
}

return false;
}

private static boolean potentiallySend(String name) {
return name.equals("send") || name.equals("__send__");
private static boolean potentiallySend(String name, int argsCount) {
return (name.equals("send") || name.equals("__send__") || name.equals("public_send")) && argsCount >= 1;
}

/**
* Determine based on the method name what frame fields it is likely to need.
*
* @param name the name of the method that will be called
*/
private void captureFrameReadsAndWrites() {
// grab a reference to frame fields this method name is known to be associated with
if (potentiallySend(getName(), argsCount)) {
// Might be a #send, use the frame reads and writes of what it might call
Operand meth = getArg1();
String aliasName;
if (meth instanceof Stringable) {
aliasName = ((Stringable) meth).getString();
frameReads = MethodIndex.METHOD_FRAME_READS.getOrDefault(aliasName, Collections.EMPTY_SET);
frameWrites = MethodIndex.METHOD_FRAME_WRITES.getOrDefault(aliasName, Collections.EMPTY_SET);
} else {
// We don't know -- could be anything
frameReads = ALL;
frameWrites = ALL;
}
} else {
frameReads = MethodIndex.METHOD_FRAME_READS.getOrDefault(name, Collections.EMPTY_SET);
frameWrites = MethodIndex.METHOD_FRAME_WRITES.getOrDefault(name, Collections.EMPTY_SET);
}
}

private void computeFlags() {
Original file line number Diff line number Diff line change
@@ -30,10 +30,12 @@ public GlobalVariable getTarget() {
public boolean computeScopeFlags(IRScope scope) {
String name = getTarget().getName();

if (name.equals("$_") || name.equals("$~") || name.equals("$`") || name.equals("$'") ||
name.equals("$+") || name.equals("$LAST_READ_LINE") || name.equals("$LAST_MATCH_INFO") ||
if (name.equals("$_") || name.equals("$LAST_READ_LINE")) {
scope.getFlags().add(IRFlags.REQUIRES_LASTLINE);
} else if (name.equals("$~") || name.equals("$`") || name.equals("$'") ||
name.equals("$+") || name.equals("$LAST_MATCH_INFO") ||
name.equals("$PREMATCH") || name.equals("$POSTMATCH") || name.equals("$LAST_PAREN_MATCH")) {
scope.getFlags().add(IRFlags.USES_BACKREF_OR_LASTLINE);
scope.getFlags().add(IRFlags.REQUIRES_BACKREF);
return true;
}

Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
import org.jruby.ir.transformations.inlining.InlineCloneInfo;
import org.jruby.ir.transformations.inlining.SimpleCloneInfo;

import static org.jruby.ir.IRFlags.REQUIRES_FRAME;
import static org.jruby.ir.IRFlags.REQUIRES_BLOCK;

/**
* Load the block passed to this scope via the on-heap frame (or similar cross-call structure).
@@ -46,7 +46,7 @@ public static LoadFrameClosureInstr decode(IRReaderDecoder d) {
@Override
public boolean computeScopeFlags(IRScope scope) {
super.computeScopeFlags(scope);
scope.getFlags().add(REQUIRES_FRAME);
scope.getFlags().add(REQUIRES_BLOCK);
return true;
}

4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/instructions/MatchInstr.java
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.runtime.CallType;

import static org.jruby.ir.IRFlags.USES_BACKREF_OR_LASTLINE;
import static org.jruby.ir.IRFlags.REQUIRES_BACKREF;

public class MatchInstr extends CallInstr implements FixedArityInstr {
public MatchInstr(Variable result, Operand receiver, Operand arg) {
@@ -24,7 +24,7 @@ public boolean computeScopeFlags(IRScope scope) {
super.computeScopeFlags(scope);
// $~ is implicitly used since Backref and NthRef operands
// access it and $~ is not made explicit in those operands.
scope.getFlags().add(USES_BACKREF_OR_LASTLINE);
scope.getFlags().add(REQUIRES_BACKREF);
return true;
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.jruby.ir.instructions;

import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.persistence.IRReaderDecoder;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.ir.transformations.inlining.SimpleCloneInfo;

public class PopBackrefFrameInstr extends NoOperandInstr implements FixedArityInstr {
public PopBackrefFrameInstr() {
super(Operation.POP_BACKREF_FRAME);
}

@Override
public Instr clone(CloneInfo ii) {
return ii instanceof SimpleCloneInfo ? this : NopInstr.NOP; // FIXME: Is this correct
}

public static PopBackrefFrameInstr decode(IRReaderDecoder d) {
return new PopBackrefFrameInstr();
}

@Override
public void visit(IRVisitor visitor) {
visitor.PopBackrefFrameInstr(this);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.jruby.ir.instructions;

import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.persistence.IRReaderDecoder;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.ir.transformations.inlining.SimpleCloneInfo;

public class PushBackrefFrameInstr extends NoOperandInstr implements FixedArityInstr {
public PushBackrefFrameInstr() {
super(Operation.PUSH_BACKREF_FRAME);
}

@Override
public Instr clone(CloneInfo ii) {
return ii instanceof SimpleCloneInfo ? this : NopInstr.NOP; // FIXME: Is this correct?
}

public static PushBackrefFrameInstr decode(IRReaderDecoder d) {
return new PushBackrefFrameInstr();
}

@Override
public void visit(IRVisitor visitor) {
visitor.PushBackrefFrameInstr(this);
}
}
Original file line number Diff line number Diff line change
@@ -27,8 +27,12 @@ public PutGlobalVarInstr(GlobalVariable gvar, Operand value) {
public boolean computeScopeFlags(IRScope scope) {
String name = getTarget().getName();

if (name.equals("$_") || name.equals("$~")) {
scope.getFlags().add(IRFlags.USES_BACKREF_OR_LASTLINE);
if (name.equals("$_") || name.equals("$LAST_READ_LINE")) {
scope.getFlags().add(IRFlags.REQUIRES_LASTLINE);
} else if (name.equals("$~") || name.equals("$`") || name.equals("$'") ||
name.equals("$+") || name.equals("$LAST_MATCH_INFO") ||
name.equals("$PREMATCH") || name.equals("$POSTMATCH") || name.equals("$LAST_PAREN_MATCH")) {
scope.getFlags().add(IRFlags.REQUIRES_BACKREF);
return true;
}

Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

import static org.jruby.ir.IRFlags.REQUIRES_FRAME;
import static org.jruby.ir.IRFlags.REQUIRES_CLASS;

public class RuntimeHelperCall extends NOperandResultBaseInstr {
public enum Methods {
@@ -60,7 +60,7 @@ public boolean computeScopeFlags(IRScope scope) {
// FIXME: Impl of this helper uses frame class. Determine if we can do this another way.
if (helperMethod == Methods.IS_DEFINED_SUPER) {
modifiedScope = true;
scope.getFlags().add(REQUIRES_FRAME);
scope.getFlags().add(REQUIRES_CLASS);
}

return modifiedScope;
Original file line number Diff line number Diff line change
@@ -34,7 +34,8 @@ public UnresolvedSuperInstr(Variable result, Operand receiver, Operand[] args, O
@Override
public boolean computeScopeFlags(IRScope scope) {
super.computeScopeFlags(scope);
scope.getFlags().add(IRFlags.REQUIRES_FRAME); // for current class and method name
scope.getFlags().add(IRFlags.REQUIRES_CLASS); // for current class and method name
scope.getFlags().add(IRFlags.REQUIRES_METHODNAME); // for current class and method name
return true;
}

Original file line number Diff line number Diff line change
@@ -388,9 +388,15 @@ protected static void processBookKeepingOp(ThreadContext context, Block block, I
// Everything else is PUBLIC by default.
context.setCurrentVisibility(Visibility.PUBLIC);
break;
case PUSH_BACKREF_FRAME:
context.preBackrefMethod();
break;
case POP_METHOD_FRAME:
context.popFrame();
break;
case POP_BACKREF_FRAME:
context.postBackrefMethod();
break;
case POP_BINDING:
context.popScope();
break;
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/ir/operands/Symbol.java
Original file line number Diff line number Diff line change
@@ -11,7 +11,7 @@
import org.jruby.util.ByteList;
import org.jruby.util.io.EncodingUtils;

public class Symbol extends ImmutableLiteral {
public class Symbol extends ImmutableLiteral implements Stringable {
public static final Symbol KW_REST_ARG_DUMMY = new Symbol("", ASCIIEncoding.INSTANCE);

private final ByteList bytes;
Original file line number Diff line number Diff line change
@@ -63,7 +63,13 @@ private void popSavedState(IRScope scope, boolean isGEB, boolean requireBinding,
instrs.add(new PopBlockFrameInstr(savedFrame));
}
} else {
if (requireFrame) instrs.add(new PopMethodFrameInstr());
if (requireFrame) {
if (scope.needsOnlyBackref()) {
instrs.add(new PopBackrefFrameInstr());
} else {
instrs.add(new PopMethodFrameInstr());
}
}
}
}

@@ -125,7 +131,13 @@ public Object execute(IRScope scope, Object... data) {
}
}
} else {
if (requireFrame) entryBB.addInstr(new PushMethodFrameInstr(scope.getName()));
if (requireFrame) {
if (scope.needsOnlyBackref()) {
entryBB.addInstr(new PushBackrefFrameInstr());
} else {
entryBB.addInstr(new PushMethodFrameInstr(scope.getName()));
}
}
if (requireBinding) entryBB.addInstr(new PushMethodBindingInstr());
}

12 changes: 12 additions & 0 deletions core/src/main/java/org/jruby/ir/targets/JVMVisitor.java
Original file line number Diff line number Diff line change
@@ -1617,6 +1617,12 @@ public void PopMethodFrameInstr(PopMethodFrameInstr popframeinstr) {
jvmMethod().invokeVirtual(Type.getType(ThreadContext.class), Method.getMethod("void postMethodFrameOnly()"));
}

@Override
public void PopBackrefFrameInstr(PopBackrefFrameInstr popframeinstr) {
jvmMethod().loadContext();
jvmMethod().invokeVirtual(Type.getType(ThreadContext.class), Method.getMethod("void postBackrefMethod()"));
}

@Override
public void PrepareBlockArgsInstr(PrepareBlockArgsInstr instr) {
jvmMethod().loadContext();
@@ -1721,6 +1727,12 @@ public void PushMethodFrameInstr(PushMethodFrameInstr pushframeinstr) {
jvmAdapter().invokevirtual(p(ThreadContext.class), "setCurrentVisibility", sig(void.class, Visibility.class));
}

@Override
public void PushBackrefFrameInstr(PushBackrefFrameInstr pushframeinstr) {
jvmMethod().loadContext();
jvmMethod().invokeVirtual(Type.getType(ThreadContext.class), Method.getMethod("void preBackrefMethod()"));
}

@Override
public void PutClassVariableInstr(PutClassVariableInstr putclassvariableinstr) {
visit(putclassvariableinstr.getValue());
8 changes: 8 additions & 0 deletions core/src/main/java/org/jruby/runtime/Frame.java
Original file line number Diff line number Diff line change
@@ -177,6 +177,14 @@ public void updateFrameForEval(IRubyObject self) {
this.visibility = Visibility.PRIVATE;
}

public void updateFrameForBackref() {
// nothing
}

public void clearFrameForBackref() {
this.backRef = null;
}

/**
* Clear the frame, as when the call completes. Clearing prevents cached
* frames from holding references after the call is done.
3 changes: 3 additions & 0 deletions core/src/main/java/org/jruby/runtime/Helpers.java
Original file line number Diff line number Diff line change
@@ -12,6 +12,7 @@
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

import com.headius.modulator.Modulator;
import jnr.constants.platform.Errno;
@@ -71,6 +72,8 @@
*/
public class Helpers {

public static final Pattern SEMICOLON_PATTERN = Pattern.compile(";");

public static RubyClass getSingletonClass(Ruby runtime, IRubyObject receiver) {
if (receiver instanceof RubyFixnum || receiver instanceof RubySymbol) {
throw runtime.newTypeError("can't define singleton");
54 changes: 54 additions & 0 deletions core/src/main/java/org/jruby/runtime/MethodIndex.java
Original file line number Diff line number Diff line change
@@ -33,9 +33,12 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.jruby.anno.FrameField;
import org.jruby.runtime.callsite.LtCallSite;
import org.jruby.runtime.callsite.LeCallSite;
import org.jruby.runtime.callsite.MinusCallSite;
@@ -94,6 +97,9 @@ public class MethodIndex {
public static final Set<String> FRAME_AWARE_METHODS = Collections.synchronizedSet(new HashSet<String>());
public static final Set<String> SCOPE_AWARE_METHODS = Collections.synchronizedSet(new HashSet<String>());

public static final Map<String, Set<FrameField>> METHOD_FRAME_READS = new ConcurrentHashMap<>();
public static final Map<String, Set<FrameField>> METHOD_FRAME_WRITES = new ConcurrentHashMap<>();

public static CallSite getCallSite(String name) {
// fast and safe respond_to? call site logic
if (name.equals("respond_to?")) return new RespondToCallSite();
@@ -218,11 +224,59 @@ public static CallSite getSuperCallSite() {
return new SuperCallSite();
}

public static void addMethodReadFieldsPacked(int readBits, String methodsPacked) {
Set<FrameField> reads = Collections.synchronizedSet(FrameField.unpack(readBits));

if (DEBUG) LOG.debug("Adding method field reads: {} for {}", reads, methodsPacked);

String[] names = Helpers.SEMICOLON_PATTERN.split(methodsPacked);

if (FrameField.needsFrame(readBits)) FRAME_AWARE_METHODS.addAll(Arrays.asList(names));
if (FrameField.needsScope(readBits)) SCOPE_AWARE_METHODS.addAll(Arrays.asList(names));

for (String name : names) {
Set<FrameField> current = METHOD_FRAME_READS.putIfAbsent(name, reads);

if (current != null) {
current.addAll(reads);
}
}
}

public static void addMethodWriteFieldsPacked(int writeBits, String methodsPacked) {
Set<FrameField> writes = FrameField.unpack(writeBits);

if (DEBUG) LOG.debug("Adding scope-aware method names: {} for {}", writes, methodsPacked);

String[] names = Helpers.SEMICOLON_PATTERN.split(methodsPacked);

if (FrameField.needsFrame(writeBits)) FRAME_AWARE_METHODS.addAll(Arrays.asList(names));
if (FrameField.needsScope(writeBits)) SCOPE_AWARE_METHODS.addAll(Arrays.asList(names));

for (String name : names) {
Set<FrameField> current = METHOD_FRAME_WRITES.putIfAbsent(name, writes);

if (current != null) {
current.addAll(writes);
}
}
}

public static void addMethodReadFields(String name, FrameField[] reads) {
addMethodReadFieldsPacked(FrameField.pack(reads), name);
}

public static void addMethodWriteFields(String name, FrameField[] write) {
addMethodWriteFieldsPacked(FrameField.pack(write), name);
}

@Deprecated
public static void addFrameAwareMethods(String... methods) {
if (DEBUG) LOG.debug("Adding frame-aware method names: {}", Arrays.toString(methods));
FRAME_AWARE_METHODS.addAll(Arrays.asList(methods));
}

@Deprecated
public static void addScopeAwareMethods(String... methods) {
if (DEBUG) LOG.debug("Adding scope-aware method names: {}", Arrays.toString(methods));
SCOPE_AWARE_METHODS.addAll(Arrays.asList(methods));
30 changes: 30 additions & 0 deletions core/src/main/java/org/jruby/runtime/ThreadContext.java
Original file line number Diff line number Diff line change
@@ -463,6 +463,28 @@ private void pushCallFrame(RubyModule clazz, String name,
}
}

private void pushBackrefFrame() {
int index = ++this.frameIndex;
Frame[] stack = frameStack;
stack[index].updateFrameForBackref();
if (index + 1 == stack.length) {
expandFrameStack();
}
}

private void popBackrefFrame() {
Frame[] stack = frameStack;
int index = frameIndex--;
Frame frame = stack[index];

// if the frame was captured, we must replace it but not clear
if (frame.isCaptured()) {
stack[index] = new Frame();
} else {
frame.clearFrameForBackref();
}
}

private void pushEvalFrame(IRubyObject self) {
int index = ++this.frameIndex;
Frame[] stack = frameStack;
@@ -908,10 +930,18 @@ public void preMethodFrameOnly(RubyModule clazz, String name, IRubyObject self)
pushCallFrame(clazz, name, self, Block.NULL_BLOCK);
}

public void preBackrefMethod() {
pushBackrefFrame();
}

public void postMethodFrameOnly() {
popFrame();
}

public void postBackrefMethod() {
popBackrefFrame();
}

public void preMethodScopeOnly(StaticScope staticScope) {
pushScope(DynamicScope.newDynamicScope(staticScope));
}

0 comments on commit 3eddbcb

Please sign in to comment.