Skip to content

Commit

Permalink
Showing 18 changed files with 420 additions and 144 deletions.
4 changes: 2 additions & 2 deletions COPYING
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
JRuby is Copyright (c) 2007-2015 The JRuby project, and is released
JRuby is Copyright (c) 2007-2016 The JRuby project, and is released
under a tri EPL/GPL/LGPL license. You can use it, redistribute it
and/or modify it under the terms of the:

@@ -12,7 +12,7 @@ bytelist (http://github.com/jruby/bytelist),
yydebug (http://svn.codehaus.org/jruby/trunk/jay/yydebug)
are released under the same copyright/license.

The Truffle component is copyright (c) 2013-2015 Oracle and/or its
The Truffle component is copyright (c) 2013-2016 Oracle and/or its
affiliates and is released under the same licenses.

Some additional libraries distributed with JRuby are not covered by
21 changes: 16 additions & 5 deletions core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
@@ -2493,19 +2493,30 @@ public RubyModule include(IRubyObject[] modules) {
ThreadContext context = getRuntime().getCurrentContext();
// MRI checks all types first:
for (int i = modules.length; --i >= 0; ) {
IRubyObject obj = modules[i];
if (!obj.isModule()) {
throw context.runtime.newTypeError(obj, context.runtime.getModule());
IRubyObject module = modules[i];
if ( ! module.isModule() ) {
throw context.runtime.newTypeError(module, context.runtime.getModule());
}
}
for (int i = modules.length - 1; i >= 0; i--) {
modules[i].callMethod(context, "append_features", this);
modules[i].callMethod(context, "included", this);
IRubyObject module = modules[i];
module.callMethod(context, "append_features", this);
module.callMethod(context, "included", this);
}

return this;
}

@JRubyMethod(name = "include", required = 1) // most common path: include Enumerable
public RubyModule include(ThreadContext context, IRubyObject module) {
if ( ! module.isModule() ) {
throw context.runtime.newTypeError(module, context.runtime.getModule());
}
module.callMethod(context, "append_features", this);
module.callMethod(context, "included", this);
return this;
}

@JRubyMethod(name = "included", required = 1, visibility = PRIVATE)
public IRubyObject included(ThreadContext context, IRubyObject other) {
return context.runtime.getNil();
Original file line number Diff line number Diff line change
@@ -42,6 +42,7 @@ public static RubyModule createJavaInterfaceTemplateModule(ThreadContext context
return JavaInterfaceTemplate;
}

@Deprecated // not used - should go away in >= 9.2
// not intended to be called directly by users (private)
// OLD TODO from Ruby code:
// This should be implemented in JavaClass.java, where we can
@@ -182,37 +183,14 @@ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {

// If we hold a Java object, we need a java_class accessor
clazz.addMethod("java_class", new JavaClassAccessor(clazz));

// Because we implement Java interfaces now, we need a new === that's
// aware of those additional "virtual" supertypes
if (!clazz.searchMethod("===").isUndefined()) {
clazz.defineAlias("old_eqq", "===");
clazz.addMethod("===", new JavaMethodOne(clazz, Visibility.PUBLIC) {

@Override
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, IRubyObject arg) {
// TODO: WRONG - get interfaces from class
if (arg.respondsTo("java_object")) {
IRubyObject interfaces = self.getMetaClass().getInstanceVariables().getInstanceVariable("@java_interfaces");
assert interfaces instanceof RubyArray : "interface list was not an array";

return context.runtime.newBoolean(((RubyArray) interfaces).op_diff(
((JavaObject) arg.dataGetStruct()).java_class().interfaces()).equals(RubyArray.newArray(context.runtime)));
} else {
return Helpers.invoke(context, self, "old_eqq", arg);
}
}
});
}
}

// Now we add an "implement" and "implement_all" methods to the class
if ( ! clazz.isMethodBound("implement", false) ) {
final RubyClass singleton = clazz.getSingletonClass();

// implement is called to force this class to create stubs for all
// methods in the given interface, so they'll show up in the list
// of methods and be invocable without passing through method_missing
// implement is called to force this class to create stubs for all methods in the given interface,
// so they'll show up in the list of methods and be invocable without passing through method_missing
singleton.addMethod("implement", new JavaMethodOne(clazz, Visibility.PRIVATE) {

@Override
@@ -225,8 +203,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
});

// implement all forces implementation of all interfaces we intend
// for this class to implement
// implement all forces implementation of all interfaces we intend for this class to implement
singleton.addMethod("implement_all", new JavaMethodOne(clazz, Visibility.PRIVATE) {

@Override
@@ -243,12 +220,17 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}
}

private static class InterfaceProxyFactory extends JavaMethodN { // __jcreate! and __jcreate_meta!
private static final class InterfaceProxyFactory extends JavaMethodN { // __jcreate! and __jcreate_meta!

InterfaceProxyFactory(final RubyClass clazz) { super(clazz, Visibility.PRIVATE); }

@Override // will be called with zero args
public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name, Block block) {
return newInterfaceProxy(self);
}

@Override
public final 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) {
return newInterfaceProxy(self);
}

@@ -295,8 +277,8 @@ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
private static IRubyObject newInterfaceProxy(final IRubyObject self) {
final RubyClass current = self.getMetaClass();
// construct the new interface impl and set it into the object
IRubyObject newObject = Java.newInterfaceImpl(self, Java.getInterfacesFromRubyClass(current));
JavaUtilities.set_java_object(self, self, newObject);
JavaObject newObject = Java.newInterfaceImpl(self, Java.getInterfacesFromRubyClass(current));
JavaUtilities.set_java_object(self, self, newObject); // self.dataWrapStruct(newObject);
return newObject;
}

@@ -305,9 +287,9 @@ private static void appendFeaturesToModule(ThreadContext context, final IRubyObj
// included together. make it so.
final Ruby runtime = context.runtime;

final IRubyObject java_class = module.getInstanceVariables().getInstanceVariable("@java_class");
// not allowed for existing Java interface modules
if (module.getInstanceVariables().hasInstanceVariable("@java_class") &&
module.getInstanceVariables().getInstanceVariable("@java_class").isTrue()) {
if (java_class != null && java_class.isTrue()) {
throw runtime.newTypeError("can not add Java interface to existing Java interface");
}

@@ -341,7 +323,7 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz
}

final RubyModule target = (RubyModule) arg;
target.include( getInterfaceModules(self).toJavaArray() );
target.include( getInterfaceModules(self).toJavaArrayMaybeUnsafe() );

return Helpers.invokeAs(context, clazz.getSuperClass(), self, name, arg, block);
}
@@ -350,11 +332,8 @@ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule claz

@JRubyMethod
public static IRubyObject extended(ThreadContext context, IRubyObject self, IRubyObject object) {
if ( ! (self instanceof RubyModule) ) {
throw context.runtime.newTypeError(self, context.runtime.getModule());
}
RubyClass singleton = object.getSingletonClass();
singleton.include(new IRubyObject[] { self });
singleton.include(context, self);
return singleton;
}

@@ -376,11 +355,11 @@ public static IRubyObject impl(ThreadContext context, IRubyObject self, IRubyObj
else {
methodNames = args.clone();
Arrays.sort(methodNames); // binarySearch needs a sorted array
// RubySymbol implements a Java compareTo thus will allways work
// RubySymbol implements a Java compareTo thus will always work
}

RubyClass implClass = RubyClass.newClass(runtime, runtime.getObject());
implClass.include(new IRubyObject[] { self });
implClass.include(context, self);

final IRubyObject implObject = implClass.callMethod(context, "new");

@@ -389,7 +368,7 @@ public static IRubyObject impl(ThreadContext context, IRubyObject self, IRubyObj
return implObject;
}

private static class BlockInterfaceImpl extends org.jruby.internal.runtime.methods.JavaMethod {
private static final class BlockInterfaceImpl extends org.jruby.internal.runtime.methods.JavaMethod {

private final IRubyObject[] methodNames; // RubySymbol[]
private final Block implBlock;
@@ -456,8 +435,8 @@ private static JavaClass getJavaClassForInterface(final IRubyObject module) {
return (JavaClass) module.getInstanceVariables().getInstanceVariable("@java_class");
}

private static RubyArray getJavaInterfaces(final IRubyObject module) {
return (RubyArray) module.getInstanceVariables().getInstanceVariable("@java_interfaces");
private static RubyArray getJavaInterfaces(final IRubyObject clazz) {
return (RubyArray) clazz.getInstanceVariables().getInstanceVariable("@java_interfaces");
}

private static RubyArray getInterfaceModules(final IRubyObject module) {
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/javasupport/Java.java
Original file line number Diff line number Diff line change
@@ -1143,7 +1143,7 @@ public static IRubyObject new_proxy_instance2(IRubyObject recv, final IRubyObjec
return newInterfaceImpl(wrapper, interfaces);
}

public static IRubyObject newInterfaceImpl(final IRubyObject wrapper, Class[] interfaces) {
public static JavaObject newInterfaceImpl(final IRubyObject wrapper, Class[] interfaces) {
final Ruby runtime = wrapper.getRuntime();

final int length = interfaces.length;
36 changes: 18 additions & 18 deletions core/src/main/java/org/jruby/javasupport/JavaObject.java
Original file line number Diff line number Diff line change
@@ -111,12 +111,12 @@ public static IRubyObject wrap(final ThreadContext context,
}

@Override
public Class<?> getJavaClass() {
public final Class<?> getJavaClass() {
Object dataStruct = dataGetStruct();
return dataStruct != null ? dataStruct.getClass() : Void.TYPE;
}

public Object getValue() {
public final Object getValue() {
return dataGetStruct();
}

@@ -253,50 +253,50 @@ public IRubyObject is_java_proxy() {

@JRubyMethod(name = "synchronized")
public final IRubyObject ruby_synchronized(ThreadContext context, Block block) {
Object lock = getValue();
final Object lock = getValue();
synchronized (lock != null ? lock : NULL_LOCK) {
return block.yield(context, null);
}
}

public static IRubyObject ruby_synchronized(ThreadContext context, Object lock, Block block) {
public static IRubyObject ruby_synchronized(ThreadContext context, final Object lock, Block block) {
synchronized (lock != null ? lock : NULL_LOCK) {
return block.yield(context, null);
}
}

@JRubyMethod
public IRubyObject marshal_dump() {
public IRubyObject marshal_dump(ThreadContext context) {
if (Serializable.class.isAssignableFrom(getJavaClass())) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);

oos.writeObject(getValue());
new ObjectOutputStream(baos).writeObject(getValue());

return getRuntime().newString(new ByteList(baos.toByteArray()));
} catch (IOException ioe) {
throw getRuntime().newIOErrorFromException(ioe);
return context.runtime.newString(new ByteList(baos.toByteArray(), false));
}
catch (IOException ex) {
throw context.runtime.newIOErrorFromException(ex);
}
} else {
throw getRuntime().newTypeError("no marshal_dump is defined for class " + getJavaClass());
}
throw context.runtime.newTypeError("no marshal_dump is defined for class " + getJavaClass());
}

@JRubyMethod
public IRubyObject marshal_load(ThreadContext context, IRubyObject str) {
try {
ByteList byteList = str.convertToString().getByteList();
ByteArrayInputStream bais = new ByteArrayInputStream(byteList.getUnsafeBytes(), byteList.getBegin(), byteList.getRealSize());
ObjectInputStream ois = new JRubyObjectInputStream(context.runtime, bais);

dataWrapStruct(ois.readObject());
dataWrapStruct(new JRubyObjectInputStream(context.runtime, bais).readObject());

return this;
} catch (IOException ioe) {
throw context.runtime.newIOErrorFromException(ioe);
} catch (ClassNotFoundException cnfe) {
throw context.runtime.newTypeError("Class not found unmarshaling Java type: " + cnfe.getLocalizedMessage());
}
catch (IOException ex) {
throw context.runtime.newIOErrorFromException(ex);
}
catch (ClassNotFoundException ex) {
throw context.runtime.newTypeError("Class not found unmarshaling Java type: " + ex.getLocalizedMessage());
}
}

66 changes: 66 additions & 0 deletions spec/java_integration/interfaces/comparison_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
require File.dirname(__FILE__) + "/../spec_helper"

java_import 'java_integration.fixtures.SingleMethodInterface'
java_import 'java_integration.fixtures.BeanLikeInterface'

describe 'interface comparison' do

it 'compares against interface like with an included module' do
value_holder1 = Class.new do
include SingleMethodInterface
def initialize(val)
@value = val
end
def callIt
@value
end
end

expect( value_holder1 < SingleMethodInterface ).to be true
expect( value_holder1 === SingleMethodInterface ).to be false
expect( SingleMethodInterface === value_holder1 ).to be false

val = value_holder1.new(111)
expect( val.class < SingleMethodInterface ).to be true
expect( SingleMethodInterface === val.class ).to be false
expect( SingleMethodInterface === val ).to be true
expect( val === SingleMethodInterface ).to be false
end

it 'compares with interface like with an included module' do
mod = Module.new { include Enumerable }
val = Class.new { include mod }.new

expect( val.class < Enumerable ).to be true
expect( Enumerable === val.class ).to be false
expect( val.class === Enumerable ).to be false
expect( Enumerable === val ).to be true
expect( val === Enumerable ).to be false
expect( val === val.class.new ).to be false
expect( val === mod ).to be false

mod = Module.new do
include BeanLikeInterface
end
sup = Class.new do
include mod, java.lang.Runnable, java.lang.Iterable # includes Enumerable
def each; yield nil end
end
child = Class.new(sup) { include java.lang.Cloneable; def getValue; 1; end }
obj = child.new

expect( child < BeanLikeInterface ).to be true
expect( obj.class < java.lang.Runnable ).to be true
expect( java.lang.Cloneable === obj ).to be true
expect( java.lang.Iterable === obj ).to be true
expect( BeanLikeInterface === obj ).to be true
expect( Enumerable === obj ).to be true
expect( obj === BeanLikeInterface ).to be false

expect( obj === obj ).to be true
expect( obj === sup.new ).to be false
expect( obj === java.lang.Runnable.impl {} ).to be false # was true in < 9.1 !
expect( obj === java.util.HashSet.new ).to be false # was true in < 9.1 !
end

end
5 changes: 3 additions & 2 deletions spec/java_integration/interfaces/implementation_spec.rb
Original file line number Diff line number Diff line change
@@ -43,8 +43,9 @@ def callIt
end

it "should be kind_of? the interface" do
expect(@value_holder1.new(1)).to be_kind_of(SingleMethodInterface)
expect(SingleMethodInterface === @value_holder1.new(1)).to be true
expect( @value_holder1.new(1) ).to be_kind_of SingleMethodInterface
expect( @value_holder1.new(1) ).to be_a SingleMethodInterface
expect( SingleMethodInterface === @value_holder1.new(1) ).to be true
end

it "should be implemented with 'include InterfaceClass'" do
84 changes: 84 additions & 0 deletions test/jruby/test_include.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
require 'test/unit'

class TestInclude < Test::Unit::TestCase
module X ; end

class Q
def foo arg = []
arg << :Q
end
end

class Y < Q ; include X end

module A
def foo arg = []
arg << :A ; super
end
end

module X ; include A end

module Z
def foo arg = []
arg << :Z ; super
end
end

class Y ; include Z end

class Y ; include X end

def test_include_order
pend '[:A, :Z, :Q] on JRuby GH-1938' if defined? JRUBY_VERSION
assert_equal [:Z, :A, :Q], Y.new.foo
end

module M1 ; V = 123 end
module M2 ; V = 456 end

class Foo
include M1

def self.get; return V end
end

def test_including_module_busts_constant_caches
assert_equal 123, Foo.get
Foo.send(:include, M2)
assert_equal 456, Foo.get
end

class Bar
include M1, M2
end

def test_multi_include
assert_equal 123, Bar::V
assert_equal Bar, Bar.send(:include)
assert_equal Bar, Bar.send(:include, Comparable, Enumerable)

assert_equal Object, Object.include
assert_equal Object, Object.include(*[])
end

# JRUBY-3036
def test_included_does_not_hit_each_class
ObjectSpace.each_object(Class) do |cls|
if cls < Top
assert cls.top
end
end
end

end

module IncludedTestCaseModule; end

class Top
def self.top; true; end
end

class Next < Top
include IncludedTestCaseModule
end
22 changes: 0 additions & 22 deletions test/jruby/test_included_in_object_space.rb

This file was deleted.

25 changes: 0 additions & 25 deletions test/jruby/test_including_module_busts_constant_caches.rb

This file was deleted.

3 changes: 3 additions & 0 deletions test/truffle/compiler/pe/core/string_pe.rb
Original file line number Diff line number Diff line change
@@ -49,3 +49,6 @@
example "'abc'.getbyte(0) == 97", true
example "'abc'.getbyte(-1) == 99", true
example "'abc'.getbyte(10_000) == nil", true

example "14.to_s.length", 2
counter_example "14.to_s.getbyte(0)", '1'.ord # Doesn't work becuase the bytes are only populated on demand and so aren't constant
44 changes: 28 additions & 16 deletions tool/jt.rb
Original file line number Diff line number Diff line change
@@ -110,40 +110,50 @@ def self.git_branch
def self.mangle_for_env(name)
name.upcase.tr('-', '_')
end

def self.find_jvmci
jvmci_locations = [
ENV['JVMCI_DIR'],
ENV["JVMCI_DIR#{mangle_for_env(git_branch)}"]
].compact.map { |path| File.expand_path(path, JRUBY_DIR) }

def self.find_graal_parent
graal = File.expand_path('../../../../../graal-compiler', find_graal)
raise "couldn't find graal - set GRAAL_BIN, and you need to use a checkout of Graal, not a build" unless Dir.exist?(graal)
graal
end
not_found = -> {
raise "couldn't find JVMCI"
}

def self.find_graal_mx
mx = File.expand_path('../../../../../../mx/mx', find_graal)
raise "couldn't find mx - set GRAAL_BIN, and you need to use a checkout of Graal, not a build" unless File.executable?(mx)
mx
jvmci_locations.find(not_found) do |location|
Dir.exist?(location)
end
end

def self.igv_running?
`ps ax`.include? 'IdealGraphVisualizer'
`ps ax`.include?('IdealGraphVisualizer')
end

def self.ensure_igv_running
unless igv_running?
Dir.chdir(find_graal_parent + "/../jvmci") do
spawn "#{find_graal_mx} --vm server igv", pgroup: true
Dir.chdir(find_jvmci) do
spawn 'mx --vm server igv', pgroup: true
end

sleep 5
puts
puts
puts "-------------"
puts "Waiting for IGV start"
puts "The first time you run IGV it may take several minutes to download dependencies and compile"
puts "Press enter when you see the IGV window"
puts "-------------"
puts
puts
$stdin.gets

sleep 3

until igv_running?
puts 'still waiting for IGV to appear in ps ax...'
sleep 3
end

puts 'just a few more seconds...'
sleep 6
end
end

@@ -312,6 +322,8 @@ def help
puts ' GRAAL_BIN GraalVM executable (java command) to use'
puts ' GRAAL_BIN_...git_branch_name... GraalVM executable to use for a given branch'
puts ' branch names are mangled - eg truffle-head becomes GRAAL_BIN_TRUFFLE_HEAD'
puts ' JVMCI_DIR JMVCI repository checkout to use when running IGV (mx must already be on the $PATH)'
puts ' JVMCI_DIR_...git_branch_name... JMVCI repository to use for a given branch'
puts ' GRAAL_JS_JAR The location of trufflejs.jar'
puts ' SULONG_DIR The location of a built checkout of the Sulong repository'
end
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.rope.LazyIntRope;
import org.jruby.truffle.core.rope.RopeConstants;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyNode;
@@ -1068,15 +1069,18 @@ public InspectNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@TruffleBoundary
@Specialization
public DynamicObject inspect(int n) {
return create7BitString(Integer.toString(n), USASCIIEncoding.INSTANCE);
return createString(new LazyIntRope(n));
}

@TruffleBoundary
@Specialization
public DynamicObject inspect(long n) {
if (CoreLibrary.fitsIntoInteger(n)) {
return inspect((int) n);
}

return create7BitString(Long.toString(n), USASCIIEncoding.INSTANCE);
}

@@ -1122,16 +1126,15 @@ public ToSNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@TruffleBoundary
@Specialization
public DynamicObject toS(int n, NotProvided base) {
return create7BitString(Integer.toString(n), USASCIIEncoding.INSTANCE);
return createString(new LazyIntRope(n));
}

@TruffleBoundary
@Specialization
public DynamicObject toS(long n, NotProvided base) {
if (n >= Integer.MIN_VALUE && n <= Integer.MAX_VALUE) {
if (CoreLibrary.fitsIntoInteger(n)) {
return toS((int) n, base);
}

82 changes: 82 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/rope/LazyIntRope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.rope;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.nodes.ExplodeLoop;
import org.jcodings.Encoding;
import org.jcodings.specific.USASCIIEncoding;

import java.nio.charset.StandardCharsets;

public class LazyIntRope extends LazyRope {

final int value;

public LazyIntRope(int value) {
this(value, USASCIIEncoding.INSTANCE, length(value));
}

protected LazyIntRope(int value, Encoding encoding, int length) {
super(encoding, length, length);
this.value = value;
assert Integer.toString(value).length() == length;
}

@ExplodeLoop
private static int length(int value) {
if (value < 0) {
/*
* We can't represent -Integer.MIN_VALUE, and we're about to multiple by 10 to add the space needed for the
* negative character, so handle both of those out-of-range cases.
*/

if (value <= -1000000000) {
return 11;
}

value = -value;
value *= 10;
}

// If values were evenly distributed it would make more sense to do this in the reverse order, but they aren't

for (int n = 1, limit = 10; n < 10; n++, limit *= 10) {
if (value < limit) {
return n;
}
}

return 10;
}

@Override
public Rope withEncoding(Encoding newEncoding, CodeRange newCodeRange) {
if (newCodeRange != getCodeRange()) {
CompilerDirectives.transferToInterpreter();
throw new UnsupportedOperationException("Cannot fast-path updating encoding with different code range.");
}

return new LazyIntRope(value, newEncoding, length(value));
}

@Override
public byte[] fulfill() {
if (bytes == null) {
bytes = Integer.toString(value).getBytes(StandardCharsets.US_ASCII);
}

return bytes;
}

public int getValue() {
return value;
}
}
46 changes: 46 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/rope/LazyRope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2016 Oracle and/or its affiliates. All rights reserved. This
* code is released under a tri EPL/GPL/LGPL license. You can use it,
* redistribute it and/or modify it under the terms of the:
*
* Eclipse Public License version 1.0
* GNU General Public License version 2
* GNU Lesser General Public License version 2.1
*/
package org.jruby.truffle.core.rope;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import org.jcodings.Encoding;

public abstract class LazyRope extends Rope {

protected LazyRope(Encoding encoding, int byteLength, int characterLength) {
super(encoding, CodeRange.CR_7BIT, true, byteLength, characterLength, 1, null);
}

@Override
protected byte getByteSlow(int index) {
return getBytes()[index];
}

public byte[] getBytes() {
if (bytes == null) {
doFulfill();
}

return bytes;
}

@TruffleBoundary
private void doFulfill() {
bytes = fulfill();
}

protected abstract byte[] fulfill();

@Override
public String toString() {
return RopeOperations.decodeUTF8(this);
}

}
4 changes: 2 additions & 2 deletions truffle/src/main/java/org/jruby/truffle/core/rope/Rope.java
Original file line number Diff line number Diff line change
@@ -22,7 +22,7 @@ public abstract class Rope {
private final int characterLength;
private final int ropeDepth;
private int hashCode = 0;
private byte[] bytes;
protected byte[] bytes;

protected Rope(Encoding encoding, CodeRange codeRange, boolean singleByteOptimizable, int byteLength, int characterLength, int ropeDepth, byte[] bytes) {
this.encoding = encoding;
@@ -54,7 +54,7 @@ public final byte[] getRawBytes() {
return bytes;
}

public final byte[] getBytes() {
public byte[] getBytes() {
if (bytes == null) {
bytes = RopeOperations.flattenBytes(this);
}
26 changes: 26 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/rope/RopeNodes.java
Original file line number Diff line number Diff line change
@@ -687,6 +687,27 @@ public DynamicObject debugPrintRepeatingRope(RepeatingRope rope, int currentLeve
return nil();
}

@TruffleBoundary
@Specialization
public DynamicObject debugPrintLazyInt(LazyIntRope rope, int currentLevel, boolean printString) {
printPreamble(currentLevel);

// Converting a rope to a java.lang.String may populate the byte[], so we need to query for the array status beforehand.
final boolean bytesAreNull = rope.getRawBytes() == null;

System.err.println(String.format("%s (%s; BN: %b; BL: %d; CL: %d; CR: %s; V: %d, D: %d)",
printString ? rope.toString() : "<skipped>",
rope.getClass().getSimpleName(),
bytesAreNull,
rope.byteLength(),
rope.characterLength(),
rope.getCodeRange(),
rope.getValue(),
rope.depth()));

return nil();
}

private void printPreamble(int level) {
if (level > 0) {
for (int i = 0; i < level; i++) {
@@ -783,6 +804,11 @@ public int getByte(Rope rope, int index) {
return rope.getRawBytes()[index] & 0xff;
}

@Specialization(guards = "rope.getRawBytes() == null")
public int getByte(LazyRope rope, int index) {
return rope.getBytes()[index] & 0xff;
}

@Specialization(guards = "rope.getRawBytes() == null")
public int getByteSubstringRope(SubstringRope rope, int index,
@Cached("createBinaryProfile()") ConditionProfile childRawBytesNullProfile) {
Original file line number Diff line number Diff line change
@@ -164,8 +164,9 @@ public static String decodeRope(Ruby runtime, Rope value) {
}

return builder.toString();
}
else {
} else if (value instanceof LazyIntRope) {
return Integer.toString(((LazyIntRope) value).getValue());
} else {
throw new RuntimeException("Decoding to String is not supported for rope of type: " + value.getClass().getName());
}
}
@@ -285,6 +286,8 @@ public static void visitBytes(Rope rope, BytesVisitor visitor, int offset, int l
} else {
visitBytes(child, visitor, 0, remainingEnd);
}
} else if (rope instanceof LazyRope) {
visitor.accept(rope.getBytes(), offset, length);
} else {
throw new UnsupportedOperationException("Don't know how to visit rope of type: " + rope.getClass().getName());
}
@@ -350,6 +353,11 @@ public static byte[] flattenBytes(Rope rope) {
continue;
}

// Force lazy ropes
if (current instanceof LazyRope) {
current.getBytes();
}

if (current.getRawBytes() != null) {
// In the absence of any SubstringRopes, we always take the full contents of the current rope.
if (substringLengths.isEmpty()) {
@@ -562,6 +570,8 @@ public static int hashForRange(Rope rope, int startingHashCode, int offset, int
}

return hash;
} else if (rope instanceof LazyRope) {
return hashCodeForLeafRope(rope.getBytes(), startingHashCode, offset, length);
} else {
throw new RuntimeException("Hash code not supported for rope of type: " + rope.getClass().getName());
}
@@ -664,7 +674,7 @@ public static Rope format(RubyContext context, Object... values) {
valueRope = StringOperations.encodeRope(decodeRope(context.getJRubyRuntime(), stringRope), UTF8Encoding.INSTANCE);
}
} else if (value instanceof Integer) {
valueRope = StringOperations.encodeRope(Integer.toString((int) value), UTF8Encoding.INSTANCE, CodeRange.CR_7BIT);
valueRope = new LazyIntRope((int) value);
} else if (value instanceof String) {
valueRope = context.getRopeTable().getRope((String) value);
} else {

0 comments on commit 610d2a5

Please sign in to comment.