/* * 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; import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; import com.oracle.truffle.api.interop.Message; import com.oracle.truffle.api.interop.TruffleObject; import com.oracle.truffle.api.interop.UnsupportedTypeException; import com.oracle.truffle.api.interop.java.JavaInterop; import com.oracle.truffle.nfi.ClosureArgumentNode.ObjectClosureArgumentNode; import com.oracle.truffle.nfi.ClosureArgumentNode.StringClosureArgumentNode; import com.oracle.truffle.nfi.ClosureArgumentNodeFactory.BufferClosureArgumentNodeGen; import com.oracle.truffle.nfi.NativeArgumentBuffer.TypeTag; import com.oracle.truffle.nfi.SerializeArgumentNode.SerializeObjectArgumentNode; import com.oracle.truffle.nfi.SerializeArgumentNodeFactory.SerializeArrayArgumentNodeGen; import com.oracle.truffle.nfi.SerializeArgumentNodeFactory.SerializeClosureArgumentNodeGen; import com.oracle.truffle.nfi.SerializeArgumentNodeFactory.SerializePointerArgumentNodeGen; import com.oracle.truffle.nfi.SerializeArgumentNodeFactory.SerializeSimpleArgumentNodeGen; import com.oracle.truffle.nfi.SerializeArgumentNodeFactory.SerializeStringArgumentNodeGen; import com.oracle.truffle.nfi.types.NativeArrayTypeMirror; import com.oracle.truffle.nfi.types.NativeFunctionTypeMirror; import com.oracle.truffle.nfi.types.NativeSimpleType; import com.oracle.truffle.nfi.types.NativeSimpleTypeMirror; import com.oracle.truffle.nfi.types.NativeTypeMirror; import com.oracle.truffle.nfi.types.NativeTypeMirror.Kind; import java.nio.ByteOrder; abstract class LibFFIType { @CompilationFinal(dimensions = 1) static final LibFFIType[] simpleTypeMap = new LibFFIType[NativeSimpleType.values().length]; @CompilationFinal(dimensions = 1) static final LibFFIType[] arrayTypeMap = new LibFFIType[NativeSimpleType.values().length]; public static LibFFIType lookupArgType(NativeTypeMirror type) { return lookup(type, false); } public static LibFFIType lookupRetType(NativeTypeMirror type) { return lookup(type, true); } private static LibFFIType lookup(NativeTypeMirror type, boolean asRetType) { switch (type.getKind()) { case SIMPLE: NativeSimpleTypeMirror simpleType = (NativeSimpleTypeMirror) type; return simpleTypeMap[simpleType.getSimpleType().ordinal()]; case ARRAY: NativeArrayTypeMirror arrayType = (NativeArrayTypeMirror) type; NativeTypeMirror elementType = arrayType.getElementType(); LibFFIType ret = null; if (elementType.getKind() == Kind.SIMPLE) { ret = arrayTypeMap[((NativeSimpleTypeMirror) elementType).getSimpleType().ordinal()]; } if (ret == null) { throw new AssertionError("unsupported array type"); } else { return ret; } case FUNCTION: NativeFunctionTypeMirror functionType = (NativeFunctionTypeMirror) type; LibFFISignature signature = LibFFISignature.create(functionType.getSignature()); return new ClosureType(signature, asRetType); } throw new AssertionError("unsupported type"); } protected static void initializeSimpleType(NativeSimpleType simpleType, int size, int alignment, long ffiType) { assert simpleTypeMap[simpleType.ordinal()] == null : "initializeSimpleType called twice for " + simpleType; simpleTypeMap[simpleType.ordinal()] = createSimpleType(simpleType, size, alignment, ffiType); arrayTypeMap[simpleType.ordinal()] = createArrayType(simpleType); } private static LibFFIType createSimpleType(NativeSimpleType simpleType, int size, int alignment, long ffiType) { switch (simpleType) { case VOID: return new VoidType(size, alignment, ffiType); case UINT8: case SINT8: case UINT16: case SINT16: case UINT32: case SINT32: case UINT64: case SINT64: case FLOAT: case DOUBLE: return new SimpleType(simpleType, size, alignment, ffiType); case POINTER: return new PointerType(size, alignment, ffiType); case STRING: return new StringType(size, alignment, ffiType); case OBJECT: return new ObjectType(size, alignment, ffiType); default: throw new AssertionError(simpleType.name()); } } private static LibFFIType createArrayType(NativeSimpleType simpleType) { switch (simpleType) { case UINT8: case SINT8: case UINT16: case SINT16: case UINT32: case SINT32: case UINT64: case SINT64: case FLOAT: case DOUBLE: return new ArrayType(simpleType); default: return null; } } static class SimpleType extends LibFFIType { final NativeSimpleType simpleType; SimpleType(NativeSimpleType simpleType, int size, int alignment, long ffiType) { super(size, alignment, 0, ffiType, Direction.BOTH); this.simpleType = simpleType; } private static Number asNumber(Object object) { if (object instanceof Number) { return (Number) object; } else if (object instanceof Boolean) { return (Boolean) object ? 1 : 0; } else if (object instanceof Character) { return (int) (Character) object; } else { throw UnsupportedTypeException.raise(new Object[]{object}); } } @Override protected void doSerialize(NativeArgumentBuffer buffer, Object value) { Number number = asNumber(value); switch (simpleType) { case UINT8: case SINT8: buffer.putInt8(number.byteValue()); break; case UINT16: case SINT16: buffer.putInt16(number.shortValue()); break; case UINT32: case SINT32: buffer.putInt32(number.intValue()); break; case UINT64: case SINT64: buffer.putInt64(number.longValue()); break; case FLOAT: buffer.putFloat(number.floatValue()); break; case DOUBLE: buffer.putDouble(number.doubleValue()); break; case POINTER: buffer.putPointer(number.longValue(), size); break; default: throw new AssertionError(simpleType.name()); } } @Override protected Object doDeserialize(NativeArgumentBuffer buffer) { switch (simpleType) { case VOID: return null; case UINT8: case SINT8: return buffer.getInt8(); case UINT16: case SINT16: return buffer.getInt16(); case UINT32: case SINT32: return buffer.getInt32(); case UINT64: case SINT64: return buffer.getInt64(); case FLOAT: return buffer.getFloat(); case DOUBLE: return buffer.getDouble(); default: throw new AssertionError(simpleType.name()); } } public final Object fromPrimitive(long primitive) { switch (simpleType) { case VOID: return new NativePointer(0); case UINT8: case SINT8: return (byte) primitive; case UINT16: case SINT16: return (short) primitive; case UINT32: case SINT32: return (int) primitive; case UINT64: case SINT64: return primitive; case FLOAT: int ret; if (ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN) { /* * This isn't really a long, but a float stored in an 8-byte container. We * want to get the first 4 byte out of it. On a little-endian machine that's * just a cast to int, but on a big-endian machine we additionally need a * shift. */ ret = (int) (primitive >>> 32); } else { ret = (int) primitive; } return Float.intBitsToFloat(ret); case DOUBLE: return Double.longBitsToDouble(primitive); case POINTER: return new NativePointer(primitive); default: throw new AssertionError(simpleType.name()); } } @Override public SerializeArgumentNode createSerializeArgumentNode() { return SerializeSimpleArgumentNodeGen.create(this); } @Override public ClosureArgumentNode createClosureArgumentNode() { return BufferClosureArgumentNodeGen.create(this); } @Override public SerializeArgumentNode createClosureReturnNode() { // special handling for small integers: return them as long (native type: ffi_arg) switch (simpleType) { case UINT8: case UINT16: case UINT32: return SerializeSimpleArgumentNodeGen.create(simpleTypeMap[NativeSimpleType.UINT64.ordinal()]); case SINT8: case SINT16: case SINT32: return SerializeSimpleArgumentNodeGen.create(simpleTypeMap[NativeSimpleType.SINT64.ordinal()]); default: return super.createClosureReturnNode(); } } @Override public Object slowpathPrepareArgument(TruffleObject value) { // we always need an unbox here return null; } } static final class VoidType extends SimpleType { private VoidType(int size, int alignment, long ffiType) { super(NativeSimpleType.VOID, size, alignment, ffiType); } @Override public SerializeArgumentNode createSerializeArgumentNode() { throw new AssertionError("invalid argument type VOID"); } @Override public ClosureArgumentNode createClosureArgumentNode() { throw new AssertionError("invalid argument type VOID"); } } static final class PointerType extends SimpleType { private PointerType(int size, int alignment, long ffiType) { super(NativeSimpleType.POINTER, size, alignment, ffiType); } @Override protected void doSerialize(NativeArgumentBuffer buffer, Object value) { if (value == null) { buffer.putPointer(0, size); } else if (value instanceof NativePointer) { NativePointer ptr = (NativePointer) value; buffer.putPointer(ptr.nativePointer, size); } else if (value instanceof NativeString) { NativeString str = (NativeString) value; buffer.putPointer(str.nativePointer, size); } else { super.doSerialize(buffer, value); } } @Override protected Object doDeserialize(NativeArgumentBuffer buffer) { return new NativePointer(buffer.getPointer(size)); } @Override public SerializeArgumentNode createSerializeArgumentNode() { return SerializePointerArgumentNodeGen.create(this); } @Override public Object slowpathPrepareArgument(TruffleObject value) { if (value instanceof NativePointer || value instanceof NativeString) { return value; } else { return super.slowpathPrepareArgument(value); } } } static final class StringType extends LibFFIType { private StringType(int size, int alignment, long ffiType) { super(size, alignment, 1, ffiType, Direction.BOTH); } @Override protected void doSerialize(NativeArgumentBuffer buffer, Object value) { if (value == null) { buffer.putPointer(0, size); } else if (value instanceof String) { String str = (String) value; buffer.putObject(TypeTag.STRING, str, size); } else if (value instanceof NativeString) { NativeString str = (NativeString) value; buffer.putPointer(str.nativePointer, size); } else { throw UnsupportedTypeException.raise(new Object[]{value}); } } @Override protected Object doDeserialize(NativeArgumentBuffer buffer) { long ptr = buffer.getPointer(size); return new NativeString(ptr); } @Override public SerializeArgumentNode createSerializeArgumentNode() { return SerializeStringArgumentNodeGen.create(this); } @Override public ClosureArgumentNode createClosureArgumentNode() { return new StringClosureArgumentNode(); } @Override public Object slowpathPrepareArgument(TruffleObject value) { if (value instanceof NativeString) { return value; } else { return null; } } } static class ObjectType extends LibFFIType { ObjectType(int size, int alignment, long ffiType) { super(size, alignment, 1, ffiType, Direction.BOTH); } @Override protected void doSerialize(NativeArgumentBuffer buffer, Object object) { buffer.putObject(TypeTag.OBJECT, object, size); } @Override protected Object doDeserialize(NativeArgumentBuffer buffer) { Object ret = buffer.getObject(size); if (ret == null) { return new NativePointer(0); } else { return ret; } } @Override public SerializeArgumentNode createSerializeArgumentNode() { return new SerializeObjectArgumentNode(this); } @Override public ClosureArgumentNode createClosureArgumentNode() { return new ObjectClosureArgumentNode(); } @Override public Object slowpathPrepareArgument(TruffleObject value) { return value; } } abstract static class BasePointerType extends LibFFIType { static { // make sure simpleTypeMap is initialized NativeAccess.ensureInitialized(); } private static LibFFIType getPointerType() { return simpleTypeMap[NativeSimpleType.POINTER.ordinal()]; } protected BasePointerType(Direction direction) { super(getPointerType().size, getPointerType().alignment, 1, getPointerType().type, direction); } } static class ArrayType extends BasePointerType { final NativeSimpleType elementType; ArrayType(NativeSimpleType elementType) { super(Direction.JAVA_TO_NATIVE_ONLY); switch (elementType) { case UINT8: case SINT8: case UINT16: case SINT16: case UINT32: case SINT32: case UINT64: case SINT64: case FLOAT: case DOUBLE: this.elementType = elementType; break; default: throw new IllegalArgumentException(String.format("only primitive array types are supported, got [%s]", elementType)); } } @Override protected void doSerialize(NativeArgumentBuffer buffer, Object value) { if (value == null) { buffer.putPointer(0, size); return; } TypeTag tag = null; switch (elementType) { case UINT8: case SINT8: if (value instanceof byte[]) { tag = TypeTag.BYTE_ARRAY; } else if (value instanceof boolean[]) { tag = TypeTag.BOOLEAN_ARRAY; } break; case UINT16: case SINT16: if (value instanceof short[]) { tag = TypeTag.SHORT_ARRAY; } else if (value instanceof char[]) { tag = TypeTag.CHAR_ARRAY; } break; case UINT32: case SINT32: if (value instanceof int[]) { tag = TypeTag.INT_ARRAY; } break; case UINT64: case SINT64: if (value instanceof long[]) { tag = TypeTag.LONG_ARRAY; } break; case FLOAT: if (value instanceof float[]) { tag = TypeTag.FLOAT_ARRAY; } break; case DOUBLE: if (value instanceof double[]) { tag = TypeTag.DOUBLE_ARRAY; } break; default: throw new AssertionError(elementType.name()); } if (tag != null) { buffer.putObject(tag, value, size); } else { throw UnsupportedTypeException.raise(new Object[]{value}); } } @Override public SerializeArgumentNode createSerializeArgumentNode() { return SerializeArrayArgumentNodeGen.create(this); } @Override protected Object doDeserialize(NativeArgumentBuffer buffer) { throw new AssertionError("Arrays can only be passed from Java to native"); } @Override public ClosureArgumentNode createClosureArgumentNode() { throw new AssertionError("Arrays can only be passed from Java to native"); } protected Class<?> getArrayType(TruffleObject object) { switch (elementType) { case UINT8: case SINT8: if (JavaInterop.isJavaObject(byte[].class, object)) { return byte[].class; } else if (JavaInterop.isJavaObject(boolean[].class, object)) { return boolean[].class; } break; case UINT16: case SINT16: if (JavaInterop.isJavaObject(short[].class, object)) { return short[].class; } else if (JavaInterop.isJavaObject(char[].class, object)) { return char[].class; } break; case UINT32: case SINT32: if (JavaInterop.isJavaObject(int[].class, object)) { return int[].class; } break; case UINT64: case SINT64: if (JavaInterop.isJavaObject(long[].class, object)) { return long[].class; } break; case FLOAT: if (JavaInterop.isJavaObject(float[].class, object)) { return float[].class; } break; case DOUBLE: if (JavaInterop.isJavaObject(double[].class, object)) { return double[].class; } break; } return null; } @Override public Object slowpathPrepareArgument(TruffleObject value) { Class<?> arrayType = getArrayType(value); if (arrayType == null) { return null; } else { return JavaInterop.asJavaObject(arrayType, value); } } } static class ClosureType extends BasePointerType { private final LibFFISignature signature; private final boolean asRetType; ClosureType(LibFFISignature signature, boolean asRetType) { super(signature.getAllowedCallDirection().reverse()); this.signature = signature; this.asRetType = asRetType; } @Override protected void doSerialize(NativeArgumentBuffer buffer, Object value) { if (value instanceof NativePointer) { NativePointer pointer = (NativePointer) value; buffer.putPointer(pointer.nativePointer, size); } else { LibFFIClosure closure; if (value instanceof LibFFIClosure) { closure = (LibFFIClosure) value; } else if (value instanceof TruffleObject) { closure = LibFFIClosure.create(signature, (TruffleObject) value); } else { throw UnsupportedTypeException.raise(new Object[]{value}); } if (asRetType) { /* * The closure is passed as return type from a callback down to native code. In * that case we transfer ownership of the native pointer to the caller, so we * have to increase the reference count. */ closure.nativePointer.addRef(); } else { /* * The closure is passed as argument down to native code. Keep it alive for the * duration of the call by storing it in the object arguments array. The native * code will ignore this entry in the array. */ buffer.putObject(TypeTag.CLOSURE, closure, 0); } buffer.putPointer(closure.nativePointer.getCodePointer(), size); } } @Override protected Object doDeserialize(NativeArgumentBuffer buffer) { long functionPointer = buffer.getPointer(size); NativePointer symbol = new NativePointer(functionPointer); if (functionPointer == 0) { // cannot bind null pointer return symbol; } else { return new LibFFIFunction(symbol, signature); } } @Override public SerializeArgumentNode createSerializeArgumentNode() { return SerializeClosureArgumentNodeGen.create(this, signature); } @Override public ClosureArgumentNode createClosureArgumentNode() { return BufferClosureArgumentNodeGen.create(this); } @Override public Object slowpathPrepareArgument(TruffleObject value) { return value; } } enum Direction { JAVA_TO_NATIVE_ONLY, NATIVE_TO_JAVA_ONLY, BOTH; Direction reverse() { switch (this) { case JAVA_TO_NATIVE_ONLY: return NATIVE_TO_JAVA_ONLY; case NATIVE_TO_JAVA_ONLY: return JAVA_TO_NATIVE_ONLY; case BOTH: return BOTH; default: return null; } } } protected final int size; protected final int alignment; protected final int objectCount; protected final long type; protected final Direction allowedDataFlowDirection; protected LibFFIType(int size, int alignment, int objectCount, long type, Direction direction) { this.size = size; this.alignment = alignment; this.objectCount = objectCount; this.type = type; this.allowedDataFlowDirection = direction; } protected abstract void doSerialize(NativeArgumentBuffer buffer, Object value); protected abstract Object doDeserialize(NativeArgumentBuffer buffer); public final void serialize(NativeArgumentBuffer buffer, Object value) { buffer.align(alignment); doSerialize(buffer, value); } public final Object deserialize(NativeArgumentBuffer buffer) { buffer.align(alignment); return doDeserialize(buffer); } public abstract SerializeArgumentNode createSerializeArgumentNode(); public abstract ClosureArgumentNode createClosureArgumentNode(); public SerializeArgumentNode createClosureReturnNode() { return createSerializeArgumentNode(); } /** * Prepare the argument so it can be passed to the {@link #serialize} method. This should only * be called from the slow-path, on the fast-path the node created by * {@link #createSerializeArgumentNode()} will do this already in a more efficient way. If this * method returns {@code null}, you should send an {@link Message#UNBOX} message to the object * and try again. */ @TruffleBoundary public abstract Object slowpathPrepareArgument(TruffleObject value); }