Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 53a43caf7fb3
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 8f628ede4869
Choose a head ref
  • 3 commits
  • 17 files changed
  • 1 contributor

Commits on Nov 7, 2014

  1. Fix excludes logic for included modules.

    See ruby/test_struct.rb for one such example. Test methods pulled
    in from a module were not firing exclude logic, inflating our
    failure rate on the updated 2.2 tests. With this fix, about 30
    failures went back underground.
    headius committed Nov 7, 2014
    Copy the full SHA
    bea6e70 View commit details
  2. Update tags for #2142.

    headius committed Nov 7, 2014
    Copy the full SHA
    a65e66f View commit details
  3. Add frozen string optz and improve string handling in JIT.

    * FrozenString operand represents "str".freeze
    * All modes cache frozen strings and bytelists now (none before)
    headius committed Nov 7, 2014
    Copy the full SHA
    8f628ed View commit details
2 changes: 1 addition & 1 deletion core/pom.rb
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@
jar 'org.jruby.extras:bytelist:1.0.12-SNAPSHOT'
jar 'org.jruby.jcodings:jcodings:1.0.12-SNAPSHOT'

jar 'com.headius:invokebinder:1.4'
jar 'com.headius:invokebinder:1.5-SNAPSHOT'
jar 'com.headius:options:1.1'
jar 'com.headius:coro-mock:1.0', :scope => 'provided'
jar 'com.headius:unsafe-mock', '${unsafe.version}', :scope => 'provided'
2 changes: 1 addition & 1 deletion core/pom.xml
Original file line number Diff line number Diff line change
@@ -112,7 +112,7 @@
<dependency>
<groupId>com.headius</groupId>
<artifactId>invokebinder</artifactId>
<version>1.4</version>
<version>1.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.headius</groupId>
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -4606,14 +4606,14 @@ public RubyString getThreadStatus(RubyThread.Status status) {
* existing copy already prepared. This is used to reduce in-memory
* duplication of pre-frozen or known-frozen strings.
*
* Note that this cache is synchronized against the Ruby instance. This
* Note that this cache does some sync against the Ruby instance. This
* could cause contention under heavy concurrent load, so a reexamination
* of this design might be warranted.
*
* @param string the string to freeze-dup if an equivalent does not already exist
* @return the freeze-duped version of the string
*/
public synchronized RubyString freezeAndDedupString(RubyString string) {
public RubyString freezeAndDedupString(RubyString string) {
if (string.getMetaClass().isSingleton()) {
// never cache a singleton
RubyString duped = string.strDup(this);
7 changes: 7 additions & 0 deletions core/src/main/java/org/jruby/ir/IRBuilder.java
Original file line number Diff line number Diff line change
@@ -973,6 +973,13 @@ private void receiveBreakException(final IRScope s, Operand block, final CallIns
public Operand buildCall(CallNode callNode, IRScope s) {
Node callArgsNode = callNode.getArgsNode();
Node receiverNode = callNode.getReceiverNode();

// check for "string".freeze
if (receiverNode instanceof StrNode && callNode.getName().equals("freeze")) {
// frozen string optimization
return new FrozenString(((StrNode)receiverNode).getValue());
}

// Though you might be tempted to move this build into the CallInstr as:
// new Callinstr( ... , build(receiverNode, s), ...)
// that is incorrect IR because the receiver has to be built *before* call arguments are built
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/IRVisitor.java
Original file line number Diff line number Diff line change
@@ -144,6 +144,7 @@ private void error(Object object) {
public void CurrentScope(CurrentScope currentscope) { error(currentscope); }
public void DynamicSymbol(DynamicSymbol dynamicsymbol) { error(dynamicsymbol); }
public void Fixnum(Fixnum fixnum) { error(fixnum); }
public void FrozenString(FrozenString frozen) { error(frozen); }
public void UnboxedFixnum(UnboxedFixnum fixnum) { error(fixnum); }
public void Float(org.jruby.ir.operands.Float flote) { error(flote); }
public void UnboxedFloat(org.jruby.ir.operands.UnboxedFloat flote) { error(flote); }
61 changes: 61 additions & 0 deletions core/src/main/java/org/jruby/ir/operands/FrozenString.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package org.jruby.ir.operands;

import org.jruby.RubyString;
import org.jruby.ir.IRVisitor;
import org.jruby.ir.transformations.inlining.CloneInfo;
import org.jruby.parser.StaticScope;
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.Helpers;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.util.ByteList;

import java.nio.charset.UnsupportedCharsetException;
import java.util.List;

/**
* Represents a literal string value.
*
* This is not an immutable literal because I can gsub!,
* for example, and modify the contents of the string.
* This is not like a Java string.
*/
public class FrozenString extends ImmutableLiteral {
final private ByteList bytelist;
private RubyString cached;

public FrozenString(ByteList byteList) {
super(OperandType.STRING_LITERAL);

this.bytelist = byteList;
}

@Override
public Object createCacheObject(ThreadContext context) {
return context.runtime.freezeAndDedupString(RubyString.newString(context.runtime, bytelist));
}

@Override
public int hashCode() {
return bytelist.hashCode();
}

@Override
public boolean equals(Object other) {
return other instanceof FrozenString && bytelist.equals(((FrozenString) other).bytelist);
}

@Override
public String toString() {
return "frozen:\"" + bytelist + "\"";
}

@Override
public void visit(IRVisitor visitor) {
visitor.FrozenString(this);
}

public ByteList getByteList() {
return bytelist;
}
}
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/operands/OperandType.java
Original file line number Diff line number Diff line change
@@ -47,6 +47,7 @@ public enum OperandType {
UNDEFINED_VALUE((byte) 'u'),
UNEXECUTABLE_NIL((byte) 'n'),
WRAPPED_IR_CLOSURE((byte) 'w'),
FROZEN_STRING((byte) 'z')
;

private final byte coded;
4 changes: 2 additions & 2 deletions core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java
Original file line number Diff line number Diff line change
@@ -941,8 +941,8 @@ public static final Type[] typesFromSignature(Signature signature) {
}

// Used by JIT
public static RubyString newStringFromRaw(Ruby runtime, String str, String encoding) {
return new RubyString(runtime, runtime.getString(), newByteListFromRaw(runtime, str, encoding));
public static RubyString newFrozenStringFromRaw(Ruby runtime, String str, String encoding) {
return runtime.freezeAndDedupString(new RubyString(runtime, runtime.getString(), newByteListFromRaw(runtime, str, encoding)));
}

// Used by JIT
48 changes: 43 additions & 5 deletions core/src/main/java/org/jruby/ir/targets/Bootstrap.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package org.jruby.ir.targets;

import com.headius.invokebinder.Binder;
import com.headius.invokebinder.Signature;
import com.headius.invokebinder.SmartBinder;
import com.headius.invokebinder.SmartHandle;
import org.jcodings.Encoding;
import org.jcodings.EncodingDB;
import org.jruby.*;
import org.jruby.common.IRubyWarnings;
import org.jruby.compiler.Constantizable;
import org.jruby.internal.runtime.methods.*;
import org.jruby.ir.JIT;
import org.jruby.ir.operands.UndefinedValue;
import org.jruby.ir.runtime.IRRuntimeHelpers;
import org.jruby.parser.StaticScope;
@@ -43,11 +46,17 @@ public static CallSite string(Lookup lookup, String name, MethodType type, Strin
if (entry == null) throw new RuntimeException("could not find encoding: " + encodingName);
encoding = entry.getEncoding();
ByteList byteList = new ByteList(value.getBytes(RubyEncoding.ISO), encoding);
MethodHandle handle = Binder
MutableCallSite site = new MutableCallSite(type);
Binder binder = Binder
.from(RubyString.class, ThreadContext.class)
.insert(0, ByteList.class, byteList)
.invokeStaticQuiet(LOOKUP, Bootstrap.class, "string");
return new ConstantCallSite(handle);
.insert(0, arrayOf(MutableCallSite.class, ByteList.class), site, byteList);
if (name.equals("frozen")) {
site.setTarget(binder.invokeStaticQuiet(lookup, Bootstrap.class, "frozenString"));
} else {
site.setTarget(binder.invokeStaticQuiet(lookup, Bootstrap.class, "string"));
}

return site;
}

public static CallSite bytelist(Lookup lookup, String name, MethodType type, String value, String encodingName) {
@@ -137,10 +146,39 @@ public static Handle searchConst() {
return new Handle(Opcodes.H_INVOKESTATIC, p(Bootstrap.class), "searchConst", sig(CallSite.class, Lookup.class, String.class, MethodType.class, int.class));
}

public static RubyString string(ByteList value, ThreadContext context) {
public static RubyString string(MutableCallSite site, ByteList value, ThreadContext context) throws Throwable {
MethodHandle handle = SmartBinder
.from(STRING_SIGNATURE)
.invoke(NEW_STRING_SHARED_HANDLE.apply("byteList", value))
.handle();

site.setTarget(handle);

return RubyString.newStringShared(context.runtime, value);
}

public static RubyString frozenString(MutableCallSite site, ByteList value, ThreadContext context) throws Throwable {
RubyString frozen = context.runtime.freezeAndDedupString(RubyString.newStringShared(context.runtime, value));
MethodHandle handle = Binder.from(RubyString.class, ThreadContext.class)
.dropAll()
.constant(frozen);

site.setTarget(handle);

return frozen;
}

private static final Signature STRING_SIGNATURE = Signature.from(RubyString.class, arrayOf(ThreadContext.class), "context");
private static final Signature NEW_STRING_SHARED_SIGNATURE = Signature.from(RubyString.class, arrayOf(ThreadContext.class, ByteList.class), "context", "byteList");

private static final SmartHandle NEW_STRING_SHARED_HANDLE =
SmartBinder.from(NEW_STRING_SHARED_SIGNATURE)
.invokeStaticQuiet(MethodHandles.lookup(), Bootstrap.class, "newStringShared");
@JIT
private static RubyString newStringShared(ThreadContext context, ByteList byteList) {
return RubyString.newStringShared(context.runtime, byteList);
}

public static IRubyObject array(ThreadContext context, IRubyObject[] elts) {
return RubyArray.newArrayNoCopy(context.runtime, elts);
}
Original file line number Diff line number Diff line change
@@ -208,6 +208,13 @@ public org.objectweb.asm.Label newLabel() {
*/
public abstract void pushString(ByteList bl);

/**
* Stack required: none
*
* @param bl ByteList for the String to push
*/
public abstract void pushFrozenString(ByteList bl);

/**
* Stack required: none
*
35 changes: 34 additions & 1 deletion core/src/main/java/org/jruby/ir/targets/IRBytecodeAdapter6.java
Original file line number Diff line number Diff line change
@@ -67,17 +67,50 @@ public void pushFloat(double d) {
}

public void pushString(ByteList bl) {
loadRuntime();
pushByteList(bl);
adapter.invokestatic(p(RubyString.class), "newStringShared", sig(RubyString.class, Ruby.class, ByteList.class));
}

/**
* Stack required: none
*
* @param bl ByteList for the String to push
*/
public void pushFrozenString(ByteList bl) {
// FIXME: too much bytecode
String cacheField = "frozenString" + getClassData().callSiteCount.getAndIncrement();
Label done = new Label();
adapter.getClassVisitor().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, cacheField, ci(RubyString.class), null, null).visitEnd();
adapter.getstatic(getClassData().clsName, cacheField, ci(RubyString.class));
adapter.dup();
adapter.ifnonnull(done);
adapter.pop();
loadRuntime();
adapter.ldc(bl.toString());
adapter.ldc(bl.getEncoding().toString());
invokeIRHelper("newStringFromRaw", sig(RubyString.class, Ruby.class, String.class, String.class));
invokeIRHelper("newFrozenStringFromRaw", sig(RubyString.class, Ruby.class, String.class, String.class));
adapter.dup();
adapter.putstatic(getClassData().clsName, cacheField, ci(RubyString.class));
adapter.label(done);
}

public void pushByteList(ByteList bl) {
// FIXME: too much bytecode
String cacheField = "byteList" + getClassData().callSiteCount.getAndIncrement();
Label done = new Label();
adapter.getClassVisitor().visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, cacheField, ci(ByteList.class), null, null).visitEnd();
adapter.getstatic(getClassData().clsName, cacheField, ci(ByteList.class));
adapter.dup();
adapter.ifnonnull(done);
adapter.pop();
loadRuntime();
adapter.ldc(bl.toString());
adapter.ldc(bl.getEncoding().toString());
invokeIRHelper("newByteListFromRaw", sig(ByteList.class, Ruby.class, String.class, String.class));
adapter.dup();
adapter.putstatic(getClassData().clsName, cacheField, ci(ByteList.class));
adapter.label(done);
}

public void pushRegexp(int options) {
Original file line number Diff line number Diff line change
@@ -57,6 +57,11 @@ public void pushString(ByteList bl) {
adapter.invokedynamic("string", sig(RubyString.class, ThreadContext.class), Bootstrap.string(), new String(bl.bytes(), RubyEncoding.ISO), bl.getEncoding().toString());
}

public void pushFrozenString(ByteList bl) {
loadContext();
adapter.invokedynamic("frozen", sig(RubyString.class, ThreadContext.class), Bootstrap.string(), new String(bl.bytes(), RubyEncoding.ISO), bl.getEncoding().toString());
}

public void pushByteList(ByteList bl) {
adapter.invokedynamic("bytelist", sig(ByteList.class), Bootstrap.bytelist(), new String(bl.bytes(), RubyEncoding.ISO), bl.getEncoding().toString());
}
5 changes: 5 additions & 0 deletions core/src/main/java/org/jruby/ir/targets/JVMVisitor.java
Original file line number Diff line number Diff line change
@@ -1913,6 +1913,11 @@ public void Fixnum(Fixnum fixnum) {
jvmMethod().pushFixnum(fixnum.getValue());
}

@Override
public void FrozenString(FrozenString frozen) {
jvmMethod().pushFrozenString(frozen.getByteList());
}

@Override
public void UnboxedFixnum(UnboxedFixnum fixnum) {
jvmAdapter().ldc(fixnum.getValue());
1 change: 1 addition & 0 deletions test/mri/excludes/PPTestModule/PPCycleTest.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
exclude :test_anonymous, "string formatting of object ID differs"
exclude :test_withinspect, "recursion issue"
exclude :test_object, "intermittent failure due to inspect formatting differences"
exclude :test_struct, "Struct#inspect differences (#2142)"
2 changes: 1 addition & 1 deletion test/mri/excludes/TestStruct/SubStruct.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
exclude :test_inspect, ''
exclude :test_inspect, '#2142'
exclude :test_nonascii, ''
exclude :test_redefinition_warning, ''
2 changes: 1 addition & 1 deletion test/mri/excludes/TestStruct/TopStruct.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
exclude :test_inspect, ''
exclude :test_inspect, '#2142'
exclude :test_nonascii, ''
13 changes: 13 additions & 0 deletions test/mri/lib/test/unit/testcase.rb
Original file line number Diff line number Diff line change
@@ -35,6 +35,19 @@ def self.method_added(name)
end
@test_methods[name] = true
end

# Override include so that tests pulled out of modules can also be excluded
def self.include(mod)
result = super(mod)

mod.public_instance_methods.each do |method|
if @excludes && @excludes[method]
undef_method method
end
end

result
end
end
end
end