Skip to content

Commit

Permalink
Implement support for the NEW message
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisseaton committed Aug 16, 2017
1 parent 504f269 commit ce5ac9d
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 2 deletions.
14 changes: 14 additions & 0 deletions doc/user/interop.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Doesn't pass a block.
Calls the method with the name you provided, passing the arguments as you'd
expect. Doesn't pass a block.

### `NEW`

Calls `new`, passing the arguments as you'd expect.

### `HAS_SIZE`

Returns true if the object responds to `size`.
Expand Down Expand Up @@ -154,6 +158,10 @@ In all cases where a call is made no block is passed.

`name` can be a `String` or `Symbol`.

### `NEW`

`Truffle::Interop.new(receiver, *args)`

### `HAS_SIZE`

`Truffle::Interop.size?(value)`
Expand Down Expand Up @@ -221,6 +229,10 @@ Not supported.

`object.name!` if there are no arguments (otherwise it is a `READ`)

### `NEW`

`object.new(*args)`

### `HAS_SIZE`

Not supported.
Expand Down Expand Up @@ -289,6 +301,8 @@ an integer, or anything else

`object.name!` sends `INVOKE`

`object.new(*args)` sends `NEW`

## Import and export

`Truffle::Interop.export(:name, value)`
Expand Down
31 changes: 31 additions & 0 deletions spec/truffle/interop/new_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Copyright (c) 2017 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

require_relative '../../ruby/spec_helper'

describe "Truffle::Interop.new" do

class NewTestClass

attr_reader :x

def initialize(a, b)
@x = a + b
end

end

it "creates new instances of objects" do
Truffle::Interop.new(NewTestClass, 14, 2).should be_an_instance_of NewTestClass
end

it "calls initialize" do
Truffle::Interop.new(NewTestClass, 14, 2).x.should == 16
end

end
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ public class Options {
public final int INTEROP_READ_CACHE;
public final int INTEROP_WRITE_CACHE;
public final int INTEROP_INVOKE_CACHE;
public final int INTEROP_NEW_CACHE;
public final int TIME_FORMAT_CACHE;
public final int POW_CACHE;
public final boolean CLONE_DEFAULT;
Expand Down Expand Up @@ -203,6 +204,7 @@ public class Options {
INTEROP_READ_CACHE = builder.getOrDefault(OptionsCatalog.INTEROP_READ_CACHE, DEFAULT_CACHE);
INTEROP_WRITE_CACHE = builder.getOrDefault(OptionsCatalog.INTEROP_WRITE_CACHE, DEFAULT_CACHE);
INTEROP_INVOKE_CACHE = builder.getOrDefault(OptionsCatalog.INTEROP_INVOKE_CACHE, DEFAULT_CACHE);
INTEROP_NEW_CACHE = builder.getOrDefault(OptionsCatalog.INTEROP_NEW_CACHE, DEFAULT_CACHE);
TIME_FORMAT_CACHE = builder.getOrDefault(OptionsCatalog.TIME_FORMAT_CACHE, DEFAULT_CACHE);
POW_CACHE = builder.getOrDefault(OptionsCatalog.POW_CACHE, DEFAULT_CACHE);
CLONE_DEFAULT = builder.getOrDefault(OptionsCatalog.CLONE_DEFAULT);
Expand Down Expand Up @@ -392,6 +394,8 @@ public Object fromDescription(OptionDescription<?> description) {
return INTEROP_WRITE_CACHE;
case "ruby.interop.invoke.cache":
return INTEROP_INVOKE_CACHE;
case "ruby.interop.new.cache":
return INTEROP_NEW_CACHE;
case "ruby.time.format.cache":
return TIME_FORMAT_CACHE;
case "ruby.integer.pow.cache":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,10 @@ public class OptionsCatalog {
"ruby.interop.invoke.cache",
"Cache size for interop INVOKE messages",
DEFAULT_CACHE.getDefaultValue());
public static final IntegerOptionDescription INTEROP_NEW_CACHE = new IntegerOptionDescription(
"ruby.interop.new.cache",
"Cache size for interop NEW messages",
DEFAULT_CACHE.getDefaultValue());
public static final IntegerOptionDescription TIME_FORMAT_CACHE = new IntegerOptionDescription(
"ruby.time.format.cache",
"Cache size for parsed time format specifiers",
Expand Down Expand Up @@ -611,6 +615,8 @@ public static OptionDescription<?> fromName(String name) {
return INTEROP_WRITE_CACHE;
case "ruby.interop.invoke.cache":
return INTEROP_INVOKE_CACHE;
case "ruby.interop.new.cache":
return INTEROP_NEW_CACHE;
case "ruby.time.format.cache":
return TIME_FORMAT_CACHE;
case "ruby.integer.pow.cache":
Expand Down Expand Up @@ -766,6 +772,7 @@ public static OptionDescription<?>[] allDescriptions() {
INTEROP_READ_CACHE,
INTEROP_WRITE_CACHE,
INTEROP_INVOKE_CACHE,
INTEROP_NEW_CACHE,
TIME_FORMAT_CACHE,
POW_CACHE,
CLONE_DEFAULT,
Expand Down
54 changes: 53 additions & 1 deletion src/main/java/org/truffleruby/interop/InteropNodes.java
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,62 @@ protected String objectToString(Object object) {
return object.toString();
}

protected int getCacheLimit() {
return getContext().getOptions().INTEROP_INVOKE_CACHE;
}

}

@CoreMethod(names = "new", isModuleFunction = true, required = 1, rest = true)
public abstract static class NewNode extends CoreMethodArrayArgumentsNode {

@Specialization(
guards = "args.length == cachedArgsLength",
limit = "getCacheLimit()"
)
public Object newCached(
TruffleObject receiver,
Object[] args,
@Cached("args.length") int cachedArgsLength,
@Cached("createNewNode(cachedArgsLength)") Node newNode,
@Cached("create()") BranchProfile exceptionProfile) {
try {
return ForeignAccess.sendNew(
newNode,
receiver,
args);
} catch (UnsupportedTypeException
| ArityException
| UnsupportedMessageException e) {
exceptionProfile.enter();
throw new JavaException(e);
}
}

@Specialization(replaces = "newCached")
public Object newUncached(
TruffleObject receiver,
Object[] args) {
Log.notOptimizedOnce("megamorphic interop NEW message send");

final Node invokeNode = createNewNode(args.length);

try {
return ForeignAccess.sendNew(invokeNode, receiver, args);
} catch (UnsupportedTypeException
| ArityException
| UnsupportedMessageException e) {
throw new JavaException(e);
}
}

@TruffleBoundary
protected Node createNewNode(int argsLength) {
return Message.createNew(argsLength).createNode();
}

protected int getCacheLimit() {
return getContext().getOptions().INTEROP_INVOKE_CACHE;
return getContext().getOptions().INTEROP_NEW_CACHE;
}

}
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/org/truffleruby/interop/OutgoingForeignCallNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ protected OutgoingNode createHelperNode(int argsLength) {
return new PropertyWriteOutgoingNode(name.substring(0, name.length() - 1));
} else if (name.equals("call")) {
return new CallOutgoingNode(argsLength);
} else if (name.equals("new")) {
return new NewOutgoingNode(argsLength);
} else if (name.equals("nil?") && argsLength == 0) {
return new IsNilOutgoingNode();
} else if (name.endsWith("!") && argsLength == 0) {
Expand Down Expand Up @@ -280,6 +282,31 @@ public Object executeCall(VirtualFrame frame, TruffleObject receiver, Object[] a

}

protected class NewOutgoingNode extends OutgoingNode {

private final int argsLength;

@Child private Node node;

public NewOutgoingNode(int argsLength) {
this.argsLength = argsLength;
node = Message.createNew(argsLength).createNode();
}

@Override
public Object executeCall(VirtualFrame frame, TruffleObject receiver, Object[] args) {
assert args.length == argsLength;

try {
return ForeignAccess.sendNew(node, receiver, args);
} catch (UnsupportedTypeException | ArityException | UnsupportedMessageException e) {
exceptionProfile();
throw new JavaException(e);
}
}

}

protected class IsNilOutgoingNode extends OutgoingNode {

@Child private Node node;
Expand Down
14 changes: 13 additions & 1 deletion src/main/java/org/truffleruby/interop/RubyMessageResolution.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.truffleruby.language.RubyGuards;
import org.truffleruby.language.RubyObjectType;
import org.truffleruby.language.control.RaiseException;
import org.truffleruby.language.dispatch.CallDispatchHeadNode;
import org.truffleruby.language.dispatch.DispatchAction;
import org.truffleruby.language.dispatch.DispatchHeadNode;
import org.truffleruby.language.dispatch.DoesRespondDispatchHeadNode;
Expand Down Expand Up @@ -270,12 +271,23 @@ protected Object access(VirtualFrame frame, DynamicObject object, Object[] argum
@Resolve(message = "INVOKE")
public static abstract class ForeignInvokeNode extends Node {

@Child private DispatchHeadNode dispatchHeadNode = insert(new DispatchHeadNode(true, false, MissingBehavior.CALL_METHOD_MISSING, DispatchAction.CALL_METHOD));
@Child private DispatchHeadNode dispatchHeadNode = CallDispatchHeadNode.createOnSelf();

protected Object access(VirtualFrame frame, DynamicObject receiver, String name, Object[] arguments) {
return dispatchHeadNode.dispatch(frame, receiver, name, null, arguments);
}

}

@Resolve(message = "NEW")
public static abstract class ForeignNewNode extends Node {

@Child private DispatchHeadNode dispatchHeadNode = CallDispatchHeadNode.createOnSelf();

protected Object access(VirtualFrame frame, DynamicObject receiver, Object[] arguments) {
return dispatchHeadNode.dispatch(frame, receiver, "new", null, arguments);
}

}

}
1 change: 1 addition & 0 deletions tool/options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ INTEROP_EXECUTE_CACHE: [interop.execute.cache, integer, DEFAULT_CACHE, Cache siz
INTEROP_READ_CACHE: [interop.read.cache, integer, DEFAULT_CACHE, Cache size for interop READ messages]
INTEROP_WRITE_CACHE: [interop.write.cache, integer, DEFAULT_CACHE, Cache size for interop WRITE messages]
INTEROP_INVOKE_CACHE: [interop.invoke.cache, integer, DEFAULT_CACHE, Cache size for interop INVOKE messages]
INTEROP_NEW_CACHE: [interop.new.cache, integer, DEFAULT_CACHE, Cache size for interop NEW messages]
TIME_FORMAT_CACHE: [time.format.cache, integer, DEFAULT_CACHE, Cache size for parsed time format specifiers]
POW_CACHE: [integer.pow.cache, integer, DEFAULT_CACHE, Cache size for Integer#** with a constant exponent]

Expand Down

0 comments on commit ce5ac9d

Please sign in to comment.