/*
* Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.nfi.test;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.interop.ForeignAccess;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.Message;
import com.oracle.truffle.api.interop.TruffleObject;
import com.oracle.truffle.api.interop.java.JavaInterop;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.nfi.test.interop.BoxedPrimitive;
import com.oracle.truffle.nfi.test.interop.TestCallback;
import com.oracle.truffle.nfi.types.NativeSimpleType;
import com.oracle.truffle.tck.TruffleRunner;
import com.oracle.truffle.tck.TruffleRunner.Inject;
import java.util.ArrayList;
import java.util.Collection;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.core.Is.is;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
@Parameterized.UseParametersRunnerFactory(TruffleRunner.ParametersFactory.class)
public class NumericNFITest extends NFITest {
public static final NativeSimpleType[] NUMERIC_TYPES = {
NativeSimpleType.UINT8, NativeSimpleType.SINT8,
NativeSimpleType.UINT16, NativeSimpleType.SINT16,
NativeSimpleType.UINT32, NativeSimpleType.SINT32,
NativeSimpleType.UINT64, NativeSimpleType.SINT64,
NativeSimpleType.FLOAT, NativeSimpleType.DOUBLE,
NativeSimpleType.POINTER
};
@Parameters(name = "{0}")
public static Collection<Object[]> data() {
ArrayList<Object[]> ret = new ArrayList<>();
for (NativeSimpleType type : NUMERIC_TYPES) {
ret.add(new Object[]{type});
}
return ret;
}
@Parameter(0) public NativeSimpleType type;
private void checkExpectedRet(long expected, Object arg) {
checkExpected("return", expected, arg);
}
private void checkExpectedArg(long expected, Object arg) {
checkExpected("argument", expected, arg);
}
static long unboxNumber(Object arg) {
Object value = arg;
while (value instanceof TruffleObject) {
TruffleObject obj = (TruffleObject) value;
Assert.assertTrue("isBoxed", JavaInterop.isBoxed(obj));
value = JavaInterop.unbox(obj);
}
Assert.assertThat(value, is(instanceOf(Number.class)));
return ((Number) value).longValue();
}
private void checkExpected(String thing, long expected, Object arg) {
Object value = arg;
switch (type) {
case UINT8:
case SINT8:
Assert.assertThat(thing + " type", value, is(instanceOf(Byte.class)));
break;
case UINT16:
case SINT16:
Assert.assertThat(thing + " type", value, is(instanceOf(Short.class)));
break;
case UINT32:
case SINT32:
Assert.assertThat(thing + " type", value, is(instanceOf(Integer.class)));
break;
case UINT64:
case SINT64:
Assert.assertThat(thing + " type", value, is(instanceOf(Long.class)));
break;
case FLOAT:
Assert.assertThat(thing + " type", value, is(instanceOf(Float.class)));
break;
case DOUBLE:
Assert.assertThat(thing + " type", value, is(instanceOf(Double.class)));
break;
case POINTER:
Assert.assertThat(thing + " type", value, is(instanceOf(TruffleObject.class)));
TruffleObject obj = (TruffleObject) value;
Assert.assertTrue(thing + " is boxed", JavaInterop.isBoxed(obj));
value = JavaInterop.unbox(obj);
Assert.assertThat("unboxed " + thing, value, is(instanceOf(Long.class)));
break;
default:
Assert.fail();
}
Assert.assertEquals(expected, ((Number) value).longValue());
}
/**
* Test all primitive types as argument and return type of native functions.
*/
public class TestIncrementNode extends SendExecuteNode {
public TestIncrementNode() {
super("increment_" + type, String.format("(%s):%s", type, type), 1);
}
}
@Test
public void testIncrement(@Inject(TestIncrementNode.class) CallTarget callTarget) {
Object ret = callTarget.call(42);
checkExpectedRet(43, ret);
}
/**
* Test boxed primitive types as argument to native functions.
*
* @param callTarget
*/
@Test
public void testBoxed(@Inject(TestIncrementNode.class) CallTarget callTarget) {
Object ret = callTarget.call(new BoxedPrimitive(42));
checkExpectedRet(43, ret);
}
/**
* Test callback function as argument to native functions, and all primitive types as argument
* and return type of callback functions.
*/
public class TestCallbackNode extends SendExecuteNode {
public TestCallbackNode() {
super("callback_" + type, String.format("((%s):%s, %s) : %s", type, type, type, type), 2);
}
}
@Test
public void testCallback(@Inject(TestCallbackNode.class) CallTarget callTarget) {
TruffleObject callback = new TestCallback(1, (args) -> {
checkExpectedArg(42 + 1, args[0]);
return unboxNumber(args[0]) + 5;
});
Object ret = callTarget.call(callback, 42);
checkExpectedRet((42 + 6) * 2, ret);
}
/**
* Test callback function as return type of native function.
*/
public class TestCallbackRetNode extends NFITestRootNode {
final TruffleObject getIncrement = lookupAndBind("callback_ret_" + type, String.format("() : (%s):%s", type, type));
@Child Node executeGetIncrement = Message.createExecute(0).createNode();
@Child Node executeClosure = Message.createExecute(1).createNode();
@Override
public Object executeTest(VirtualFrame frame) throws InteropException {
Object functionPtr = ForeignAccess.sendExecute(executeGetIncrement, getIncrement);
checkIsClosure(functionPtr);
return ForeignAccess.sendExecute(executeClosure, (TruffleObject) functionPtr, 42);
}
@TruffleBoundary
private void checkIsClosure(Object value) {
Assert.assertThat("closure", value, is(instanceOf(TruffleObject.class)));
}
}
@Test
public void testCallbackRet(@Inject(TestCallbackRetNode.class) CallTarget callTarget) {
Object ret = callTarget.call();
checkExpectedRet(43, ret);
}
private String getPingPongSignature() {
String fnPointer = String.format("(%s):%s", type, type);
String wrapPointer = String.format("(%s):%s", fnPointer, fnPointer);
return String.format("(%s, %s) : %s", wrapPointer, type, type);
}
/**
* Test callback functions as argument and return type of other callback functions.
*/
public class TestPingPongNode extends SendExecuteNode {
public TestPingPongNode() {
super("pingpong_" + type, getPingPongSignature(), 2);
}
}
@Test
public void testPingPong(@Inject(TestPingPongNode.class) CallTarget callTarget) {
TruffleObject wrap = new TestCallback(1, (args) -> {
Assert.assertThat("argument", args[0], is(instanceOf(TruffleObject.class)));
TruffleObject fn = (TruffleObject) args[0];
TruffleObject wrapped = new TestCallback(1, (innerArgs) -> {
checkExpectedArg(6, innerArgs[0]);
try {
return ForeignAccess.sendExecute(Message.createExecute(1).createNode(), fn, unboxNumber(innerArgs[0]) * 3);
} catch (InteropException ex) {
throw new AssertionError(ex);
}
});
return wrapped;
});
Object ret = callTarget.call(wrap, 5);
checkExpectedRet(38, ret);
}
}