Skip to content

Commit

Permalink
Showing 72 changed files with 1,974 additions and 1,503 deletions.
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyFile.java
Original file line number Diff line number Diff line change
@@ -586,7 +586,7 @@ public static IRubyObject chmod(ThreadContext context, IRubyObject recv, IRubyOb
return runtime.newFixnum(count);
}

@JRubyMethod(required = 3, rest = true, meta = true)
@JRubyMethod(required = 2, rest = true, meta = true)
public static IRubyObject chown(ThreadContext context, IRubyObject recv, IRubyObject[] args) {
Ruby runtime = context.runtime;

18 changes: 14 additions & 4 deletions core/src/main/java/org/jruby/RubyInstanceConfig.java
Original file line number Diff line number Diff line change
@@ -1407,7 +1407,15 @@ public void setProfilingService( String service ) {
}

private static ClassLoader setupLoader() {
return RubyInstanceConfig.class.getClassLoader();
ClassLoader loader = RubyInstanceConfig.class.getClassLoader();

// loader can be null for example when jruby comes from the boot-classLoader

if (loader == null) {
loader = Thread.currentThread().getContextClassLoader();
}

return loader;
}

////////////////////////////////////////////////////////////////////////////
@@ -1454,9 +1462,11 @@ private static ClassLoader setupLoader() {
private ProfileOutput profileOutput = new ProfileOutput(System.err);
private String profilingService;

private ClassLoader thisLoader = setupLoader();
// thisLoader can be null for example when jruby comes from the boot-classLoader
private ClassLoader loader = thisLoader == null ? Thread.currentThread().getContextClassLoader() : thisLoader;
private ClassLoader loader = setupLoader();

public ClassLoader getCurrentThreadClassLoader() {
return Thread.currentThread().getContextClassLoader();
}

// from CommandlineParser
private List<String> loadPaths = new ArrayList<String>();
6 changes: 3 additions & 3 deletions core/src/main/java/org/jruby/ir/IRBuilder.java
Original file line number Diff line number Diff line change
@@ -782,7 +782,7 @@ public Operand buildAnd(final AndNode andNode) {
public Operand buildArray(Node node) {
List<Operand> elts = new ArrayList<>();
for (Node e: node.childNodes())
elts.add(build(e));
elts.add(copyAndReturnValue(build(e)));

return copyAndReturnValue(new Array(elts));
}
@@ -2393,10 +2393,10 @@ public Operand buildHash(HashNode hashNode) {
splatKeywordArgument = build(pair.getValue());
break;
} else {
keyOperand = build(key);
keyOperand = copyAndReturnValue(build(key));
}

args.add(new KeyValuePair<>(keyOperand, build(pair.getValue())));
args.add(new KeyValuePair<Operand, Operand>(keyOperand, copyAndReturnValue(build(pair.getValue()))));
}

if (splatKeywordArgument != null) { // splat kwargs merge with any explicit kwargs
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/ir/IRManager.java
Original file line number Diff line number Diff line change
@@ -18,7 +18,7 @@
public class IRManager {
public static final String SAFE_COMPILER_PASSES = "";
public static final String DEFAULT_COMPILER_PASSES = "OptimizeTempVarsPass,LocalOptimizationPass";
public static final String DEFAULT_JIT_PASSES = "DeadCodeElimination,AddLocalVarLoadStoreInstructions,OptimizeDynScopesPass,AddCallProtocolInstructions,EnsureTempsAssigned";
public static final String DEFAULT_JIT_PASSES = "OptimizeDelegationPass,DeadCodeElimination,AddLocalVarLoadStoreInstructions,OptimizeDynScopesPass,AddCallProtocolInstructions,EnsureTempsAssigned";
public static final String DEFAULT_INLINING_COMPILER_PASSES = "LocalOptimizationPass";

private int dummyMetaClassCount = 0;
2 changes: 2 additions & 0 deletions core/src/main/java/org/jruby/ir/IRScope.java
Original file line number Diff line number Diff line change
@@ -566,6 +566,8 @@ private void optimizeSimpleScopes() {
// stymied by escaped bindings. We can also eliminate
// dynscopes for these scopes.
if (!isUnsafeScope() && !flags.contains(REQUIRES_DYNSCOPE)) {
if (flags.contains(RECEIVES_CLOSURE_ARG))
(new OptimizeDelegationPass()).run(this);
(new DeadCodeElimination()).run(this);
(new OptimizeDynScopesPass()).run(this);
}
100 changes: 100 additions & 0 deletions core/src/main/java/org/jruby/ir/passes/OptimizeDelegationPass.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package org.jruby.ir.passes;

import org.jruby.ir.IRClosure;
import org.jruby.ir.IRScope;
import org.jruby.ir.IRFlags;
import org.jruby.ir.instructions.*;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.Variable;
import org.jruby.ir.representations.BasicBlock;

import java.util.*;

public class OptimizeDelegationPass extends CompilerPass {
public static List<Class<? extends CompilerPass>> DEPENDENCIES = Arrays.<Class<? extends CompilerPass>>asList(CFGBuilder.class);

@Override
public String getLabel() {
return "Delegated Variable Removal";
}

@Override
public List<Class<? extends CompilerPass>> getDependencies() {
return DEPENDENCIES;
}

@Override
public Object execute(IRScope s, Object... data) {
for (IRClosure c: s.getClosures()) {
run(c, false, true);
}

s.computeScopeFlags();

if (s.getFlags().contains(IRFlags.BINDING_HAS_ESCAPED))
return null;

if (!s.getFlags().contains(IRFlags.RECEIVES_CLOSURE_ARG))
return null;

optimizeDelegatedVars(s);

return true;
}

@Override
public boolean invalidate(IRScope s) {
// Not reversible right now
return false;
}

private static void optimizeDelegatedVars(IRScope s) {
Map<Operand, Operand> unusedExplicitBlocks = new HashMap<Operand, Operand>();

for (BasicBlock bb: s.cfg().getBasicBlocks()) {
for (Instr i: bb.getInstrs()) {
if (i instanceof ReifyClosureInstr) {
ReifyClosureInstr ri = (ReifyClosureInstr) i;
unusedExplicitBlocks.put(ri.getResult(), ri.getSource());
} else {
Iterator<Operand> it = unusedExplicitBlocks.keySet().iterator();
while (it.hasNext()) {
Variable explicitBlock = (Variable) it.next();
if (usesVariableAsNonClosureArg(i, explicitBlock)) {
it.remove();
}
}
}
}
}

for (BasicBlock bb: s.cfg().getBasicBlocks()) {
ListIterator<Instr> instrs = bb.getInstrs().listIterator();
while (instrs.hasNext()) {
Instr i = instrs.next();
if (i instanceof ReifyClosureInstr) {
ReifyClosureInstr ri = (ReifyClosureInstr) i;
Variable procVar = ri.getResult();
Operand blockVar = unusedExplicitBlocks.get(procVar);

if (blockVar != null) {
ri.markDead();
instrs.set(new CopyInstr(procVar, blockVar));
}
}
}
}
}

private static boolean usesVariableAsNonClosureArg(Instr i, Variable v) {
List<Variable> usedVariables = i.getUsedVariables();
if (usedVariables.contains(v)) {
if (i instanceof ClosureAcceptingInstr) {
return usedVariables.indexOf(v) != usedVariables.lastIndexOf(v) ||
v != ((ClosureAcceptingInstr) i).getClosureArg();
} else
return true;
}
return false;
}
}
61 changes: 43 additions & 18 deletions core/src/main/java/org/jruby/ir/passes/OptimizeTempVarsPass.java
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
import org.jruby.ir.IRClosure;
import org.jruby.ir.IRScope;
import org.jruby.ir.instructions.*;
import org.jruby.ir.operands.ImmutableLiteral;
import org.jruby.ir.operands.Operand;
import org.jruby.ir.operands.TemporaryVariable;
import org.jruby.ir.operands.Variable;
@@ -123,13 +124,26 @@ else if (use != NopInstr.NOP && def != null && def != NopInstr.NOP && i instance
if (!(use instanceof ReturnInstr)) {
CopyInstr ci = (CopyInstr)i;
Operand src = ci.getSource();
i.markDead();
instrs.remove();
// Only tmp vars are in SSA form post IR-building and it is safe to
// replace uses with defs without examining intervening instrs. But,
// not true for local vars and other operands that use local vars.
// a = 0
// %v_1 = a
// a = 1
// x = %v_1
// In that snippet, it would be buggy to rewrite it to:
// a = 0
// a = 1
// x = a
if (src instanceof TemporaryVariable || src instanceof ImmutableLiteral) {
i.markDead();
instrs.remove();

// Fix up use
Map<Operand, Operand> copyMap = new HashMap<>();
copyMap.put(v, src);
use.simplifyOperands(copyMap, true);
// Fix up use
Map<Operand, Operand> copyMap = new HashMap<>();
copyMap.put(v, src);
use.simplifyOperands(copyMap, true);
}
}
}
}
@@ -138,25 +152,36 @@ else if (use != NopInstr.NOP && def != null && def != NopInstr.NOP && i instance
// 2: x = %v
// If %v is not used anywhere else, the result of 1. can be updated to use x and 2. can be removed
//
// NOTE: consider this pattern:
// %v = <operand> (copy instr)
// x = %v
// This code will have been captured in the previous if branch which would have deleted %v = 5
// Hence the check for whether the src def instr is dead
// CAVEATS:
// --------
// 1. We only do this if 'x' is a temporary variable since only tmp vars are in SSA form.
// %v = ...(not a copy-1)
// x = .. (not a copy-2)
// x = %v
// In that snippet above, it would be buggy to replace it with:
// x = ...(not a copy-1)
// x = .. (not a copy-2)
//
// 2. Consider this pattern
// %v = <operand> (copy instr)
// x = %v
// This code will have been captured in the previous if branch which would have deleted %v = 5
// Hence the check for whether the src def instr is dead
else if (i instanceof CopyInstr) {
CopyInstr ci = (CopyInstr)i;
Operand src = ci.getSource();
if (src instanceof TemporaryVariable) {
TemporaryVariable vsrc = (TemporaryVariable)src;
Instr use = tmpVarUses.get(vsrc);
Instr def = tmpVarDefs.get(vsrc);
if ((use != null && use != NopInstr.NOP) && (def != null && def != NopInstr.NOP)) {
if (!def.isDead()) {
// Fix up def
((ResultInstr) def).updateResult(ci.getResult());
ci.markDead();
instrs.remove();
}
if (use != null && use != NopInstr.NOP &&
def != null && def != NopInstr.NOP &&
!def.isDead() && ((ResultInstr)def).getResult() instanceof TemporaryVariable)
{
// Fix up def
((ResultInstr) def).updateResult(ci.getResult());
ci.markDead();
instrs.remove();
}
}
}
1 change: 0 additions & 1 deletion core/src/main/java/org/jruby/util/cli/Options.java
Original file line number Diff line number Diff line change
@@ -138,7 +138,6 @@ public class Options {
public static final Option<Integer> TRUFFLE_HASHES_SMALL = integer(TRUFFLE, "truffle.hashes.small", 3, "Maximum size of a Hash to consider small for optimisations.");

public static final Option<Boolean> TRUFFLE_LOAD_CORE = bool(TRUFFLE, "truffle.load_core", true, "Load the Truffle core library.");
public static final Option<Boolean> TRUFFLE_PROC_BINDING = bool(TRUFFLE, "truffle.proc.binding", true, "Enable Proc#binding.");

public static final Option<Integer> TRUFFLE_PASSALOT = integer(TRUFFLE, "truffle.passalot", 0, "Probabilty between 0 and 100 to randomly insert Thread.pass at a given line.");
public static final Option<Integer> TRUFFLE_STACK_SERVER_PORT = integer(TRUFFLE, "truffle.stack_server_port", 0, "Port number to run an HTTP server on that returns stack traces");
1 change: 1 addition & 0 deletions lib/ruby/truffle/mri/enumerator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Provided by default
9 changes: 9 additions & 0 deletions lib/ruby/truffle/shims/thread.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Copyright (c) 2015 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

# Empty thread file - everything is loaded by default at the moment
17 changes: 17 additions & 0 deletions spec/regression/GH-2574_dont_clobber_variable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# https://github.com/jruby/jruby/issues/2574
describe 'Local variable assignments should not get clobbered' do
it 'returns the right value for array literals' do
a = 0
b = [a,a=1]
expect(b).to eq([0,1])
end

it 'returns the right value for hash literals' do
a = 0
b = { a => a, (a = 1) => a } # => { 1 => 1 } (MRI: {0=>0, 1=>1})
c = { a => a, a => (a = 2) } # => { 2 => 2 } (MRI: {1=>2})
expect(b).to eq({0=>0, 1=>1})
expect(c).to eq({1=>2})
end
end

4 changes: 0 additions & 4 deletions spec/truffle/tags/core/array/eql_tags.txt

This file was deleted.

4 changes: 0 additions & 4 deletions spec/truffle/tags/core/array/equal_value_tags.txt

This file was deleted.

23 changes: 0 additions & 23 deletions spec/truffle/tags/core/array/flatten_tags.txt
Original file line number Diff line number Diff line change
@@ -1,28 +1,5 @@
fails:Array#flatten returns a one-dimensional flattening recursively
fails:Array#flatten takes an optional argument that determines the level of recursion
fails:Array#flatten returns dup when the level of recursion is 0
fails:Array#flatten ignores negative levels
fails:Array#flatten tries to convert passed Objects to Integers using #to_int
fails:Array#flatten raises a TypeError when the passed Object can't be converted to an Integer
fails:Array#flatten does not call flatten on elements
fails:Array#flatten raises an ArgumentError on recursive arrays
fails:Array#flatten flattens any element which responds to #to_ary, using the return value of said method
fails:Array#flatten returns subclass instance for Array subclasses
fails:Array#flatten returns a tainted array if self is tainted
fails:Array#flatten returns an untrusted array if self is untrusted
fails:Array#flatten with a non-Array object in the Array ignores the return value of #to_ary if it is nil
fails:Array#flatten with a non-Array object in the Array raises a TypeError if the return value of #to_ary is not an Array
fails:Array#flatten! modifies array to produce a one-dimensional flattening recursively
fails:Array#flatten! returns self if made some modifications
fails:Array#flatten! returns nil if no modifications took place
fails:Array#flatten! should not check modification by size
fails:Array#flatten! takes an optional argument that determines the level of recursion
fails:Array#flatten! returns nil when the level of recursion is 0
fails:Array#flatten! treats negative levels as no arguments
fails:Array#flatten! tries to convert passed Objects to Integers using #to_int
fails:Array#flatten! raises a TypeError when the passed Object can't be converted to an Integer
fails:Array#flatten! does not call flatten! on elements
fails:Array#flatten! raises an ArgumentError on recursive arrays
fails:Array#flatten! flattens any elements which responds to #to_ary, using the return value of said method
fails:Array#flatten! raises a RuntimeError on frozen arrays when the array is modified
fails:Array#flatten! raises a RuntimeError on frozen arrays when the array would not be modified
1 change: 1 addition & 0 deletions spec/truffle/tags/core/array/hash_tags.txt
Original file line number Diff line number Diff line change
@@ -5,3 +5,4 @@ fails:Array#hash calls to_int on result of calling hash on each element
fails:Array#hash ignores array class differences
fails:Array#hash returns same hash code for arrays with the same content
fails:Array#hash returns the same value if arrays are #eql?
fails:Array#hash properly handles recursive arrays
2 changes: 0 additions & 2 deletions spec/truffle/tags/core/array/intersection_tags.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
fails:Array#& creates an array with no duplicates
fails:Array#& creates an array with elements in order they are first encountered
fails:Array#& properly handles recursive arrays
fails:Array#& determines equivalence between elements in the sense of eql?
6 changes: 1 addition & 5 deletions spec/truffle/tags/core/array/minus_tags.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
fails:Array#- tries to convert the passed arguments to Arrays using #to_ary
fails:Array#- does not return subclass instance for Array subclasses
fails:Array#- does not call to_ary on array subclasses
fails:Array#- removes an item identified as equivalent via #hash and #eql?
fails:Array#- doesn't remove an item with the same hash but not #eql?
fails:Array#- properly handles recursive arrays
12 changes: 0 additions & 12 deletions spec/truffle/tags/core/array/permutation_tags.txt

This file was deleted.

13 changes: 0 additions & 13 deletions spec/truffle/tags/core/encoding/default_internal_tags.txt

This file was deleted.

6 changes: 0 additions & 6 deletions spec/truffle/tags/core/encoding/find_tags.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
fails:Encoding.find returns the corresponding Encoding object if given a valid encoding name
fails:Encoding.find returns the corresponding Encoding object if given a valid alias name
fails:Encoding.find raises a TypeError if passed a Symbol
fails:Encoding.find returns the passed Encoding object
fails:Encoding.find accepts encoding names as Strings
fails:Encoding.find accepts any object as encoding name, if it responds to #to_str
fails:Encoding.find is case insensitive
fails:Encoding.find raises an ArgumentError if the given encoding does not exist
fails:Encoding.find supports the 'locale' encoding alias
fails:Encoding.find returns default external encoding for the 'external' encoding alias
fails:Encoding.find returns default internal encoding for the 'internal' encoding alias
fails:Encoding.find uses default external encoding for the 'filesystem' encoding alias
fails(windows - Encoding.aliases):Encoding.find needs to be reviewed for spec completeness
2 changes: 0 additions & 2 deletions spec/truffle/tags/core/string/append_tags.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
fails:String#<< converts the given argument to a String using to_str
fails:String#<< raises a RuntimeError when self is frozen
fails:String#<< taints self if other is tainted
fails:String#<< untrusts self if other is untrusted
fails:String#<< with Integer concatencates the argument interpreted as a codepoint
19 changes: 0 additions & 19 deletions spec/truffle/tags/core/string/chomp_tags.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
fails:String#chomp when passed no argument removes one trailing newline
fails:String#chomp when passed no argument removes one trailing carrige return, newline pair
fails:String#chomp when passed no argument taints the result if self is tainted
fails:String#chomp when passed no argument returns subclass instances when called on a subclass
fails:String#chomp when passed no argument removes trailing characters that match $/ when it has been assigned a value
fails:String#chomp when passed nil does not modify the String
fails:String#chomp when passed nil returns a copy of the String
fails:String#chomp when passed nil taints the result if self is tainted
fails:String#chomp when passed nil returns an empty String when self is empty
fails:String#chomp when passed '' removes a final newline
fails:String#chomp when passed '' removes a final carriage return, newline
fails:String#chomp when passed '' removes more than one trailing newlines
@@ -15,37 +9,24 @@ fails:String#chomp when passed '' taints the result if self is tainted
fails:String#chomp when passed '\n' removes one trailing carriage return
fails:String#chomp when passed '\n' removes one trailing carrige return, newline pair
fails:String#chomp when passed '\n' taints the result if self is tainted
fails:String#chomp when passed an Object calls #to_str to convert to a String
fails:String#chomp when passed an Object raises a TypeError if #to_str does not return a String
fails:String#chomp when passed a String taints the result if self is tainted
fails:String#chomp when passed a String does not taint the result when the argument is tainted
fails:String#chomp! raises a RuntimeError on a frozen instance when it is modified
fails:String#chomp! raises a RuntimeError on a frozen instance when it would not be modified
fails:String#chomp! when passed no argument returns nil if self is not modified
fails:String#chomp! when passed no argument removes one trailing newline
fails:String#chomp! when passed no argument removes one trailing carrige return, newline pair
fails:String#chomp! when passed no argument returns nil when self is empty
fails:String#chomp! when passed no argument removes trailing characters that match $/ when it has been assigned a value
fails:String#chomp! when passed nil returns nil
fails:String#chomp! when passed nil returns nil when self is empty
fails:String#chomp! when passed '' removes a final newline
fails:String#chomp! when passed '' removes a final carriage return, newline
fails:String#chomp! when passed '' does not remove a final carriage return
fails:String#chomp! when passed '' removes more than one trailing newlines
fails:String#chomp! when passed '' removes more than one trailing carriage return, newline pairs
fails:String#chomp! when passed '' taints the result if self is tainted
fails:String#chomp! when passed '' returns nil when self is empty
fails:String#chomp! when passed '\n' removes one trailing newline
fails:String#chomp! when passed '\n' removes one trailing carriage return
fails:String#chomp! when passed '\n' removes one trailing carrige return, newline pair
fails:String#chomp! when passed '\n' taints the result if self is tainted
fails:String#chomp! when passed '\n' returns nil when self is empty
fails:String#chomp! when passed an Object calls #to_str to convert to a String
fails:String#chomp! when passed an Object raises a TypeError if #to_str does not return a String
fails:String#chomp! when passed a String removes the trailing characters if they match the argument
fails:String#chomp! when passed a String returns nil if the argument does not match the trailing characters
fails:String#chomp! when passed a String returns nil when self is empty
fails:String#chomp! when passed a String taints the result if self is tainted
fails:String#chomp! when passed a String does not taint the result when the argument is tainted
fails:String#chomp removes the final carriage return, newline from a non-ASCII String
fails:String#chomp! returns nil when the String is not modified
5 changes: 0 additions & 5 deletions spec/truffle/tags/core/string/comparison_tags.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
fails:String#<=> with String returns -1 when self is less than other
fails:String#<=> with String returns 1 when self is greater than other
fails:String#<=> with String considers string that comes lexicographically first to be less if strings have same size
fails:String#<=> with String compares shorter string with corresponding number of first chars of longer string
fails:String#<=> with String ignores encoding difference
fails:String#<=> with String compares the indices of the encodings when the strings have identical non-ASCII-compatible bytes
fails:String#<=> returns nil if its argument provides neither #to_str nor #<=>
fails:String#<=> uses the result of calling #to_str for comparison when #to_str is defined
fails:String#<=> uses the result of calling #<=> on its argument when #<=> is defined but #to_str is not
fails:String#<=> returns nil if argument also uses an inverse comparison for <=>
2 changes: 0 additions & 2 deletions spec/truffle/tags/core/string/concat_tags.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
fails:String#concat converts the given argument to a String using to_str
fails:String#concat raises a RuntimeError when self is frozen
fails:String#concat taints self if other is tainted
fails:String#concat untrusts self if other is untrusted
fails:String#concat with Integer concatencates the argument interpreted as a codepoint
1 change: 0 additions & 1 deletion spec/truffle/tags/core/string/element_set_tags.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
fails:String#[]= with Fixnum index taints self if other_str is tainted
fails:String#[]= with Fixnum index raises IndexError if the string index doesn't match a position in the string
fails:String#[]= with Fixnum index calls to_int on index
fails:String#[]= with Fixnum index calls #to_str to convert other to a String
fails:String#[]= with Fixnum index calls #to_int to convert the index
fails:String#[]= with Fixnum index raises a TypeError if #to_int does not return an Fixnum
fails:String#[]= with Fixnum index raises an IndexError if #to_int returns a value out of range
1 change: 0 additions & 1 deletion spec/truffle/tags/core/string/encode_tags.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
fails:String#encode when passed no options transcodes to Encoding.default_internal when set
fails:String#encode when passed no options transcodes a 7-bit String despite no generic converting being available
fails:String#encode when passed no options raises an Encoding::ConverterNotFoundError when no conversion is possible
fails:String#encode when passed to encoding calls #to_str to convert the object to an Encoding
fails:String#encode when passed to encoding transcodes a 7-bit String despite no generic converting being available
fails:String#encode when passed to encoding raises an Encoding::ConverterNotFoundError when no conversion is possible
fails:String#encode when passed to encoding raises an Encoding::ConverterNotFoundError for an invalid encoding
1 change: 0 additions & 1 deletion spec/truffle/tags/core/string/gsub_tags.txt
Original file line number Diff line number Diff line change
@@ -11,7 +11,6 @@ fails:String#gsub with pattern and replacement taints the result if the original
fails:String#gsub with pattern and replacement untrusts the result if the original string or replacement is untrusted
fails:String#gsub with pattern and replacement tries to convert pattern to a string using to_str
fails:String#gsub with pattern and replacement tries to convert replacement to a string using to_str
fails:String#gsub with pattern and replacement returns subclass instances when called on a subclass
fails:String#gsub with pattern and replacement sets $~ to MatchData of last match and nil when there's none
fails:String#gsub with pattern and Hash returns a copy of self with all occurrences of pattern replaced with the value of the corresponding hash key
fails:String#gsub with pattern and Hash ignores keys that don't correspond to matches
5 changes: 0 additions & 5 deletions spec/truffle/tags/core/string/start_with_tags.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1 @@
fails:String#start_with? returns true only if beginning match
fails:String#start_with? returns true only if any beginning match
fails:String#start_with? converts its argument using :to_str
fails:String#start_with? ignores arguments not convertible to string
fails:String#start_with? uses only the needed arguments
fails:String#start_with? works for multibyte strings
1 change: 0 additions & 1 deletion test/mri/excludes/TestFile.rb
Original file line number Diff line number Diff line change
@@ -7,6 +7,5 @@
exclude :test_gets_para_extended_file , "needs investigation"
exclude :test_open_nul, "needs investigation"
exclude :test_read_all_extended_file , "needs investigation"
exclude :test_s_chown , "needs investigation"
exclude :test_stat , "birthtime does not match MRI behavior (#2152)"
exclude :test_truncate_wbuf, "fails on Linux"
1 change: 0 additions & 1 deletion test/pom.rb
Original file line number Diff line number Diff line change
@@ -345,7 +345,6 @@
'<arg value="-J-server" />' +
'<arg value="-J-G:+TruffleCompilationExceptionsAreThrown" />' +
'<arg value="-X+T" />' +
'<arg value="-Xtruffle.proc.binding=false" />' +
'<arg value="-Xtruffle.debug.enable_assert_constant=true" />' +
'<arg value="test/truffle/pe/pe.rb" />' +
'</exec>' +
1 change: 0 additions & 1 deletion test/pom.xml
Original file line number Diff line number Diff line change
@@ -810,7 +810,6 @@
<arg value="-J-server" />
<arg value="-J-G:+TruffleCompilationExceptionsAreThrown" />
<arg value="-X+T" />
<arg value="-Xtruffle.proc.binding=false" />
<arg value="-Xtruffle.debug.enable_assert_constant=true" />
<arg value="test/truffle/pe/pe.rb" />
</exec>
2 changes: 1 addition & 1 deletion test/truffle/pe/core/hash_pe.rb
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@
truffle_assert_constant hash[:b]
end

example "mapped to an Array and indexed by a constant" do
broken_example "mapped to an Array and indexed by a constant" do
hash = {a: 0, b: 1, c: 2}
array = hash.map { |k, v| v }
truffle_assert_constant array[0]
2 changes: 1 addition & 1 deletion test/truffle/pe/macro/pushing_pixels_pe.rb
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@ def method_missing(method, *args)

PETests.tests do

example "A set of constants used in a literal hash, mapped to an array, indexed, used in an array literal, sorted, indexed, and added, all via method_missing, respond_to? and send" do
broken_example "A set of constants used in a literal hash, mapped to an array, indexed, used in an array literal, sorted, indexed, and added, all via method_missing, respond_to? and send" do
bar = PushingPixelsFixtures::Bar.new
truffle_assert_constant bar.foo(14, 8, 6)
end
35 changes: 32 additions & 3 deletions tool/jt.rb
Original file line number Diff line number Diff line change
@@ -10,6 +10,9 @@

# Recommended: function jt { ruby PATH/TO/jruby/tool/jt.rb $@; }

require 'fileutils'
require 'digest/sha1'

JRUBY_DIR = File.expand_path('../..', __FILE__)

module Utilities
@@ -43,6 +46,10 @@ def self.find_bench
end
end

def self.jruby_version
File.read('VERSION').strip
end

end

module ShellUtils
@@ -101,6 +108,7 @@ def help
puts ' by bench9000, eg all, classic, chunky, 3, 5, 10, 15 - default is 5'
puts 'jt findbugs run findbugs'
puts 'jt findbugs report run findbugs and generate an HTML report'
puts 'jt install ..../graal/mx/suite.py install a JRuby distribution into an mx suite'
puts
puts 'you can also put build or rebuild in front of any command'
end
@@ -154,7 +162,6 @@ def test_pe
run(*%w[
--graal
-J-G:+TruffleCompilationExceptionsAreThrown
-Xtruffle.proc.binding=false
-Xtruffle.debug.enable_assert_constant=true
test/truffle/pe/pe.rb])
end
@@ -193,14 +200,36 @@ def bench(command, *args)
def findbugs(report=nil)
case report
when 'report'
sh 'tool/truffle-findbugs.sh', '--report' rescue nil
sh 'open', 'truffle-findbugs-report.html' rescue nil
sh 'tool/truffle-findbugs.sh', '--report'
sh 'open', 'truffle-findbugs-report.html'
when nil
sh 'tool/truffle-findbugs.sh'
else
raise ArgumentError, report
end
end

def install(arg)
case arg
when /.*suite.*\.py$/
suite_file = arg
mvn '-Pcomplete'
sh 'tool/remove-bundled-truffle.sh'
jar_name = "jruby-complete-no-truffle-#{Utilities.jruby_version}.jar"
source_jar_name = "maven/jruby-complete/target/#{jar_name}"
shasum = Digest::SHA1.hexdigest File.read(source_jar_name)
shasum_jar_name = "jruby-complete-no-truffle-#{Utilities.jruby_version}-#{shasum}.jar"
FileUtils.cp source_jar_name, "#{File.expand_path('../..', suite_file)}/lib/#{shasum_jar_name}"
suite_lines = File.readlines(suite_file)
line_index = suite_lines.find_index { |line| line.start_with? ' "path" : "lib/jruby-complete-no-truffle' }
suite_lines[line_index] = " \"path\" : \"lib/#{shasum_jar_name}\",\n"
suite_lines[line_index + 1] = " \#\"urls\" : [\"http://lafo.ssw.uni-linz.ac.at/truffle/ruby/#{shasum_jar_name}\"],\n"
suite_lines[line_index + 2] = " \"sha1\" : \"#{shasum}\"\n"
File.write(suite_file, suite_lines.join())
else
raise ArgumentError, kind
end
end
end

class JT
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ public Object read(VirtualFrame frame, RubyArray array, int index) {
readNode = insert(ArrayReadNormalizedNodeFactory.create(getContext(), getSourceSection(), null, null));
}

final int normalizedIndex = array.normaliseIndex(index);
final int normalizedIndex = array.normalizeIndex(index);

return readNode.executeRead(frame, array, normalizedIndex);
}
Original file line number Diff line number Diff line change
@@ -39,46 +39,58 @@ public ArrayReadNormalizedNode(ArrayReadNormalizedNode prev) {

public abstract Object executeRead(VirtualFrame frame, RubyArray array, int index);

// Anything from a null array is nil

@Specialization(
guards="isNullArray"
)
public RubyNilClass readNull(RubyArray array, int index) {
return getContext().getCoreLibrary().getNilObject();
}

// Read within the bounds of an array with actual storage

@Specialization(
guards={"isNormalisedInBounds", "isIntegerArray"}
guards={"isInBounds", "isIntArray"}
)
public int readIntegerInBounds(RubyArray array, int index) {
public int readIntInBounds(RubyArray array, int index) {
return ((int[]) array.getStore())[index];
}

@Specialization(
guards={"isNormalisedInBounds", "isLongArray"}
guards={"isInBounds", "isLongArray"}
)
public long readLongInBounds(RubyArray array, int index) {
return ((long[]) array.getStore())[index];
}

@Specialization(
guards={"isNormalisedInBounds", "isDoubleArray"}
guards={"isInBounds", "isDoubleArray"}
)
public double readDoubleInBounds(RubyArray array, int index) {
return ((double[]) array.getStore())[index];
}

@Specialization(
guards={"isNormalisedInBounds", "isObjectArray"}
guards={"isInBounds", "isObjectArray"}
)
public Object readObjectInBounds(RubyArray array, int index) {
return ((Object[]) array.getStore())[index];
}

// Reading out of bounds is nil of any array is nil - cannot contain isNullArray

@Specialization(
guards="!isNormalisedInBounds"
guards="!isInBounds"
)
public RubyNilClass readOutOfBounds(RubyArray array, int index) {
return getContext().getCoreLibrary().getNilObject();
}

// Guards

public static boolean isInBounds(RubyArray array, int index) {
return index >= 0 && index < array.getSize();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2015 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.nodes.array;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyArray;

@NodeChildren({
@NodeChild(value="array", type=RubyNode.class),
@NodeChild(value="index", type=RubyNode.class),
@NodeChild(value="length", type=RubyNode.class)
})
public abstract class ArrayReadSliceDenormalizedNode extends RubyNode {

@Child private ArrayReadSliceNormalizedNode readNode;

public ArrayReadSliceDenormalizedNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public ArrayReadSliceDenormalizedNode(ArrayReadSliceDenormalizedNode prev) {
super(prev);
readNode = prev.readNode;
}

public abstract Object executeReadSlice(VirtualFrame frame, RubyArray array, int index, int length);

@Specialization
public Object read(VirtualFrame frame, RubyArray array, int index, int length) {
if (readNode == null) {
CompilerDirectives.transferToInterpreter();
readNode = insert(ArrayReadSliceNormalizedNodeFactory.create(getContext(), getSourceSection(), null, null, null));
}

final int normalizedIndex = array.normalizeIndex(index);

return readNode.executeReadSlice(frame, array, normalizedIndex, length);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2015 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.nodes.array;

import com.oracle.truffle.api.dsl.ImportGuards;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.core.ArrayGuards;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyNilClass;

import java.util.Arrays;

@NodeChildren({
@NodeChild(value="array", type=RubyNode.class),
@NodeChild(value="index", type=RubyNode.class),
@NodeChild(value="length", type=RubyNode.class)
})
@ImportGuards(ArrayGuards.class)
public abstract class ArrayReadSliceNormalizedNode extends RubyNode {

public ArrayReadSliceNormalizedNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

public ArrayReadSliceNormalizedNode(ArrayReadSliceNormalizedNode prev) {
super(prev);
}

public abstract Object executeReadSlice(VirtualFrame frame, RubyArray array, int index, int length);

// Index out of bounds or negative length always gives you nil

@Specialization(
guards={"!indexInBounds"}
)
public RubyNilClass readIndexOutOfBounds(RubyArray array, int index, int length) {
return getContext().getCoreLibrary().getNilObject();
}

@Specialization(
guards={"!lengthPositive"}
)
public RubyNilClass readNegativeLength(RubyArray array, int index, int length) {
return getContext().getCoreLibrary().getNilObject();
}

// If these guards pass for a null array you can only get an empty array

@Specialization(
guards={"indexInBounds", "lengthPositive", "isNullArray"}
)
public RubyArray readNull(RubyArray array, int index, int length) {
return new RubyArray(array.getLogicalClass(), null, 0);
}

// Reading within bounds on an array with actual storage

@Specialization(
guards={"indexInBounds", "lengthPositive", "endInBounds", "isIntArray"}
)
public RubyArray readIntInBounds(RubyArray array, int index, int length) {
return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((int[]) array.getStore(), index, index + length), length);
}

@Specialization(
guards={"indexInBounds", "lengthPositive", "endInBounds", "isLongArray"}
)
public RubyArray readLongInBounds(RubyArray array, int index, int length) {
return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((long[]) array.getStore(), index, index + length), length);
}

@Specialization(
guards={"indexInBounds", "lengthPositive", "endInBounds", "isDoubleArray"}
)
public RubyArray readDoubleInBounds(RubyArray array, int index, int length) {
return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((double[]) array.getStore(), index, index + length), length);
}

@Specialization(
guards={"indexInBounds", "lengthPositive", "endInBounds", "isObjectArray"}
)
public RubyArray readObjectInBounds(RubyArray array, int index, int length) {
return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((Object[]) array.getStore(), index, index + length), length);
}

// Reading beyond upper bounds on an array with actual storage needs clamping

@Specialization(
guards={"indexInBounds", "lengthPositive", "!endInBounds", "isIntArray"}
)
public RubyArray readIntOutOfBounds(RubyArray array, int index, int length) {
final int clampedLength = Math.min(array.getSize(), index + length) - index;

return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((int[]) array.getStore(), index, index + clampedLength), clampedLength);
}

@Specialization(
guards={"indexInBounds", "lengthPositive", "!endInBounds", "isLongArray"}
)
public RubyArray readLongOutOfBounds(RubyArray array, int index, int length) {
final int clampedLength = Math.min(array.getSize(), index + length) - index;

return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((long[]) array.getStore(), index, index + clampedLength), clampedLength);
}

@Specialization(
guards={"indexInBounds", "lengthPositive", "!endInBounds", "isDoubleArray"}
)
public RubyArray readDoubleOutOfBounds(RubyArray array, int index, int length) {
final int clampedLength = Math.min(array.getSize(), index + length) - index;

return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((double[]) array.getStore(), index, index + clampedLength), clampedLength);
}

@Specialization(
guards={"indexInBounds", "lengthPositive", "!endInBounds", "isObjectArray"}
)
public RubyArray readObjectOutOfBounds(RubyArray array, int index, int length) {
final int clampedLength = Math.min(array.getSize(), index + length) - index;

return new RubyArray(array.getLogicalClass(),
Arrays.copyOfRange((Object[]) array.getStore(), index, index + clampedLength), clampedLength);
}

// Guards

public static boolean indexInBounds(RubyArray array, int index, int length) {
return index >= 0 && index <= array.getSize();
}

public static boolean lengthPositive(RubyArray array, int index, int length) {
return length >= 0;
}

public static boolean endInBounds(RubyArray array, int index, int length) {
return index + length < array.getSize();
}

}
Original file line number Diff line number Diff line change
@@ -17,16 +17,30 @@
public abstract class PrimitiveArrayNodeFactory {

/**
* Create a node to read from an array with a constant denormalised index.
* Create a node to read from an array with a constant denormalized index.
*/
public static RubyNode read(RubyContext context, SourceSection sourceSection, RubyNode array, int index) {
final RubyNode literalIndex = new FixnumLiteralNode.IntegerFixnumLiteralNode(context, sourceSection, index);

if (index > 0) {
if (index >= 0) {
return ArrayReadNormalizedNodeFactory.create(context, sourceSection, array, literalIndex);
} else {
return ArrayReadDenormalizedNodeFactory.create(context, sourceSection, array, literalIndex);
}
}

/**
* Create a node to read a slice from an array with a constant denormalized start and exclusive end.
*/
public static RubyNode readSlice(RubyContext context, SourceSection sourceSection, RubyNode array, int start, int exclusiveEnd) {
final RubyNode literalStart = new FixnumLiteralNode.IntegerFixnumLiteralNode(context, sourceSection, start);
final RubyNode literalExclusiveEnd = new FixnumLiteralNode.IntegerFixnumLiteralNode(context, sourceSection, exclusiveEnd);

if (start >= 0 && exclusiveEnd >= 0) {
return ArrayReadSliceNormalizedNodeFactory.create(context, sourceSection, array, literalStart, literalExclusiveEnd);
} else {
return ArrayReadSliceDenormalizedNodeFactory.create(context, sourceSection, array, literalStart, literalExclusiveEnd);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (c) 2015 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.nodes.coerce;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNodeFactory;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyString;

@NodeChild(value = "child", type = RubyNode.class)
public abstract class ToStrNode extends RubyNode {

@Child private CallDispatchHeadNode toStr;

public ToStrNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
toStr = DispatchHeadNodeFactory.createMethodCall(context);
}

public ToStrNode(ToStrNode prev) {
super(prev);
toStr = prev.toStr;
}

@Specialization
public RubyString coerceRubyString(RubyString string) {
return string;
}

@Specialization(guards = "!isRubyString")
public RubyString coerceObject(VirtualFrame frame, Object object) {
notDesignedForCompilation();

final Object coerced;

try {
coerced = toStr.call(frame, object, "to_str", null);
} catch (RaiseException e) {
if (e.getRubyException().getLogicalClass() == getContext().getCoreLibrary().getNoMethodErrorClass()) {
CompilerDirectives.transferToInterpreter();

throw new RaiseException(
getContext().getCoreLibrary().typeErrorNoImplicitConversion(object, "String", this));
} else {
throw e;
}
}

if (coerced instanceof RubyString) {
return (RubyString) coerced;
} else {
CompilerDirectives.transferToInterpreter();

throw new RaiseException(
getContext().getCoreLibrary().typeErrorBadCoercion(object, "String", "to_str", coerced, this));
}
}

@Override
public abstract RubyString executeRubyString(VirtualFrame frame);

public abstract RubyString executeRubyString(VirtualFrame frame, Object object);

@Override
public final Object execute(VirtualFrame frame) {
return executeRubyString(frame);
}
}
Original file line number Diff line number Diff line change
@@ -73,13 +73,13 @@ public static boolean areBothObject(RubyArray a, RubyArray b) {
return a.getStore() instanceof Object[] && b.getStore() instanceof Object[];
}

// New names
// New names being used for the new primitive nodes - old guards will be removed over time

public static boolean isNullArray(RubyArray array) {
return array.getStore() == null;
}

public static boolean isIntegerArray(RubyArray array) {
public static boolean isIntArray(RubyArray array) {
return array.getStore() instanceof int[];
}

@@ -95,8 +95,4 @@ public static boolean isObjectArray(RubyArray array) {
return array.getStore() instanceof Object[];
}

public static boolean isNormalisedInBounds(RubyArray array, int index) {
return index >= 0 && index < array.getSize();
}

}
1,016 changes: 122 additions & 894 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/ArrayNodes.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -281,7 +281,7 @@ public InstanceEvalNode(InstanceEvalNode prev) {
public Object instanceEval(VirtualFrame frame, Object receiver, RubyString string, UndefinedPlaceholder block) {
notDesignedForCompilation();

return getContext().eval(string.getBytes(), receiver, this);
return getContext().instanceEval(string.getBytes(), receiver, this);
}

@Specialization
Original file line number Diff line number Diff line change
@@ -231,7 +231,7 @@ private static boolean assertNoAmbiguousDefaultArguments(MethodDetails methodDet
}

if (undefined && object) {
System.out.println("Ambiguous default argument " + (argc - i) + " in " + node.getCanonicalName());
throw new RuntimeException("Ambiguous default argument " + (argc - i) + " in " + node.getCanonicalName());
}
}
}
Original file line number Diff line number Diff line change
@@ -10,7 +10,10 @@
package org.jruby.truffle.nodes.core;

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.ConditionProfile;
import org.jcodings.Encoding;
@@ -20,6 +23,9 @@
import org.jcodings.util.CaseInsensitiveBytesHash;
import org.jcodings.util.Hash;
import org.jruby.runtime.encoding.EncodingService;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.coerce.ToStrNode;
import org.jruby.truffle.nodes.coerce.ToStrNodeFactory;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.core.RubyArray;
import org.jruby.truffle.runtime.core.RubyEncoding;
@@ -195,6 +201,8 @@ public RubyEncoding defaultExternal(RubyEncoding encoding) {
@CoreMethod(names = "default_internal=", onSingleton = true, required = 1)
public abstract static class SetDefaultInternalNode extends CoreMethodNode {

@Child private ToStrNode toStrNode;

public SetDefaultInternalNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}
@@ -204,7 +212,7 @@ public SetDefaultInternalNode(SetDefaultInternalNode prev) {
}

@Specialization
public RubyEncoding defaultExternal(RubyEncoding encoding) {
public RubyEncoding defaultInternal(RubyEncoding encoding) {
notDesignedForCompilation();

getContext().getRuntime().setDefaultInternalEncoding(encoding.getEncoding());
@@ -213,18 +221,34 @@ public RubyEncoding defaultExternal(RubyEncoding encoding) {
}

@Specialization
public RubyNilClass defaultExternal(RubyNilClass encoding) {
public RubyNilClass defaultInternal(RubyNilClass encoding) {
notDesignedForCompilation();

getContext().getRuntime().setDefaultInternalEncoding(ASCIIEncoding.INSTANCE);
getContext().getRuntime().setDefaultInternalEncoding(null);

return encoding;
}

@Specialization(guards = { "!isRubyEncoding", "!isRubyNilClass" })
public RubyString defaultInternal(VirtualFrame frame, Object encoding) {
notDesignedForCompilation();

if (toStrNode == null) {
CompilerDirectives.transferToInterpreter();
toStrNode = insert(ToStrNodeFactory.create(getContext(), getSourceSection(), null));
}

final RubyString encodingName = toStrNode.executeRubyString(frame, encoding);
getContext().getRuntime().setDefaultInternalEncoding(RubyEncoding.getEncoding(encodingName.toString()).getEncoding());

return encodingName;
}

}

@CoreMethod(names = "find", onSingleton = true, required = 1)
public abstract static class FindNode extends CoreMethodNode {
@NodeChild(value = "name")
public abstract static class FindNode extends RubyNode {

public FindNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
@@ -234,6 +258,10 @@ public FindNode(FindNode prev) {
super(prev);
}

@CreateCast("name") public RubyNode coerceNameToString(RubyNode name) {
return ToStrNodeFactory.create(getContext(), getSourceSection(), name);
}

@Specialization
public RubyEncoding find(RubyString name) {
notDesignedForCompilation();
66 changes: 18 additions & 48 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/KernelNodes.java
Original file line number Diff line number Diff line change
@@ -12,6 +12,9 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.dsl.CreateCast;
import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.NodeChildren;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.*;
import com.oracle.truffle.api.nodes.UnexpectedResultException;
@@ -25,6 +28,8 @@
import org.jruby.truffle.nodes.cast.BooleanCastNodeFactory;
import org.jruby.truffle.nodes.cast.NumericToFloatNode;
import org.jruby.truffle.nodes.cast.NumericToFloatNodeFactory;
import org.jruby.truffle.nodes.coerce.ToStrNode;
import org.jruby.truffle.nodes.coerce.ToStrNodeFactory;
import org.jruby.truffle.nodes.control.WhileNode;
import org.jruby.truffle.nodes.core.KernelNodesFactory.SameOrEqualNodeFactory;
import org.jruby.truffle.nodes.dispatch.*;
@@ -517,19 +522,27 @@ public Object dup(VirtualFrame frame, RubyBasicObject self) {
}

@CoreMethod(names = "eval", isModuleFunction = true, required = 1, optional = 3)
public abstract static class EvalNode extends CoreMethodNode {
@NodeChildren({
@NodeChild(value = "source", type = RubyNode.class),
@NodeChild(value = "binding", type = RubyNode.class),
@NodeChild(value = "filename", type = RubyNode.class),
@NodeChild(value = "lineNumber", type = RubyNode.class)
})
public abstract static class EvalNode extends RubyNode {

@Child private CallDispatchHeadNode toStr;
@Child private BindingNode bindingNode;

public EvalNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
toStr = DispatchHeadNodeFactory.createMethodCall(context);
}

public EvalNode(EvalNode prev) {
super(prev);
toStr = prev.toStr;
}

@CreateCast("source") public RubyNode coerceSourceToString(RubyNode source) {
return ToStrNodeFactory.create(getContext(), getSourceSection(), source);
}

protected RubyBinding getCallerBinding(VirtualFrame frame) {
@@ -578,57 +591,14 @@ public Object eval(RubyString source, RubyBinding binding, RubyString filename,
return getContext().eval(source.getBytes(), binding, false, this);
}

@Specialization(guards = "!isRubyString(arguments[0])")
public Object eval(VirtualFrame frame, RubyBasicObject object, UndefinedPlaceholder binding, UndefinedPlaceholder filename, UndefinedPlaceholder lineNumber) {
notDesignedForCompilation();

return evalCoerced(frame, object, getCallerBinding(frame), true, filename, lineNumber);
}

@Specialization(guards = "!isRubyString(arguments[0])")
public Object eval(VirtualFrame frame, RubyBasicObject object, RubyBinding binding, UndefinedPlaceholder filename, UndefinedPlaceholder lineNumber) {
notDesignedForCompilation();

return evalCoerced(frame, object, binding, false, filename, lineNumber);
}

@Specialization(guards = "!isRubyBinding(arguments[1])")
public Object eval(RubyBasicObject source, RubyBasicObject badBinding, UndefinedPlaceholder filename, UndefinedPlaceholder lineNumber) {
@Specialization(guards = "!isRubyBinding(binding)")
public Object eval(RubyString source, RubyBasicObject badBinding, UndefinedPlaceholder filename, UndefinedPlaceholder lineNumber) {
throw new RaiseException(
getContext().getCoreLibrary().typeError(
String.format("wrong argument type %s (expected binding)",
badBinding.getLogicalClass().getName()),
this));
}

private Object evalCoerced(VirtualFrame frame, RubyBasicObject object, RubyBinding binding, boolean ownScopeForAssignments, UndefinedPlaceholder filename, UndefinedPlaceholder lineNumber) {
Object coerced;

try {
coerced = toStr.call(frame, object, "to_str", null);
} catch (RaiseException e) {
if (e.getRubyException().getLogicalClass() == getContext().getCoreLibrary().getNoMethodErrorClass()) {
throw new RaiseException(
getContext().getCoreLibrary().typeError(
String.format("no implicit conversion of %s into String", object.getLogicalClass().getName()),
this));
} else {
throw e;
}
}

if (coerced instanceof RubyString) {
return getContext().eval(((RubyString) coerced).getBytes(), binding, ownScopeForAssignments, this);
} else {
throw new RaiseException(
getContext().getCoreLibrary().typeError(
String.format("can't convert %s to String (%s#to_str gives %s)",
object.getLogicalClass().getName(),
object.getLogicalClass().getName(),
getContext().getCoreLibrary().getLogicalClass(coerced).getName()),
this));
}
}
}

@CoreMethod(names = "exec", isModuleFunction = true, required = 1, argumentsAsArray = true)
Original file line number Diff line number Diff line change
@@ -436,7 +436,7 @@ private Object classEvalSource(VirtualFrame frame, RubyModule module, Source sou
return getContext().execute(getContext(), source, encoding, TranslatorDriver.ParserContext.MODULE, module, binding.getFrame(), this, new NodeWrapper() {
@Override
public RubyNode wrap(RubyNode node) {
return new SetMethodDeclarationContext(node.getContext(), node.getSourceSection(), "class_eval", node);
return new SetMethodDeclarationContext(node.getContext(), node.getSourceSection(), Visibility.PUBLIC, "class_eval", node);
}
});
}
Original file line number Diff line number Diff line change
@@ -61,11 +61,6 @@ public BindingNode(BindingNode prev) {

@Specialization
public Object binding(RubyProc proc) {
if (!RubyProc.PROC_BINDING) {
getContext().getWarnings().warn("Proc#binding disabled, returning nil. Use -Xtruffle.proc.binding=true to enable it.");
return getContext().getCoreLibrary().getNilObject();
}

final MaterializedFrame frame = proc.getDeclarationFrame();

return new RubyBinding(getContext().getCoreLibrary().getBindingClass(),
235 changes: 136 additions & 99 deletions truffle/src/main/java/org/jruby/truffle/nodes/core/StringNodes.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -20,7 +20,9 @@

import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyArguments;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.TruffleFatalException;
import org.jruby.truffle.runtime.core.RubyBasicObject;
import org.jruby.truffle.runtime.core.RubyModule;
import org.jruby.truffle.runtime.core.RubySymbol;
@@ -66,28 +68,28 @@ public RubySymbol execute(VirtualFrame frame) {
return getContext().newSymbol(method.getName());
}

private Visibility getVisibility(VirtualFrame frame, String name) {
private static Visibility getVisibility(Frame frame, String name) {
notDesignedForCompilation();

if (name.equals("initialize") || name.equals("initialize_copy") || name.equals("initialize_clone") || name.equals("initialize_dup") || name.equals("respond_to_missing?")) {
return Visibility.PRIVATE;
} else {
// Ignore scopes who do not have a visibility slot.
Visibility currentFrameVisibility = findVisibility(frame);
if (currentFrameVisibility != null) {
return currentFrameVisibility;
}

return Truffle.getRuntime().iterateFrames(new FrameInstanceVisitor<Visibility>() {
return getVisibility(frame);
}
}

@Override
public Visibility visitFrame(FrameInstance frameInstance) {
Frame frame = frameInstance.getFrame(FrameAccess.READ_ONLY, true);
return findVisibility(frame);
}
private static Visibility getVisibility(Frame frame) {
notDesignedForCompilation();

});
while (frame != null) {
Visibility visibility = findVisibility(frame);
if (visibility != null) {
return visibility;
}
frame = RubyArguments.getDeclarationFrame(frame.getArguments());
}

throw new UnsupportedOperationException("No declaration frame with visibility found");
}

private static Visibility findVisibility(Frame frame) {
Original file line number Diff line number Diff line change
@@ -23,11 +23,13 @@ public class SetMethodDeclarationContext extends RubyNode {

@Child private RubyNode child;

final Visibility visibility;
final String what;

public SetMethodDeclarationContext(RubyContext context, SourceSection sourceSection, String what, RubyNode child) {
public SetMethodDeclarationContext(RubyContext context, SourceSection sourceSection, Visibility visibility, String what, RubyNode child) {
super(context, sourceSection);
this.child = child;
this.visibility = visibility;
this.what = what;
}

@@ -39,7 +41,7 @@ public Object execute(VirtualFrame frame) {
Object oldVisibility = frame.getValue(slot);

try {
frame.setObject(slot, Visibility.PUBLIC);
frame.setObject(slot, visibility);

return child.execute(frame);
} finally {
Original file line number Diff line number Diff line change
@@ -109,8 +109,28 @@ public VMObjectEqualPrimitiveNode(VMObjectEqualPrimitiveNode prev) {
}

@Specialization
public Object vmObjectEqual(Object a, Object b) {
throw new UnsupportedOperationException("vm_object_equal");
public Object vmObjectEqual(boolean a, boolean b) {
return a == b;
}

@Specialization
public Object vmObjectEqual(int a, int b) {
return a == b;
}

@Specialization
public Object vmObjectEqual(long a, long b) {
return a == b;
}

@Specialization
public Object vmObjectEqual(double a, double b) {
return a == b;
}

@Specialization
public Object vmObjectEqual(RubyBasicObject a, RubyBasicObject b) {
return a == b;
}

}
Original file line number Diff line number Diff line change
@@ -38,16 +38,17 @@ public Object dispatchWithModifiedBlock(VirtualFrame frame, RubyProc block, Ruby
return dispatch.dispatchWithModifiedBlock(frame, block, modifiedBlock, argumentsObjects);
}

public Object dispatchWithModifiedSelf(VirtualFrame frame, RubyProc block, Object self, Object... argumentsObjects) {
public Object dispatchWithModifiedSelf(VirtualFrame currentFrame, RubyProc block, Object self, Object... argumentsObjects) {
// TODO: assumes this also changes the default definee.

Frame frame = block.getDeclarationFrame();
FrameSlot slot = frame.getFrameDescriptor().findOrAddFrameSlot(RubyModule.VISIBILITY_FRAME_SLOT_ID, "dynamic visibility for def", FrameSlotKind.Object);
Object oldVisibility = frame.getValue(slot);

try {
frame.setObject(slot, Visibility.PUBLIC);

return dispatch.dispatchWithModifiedSelf(frame, block, self, argumentsObjects);
return dispatch.dispatchWithModifiedSelf(currentFrame, block, self, argumentsObjects);
} finally {
frame.setObject(slot, oldVisibility);
}
18 changes: 9 additions & 9 deletions truffle/src/main/java/org/jruby/truffle/runtime/RubyContext.java
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@
import org.jcodings.specific.UTF8Encoding;
import org.jruby.Ruby;
import org.jruby.RubyNil;
import org.jruby.runtime.Visibility;
import org.jruby.runtime.builtin.IRubyObject;
import org.jruby.truffle.TruffleHooks;
import org.jruby.truffle.nodes.RubyNode;
@@ -115,7 +116,6 @@ public RubyContext(Ruby runtime) {

emptyShape = RubyBasicObject.LAYOUT.createShape(new RubyOperations(this));

// See note in CoreLibrary#initialize to see why we need to break this into two statements
coreLibrary = new CoreLibrary(this);
coreLibrary.initialize();

@@ -182,7 +182,7 @@ public void load(Source source, RubyNode currentNode, final NodeWrapper nodeWrap
final NodeWrapper loadWrapper = new NodeWrapper() {
@Override
public RubyNode wrap(RubyNode node) {
return new SetMethodDeclarationContext(node.getContext(), node.getSourceSection(), "load", node);
return new SetMethodDeclarationContext(node.getContext(), node.getSourceSection(), Visibility.PRIVATE, "load", node);
}
};

@@ -210,14 +210,14 @@ public RubySymbol newSymbol(ByteList name) {
return symbolTable.getSymbol(name);
}

public Object eval(ByteList code, RubyNode currentNode) {
public Object instanceEval(ByteList code, Object self, RubyNode currentNode) {
final Source source = Source.fromText(code, "(eval)");
return execute(this, source, code.getEncoding(), TranslatorDriver.ParserContext.TOP_LEVEL, coreLibrary.getMainObject(), null, currentNode, NodeWrapper.IDENTITY);
}

public Object eval(ByteList code, Object self, RubyNode currentNode) {
final Source source = Source.fromText(code, "(eval)");
return execute(this, source, code.getEncoding(), TranslatorDriver.ParserContext.TOP_LEVEL, self, null, currentNode, NodeWrapper.IDENTITY);
return execute(this, source, code.getEncoding(), TranslatorDriver.ParserContext.TOP_LEVEL, self, null, currentNode, new NodeWrapper() {
@Override
public RubyNode wrap(RubyNode node) {
return new SetMethodDeclarationContext(node.getContext(), node.getSourceSection(), Visibility.PUBLIC, "instance_eval", node);
}
});
}

public Object eval(ByteList code, RubyBinding binding, boolean ownScopeForAssignments, RubyNode currentNode) {
475 changes: 254 additions & 221 deletions truffle/src/main/java/org/jruby/truffle/runtime/core/CoreLibrary.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -179,11 +179,11 @@ public void slowPush(Object value) {
size++;
}

public int normaliseIndex(int index) {
return normaliseIndex(size, index);
public int normalizeIndex(int index) {
return normalizeIndex(size, index);
}

public static int normaliseIndex(int length, int index) {
public static int normalizeIndex(int length, int index) {
if (CompilerDirectives.injectBranchProbability(CompilerDirectives.UNLIKELY_PROBABILITY, index < 0)) {
return length + index;
} else {
@@ -300,6 +300,8 @@ public int getSize() {
}

private boolean verifyStore(Object store, int size) {
assert size >= 0;

assert store == null
|| store instanceof Object[]
|| store instanceof int[]
Original file line number Diff line number Diff line change
@@ -38,24 +38,24 @@ public class RubyClass extends RubyModule {
* This constructor supports initialization and solves boot-order problems and should not
* normally be used from outside this class.
*/
public static RubyClass createBootClass(RubyContext context, String name) {
return new RubyClass(context, null, null, name, false);
public static RubyClass createBootClass(RubyContext context, RubyClass classClass, String name) {
return new RubyClass(context, classClass, null, null, name, false);
}

public RubyClass(RubyContext context, RubyModule lexicalParent, RubyClass superclass, String name) {
this(context, lexicalParent, superclass, name, false);
this(context, superclass.getLogicalClass(), lexicalParent, superclass, name, false);
// Always create a class singleton class for normal classes for consistency.
ensureSingletonConsistency();
}

protected static RubyClass createSingletonClassOfObject(RubyContext context, RubyClass superclass, String name) {
// We also need to create the singleton class of a singleton class for proper lookup and consistency.
// See rb_singleton_class() documentation in MRI.
return new RubyClass(context, null, superclass, name, true).ensureSingletonConsistency();
return new RubyClass(context, superclass.getLogicalClass(), null, superclass, name, true).ensureSingletonConsistency();
}

protected RubyClass(RubyContext context, RubyModule lexicalParent, RubyClass superclass, String name, boolean isSingleton) {
super(context, context.getCoreLibrary().getClassClass(), lexicalParent, name, null);
protected RubyClass(RubyContext context, RubyClass classClass, RubyModule lexicalParent, RubyClass superclass, String name, boolean isSingleton) {
super(context, classClass, lexicalParent, name, null);
this.isSingleton = isSingleton;

if (superclass != null) {
@@ -108,7 +108,7 @@ private RubyClass createOneSingletonClass() {
}

metaClass = new RubyClass(getContext(),
null, singletonSuperclass, String.format("#<Class:%s>", getName()), true);
getLogicalClass(), null, singletonSuperclass, String.format("#<Class:%s>", getName()), true);

return metaClass;
}
@@ -164,7 +164,7 @@ public static class ClassAllocator implements Allocator {

@Override
public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, RubyNode currentNode) {
return new RubyClass(context, null, null, null, false);
return new RubyClass(context, context.getCoreLibrary().getClassClass(), null, null, null, false);
}

}
Original file line number Diff line number Diff line change
@@ -54,8 +54,8 @@ public static void storeAlias(String aliasName, RubyEncoding encoding) {
lookup.put(aliasName.toLowerCase(Locale.ENGLISH), encoding);
}

public static RubyEncoding newEncoding(RubyContext context, Encoding encoding, byte[] name, int p, int end, boolean dummy) {
return new RubyEncoding(context.getCoreLibrary().getEncodingClass(), encoding, new ByteList(name, p, end), dummy);
public static RubyEncoding newEncoding(RubyClass encodingClass, Encoding encoding, byte[] name, int p, int end, boolean dummy) {
return new RubyEncoding(encodingClass, encoding, new ByteList(name, p, end), dummy);
}

private RubyEncoding(RubyClass encodingClass, Encoding encoding, ByteList name, boolean dummy) {
Original file line number Diff line number Diff line change
@@ -16,11 +16,13 @@
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameInstance;
import com.oracle.truffle.api.frame.FrameSlot;
import com.oracle.truffle.api.frame.FrameSlotKind;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.utilities.CyclicAssumption;

import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.methods.AddMethodNode;
import org.jruby.truffle.nodes.objects.Allocator;
import org.jruby.truffle.runtime.*;
import org.jruby.truffle.runtime.control.RaiseException;
@@ -104,10 +106,6 @@ public static void debugModuleChain(RubyModule module) {
*/
private final Set<RubyModule> lexicalDependents = Collections.newSetFromMap(new WeakHashMap<RubyModule, Boolean>());

public RubyModule(RubyContext context, RubyModule lexicalParent, String name) {
this(context, lexicalParent, name, null);
}

public RubyModule(RubyContext context, RubyModule lexicalParent, String name, RubyNode currentNode) {
this(context, context.getCoreLibrary().getModuleClass(), lexicalParent, name, currentNode);
}
@@ -127,7 +125,11 @@ protected void getAdoptedByLexicalParent(RubyModule lexicalParent, RubyNode curr
lexicalParent.setConstant(currentNode, name, this);
lexicalParent.addLexicalDependent(this);

if (lexicalParent != context.getCoreLibrary().getObjectClass()) {
// Tricky, we need to compare with the Object class, but we only have a Module at hand.
RubyClass classClass = lexicalParent.getLogicalClass();
RubyClass objectClass = classClass.getSuperClass().getSuperClass();

if (lexicalParent != objectClass) {
name = lexicalParent.getName() + "::" + name;
}
}
@@ -365,8 +367,8 @@ public static void setCurrentVisibility(Visibility visibility) {
assert callerFrame != null;
assert callerFrame.getFrameDescriptor() != null;

final FrameSlot visibilitySlot = callerFrame.getFrameDescriptor().findFrameSlot(VISIBILITY_FRAME_SLOT_ID);
assert visibilitySlot != null : "no visibility slot";
final FrameSlot visibilitySlot = callerFrame.getFrameDescriptor().findOrAddFrameSlot(
RubyModule.VISIBILITY_FRAME_SLOT_ID, "visibility for frame", FrameSlotKind.Object);

callerFrame.setObject(visibilitySlot, visibility);
}
@@ -530,7 +532,7 @@ public static class ModuleAllocator implements Allocator {

@Override
public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, RubyNode currentNode) {
return new RubyModule(context, null, null);
return new RubyModule(context, null, null, currentNode);
}

}
Original file line number Diff line number Diff line change
@@ -19,15 +19,12 @@
import org.jruby.truffle.runtime.methods.MethodLike;
import org.jruby.truffle.runtime.methods.SharedMethodInfo;
import org.jruby.truffle.runtime.subsystems.ObjectSpaceManager;
import org.jruby.util.cli.Options;

/**
* Represents the Ruby {@code Proc} class.
*/
public class RubyProc extends RubyBasicObject implements MethodLike {

public static final boolean PROC_BINDING = Options.TRUFFLE_PROC_BINDING.load();

public static enum Type {
BLOCK, PROC, LAMBDA
}
Original file line number Diff line number Diff line change
@@ -163,8 +163,8 @@ public int length() {
return StringSupport.strLengthFromRubyString(this);
}

public int normaliseIndex(int index) {
return RubyArray.normaliseIndex(bytes.length(), index);
public int normalizeIndex(int index) {
return RubyArray.normalizeIndex(bytes.length(), index);
}

public int clampExclusiveIndex(int index) {
Original file line number Diff line number Diff line change
@@ -52,20 +52,11 @@ public boolean require(String path, String feature, RubyNode currentNode) throws
return true;
}

if (feature.equals("enumerator")) {
context.getWarnings().warn("enumerator not yet implemented");
return true;
}

if (feature.equals("rbconfig")) {
// Kernel#rbconfig is always there
return true;
}

if (feature.equals("thread")) {
return true;
}

if (feature.equals("time")) {
return true;
}
Original file line number Diff line number Diff line change
@@ -20,6 +20,7 @@
import org.jruby.ast.*;
import org.jruby.common.IRubyWarnings;
import org.jruby.lexer.yacc.InvalidSourcePosition;
import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.*;
import org.jruby.truffle.nodes.DefinedNode;
import org.jruby.truffle.nodes.ForNode;
@@ -1051,8 +1052,8 @@ public RubyNode visitDefsNode(org.jruby.ast.DefsNode node) {

final SingletonClassNode singletonClassNode = SingletonClassNodeFactory.create(context, sourceSection, objectNode);

return new SetMethodDeclarationContext(context, sourceSection, "defs",
translateMethodDefinition(sourceSection, singletonClassNode, node.getName(), node, node.getArgsNode(), node.getBodyNode()));
return new SetMethodDeclarationContext(context, sourceSection, Visibility.PUBLIC,
"defs", translateMethodDefinition(sourceSection, singletonClassNode, node.getName(), node, node.getArgsNode(), node.getBodyNode()));
}

protected RubyNode translateMethodDefinition(SourceSection sourceSection, RubyNode classNode, String methodName, org.jruby.ast.Node parseTree, org.jruby.ast.ArgsNode argsNode, org.jruby.ast.Node bodyNode) {
@@ -1492,8 +1493,31 @@ public RubyNode visitInstAsgnNode(org.jruby.ast.InstAsgnNode node) {
@Override
public RubyNode visitInstVarNode(org.jruby.ast.InstVarNode node) {
final SourceSection sourceSection = translate(node.getPosition());

// TODO CS 6-Feb-15 - this appears to be the name *with* sigil - need to clarify this

final String nameWithoutSigil = node.getName();

/*
* Rubinius uses the instance variable @total to store the size of an array. In order to use code that
* expects that we'll replace it statically with a call to Array#size. We also replace @tuple with
* self, and @start to be 0.
*/

if (sourceSection.getSource().getPath().equals("core:/jruby/truffle/core/rubinius/kernel/common/array.rb")) {
if (nameWithoutSigil.equals("@total")) {
return new RubyCallNode(context, sourceSection,
"size",
new SelfNode(context, sourceSection),
null,
false);
} else if (nameWithoutSigil.equals("@tuple")) {
return new SelfNode(context, sourceSection);
} else if (nameWithoutSigil.equals("@start")) {
return new FixnumLiteralNode.IntegerFixnumLiteralNode(context, sourceSection, 0);
}
}

final RubyNode receiver = new SelfNode(context, sourceSection);

return new ReadInstanceVariableNode(context, sourceSection, nameWithoutSigil, receiver, false);
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;

import org.jruby.runtime.Visibility;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.nodes.RubyRootNode;
import org.jruby.truffle.nodes.control.SequenceNode;
@@ -61,7 +62,7 @@ public MethodDefinitionNode compileClassNode(SourceSection sourceSection, String

body = new CatchReturnPlaceholderNode(context, sourceSection, body, environment.getReturnID());

body = new SetMethodDeclarationContext(context, sourceSection, name, body);
body = new SetMethodDeclarationContext(context, sourceSection, Visibility.PUBLIC, name, body);

final RubyRootNode rootNode = new RubyRootNode(context, sourceSection, environment.getFrameDescriptor(), environment.getSharedMethodInfo(), body);

Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@ public class TranslatorEnvironment {
private final boolean neverAssignInParentScope;

protected final TranslatorEnvironment parent;
private boolean needsDeclarationFrame = RubyProc.PROC_BINDING;
private boolean needsDeclarationFrame = true; // We keep the logic as we might do it differently one day.
private final SharedMethodInfo sharedMethodInfo;

private final String namedMethodName;
3 changes: 3 additions & 0 deletions truffle/src/main/ruby/jruby/truffle/core.rb
Original file line number Diff line number Diff line change
@@ -12,9 +12,11 @@
require_relative 'core/rubinius/api/kernel/common/type'

# Patch rubinius-core-api to make it work for us
require_relative 'core/rubinius/api/shims/array'
require_relative 'core/rubinius/api/shims/rubinius'
require_relative 'core/rubinius/api/shims/lookuptable'
require_relative 'core/rubinius/api/shims/thread'
require_relative 'core/rubinius/api/shims/tuple'
require_relative 'core/rubinius/api/shims/undefined'
require_relative 'core/rubinius/api/shims/metrics'

@@ -42,6 +44,7 @@
require_relative 'core/rubinius/kernel/common/kernel'
require_relative 'core/rubinius/kernel/common/comparable'
require_relative 'core/rubinius/kernel/common/numeric'
require_relative 'core/rubinius/kernel/common/identity_map'
require_relative 'core/rubinius/kernel/common/integer'
require_relative 'core/rubinius/kernel/common/fixnum'
require_relative 'core/rubinius/kernel/common/false'
5 changes: 3 additions & 2 deletions truffle/src/main/ruby/jruby/truffle/core/kernel.rb
Original file line number Diff line number Diff line change
@@ -8,8 +8,6 @@

module Kernel

module_function

def p(*args)
args.each do |arg|
print arg.inspect
@@ -18,6 +16,7 @@ def p(*args)

args.size <= 1 ? args.first : args
end
module_function :p

def puts(*args)
print "\n" if args.empty?
@@ -32,10 +31,12 @@ def puts(*args)
end
end
end
module_function :puts

def printf(*args)
print sprintf(*args)
end
module_function :printf

alias_method :trust, :untaint
alias_method :untrust, :taint
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright (c) 2015 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

module Rubinius
module Mirror
class Array

def self.reflect(array)
Array.new(array)
end

def initialize(array)
@array = array
end

def total
@array.size
end

def tuple
@array
end

def start
0
end

end
end
end

class Array

def new_reserved(count)
# TODO CS 6-Feb-15 do we want to reserve space or allow the runtime to optimise for us?
[]
end

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright (c) 2014 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

module Rubinius

class Tuple < Array

def copy_from(other, start, length, dest)
# TODO CS 6-Feb-15 use higher level indexing when it works
length.times do |n|
self[dest + n] = other[start + n]
end
end

end

end
Original file line number Diff line number Diff line change
@@ -26,6 +26,10 @@

# Only part of Rubinius' array.rb

# Rubinius uses the instance variable @total to store the size. We replace this
# in the translator with a call to size. We also replace the instance variable
# @tuple to be self, and @start to be 0.

class Array

def self.[](*args)
@@ -34,13 +38,13 @@ def self.[](*args)
ary
end

# Modified implementation until we support Rubinius::IdentityMap.
def &(other)
other = Rubinius::Type.coerce_to other, Array, :to_ary

array = []
im = Rubinius::IdentityMap.from other

each { |x| array << x if other.include? x }
each { |x| array << x if im.delete x }

array
end
@@ -74,4 +78,307 @@ def values_at(*args)
out
end

end
def first(n = undefined)
return at(0) if undefined.equal?(n)

n = Rubinius::Type.coerce_to_collection_index n
raise ArgumentError, "Size must be positive" if n < 0

Array.new self[0, n]
end

def hash
hash_val = size
mask = Fixnum::MAX >> 1

# This is duplicated and manually inlined code from Thread for performance
# reasons. Before refactoring it, please benchmark it and compare your
# refactoring against the original.

id = object_id
objects = Thread.current.recursive_objects

# If there is already an our version running...
if objects.key? :__detect_outermost_recursion__

# If we've seen self, unwind back to the outer version
if objects.key? id
raise Thread::InnerRecursionDetected
end

# .. or compute the hash value like normal
begin
objects[id] = true

each { |x| hash_val = ((hash_val & mask) << 1) ^ x.hash }
ensure
objects.delete id
end

return hash_val
else
# Otherwise, we're the outermost version of this code..
begin
objects[:__detect_outermost_recursion__] = true
objects[id] = true

each { |x| hash_val = ((hash_val & mask) << 1) ^ x.hash }

# An inner version will raise to return back here, indicating that
# the whole structure is recursive. In which case, abondon most of
# the work and return a simple hash value.
rescue Thread::InnerRecursionDetected
return size
ensure
objects.delete :__detect_outermost_recursion__
objects.delete id
end
end

return hash_val
end

def last(n=undefined)
if undefined.equal?(n)
return at(-1)
elsif size < 1
return []
end

n = Rubinius::Type.coerce_to_collection_index n
return [] if n == 0

raise ArgumentError, "count must be positive" if n < 0

n = size if n > size
Array.new self[-n..-1]
end

def permutation(num=undefined, &block)
return to_enum(:permutation, num) unless block_given?

if undefined.equal? num
num = @total
else
num = Rubinius::Type.coerce_to_collection_index num
end

if num < 0 || @total < num
# no permutations, yield nothing
elsif num == 0
# exactly one permutation: the zero-length array
yield []
elsif num == 1
# this is a special, easy case
each { |val| yield [val] }
else
# this is the general case
perm = Array.new(num)
used = Array.new(@total, false)

if block
# offensive (both definitions) copy.
offensive = dup
Rubinius.privately do
offensive.__permute__(num, perm, 0, used, &block)
end
else
__permute__(num, perm, 0, used, &block)
end
end

self
end

def __permute__(num, perm, index, used, &block)
# Recursively compute permutations of r elements of the set [0..n-1].
# When we have a complete permutation of array indexes, copy the values
# at those indexes into a new array and yield that array.
#
# num: the number of elements in each permutation
# perm: the array (of size num) that we're filling in
# index: what index we're filling in now
# used: an array of booleans: whether a given index is already used
#
# Note: not as efficient as could be for big num.
@total.times do |i|
unless used[i]
perm[index] = i
if index < num-1
used[i] = true
__permute__(num, perm, index+1, used, &block)
used[i] = false
else
yield values_at(*perm)
end
end
end
end
private :__permute__

def <=>(other)
other = Rubinius::Type.check_convert_type other, Array, :to_ary
return 0 if equal? other
return nil if other.nil?

total = Rubinius::Mirror::Array.reflect(other).total

Thread.detect_recursion self, other do
i = 0
count = total < @total ? total : @total

while i < count
order = self[i] <=> other[i]
return order unless order == 0

i += 1
end
end

# subtle: if we are recursing on that pair, then let's
# no go any further down into that pair;
# any difference will be found elsewhere if need be
@total <=> total
end

def -(other)
other = Rubinius::Type.coerce_to other, Array, :to_ary

array = []
im = Rubinius::IdentityMap.from other

each { |x| array << x unless im.include? x }

array
end

def |(other)
other = Rubinius::Type.coerce_to other, Array, :to_ary

im = Rubinius::IdentityMap.from self, other
im.to_array
end

def ==(other)
return true if equal?(other)
unless other.kind_of? Array
return false unless other.respond_to? :to_ary
return other == self
end

return false unless size == other.size

Thread.detect_recursion self, other do
m = Rubinius::Mirror::Array.reflect other

md = @tuple
od = m.tuple

i = @start
j = m.start

total = i + @total

while i < total
return false unless md[i] == od[j]
i += 1
j += 1
end
end

true
end

def eql?(other)
return true if equal? other
return false unless other.kind_of?(Array)
return false if @total != other.size

Thread.detect_recursion self, other do
i = 0
each do |x|
return false unless x.eql? other[i]
i += 1
end
end

true
end

def empty?
@total == 0
end

# Helper to recurse through flattening since the method
# is not allowed to recurse itself. Detects recursive structures.
def recursively_flatten(array, out, max_levels = -1)
modified = false

# Strict equality since < 0 means 'infinite'
if max_levels == 0
out.concat(array)
return false
end

max_levels -= 1
recursion = Thread.detect_recursion(array) do
m = Rubinius::Mirror::Array.reflect array

i = m.start
total = i + m.total
tuple = m.tuple

while i < total
o = tuple.at i

if ary = Rubinius::Type.check_convert_type(o, Array, :to_ary)
modified = true
recursively_flatten(ary, out, max_levels)
else
out << o
end

i += 1
end
end

raise ArgumentError, "tried to flatten recursive array" if recursion
modified
end

private :recursively_flatten

def flatten(level=-1)
level = Rubinius::Type.coerce_to_collection_index level
return self.dup if level == 0

out = new_reserved size
recursively_flatten(self, out, level)
Rubinius::Type.infect(out, self)
out
end

def flatten!(level=-1)
Rubinius.check_frozen

level = Rubinius::Type.coerce_to_collection_index level
return nil if level == 0

out = new_reserved size
if recursively_flatten(self, out, level)
replace(out)
return self
end

nil
end

def to_a
if self.instance_of? Array
self
else
Array.new(self)
end
end

end
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
# Copyright (c) 2007-2014, Evan Phoenix and contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Rubinius nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

module Rubinius

# IdentityMap is customized for uniquely storing elements from an Array to
# implement the following Array methods: #&, #|, #-, #uniq, and #uniq!
#
# The algorithm used is double hashing with an overflow array. For each
# array element, the element, its hash value, and its ordinal in the
# sequence of elements added to the map are stored.
#
# Methods are provided to test an element for inclusion in the map and to
# delete an entry from the map. The contents of a map can be returned as an
# array in the order the elements were added to the map.

class IdentityMap
attr_reader :size

Row = Table = Rubinius::Tuple
MIN_CAPACITY = 64
MIN_ROW = 10
ROW_GROWTH = 9

# Converts one or more Enumerable instances to a single IdentityMap
def self.from(*arrays, &block)
im = allocate
Rubinius.privately { im.load(arrays, &block) }
im
end

def initialize
capacity = MIN_CAPACITY
@table = Table.new capacity
@mask = capacity - 4
@max = capacity
@size = 0
end

# Adds +item+ to the IdentityMap if it does not already exist. May cause
# a row to be added or enlarged. Returns +self+.
def insert(item, &block)
redistribute if @size > @max

if block_given?
item_hash = yield(item).hash
else
item_hash = item.hash
end

index = item_hash & @mask
table = @table

if num_entries = table[index]
index += 1

if num_entries == 1
return self if match?(table, index, item, item_hash, &block)

table[index-1] = 2
table[index] = promote_row table, index, item, item_hash, @size
else
i = 1
row = table[index]
total = row[0]

while i < total
return self if match?(row, i, item, item_hash, &block)
i += 3
end

if total == row.size
table[index] = enlarge_row row, item, item_hash, @size
else
i = row[0]
set_item row, i, item, item_hash, @size
row[0] = i + 3
end
end
else
table[index] = 1
set_item table, index+1, item, item_hash, @size
end
@size += 1

self
end

# Returns +true+ if +item+ is in the IdentityMap, +false+ otherwise.
def include?(item)
item_hash = item.hash

index = item_hash & @mask
table = @table
if num_entries = table[index]
index += 1

if num_entries == 1
return true if match? table, index, item, item_hash
else
row = table[index]
i = 1
total = row[0]
while i < total
return true if match? row, i, item, item_hash
i += 3
end
end
end

false
end

# If +item+ is in the IdentityMap, removes it and returns +true+.
# Otherwise, returns +false+.
def delete(item)
item_hash = item.hash

index = item_hash & @mask
table = @table

if num_entries = table[index]
index += 1
if num_entries == 1
if match? table, index, item, item_hash
table[index] = nil
@size -= 1
return true
end
else
row = table[index]
i = 1
total = row[0]
while i < total
if match? row, i, item, item_hash
row[i] = nil
@size -= 1
return true
end
i += 3
end
end
end

false
end

# Returns an Array containing all items in the IdentityMap in the order
# in which they were added to the IdentityMap.
def to_array
array = Array.new @size

i = 0
table = @table
total = table.size

while i < total
if num_entries = table[i]
if num_entries == 1
array[table[i+3]] = table[i+2] if table[i+1]
else
row = table[i+1]
k = row[0]
j = 1
while j < k
array[row[j+2]] = row[j+1] if row[j]
j += 3
end
end
end

i += 4
end

array
end

# Private implementation methods

def resize(total)
capacity = MIN_CAPACITY
while capacity < total
capacity <<= 2
end

@table = Table.new capacity
@mask = capacity - 4
@max = capacity
end
private :resize

def redistribute
table = @table
resize @size

i = 0
total = table.size

while i < total
if num_entries = table[i]
if num_entries == 1
if item_hash = table[i+1]
add_item table[i+2], item_hash, table[i+3]
end
else
row = table[i+1]
k = row[0]
j = 1
while j < k
if item_hash = row[j]
add_item row[j+1], item_hash, row[j+2]
end
j += 3
end
end
end

i += 4
end
end
private :redistribute

def add_item(item, item_hash, ordinal)
index = item_hash & @mask
table = @table

if num_entries = table[index]
index += 1

if num_entries == 1
table[index-1] = 2
table[index] = promote_row table, index, item, item_hash, ordinal
else
row = table[index]
i = row[0]

if i == row.size
table[index] = enlarge_row row, item, item_hash, ordinal
else
set_item row, i, item, item_hash, ordinal
row[0] = i + 3
end
end
else
table[index] = 1
set_item table, index+1, item, item_hash, ordinal
end
end
private :add_item

# Given an Array of Enumerable instances, computes a bounding set
# to contain them and then adds each item to the IdentityMap.
def load(arrays, &block)
resize(arrays.inject(0) { |sum, array| sum + array.size })
@size = 0

arrays.each do |array|
array.each { |item| insert(item, &block) }
end
end
private :load

def match?(table, index, item, item_hash)
return false unless table[index] == item_hash
other = table[index+1]
if block_given?
item = yield item
other = yield other
end
Rubinius::Type.object_equal(item, other) or item.eql?(other)
end
private :match?

def set_item(table, index, item, item_hash, ordinal)
table[index] = item_hash
table[index+1] = item
table[index+2] = ordinal
end
private :set_item

def promote_row(row, index, item, item_hash, ordinal)
new_row = Row.new MIN_ROW

new_row[0] = 7
new_row[1] = row[index]
new_row[2] = row[index+1]
new_row[3] = row[index+2]
new_row[4] = item_hash
new_row[5] = item
new_row[6] = ordinal

new_row
end
private :promote_row

def enlarge_row(row, item, item_hash, ordinal)
new_row = Row.new row.size + ROW_GROWTH
new_row.copy_from row, 1, row.size-1, 1

index = row[0]
new_row[0] = index + 3
set_item new_row, index, item, item_hash, ordinal

new_row
end
private :enlarge_row
end
end
Original file line number Diff line number Diff line change
@@ -42,4 +42,30 @@ def chars
end
end

def chomp(separator=$/)
str = dup
str.chomp!(separator) || str
end

def lines(sep=$/)
if block_given?
each_line(sep) do |line|
yield line
end
else
each_line(sep).to_a
end
end

def start_with?(*prefixes)
prefixes.each do |original_prefix|
prefix = Rubinius::Type.check_convert_type original_prefix, String, :to_str
unless prefix
raise TypeError, "no implicit conversion of #{original_prefix.class} into String"
end
return true if self[0, prefix.length] == prefix
end
false
end

end

0 comments on commit fc3ac5e

Please sign in to comment.