Skip to content
Permalink
Browse files
[Truffle] Make $_ thread local - at the same time as being frame local.
We achieve this by wrapping the value in a ThreadLocal when it's
assigned, giving it the default value nil for other threads.
  • Loading branch information
chrisseaton committed Dec 29, 2014
1 parent 3792e0f commit 73aa13ce12cc00c91af2d7dee6a68883747d62c5
@@ -383,6 +383,11 @@ public boolean isRubyBasicObject(Object value) {
return value instanceof RubyBasicObject;
}

@SuppressWarnings("static-method")
public boolean isThreadLocal(Object value) {
return value instanceof ThreadLocal;
}

@SuppressWarnings("static-method")
public boolean isObjectArray(Object value) {
return value instanceof Object[];
@@ -56,6 +56,7 @@
RubyTime.class, //
RubyEncodingConverter.class, //
RubyBasicObject.class, //
ThreadLocal.class, //
Object[].class})

public class RubyTypes {
@@ -12,6 +12,8 @@
import com.oracle.truffle.api.source.*;
import com.oracle.truffle.api.dsl.*;
import com.oracle.truffle.api.frame.*;
import org.jruby.truffle.nodes.globals.GetFromThreadLocalNode;
import org.jruby.truffle.nodes.globals.WrapInThreadLocalNode;
import org.jruby.truffle.runtime.*;
import org.jruby.truffle.runtime.core.*;

@@ -34,7 +36,15 @@ public Object localVariableGet(RubyBinding binding, RubySymbol symbol) {
notDesignedForCompilation();

final MaterializedFrame frame = binding.getFrame();
return frame.getValue(frame.getFrameDescriptor().findFrameSlot(symbol.toString()));

Object value = frame.getValue(frame.getFrameDescriptor().findFrameSlot(symbol.toString()));

// TODO(CS): temporary hack for $_
if (symbol.equals("$_")) {
value = GetFromThreadLocalNode.get(getContext(), value);
}

return value;
}
}

@@ -53,6 +63,11 @@ public LocalVariableSetNode(LocalVariableSetNode prev) {
public Object localVariableSetNode(RubyBinding binding, RubySymbol symbol, Object value) {
notDesignedForCompilation();

// TODO(CS): temporary hack for $_
if (symbol.toString().equals("$_")) {
value = WrapInThreadLocalNode.wrap(getContext(), value);
}

MaterializedFrame frame = binding.getFrame();

while (true) {
@@ -28,6 +28,7 @@
import org.jruby.truffle.nodes.dispatch.Dispatch;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.nodes.dispatch.PredicateDispatchHeadNode;
import org.jruby.truffle.nodes.globals.WrapInThreadLocalNode;
import org.jruby.truffle.nodes.literal.*;
import org.jruby.truffle.nodes.objectstorage.ReadHeadObjectFieldNode;
import org.jruby.truffle.nodes.objectstorage.WriteHeadObjectFieldNode;
@@ -799,7 +800,7 @@ public RubyString gets(VirtualFrame frame) {
final FrameSlot slot = caller.getFrameDescriptor().findFrameSlot("$_");

if (slot != null) {
caller.setObject(slot, rubyLine);
caller.setObject(slot, WrapInThreadLocalNode.wrap(getContext(), rubyLine));
}

return rubyLine;
@@ -0,0 +1,48 @@
package org.jruby.truffle.nodes.globals;

import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyContext;

/**
* If a child node produces a {@link ThreadLocal}, get the value from it. If the value is not a {@code ThreadLocal},
* return it unmodified.
*
* This is used in combination with nodes that read and writes from storage locations such as frames to make them
* thread-local.
*
* Also see {@link WrapInThreadLocalNode}.
*/
@NodeChild(value = "value", type = RubyNode.class)
public abstract class GetFromThreadLocalNode extends RubyNode {

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

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

@Specialization
public Object get(ThreadLocal threadLocal) {
return threadLocal.get();
}

@Specialization(guards = "!isThreadLocal")
public Object get(Object value) {
return value;
}

public static Object get(RubyContext context, Object value) {
if (value instanceof ThreadLocal) {
return ((ThreadLocal) value).get();
} else {
return value;
}
}


}
@@ -0,0 +1,51 @@
package org.jruby.truffle.nodes.globals;

import com.oracle.truffle.api.dsl.NodeChild;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
import org.jruby.truffle.runtime.RubyContext;

/**
* Wrap a child value in a new {@link ThreadLocal} so that a value can be stored in a location such as a frame without
* making that value visible to other threads.
*
* This is used in combination with nodes that read and writes from storage locations such as frames to make them
* thread-local.
*
* Also see {@link GetFromThreadLocalNode}.
*/
@NodeChild(value = "value", type = RubyNode.class)
public abstract class WrapInThreadLocalNode extends RubyNode {

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

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

@Specialization
public ThreadLocal wrap(Object value) {
return wrap(getContext(), value);
}

public static ThreadLocal wrap(RubyContext context, Object value) {
final RubyContext finalContext = context;

final ThreadLocal threadLocal = new ThreadLocal() {

@Override
protected Object initialValue() {
return finalContext.getCoreLibrary().getNilObject();
}

};

threadLocal.set(value);

return threadLocal;
}

}
@@ -10,6 +10,8 @@
package org.jruby.truffle.nodes.globals;

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.frame.VirtualFrame;
import com.oracle.truffle.api.source.SourceSection;
import org.jruby.truffle.nodes.RubyNode;
@@ -1137,6 +1137,8 @@ public RubyNode visitGlobalAsgnNode(org.jruby.ast.GlobalAsgnNode node) {
rhs = new CheckRecordSeparatorVariableTypeNode(context, sourceSection, rhs);
} else if (name.equals("$,")) {
rhs = new CheckOutputSeparatorVariableTypeNode(context, sourceSection, rhs);
} else if (name.equals("$_")) {
rhs = WrapInThreadLocalNodeFactory.create(context, sourceSection, rhs);
}

if (readOnlyGlobalVariables.contains(name)) {
@@ -1196,7 +1198,11 @@ public RubyNode visitGlobalVarNode(org.jruby.ast.GlobalVarNode node) {

environment.declareVarWhereAllowed(name);

final RubyNode readNode = environment.findLocalVarNode(name, sourceSection);
RubyNode readNode = environment.findLocalVarNode(name, sourceSection);

if (name.equals("$_")) {
readNode = GetFromThreadLocalNodeFactory.create(context, sourceSection, readNode);
}

return readNode;
} else if (THREAD_LOCAL_GLOBAL_VARIABLES.contains(name)) {

This file was deleted.

2 comments on commit 73aa13c

@nirvdrum
Copy link
Contributor

@nirvdrum nirvdrum commented on 73aa13c Dec 29, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought Truffle 0.6 was going to give us the guard methods for free for declared types. Did that not land?

@chrisseaton
Copy link
Contributor Author

@chrisseaton chrisseaton commented on 73aa13c Dec 29, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No - we've got a bug open for it, but it didn't happen yet.

Please sign in to comment.