From 6ade575ad72d319f326eae239e9ad41de2168754 Mon Sep 17 00:00:00 2001 From: Andrei Pangin Date: Sat, 22 Oct 2022 14:58:45 +0300 Subject: [PATCH] Access object fields --- README.md | 28 +++++++++-- .../example/{LibSSL.java => LibCrypto.java} | 4 +- example/one/nalim/example/Time.java | 13 +++-- src/one/nalim/AArch64CallingConvention.java | 37 +++++++------- .../nalim/AMD64LinuxCallingConvention.java | 35 ++++++-------- .../nalim/AMD64WindowsCallingConvention.java | 36 +++++++------- src/one/nalim/CallingConvention.java | 48 ++++++++++++++++++- src/one/nalim/FieldOffset.java | 38 +++++++++++++++ src/one/nalim/Linker.java | 2 +- 9 files changed, 168 insertions(+), 73 deletions(-) rename example/one/nalim/example/{LibSSL.java => LibCrypto.java} (88%) create mode 100644 src/one/nalim/FieldOffset.java diff --git a/README.md b/README.md index 7922bb1..1142055 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ public class Mem { #### 3. Working with arrays ```java -@Library("ssl") -public class LibSSL { +@Library("crypto") +public class LibCrypto { public static byte[] sha256(byte[] data) { byte[] digest = new byte[32]; @@ -77,7 +77,29 @@ public class LibSSL { } ``` -#### 4. Inlining raw machine code +#### 4. Accessing object fields + +```java +public class Time { + public long sec; + public long nsec; + + public static Time current() { + Time time = new Time(); + clock_gettime(0, time); + return time; + } + + @Link + private static native void clock_gettime(int clk_id, @FieldOffset("sec") Time time); + + static { + Linker.linkClass(Time.class); + } +} +``` + +#### 5. Inlining raw machine code ```java public class Cpu { diff --git a/example/one/nalim/example/LibSSL.java b/example/one/nalim/example/LibCrypto.java similarity index 88% rename from example/one/nalim/example/LibSSL.java rename to example/one/nalim/example/LibCrypto.java index 4d44c1a..2817ee7 100644 --- a/example/one/nalim/example/LibSSL.java +++ b/example/one/nalim/example/LibCrypto.java @@ -3,8 +3,8 @@ import one.nalim.Library; import one.nalim.Link; -@Library("ssl") -public class LibSSL { +@Library("crypto") +public class LibCrypto { public static byte[] sha256(byte[] data) { byte[] digest = new byte[32]; diff --git a/example/one/nalim/example/Time.java b/example/one/nalim/example/Time.java index fcbd502..8311904 100644 --- a/example/one/nalim/example/Time.java +++ b/example/one/nalim/example/Time.java @@ -1,18 +1,21 @@ package one.nalim.example; +import one.nalim.FieldOffset; import one.nalim.Link; import one.nalim.Linker; public class Time { + public long sec; + public long nsec; - public static long[] current() { - long[] result = new long[2]; - clock_gettime(0, result); - return result; + public static Time current() { + Time time = new Time(); + clock_gettime(0, time); + return time; } @Link - private static native void clock_gettime(int clk_id, long[] tp); + private static native void clock_gettime(int clk_id, @FieldOffset("sec") Time time); static { Linker.linkClass(Time.class); diff --git a/src/one/nalim/AArch64CallingConvention.java b/src/one/nalim/AArch64CallingConvention.java index d6ba079..e3d3526 100644 --- a/src/one/nalim/AArch64CallingConvention.java +++ b/src/one/nalim/AArch64CallingConvention.java @@ -18,6 +18,7 @@ package one.nalim; +import java.lang.annotation.Annotation; import java.nio.ByteBuffer; class AArch64CallingConvention extends CallingConvention { @@ -27,15 +28,28 @@ class AArch64CallingConvention extends CallingConvention { // Native: x0, x1, x2, x3, x4, x5, x6, x7, stack @Override - public void javaToNative(ByteBuffer buf, Class... types) { + public void javaToNative(ByteBuffer buf, Class[] types, Annotation[][] annotations) { if (types.length >= 8) { // 8th Java argument clashes with the 1st native arg buf.putInt(0xaa0003e8); // mov x8, x0 } int index = 0; - for (Class type : types) { - index += moveArg(buf, index, type); + for (int i = 0; i < types.length; i++) { + Class type = types[i]; + if (type.isPrimitive()) { + if (index < 8 && type != float.class && type != double.class) { + // mov x0, x1 + buf.putInt((type == long.class ? 0xaa0003e0 : 0x2a0003e0) | index | (index + 1) << 16); + index++; + } + } else if (index < 8) { + // add x0, x1, #offset + buf.putInt(0x91000000 | index | (index + 1) << 5 | baseOffset(type, annotations[i]) << 10); + index++; + } else { + throw new IllegalArgumentException("Too many object arguments"); + } } } @@ -53,21 +67,4 @@ public void emitCall(ByteBuffer buf, long address) { buf.putInt(0xd61f0120); // br x9 } - - private static int moveArg(ByteBuffer buf, int index, Class type) { - if (type == float.class || type == double.class) { - return 0; - } else if (index >= 8 && (type.isPrimitive() || type.isArray())) { - return 0; - } else if (type.isPrimitive()) { - // mov x0, x1 - buf.putInt((type == long.class ? 0xaa0003e0 : 0x2a0003e0) | index | (index + 1) << 16); - return 1; - } else if (type.isArray()) { - // add x0, x1, #offset - buf.putInt(0x91000000 | index | (index + 1) << 5 | arrayBaseOffset(type) << 10); - return 1; - } - throw new IllegalArgumentException("Unsupported argument type: " + type); - } } diff --git a/src/one/nalim/AMD64LinuxCallingConvention.java b/src/one/nalim/AMD64LinuxCallingConvention.java index 24d1c99..dbcc699 100644 --- a/src/one/nalim/AMD64LinuxCallingConvention.java +++ b/src/one/nalim/AMD64LinuxCallingConvention.java @@ -18,6 +18,7 @@ package one.nalim; +import java.lang.annotation.Annotation; import java.nio.ByteBuffer; class AMD64LinuxCallingConvention extends CallingConvention { @@ -47,7 +48,7 @@ class AMD64LinuxCallingConvention extends CallingConvention { 0x4989c1, // mov r9, rax }; - private static final int[] MOVE_ARRAY_ARG = { + private static final int[] MOVE_OBJ_ARG = { 0x488d7e, // lea rdi, [rsi+N] 0x488d72, // lea rsi, [rdx+N] 0x488d51, // lea rdx, [rcx+N] @@ -57,15 +58,25 @@ class AMD64LinuxCallingConvention extends CallingConvention { }; @Override - public void javaToNative(ByteBuffer buf, Class... types) { + public void javaToNative(ByteBuffer buf, Class[] types, Annotation[][] annotations) { if (types.length >= 6) { // 6th Java argument clashes with the 1st native arg emit(buf, SAVE_LAST_ARG); } int index = 0; - for (Class type : types) { - index += moveArg(buf, index, type); + for (int i = 0; i < types.length; i++) { + Class type = types[i]; + if (type.isPrimitive()) { + if (index < 8 && type != float.class && type != double.class) { + emit(buf, (type == long.class ? MOVE_LONG_ARG : MOVE_INT_ARG)[index++]); + } + } else if (index < 8) { + emit(buf, MOVE_OBJ_ARG[index++]); + buf.put(asByte(baseOffset(type, annotations[i]))); + } else { + throw new IllegalArgumentException("Too many object arguments"); + } } } @@ -74,20 +85,4 @@ public void emitCall(ByteBuffer buf, long address) { buf.putShort((short) 0xb848).putLong(address); // mov rax, address buf.putShort((short) 0xe0ff); // jmp rax } - - private static int moveArg(ByteBuffer buf, int index, Class type) { - if (type == float.class || type == double.class) { - return 0; - } else if (index >= 6 && (type.isPrimitive() || type.isArray())) { - return 0; - } else if (type.isPrimitive()) { - emit(buf, (type == long.class ? MOVE_LONG_ARG : MOVE_INT_ARG)[index]); - return 1; - } else if (type.isArray()) { - emit(buf, MOVE_ARRAY_ARG[index]); - buf.put((byte) arrayBaseOffset(type)); - return 1; - } - throw new IllegalArgumentException("Unsupported argument type: " + type); - } } diff --git a/src/one/nalim/AMD64WindowsCallingConvention.java b/src/one/nalim/AMD64WindowsCallingConvention.java index 61b491d..fb81971 100644 --- a/src/one/nalim/AMD64WindowsCallingConvention.java +++ b/src/one/nalim/AMD64WindowsCallingConvention.java @@ -18,6 +18,7 @@ package one.nalim; +import java.lang.annotation.Annotation; import java.nio.ByteBuffer; class AMD64WindowsCallingConvention extends CallingConvention { @@ -40,7 +41,7 @@ class AMD64WindowsCallingConvention extends CallingConvention { 0x4989f9, // mov r9, rdi }; - private static final int[] MOVE_ARRAY_ARG = { + private static final int[] MOVE_OBJ_ARG = { 0x488d4a, // lea rcx, [rdx+N] 0x498d50, // lea rdx, [r8+N] 0x4d8d41, // lea r8, [r9+N] @@ -48,10 +49,21 @@ class AMD64WindowsCallingConvention extends CallingConvention { }; @Override - public void javaToNative(ByteBuffer buf, Class... types) { + public void javaToNative(ByteBuffer buf, Class[] types, Annotation[][] annotations) { int index = 0; - for (Class type : types) { - index += moveArg(buf, index, type); + for (int i = 0; i < types.length; i++) { + Class type = types[i]; + if (type == float.class || type == double.class) { + continue; + } + if (index >= 4) { + throw new IllegalArgumentException("At most 4 integer arguments are supported"); + } else if (type.isPrimitive()) { + emit(buf, (type == long.class ? MOVE_LONG_ARG : MOVE_INT_ARG)[index++]); + } else { + emit(buf, MOVE_OBJ_ARG[index++]); + buf.put(asByte(baseOffset(type, annotations[i]))); + } } } @@ -60,20 +72,4 @@ public void emitCall(ByteBuffer buf, long address) { buf.putShort((short) 0xb848).putLong(address); // mov rax, address buf.putShort((short) 0xe0ff); // jmp rax } - - private static int moveArg(ByteBuffer buf, int index, Class type) { - if (type == float.class || type == double.class) { - return 0; - } else if (index >= 4 && (type.isPrimitive() || type.isArray())) { - throw new IllegalArgumentException("At most 4 integer arguments are supported"); - } else if (type.isPrimitive()) { - emit(buf, (type == long.class ? MOVE_LONG_ARG : MOVE_INT_ARG)[index]); - return 1; - } else if (type.isArray()) { - emit(buf, MOVE_ARRAY_ARG[index]); - buf.put((byte) arrayBaseOffset(type)); - return 1; - } - throw new IllegalArgumentException("Unsupported argument type: " + type); - } } diff --git a/src/one/nalim/CallingConvention.java b/src/one/nalim/CallingConvention.java index fc920bb..c6ff70e 100644 --- a/src/one/nalim/CallingConvention.java +++ b/src/one/nalim/CallingConvention.java @@ -19,8 +19,11 @@ package one.nalim; import jdk.vm.ci.meta.JavaKind; +import jdk.vm.ci.meta.MetaAccessProvider; +import jdk.vm.ci.meta.ResolvedJavaField; import jdk.vm.ci.runtime.JVMCI; +import java.lang.annotation.Annotation; import java.nio.ByteBuffer; abstract class CallingConvention { @@ -43,13 +46,54 @@ static CallingConvention getInstance() { } } - abstract void javaToNative(ByteBuffer buf, Class... types); + abstract void javaToNative(ByteBuffer buf, Class[] types, Annotation[][] annotations); abstract void emitCall(ByteBuffer buf, long address); + protected static int baseOffset(Class type, Annotation[] annotations) { + if (type.isArray() && type.getComponentType().isPrimitive()) { + return arrayBaseOffset(type); + } + + for (Annotation annotation : annotations) { + if (annotation instanceof FieldOffset) { + return fieldOffset(type, ((FieldOffset) annotation).value()); + } + } + + throw new IllegalArgumentException("Unsupported argument type: " + type); + } + protected static int arrayBaseOffset(Class arrayType) { + MetaAccessProvider meta = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess(); JavaKind elementKind = JavaKind.fromJavaClass(arrayType.getComponentType()); - return JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess().getArrayBaseOffset(elementKind); + return meta.getArrayBaseOffset(elementKind); + } + + protected static int fieldOffset(Class type, String fieldName) { + MetaAccessProvider meta = JVMCI.getRuntime().getHostJVMCIBackend().getMetaAccess(); + ResolvedJavaField[] fields = meta.lookupJavaType(type).getInstanceFields(true); + if (fields == null || fields.length == 0) { + throw new IllegalArgumentException(type.getName() + " does not have instance fields"); + } + + if (fieldName.isEmpty()) { + return fields[0].getOffset(); + } + + for (ResolvedJavaField field : fields) { + if (field.getName().equals(fieldName)) { + return field.getOffset(); + } + } + throw new IllegalArgumentException("No such field: " + type.getName() + "." + fieldName); + } + + protected static byte asByte(int value) { + if (value < 0 || value > 255) { + throw new IllegalArgumentException("Not in the byte range: " + value); + } + return (byte) value; } protected static void emit(ByteBuffer buf, int code) { diff --git a/src/one/nalim/FieldOffset.java b/src/one/nalim/FieldOffset.java new file mode 100644 index 0000000..52d321c --- /dev/null +++ b/src/one/nalim/FieldOffset.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2022 Andrei Pangin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package one.nalim; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an object parameter, whose field's address + * is passed as an argument to a native function. + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.PARAMETER) +public @interface FieldOffset { + /** + * The name of the field whose offset is added to an object address. + * If omitted, the first field with the lowest offset is assumed. + */ + String value() default ""; +} diff --git a/src/one/nalim/Linker.java b/src/one/nalim/Linker.java index 00885a7..d306a43 100644 --- a/src/one/nalim/Linker.java +++ b/src/one/nalim/Linker.java @@ -114,7 +114,7 @@ public static void linkMethod(Method m, String symbol, boolean naked) { ByteBuffer buf = ByteBuffer.allocate(100).order(ByteOrder.nativeOrder()); if (!naked) { - callingConvention.javaToNative(buf, m.getParameterTypes()); + callingConvention.javaToNative(buf, m.getParameterTypes(), m.getParameterAnnotations()); } callingConvention.emitCall(buf, address);