Skip to content

Commit

Permalink
Access object fields
Browse files Browse the repository at this point in the history
  • Loading branch information
apangin committed Oct 22, 2022
1 parent ee36e3b commit 6ade575
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 73 deletions.
28 changes: 25 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
13 changes: 8 additions & 5 deletions example/one/nalim/example/Time.java
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
37 changes: 17 additions & 20 deletions src/one/nalim/AArch64CallingConvention.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package one.nalim;

import java.lang.annotation.Annotation;
import java.nio.ByteBuffer;

class AArch64CallingConvention extends CallingConvention {
Expand All @@ -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");
}
}
}

Expand All @@ -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);
}
}
35 changes: 15 additions & 20 deletions src/one/nalim/AMD64LinuxCallingConvention.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package one.nalim;

import java.lang.annotation.Annotation;
import java.nio.ByteBuffer;

class AMD64LinuxCallingConvention extends CallingConvention {
Expand Down Expand Up @@ -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]
Expand All @@ -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");
}
}
}

Expand All @@ -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);
}
}
36 changes: 16 additions & 20 deletions src/one/nalim/AMD64WindowsCallingConvention.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package one.nalim;

import java.lang.annotation.Annotation;
import java.nio.ByteBuffer;

class AMD64WindowsCallingConvention extends CallingConvention {
Expand All @@ -40,18 +41,29 @@ 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]
0x4c8d4f, // lea r9, [rdi+N]
};

@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])));
}
}
}

Expand All @@ -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);
}
}
48 changes: 46 additions & 2 deletions src/one/nalim/CallingConvention.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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) {
Expand Down
38 changes: 38 additions & 0 deletions src/one/nalim/FieldOffset.java
Original file line number Diff line number Diff line change
@@ -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 "";
}
2 changes: 1 addition & 1 deletion src/one/nalim/Linker.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down

0 comments on commit 6ade575

Please sign in to comment.