Skip to content
Permalink

Comparing changes

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

Open a pull request

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

Commits on Apr 10, 2016

  1. Copy the full SHA
    f0bfd1d View commit details
  2. Copy the full SHA
    7cd624e View commit details
  3. Copy the full SHA
    22baf57 View commit details
  4. Copy the full SHA
    3fa007b View commit details
  5. Copy the full SHA
    0e90277 View commit details
  6. Copy the full SHA
    f54781a View commit details
  7. Copy the full SHA
    7f32fac View commit details
  8. Copy the full SHA
    e9d7eb7 View commit details
  9. Copy the full SHA
    3df2fe7 View commit details
  10. Copy the full SHA
    f018fb7 View commit details
  11. Copy the full SHA
    b417633 View commit details
  12. Copy the full SHA
    ce851b1 View commit details
  13. [Truffle] Make backtrace warnings core strings, as the whole point of…

    … them is to reduce churn.
    chrisseaton committed Apr 10, 2016
    Copy the full SHA
    d382797 View commit details
  14. Copy the full SHA
    7d7dad6 View commit details
  15. 2
    Copy the full SHA
    7e7f844 View commit details
  16. 8
    Copy the full SHA
    7d82684 View commit details
  17. Copy the full SHA
    b7fe78a View commit details
  18. Copy the full SHA
    0b6169c View commit details
  19. 2
    Copy the full SHA
    d166ef9 View commit details
  20. 1
    Copy the full SHA
    38e32b0 View commit details
8 changes: 8 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/CoreLibrary.java
Original file line number Diff line number Diff line change
@@ -957,6 +957,10 @@ public DynamicObject argumentError(String message, Node currentNode) {
return argumentError(message, currentNode, null);
}

public DynamicObject argumentErrorProcWithoutBlock(Node currentNode) {
return argumentError("tried to create Proc object without a block", currentNode, null);
}

public DynamicObject argumentErrorTooFewArguments(Node currentNode) {
return argumentError("too few arguments", currentNode, null);
}
@@ -1416,6 +1420,10 @@ public DynamicObject threadError(String message, Node currentNode) {
return ExceptionOperations.createRubyException(threadErrorClass, StringOperations.createString(context, StringOperations.encodeRope(message, UTF8Encoding.INSTANCE)), context.getCallStack().getBacktrace(currentNode));
}

public DynamicObject threadErrorKilledThread(Node currentNode) {
return threadError("killed thread", currentNode);
}

public DynamicObject threadErrorRecursiveLocking(Node currentNode) {
return threadError("deadlock; recursive locking", currentNode);
}
Original file line number Diff line number Diff line change
@@ -40,6 +40,10 @@ public Object yield(VirtualFrame frame, DynamicObject block, Object... arguments
return dispatchNode.dispatch(frame, block, arguments);
}

public Object yieldWithModifiedBlock(VirtualFrame frame, DynamicObject block, DynamicObject modifiedBlock, Object... arguments) {
return dispatchNode.dispatchWithModifiedBlock(frame, block, modifiedBlock, arguments);
}

public boolean yieldIsTruthy(VirtualFrame frame, DynamicObject block, Object... arguments) {
return booleanCast(frame, yield(frame, block, arguments));
}
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.rope.RopeConstants;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.control.RaiseException;
@@ -1121,23 +1122,29 @@ public ToSNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

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

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

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

@TruffleBoundary
@Specialization
public DynamicObject toS(long n, int base) {
if (base == 10) {
return toS(n, NotProvided.INSTANCE);
}

if (base < 2 || base > 36) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(coreLibrary().argumentErrorInvalidRadix(base, this));
}

Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@
import org.jruby.RubyGC;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.thread.ThreadManager;
import org.jruby.truffle.core.thread.ThreadNodes;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.objects.ObjectIDOperations;

@@ -93,8 +92,8 @@ public synchronized void defineFinalizer(DynamicObject object, Object callable)
if (finalizerThread == null) {
// TODO(CS): should we be running this in a real Ruby thread?

finalizerThread = ThreadNodes.createRubyThread(context, context.getCoreLibrary().getThreadClass());
ThreadNodes.initialize(finalizerThread, context, null, "finalizer", new Runnable() {
finalizerThread = ThreadManager.createRubyThread(context, context.getCoreLibrary().getThreadClass());
ThreadManager.initialize(finalizerThread, context, null, "finalizer", new Runnable() {
@Override
public void run() {
runFinalizers();
74 changes: 53 additions & 21 deletions truffle/src/main/java/org/jruby/truffle/core/proc/ProcNodes.java
Original file line number Diff line number Diff line change
@@ -27,6 +27,7 @@
import org.jruby.truffle.core.CoreMethodArrayArgumentsNode;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.UnaryCoreMethodNode;
import org.jruby.truffle.core.YieldingCoreMethodNode;
import org.jruby.truffle.core.binding.BindingNodes;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.NotProvided;
@@ -49,7 +50,6 @@ public AllocateNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@TruffleBoundary
@Specialization
public DynamicObject allocate(DynamicObject rubyClass) {
throw new RaiseException(coreLibrary().typeErrorAllocatorUndefinedFor(rubyClass, this));
@@ -68,20 +68,23 @@ public abstract static class ProcNewNode extends CoreMethodArrayArgumentsNode {

public ProcNewNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
initializeNode = DispatchHeadNodeFactory.createMethodCallOnSelf(context);
allocateObjectNode = AllocateObjectNodeGen.create(context, sourceSection, null, null);
}

public abstract DynamicObject executeProcNew(VirtualFrame frame, DynamicObject procClass, Object[] args, Object block);
public abstract DynamicObject executeProcNew(
VirtualFrame frame,
DynamicObject procClass,
Object[] args,
Object block);

@Specialization
public DynamicObject proc(VirtualFrame frame, DynamicObject procClass, Object[] args, NotProvided block) {
final Frame parentFrame = getContext().getCallStack().getCallerFrameIgnoringSend().getFrame(FrameAccess.READ_ONLY, true);
final Frame parentFrame = getContext().getCallStack().getCallerFrameIgnoringSend()
.getFrame(FrameAccess.READ_ONLY, true);

final DynamicObject parentBlock = RubyArguments.getBlock(parentFrame.getArguments());

if (parentBlock == null) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(coreLibrary().argumentError("tried to create Proc object without a block", this));
throw new RaiseException(coreLibrary().argumentErrorProcWithoutBlock(this));
}

return executeProcNew(frame, procClass, args, parentBlock);
@@ -100,7 +103,8 @@ public DynamicObject procNormal(DynamicObject procClass, Object[] args, DynamicO
@Specialization(guards = "procClass != metaClass(block)")
public DynamicObject procSpecial(VirtualFrame frame, DynamicObject procClass, Object[] args, DynamicObject block) {
// Instantiate a new instance of procClass as classes do not correspond
DynamicObject proc = allocateObjectNode.allocate(

final DynamicObject proc = getAllocateObjectNode().allocate(
procClass,
Layouts.PROC.getType(block),
Layouts.PROC.getSharedMethodInfo(block),
@@ -111,14 +115,34 @@ public DynamicObject procSpecial(VirtualFrame frame, DynamicObject procClass, Ob
Layouts.PROC.getSelf(block),
Layouts.PROC.getBlock(block),
Layouts.PROC.getFrameOnStackMarker(block));
initializeNode.call(frame, proc, "initialize", block, args);

getInitializeNode().call(frame, proc, "initialize", block, args);

return proc;
}

protected DynamicObject metaClass(DynamicObject object) {
return Layouts.BASIC_OBJECT.getMetaClass(object);
}

private AllocateObjectNode getAllocateObjectNode() {
if (allocateObjectNode == null) {
CompilerDirectives.transferToInterpreter();
allocateObjectNode = insert(AllocateObjectNodeGen.create(getContext(), null, null, null));
}

return allocateObjectNode;
}

private CallDispatchHeadNode getInitializeNode() {
if (initializeNode == null) {
CompilerDirectives.transferToInterpreter();
initializeNode = insert(DispatchHeadNodeFactory.createMethodCallOnSelf(getContext()));
}

return initializeNode;
}

}

@CoreMethod(names = { "dup", "clone" })
@@ -128,12 +152,11 @@ public abstract static class DupNode extends UnaryCoreMethodNode {

public DupNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
allocateObjectNode = AllocateObjectNodeGen.create(context, sourceSection, null, null);
}

@Specialization
public DynamicObject dup(DynamicObject proc) {
DynamicObject copy = allocateObjectNode.allocate(
final DynamicObject copy = getAllocateObjectNode().allocate(
Layouts.BASIC_OBJECT.getLogicalClass(proc),
Layouts.PROC.getType(proc),
Layouts.PROC.getSharedMethodInfo(proc),
@@ -144,9 +167,19 @@ public DynamicObject dup(DynamicObject proc) {
Layouts.PROC.getSelf(proc),
Layouts.PROC.getBlock(proc),
Layouts.PROC.getFrameOnStackMarker(proc));

return copy;
}

private AllocateObjectNode getAllocateObjectNode() {
if (allocateObjectNode == null) {
CompilerDirectives.transferToInterpreter();
allocateObjectNode = insert(AllocateObjectNodeGen.create(getContext(), null, null, null));
}

return allocateObjectNode;
}

}

@CoreMethod(names = "arity")
@@ -179,23 +212,20 @@ public DynamicObject binding(DynamicObject proc) {
}

@CoreMethod(names = {"call", "[]", "yield"}, rest = true, needsBlock = true)
public abstract static class CallNode extends CoreMethodArrayArgumentsNode {

@Child private YieldNode yieldNode;
public abstract static class CallNode extends YieldingCoreMethodNode {

public CallNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
yieldNode = new YieldNode(context);
}

@Specialization
public Object call(VirtualFrame frame, DynamicObject proc, Object[] args, NotProvided block) {
return yieldNode.dispatch(frame, proc, args);
return yield(frame, proc, args);
}

@Specialization
public Object call(VirtualFrame frame, DynamicObject proc, Object[] args, DynamicObject block) {
return yieldNode.dispatchWithModifiedBlock(frame, proc, block, args);
return yieldWithModifiedBlock(frame, proc, block, args);
}

}
@@ -225,8 +255,8 @@ public ParametersNode(RubyContext context, SourceSection sourceSection) {
@Specialization
public DynamicObject parameters(DynamicObject proc) {
final ArgumentDescriptor[] argsDesc = Layouts.PROC.getSharedMethodInfo(proc).getArgumentDescriptors();

return ArgumentDescriptorUtils.argumentDescriptorsToParameters(getContext(), argsDesc, Layouts.PROC.getType(proc) == ProcType.LAMBDA);
final boolean isLambda = Layouts.PROC.getType(proc) == ProcType.LAMBDA;
return ArgumentDescriptorUtils.argumentDescriptorsToParameters(getContext(), argsDesc, isLambda);
}

}
@@ -246,8 +276,10 @@ public Object sourceLocation(DynamicObject proc) {
if (sourceSection.getSource() == null) {
return nil();
} else {
DynamicObject file = createString(StringOperations.encodeRope(sourceSection.getSource().getName(), UTF8Encoding.INSTANCE));
Object[] objects = new Object[]{file, sourceSection.getStartLine()};
final DynamicObject file = createString(StringOperations.encodeRope(
sourceSection.getSource().getName(), UTF8Encoding.INSTANCE));

final Object[] objects = new Object[]{file, sourceSection.getStartLine()};
return Layouts.ARRAY.createArray(coreLibrary().getArrayFactory(), objects, objects.length);
}
}
Original file line number Diff line number Diff line change
@@ -10,10 +10,16 @@

package org.jruby.truffle.core.rope;

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import org.jcodings.specific.ASCIIEncoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jcodings.specific.UTF8Encoding;

import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class RopeConstants {

public static final LeafRope EMPTY_ASCII_8BIT_ROPE;
@@ -48,4 +54,26 @@ public class RopeConstants {
}
}

private static final Map<Integer, WeakReference<LeafRope>> integerRopes = new ConcurrentHashMap<>();

@TruffleBoundary
public static LeafRope getIntegerRope(int value) {
WeakReference<LeafRope> ropeReference = integerRopes.get(value);

if (ropeReference != null && ropeReference.get() != null) {
return ropeReference.get();
}

// On misses we don't care too much about racing to populate the cache

final LeafRope rope = new AsciiOnlyLeafRope(
Integer.toString(value).getBytes(StandardCharsets.UTF_8), USASCIIEncoding.INSTANCE);

ropeReference = new WeakReference<>(rope);

integerRopes.put(value, ropeReference);

return rope;
}

}
Original file line number Diff line number Diff line change
@@ -23,12 +23,18 @@

import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.profiles.ConditionProfile;
import org.jcodings.Encoding;
import org.jcodings.specific.ASCIIEncoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.Ruby;
import org.jruby.RubyEncoding;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.util.ByteList;
import org.jruby.util.Memo;
import org.jruby.util.StringSupport;
@@ -41,6 +47,7 @@
import java.util.concurrent.ConcurrentHashMap;

import static org.jruby.truffle.core.rope.CodeRange.CR_7BIT;
import static org.jruby.truffle.core.rope.CodeRange.CR_BROKEN;
import static org.jruby.truffle.core.rope.CodeRange.CR_UNKNOWN;
import static org.jruby.truffle.core.rope.CodeRange.CR_VALID;

@@ -639,4 +646,69 @@ public static ByteList toByteListCopy(Rope rope) {
return new ByteList(rope.getBytes(), rope.getEncoding(), true);
}

@TruffleBoundary
public static Rope format(RubyContext context, Object... values) {
Rope rope = null;

for (Object value : values) {
final Rope valueRope;

if (value instanceof DynamicObject && RubyGuards.isRubyString(value)) {
final Rope stringRope = Layouts.STRING.getRope((DynamicObject) value);
final Encoding encoding = stringRope.getEncoding();

if (encoding == UTF8Encoding.INSTANCE
|| encoding == USASCIIEncoding.INSTANCE
|| encoding == ASCIIEncoding.INSTANCE) {
valueRope = stringRope;
} else {
valueRope = StringOperations.encodeRope(decodeRope(context.getJRubyRuntime(), stringRope), UTF8Encoding.INSTANCE);
}
} else if (value instanceof Integer) {
valueRope = RopeConstants.getIntegerRope((int) value);
} else if (value instanceof String) {
valueRope = context.getRopeTable().getRope((String) value);
} else {
throw new IllegalArgumentException();
}

if (rope == null) {
rope = valueRope;
} else {
if (valueRope == null) {
throw new UnsupportedOperationException(value.getClass().toString());
}

rope = new ConcatRope(
rope,
valueRope,
UTF8Encoding.INSTANCE,
commonCodeRange(rope.getCodeRange(), valueRope.getCodeRange()),
rope.isSingleByteOptimizable() && valueRope.isSingleByteOptimizable(),
Math.max(rope.depth(), valueRope.depth()) + 1);

}
}

if (rope == null) {
rope = RopeConstants.EMPTY_UTF8_ROPE;
}

return rope;
}

private static CodeRange commonCodeRange(CodeRange first, CodeRange second) {
if (first == second) {
return first;
}

if ((first == CR_BROKEN) || (second == CR_BROKEN)) {
return CR_BROKEN;
}

// If we get this far, one must be CR_7BIT and the other must be CR_VALID, so promote to the more general code range.

return CR_VALID;
}

}
48 changes: 48 additions & 0 deletions truffle/src/main/java/org/jruby/truffle/core/rope/RopeTable.java
Original file line number Diff line number Diff line change
@@ -12,8 +12,12 @@

import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import org.jcodings.Encoding;
import org.jcodings.specific.UTF8Encoding;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.util.StringSupport;

import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
@@ -25,13 +29,57 @@
public class RopeTable {

private final ReadWriteLock lock = new ReentrantReadWriteLock();
private final WeakHashMap<String, Key> stringsTable = new WeakHashMap<>();
private final WeakHashMap<Key, WeakReference<Rope>> ropesTable = new WeakHashMap<>();
private final Set<Key> keys = new HashSet<>();

private int byteArrayReusedCount;
private int ropesReusedCount;
private int ropeBytesSaved;

@TruffleBoundary
public Rope getRope(String string) {
lock.readLock().lock();

try {
final Key key = stringsTable.get(string);

if (key != null) {
final WeakReference<Rope> ropeReference = ropesTable.get(key);

if (ropeReference != null && ropeReference.get() != null) {
return ropeReference.get();
}
}
} finally {
lock.readLock().unlock();
}

lock.writeLock().lock();

try {
final Rope rope = StringOperations.encodeRope(string, UTF8Encoding.INSTANCE);

Key key = stringsTable.get(string);

if (key == null) {
key = new Key(rope.getBytes(), UTF8Encoding.INSTANCE);
stringsTable.put(string, key);
}

WeakReference<Rope> ropeReference = ropesTable.get(key);

if (ropeReference == null || ropeReference.get() == null) {
ropeReference = new WeakReference<>(rope);
ropesTable.put(key, ropeReference);
}

return rope;
} finally {
lock.writeLock().unlock();
}
}

@TruffleBoundary
public Rope getRope(byte[] bytes, Encoding encoding, CodeRange codeRange) {
final Key key = new Key(bytes, encoding);
Original file line number Diff line number Diff line change
@@ -69,4 +69,9 @@ private static boolean is7Bit(String literal) {
return true;
}

@Override
public String toString() {
return literal;
}

}
Original file line number Diff line number Diff line change
@@ -14,6 +14,8 @@
public class CoreStrings {

public final CoreString ASSIGNMENT;
public final CoreString BACKTRACE_OMITTED_LIMIT;
public final CoreString BACKTRACE_OMITTED_UNUSED;
public final CoreString CLASS_VARIABLE;
public final CoreString EXPRESSION;
public final CoreString FALSE;
@@ -22,12 +24,15 @@ public class CoreStrings {
public final CoreString LOCAL_VARIABLE;
public final CoreString METHOD;
public final CoreString NIL;
public final CoreString UNKNOWN;
public final CoreString SELF;
public final CoreString TRUE;
public final CoreString YIELD;

public CoreStrings(RubyContext context) {
ASSIGNMENT = new CoreString(context, "assignment");
BACKTRACE_OMITTED_LIMIT = new CoreString(context, "(omitted due to -Xtruffle.backtraces.limit)");
BACKTRACE_OMITTED_UNUSED = new CoreString(context, "(omitted as the rescue expression was pure; use -Xtruffle.backtraces.omit_for_unused=false to disable)");
CLASS_VARIABLE = new CoreString(context, "class variable");
EXPRESSION = new CoreString(context, "expression");
FALSE = new CoreString(context, "false");
@@ -36,6 +41,7 @@ public CoreStrings(RubyContext context) {
LOCAL_VARIABLE = new CoreString(context, "local-variable");
METHOD = new CoreString(context, "method");
NIL = new CoreString(context, "nil");
UNKNOWN = new CoreString(context, "(unknown)");
SELF = new CoreString(context, "self");
TRUE = new CoreString(context, "true");
YIELD = new CoreString(context, "yield");
Original file line number Diff line number Diff line change
@@ -17,11 +17,13 @@
@org.jruby.truffle.om.dsl.api.Layout
public interface ThreadBacktraceLocationLayout extends BasicObjectLayout {

DynamicObjectFactory createThreadBacktraceLocationShape(DynamicObject logicalClass,
DynamicObject metaClass);
DynamicObjectFactory createThreadBacktraceLocationShape(
DynamicObject logicalClass,
DynamicObject metaClass);

DynamicObject createThreadBacktraceLocation(DynamicObjectFactory factory,
Activation activation);
DynamicObject createThreadBacktraceLocation(
DynamicObjectFactory factory,
Activation activation);

Activation getActivation(DynamicObject object);

Original file line number Diff line number Diff line change
@@ -18,6 +18,9 @@
import org.jruby.truffle.core.CoreClass;
import org.jruby.truffle.core.CoreMethod;
import org.jruby.truffle.core.UnaryCoreMethodNode;
import org.jruby.truffle.core.rope.RopeOperations;
import org.jruby.truffle.core.string.StringNodes;
import org.jruby.truffle.core.string.StringNodesFactory;
import org.jruby.truffle.core.string.StringOperations;
import org.jruby.truffle.language.backtrace.Activation;
import org.jruby.truffle.language.backtrace.BacktraceFormatter;
@@ -39,7 +42,7 @@ public DynamicObject absolutePath(DynamicObject threadBacktraceLocation) {
final Activation activation = ThreadBacktraceLocationLayoutImpl.INSTANCE.getActivation(threadBacktraceLocation);

if (activation.getCallNode() == null) {
return createString(StringOperations.encodeRope(BacktraceFormatter.OMITTED_LIMIT, UTF8Encoding.INSTANCE));
return coreStrings().BACKTRACE_OMITTED_LIMIT.createInstance();
}

final SourceSection sourceSection = activation.getCallNode().getEncapsulatingSourceSection();
@@ -50,13 +53,13 @@ public DynamicObject absolutePath(DynamicObject threadBacktraceLocation) {

// TODO CS 30-Apr-15: not absolute - not sure how to solve that

String path = sourceSection.getSource().getPath();
final String path = sourceSection.getSource().getPath();

if (path == null) {
path = "(unknown)";
return coreStrings().UNKNOWN.createInstance();
} else {
return createString(getContext().getRopeTable().getRope(path));
}

return createString(StringOperations.encodeRope(path, UTF8Encoding.INSTANCE));
}

}
@@ -87,13 +90,13 @@ public ToSNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@TruffleBoundary
@Specialization
public DynamicObject toS(DynamicObject threadBacktraceLocation) {
final Activation activation = ThreadBacktraceLocationLayoutImpl.INSTANCE.getActivation(threadBacktraceLocation);
final Activation activation= ThreadBacktraceLocationLayoutImpl.INSTANCE
.getActivation(threadBacktraceLocation);

if (activation.getCallNode() == null) {
return createString(StringOperations.encodeRope(BacktraceFormatter.OMITTED_LIMIT, UTF8Encoding.INSTANCE));
return coreStrings().BACKTRACE_OMITTED_LIMIT.createInstance();
}

final SourceSection sourceSection = activation.getCallNode().getEncapsulatingSourceSection();
@@ -102,10 +105,13 @@ public DynamicObject toS(DynamicObject threadBacktraceLocation) {
return createString(StringOperations.encodeRope(sourceSection.getShortDescription(), UTF8Encoding.INSTANCE));
}

return createString(StringOperations.encodeRope(String.format("%s:%d:in `%s'",
return createString(RopeOperations.format(getContext(),
sourceSection.getSource().getName(),
":",
sourceSection.getStartLine(),
sourceSection.getIdentifier()), UTF8Encoding.INSTANCE));
":in `",
sourceSection.getIdentifier(),
"'"));
}

}
Original file line number Diff line number Diff line change
@@ -28,33 +28,31 @@
@Layout
public interface ThreadLayout extends BasicObjectLayout {

DynamicObjectFactory createThreadShape(DynamicObject logicalClass,
DynamicObject metaClass);

DynamicObject createThread(DynamicObjectFactory factory,
DynamicObject threadLocals,
@Volatile InterruptMode interruptMode, // needs to be volatile for fibers implemented by threads
@Volatile RubyThread.Status status,
List<Lock> ownedLocks,
@Nullable FiberManager fiberManager,
@Nullable String name,
CountDownLatch finishedLatch,
boolean abortOnException,
@Nullable @Volatile Thread thread,
@Nullable @Volatile DynamicObject exception,
@Nullable @Volatile Object value,
AtomicBoolean wakeUp,
@Volatile int priority);
DynamicObjectFactory createThreadShape(
DynamicObject logicalClass,
DynamicObject metaClass);

DynamicObject createThread(
DynamicObjectFactory factory,
DynamicObject threadLocals,
@Volatile InterruptMode interruptMode, // needs to be volatile for fibers implemented by threads
@Volatile RubyThread.Status status,
List<Lock> ownedLocks,
@Nullable FiberManager fiberManager,
CountDownLatch finishedLatch,
boolean abortOnException,
@Nullable @Volatile Thread thread,
@Nullable @Volatile DynamicObject exception,
@Nullable @Volatile Object value,
AtomicBoolean wakeUp,
@Volatile int priority);

boolean isThread(ObjectType objectType);
boolean isThread(DynamicObject object);

FiberManager getFiberManager(DynamicObject object);
void setFiberManagerUnsafe(DynamicObject object, FiberManager value);

String getName(DynamicObject object);
void setNameUnsafe(DynamicObject object, String value);

CountDownLatch getFinishedLatch(DynamicObject object);

DynamicObject getThreadLocals(DynamicObject object);
150 changes: 140 additions & 10 deletions truffle/src/main/java/org/jruby/truffle/core/thread/ThreadManager.java
Original file line number Diff line number Diff line change
@@ -12,42 +12,172 @@
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectFactory;
import jnr.posix.DefaultNativeTimeval;
import jnr.posix.Timeval;
import org.jruby.RubyThread.Status;
import org.jruby.truffle.RubyContext;
import org.jruby.truffle.core.InterruptMode;
import org.jruby.truffle.core.Layouts;
import org.jruby.truffle.core.fiber.FiberManager;
import org.jruby.truffle.core.fiber.FiberNodes;
import org.jruby.truffle.core.proc.ProcOperations;
import org.jruby.truffle.core.rubinius.ThreadPrimitiveNodes;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.language.SafepointAction;
import org.jruby.truffle.language.SafepointManager;
import org.jruby.truffle.language.backtrace.BacktraceFormatter;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.control.ReturnException;
import org.jruby.truffle.language.control.ThreadExitException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;

/**
* Manages Ruby {@code Thread} objects.
*/
public class ThreadManager {

private final RubyContext context;

private final DynamicObject rootThread;
private final ThreadLocal<DynamicObject> currentThread = new ThreadLocal<DynamicObject>();
private final ThreadLocal<DynamicObject> currentThread = new ThreadLocal<>();

private final Set<DynamicObject> runningRubyThreads = Collections.newSetFromMap(new ConcurrentHashMap<DynamicObject, Boolean>());
private final Set<DynamicObject> runningRubyThreads
= Collections.newSetFromMap(new ConcurrentHashMap<DynamicObject, Boolean>());

public ThreadManager(RubyContext context) {
this.context = context;
this.rootThread = ThreadNodes.createRubyThread(context, context.getCoreLibrary().getThreadClass());
Layouts.THREAD.setNameUnsafe(rootThread, "main");
this.rootThread = createRubyThread(context, context.getCoreLibrary().getThreadClass());
}

public static DynamicObject createRubyThread(RubyContext context, DynamicObject rubyClass) {
final DynamicObject threadLocals = createThreadLocals(context);
final DynamicObject object = Layouts.THREAD.createThread(
Layouts.CLASS.getInstanceFactory(rubyClass),
threadLocals,
InterruptMode.IMMEDIATE,
Status.RUN,
new ArrayList<Lock>(),
null,
new CountDownLatch(1),
getGlobalAbortOnException(context),
null,
null,
null,
new AtomicBoolean(false),
0);
Layouts.THREAD.setFiberManagerUnsafe(object, new FiberManager(context, object)); // Because it is cyclic
return object;
}

public static boolean getGlobalAbortOnException(RubyContext context) {
final DynamicObject threadClass = context.getCoreLibrary().getThreadClass();
return (boolean) threadClass.get("@abort_on_exception");
}

private static DynamicObject createThreadLocals(RubyContext context) {
final DynamicObjectFactory instanceFactory = Layouts.CLASS.getInstanceFactory(context.getCoreLibrary().getObjectClass());
final DynamicObject threadLocals = Layouts.BASIC_OBJECT.createBasicObject(instanceFactory);
threadLocals.define("$!", context.getCoreLibrary().getNilObject(), 0);
threadLocals.define("$~", context.getCoreLibrary().getNilObject(), 0);
threadLocals.define("$?", context.getCoreLibrary().getNilObject(), 0);
return threadLocals;
}

public static void initialize(final DynamicObject thread, RubyContext context, Node currentNode, final Object[] arguments, final DynamicObject block) {
String info = Layouts.PROC.getSharedMethodInfo(block).getSourceSection().getShortDescription();
initialize(thread, context, currentNode, info, new Runnable() {
@Override
public void run() {
final Object value = ProcOperations.rootCall(block, arguments);
Layouts.THREAD.setValue(thread, value);
}
});
}

public static void initialize(final DynamicObject thread, final RubyContext context, final Node currentNode, final String info, final Runnable task) {
assert RubyGuards.isRubyThread(thread);
new Thread(new Runnable() {
@Override
public void run() {
ThreadManager.run(thread, context, currentNode, info, task);
}
}).start();

FiberNodes.waitForInitialization(context, Layouts.THREAD.getFiberManager(thread).getRootFiber(), currentNode);
}

public static void run(DynamicObject thread, final RubyContext context, Node currentNode, String info, Runnable task) {
assert RubyGuards.isRubyThread(thread);

final String name = "Ruby Thread@" + info;
Thread.currentThread().setName(name);
DynamicObject fiber = Layouts.THREAD.getFiberManager(thread).getRootFiber();

start(context, thread);
FiberNodes.start(context, fiber);
try {
task.run();
} catch (ThreadExitException e) {
Layouts.THREAD.setValue(thread, context.getCoreLibrary().getNilObject());
return;
} catch (RaiseException e) {
setException(context, thread, e.getException(), currentNode);
} catch (ReturnException e) {
setException(context, thread, context.getCoreLibrary().unexpectedReturn(currentNode), currentNode);
} finally {
FiberNodes.cleanup(context, fiber);
cleanup(context, thread);
}
}

private static void setException(RubyContext context, DynamicObject thread, DynamicObject exception, Node currentNode) {
final DynamicObject mainThread = context.getThreadManager().getRootThread();
final boolean isSystemExit = Layouts.BASIC_OBJECT.getLogicalClass(exception) == context.getCoreLibrary().getSystemExitClass();
if (thread != mainThread && (isSystemExit || Layouts.THREAD.getAbortOnException(thread))) {
ThreadPrimitiveNodes.ThreadRaisePrimitiveNode.raiseInThread(context, mainThread, exception, currentNode);
}
Layouts.THREAD.setException(thread, exception);
}

public static void start(RubyContext context, DynamicObject thread) {
assert RubyGuards.isRubyThread(thread);
Layouts.THREAD.setThread(thread, Thread.currentThread());
context.getThreadManager().registerThread(thread);
}

public static void cleanup(RubyContext context, DynamicObject thread) {
assert RubyGuards.isRubyThread(thread);

Layouts.THREAD.setStatus(thread, Status.ABORTING);
context.getThreadManager().unregisterThread(thread);

Layouts.THREAD.setStatus(thread, Status.DEAD);
Layouts.THREAD.setThread(thread, null);
assert RubyGuards.isRubyThread(thread);
for (Lock lock : Layouts.THREAD.getOwnedLocks(thread)) {
lock.unlock();
}
Layouts.THREAD.getFinishedLatch(thread).countDown();
}

public static void shutdown(RubyContext context, DynamicObject thread, Node currentNode) {
assert RubyGuards.isRubyThread(thread);
Layouts.THREAD.getFiberManager(thread).shutdown();

if (thread == context.getThreadManager().getRootThread()) {
throw new RaiseException(context.getCoreLibrary().systemExit(0, currentNode));
} else {
throw new ThreadExitException();
}
}

public void initialize() {
ThreadNodes.start(context, rootThread);
start(context, rootThread);
FiberNodes.start(context, Layouts.THREAD.getFiberManager(rootThread).getRootFiber());
}

@@ -216,7 +346,7 @@ public void shutdown() {
} finally {
Layouts.THREAD.getFiberManager(rootThread).shutdown();
FiberNodes.cleanup(context, Layouts.THREAD.getFiberManager(rootThread).getRootFiber());
ThreadNodes.cleanup(context, rootThread);
cleanup(context, rootThread);
}
}

@@ -233,7 +363,7 @@ private void killOtherThreads() {
@Override
public synchronized void run(DynamicObject thread, Node currentNode) {
if (thread != rootThread && Thread.currentThread() == Layouts.THREAD.getThread(thread)) {
ThreadNodes.shutdown(context, thread, currentNode);
shutdown(context, thread, currentNode);
}
}
});
183 changes: 30 additions & 153 deletions truffle/src/main/java/org/jruby/truffle/core/thread/ThreadNodes.java
Original file line number Diff line number Diff line change
@@ -15,7 +15,6 @@
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.DynamicObject;
import com.oracle.truffle.api.object.DynamicObjectFactory;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.RubyThread.Status;
import org.jruby.runtime.Visibility;
@@ -28,153 +27,19 @@
import org.jruby.truffle.core.RubiniusOnly;
import org.jruby.truffle.core.YieldingCoreMethodNode;
import org.jruby.truffle.core.exception.ExceptionOperations;
import org.jruby.truffle.core.fiber.FiberManager;
import org.jruby.truffle.core.fiber.FiberNodes;
import org.jruby.truffle.core.proc.ProcOperations;
import org.jruby.truffle.core.rubinius.ThreadPrimitiveNodes.ThreadRaisePrimitiveNode;
import org.jruby.truffle.language.NotProvided;
import org.jruby.truffle.language.RubyGuards;
import org.jruby.truffle.language.RubyNode;
import org.jruby.truffle.language.SafepointAction;
import org.jruby.truffle.language.backtrace.Backtrace;
import org.jruby.truffle.language.control.RaiseException;
import org.jruby.truffle.language.control.ReturnException;
import org.jruby.truffle.language.control.ThreadExitException;
import org.jruby.truffle.platform.UnsafeGroup;
import org.jruby.util.Memo;

import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;

@CoreClass(name = "Thread")
public abstract class ThreadNodes {

public static DynamicObject createRubyThread(RubyContext context, DynamicObject rubyClass) {
final DynamicObject threadLocals = createThreadLocals(context);
final DynamicObject object = Layouts.THREAD.createThread(
Layouts.CLASS.getInstanceFactory(rubyClass),
threadLocals,
InterruptMode.IMMEDIATE,
Status.RUN,
new ArrayList<Lock>(),
null,
null,
new CountDownLatch(1),
getGlobalAbortOnException(context),
null,
null,
null,
new AtomicBoolean(false),
0);
Layouts.THREAD.setFiberManagerUnsafe(object, new FiberManager(context, object)); // Because it is cyclic
return object;
}

public static boolean getGlobalAbortOnException(RubyContext context) {
final DynamicObject threadClass = context.getCoreLibrary().getThreadClass();
return (boolean) threadClass.get("@abort_on_exception");
}

private static DynamicObject createThreadLocals(RubyContext context) {
final DynamicObjectFactory instanceFactory = Layouts.CLASS.getInstanceFactory(context.getCoreLibrary().getObjectClass());
final DynamicObject threadLocals = Layouts.BASIC_OBJECT.createBasicObject(instanceFactory);
threadLocals.define("$!", context.getCoreLibrary().getNilObject(), 0);
threadLocals.define("$~", context.getCoreLibrary().getNilObject(), 0);
threadLocals.define("$?", context.getCoreLibrary().getNilObject(), 0);
return threadLocals;
}

public static void initialize(final DynamicObject thread, RubyContext context, Node currentNode, final Object[] arguments, final DynamicObject block) {
String info = Layouts.PROC.getSharedMethodInfo(block).getSourceSection().getShortDescription();
initialize(thread, context, currentNode, info, new Runnable() {
@Override
public void run() {
final Object value = ProcOperations.rootCall(block, arguments);
Layouts.THREAD.setValue(thread, value);
}
});
}

public static void initialize(final DynamicObject thread, final RubyContext context, final Node currentNode, final String info, final Runnable task) {
assert RubyGuards.isRubyThread(thread);
new Thread(new Runnable() {
@Override
public void run() {
ThreadNodes.run(thread, context, currentNode, info, task);
}
}).start();

FiberNodes.waitForInitialization(context, Layouts.THREAD.getFiberManager(thread).getRootFiber(), currentNode);
}

public static void run(DynamicObject thread, final RubyContext context, Node currentNode, String info, Runnable task) {
assert RubyGuards.isRubyThread(thread);

final String name = "Ruby Thread@" + info;
Layouts.THREAD.setNameUnsafe(thread, name);
Thread.currentThread().setName(name);
DynamicObject fiber = Layouts.THREAD.getFiberManager(thread).getRootFiber();

start(context, thread);
FiberNodes.start(context, fiber);
try {
task.run();
} catch (ThreadExitException e) {
Layouts.THREAD.setValue(thread, context.getCoreLibrary().getNilObject());
return;
} catch (RaiseException e) {
setException(context, thread, e.getException(), currentNode);
} catch (ReturnException e) {
setException(context, thread, context.getCoreLibrary().unexpectedReturn(currentNode), currentNode);
} finally {
FiberNodes.cleanup(context, fiber);
cleanup(context, thread);
}
}

private static void setException(RubyContext context, DynamicObject thread, DynamicObject exception, Node currentNode) {
final DynamicObject mainThread = context.getThreadManager().getRootThread();
final boolean isSystemExit = Layouts.BASIC_OBJECT.getLogicalClass(exception) == context.getCoreLibrary().getSystemExitClass();
if (thread != mainThread && (isSystemExit || Layouts.THREAD.getAbortOnException(thread))) {
ThreadRaisePrimitiveNode.raiseInThread(context, mainThread, exception, currentNode);
}
Layouts.THREAD.setException(thread, exception);
}

public static void start(RubyContext context, DynamicObject thread) {
assert RubyGuards.isRubyThread(thread);
Layouts.THREAD.setThread(thread, Thread.currentThread());
context.getThreadManager().registerThread(thread);
}

public static void cleanup(RubyContext context, DynamicObject thread) {
assert RubyGuards.isRubyThread(thread);

Layouts.THREAD.setStatus(thread, Status.ABORTING);
context.getThreadManager().unregisterThread(thread);

Layouts.THREAD.setStatus(thread, Status.DEAD);
Layouts.THREAD.setThread(thread, null);
assert RubyGuards.isRubyThread(thread);
for (Lock lock : Layouts.THREAD.getOwnedLocks(thread)) {
lock.unlock();
}
Layouts.THREAD.getFinishedLatch(thread).countDown();
}

public static void shutdown(RubyContext context, DynamicObject thread, Node currentNode) {
assert RubyGuards.isRubyThread(thread);
Layouts.THREAD.getFiberManager(thread).shutdown();

if (thread == context.getThreadManager().getRootThread()) {
throw new RaiseException(context.getCoreLibrary().systemExit(0, currentNode));
} else {
throw new ThreadExitException();
}
}

@CoreMethod(names = "alive?", unsafe = UnsafeGroup.THREADS)
public abstract static class AliveNode extends CoreMethodArrayArgumentsNode {

@@ -197,23 +62,27 @@ public BacktraceNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@TruffleBoundary
@Specialization
public DynamicObject backtrace(DynamicObject rubyThread) {
final Thread thread = Layouts.FIBER.getThread(Layouts.THREAD.getFiberManager(rubyThread).getCurrentFiber());

final DynamicObject[] result = new DynamicObject[1];
final Memo<DynamicObject> result = new Memo<>(null);

getContext().getSafepointManager().pauseThreadAndExecute(thread, this, new SafepointAction() {

@Override
public void run(DynamicObject thread, Node currentNode) {
final Backtrace backtrace = getContext().getCallStack().getBacktrace(currentNode);
result[0] = ExceptionOperations.backtraceAsRubyStringArray(getContext(), null, backtrace);
result.set(ExceptionOperations.backtraceAsRubyStringArray(getContext(), null, backtrace));
}

});

// if the thread id dead or aborting the SafepointAction will not run
if (result[0] != null) {
return result[0];
// If the thread id dead or aborting the SafepointAction will not run

if (result.get() != null) {
return result.get();
} else {
return nil();
}
@@ -246,16 +115,19 @@ public KillNode(RubyContext context, SourceSection sourceSection) {
@Specialization
public DynamicObject kill(final DynamicObject rubyThread) {
final Thread toKill = Layouts.THREAD.getThread(rubyThread);

if (toKill == null) {
// Already dead
return rubyThread;
}

getContext().getSafepointManager().pauseThreadAndExecuteLater(toKill, this, new SafepointAction() {

@Override
public void run(DynamicObject currentThread, Node currentNode) {
shutdown(getContext(), currentThread, currentNode);
ThreadManager.shutdown(getContext(), currentThread, currentNode);
}

});

return rubyThread;
@@ -314,7 +186,7 @@ public InitializeNode(RubyContext context, SourceSection sourceSection) {
@TruffleBoundary
@Specialization
public DynamicObject initialize(DynamicObject thread, Object[] arguments, DynamicObject block) {
ThreadNodes.initialize(thread, getContext(), this, arguments, block);
ThreadManager.initialize(thread, getContext(), this, arguments, block);
return nil();
}

@@ -359,11 +231,13 @@ private Object joinMillis(DynamicObject self, int timeoutInMillis) {
@TruffleBoundary
public static void doJoin(RubyNode currentNode, final DynamicObject thread) {
currentNode.getContext().getThreadManager().runUntilResult(currentNode, new ThreadManager.BlockingAction<Boolean>() {

@Override
public Boolean block() throws InterruptedException {
Layouts.THREAD.getFinishedLatch(thread).await();
return SUCCESS;
}

});

if (Layouts.THREAD.getException(thread) != null) {
@@ -374,7 +248,9 @@ public Boolean block() throws InterruptedException {
@TruffleBoundary
private boolean doJoinMillis(final DynamicObject thread, final int timeoutInMillis) {
final long start = System.currentTimeMillis();

final boolean joined = getContext().getThreadManager().runUntilResult(this, new ThreadManager.BlockingAction<Boolean>() {

@Override
public Boolean block() throws InterruptedException {
long now = System.currentTimeMillis();
@@ -385,6 +261,7 @@ public Boolean block() throws InterruptedException {
}
return Layouts.THREAD.getFinishedLatch(thread).await(timeoutInMillis - waited, TimeUnit.MILLISECONDS);
}

});

if (joined && Layouts.THREAD.getException(thread) != null) {
@@ -418,7 +295,7 @@ public PassNode(RubyContext context, SourceSection sourceSection) {
}

@Specialization
public DynamicObject pass(VirtualFrame frame) {
public DynamicObject pass() {
Thread.yield();
return nil();
}
@@ -490,15 +367,16 @@ public WakeupNode(RubyContext context, SourceSection sourceSection) {
@Specialization
public DynamicObject wakeup(final DynamicObject thread) {
if (Layouts.THREAD.getStatus(thread) == Status.DEAD) {
CompilerDirectives.transferToInterpreter();
throw new RaiseException(coreLibrary().threadError("killed thread", this));
throw new RaiseException(coreLibrary().threadErrorKilledThread(this));
}

// TODO: should only interrupt sleep
Layouts.THREAD.getWakeUp(thread).set(true);
Thread t = Layouts.THREAD.getThread(thread);
if (t != null) {
t.interrupt();

final Thread toInterrupt = Layouts.THREAD.getThread(thread);

if (toInterrupt != null) {
// TODO: should only interrupt sleep
toInterrupt.interrupt();
}

return thread;
@@ -542,11 +420,10 @@ public AllocateNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

// TODO (eregon, 13/10/2015): Thread is not allocatable in MRI
@TruffleBoundary
@Specialization
public DynamicObject allocate(DynamicObject rubyClass) {
return createRubyThread(getContext(), rubyClass);
// TODO (eregon, 13/10/2015): Thread is not allocatable in MRI but Rubinius's kernel uses it
return ThreadManager.createRubyThread(getContext(), rubyClass);
}

}
Original file line number Diff line number Diff line change
@@ -28,9 +28,6 @@

public class BacktraceFormatter {

public static final String OMITTED_LIMIT = "(omitted due to -Xtruffle.backtraces.limit)";
public static final String OMITTED_UNUSED = "(omitted as the rescue expression was pure; use -Xtruffle.backtraces.omit_for_unused=false to disable)";

public enum FormattingFlags {
OMIT_EXCEPTION,
OMIT_FROM_PREFIX,
@@ -118,11 +115,11 @@ public String formatLine(List<Activation> activations, int n, DynamicObject exce
final Activation activation = activations.get(n);

if (activation == Activation.OMITTED_LIMIT) {
return OMITTED_LIMIT;
return context.getCoreStrings().BACKTRACE_OMITTED_LIMIT.toString();
}

if (activation == Activation.OMITTED_UNUSED) {
return OMITTED_UNUSED;
return context.getCoreStrings().BACKTRACE_OMITTED_UNUSED.toString();
}

final StringBuilder builder = new StringBuilder();