Skip to content

Commit

Permalink
[Truffle] Remove use of slow ruby helper in IO primitives.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisseaton committed May 26, 2015
1 parent 6fc0135 commit 73285cb
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 28 deletions.
Expand Up @@ -40,13 +40,19 @@
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.object.*;
import com.oracle.truffle.api.source.SourceSection;

import jnr.constants.platform.Errno;
import jnr.constants.platform.Fcntl;

import jnr.ffi.byref.IntByReference;
import org.jruby.RubyEncoding;
import org.jruby.truffle.nodes.dispatch.CallDispatchHeadNode;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNode;
import org.jruby.truffle.nodes.dispatch.DispatchHeadNodeFactory;
import org.jruby.truffle.nodes.objects.Allocator;
import org.jruby.truffle.runtime.RubyContext;
import org.jruby.truffle.runtime.control.RaiseException;
import org.jruby.truffle.runtime.core.RubyArray;
Expand All @@ -63,33 +69,117 @@

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.EnumSet;

public abstract class IOPrimitiveNodes {

private static int STDOUT = 1;

private static final String IBUFFER_IDENTIFIER = "@ibuffer";
private static final Property IBUFFER_PROPERTY;

private static final String LINENO_IDENTIFIER = "@lineno";
private static final Property LINENO_PROPERTY;

private static final String DESCRIPTOR_IDENTIFIER = "@descriptor";
private static final Property DESCRIPTOR_PROPERTY;

private static final String MODE_IDENTIFIER = "@mode";
private static final Property MODE_PROPERTY;

This comment has been minimized.

Copy link
@eregon

eregon May 27, 2015

Member

This is great! So it means we can use predefined properties for ivars as well, we just need the right identifier.
(And no more needing internal nodes and translator hacks to translate those)

This comment has been minimized.

Copy link
@chrisseaton

chrisseaton May 27, 2015

Author Contributor

Yeah, but I didn't think there were any other cases where this would be useful. Are you thinking of Time?

This comment has been minimized.

Copy link
@eregon

eregon May 27, 2015

Member

Yes, potentially.
I know I wanted to do this for JRuby's date library but it was not really feasible to have it both as a an easy field to access from Java and a ruby ivar.


private static final DynamicObjectFactory IO_FACTORY;

static {
final Shape.Allocator allocator = RubyBasicObject.LAYOUT.createAllocator();

IBUFFER_PROPERTY = Property.create(IBUFFER_IDENTIFIER, allocator.locationForType(RubyBasicObject.class, EnumSet.of(LocationModifier.NonNull)), 0);
LINENO_PROPERTY = Property.create(LINENO_IDENTIFIER, allocator.locationForType(Integer.class, EnumSet.of(LocationModifier.NonNull)), 0);
DESCRIPTOR_PROPERTY = Property.create(DESCRIPTOR_IDENTIFIER, allocator.locationForType(Integer.class, EnumSet.of(LocationModifier.NonNull)), 0);
MODE_PROPERTY = Property.create(MODE_IDENTIFIER, allocator.locationForType(RubyBasicObject.class, EnumSet.of(LocationModifier.NonNull)), 0);

IO_FACTORY = RubyBasicObject.EMPTY_SHAPE
.addProperty(IBUFFER_PROPERTY)
.addProperty(LINENO_PROPERTY)
.addProperty(DESCRIPTOR_PROPERTY)
.addProperty(MODE_PROPERTY)
.createFactory();
}

public static class IOAllocator implements Allocator {
@Override
public RubyBasicObject allocate(RubyContext context, RubyClass rubyClass, Node currentNode) {
return new RubyBasicObject(rubyClass, IO_FACTORY.newInstance(context.getCoreLibrary().getNilObject(), 0, 0, context.getCoreLibrary().getNilObject()));
}
}

private static Object getIBuffer(RubyBasicObject io) {
assert io.getDynamicObject().getShape().hasProperty(IBUFFER_IDENTIFIER);
return IBUFFER_PROPERTY.get(io.getDynamicObject(), true);
}

private static Object getLineNo(RubyBasicObject io) {
assert io.getDynamicObject().getShape().hasProperty(LINENO_IDENTIFIER);
return IBUFFER_PROPERTY.get(io.getDynamicObject(), true);
}

private static int getDescriptor(RubyBasicObject io) {
assert io.getDynamicObject().getShape().hasProperty(DESCRIPTOR_IDENTIFIER);
return (int) DESCRIPTOR_PROPERTY.get(io.getDynamicObject(), true);
}

private static Object getMode(RubyBasicObject io) {
assert io.getDynamicObject().getShape().hasProperty(MODE_IDENTIFIER);
return MODE_PROPERTY.get(io.getDynamicObject(), true);
}

public static void setDescriptor(RubyBasicObject io, int newDescriptor) {
assert io.getDynamicObject().getShape().hasProperty(DESCRIPTOR_IDENTIFIER);

try {
DESCRIPTOR_PROPERTY.set(io.getDynamicObject(), newDescriptor, io.getDynamicObject().getShape());
} catch (IncompatibleLocationException | FinalLocationException e) {
throw new UnsupportedOperationException(e);
}
}

public static void setMode(RubyBasicObject io, Object newMode) {
assert io.getDynamicObject().getShape().hasProperty(MODE_IDENTIFIER);

try {
MODE_PROPERTY.set(io.getDynamicObject(), newMode, io.getDynamicObject().getShape());
} catch (IncompatibleLocationException | FinalLocationException e) {
throw new UnsupportedOperationException(e);
}
}

@RubiniusPrimitive(name = "io_allocate")
public static abstract class IOAllocatePrimitiveNode extends RubiniusPrimitiveNode {

@Child private CallDispatchHeadNode newBufferNode;

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

@Specialization
public RubyBasicObject allocate(VirtualFrame frame, RubyClass classToAllocate) {
final RubyBasicObject object = new RubyBasicObject(classToAllocate);
rubyWithSelf(frame, object, "@ibuffer = IO::InternalBuffer.new");
rubyWithSelf(frame, object, "@lineno = 0");
return object;
final Object buffer = newBufferNode.call(frame, getContext().getCoreLibrary().getIOBufferClass(), "new", null);
return new RubyBasicObject(classToAllocate, IO_FACTORY.newInstance(buffer, 0, 0, nil()));
}

}

@RubiniusPrimitive(name = "io_connect_pipe", needsSelf = false)
public static abstract class IOConnectPipeNode extends RubiniusPrimitiveNode {

private final int RDONLY;
private final int WRONLY;

public IOConnectPipeNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
RDONLY = (int) context.getRubiniusConfiguration().get("rbx.platform.file.O_RDONLY");
WRONLY = (int) context.getRubiniusConfiguration().get("rbx.platform.file.O_WRONLY");
}

@Specialization
Expand All @@ -104,11 +194,11 @@ public boolean connectPipe(VirtualFrame frame, RubyBasicObject lhs, RubyBasicObj
newOpenFd(fds[0]);
newOpenFd(fds[1]);

rubyWithSelf(frame, lhs, "@descriptor = fd", "fd", fds[0]);
rubyWithSelf(frame, lhs, "@mode = File::Constants::RDONLY");
setDescriptor(lhs, fds[0]);
setMode(lhs, RDONLY);

rubyWithSelf(frame, rhs, "@descriptor = fd", "fd", fds[1]);
rubyWithSelf(frame, rhs, "@mode = File::Constants::WRONLY");
setDescriptor(rhs, fds[1]);
setMode(rhs, WRONLY);

return true;
}
Expand Down Expand Up @@ -188,7 +278,7 @@ public int ftruncate(VirtualFrame frame, RubyBasicObject io, int length) {

@Specialization
public int ftruncate(VirtualFrame frame, RubyBasicObject io, long length) {
final int fd = (int) rubyWithSelf(frame, io, "@descriptor");
final int fd = getDescriptor(io);
return posix().ftruncate(fd, length);
}

Expand Down Expand Up @@ -225,7 +315,7 @@ public IOEnsureOpenPrimitiveNode(RubyContext context, SourceSection sourceSectio
@Specialization
public RubyBasicObject ensureOpen(VirtualFrame frame, RubyBasicObject file) {
// TODO BJF 13-May-2015 Handle nil case
final int fd = (int) rubyWithSelf(frame, file, "@descriptor");
final int fd = getDescriptor(file);
if(fd == -1){
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().ioError("closed stream",this));
Expand All @@ -241,14 +331,17 @@ public RubyBasicObject ensureOpen(VirtualFrame frame, RubyBasicObject file) {
@RubiniusPrimitive(name = "io_reopen")
public static abstract class IOReopenPrimitiveNode extends RubiniusPrimitiveNode {

@Child private CallDispatchHeadNode resetBufferingNode;

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

@Specialization
public Object reopen(VirtualFrame frame, RubyBasicObject file, RubyBasicObject io) {
final int fd = (int) rubyWithSelf(frame, file, "@descriptor");
final int fdOther = (int) rubyWithSelf(frame, io, "@descriptor");
final int fd = getDescriptor(file);
final int fdOther = getDescriptor(io);

final int result = posix().dup2(fd, fdOther);
if (result == -1) {
Expand All @@ -261,9 +354,9 @@ public Object reopen(VirtualFrame frame, RubyBasicObject file, RubyBasicObject i
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().errnoError(posix().errno(), this));
}
rubyWithSelf(frame, file, "@mode = mode", "mode", mode);
setMode(file, mode);

rubyWithSelf(frame, io, "reset_buffering");
resetBufferingNode.call(frame, io, "reset_buffering", null);

return nil();
}
Expand All @@ -273,13 +366,16 @@ public Object reopen(VirtualFrame frame, RubyBasicObject file, RubyBasicObject i
@RubiniusPrimitive(name = "io_reopen_path")
public static abstract class IOReopenPathPrimitiveNode extends RubiniusPrimitiveNode {

@Child private CallDispatchHeadNode resetBufferingNode;

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

@Specialization
public Object reopenPath(VirtualFrame frame, RubyBasicObject file, RubyString path, int mode) {
int fd = (int) rubyWithSelf(frame, file, "@descriptor");
int fd = getDescriptor(file);
final String pathString = path.toString();

int otherFd = posix().open(pathString, mode, 666);
Expand All @@ -292,7 +388,7 @@ public Object reopenPath(VirtualFrame frame, RubyBasicObject file, RubyString pa
if (result == -1) {
final int errno = posix().errno();
if (errno == Errno.EBADF.intValue()) {
rubyWithSelf(frame, file, "@descriptor = desc", "desc", otherFd);
setDescriptor(file, otherFd);
fd = otherFd;
} else {
if (otherFd > 0) {
Expand All @@ -312,9 +408,9 @@ public Object reopenPath(VirtualFrame frame, RubyBasicObject file, RubyString pa
CompilerDirectives.transferToInterpreter();
throw new RaiseException(getContext().getCoreLibrary().errnoError(posix().errno(), this));
}
rubyWithSelf(frame, file, "@mode = mode", "mode", newMode);
setMode(file, newMode);

rubyWithSelf(frame, file, "reset_buffering");
resetBufferingNode.call(frame, file, "reset_buffering", null);

return nil();
}
Expand All @@ -330,7 +426,7 @@ public IOWritePrimitiveNode(RubyContext context, SourceSection sourceSection) {

@Specialization
public int write(VirtualFrame frame, RubyBasicObject file, RubyString string) {
final int fd = (int) rubyWithSelf(frame, file, "@descriptor");
final int fd = getDescriptor(file);

if (getContext().getDebugStandardOut() != null && fd == STDOUT) {
getContext().getDebugStandardOut().write(string.getByteList().unsafeBytes(), string.getByteList().begin(), string.getByteList().length());
Expand Down Expand Up @@ -368,20 +464,24 @@ public int write(VirtualFrame frame, RubyBasicObject file, RubyString string) {
@RubiniusPrimitive(name = "io_close")
public static abstract class IOClosePrimitiveNode extends RubiniusPrimitiveNode {

@Child private CallDispatchHeadNode ensureOpenNode;

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

@Specialization
public int close(VirtualFrame frame, RubyBasicObject io) {
rubyWithSelf(frame, io, "ensure_open");
final int fd = (int) rubyWithSelf(frame, io, "@descriptor");
ensureOpenNode.call(frame, io, "ensure_open", null);

final int fd = getDescriptor(io);

if (fd == -1) {
return 0;
}

rubyWithSelf(frame, io, "@descriptor = -1");
setDescriptor(io, -1);

if (fd < 3) {
return 0;
Expand All @@ -408,7 +508,7 @@ public IOSeekPrimitiveNode(RubyContext context, SourceSection sourceSection) {

@Specialization
public int seek(VirtualFrame frame, RubyBasicObject io, int amount, int whence) {
final int fd = (int) rubyWithSelf(frame, io, "@descriptor");
final int fd = getDescriptor(io);
return posix().lseek(fd, amount, whence);
}

Expand All @@ -423,7 +523,7 @@ public AcceptNode(RubyContext context, SourceSection sourceSection) {

@Specialization
public int accept(VirtualFrame frame, RubyBasicObject io) {
final int fd = (int) rubyWithSelf(frame, io, "@descriptor");
final int fd = getDescriptor(io);

final IntByReference addressLength = new IntByReference(16);
final long address = UnsafeHolder.U.allocateMemory(addressLength.intValue());
Expand Down Expand Up @@ -455,7 +555,7 @@ public IOSysReadPrimitiveNode(RubyContext context, SourceSection sourceSection)

@Specialization
public RubyString sysread(VirtualFrame frame, RubyBasicObject file, int length) {
final int fd = (int) rubyWithSelf(frame, file, "@descriptor");
final int fd = getDescriptor(file);

final ByteBuffer buffer = ByteBuffer.allocate(length);

Expand Down Expand Up @@ -483,10 +583,9 @@ public IOSelectPrimitiveNode(RubyContext context, SourceSection sourceSection) {
super(context, sourceSection);
}

@CompilerDirectives.TruffleBoundary
@Specialization(guards = {"isNil(writables)", "isNil(errorables)"})
public Object select(VirtualFrame frame, RubyArray readables, RubyBasicObject writables, RubyBasicObject errorables, int timeout) {
CompilerDirectives.transferToInterpreter();

final Object[] readableObjects = readables.slowToArray();
final int[] readableFds = getFileDescriptors(frame, readables);

Expand Down Expand Up @@ -524,7 +623,11 @@ private int[] getFileDescriptors(VirtualFrame frame, RubyArray fileDescriptorArr
final int[] fileDescriptors = new int[objects.length];

for (int n = 0; n < objects.length; n++) {
fileDescriptors[n] = (int) rubyWithSelf(frame, objects[n], "@descriptor");
if (!(objects[n] instanceof RubyBasicObject)) {
throw new UnsupportedOperationException();
}

fileDescriptors[n] = getDescriptor((RubyBasicObject) objects[n]);
}

return fileDescriptors;
Expand Down
Expand Up @@ -135,6 +135,7 @@ public class CoreLibrary {
private final RubyClass byteArrayClass;
private final RubyClass fiberErrorClass;
private final RubyClass threadErrorClass;
private final RubyClass ioBufferClass;

private final RubyArray argv;
private final RubyBasicObject globalVariablesObject;
Expand Down Expand Up @@ -321,6 +322,8 @@ public CoreLibrary(RubyContext context) {
timeClass = defineClass("Time", new RubyTime.TimeAllocator());
trueClass = defineClass("TrueClass", NO_ALLOCATOR);
unboundMethodClass = defineClass("UnboundMethod", NO_ALLOCATOR);
final RubyClass ioClass = defineClass("IO", new IOPrimitiveNodes.IOAllocator());
ioBufferClass = defineClass(ioClass, objectClass, "InternalBuffer");

// Modules

Expand Down Expand Up @@ -1377,4 +1380,8 @@ public RubyClass getThreadBacktraceLocationClass() {
return threadBacktraceLocationClass;
}

public RubyClass getIOBufferClass() {
return ioBufferClass;
}

}

0 comments on commit 73285cb

Please sign in to comment.