From be2a8b008f9292d8f01ca1614321d97be008b84e Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Fri, 23 Aug 2024 16:46:10 +0800 Subject: [PATCH 01/13] Remove CEnum.java --- README.md | 13 +- build.gradle.kts | 8 +- gradle.properties | 5 +- src/main/java/module-info.java | 1 + src/main/java/overrun/marshal/CEnum.java | 69 --- .../java/overrun/marshal/Configurations.java | 3 + src/main/java/overrun/marshal/Downcall.java | 469 ++++++++---------- src/main/java/overrun/marshal/Marshal.java | 28 -- .../java/overrun/marshal/MemoryStack.java | 1 + .../gen/processor/ArgumentProcessor.java | 2 +- .../marshal/gen/processor/ProcessorType.java | 23 - .../marshal/gen/processor/ProcessorTypes.java | 2 - .../overrun/marshal/internal/Constants.java | 5 +- .../java/overrun/marshal/test/MyEnum.java | 51 -- .../test/downcall/DowncallSoutTest.java | 13 +- .../marshal/test/downcall/DowncallTest.java | 6 - .../marshal/test/downcall/IDowncall.java | 7 +- .../marshal/test/upcall/ComplexUpcall.java | 6 +- 18 files changed, 244 insertions(+), 468 deletions(-) delete mode 100644 src/main/java/overrun/marshal/CEnum.java delete mode 100644 src/test/java/overrun/marshal/test/MyEnum.java diff --git a/README.md b/README.md index c54a540..a55d69c 100644 --- a/README.md +++ b/README.md @@ -34,24 +34,17 @@ interface GLFW { int GLFW_KEY_A = 65; /** - * Sets the swap interval. - * - * @param interval the interval + * A normal function */ void glfwSwapInterval(int interval); /** - * Gets the window position. + * A function with ref parameters. * Marks the array parameter as a place to store the value returned by C - * - * @param window the window - * @param posX the position x - * @param posY the position y */ void glfwGetWindowPos(MemorySegment window, @Ref int[] posX, @Ref int[] posY); /** - * Creates a window. * Requires a segment allocator to allocate the string; * if the first parameter is not segment allocator, then the memory stack is used * @@ -95,7 +88,7 @@ Import as a Gradle dependency: ```groovy dependencies { - implementation("io.github.over-run:marshal:0.1.0-alpha.29-jdk23") + implementation("io.github.over-run:marshal:0.1.0-alpha.30-jdk23") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index 9f2515d..74d2089 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -34,6 +34,9 @@ val projDevelopers = arrayOf( Developer("squid233") ) +val junitVersion: String by rootProject +val memstackVersion: String by rootProject + data class Organization( val name: String, val url: String @@ -82,13 +85,14 @@ allprojects { //maven { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots") } //maven { url = uri("https://oss.oss.sonatype.org/content/repositories/releases") } - //maven { url = uri("https://s01.oss.sonatype.org/content/repositories/releases") } + maven { url = uri("https://s01.oss.sonatype.org/content/repositories/releases") } } dependencies { // add your dependencies compileOnly("org.jetbrains:annotations:24.1.0") - testImplementation("org.junit.jupiter:junit-jupiter:5.10.3") + testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion") + api("io.github.over-run:memstack:$memstackVersion") } tasks.withType { diff --git a/gradle.properties b/gradle.properties index d8f0af0..bf0dafd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ projGroupId=io.github.over-run projArtifactId=marshal # The project name should only contain lowercase letters, numbers and hyphen. projName=marshal -projVersion=0.1.0-alpha.29-jdk23 +projVersion=0.1.0-alpha.30-jdk23 projDesc=Marshaler of native libraries # Uncomment them if you want to publish to maven repository. projUrl=https://github.com/Over-Run/marshal @@ -33,3 +33,6 @@ jdkEnablePreview=true # https://download.java.net/java/early_access/$jdkEarlyAccessDoc/docs/api/ # Uncomment it if you need to use EA build of JDK. jdkEarlyAccessDoc=jdk23 + +junitVersion=5.11.0 +memstackVersion=0.2.0 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 1966ac2..deedd12 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -29,4 +29,5 @@ opens overrun.marshal.internal.data; requires static org.jetbrains.annotations; + requires io.github.overrun.memstack; } diff --git a/src/main/java/overrun/marshal/CEnum.java b/src/main/java/overrun/marshal/CEnum.java deleted file mode 100644 index eb12d99..0000000 --- a/src/main/java/overrun/marshal/CEnum.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * A C enum value provider. - *

Example

- *
{@code
- * enum MyEnum implements CEnum {
- *     A(0), B(1);
- *     final int value;
- *     MyEnum(int value) { this.value = value; }
- *
- *     @Wrapper
- *     public static MyEnum of(int value) {
- *         return switch (value) {
- *             case 0 -> A;
- *             case 1 -> B;
- *             default -> throw new IllegalArgumentException("Unexpected value: " + value);
- *         }
- *     }
- *
- *     @Override
- *     public int value() { return value; }
- * }
- * }
- * - * @author squid233 - * @since 0.1.0 - */ -public interface CEnum { - /** - * {@return the value of the enum} - */ - int value(); - - /** - * Marks a static method as an enum value wrapper. - *

- * The marked method must only contain one {@code int} parameter. - * - * @author squid233 - * @see CEnum - * @since 0.1.0 - */ - @Target(ElementType.METHOD) - @Retention(RetentionPolicy.RUNTIME) - @interface Wrapper { - } -} diff --git a/src/main/java/overrun/marshal/Configurations.java b/src/main/java/overrun/marshal/Configurations.java index 711f99f..e76d59a 100644 --- a/src/main/java/overrun/marshal/Configurations.java +++ b/src/main/java/overrun/marshal/Configurations.java @@ -53,18 +53,21 @@ public final class Configurations { *

* The default value is {@code false}. */ + @Deprecated(since = "0.1.0-alpha.30", forRemoval = true) public static final Entry DEBUG_STACK = new Entry<>(() -> false); /** * The default stack size in KiB of {@link MemoryStack}. *

* The default value is {@code 64}. */ + @Deprecated(since = "0.1.0-alpha.30", forRemoval = true) public static final Entry STACK_SIZE = new Entry<>(() -> 64L); /** * The default stack frames of {@link MemoryStack}. *

* The default value is {@code 8}. */ + @Deprecated(since = "0.1.0-alpha.30", forRemoval = true) public static final Entry STACK_FRAMES = new Entry<>(() -> 8); private static Consumer apiLogger = System.err::println; diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index ef20d40..6e7830a 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -26,7 +26,6 @@ import overrun.marshal.struct.Struct; import overrun.marshal.struct.StructAllocator; -import java.lang.annotation.Annotation; import java.lang.classfile.ClassFile; import java.lang.classfile.CodeBuilder; import java.lang.classfile.Opcode; @@ -201,7 +200,7 @@ private static ValueLayout getValueLayout(Class carrier) { if (carrier == char.class) return ValueLayout.JAVA_CHAR; if (carrier == byte.class) return ValueLayout.JAVA_BYTE; if (carrier == short.class) return ValueLayout.JAVA_SHORT; - if (carrier == int.class || CEnum.class.isAssignableFrom(carrier)) return ValueLayout.JAVA_INT; + if (carrier == int.class) return ValueLayout.JAVA_INT; if (carrier == long.class) return ValueLayout.JAVA_LONG; if (carrier == float.class) return ValueLayout.JAVA_FLOAT; if (carrier == double.class) return ValueLayout.JAVA_DOUBLE; @@ -221,7 +220,6 @@ private static ClassDesc convertToDowncallCD(AnnotatedElement element, Class final Convert convert = element.getDeclaredAnnotation(Convert.class); if (convert != null && aClass == boolean.class) return convert.value().classDesc(); if (aClass.isPrimitive()) return aClass.describeConstable().orElseThrow(); - if (CEnum.class.isAssignableFrom(aClass)) return CD_int; if (SegmentAllocator.class.isAssignableFrom(aClass)) return CD_SegmentAllocator; if (aClass == Object.class) return CD_Object; return CD_MemorySegment; @@ -231,34 +229,10 @@ private static ClassDesc convertToMarshalCD(Class aClass) { if (aClass.isPrimitive() || aClass == String.class) return aClass.describeConstable().orElseThrow(); if (Addressable.class.isAssignableFrom(aClass)) return CD_Addressable; - if (CEnum.class.isAssignableFrom(aClass)) return CD_CEnum; if (Upcall.class.isAssignableFrom(aClass)) return CD_Upcall; return CD_MemorySegment; } - // wrapper - - private static Method findWrapper( - Class aClass, - Class annotation, - Predicate predicate) { - return Arrays.stream(aClass.getDeclaredMethods()) - .filter(method -> method.getDeclaredAnnotation(annotation) != null) - .filter(predicate) - .findFirst() - .orElseThrow(() -> new IllegalStateException( - "Couldn't find wrapper method in " + aClass + "; mark it with @" + annotation.getSimpleName())); - } - - private static Method findCEnumWrapper(Class aClass) { - return findWrapper(aClass, CEnum.Wrapper.class, method -> { - final var types = method.getParameterTypes(); - return types.length == 1 && - types[0] == int.class && - CEnum.class.isAssignableFrom(method.getReturnType()); - }); - } - @SuppressWarnings("unchecked") private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup lookup, DowncallOption... options) { Class _targetClass = null, targetClass; @@ -369,6 +343,7 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look methodBuilder -> methodBuilder.withCode(codeBuilder -> { final TypeKind returnTypeKind = TypeKind.from(cd_returnType).asLoadable(); + // returns MethodHandle if (returnType == MethodHandle.class) { if (method.isDefault() && downcallData.handleMap().get(methodData.entrypoint()) == null) { @@ -395,257 +370,252 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look mtd_method, targetClass.isInterface() ).return_(returnTypeKind); + return; + } + + //region body + final boolean hasAllocator = + !parameters.isEmpty() && + SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()); + final boolean shouldAddStack = + !parameters.isEmpty() && + !SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) && + parameters.stream().anyMatch(parameter -> requireAllocator(parameter.getType())); + final int stackSlot; + final int stackPointerSlot; + final int allocatorSlot; + + if (shouldAddStack) { + stackSlot = codeBuilder.allocateLocal(TypeKind.ReferenceType); + stackPointerSlot = codeBuilder.allocateLocal(TypeKind.LongType); + allocatorSlot = stackSlot; } else { - //region body - final boolean hasAllocator = - !parameters.isEmpty() && - SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()); - final boolean shouldAddStack = - !parameters.isEmpty() && - !SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) && - parameters.stream().anyMatch(parameter -> requireAllocator(parameter.getType())); - final int stackSlot; - final int stackPointerSlot; - final int allocatorSlot; - - if (shouldAddStack) { - stackSlot = codeBuilder.allocateLocal(TypeKind.ReferenceType); - stackPointerSlot = codeBuilder.allocateLocal(TypeKind.LongType); - allocatorSlot = stackSlot; - } else { - stackSlot = -1; - stackPointerSlot = -1; - allocatorSlot = hasAllocator ? codeBuilder.parameterSlot(0) : -1; - } + stackSlot = -1; + stackPointerSlot = -1; + allocatorSlot = hasAllocator ? codeBuilder.parameterSlot(0) : -1; + } - final Map parameterRefSlot = HashMap.newHashMap(Math.toIntExact(parameters.stream() - .filter(parameter -> parameter.getDeclaredAnnotation(Ref.class) != null) - .count())); - - // check size - for (int i = 0, size = parameters.size(); i < size; i++) { - final Parameter parameter = parameters.get(i); - final Sized sized = parameter.getDeclaredAnnotation(Sized.class); - final Class type = parameter.getType(); - if (sized == null || !type.isArray()) { - continue; - } - codeBuilder.ldc(sized.value()) - .aload(codeBuilder.parameterSlot(i)) - .arraylength() - .invokestatic(CD_Checks, - "checkArraySize", - MTD_void_int_int); + final Map parameterRefSlot = HashMap.newHashMap(Math.toIntExact(parameters.stream() + .filter(parameter -> parameter.getDeclaredAnnotation(Ref.class) != null) + .count())); + + // check size + for (int i = 0, size = parameters.size(); i < size; i++) { + final Parameter parameter = parameters.get(i); + final Sized sized = parameter.getDeclaredAnnotation(Sized.class); + final Class type = parameter.getType(); + if (sized == null || !type.isArray()) { + continue; } + codeBuilder.ldc(sized.value()) + .aload(codeBuilder.parameterSlot(i)) + .arraylength() + .invokestatic(CD_Checks, + "checkArraySize", + MTD_void_int_int); + } - // initialize stack - if (shouldAddStack) { - codeBuilder.invokestatic(CD_MemoryStack, "stackGet", MTD_MemoryStack) - .astore(stackSlot) - .aload(stackSlot) - .invokevirtual(CD_MemoryStack, "pointer", MTD_long) - .lstore(stackPointerSlot); - } + // initialize stack + if (shouldAddStack) { + codeBuilder.invokestatic(CD_MemoryStack, "stackGet", MTD_MemoryStack) + .astore(stackSlot) + .aload(stackSlot) + .invokevirtual(CD_MemoryStack, "pointer", MTD_long) + .lstore(stackPointerSlot); + } - codeBuilder.trying( - blockCodeBuilder -> { - final boolean skipFirstParam = methodData.skipFirstParam(); - final int parameterSize = parameters.size(); + codeBuilder.trying( + blockCodeBuilder -> { + final boolean skipFirstParam = methodData.skipFirstParam(); + final int parameterSize = parameters.size(); - final ClassDesc cd_returnTypeDowncall = convertToDowncallCD(method, returnType); - final boolean returnVoid = returnType == void.class; + final ClassDesc cd_returnTypeDowncall = convertToDowncallCD(method, returnType); + final boolean returnVoid = returnType == void.class; - // ref - for (int i = 0, size = parameters.size(); i < size; i++) { - final Parameter parameter = parameters.get(i); - if (parameter.getDeclaredAnnotation(Ref.class) == null) { - continue; - } - final Class type = parameter.getType(); - if (type.isArray()) { - final Class componentType = type.getComponentType(); - final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); - blockCodeBuilder.aload(allocatorSlot) - .aload(blockCodeBuilder.parameterSlot(i)); - - if (componentType == String.class && getCharset(blockCodeBuilder, parameter)) { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_StringArray_Charset - ).astore(slot); - } else { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MethodTypeDesc.of(CD_MemorySegment, - CD_SegmentAllocator, - convertToMarshalCD(componentType).arrayType()) - ).astore(slot); - } - parameterRefSlot.put(parameter, slot); - } + // ref + for (int i = 0, size = parameters.size(); i < size; i++) { + final Parameter parameter = parameters.get(i); + if (parameter.getDeclaredAnnotation(Ref.class) == null) { + continue; } - - // invocation - final List parameterCDList = new ArrayList<>(skipFirstParam ? parameterSize - 1 : parameterSize); - blockCodeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle); - for (int i = skipFirstParam ? 1 : 0; i < parameterSize; i++) { - final Parameter parameter = parameters.get(i); - final Class type = parameter.getType(); - final ClassDesc cd_parameterDowncall = convertToDowncallCD(parameter, type); - - if (parameterRefSlot.containsKey(parameter)) { - blockCodeBuilder.aload(parameterRefSlot.get(parameter)); + final Class type = parameter.getType(); + if (type.isArray()) { + final Class componentType = type.getComponentType(); + final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); + blockCodeBuilder.aload(allocatorSlot) + .aload(blockCodeBuilder.parameterSlot(i)); + + if (componentType == String.class && getCharset(blockCodeBuilder, parameter)) { + blockCodeBuilder.invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_SegmentAllocator_StringArray_Charset + ).astore(slot); } else { - ArgumentProcessor.getInstance().process( - blockCodeBuilder, - ProcessorTypes.fromClass(type), - new ArgumentProcessorContext( - parameter, - blockCodeBuilder.parameterSlot(i), - allocatorSlot, - parameter.getDeclaredAnnotation(Convert.class) - ) - ); + blockCodeBuilder.invokestatic(CD_Marshal, + "marshal", + MethodTypeDesc.of(CD_MemorySegment, + CD_SegmentAllocator, + convertToMarshalCD(componentType).arrayType()) + ).astore(slot); } - - parameterCDList.add(cd_parameterDowncall); + parameterRefSlot.put(parameter, slot); } - blockCodeBuilder.invokevirtual(CD_MethodHandle, - "invokeExact", - MethodTypeDesc.of(cd_returnTypeDowncall, parameterCDList)); + } + + // invocation + final List parameterCDList = new ArrayList<>(skipFirstParam ? parameterSize - 1 : parameterSize); + blockCodeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle); + for (int i = skipFirstParam ? 1 : 0; i < parameterSize; i++) { + final Parameter parameter = parameters.get(i); + final Class type = parameter.getType(); + final ClassDesc cd_parameterDowncall = convertToDowncallCD(parameter, type); - final int resultSlot; - if (returnVoid) { - resultSlot = -1; + if (parameterRefSlot.containsKey(parameter)) { + blockCodeBuilder.aload(parameterRefSlot.get(parameter)); } else { - final TypeKind typeKind = TypeKind.from(cd_returnTypeDowncall); - resultSlot = blockCodeBuilder.allocateLocal(typeKind); - blockCodeBuilder.storeLocal(typeKind, resultSlot); + ArgumentProcessor.getInstance().process( + blockCodeBuilder, + ProcessorTypes.fromClass(type), + new ArgumentProcessorContext( + parameter, + blockCodeBuilder.parameterSlot(i), + allocatorSlot, + parameter.getDeclaredAnnotation(Convert.class) + ) + ); } - // copy ref result - for (int i = 0, size = parameters.size(); i < size; i++) { - final Parameter parameter = parameters.get(i); - final Class type = parameter.getType(); - final Class componentType = type.getComponentType(); - if (parameter.getDeclaredAnnotation(Ref.class) != null && - type.isArray()) { - final boolean isPrimitiveArray = componentType.isPrimitive(); - final boolean isStringArray = componentType == String.class; - if (isPrimitiveArray || isStringArray) { - final int refSlot = parameterRefSlot.get(parameter); - final int parameterSlot = blockCodeBuilder.parameterSlot(i); - blockCodeBuilder.aload(refSlot) - .aload(parameterSlot); - if (isPrimitiveArray) { + parameterCDList.add(cd_parameterDowncall); + } + blockCodeBuilder.invokevirtual(CD_MethodHandle, + "invokeExact", + MethodTypeDesc.of(cd_returnTypeDowncall, parameterCDList)); + + final int resultSlot; + if (returnVoid) { + resultSlot = -1; + } else { + final TypeKind typeKind = TypeKind.from(cd_returnTypeDowncall); + resultSlot = blockCodeBuilder.allocateLocal(typeKind); + blockCodeBuilder.storeLocal(typeKind, resultSlot); + } + + // copy ref result + for (int i = 0, size = parameters.size(); i < size; i++) { + final Parameter parameter = parameters.get(i); + final Class type = parameter.getType(); + final Class componentType = type.getComponentType(); + if (parameter.getDeclaredAnnotation(Ref.class) != null && + type.isArray()) { + final boolean isPrimitiveArray = componentType.isPrimitive(); + final boolean isStringArray = componentType == String.class; + if (isPrimitiveArray || isStringArray) { + final int refSlot = parameterRefSlot.get(parameter); + final int parameterSlot = blockCodeBuilder.parameterSlot(i); + blockCodeBuilder.aload(refSlot) + .aload(parameterSlot); + if (isPrimitiveArray) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + "copy", + MethodTypeDesc.of(CD_void, CD_MemorySegment, ClassDesc.ofDescriptor(type.descriptorString()))); + } else { + if (getCharset(blockCodeBuilder, parameter)) { blockCodeBuilder.invokestatic(CD_Unmarshal, "copy", - MethodTypeDesc.of(CD_void, CD_MemorySegment, ClassDesc.ofDescriptor(type.descriptorString()))); + MTD_void_MemorySegment_StringArray_Charset); } else { - if (getCharset(blockCodeBuilder, parameter)) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MTD_void_MemorySegment_StringArray_Charset); - } else { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MTD_void_MemorySegment_StringArray); - } + blockCodeBuilder.invokestatic(CD_Unmarshal, + "copy", + MTD_void_MemorySegment_StringArray); } } } } + } - // wrap return value - final int unmarshalSlot; - if (returnVoid) { - unmarshalSlot = -1; + // wrap return value + final int unmarshalSlot; + if (returnVoid) { + unmarshalSlot = -1; + } else { + if (shouldAddStack) { + unmarshalSlot = blockCodeBuilder.allocateLocal(returnTypeKind); } else { - if (shouldAddStack) { - unmarshalSlot = blockCodeBuilder.allocateLocal(returnTypeKind); - } else { - unmarshalSlot = -1; - } - blockCodeBuilder.loadLocal(TypeKind.from(cd_returnTypeDowncall), resultSlot); + unmarshalSlot = -1; } - final Convert convert = method.getDeclaredAnnotation(Convert.class); - if (convert != null && returnType == boolean.class) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsBoolean", - MethodTypeDesc.of(CD_boolean, convert.value().classDesc())); - } else if (returnType == String.class) { + blockCodeBuilder.loadLocal(TypeKind.from(cd_returnTypeDowncall), resultSlot); + } + final Convert convert = method.getDeclaredAnnotation(Convert.class); + if (convert != null && returnType == boolean.class) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + "unmarshalAsBoolean", + MethodTypeDesc.of(CD_boolean, convert.value().classDesc())); + } else if (returnType == String.class) { + final boolean hasCharset = getCharset(blockCodeBuilder, method); + blockCodeBuilder.invokestatic(CD_Unmarshal, + "unboundString", + hasCharset ? MTD_String_MemorySegment_Charset : MTD_String_MemorySegment); + } else if (Struct.class.isAssignableFrom(returnType)) { + blockCodeBuilder.ifThenElse(Opcode.IFNONNULL, + blockCodeBuilder1 -> { + final var structAllocatorField = getStructAllocatorField(returnType); + Objects.requireNonNull(structAllocatorField); + blockCodeBuilder1.getstatic(cd_returnType, structAllocatorField.getName(), CD_StructAllocator) + .aload(resultSlot) + .invokevirtual(CD_StructAllocator, "of", MTD_Object_MemorySegment) + .checkcast(cd_returnType); + }, + CodeBuilder::aconst_null); + } else if (returnType.isArray()) { + final Class componentType = returnType.getComponentType(); + if (componentType == String.class) { final boolean hasCharset = getCharset(blockCodeBuilder, method); blockCodeBuilder.invokestatic(CD_Unmarshal, - "unboundString", - hasCharset ? MTD_String_MemorySegment_Charset : MTD_String_MemorySegment); - } else if (Struct.class.isAssignableFrom(returnType)) { - blockCodeBuilder.ifThenElse(Opcode.IFNONNULL, - blockCodeBuilder1 -> { - final var structAllocatorField = getStructAllocatorField(returnType); - Objects.requireNonNull(structAllocatorField); - blockCodeBuilder1.getstatic(cd_returnType, structAllocatorField.getName(), CD_StructAllocator) - .aload(resultSlot) - .invokevirtual(CD_StructAllocator, "of", MTD_Object_MemorySegment) - .checkcast(cd_returnType); - }, - CodeBuilder::aconst_null); - } else if (CEnum.class.isAssignableFrom(returnType)) { - final Method wrapper = findCEnumWrapper(returnType); - blockCodeBuilder.invokestatic(cd_returnType, - wrapper.getName(), - MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), CD_int), - wrapper.getDeclaringClass().isInterface()); - } else if (returnType.isArray()) { - final Class componentType = returnType.getComponentType(); - if (componentType == String.class) { - final boolean hasCharset = getCharset(blockCodeBuilder, method); - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsStringArray", - hasCharset ? MTD_StringArray_MemorySegment_Charset : MTD_StringArray_MemorySegment); - } else if (componentType.isPrimitive() || componentType == MemorySegment.class) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - unmarshalMethod(returnType), - MethodTypeDesc.of(cd_returnType, CD_MemorySegment)); - } + "unmarshalAsStringArray", + hasCharset ? MTD_StringArray_MemorySegment_Charset : MTD_StringArray_MemorySegment); + } else if (componentType.isPrimitive() || componentType == MemorySegment.class) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + unmarshalMethod(returnType), + MethodTypeDesc.of(cd_returnType, CD_MemorySegment)); } + } - // reset stack - if (shouldAddStack) { - if (!returnVoid) { - blockCodeBuilder.storeLocal(returnTypeKind, unmarshalSlot); - } - blockCodeBuilder.aload(stackSlot) - .lload(stackPointerSlot) - .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); - if (!returnVoid) { - blockCodeBuilder.loadLocal(returnTypeKind, unmarshalSlot); - } + // reset stack + if (shouldAddStack) { + if (!returnVoid) { + blockCodeBuilder.storeLocal(returnTypeKind, unmarshalSlot); } - - // return - blockCodeBuilder.return_(returnTypeKind); - }, - catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> { - final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); - // create exception - blockCodeBuilder.astore(slot) - .new_(CD_IllegalStateException) - .dup() - .ldc(methodData.exceptionString()) - .aload(slot) - .invokespecial(CD_IllegalStateException, INIT_NAME, MTD_void_String_Throwable); - // reset stack - if (shouldAddStack) { - blockCodeBuilder.aload(stackSlot) - .lload(stackPointerSlot) - .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); + blockCodeBuilder.aload(stackSlot) + .lload(stackPointerSlot) + .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); + if (!returnVoid) { + blockCodeBuilder.loadLocal(returnTypeKind, unmarshalSlot); } - // throw - blockCodeBuilder.athrow(); - }) - ); - //endregion - } + } + + // return + blockCodeBuilder.return_(returnTypeKind); + }, + catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> { + final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); + // create exception + blockCodeBuilder.astore(slot) + .new_(CD_IllegalStateException) + .dup() + .ldc(methodData.exceptionString()) + .aload(slot) + .invokespecial(CD_IllegalStateException, INIT_NAME, MTD_void_String_Throwable); + // reset stack + if (shouldAddStack) { + blockCodeBuilder.aload(stackSlot) + .lload(stackPointerSlot) + .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); + } + // throw + blockCodeBuilder.athrow(); + }) + ); + //endregion })); } //endregion @@ -811,8 +781,7 @@ private static boolean isValidParamArrayType(Class aClass) { type == MemorySegment.class || type == String.class || Addressable.class.isAssignableFrom(type) || - Upcall.class.isAssignableFrom(type) || - CEnum.class.isAssignableFrom(type); + Upcall.class.isAssignableFrom(type); } private static boolean isValidReturnArrayType(Class aClass) { @@ -830,7 +799,6 @@ private static boolean isValidParamType(Class aClass) { SegmentAllocator.class.isAssignableFrom(aClass) || Addressable.class.isAssignableFrom(aClass) || Upcall.class.isAssignableFrom(aClass) || - CEnum.class.isAssignableFrom(aClass) || isValidParamArrayType(aClass); } @@ -839,7 +807,6 @@ private static boolean isValidReturnType(Class aClass) { aClass == MemorySegment.class || aClass == String.class || Struct.class.isAssignableFrom(aClass) || - CEnum.class.isAssignableFrom(aClass) || isValidReturnArrayType(aClass) || aClass == MethodHandle.class; } diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java index 5f018c4..c60cbd3 100644 --- a/src/main/java/overrun/marshal/Marshal.java +++ b/src/main/java/overrun/marshal/Marshal.java @@ -38,7 +38,6 @@ public final class Marshal { private static final VarHandle vh_addressArray = arrayVarHandle(ADDRESS); static final VarHandle vh_booleanArray = arrayVarHandle(JAVA_BOOLEAN); - private static final VarHandle vh_intArray = arrayVarHandle(JAVA_INT); private Marshal() { } @@ -142,16 +141,6 @@ public static MemorySegment marshal(SegmentAllocator allocator, @Nullable String return allocator.allocateFrom(string, charset); } - /** - * Converts the given CEnum to an integer. - * - * @param cEnum the CEnum - * @return the integer - */ - public static int marshal(@Nullable CEnum cEnum) { - return cEnum != null ? cEnum.value() : 0; - } - /** * Converts the given addressable to a segment. * @@ -328,23 +317,6 @@ public static MemorySegment marshal(SegmentAllocator allocator, String @Nullable return marshal(allocator, arr, (segmentAllocator, str) -> marshal(segmentAllocator, str, charset)); } - /** - * Converts the given array to a segment. - * - * @param allocator the allocator - * @param arr the array - * @return the segment - */ - public static MemorySegment marshal(SegmentAllocator allocator, CEnum @Nullable [] arr) { - if (arr == null) return MemorySegment.NULL; - final MemorySegment segment = allocator.allocate(JAVA_INT, arr.length); - for (int i = 0; i < arr.length; i++) { - final CEnum cEnum = arr[i]; - vh_intArray.set(segment, (long) i, cEnum != null ? cEnum.value() : 0); - } - return segment; - } - /** * Converts the given array to a segment. * diff --git a/src/main/java/overrun/marshal/MemoryStack.java b/src/main/java/overrun/marshal/MemoryStack.java index 710d68e..a14e0cc 100644 --- a/src/main/java/overrun/marshal/MemoryStack.java +++ b/src/main/java/overrun/marshal/MemoryStack.java @@ -34,6 +34,7 @@ * @see Configurations#DEBUG_STACK * @since 0.1.0 */ +@Deprecated(since = "0.1.0-alpha.30", forRemoval = true) public sealed class MemoryStack implements Arena { private static final boolean CHECKS = Configurations.CHECKS.get(); private static final boolean DEBUG = Configurations.DEBUG.get(); diff --git a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java b/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java index 2197a4f..bb30498 100644 --- a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java @@ -74,7 +74,7 @@ public boolean process(CodeBuilder builder, ProcessorType type, ArgumentProcesso MTD_MemorySegment_SegmentAllocator_String); } } - case ProcessorType.Addr _, ProcessorType.CEnum _ -> builder + case ProcessorType.Addr _ -> builder .aload(context.parameterSlot()) .invokestatic(CD_Marshal, "marshal", diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java index 8decc8b..57bc454 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -196,29 +196,6 @@ public ClassDesc marshalClassDesc() { } } - /** - * {@link overrun.marshal.CEnum} - */ - final class CEnum implements ProcessorType { - /** - * The instance - */ - public static final CEnum INSTANCE = new CEnum(); - - private CEnum() { - } - - @Override - public ClassDesc downcallClassDesc() { - return CD_int; - } - - @Override - public ClassDesc marshalClassDesc() { - return CD_CEnum; - } - } - /** * {@link overrun.marshal.Upcall} */ diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java index aab8f21..c52931e 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java @@ -17,7 +17,6 @@ package overrun.marshal.gen.processor; import overrun.marshal.Addressable; -import overrun.marshal.CEnum; import overrun.marshal.Upcall; import java.lang.foreign.MemorySegment; @@ -57,7 +56,6 @@ public static ProcessorType fromClass(Class aClass) { if (aClass == String.class) return ProcessorType.Str.INSTANCE; if (SegmentAllocator.class.isAssignableFrom(aClass)) return ProcessorType.Allocator.INSTANCE; if (Addressable.class.isAssignableFrom(aClass)) return ProcessorType.Addr.INSTANCE; - if (CEnum.class.isAssignableFrom(aClass)) return ProcessorType.CEnum.INSTANCE; if (Upcall.class.isAssignableFrom(aClass)) return ProcessorType.Upcall.INSTANCE; if (aClass.isArray()) return new ProcessorType.Array(fromClass(aClass.componentType())); return Objects.requireNonNull(map.get(aClass), "Cannot find processor type of " + aClass); diff --git a/src/main/java/overrun/marshal/internal/Constants.java b/src/main/java/overrun/marshal/internal/Constants.java index 0acb8a0..fee3a23 100644 --- a/src/main/java/overrun/marshal/internal/Constants.java +++ b/src/main/java/overrun/marshal/internal/Constants.java @@ -37,10 +37,6 @@ public final class Constants { * CD_Arena */ public static final ClassDesc CD_Arena = ClassDesc.of("java.lang.foreign.Arena"); - /** - * CD_CEnum - */ - public static final ClassDesc CD_CEnum = ClassDesc.of("overrun.marshal.CEnum"); /** * CD_Charset */ @@ -72,6 +68,7 @@ public final class Constants { /** * CD_MemoryStack */ + @Deprecated public static final ClassDesc CD_MemoryStack = ClassDesc.of("overrun.marshal.MemoryStack"); /** * CD_SegmentAllocator diff --git a/src/test/java/overrun/marshal/test/MyEnum.java b/src/test/java/overrun/marshal/test/MyEnum.java deleted file mode 100644 index b5b9f50..0000000 --- a/src/test/java/overrun/marshal/test/MyEnum.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import overrun.marshal.CEnum; - -/** - * Enum - * - * @author squid233 - * @since 0.1.0 - */ -public enum MyEnum implements CEnum { - A(0), - B(2), - C(4); - private final int value; - - MyEnum(int value) { - this.value = value; - } - - @Override - public int value() { - return value; - } - - @Wrapper - public static MyEnum wrap(int value) { - return switch (value) { - case 0 -> A; - case 2 -> B; - case 4 -> C; - default -> throw new IllegalStateException("Unexpected value: " + value); - }; - } -} diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallSoutTest.java b/src/test/java/overrun/marshal/test/downcall/DowncallSoutTest.java index bd691ea..efa6d30 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallSoutTest.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallSoutTest.java @@ -16,9 +16,8 @@ package overrun.marshal.test.downcall; +import io.github.overrun.memstack.MemoryStack; import org.junit.jupiter.api.*; -import overrun.marshal.MemoryStack; -import overrun.marshal.test.MyEnum; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -101,14 +100,6 @@ void testUTF16String() { assertEquals(TEST_UTF16_STRING, outputStream.toString()); } - @Test - void testCEnum() { - d.testCEnum(MyEnum.A); - d.testCEnum(MyEnum.B); - d.testCEnum(MyEnum.C); - assertEquals("024", outputStream.toString()); - } - @Test void testIntArray() { d.testIntArray(new int[]{4, 2}); @@ -116,7 +107,7 @@ void testIntArray() { d.testIntArray((SegmentAllocator) arena, new int[]{4, 2}); d.testIntArray(arena, new int[]{4, 2}); } - try (MemoryStack stack = MemoryStack.stackPush()) { + try (MemoryStack stack = MemoryStack.pushLocal()) { d.testIntArray(stack, new int[]{4, 2}); } d.testVarArgsJava(2, 4, 2); diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java index 15ef776..d6ad2c6 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java @@ -19,7 +19,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import overrun.marshal.test.MyEnum; import overrun.marshal.test.TestUtil; import overrun.marshal.test.struct.Vector3; import overrun.marshal.test.upcall.SimpleUpcall; @@ -79,11 +78,6 @@ void testReturnString() { assertEquals(TestUtil.TEST_UTF16_STRING, d.testReturnUTF16String()); } - @Test - void testReturnCEnum() { - Assertions.assertEquals(MyEnum.B, d.testReturnCEnum()); - } - @Test void testReturnUpcall() { try (Arena arena = Arena.ofConfined()) { diff --git a/src/test/java/overrun/marshal/test/downcall/IDowncall.java b/src/test/java/overrun/marshal/test/downcall/IDowncall.java index 32936e6..81a323d 100644 --- a/src/test/java/overrun/marshal/test/downcall/IDowncall.java +++ b/src/test/java/overrun/marshal/test/downcall/IDowncall.java @@ -16,12 +16,11 @@ package overrun.marshal.test.downcall; +import io.github.overrun.memstack.MemoryStack; import overrun.marshal.DowncallOption; import overrun.marshal.struct.ByValue; import overrun.marshal.Downcall; -import overrun.marshal.MemoryStack; import overrun.marshal.gen.*; -import overrun.marshal.test.MyEnum; import overrun.marshal.test.struct.Vector3; import overrun.marshal.test.upcall.SimpleUpcall; @@ -63,8 +62,6 @@ default int testDefault() { void testUTF16String(@StrCharset("UTF-16") String s); - void testCEnum(MyEnum myEnum); - int testUpcall(Arena arena, SimpleUpcall upcall); void testIntArray(int[] arr); @@ -86,8 +83,6 @@ default int testDefault() { @StrCharset("UTF-16") String testReturnUTF16String(); - MyEnum testReturnCEnum(); - MemorySegment testReturnUpcall(Arena arena); Vector3 testReturnStruct(); diff --git a/src/test/java/overrun/marshal/test/upcall/ComplexUpcall.java b/src/test/java/overrun/marshal/test/upcall/ComplexUpcall.java index d45a90f..5dc3afc 100644 --- a/src/test/java/overrun/marshal/test/upcall/ComplexUpcall.java +++ b/src/test/java/overrun/marshal/test/upcall/ComplexUpcall.java @@ -16,8 +16,8 @@ package overrun.marshal.test.upcall; +import io.github.overrun.memstack.MemoryStack; import overrun.marshal.Marshal; -import overrun.marshal.MemoryStack; import overrun.marshal.Unmarshal; import overrun.marshal.Upcall; import overrun.marshal.gen.Sized; @@ -39,13 +39,13 @@ public interface ComplexUpcall extends Upcall { int[] invoke(@Sized(2) int[] arr); default MemorySegment invoke(MemorySegment arr) { - try (MemoryStack stack = MemoryStack.stackPush()) { + try (MemoryStack stack = MemoryStack.pushLocal()) { return Marshal.marshal(stack, invoke(Unmarshal.unmarshalAsIntArray(arr))); } } static int[] invoke(MemorySegment stub, int[] arr) { - try (MemoryStack stack = MemoryStack.stackPush()) { + try (MemoryStack stack = MemoryStack.pushLocal()) { return Unmarshal.unmarshalAsIntArray((MemorySegment) TYPE.downcallTarget().invokeExact(stub, Marshal.marshal(stack, arr))); } catch (Throwable e) { throw new RuntimeException(e); From a78fe69f6c027d735089bb279e85d48529d28571 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 24 Aug 2024 01:29:45 +0800 Subject: [PATCH 02/13] Add Processor; remove MemoryStack.java --- build.gradle.kts | 1 + src/main/java/overrun/marshal/Checks.java | 2 +- src/main/java/overrun/marshal/Downcall.java | 475 ++++----- .../overrun/marshal/DowncallMethodData.java | 18 +- src/main/java/overrun/marshal/Marshal.java | 17 +- ...onfigurations.java => MarshalConfigs.java} | 25 +- .../java/overrun/marshal/MemoryStack.java | 950 ------------------ src/main/java/overrun/marshal/Unmarshal.java | 13 + .../java/overrun/marshal/gen/Convert.java | 8 +- src/main/java/overrun/marshal/gen/Sized.java | 4 +- .../java/overrun/marshal/gen/SizedSeg.java | 4 +- src/main/java/overrun/marshal/gen/Type.java | 27 +- .../gen/processor/AfterInvokeProcessor.java | 85 ++ .../gen/processor/AllocatorRequirement.java | 56 ++ .../gen/processor/ArgumentProcessor.java | 148 --- ...ocessorContext.java => BaseProcessor.java} | 38 +- .../gen/processor/BeforeInvokeProcessor.java | 64 ++ .../gen/processor/BeforeReturnProcessor.java | 51 + .../marshal/gen/processor/CheckProcessor.java | 65 ++ .../gen/processor/MarshalProcessor.java | 145 +++ .../marshal/gen/processor/Processor.java | 6 +- .../marshal/gen/processor/ProcessorType.java | 165 ++- .../marshal/gen/processor/ProcessorTypes.java | 131 ++- .../gen/processor/UnmarshalProcessor.java | 131 +++ .../overrun/marshal/internal/Constants.java | 241 ++++- .../marshal/internal/StringCharset.java | 21 +- .../marshal/internal/data/DowncallData.java | 6 +- .../java/overrun/marshal/struct/Struct.java | 4 +- .../marshal/struct/StructAllocator.java | 31 +- .../StructAllocatorSpec.java} | 19 +- .../overrun/marshal/test/GlobalVarTest.java | 16 +- .../test/downcall/DowncallProvider.java | 5 - .../marshal/test/downcall/DowncallTest.java | 7 +- .../marshal/test/downcall/IDowncall.java | 23 +- 34 files changed, 1396 insertions(+), 1606 deletions(-) rename src/main/java/overrun/marshal/{Configurations.java => MarshalConfigs.java} (76%) delete mode 100644 src/main/java/overrun/marshal/MemoryStack.java create mode 100644 src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java create mode 100644 src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java delete mode 100644 src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java rename src/main/java/overrun/marshal/gen/processor/{ArgumentProcessorContext.java => BaseProcessor.java} (52%) create mode 100644 src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java create mode 100644 src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java create mode 100644 src/main/java/overrun/marshal/gen/processor/CheckProcessor.java create mode 100644 src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java create mode 100644 src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java rename src/main/java/overrun/marshal/{Addressable.java => struct/StructAllocatorSpec.java} (65%) diff --git a/build.gradle.kts b/build.gradle.kts index 74d2089..62e87d0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -161,6 +161,7 @@ tasks.withType { encoding = "UTF-8" locale = "en_US" windowTitle = "$projName $projVersion Javadoc" + jFlags("-Duser.language=en-US") if (this is StandardJavadocDocletOptions) { charSet = "UTF-8" isAuthor = true diff --git a/src/main/java/overrun/marshal/Checks.java b/src/main/java/overrun/marshal/Checks.java index c4f3cb5..02007cb 100644 --- a/src/main/java/overrun/marshal/Checks.java +++ b/src/main/java/overrun/marshal/Checks.java @@ -33,7 +33,7 @@ private Checks() { * @param actual the actual size */ public static void checkArraySize(int expected, int actual) { - if (Configurations.CHECK_ARRAY_SIZE.get() && expected != actual) { + if (MarshalConfigs.CHECK_ARRAY_SIZE.get() && expected != actual) { throw new IllegalArgumentException("Expected array of size " + expected + ", got " + actual); } } diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 6e7830a..5a7527f 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -17,10 +17,9 @@ package overrun.marshal; import overrun.marshal.gen.*; -import overrun.marshal.gen.processor.ArgumentProcessor; -import overrun.marshal.gen.processor.ArgumentProcessorContext; -import overrun.marshal.gen.processor.ProcessorTypes; +import overrun.marshal.gen.processor.*; import overrun.marshal.internal.DowncallOptions; +import overrun.marshal.internal.StringCharset; import overrun.marshal.internal.data.DowncallData; import overrun.marshal.struct.ByValue; import overrun.marshal.struct.Struct; @@ -28,7 +27,6 @@ import java.lang.classfile.ClassFile; import java.lang.classfile.CodeBuilder; -import java.lang.classfile.Opcode; import java.lang.classfile.TypeKind; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; @@ -37,6 +35,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.*; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; @@ -47,7 +47,6 @@ import static java.lang.classfile.ClassFile.*; import static java.lang.constant.ConstantDescs.*; import static overrun.marshal.internal.Constants.*; -import static overrun.marshal.internal.StringCharset.getCharset; /** * Downcall library loader. @@ -218,7 +217,7 @@ private static boolean requireAllocator(Class aClass) { private static ClassDesc convertToDowncallCD(AnnotatedElement element, Class aClass) { final Convert convert = element.getDeclaredAnnotation(Convert.class); - if (convert != null && aClass == boolean.class) return convert.value().classDesc(); + if (convert != null && aClass == boolean.class) return convert.value().downcallClassDesc(); if (aClass.isPrimitive()) return aClass.describeConstable().orElseThrow(); if (SegmentAllocator.class.isAssignableFrom(aClass)) return CD_SegmentAllocator; if (aClass == Object.class) return CD_Object; @@ -228,13 +227,14 @@ private static ClassDesc convertToDowncallCD(AnnotatedElement element, Class private static ClassDesc convertToMarshalCD(Class aClass) { if (aClass.isPrimitive() || aClass == String.class) return aClass.describeConstable().orElseThrow(); - if (Addressable.class.isAssignableFrom(aClass)) return CD_Addressable; + if (Struct.class.isAssignableFrom(aClass)) return CD_Struct; if (Upcall.class.isAssignableFrom(aClass)) return CD_Upcall; return CD_MemorySegment; } - @SuppressWarnings("unchecked") - private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup lookup, DowncallOption... options) { + // TODO + /*private*/ + public static Map.Entry buildBytecode(MethodHandles.Lookup caller, SymbolLookup lookup, DowncallOption... options) { Class _targetClass = null, targetClass; Map _descriptorMap = null, descriptorMap; UnaryOperator _transform = null, transform; @@ -257,9 +257,9 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look final List methodList = Arrays.stream(targetClass.getMethods()) .filter(Predicate.not(Downcall::shouldSkip)) .toList(); - final Map exceptionStringMap = methodList.stream() - .collect(Collectors.toUnmodifiableMap(Function.identity(), Downcall::createExceptionString)); - verifyMethods(methodList, exceptionStringMap); + final Map signatureStringMap = methodList.stream() + .collect(Collectors.toUnmodifiableMap(Function.identity(), Downcall::createSignatureString)); + verifyMethods(methodList, signatureStringMap); final ClassFile cf = of(); final ClassDesc cd_thisClass = ClassDesc.of(lookupClass.getPackageName(), DEFAULT_NAME); @@ -272,14 +272,23 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look final String handleName = "$mh" + handleCount.getAndIncrement(); final var parameters = List.of(method.getParameters()); + boolean byValue = method.getDeclaredAnnotation(ByValue.class) != null; + AllocatorRequirement allocatorRequirement = parameters.stream() + .reduce(byValue ? AllocatorRequirement.ALLOCATOR : AllocatorRequirement.NONE, + (requirement, parameter) -> AllocatorRequirement.stricter( + requirement, + ProcessorTypes.fromClass(parameter.getType()).allocationRequirement() + ), AllocatorRequirement::stricter); final DowncallMethodData methodData = new DowncallMethodData( entrypoint, handleName, - exceptionStringMap.get(method), + signatureStringMap.get(method), parameters, - method.getDeclaredAnnotation(ByValue.class) == null && + !byValue && !parameters.isEmpty() && - SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) + allocatorRequirement != AllocatorRequirement.NONE && + SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()), + allocatorRequirement ); methodDataMap.put(method, methodData); } @@ -287,7 +296,7 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look final DowncallData downcallData = generateData(methodDataMap, lookup, descriptorMap, transform); - final byte[] bytes = cf.build(cd_thisClass, classBuilder -> { + return Map.entry(cf.build(cd_thisClass, classBuilder -> { classBuilder.withFlags(ACC_FINAL | ACC_SUPER); // inherit @@ -336,12 +345,12 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look .map(parameter -> ClassDesc.ofDescriptor(parameter.getType().descriptorString())) .toList()); - final String handleName = methodData.handleName(); - classBuilder.withMethod(methodName, + classBuilder.withMethodBody(methodName, mtd_method, Modifier.isPublic(modifiers) ? ACC_PUBLIC : ACC_PROTECTED, - methodBuilder -> methodBuilder.withCode(codeBuilder -> { - final TypeKind returnTypeKind = TypeKind.from(cd_returnType).asLoadable(); + codeBuilder -> { + final String handleName = methodData.handleName(); + final TypeKind returnTypeKind = TypeKind.from(cd_returnType); // returns MethodHandle if (returnType == MethodHandle.class) { @@ -374,282 +383,130 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look } //region body - final boolean hasAllocator = - !parameters.isEmpty() && - SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()); - final boolean shouldAddStack = - !parameters.isEmpty() && - !SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) && - parameters.stream().anyMatch(parameter -> requireAllocator(parameter.getType())); - final int stackSlot; - final int stackPointerSlot; - final int allocatorSlot; - - if (shouldAddStack) { - stackSlot = codeBuilder.allocateLocal(TypeKind.ReferenceType); - stackPointerSlot = codeBuilder.allocateLocal(TypeKind.LongType); - allocatorSlot = stackSlot; - } else { - stackSlot = -1; - stackPointerSlot = -1; - allocatorSlot = hasAllocator ? codeBuilder.parameterSlot(0) : -1; - } - final Map parameterRefSlot = HashMap.newHashMap(Math.toIntExact(parameters.stream() - .filter(parameter -> parameter.getDeclaredAnnotation(Ref.class) != null) - .count())); - - // check size - for (int i = 0, size = parameters.size(); i < size; i++) { - final Parameter parameter = parameters.get(i); - final Sized sized = parameter.getDeclaredAnnotation(Sized.class); - final Class type = parameter.getType(); - if (sized == null || !type.isArray()) { - continue; - } - codeBuilder.ldc(sized.value()) - .aload(codeBuilder.parameterSlot(i)) - .arraylength() - .invokestatic(CD_Checks, - "checkArraySize", - MTD_void_int_int); - } - - // initialize stack - if (shouldAddStack) { - codeBuilder.invokestatic(CD_MemoryStack, "stackGet", MTD_MemoryStack) - .astore(stackSlot) - .aload(stackSlot) - .invokevirtual(CD_MemoryStack, "pointer", MTD_long) - .lstore(stackPointerSlot); + AllocatorRequirement allocatorRequirement = methodData.allocatorRequirement(); + boolean isFirstAllocator = !parameters.isEmpty() && + SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()); + boolean hasMemoryStack = switch (allocatorRequirement) { + case NONE, ALLOCATOR, ARENA -> false; + case STACK -> !isFirstAllocator; + }; + + var beforeReturnContext = new BeforeReturnProcessor.Context(hasMemoryStack); + + CheckProcessor.getInstance().process(codeBuilder, new CheckProcessor.Context(parameters)); + + int allocatorSlot; + if (isFirstAllocator) { + allocatorSlot = codeBuilder.parameterSlot(0); + } else if (hasMemoryStack) { + allocatorSlot = codeBuilder.allocateLocal(TypeKind.ReferenceType); + codeBuilder.invokestatic(CD_MemoryStack, + "pushLocal", + MTD_MemoryStack, + true + ).astore(allocatorSlot); + } else { + allocatorSlot = -1; } codeBuilder.trying( blockCodeBuilder -> { - final boolean skipFirstParam = methodData.skipFirstParam(); - final int parameterSize = parameters.size(); + // before invoke + Map refSlotMap = new HashMap<>(); + BeforeInvokeProcessor.getInstance().process(blockCodeBuilder, + new BeforeInvokeProcessor.Context(parameters, refSlotMap, allocatorSlot)); - final ClassDesc cd_returnTypeDowncall = convertToDowncallCD(method, returnType); - final boolean returnVoid = returnType == void.class; - - // ref - for (int i = 0, size = parameters.size(); i < size; i++) { - final Parameter parameter = parameters.get(i); - if (parameter.getDeclaredAnnotation(Ref.class) == null) { - continue; - } - final Class type = parameter.getType(); - if (type.isArray()) { - final Class componentType = type.getComponentType(); - final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); - blockCodeBuilder.aload(allocatorSlot) - .aload(blockCodeBuilder.parameterSlot(i)); - - if (componentType == String.class && getCharset(blockCodeBuilder, parameter)) { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_StringArray_Charset - ).astore(slot); - } else { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MethodTypeDesc.of(CD_MemorySegment, - CD_SegmentAllocator, - convertToMarshalCD(componentType).arrayType()) - ).astore(slot); - } - parameterRefSlot.put(parameter, slot); - } - } - - // invocation - final List parameterCDList = new ArrayList<>(skipFirstParam ? parameterSize - 1 : parameterSize); + // invoke blockCodeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle); - for (int i = skipFirstParam ? 1 : 0; i < parameterSize; i++) { - final Parameter parameter = parameters.get(i); - final Class type = parameter.getType(); - final ClassDesc cd_parameterDowncall = convertToDowncallCD(parameter, type); - - if (parameterRefSlot.containsKey(parameter)) { - blockCodeBuilder.aload(parameterRefSlot.get(parameter)); + List downcallClassDescList = new ArrayList<>(); + for (int i = methodData.skipFirstParam() ? 1 : 0, size = parameters.size(); i < size; i++) { + Parameter parameter = parameters.get(i); + ProcessorType processorType = ProcessorTypes.fromParameter(parameter); + if (refSlotMap.containsKey(parameter)) { + blockCodeBuilder.aload(refSlotMap.get(parameter)); } else { - ArgumentProcessor.getInstance().process( - blockCodeBuilder, - ProcessorTypes.fromClass(type), - new ArgumentProcessorContext( - parameter, + MarshalProcessor.getInstance().process(blockCodeBuilder, + new MarshalProcessor.Context(processorType, + StringCharset.getCharset(parameter), blockCodeBuilder.parameterSlot(i), - allocatorSlot, - parameter.getDeclaredAnnotation(Convert.class) - ) - ); + allocatorSlot)); } - - parameterCDList.add(cd_parameterDowncall); + downcallClassDescList.add(processorType.downcallClassDesc()); } + ProcessorType returnProcessorType = ProcessorTypes.fromMethod(method); + ClassDesc cd_returnDowncall = returnProcessorType.downcallClassDesc(); + TypeKind returnDowncallTypeKind = TypeKind.from(cd_returnDowncall); blockCodeBuilder.invokevirtual(CD_MethodHandle, "invokeExact", - MethodTypeDesc.of(cd_returnTypeDowncall, parameterCDList)); - - final int resultSlot; - if (returnVoid) { - resultSlot = -1; - } else { - final TypeKind typeKind = TypeKind.from(cd_returnTypeDowncall); - resultSlot = blockCodeBuilder.allocateLocal(typeKind); - blockCodeBuilder.storeLocal(typeKind, resultSlot); + MethodTypeDesc.of(cd_returnDowncall, downcallClassDescList)); + boolean returnVoid = returnType == void.class; + int resultSlot = returnVoid ? -1 : blockCodeBuilder.allocateLocal(returnDowncallTypeKind); + if (!returnVoid) { + blockCodeBuilder.storeLocal(returnDowncallTypeKind, resultSlot); } - // copy ref result - for (int i = 0, size = parameters.size(); i < size; i++) { - final Parameter parameter = parameters.get(i); - final Class type = parameter.getType(); - final Class componentType = type.getComponentType(); - if (parameter.getDeclaredAnnotation(Ref.class) != null && - type.isArray()) { - final boolean isPrimitiveArray = componentType.isPrimitive(); - final boolean isStringArray = componentType == String.class; - if (isPrimitiveArray || isStringArray) { - final int refSlot = parameterRefSlot.get(parameter); - final int parameterSlot = blockCodeBuilder.parameterSlot(i); - blockCodeBuilder.aload(refSlot) - .aload(parameterSlot); - if (isPrimitiveArray) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MethodTypeDesc.of(CD_void, CD_MemorySegment, ClassDesc.ofDescriptor(type.descriptorString()))); - } else { - if (getCharset(blockCodeBuilder, parameter)) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MTD_void_MemorySegment_StringArray_Charset); - } else { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MTD_void_MemorySegment_StringArray); - } - } - } - } - } - - // wrap return value - final int unmarshalSlot; - if (returnVoid) { - unmarshalSlot = -1; - } else { - if (shouldAddStack) { - unmarshalSlot = blockCodeBuilder.allocateLocal(returnTypeKind); - } else { - unmarshalSlot = -1; - } - blockCodeBuilder.loadLocal(TypeKind.from(cd_returnTypeDowncall), resultSlot); - } - final Convert convert = method.getDeclaredAnnotation(Convert.class); - if (convert != null && returnType == boolean.class) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsBoolean", - MethodTypeDesc.of(CD_boolean, convert.value().classDesc())); - } else if (returnType == String.class) { - final boolean hasCharset = getCharset(blockCodeBuilder, method); - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unboundString", - hasCharset ? MTD_String_MemorySegment_Charset : MTD_String_MemorySegment); - } else if (Struct.class.isAssignableFrom(returnType)) { - blockCodeBuilder.ifThenElse(Opcode.IFNONNULL, - blockCodeBuilder1 -> { - final var structAllocatorField = getStructAllocatorField(returnType); - Objects.requireNonNull(structAllocatorField); - blockCodeBuilder1.getstatic(cd_returnType, structAllocatorField.getName(), CD_StructAllocator) - .aload(resultSlot) - .invokevirtual(CD_StructAllocator, "of", MTD_Object_MemorySegment) - .checkcast(cd_returnType); - }, - CodeBuilder::aconst_null); - } else if (returnType.isArray()) { - final Class componentType = returnType.getComponentType(); - if (componentType == String.class) { - final boolean hasCharset = getCharset(blockCodeBuilder, method); - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsStringArray", - hasCharset ? MTD_StringArray_MemorySegment_Charset : MTD_StringArray_MemorySegment); - } else if (componentType.isPrimitive() || componentType == MemorySegment.class) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - unmarshalMethod(returnType), - MethodTypeDesc.of(cd_returnType, CD_MemorySegment)); - } - } + // after invoke + AfterInvokeProcessor.getInstance().process(blockCodeBuilder, new AfterInvokeProcessor.Context(parameters, refSlotMap)); - // reset stack - if (shouldAddStack) { - if (!returnVoid) { - blockCodeBuilder.storeLocal(returnTypeKind, unmarshalSlot); - } - blockCodeBuilder.aload(stackSlot) - .lload(stackPointerSlot) - .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); - if (!returnVoid) { - blockCodeBuilder.loadLocal(returnTypeKind, unmarshalSlot); - } - } + // before return + BeforeReturnProcessor.getInstance().process(blockCodeBuilder, beforeReturnContext); // return + UnmarshalProcessor.getInstance().process(blockCodeBuilder, + new UnmarshalProcessor.Context(returnProcessorType, + StringCharset.getCharset(method), + resultSlot)); blockCodeBuilder.return_(returnTypeKind); }, catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> { - final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); - // create exception + BeforeReturnProcessor.getInstance().process(blockCodeBuilder, beforeReturnContext); + // rethrow the exception + int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); blockCodeBuilder.astore(slot) .new_(CD_IllegalStateException) .dup() - .ldc(methodData.exceptionString()) + .ldc(methodData.signatureString()) .aload(slot) - .invokespecial(CD_IllegalStateException, INIT_NAME, MTD_void_String_Throwable); - // reset stack - if (shouldAddStack) { - blockCodeBuilder.aload(stackSlot) - .lload(stackPointerSlot) - .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); - } - // throw - blockCodeBuilder.athrow(); - }) - ); + .invokespecial(CD_IllegalStateException, INIT_NAME, MTD_void_String_Throwable) + .athrow(); + })); + //endregion - })); + }); } //endregion //region DirectAccess final boolean hasDirectAccess = DirectAccess.class.isAssignableFrom(targetClass); if (hasDirectAccess) { - classBuilder.withMethod("functionDescriptors", + classBuilder.withMethodBody("functionDescriptors", MTD_Map, ACC_PUBLIC, - methodBuilder -> methodBuilder.withCode(codeBuilder -> codeBuilder + codeBuilder -> codeBuilder .ldc(DCD_classData_DowncallData) .invokevirtual(CD_DowncallData, "descriptorMap", MTD_Map) - .areturn())); - classBuilder.withMethod("methodHandles", + .areturn()); + classBuilder.withMethodBody("methodHandles", MTD_Map, ACC_PUBLIC, - methodBuilder -> methodBuilder.withCode(codeBuilder -> codeBuilder + codeBuilder -> codeBuilder .ldc(DCD_classData_DowncallData) .invokevirtual(CD_DowncallData, "handleMap", MTD_Map) - .areturn())); - classBuilder.withMethod("symbolLookup", + .areturn()); + classBuilder.withMethodBody("symbolLookup", MTD_SymbolLookup, ACC_PUBLIC, - methodBuilder -> methodBuilder.withCode(codeBuilder -> codeBuilder + codeBuilder -> codeBuilder .ldc(DCD_classData_DowncallData) .invokevirtual(CD_DowncallData, "symbolLookup", MTD_SymbolLookup) - .areturn())); + .areturn()); } //endregion //region class initializer - classBuilder.withMethod(CLASS_INIT_NAME, MTD_void, ACC_STATIC, - methodBuilder -> methodBuilder.withCode(codeBuilder -> { + classBuilder.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, + codeBuilder -> { final int handleMapSlot = codeBuilder.allocateLocal(TypeKind.ReferenceType); codeBuilder.ldc(DCD_classData_DowncallData) .invokevirtual(CD_DowncallData, "handleMap", MTD_Map) @@ -669,14 +526,27 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look }); codeBuilder.return_(); - })); + }); //endregion - }); + }), downcallData); + } + + @SuppressWarnings("unchecked") + private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup lookup, DowncallOption... options) { + var built = buildBytecode(caller, lookup, options); try { + //TODO + if (DEBUG) { + Path path = Path.of("run/" + caller.lookupClass().getSimpleName() + ".class"); + if (Files.notExists(path.getParent())) { + Files.createDirectories(path.getParent()); + } + Files.write(path, built.getKey()); + } final MethodHandles.Lookup hiddenClass = caller.defineHiddenClassWithClassData( - bytes, - downcallData, + built.getKey(), + built.getValue(), true, MethodHandles.Lookup.ClassOption.STRONG ); @@ -735,15 +605,17 @@ private static boolean shouldSkip(Method method) { return false; } - private static String createExceptionString(Method method) { + private static String createSignatureString(Method method) { return method.getReturnType().getCanonicalName() + " " + method.getDeclaringClass().getCanonicalName() + "." + method.getName() + Arrays.stream(method.getParameterTypes()).map(Class::getCanonicalName) .collect(Collectors.joining(", ", "(", ")")); } - private static void verifyMethods(List list, Map exceptionStringMap) { - for (Method method : list) {// check method return type + private static void verifyMethods(List list, Map signatureStringMap) { + for (Method method : list) { + String signature = signatureStringMap.get(method); + // check method return type final Class returnType = method.getReturnType(); if (Struct.class.isAssignableFrom(returnType)) { boolean foundAllocator = false; @@ -757,11 +629,12 @@ private static void verifyMethods(List list, Map excepti throw new IllegalStateException("The struct " + returnType + " must contain one public static field that is StructAllocator"); } } else if (!isValidReturnType(returnType)) { - throw new IllegalStateException("Invalid return type: " + exceptionStringMap.get(method)); + throw new IllegalStateException("Invalid return type: " + signature); } // check method parameter final Class[] types = method.getParameterTypes(); + final boolean isFirstAllocator = types.length > 0 && SegmentAllocator.class.isAssignableFrom(types[0]); final boolean isFirstArena = types.length > 0 && Arena.class.isAssignableFrom(types[0]); for (Parameter parameter : method.getParameters()) { final Class type = parameter.getType(); @@ -771,44 +644,62 @@ private static void verifyMethods(List list, Map excepti throw new IllegalStateException("Invalid parameter: " + parameter + " in " + method); } } - } - } - private static boolean isValidParamArrayType(Class aClass) { - if (!aClass.isArray()) return false; - final Class type = aClass.getComponentType(); - return type.isPrimitive() || - type == MemorySegment.class || - type == String.class || - Addressable.class.isAssignableFrom(type) || - Upcall.class.isAssignableFrom(type); - } + // check allocator requirement + boolean byValue = method.getDeclaredAnnotation(ByValue.class) != null; + AllocatorRequirement allocatorRequirement = byValue ? + AllocatorRequirement.ALLOCATOR : + AllocatorRequirement.NONE; + Object strictObject = byValue ? "annotation @ByValue" : null; + for (Parameter parameter : method.getParameters()) { + final Class type = parameter.getType(); + ProcessorType processorType = ProcessorTypes.fromClass(type); + AllocatorRequirement previous = allocatorRequirement; + allocatorRequirement = AllocatorRequirement.stricter(allocatorRequirement, processorType.allocationRequirement()); + if (allocatorRequirement != previous) { + strictObject = parameter; + } + } + switch (allocatorRequirement) { + case NONE, STACK -> { + } + case ALLOCATOR -> { + if (!isFirstAllocator) { + throw new IllegalStateException("A segment allocator is required by " + strictObject + ": " + signature); + } + } + case ARENA -> { + if (!isFirstArena) { + throw new IllegalStateException("An arena is required by " + strictObject + ": " + signature); + } + } + default -> throw new IllegalStateException("Invalid allocator requirement: " + allocatorRequirement); + } - private static boolean isValidReturnArrayType(Class aClass) { - if (!aClass.isArray()) return false; - final Class type = aClass.getComponentType(); - return type.isPrimitive() || - type == MemorySegment.class || - type == String.class; + // check Sized annotation + boolean sized = method.getDeclaredAnnotation(Sized.class) != null; + boolean sizedSeg = method.getDeclaredAnnotation(SizedSeg.class) != null; + if (sized && sizedSeg) { + throw new IllegalStateException("Cannot be annotated with both @Sized and @SizedSeg: " + signature); + } + if (sized) { + if (!returnType.isArray()) { + throw new IllegalStateException("Return type annotated with @Sized must be an array: " + signature); + } + } else if (sizedSeg) { + if (returnType != MemorySegment.class && !Struct.class.isAssignableFrom(returnType)) { + throw new IllegalStateException("Return type annotated with @SizedSeg must be MemorySegment or Struct: " + signature); + } + } + } } private static boolean isValidParamType(Class aClass) { - return aClass.isPrimitive() || - aClass == MemorySegment.class || - aClass == String.class || - SegmentAllocator.class.isAssignableFrom(aClass) || - Addressable.class.isAssignableFrom(aClass) || - Upcall.class.isAssignableFrom(aClass) || - isValidParamArrayType(aClass); + return ProcessorTypes.isRegistered(aClass); } private static boolean isValidReturnType(Class aClass) { - return aClass.isPrimitive() || - aClass == MemorySegment.class || - aClass == String.class || - Struct.class.isAssignableFrom(aClass) || - isValidReturnArrayType(aClass) || - aClass == MethodHandle.class; + return aClass == MethodHandle.class || ProcessorTypes.isRegistered(aClass); } private static DowncallData generateData( @@ -823,7 +714,6 @@ private static DowncallData generateData( Method method = entry.getKey(); DowncallMethodData methodData = entry.getValue(); final String entrypoint = methodData.entrypoint(); - final Optional optional = lookup.find(entrypoint); // function descriptor final FunctionDescriptor descriptor; @@ -849,8 +739,7 @@ private static DowncallData generateData( final boolean isSizedSeg = sizedSeg != null; final boolean isSized = sized != null; if (Struct.class.isAssignableFrom(returnType)) { - final var structAllocatorField = getStructAllocatorField(returnType); - Objects.requireNonNull(structAllocatorField); + final var structAllocatorField = Objects.requireNonNull(getStructAllocatorField(returnType)); final StructLayout structLayout; try { structLayout = ((StructAllocator) structAllocatorField.get(null)).layout(); @@ -912,6 +801,7 @@ private static DowncallData generateData( descriptorMap1.put(entrypoint, descriptor); + final Optional optional = lookup.find(entrypoint); if (optional.isPresent()) { // linker options final Linker.Option[] options; @@ -928,10 +818,13 @@ private static DowncallData generateData( } else if (method.isDefault()) { map.putIfAbsent(entrypoint, null); } else { - throw new UnsatisfiedLinkError("unresolved symbol: " + entrypoint + " (" + descriptor + "): " + methodData.exceptionString()); + throw new NoSuchElementException("Symbol not found: " + entrypoint + " (" + descriptor + "): " + methodData.signatureString()); } } - return new DowncallData(Collections.unmodifiableMap(descriptorMap1), Collections.unmodifiableMap(map), lookup); + return new DowncallData(Collections.unmodifiableMap(descriptorMap1), + Collections.unmodifiableMap(map), + lookup, + ProcessorTypes.collect(ProcessorType.Struct.class)); } private static Field getStructAllocatorField(Class aClass) { diff --git a/src/main/java/overrun/marshal/DowncallMethodData.java b/src/main/java/overrun/marshal/DowncallMethodData.java index 609b414..8c0c107 100644 --- a/src/main/java/overrun/marshal/DowncallMethodData.java +++ b/src/main/java/overrun/marshal/DowncallMethodData.java @@ -16,25 +16,29 @@ package overrun.marshal; +import overrun.marshal.gen.processor.AllocatorRequirement; + import java.lang.reflect.Parameter; import java.util.List; /** * Holds downcall method name * - * @param entrypoint the entrypoint - * @param handleName the handleName - * @param exceptionString the exceptionString - * @param parameters the parameters - * @param skipFirstParam the skipFirstParam + * @param entrypoint entrypoint + * @param handleName handleName + * @param signatureString signatureString + * @param parameters parameters + * @param skipFirstParam skipFirstParam + * @param allocatorRequirement allocatorRequirement * @author squid233 * @since 0.1.0 */ record DowncallMethodData( String entrypoint, String handleName, - String exceptionString, + String signatureString, List parameters, - boolean skipFirstParam + boolean skipFirstParam, + AllocatorRequirement allocatorRequirement ) { } diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java index c60cbd3..d38ff68 100644 --- a/src/main/java/overrun/marshal/Marshal.java +++ b/src/main/java/overrun/marshal/Marshal.java @@ -17,6 +17,7 @@ package overrun.marshal; import org.jetbrains.annotations.Nullable; +import overrun.marshal.struct.Struct; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -36,7 +37,7 @@ * @since 0.1.0 */ public final class Marshal { - private static final VarHandle vh_addressArray = arrayVarHandle(ADDRESS); + static final VarHandle vh_addressArray = arrayVarHandle(ADDRESS); static final VarHandle vh_booleanArray = arrayVarHandle(JAVA_BOOLEAN); private Marshal() { @@ -142,14 +143,14 @@ public static MemorySegment marshal(SegmentAllocator allocator, @Nullable String } /** - * Converts the given addressable to a segment. + * Converts the given struct to a segment. * - * @param addressable the addressable + * @param struct the struct * @return the segment */ - public static MemorySegment marshal(@Nullable Addressable addressable) { - if (addressable == null) return MemorySegment.NULL; - return addressable.segment(); + public static MemorySegment marshal(@Nullable Struct struct) { + if (struct == null) return MemorySegment.NULL; + return struct.segment(); } /** @@ -324,8 +325,8 @@ public static MemorySegment marshal(SegmentAllocator allocator, String @Nullable * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, @Nullable Addressable @Nullable [] arr) { - return marshal(allocator, arr, (_, addressable) -> Marshal.marshal(addressable)); + public static MemorySegment marshal(SegmentAllocator allocator, @Nullable Struct @Nullable [] arr) { + return marshal(allocator, arr, (_, struct) -> Marshal.marshal(struct)); } /** diff --git a/src/main/java/overrun/marshal/Configurations.java b/src/main/java/overrun/marshal/MarshalConfigs.java similarity index 76% rename from src/main/java/overrun/marshal/Configurations.java rename to src/main/java/overrun/marshal/MarshalConfigs.java index e76d59a..723fc69 100644 --- a/src/main/java/overrun/marshal/Configurations.java +++ b/src/main/java/overrun/marshal/MarshalConfigs.java @@ -27,7 +27,7 @@ * @author squid233 * @since 0.1.0 */ -public final class Configurations { +public final class MarshalConfigs { /** * Enable checks. *

@@ -48,30 +48,9 @@ public final class Configurations { * The default value is {@code false}. */ public static final Entry DEBUG = new Entry<>(() -> false); - /** - * Enable using debug memory stack. - *

- * The default value is {@code false}. - */ - @Deprecated(since = "0.1.0-alpha.30", forRemoval = true) - public static final Entry DEBUG_STACK = new Entry<>(() -> false); - /** - * The default stack size in KiB of {@link MemoryStack}. - *

- * The default value is {@code 64}. - */ - @Deprecated(since = "0.1.0-alpha.30", forRemoval = true) - public static final Entry STACK_SIZE = new Entry<>(() -> 64L); - /** - * The default stack frames of {@link MemoryStack}. - *

- * The default value is {@code 8}. - */ - @Deprecated(since = "0.1.0-alpha.30", forRemoval = true) - public static final Entry STACK_FRAMES = new Entry<>(() -> 8); private static Consumer apiLogger = System.err::println; - private Configurations() { + private MarshalConfigs() { //no instance } diff --git a/src/main/java/overrun/marshal/MemoryStack.java b/src/main/java/overrun/marshal/MemoryStack.java deleted file mode 100644 index a14e0cc..0000000 --- a/src/main/java/overrun/marshal/MemoryStack.java +++ /dev/null @@ -1,950 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal; - -import java.lang.foreign.*; -import java.util.Arrays; -import java.util.Objects; - -import static java.lang.foreign.ValueLayout.*; - -/** - * An off-heap memory stack. - * - *

This class should be used in a thread-local manner for stack allocations.

- * - * @author lwjgl3 - * @author squid233 - * @see Configurations#STACK_SIZE - * @see Configurations#STACK_FRAMES - * @see Configurations#DEBUG_STACK - * @since 0.1.0 - */ -@Deprecated(since = "0.1.0-alpha.30", forRemoval = true) -public sealed class MemoryStack implements Arena { - private static final boolean CHECKS = Configurations.CHECKS.get(); - private static final boolean DEBUG = Configurations.DEBUG.get(); - private static final boolean DEBUG_STACK = Configurations.DEBUG_STACK.get(); - private static final long DEFAULT_STACK_SIZE = Configurations.STACK_SIZE.get() * 1024; - private static final int DEFAULT_STACK_FRAMES = Configurations.STACK_FRAMES.get(); - private static final ThreadLocal TLS = ThreadLocal.withInitial(MemoryStack::create); - - static { - if (DEFAULT_STACK_SIZE <= 0) throw new IllegalStateException("Invalid stack size."); - if (DEFAULT_STACK_FRAMES <= 0) throw new IllegalStateException("Invalid stack frames."); - } - - private final MemorySegment segment; - private long pointer; - private long[] frames; - int frameIndex; - - /** - * Creates a new {@code MemoryStack} backed by the specified memory segment. - * - *

In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.

- * - * @param segment the backing memory segment - */ - protected MemoryStack(MemorySegment segment) { - this.segment = segment; - this.pointer = segment.byteSize(); - this.frames = new long[DEFAULT_STACK_FRAMES]; - } - - /** - * Creates a new {@code MemoryStack} with the default size. - * - *

In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.

- * - * @return the memory stack - */ - public static MemoryStack create() { - return create(DEFAULT_STACK_SIZE); - } - - /** - * Creates a new {@code MemoryStack} with the specified size. - * - *

In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.

- * - * @param byteSize the maximum number of bytes that may be allocated on the stack - * @return the memory stack - */ - public static MemoryStack create(long byteSize) { - return create(Arena.ofAuto(), byteSize); - } - - /** - * Creates a new {@code MemoryStack} with the specified size and arena. - * - *

In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.

- * - * @param arena the arena for allocating buffer - * @param byteSize the maximum number of bytes that may be allocated on the stack - * @return the memory stack - */ - public static MemoryStack create(Arena arena, long byteSize) { - return create(arena.allocate(byteSize)); - } - - /** - * Creates a new {@code MemoryStack} backed by the specified memory segment. - * - *

In the initial state, there is no active stack frame. The {@link #push} method must be used before any allocations.

- * - * @param segment the backing memory segment - * @return the memory stack - */ - public static MemoryStack create(MemorySegment segment) { - return DEBUG_STACK ? - new DebugMemoryStack(segment) : - new MemoryStack(segment); - } - - private void frameOverflow() { - if (DEBUG) { - Configurations.apiLog("[WARNING] Out of frame stack space (" + frames.length + ") in thread: " + Thread.currentThread()); - } - frames = Arrays.copyOf(frames, frames.length * 3 / 2); - } - - /** - * Stores the current stack pointer and pushes a new frame to the stack. - * - *

This method should be called when entering a method, before doing any stack allocations. When exiting a method, call the {@link #pop} method to - * restore the previous stack frame.

- * - *

Pairs of push/pop calls may be nested. Care must be taken to:

- * - * - * @return this stack - */ - public MemoryStack push() { - if (frameIndex == frames.length) { - frameOverflow(); - } - frames[frameIndex++] = pointer; - return this; - } - - /** - * Pops the current stack frame and moves the stack pointer to the end of the previous stack frame. - * - * @return this stack - */ - public MemoryStack pop() { - pointer = frames[--frameIndex]; - return this; - } - - /** - * {@return the backing segment} - */ - public MemorySegment segment() { - return segment; - } - - /** - * {@return the current frame index} - * - *

This is the current number of nested {@link #push} calls.

- */ - public int frameIndex() { - return frameIndex; - } - - /** - * {@return the memory segment at the current stack pointer} - */ - public MemorySegment pointerAddress() { - return segment().asSlice(pointer); - } - - /** - * {@return the current stack pointer} - * - *

The stack grows "downwards", so when the stack is empty {@code pointer} is equal to {@code size}. On every allocation {@code pointer} is reduced by - * the allocated size (after alignment) and {@code address + pointer} points to the first byte of the last allocation.

- * - *

Effectively, this methods returns how many more bytes may be allocated on the stack.

- */ - public long pointer() { - return pointer; - } - - /** - * Sets the current stack pointer. - * - *

This method directly manipulates the stack pointer. Using it irresponsibly may break the internal state of the stack. It should only be used in rare - * cases or in auto-generated code.

- * - * @param pointer the pointer - */ - public void setPointer(long pointer) { - if (CHECKS) { - checkPointer(pointer); - } - - this.pointer = pointer; - } - - private void checkPointer(long pointer) { - if (pointer < 0 || segment().byteSize() < pointer) { - throw new IndexOutOfBoundsException("Invalid stack pointer"); - } - } - - /** - * Allocates a block of {@code size} bytes of memory on the stack. - * The content of the newly allocated block of memory is not initialized, remaining with - * indeterminate values. - * - * @param byteSize the allocation size - * @param byteAlignment the required alignment - * @return the memory segment on the stack for the requested allocation - */ - public MemorySegment malloc(long byteSize, long byteAlignment) { - if (CHECKS) { - checkByteSize(byteSize); - checkAlignment(byteAlignment); - } - - // Align address to the specified alignment - long rawLong = segment().address(); - long address = (rawLong + pointer - byteSize) & -byteAlignment; - - pointer = address - rawLong; - if (CHECKS && pointer < 0) { - throw new OutOfMemoryError("Out of stack space."); - } - - return segment().asSlice(pointer, byteSize); - } - - /** - * Allocates a block of {@code size} bytes of memory on the stack. - * The content of the newly allocated block of memory is not initialized, remaining with - * indeterminate values. - * - * @param layout the layout of the memory segment - * @return the memory segment on the stack for the requested allocation - */ - public MemorySegment malloc(MemoryLayout layout) { - return malloc(layout.byteSize(), layout.byteAlignment()); - } - - private MemorySegment malloc(long byteSize, ValueLayout valueLayout) { - return malloc(byteSize, valueLayout.byteAlignment()); - } - - private static void checkByteSize(long byteSize) { - if (byteSize < 0) { - throw new IllegalArgumentException("byteSize must be >= 0."); - } - } - - private static void checkAlignment(long alignment) { - if (alignment <= 0) { - throw new IllegalArgumentException("Alignment must be > 0."); - } - if (Long.bitCount(alignment) != 1) { - throw new IllegalArgumentException("Alignment must be a power-of-two value."); - } - } - - /** - * Allocates a block of memory on the stack for an array of {@code num} elements, - * each of them {@code size} bytes long, and initializes all its bits to - * zero. - * - * @param byteSize the allocation size - * @param byteAlignment the required element alignment - * @return the memory segment on the stack for the requested allocation - */ - @Override - public MemorySegment allocate(long byteSize, long byteAlignment) { - return malloc(byteSize, byteAlignment).fill((byte) 0); - } - - @Override - public MemorySegment allocateFrom(OfByte layout, byte value) { - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(OfChar layout, char value) { - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(OfShort layout, short value) { - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(OfInt layout, int value) { - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(OfFloat layout, float value) { - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(OfLong layout, long value) { - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(OfDouble layout, double value) { - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(AddressLayout layout, MemorySegment value) { - Objects.requireNonNull(value); - Objects.requireNonNull(layout); - final var segment = malloc(layout.byteSize(), layout); - segment.set(layout, 0, value); - return segment; - } - - @Override - public MemorySegment allocateFrom(ValueLayout elementLayout, MemorySegment source, ValueLayout sourceElementLayout, long sourceOffset, long elementCount) { - Objects.requireNonNull(source); - Objects.requireNonNull(sourceElementLayout); - Objects.requireNonNull(elementLayout); - final var segment = malloc(elementLayout.byteSize() * elementCount, elementLayout); - MemorySegment.copy(source, sourceElementLayout, sourceOffset, segment, elementLayout, 0, elementCount); - return segment; - } - - @Override - public MemorySegment.Scope scope() { - return segment().scope(); - } - - @Override - public void close() { - pop(); - } - - /** - * Single value version of {@link #malloc}. - * - * @param x the first value - * @return the segment - */ - public MemorySegment bytes(byte x) { - return allocateFrom(JAVA_BYTE, x); - } - - /** - * Two value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @return the segment - */ - public MemorySegment bytes(byte x, byte y) { - final var segment = malloc(2, JAVA_BYTE); - segment.set(JAVA_BYTE, 0, x); - segment.set(JAVA_BYTE, 1, y); - return segment; - } - - /** - * Three value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @return the segment - */ - public MemorySegment bytes(byte x, byte y, byte z) { - final var segment = malloc(3, JAVA_BYTE); - segment.set(JAVA_BYTE, 0, x); - segment.set(JAVA_BYTE, 1, y); - segment.set(JAVA_BYTE, 2, z); - return segment; - } - - /** - * Four value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @param w the fourth value - * @return the segment - */ - public MemorySegment bytes(byte x, byte y, byte z, byte w) { - final var segment = malloc(4, JAVA_BYTE); - segment.set(JAVA_BYTE, 0, x); - segment.set(JAVA_BYTE, 1, y); - segment.set(JAVA_BYTE, 2, z); - segment.set(JAVA_BYTE, 3, w); - return segment; - } - - /** - * Vararg version of {@link #malloc}. - * - * @param values the values - * @return the segment - */ - public MemorySegment bytes(byte... values) { - return allocateFrom(JAVA_BYTE, values); - } - - // ------------------------------------------------- - - /** - * Single value version of {@link #malloc}. - * - * @param x the first value - * @return the segment - */ - public MemorySegment shorts(short x) { - return allocateFrom(JAVA_SHORT, x); - } - - /** - * Two value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @return the segment - */ - public MemorySegment shorts(short x, short y) { - final var segment = malloc(4, JAVA_SHORT); - segment.set(JAVA_SHORT, 0, x); - segment.set(JAVA_SHORT, 2, y); - return segment; - } - - /** - * Three value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @return the segment - */ - public MemorySegment shorts(short x, short y, short z) { - final var segment = malloc(6, JAVA_SHORT); - segment.set(JAVA_SHORT, 0, x); - segment.set(JAVA_SHORT, 2, y); - segment.set(JAVA_SHORT, 4, z); - return segment; - } - - /** - * Four value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @param w the fourth value - * @return the segment - */ - public MemorySegment shorts(short x, short y, short z, short w) { - final var segment = malloc(8, JAVA_SHORT); - segment.set(JAVA_SHORT, 0, x); - segment.set(JAVA_SHORT, 2, y); - segment.set(JAVA_SHORT, 4, z); - segment.set(JAVA_SHORT, 6, w); - return segment; - } - - /** - * Vararg version of {@link #malloc}. - * - * @param values the values - * @return the segment - */ - public MemorySegment shorts(short... values) { - return allocateFrom(JAVA_SHORT, values); - } - - // ------------------------------------------------- - - /** - * Single value version of {@link #malloc}. - * - * @param x the first value - * @return the segment - */ - public MemorySegment ints(int x) { - return allocateFrom(JAVA_INT, x); - } - - /** - * Two value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @return the segment - */ - public MemorySegment ints(int x, int y) { - final var segment = malloc(8, JAVA_INT); - segment.set(JAVA_INT, 0, x); - segment.set(JAVA_INT, 4, y); - return segment; - } - - /** - * Three value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @return the segment - */ - public MemorySegment ints(int x, int y, int z) { - final var segment = malloc(12, JAVA_INT); - segment.set(JAVA_INT, 0, x); - segment.set(JAVA_INT, 4, y); - segment.set(JAVA_INT, 8, z); - return segment; - } - - /** - * Four value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @param w the fourth value - * @return the segment - */ - public MemorySegment ints(int x, int y, int z, int w) { - final var segment = malloc(16, JAVA_INT); - segment.set(JAVA_INT, 0, x); - segment.set(JAVA_INT, 4, y); - segment.set(JAVA_INT, 8, z); - segment.set(JAVA_INT, 12, w); - return segment; - } - - /** - * Vararg version of {@link #malloc}. - * - * @param values the values - * @return the segment - */ - public MemorySegment ints(int... values) { - return allocateFrom(JAVA_INT, values); - } - - // ------------------------------------------------- - - /** - * Single value version of {@link #malloc}. - * - * @param x the first value - * @return the segment - */ - public MemorySegment longs(long x) { - return allocateFrom(JAVA_LONG, x); - } - - /** - * Two value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @return the segment - */ - public MemorySegment longs(long x, long y) { - final var segment = malloc(16, JAVA_LONG); - segment.set(JAVA_LONG, 0, x); - segment.set(JAVA_LONG, 8, y); - return segment; - } - - /** - * Three value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @return the segment - */ - public MemorySegment longs(long x, long y, long z) { - final var segment = malloc(24, JAVA_LONG); - segment.set(JAVA_LONG, 0, x); - segment.set(JAVA_LONG, 8, y); - segment.set(JAVA_LONG, 16, z); - return segment; - } - - /** - * Four value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @param w the fourth value - * @return the segment - */ - public MemorySegment longs(long x, long y, long z, long w) { - final var segment = malloc(32, JAVA_LONG); - segment.set(JAVA_LONG, 0, x); - segment.set(JAVA_LONG, 8, y); - segment.set(JAVA_LONG, 16, z); - segment.set(JAVA_LONG, 24, w); - return segment; - } - - /** - * Vararg version of {@link #malloc}. - * - * @param values the values - * @return the segment - */ - public MemorySegment longs(long... values) { - return allocateFrom(JAVA_LONG, values); - } - - // ------------------------------------------------- - - /** - * Single value version of {@link #malloc}. - * - * @param x the first value - * @return the segment - */ - public MemorySegment floats(float x) { - return allocateFrom(JAVA_FLOAT, x); - } - - /** - * Two value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @return the segment - */ - public MemorySegment floats(float x, float y) { - final var segment = malloc(8, JAVA_FLOAT); - segment.set(JAVA_FLOAT, 0, x); - segment.set(JAVA_FLOAT, 4, y); - return segment; - } - - /** - * Three value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @return the segment - */ - public MemorySegment floats(float x, float y, float z) { - final var segment = malloc(12, JAVA_FLOAT); - segment.set(JAVA_FLOAT, 0, x); - segment.set(JAVA_FLOAT, 4, y); - segment.set(JAVA_FLOAT, 8, z); - return segment; - } - - /** - * Four value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @param w the fourth value - * @return the segment - */ - public MemorySegment floats(float x, float y, float z, float w) { - final var segment = malloc(16, JAVA_FLOAT); - segment.set(JAVA_FLOAT, 0, x); - segment.set(JAVA_FLOAT, 4, y); - segment.set(JAVA_FLOAT, 8, z); - segment.set(JAVA_FLOAT, 12, w); - return segment; - } - - /** - * Vararg version of {@link #malloc}. - * - * @param values the values - * @return the segment - */ - public MemorySegment floats(float... values) { - return allocateFrom(JAVA_FLOAT, values); - } - - // ------------------------------------------------- - - /** - * Single value version of {@link #malloc}. - * - * @param x the first value - * @return the segment - */ - public MemorySegment doubles(double x) { - return allocateFrom(JAVA_DOUBLE, x); - } - - /** - * Two value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @return the segment - */ - public MemorySegment doubles(double x, double y) { - final var segment = malloc(16, JAVA_DOUBLE); - segment.set(JAVA_DOUBLE, 0, x); - segment.set(JAVA_DOUBLE, 8, y); - return segment; - } - - /** - * Three value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @return the segment - */ - public MemorySegment doubles(double x, double y, double z) { - final var segment = malloc(24, JAVA_DOUBLE); - segment.set(JAVA_DOUBLE, 0, x); - segment.set(JAVA_DOUBLE, 8, y); - segment.set(JAVA_DOUBLE, 16, z); - return segment; - } - - /** - * Four value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @param w the fourth value - * @return the segment - */ - public MemorySegment doubles(double x, double y, double z, double w) { - final var segment = malloc(32, JAVA_DOUBLE); - segment.set(JAVA_DOUBLE, 0, x); - segment.set(JAVA_DOUBLE, 8, y); - segment.set(JAVA_DOUBLE, 16, z); - segment.set(JAVA_DOUBLE, 24, w); - return segment; - } - - /** - * Vararg version of {@link #malloc}. - * - * @param values the values - * @return the segment - */ - public MemorySegment doubles(double... values) { - return allocateFrom(JAVA_DOUBLE, values); - } - - // ------------------------------------------------- - - /** - * Single value version of {@link #malloc}. - * - * @param x the first value - * @return the segment - */ - public MemorySegment segments(MemorySegment x) { - return allocateFrom(ADDRESS, x); - } - - /** - * Two value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @return the segment - */ - public MemorySegment segments(MemorySegment x, MemorySegment y) { - final var segment = malloc(ADDRESS.byteSize() * 2, ADDRESS); - segment.set(ADDRESS, 0, x); - segment.setAtIndex(ADDRESS, 1, y); - return segment; - } - - /** - * Three value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @return the segment - */ - public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z) { - final var segment = malloc(ADDRESS.byteSize() * 3, ADDRESS); - segment.set(ADDRESS, 0, x); - segment.setAtIndex(ADDRESS, 1, y); - segment.setAtIndex(ADDRESS, 2, z); - return segment; - } - - /** - * Four value version of {@link #malloc}. - * - * @param x the first value - * @param y the second value - * @param z the third value - * @param w the fourth value - * @return the segment - */ - public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z, MemorySegment w) { - final var segment = malloc(ADDRESS.byteSize() * 4, ADDRESS); - segment.set(ADDRESS, 0, x); - segment.setAtIndex(ADDRESS, 1, y); - segment.setAtIndex(ADDRESS, 2, z); - segment.setAtIndex(ADDRESS, 3, w); - return segment; - } - - /** - * Vararg version of {@link #malloc}. - * - * @param values the values - * @return the segment - */ - public MemorySegment segments(MemorySegment... values) { - final var segment = malloc(ADDRESS.byteSize() * values.length, ADDRESS); - for (int i = 0; i < values.length; i++) { - segment.setAtIndex(ADDRESS, i, values[i]); - } - return segment; - } - - // ----------------------------------------------------- - // ----------------------------------------------------- - // ----------------------------------------------------- - - /** - * {@return the stack of the current thread} - */ - public static MemoryStack stackGet() { - return TLS.get(); - } - - /** - * Calls {@link #push} on the stack of the current thread. - * - * @return the stack of the current thread. - */ - public static MemoryStack stackPush() { - return stackGet().push(); - } - - /** - * Calls {@link #pop} on the stack of the current thread. - * - * @return the stack of the current thread. - */ - public static MemoryStack stackPop() { - return stackGet().pop(); - } - - /** - * Stores the method that pushed a frame and checks if it is the same method when the frame is popped. - * - * @since 0.1.0 - */ - private static final class DebugMemoryStack extends MemoryStack { - private Object[] debugFrames; - - private DebugMemoryStack(MemorySegment address) { - super(address); - debugFrames = new Object[DEFAULT_STACK_FRAMES]; - } - - @Override - public MemoryStack push() { - if (frameIndex == debugFrames.length) { - frameOverflow(); - } - - debugFrames[frameIndex] = StackWalkUtil.stackWalkGetMethod(MemoryStack.class); - - return super.push(); - } - - private void frameOverflow() { - debugFrames = Arrays.copyOf(debugFrames, debugFrames.length * 3 / 2); - } - - @Override - public MemoryStack pop() { - Object pushed = debugFrames[frameIndex - 1]; - Object popped = StackWalkUtil.stackWalkCheckPop(MemoryStack.class, pushed); - if (popped != null) { - reportAsymmetricPop(pushed, popped); - } - - debugFrames[frameIndex - 1] = null; - return super.pop(); - } - - // No need to check pop in try-with-resources - @Override - public void close() { - debugFrames[frameIndex - 1] = null; - super.pop(); - } - - private static void reportAsymmetricPop(Object pushed, Object popped) { - Configurations.apiLog(String.format( - "[overrun.marshal] Asymmetric pop detected:\n\tPUSHED: %s\n\tPOPPED: %s\n\tTHREAD: %s\n", - pushed, - popped, - Thread.currentThread() - )); - } - } -} diff --git a/src/main/java/overrun/marshal/Unmarshal.java b/src/main/java/overrun/marshal/Unmarshal.java index 9452ff1..67fa835 100644 --- a/src/main/java/overrun/marshal/Unmarshal.java +++ b/src/main/java/overrun/marshal/Unmarshal.java @@ -570,4 +570,17 @@ public static void copy(MemorySegment src, String @Nullable [] dst, Charset char dst[i] = ((MemorySegment) vh_stringArray.get(src, (long) i)).getString(0L, charset); } } + + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ + public static void copy(MemorySegment src, MemorySegment @Nullable [] dst) { + if (isNullPointer(src) || dst == null) return; + for (int i = 0; i < dst.length; i++) { + dst[i] = ((MemorySegment) Marshal.vh_addressArray.get(src, (long) i)); + } + } } diff --git a/src/main/java/overrun/marshal/gen/Convert.java b/src/main/java/overrun/marshal/gen/Convert.java index fdf3ace..8147486 100644 --- a/src/main/java/overrun/marshal/gen/Convert.java +++ b/src/main/java/overrun/marshal/gen/Convert.java @@ -16,6 +16,8 @@ package overrun.marshal.gen; +import overrun.marshal.gen.processor.ProcessorType; + import java.lang.annotation.*; /** @@ -25,10 +27,10 @@ * The type of the marked element must be {@code boolean}; otherwise this annotation will be ignored. *

Example

*
{@code
- * @Convert(Type.INT)
+ * @Convert(ProcessorType.BoolConvert.INT)
  * boolean returnInt();
  *
- * void acceptInt(@Convert(Type.INT) boolean i);
+ * void acceptInt(@Convert(ProcessorType.BoolConvert.INT) boolean i);
  * }
* * @author squid233 @@ -41,5 +43,5 @@ /** * {@return the type to be converted} */ - Type value(); + ProcessorType.BoolConvert value(); } diff --git a/src/main/java/overrun/marshal/gen/Sized.java b/src/main/java/overrun/marshal/gen/Sized.java index d704bb8..146f270 100644 --- a/src/main/java/overrun/marshal/gen/Sized.java +++ b/src/main/java/overrun/marshal/gen/Sized.java @@ -16,7 +16,7 @@ package overrun.marshal.gen; -import overrun.marshal.Configurations; +import overrun.marshal.MarshalConfigs; import java.lang.annotation.*; @@ -30,7 +30,7 @@ * } * * @author squid233 - * @see Configurations#CHECK_ARRAY_SIZE + * @see MarshalConfigs#CHECK_ARRAY_SIZE * @since 0.1.0 */ @Documented diff --git a/src/main/java/overrun/marshal/gen/SizedSeg.java b/src/main/java/overrun/marshal/gen/SizedSeg.java index 012790f..55e496c 100644 --- a/src/main/java/overrun/marshal/gen/SizedSeg.java +++ b/src/main/java/overrun/marshal/gen/SizedSeg.java @@ -16,7 +16,7 @@ package overrun.marshal.gen; -import overrun.marshal.Configurations; +import overrun.marshal.MarshalConfigs; import java.lang.annotation.*; @@ -29,7 +29,7 @@ * } * * @author squid233 - * @see Configurations#CHECK_ARRAY_SIZE + * @see MarshalConfigs#CHECK_ARRAY_SIZE * @since 0.1.0 */ @Documented diff --git a/src/main/java/overrun/marshal/gen/Type.java b/src/main/java/overrun/marshal/gen/Type.java index 840d023..d695372 100644 --- a/src/main/java/overrun/marshal/gen/Type.java +++ b/src/main/java/overrun/marshal/gen/Type.java @@ -16,6 +16,8 @@ package overrun.marshal.gen; +import overrun.marshal.gen.processor.ProcessorType; + import java.lang.constant.ClassDesc; import java.lang.foreign.ValueLayout; @@ -31,38 +33,40 @@ public enum Type { /** * {@code char} type */ - CHAR(CD_char, ValueLayout.JAVA_CHAR), + CHAR(CD_char, ValueLayout.JAVA_CHAR, ProcessorType.Value.CHAR), /** * {@code byte} type */ - BYTE(CD_byte, ValueLayout.JAVA_BYTE), + BYTE(CD_byte, ValueLayout.JAVA_BYTE, ProcessorType.Value.BYTE), /** * {@code short} type */ - SHORT(CD_short, ValueLayout.JAVA_SHORT), + SHORT(CD_short, ValueLayout.JAVA_SHORT, ProcessorType.Value.SHORT), /** * {@code int} type */ - INT(CD_int, ValueLayout.JAVA_INT), + INT(CD_int, ValueLayout.JAVA_INT, ProcessorType.Value.INT), /** * {@code long} type */ - LONG(CD_long, ValueLayout.JAVA_LONG), + LONG(CD_long, ValueLayout.JAVA_LONG, ProcessorType.Value.LONG), /** * {@code float} type */ - FLOAT(CD_float, ValueLayout.JAVA_FLOAT), + FLOAT(CD_float, ValueLayout.JAVA_FLOAT, ProcessorType.Value.FLOAT), /** * {@code double} type */ - DOUBLE(CD_double, ValueLayout.JAVA_DOUBLE); + DOUBLE(CD_double, ValueLayout.JAVA_DOUBLE, ProcessorType.Value.DOUBLE); private final ClassDesc classDesc; private final ValueLayout layout; + private final ProcessorType.Value processorType; - Type(ClassDesc classDesc, ValueLayout layout) { + Type(ClassDesc classDesc, ValueLayout layout, ProcessorType.Value processorType) { this.classDesc = classDesc; this.layout = layout; + this.processorType = processorType; } /** @@ -78,4 +82,11 @@ public ClassDesc classDesc() { public ValueLayout layout() { return layout; } + + /** + * {@return the processor type of this type} + */ + public ProcessorType.Value processorType() { + return processorType; + } } diff --git a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java new file mode 100644 index 0000000..edb5446 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java @@ -0,0 +1,85 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.internal.StringCharset; + +import java.lang.classfile.CodeBuilder; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Map; + +import static overrun.marshal.internal.Constants.*; + +/** + * insert code after invoke + * + * @author squid233 + * @since 0.1.0 + */ +public final class AfterInvokeProcessor extends BaseProcessor { + public record Context(List parameters, Map refSlotMap) { + } + + @Override + public boolean process(CodeBuilder builder, Context context) { + List parameters = context.parameters(); + var refSlotMap = context.refSlotMap(); + for (int i = 0, size = parameters.size(); i < size; i++) { + Parameter parameter = parameters.get(i); + if (refSlotMap.containsKey(parameter)) { + ProcessorType type = ProcessorTypes.fromParameter(parameter); + // TODO: ref processor + if (type instanceof ProcessorType.Array array) { + int parameterSlot = builder.parameterSlot(i); + int refSlot = refSlotMap.get(parameter); + builder + .aload(refSlot) + .aload(parameterSlot) + .invokestatic(CD_Unmarshal, + "copy", + switch (array.componentType()) { + case ProcessorType.Str _ -> + StringCharset.getCharset(builder, StringCharset.getCharset(parameter)) ? + MTD_void_MemorySegment_StringArray_Charset : + MTD_void_MemorySegment_StringArray; + case ProcessorType.Value value -> switch (value) { + case BOOLEAN -> MTD_void_MemorySegment_booleanArray; + case CHAR -> MTD_void_MemorySegment_charArray; + case BYTE -> MTD_void_MemorySegment_byteArray; + case SHORT -> MTD_void_MemorySegment_shortArray; + case INT -> MTD_void_MemorySegment_intArray; + case LONG -> MTD_void_MemorySegment_longArray; + case FLOAT -> MTD_void_MemorySegment_floatArray; + case DOUBLE -> MTD_void_MemorySegment_doubleArray; + case ADDRESS -> MTD_void_MemorySegment_MemorySegmentArray; + }; + default -> throw new IllegalStateException("Unexpected value: " + array.componentType()); + }); + } + } + } + return super.process(builder, context); + } + + public static AfterInvokeProcessor getInstance() { + class Holder { + static final AfterInvokeProcessor INSTANCE = new AfterInvokeProcessor(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java b/src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java new file mode 100644 index 0000000..2f1b64e --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +/** + * allocator requirement + * + * @author squid233 + * @since 0.1.0 + */ +public enum AllocatorRequirement { + /** + * No allocator required + */ + NONE(0), + /** + * No allocator required; memory stack can be implicitly used + */ + STACK(1), + /** + * {@link java.lang.foreign.SegmentAllocator SegmentAllocator} required + */ + ALLOCATOR(2), + /** + * {@link java.lang.foreign.Arena Arena} required + */ + ARENA(3); + + private final int level; + + AllocatorRequirement(int level) { + this.level = level; + } + + public static AllocatorRequirement stricter(AllocatorRequirement first, AllocatorRequirement second) { + return first.isStricter(second) ? first : second; + } + + public boolean isStricter(AllocatorRequirement other) { + return this.level > other.level; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java b/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java deleted file mode 100644 index bb30498..0000000 --- a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen.processor; - -import overrun.marshal.gen.Type; -import overrun.marshal.internal.StringCharset; - -import java.lang.classfile.CodeBuilder; -import java.lang.constant.MethodTypeDesc; -import java.util.ArrayList; -import java.util.List; - -import static java.lang.constant.ConstantDescs.CD_boolean; -import static overrun.marshal.internal.Constants.*; - -/** - * Method argument processor - * - * @author squid233 - * @since 0.1.0 - */ -public final class ArgumentProcessor implements Processor { - private static final ArgumentProcessor INSTANCE = new ArgumentProcessor(); - private final List> list = new ArrayList<>(0); - - private ArgumentProcessor() { - } - - @SuppressWarnings("preview") - public boolean process(CodeBuilder builder, ProcessorType type, ArgumentProcessorContext context) { - switch (type) { - case ProcessorType.Value value -> { - if (value == ProcessorType.Value.BOOLEAN && - context.convert() != null) { - final Type convertType = context.convert().value(); - builder.loadLocal( - value.typeKind(), - context.parameterSlot() - ).invokestatic(CD_Marshal, - marshalFromBooleanMethod(convertType), - MethodTypeDesc.of(convertType.classDesc(), CD_boolean)); - } else { - builder.loadLocal( - value.typeKind().asLoadable(), - context.parameterSlot() - ); - } - } - case ProcessorType.Allocator _ -> builder.aload(context.parameterSlot()); - case ProcessorType.Str _ -> { - builder.aload(context.allocatorSlot()) - .aload(context.parameterSlot()); - if (StringCharset.getCharset(builder, context.parameter())) { - builder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_String_Charset); - } else { - builder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_String); - } - } - case ProcessorType.Addr _ -> builder - .aload(context.parameterSlot()) - .invokestatic(CD_Marshal, - "marshal", - MethodTypeDesc.of(type.downcallClassDesc(), type.marshalClassDesc())); - case ProcessorType.Upcall _ -> builder - .aload(context.allocatorSlot()) - .checkcast(CD_Arena) - .aload(context.parameterSlot()) - .invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_Arena_Upcall); - case ProcessorType.Array array -> { - final ProcessorType componentType = array.componentType(); - final boolean isStringArray = componentType instanceof ProcessorType.Str; - final boolean isUpcallArray = componentType instanceof ProcessorType.Upcall; - builder.aload(context.allocatorSlot()); - if (isUpcallArray) { - builder.checkcast(CD_Arena); - } - builder.aload(context.parameterSlot()); - if (isStringArray && StringCharset.getCharset(builder, context.parameter())) { - builder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_StringArray_Charset); - } else { - builder.invokestatic(CD_Marshal, - "marshal", - MethodTypeDesc.of(CD_MemorySegment, - isUpcallArray ? CD_Arena : CD_SegmentAllocator, - array.marshalClassDesc())); - } - } - default -> { - for (var processor : list) { - if (!processor.process(builder, type, context)) { - break; - } - } - } - } - return false; - } - - /** - * Registers a processor - * - * @param processor the processor - */ - public void registerProcessor(Processor processor) { - list.add(processor); - } - - /** - * {@return this} - */ - public static ArgumentProcessor getInstance() { - return INSTANCE; - } - - private static String marshalFromBooleanMethod(Type convertType) { - return switch (convertType) { - case CHAR -> "marshalAsChar"; - case BYTE -> "marshalAsByte"; - case SHORT -> "marshalAsShort"; - case INT -> "marshalAsInt"; - case LONG -> "marshalAsLong"; - case FLOAT -> "marshalAsFloat"; - case DOUBLE -> "marshalAsDouble"; - }; - } -} diff --git a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessorContext.java b/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java similarity index 52% rename from src/main/java/overrun/marshal/gen/processor/ArgumentProcessorContext.java rename to src/main/java/overrun/marshal/gen/processor/BaseProcessor.java index 6fc4f25..cd225a0 100644 --- a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessorContext.java +++ b/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java @@ -16,24 +16,34 @@ package overrun.marshal.gen.processor; -import overrun.marshal.gen.Convert; - -import java.lang.reflect.Parameter; +import java.lang.classfile.CodeBuilder; +import java.util.ArrayList; +import java.util.List; /** - * The argument processor context + * A processor with a list of processors * - * @param parameter parameter - * @param parameterSlot parameter slot - * @param allocatorSlot allocator slot - * @param convert boolean convert + * @param context type * @author squid233 * @since 0.1.0 */ -public record ArgumentProcessorContext( - Parameter parameter, - int parameterSlot, - int allocatorSlot, - Convert convert -) { +public abstract class BaseProcessor implements Processor { + /** + * processors + */ + protected final List> processors = new ArrayList<>(); + + @Override + public boolean process(CodeBuilder builder, C context) { + for (var processor : processors) { + if (!processor.process(builder, context)) { + return false; + } + } + return true; + } + + public void addProcessor(Processor processor) { + processors.add(processor); + } } diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java new file mode 100644 index 0000000..40623ed --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java @@ -0,0 +1,64 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.gen.Ref; +import overrun.marshal.internal.StringCharset; + +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.TypeKind; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.Map; + +/** + * insert codes before invoke + * + * @author squid233 + * @since 0.1.0 + */ +public final class BeforeInvokeProcessor extends BaseProcessor { + public record Context(List parameters, Map refSlot, int allocatorSlot) { + } + + @Override + public boolean process(CodeBuilder builder, Context context) { + List parameters = context.parameters(); + for (int i = 0, size = parameters.size(); i < size; i++) { + Parameter parameter = parameters.get(i); + if (parameter.getType().isArray() && + parameter.getDeclaredAnnotation(Ref.class) != null) { + int local = builder.allocateLocal(TypeKind.ReferenceType); + context.refSlot().put(parameter, local); + MarshalProcessor.getInstance().process(builder, + new MarshalProcessor.Context(ProcessorTypes.fromParameter(parameter), + StringCharset.getCharset(parameter), + builder.parameterSlot(i), + context.allocatorSlot())); + builder.astore(local); + } + } + return super.process(builder, context); + } + + public static BeforeInvokeProcessor getInstance() { + class Holder { + static final BeforeInvokeProcessor INSTANCE = new BeforeInvokeProcessor(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java new file mode 100644 index 0000000..1d0334f --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java @@ -0,0 +1,51 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import java.lang.classfile.CodeBuilder; + +import static java.lang.constant.ConstantDescs.MTD_void; +import static overrun.marshal.internal.Constants.CD_MemoryStack; + +/** + * insert code before return + * + * @author squid233 + * @since 0.1.0 + */ +public final class BeforeReturnProcessor extends BaseProcessor { + public record Context(boolean hasMemoryStack) { + } + + @Override + public boolean process(CodeBuilder builder, Context context) { + if (context.hasMemoryStack()) { + builder.invokestatic(CD_MemoryStack, + "popLocal", + MTD_void, + true); + } + return super.process(builder, context); + } + + public static BeforeReturnProcessor getInstance() { + class Holder { + static final BeforeReturnProcessor INSTANCE = new BeforeReturnProcessor(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java new file mode 100644 index 0000000..01bb7e9 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java @@ -0,0 +1,65 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.gen.Sized; + +import java.lang.classfile.CodeBuilder; +import java.lang.reflect.Parameter; +import java.util.List; + +import static overrun.marshal.internal.Constants.CD_Checks; +import static overrun.marshal.internal.Constants.MTD_void_int_int; + +/** + * insert check codes + * + * @author squid233 + * @since 0.1.0 + */ +public final class CheckProcessor extends BaseProcessor { + public record Context(List parameters) { + } + + @Override + public boolean process(CodeBuilder builder, Context context) { + List parameters = context.parameters(); + for (int i = 0, size = parameters.size(); i < size; i++) { + Parameter parameter = parameters.get(i); + if (parameter.getType().isArray()) { + Sized sized = parameter.getDeclaredAnnotation(Sized.class); + if (sized != null) { + int slot = builder.parameterSlot(i); + builder.ldc(sized.value()) + .aload(slot) + .arraylength() + .invokestatic(CD_Checks, + "checkArraySize", + MTD_void_int_int); + } + } + } + return super.process(builder, context); + } + + public static CheckProcessor getInstance() { + class Holder { + static final CheckProcessor INSTANCE = new CheckProcessor(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java new file mode 100644 index 0000000..05f6e34 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java @@ -0,0 +1,145 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.internal.StringCharset; + +import java.lang.classfile.CodeBuilder; + +import static overrun.marshal.internal.Constants.*; + +/** + * insert marshal (Java-to-C) method + * + * @author squid233 + * @since 0.1.0 + */ +public final class MarshalProcessor extends BaseProcessor { + public record Context( + ProcessorType type, + String charset, + int variableSlot, + int allocatorSlot + ) { + } + + @SuppressWarnings("preview") + @Override + public boolean process(CodeBuilder builder, Context context) { + final int allocatorSlot = context.allocatorSlot(); + final int variableSlot = context.variableSlot(); + switch (context.type()) { + case ProcessorType.Allocator _ -> builder.aload(variableSlot); + case ProcessorType.Custom _ -> { + } + case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); + case ProcessorType.Array array -> { + switch (array.componentType()) { + case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _, + ProcessorType.Custom _ -> { + } + case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); + case ProcessorType.Str _ -> builder + .aload(allocatorSlot) + .aload(variableSlot) + .invokestatic(CD_Marshal, + "marshal", + StringCharset.getCharset(builder, context.charset()) ? + MTD_MemorySegment_SegmentAllocator_StringArray_Charset : + MTD_MemorySegment_SegmentAllocator_StringArray); + case ProcessorType.Struct _ -> builder + .aload(allocatorSlot) + .aload(variableSlot) + .invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_SegmentAllocator_StructArray); + case ProcessorType.Upcall _ -> builder + .aload(allocatorSlot) + .aload(variableSlot) + .invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_Arena_UpcallArray); + case ProcessorType.Value value -> builder + .aload(allocatorSlot) + .aload(variableSlot) + .invokestatic(CD_Marshal, + "marshal", + switch (value) { + case BOOLEAN -> MTD_MemorySegment_SegmentAllocator_booleanArray; + case CHAR -> MTD_MemorySegment_SegmentAllocator_charArray; + case BYTE -> MTD_MemorySegment_SegmentAllocator_byteArray; + case SHORT -> MTD_MemorySegment_SegmentAllocator_shortArray; + case INT -> MTD_MemorySegment_SegmentAllocator_intArray; + case LONG -> MTD_MemorySegment_SegmentAllocator_longArray; + case FLOAT -> MTD_MemorySegment_SegmentAllocator_floatArray; + case DOUBLE -> MTD_MemorySegment_SegmentAllocator_doubleArray; + case ADDRESS -> MTD_MemorySegment_SegmentAllocator_MemorySegmentArray; + }); + } + } + case ProcessorType.BoolConvert boolConvert -> builder + .iload(variableSlot) + .invokestatic(CD_Marshal, + switch (boolConvert) { + case CHAR -> "marshalAsChar"; + case BYTE -> "marshalAsByte"; + case SHORT -> "marshalAsShort"; + case INT -> "marshalAsInt"; + case LONG -> "marshalAsLong"; + case FLOAT -> "marshalAsFloat"; + case DOUBLE -> "marshalAsDouble"; + }, + switch (boolConvert) { + case CHAR -> MTD_char_boolean; + case BYTE -> MTD_byte_boolean; + case SHORT -> MTD_short_boolean; + case INT -> MTD_int_boolean; + case LONG -> MTD_long_boolean; + case FLOAT -> MTD_float_boolean; + case DOUBLE -> MTD_double_boolean; + }); + case ProcessorType.Str _ -> builder + .aload(allocatorSlot) + .aload(variableSlot) + .invokestatic(CD_Marshal, + "marshal", + StringCharset.getCharset(builder, context.charset()) ? + MTD_MemorySegment_SegmentAllocator_String_Charset : + MTD_MemorySegment_SegmentAllocator_String); + case ProcessorType.Struct _ -> builder + .aload(variableSlot) + .invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_Struct); + case ProcessorType.Upcall _ -> builder + .aload(allocatorSlot) + .aload(variableSlot) + .invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_Arena_Upcall); + case ProcessorType.Value value -> builder.loadLocal(value.typeKind(), variableSlot); + } + return super.process(builder, context); + } + + public static MarshalProcessor getInstance() { + class Holder { + static final MarshalProcessor INSTANCE = new MarshalProcessor(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/Processor.java index 567a7cd..5987f53 100644 --- a/src/main/java/overrun/marshal/gen/processor/Processor.java +++ b/src/main/java/overrun/marshal/gen/processor/Processor.java @@ -21,19 +21,17 @@ /** * Processor * - * @param target type * @param context type * @author squid233 * @since 0.1.0 */ -public interface Processor { +public interface Processor { /** * Processes with the context * * @param builder the code builder - * @param type the type * @param context the context * @return {@code true} if should continue; {@code false} otherwise */ - boolean process(CodeBuilder builder, T type, C context); + boolean process(CodeBuilder builder, C context); } diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java index 57bc454..d712b0d 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -16,16 +16,18 @@ package overrun.marshal.gen.processor; -import overrun.marshal.Addressable; +import overrun.marshal.struct.StructAllocatorSpec; import java.lang.classfile.TypeKind; import java.lang.constant.ClassDesc; import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentAllocator; import java.lang.foreign.ValueLayout; +import java.util.Objects; import static java.lang.constant.ConstantDescs.*; -import static overrun.marshal.internal.Constants.*; +import static overrun.marshal.internal.Constants.CD_MemorySegment; +import static overrun.marshal.internal.Constants.CD_SegmentAllocator; /** * Types to be processed @@ -35,14 +37,48 @@ */ public sealed interface ProcessorType { /** - * {@return the class desc for method handles} + * {@return the class desc for method handles (functions in C)} */ ClassDesc downcallClassDesc(); /** - * {@return the class desc for methods in {@link overrun.marshal.Marshal}} + * {@return the allocator requirement} */ - ClassDesc marshalClassDesc(); + AllocatorRequirement allocationRequirement(); + + /** + * Creates a struct processor type with the given type class and struct allocator. + * + * @param typeClass the type class + * @param allocatorSpec the struct allocator + * @return a new processor type + */ + static Struct struct(Class typeClass, StructAllocatorSpec allocatorSpec) { + return new Struct(typeClass, allocatorSpec); + } + + /** + * {@code void} type + */ + final class Void implements ProcessorType { + /** + * The instance + */ + public static final Void INSTANCE = new Void(); + + private Void() { + } + + @Override + public ClassDesc downcallClassDesc() { + return CD_void; + } + + @Override + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.NONE; + } + } /** * Primitive types, including {@link MemorySegment} @@ -96,12 +132,73 @@ enum Value implements ProcessorType { } /** - * {@return the class desc of this type} + * {@return the type kind of this type} */ - public ClassDesc classDesc() { + public TypeKind typeKind() { + return typeKind; + } + + /** + * {@return the layout of this type} + */ + public ValueLayout layout() { + return layout; + } + + @Override + public ClassDesc downcallClassDesc() { return classDesc; } + @Override + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.NONE; + } + } + + /** + * Primitive types that are convertible with {@code boolean}. + */ + enum BoolConvert implements ProcessorType { + /** + * {@code char} type + */ + CHAR(CD_char, ValueLayout.JAVA_CHAR), + /** + * {@code byte} type + */ + BYTE(CD_byte, ValueLayout.JAVA_BYTE), + /** + * {@code short} type + */ + SHORT(CD_short, ValueLayout.JAVA_SHORT), + /** + * {@code int} type + */ + INT(CD_int, ValueLayout.JAVA_INT), + /** + * {@code long} type + */ + LONG(CD_long, ValueLayout.JAVA_LONG), + /** + * {@code float} type + */ + FLOAT(CD_float, ValueLayout.JAVA_FLOAT), + /** + * {@code double} type + */ + DOUBLE(CD_double, ValueLayout.JAVA_DOUBLE); + + private final ClassDesc classDesc; + private final TypeKind typeKind; + private final ValueLayout layout; + + BoolConvert(ClassDesc classDesc, ValueLayout layout) { + this.classDesc = classDesc; + this.typeKind = TypeKind.from(classDesc); + this.layout = layout; + } + /** * {@return the type kind of this type} */ @@ -118,12 +215,12 @@ public ValueLayout layout() { @Override public ClassDesc downcallClassDesc() { - return classDesc(); + return classDesc; } @Override - public ClassDesc marshalClassDesc() { - return classDesc(); + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.NONE; } } @@ -145,8 +242,8 @@ public ClassDesc downcallClassDesc() { } @Override - public ClassDesc marshalClassDesc() { - return CD_SegmentAllocator; + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.NONE; // this is invalid } } @@ -168,21 +265,33 @@ public ClassDesc downcallClassDesc() { } @Override - public ClassDesc marshalClassDesc() { - return CD_String; + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.STACK; } } /** - * {@link Addressable} + * {@link overrun.marshal.struct.Struct Struct} */ - final class Addr implements ProcessorType { - /** - * The instance - */ - public static final Addr INSTANCE = new Addr(); + final class Struct implements ProcessorType { + private final Class typeClass; + private final StructAllocatorSpec allocatorSpec; + + private Struct(Class typeClass, StructAllocatorSpec allocatorSpec) { + this.typeClass = typeClass; + this.allocatorSpec = allocatorSpec; + } + + public Class typeClass() { + return typeClass; + } + + public StructAllocatorSpec allocatorSpec() { + return allocatorSpec; + } - private Addr() { + public void checkAllocator() { + Objects.requireNonNull(allocatorSpec); } @Override @@ -191,13 +300,13 @@ public ClassDesc downcallClassDesc() { } @Override - public ClassDesc marshalClassDesc() { - return CD_Addressable; + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.NONE; } } /** - * {@link overrun.marshal.Upcall} + * {@link overrun.marshal.Upcall Upcall} */ final class Upcall implements ProcessorType { /** @@ -214,8 +323,8 @@ public ClassDesc downcallClassDesc() { } @Override - public ClassDesc marshalClassDesc() { - return CD_Upcall; + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.ARENA; } } @@ -231,8 +340,8 @@ public ClassDesc downcallClassDesc() { } @Override - public ClassDesc marshalClassDesc() { - return componentType().marshalClassDesc().arrayType(); + public AllocatorRequirement allocationRequirement() { + return AllocatorRequirement.stricter(AllocatorRequirement.STACK, componentType.allocationRequirement()); } } diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java index c52931e..9d8b3d7 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java @@ -16,14 +16,19 @@ package overrun.marshal.gen.processor; -import overrun.marshal.Addressable; import overrun.marshal.Upcall; +import overrun.marshal.gen.Convert; +import overrun.marshal.struct.Struct; +import overrun.marshal.struct.StructAllocatorSpec; import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentAllocator; -import java.util.HashMap; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.LinkedHashMap; import java.util.Map; -import java.util.Objects; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; /** * Processor types @@ -32,7 +37,24 @@ * @since 0.1.0 */ public final class ProcessorTypes { - private static final Map, ProcessorType> map = new HashMap<>(0); + private static final Map, ProcessorType> map = new LinkedHashMap<>(); + + static { + register(void.class, ProcessorType.Void.INSTANCE); + register(boolean.class, ProcessorType.Value.BOOLEAN); + register(char.class, ProcessorType.Value.CHAR); + register(byte.class, ProcessorType.Value.BYTE); + register(short.class, ProcessorType.Value.SHORT); + register(int.class, ProcessorType.Value.INT); + register(long.class, ProcessorType.Value.LONG); + register(float.class, ProcessorType.Value.FLOAT); + register(double.class, ProcessorType.Value.DOUBLE); + register(MemorySegment.class, ProcessorType.Value.ADDRESS); + register(String.class, ProcessorType.Str.INSTANCE); + register(SegmentAllocator.class, ProcessorType.Allocator.INSTANCE); + registerStruct(Struct.class, null); + register(Upcall.class, ProcessorType.Upcall.INSTANCE); + } private ProcessorTypes() { } @@ -44,21 +66,55 @@ private ProcessorTypes() { * @return the processor type */ public static ProcessorType fromClass(Class aClass) { - if (aClass == boolean.class) return ProcessorType.Value.BOOLEAN; - if (aClass == char.class) return ProcessorType.Value.CHAR; - if (aClass == byte.class) return ProcessorType.Value.BYTE; - if (aClass == short.class) return ProcessorType.Value.SHORT; - if (aClass == int.class) return ProcessorType.Value.INT; - if (aClass == long.class) return ProcessorType.Value.LONG; - if (aClass == float.class) return ProcessorType.Value.FLOAT; - if (aClass == double.class) return ProcessorType.Value.DOUBLE; - if (aClass == MemorySegment.class) return ProcessorType.Value.ADDRESS; - if (aClass == String.class) return ProcessorType.Str.INSTANCE; - if (SegmentAllocator.class.isAssignableFrom(aClass)) return ProcessorType.Allocator.INSTANCE; - if (Addressable.class.isAssignableFrom(aClass)) return ProcessorType.Addr.INSTANCE; - if (Upcall.class.isAssignableFrom(aClass)) return ProcessorType.Upcall.INSTANCE; if (aClass.isArray()) return new ProcessorType.Array(fromClass(aClass.componentType())); - return Objects.requireNonNull(map.get(aClass), "Cannot find processor type of " + aClass); + if (map.containsKey(aClass)) { + return map.get(aClass); + } + ProcessorType candidate = null; + for (var entry : map.entrySet()) { + if (entry.getKey().isAssignableFrom(aClass)) { + candidate = entry.getValue(); + } + } + if (candidate != null) { + return candidate; + } + throw new NoSuchElementException("Cannot find processor type of " + aClass); + } + + /** + * Gets the processor type from the given method. + * + * @param method the method + * @return the processor type + */ + public static ProcessorType fromMethod(Method method) { + Class returnType = method.getReturnType(); + if (returnType == boolean.class) { + Convert convert = method.getDeclaredAnnotation(Convert.class); + if (convert != null) { + return convert.value(); + } + } + return fromClass(returnType); + } + + /** + * Gets the processor type from the given parameter. + * + * @param parameter the parameter + * @return the processor type + */ + public static ProcessorType fromParameter(Parameter parameter) { + Class type = parameter.getType(); + if (type == boolean.class) { + Convert convert = parameter.getDeclaredAnnotation(Convert.class); + if (convert != null) { + return convert.value(); + } + } + // TODO: ref processor + return fromClass(type); } /** @@ -67,11 +123,48 @@ public static ProcessorType fromClass(Class aClass) { * @param aClass the class * @param type the processor type */ - public static void registerClass(Class aClass, ProcessorType type) { + public static void register(Class aClass, ProcessorType type) { if (type != null) { map.put(aClass, type); } else { map.remove(aClass); } } + + /** + * Registers a processor type for the given struct class. + * + * @param aClass the class + * @param type the processor type + */ + public static void registerStruct(Class aClass, StructAllocatorSpec type) { + register(aClass, ProcessorType.struct(aClass, type)); + } + + /** + * {@return {@code true} if the given class is registered} + * For an array type, returns {@code true} if its component type is registered. + * + * @param aClass the class + */ + public static boolean isRegistered(Class aClass) { + if (aClass.isArray()) return isRegistered(aClass.componentType()); + for (Class k : map.keySet()) { + if (k.isAssignableFrom(aClass)) { + return true; + } + } + return false; + } + + @SuppressWarnings("unchecked") + public static Map, T> collect(Class instanceType) { + return map.entrySet() + .stream() + .filter(e -> instanceType.isInstance(e.getValue())) + .collect(Collectors.toUnmodifiableMap( + Map.Entry::getKey, + entry -> (T) entry.getValue() + )); + } } diff --git a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java new file mode 100644 index 0000000..17b7706 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java @@ -0,0 +1,131 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.internal.StringCharset; + +import java.lang.classfile.CodeBuilder; +import java.lang.constant.ClassDesc; + +import static java.lang.constant.ConstantDescs.CD_Map; +import static java.lang.constant.ConstantDescs.MTD_void; +import static overrun.marshal.internal.Constants.*; + +/** + * insert unmarshal (C-to-Java) method + * + * @author squid233 + * @since 0.1.0 + */ +public final class UnmarshalProcessor extends BaseProcessor { + public record Context(ProcessorType type, String charset, int variableSlot) { + } + + @SuppressWarnings("preview") + @Override + public boolean process(CodeBuilder builder, Context context) { + int variableSlot = context.variableSlot(); + switch (context.type()) { + case ProcessorType.Allocator _, ProcessorType.Custom _, ProcessorType.Void _ -> { + } + case ProcessorType.Array array -> { + switch (array.componentType()) { + case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _, + ProcessorType.Custom _, ProcessorType.Struct _, ProcessorType.Upcall _ -> { + } + case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); + case ProcessorType.Str _ -> builder + .aload(variableSlot) + .invokestatic(CD_Unmarshal, + "unmarshalAsStringArray", + StringCharset.getCharset(builder, context.charset()) ? + MTD_StringArray_MemorySegment_Charset : + MTD_StringArray_MemorySegment); + case ProcessorType.Value value -> builder + .aload(variableSlot) + .invokestatic(CD_Unmarshal, + switch (value) { + case BOOLEAN -> "unmarshalAsBooleanArray"; + case CHAR -> "unmarshalAsCharArray"; + case BYTE -> "unmarshalAsByteArray"; + case SHORT -> "unmarshalAsShortArray"; + case INT -> "unmarshalAsIntArray"; + case LONG -> "unmarshalAsLongArray"; + case FLOAT -> "unmarshalAsFloatArray"; + case DOUBLE -> "unmarshalAsDoubleArray"; + case ADDRESS -> "unmarshalAsAddressArray"; + }, + switch (value) { + case BOOLEAN -> MTD_booleanArray_MemorySegment; + case CHAR -> MTD_charArray_MemorySegment; + case BYTE -> MTD_byteArray_MemorySegment; + case SHORT -> MTD_shortArray_MemorySegment; + case INT -> MTD_intArray_MemorySegment; + case LONG -> MTD_longArray_MemorySegment; + case FLOAT -> MTD_floatArray_MemorySegment; + case DOUBLE -> MTD_doubleArray_MemorySegment; + case ADDRESS -> MTD_MemorySegmentArray_MemorySegment; + }); + } + } + case ProcessorType.BoolConvert boolConvert -> builder + .loadLocal(boolConvert.typeKind(), variableSlot) + .invokestatic(CD_Unmarshal, + "unmarshalAsBoolean", + switch (boolConvert) { + case CHAR -> MTD_boolean_char; + case BYTE -> MTD_boolean_byte; + case SHORT -> MTD_boolean_short; + case INT -> MTD_boolean_int; + case LONG -> MTD_boolean_long; + case FLOAT -> MTD_boolean_float; + case DOUBLE -> MTD_boolean_double; + }); + case ProcessorType.Str _ -> builder + .aload(variableSlot) + .invokestatic(CD_Unmarshal, + "unboundString", + StringCharset.getCharset(builder, context.charset()) ? + MTD_String_MemorySegment_Charset : + MTD_String_MemorySegment); + case ProcessorType.Struct struct -> builder + .ldc(DCD_classData_DowncallData) + .invokevirtual(CD_DowncallData, "structTypeMap", MTD_Map) + .ldc(ClassDesc.ofDescriptor(struct.typeClass().descriptorString())) + .invokeinterface(CD_Map, "get", MTD_Object_Object) + .checkcast(CD_ProcessorType$Struct) + .dup() + .invokevirtual(CD_ProcessorType$Struct, "checkAllocator", MTD_void) + .invokevirtual(CD_ProcessorType$Struct, "allocatorSpec", MTD_StructAllocatorSpec) + .aload(variableSlot) + .invokeinterface(CD_StructAllocatorSpec, "of", MTD_Object_MemorySegment); + case ProcessorType.Upcall upcall -> { + // TODO: 2024/8/24 squid233: return upcall + builder.aconst_null(); + } + case ProcessorType.Value value -> builder.loadLocal(value.typeKind(), variableSlot); + } + return super.process(builder, context); + } + + public static UnmarshalProcessor getInstance() { + class Holder { + static final UnmarshalProcessor INSTANCE = new UnmarshalProcessor(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/internal/Constants.java b/src/main/java/overrun/marshal/internal/Constants.java index fee3a23..c0e24b0 100644 --- a/src/main/java/overrun/marshal/internal/Constants.java +++ b/src/main/java/overrun/marshal/internal/Constants.java @@ -29,10 +29,8 @@ * @since 0.1.0 */ public final class Constants { - /** - * CD_Addressable - */ - public static final ClassDesc CD_Addressable = ClassDesc.of("overrun.marshal.Addressable"); + //TODO + public static final boolean DEBUG = true; /** * CD_Arena */ @@ -58,9 +56,9 @@ public final class Constants { */ public static final ClassDesc CD_Marshal = ClassDesc.of("overrun.marshal.Marshal"); /** - * CD_MemoryLayout_PathElement + * CD_MemoryLayout$PathElement */ - public static final ClassDesc CD_MemoryLayout_PathElement = ClassDesc.of("java.lang.foreign.MemoryLayout$PathElement"); + public static final ClassDesc CD_MemoryLayout$PathElement = ClassDesc.of("java.lang.foreign.MemoryLayout$PathElement"); /** * CD_MemorySegment */ @@ -68,8 +66,11 @@ public final class Constants { /** * CD_MemoryStack */ - @Deprecated - public static final ClassDesc CD_MemoryStack = ClassDesc.of("overrun.marshal.MemoryStack"); + public static final ClassDesc CD_MemoryStack = ClassDesc.of("io.github.overrun.memstack.MemoryStack"); + /** + * CD_ProcessorType$Struct + */ + public static final ClassDesc CD_ProcessorType$Struct = ClassDesc.of("overrun.marshal.gen.processor.ProcessorType$Struct"); /** * CD_SegmentAllocator */ @@ -79,9 +80,13 @@ public final class Constants { */ public static final ClassDesc CD_StandardCharsets = ClassDesc.of("java.nio.charset.StandardCharsets"); /** - * CD_StructAllocator + * CD_Struct */ - public static final ClassDesc CD_StructAllocator = ClassDesc.of("overrun.marshal.struct.StructAllocator"); + public static final ClassDesc CD_Struct = ClassDesc.of("overrun.marshal.struct.Struct"); + /** + * CD_StructAllocatorSpec + */ + public static final ClassDesc CD_StructAllocatorSpec = ClassDesc.of("overrun.marshal.struct.StructAllocatorSpec"); /** * CD_StructLayout */ @@ -98,11 +103,99 @@ public final class Constants { * CD_Upcall */ public static final ClassDesc CD_Upcall = ClassDesc.of("overrun.marshal.Upcall"); + /** - * CD_StringArray + * MTD_boolean_char */ - public static final ClassDesc CD_StringArray = CD_String.arrayType(); - + public static final MethodTypeDesc MTD_boolean_char = MethodTypeDesc.of(CD_boolean, CD_char); + /** + * MTD_boolean_byte + */ + public static final MethodTypeDesc MTD_boolean_byte = MethodTypeDesc.of(CD_boolean, CD_byte); + /** + * MTD_boolean_short + */ + public static final MethodTypeDesc MTD_boolean_short = MethodTypeDesc.of(CD_boolean, CD_short); + /** + * MTD_boolean_int + */ + public static final MethodTypeDesc MTD_boolean_int = MethodTypeDesc.of(CD_boolean, CD_int); + /** + * MTD_boolean_long + */ + public static final MethodTypeDesc MTD_boolean_long = MethodTypeDesc.of(CD_boolean, CD_long); + /** + * MTD_boolean_float + */ + public static final MethodTypeDesc MTD_boolean_float = MethodTypeDesc.of(CD_boolean, CD_float); + /** + * MTD_boolean_double + */ + public static final MethodTypeDesc MTD_boolean_double = MethodTypeDesc.of(CD_boolean, CD_double); + /** + * MTD_char_boolean + */ + public static final MethodTypeDesc MTD_char_boolean = MethodTypeDesc.of(CD_char, CD_boolean); + /** + * MTD_byte_boolean + */ + public static final MethodTypeDesc MTD_byte_boolean = MethodTypeDesc.of(CD_byte, CD_boolean); + /** + * MTD_short_boolean + */ + public static final MethodTypeDesc MTD_short_boolean = MethodTypeDesc.of(CD_short, CD_boolean); + /** + * MTD_int_boolean + */ + public static final MethodTypeDesc MTD_int_boolean = MethodTypeDesc.of(CD_int, CD_boolean); + /** + * MTD_long_boolean + */ + public static final MethodTypeDesc MTD_long_boolean = MethodTypeDesc.of(CD_long, CD_boolean); + /** + * MTD_float_boolean + */ + public static final MethodTypeDesc MTD_float_boolean = MethodTypeDesc.of(CD_float, CD_boolean); + /** + * MTD_double_boolean + */ + public static final MethodTypeDesc MTD_double_boolean = MethodTypeDesc.of(CD_double, CD_boolean); + /** + * MTD_booleanArray_MemorySegment + */ + public static final MethodTypeDesc MTD_booleanArray_MemorySegment = MethodTypeDesc.of(CD_boolean.arrayType(), CD_MemorySegment); + /** + * MTD_charArray_MemorySegment + */ + public static final MethodTypeDesc MTD_charArray_MemorySegment = MethodTypeDesc.of(CD_boolean.arrayType(), CD_MemorySegment); + /** + * MTD_byteArray_MemorySegment + */ + public static final MethodTypeDesc MTD_byteArray_MemorySegment = MethodTypeDesc.of(CD_byte.arrayType(), CD_MemorySegment); + /** + * MTD_shortArray_MemorySegment + */ + public static final MethodTypeDesc MTD_shortArray_MemorySegment = MethodTypeDesc.of(CD_short.arrayType(), CD_MemorySegment); + /** + * MTD_intArray_MemorySegment + */ + public static final MethodTypeDesc MTD_intArray_MemorySegment = MethodTypeDesc.of(CD_int.arrayType(), CD_MemorySegment); + /** + * MTD_longArray_MemorySegment + */ + public static final MethodTypeDesc MTD_longArray_MemorySegment = MethodTypeDesc.of(CD_long.arrayType(), CD_MemorySegment); + /** + * MTD_floatArray_MemorySegment + */ + public static final MethodTypeDesc MTD_floatArray_MemorySegment = MethodTypeDesc.of(CD_float.arrayType(), CD_MemorySegment); + /** + * MTD_doubleArray_MemorySegment + */ + public static final MethodTypeDesc MTD_doubleArray_MemorySegment = MethodTypeDesc.of(CD_double.arrayType(), CD_MemorySegment); + /** + * MTD_MemorySegmentArray_MemorySegment + */ + public static final MethodTypeDesc MTD_MemorySegmentArray_MemorySegment = MethodTypeDesc.of(CD_MemorySegment.arrayType(), CD_MemorySegment); /** * MTD_Charset_String */ @@ -120,13 +213,13 @@ public final class Constants { */ public static final MethodTypeDesc MTD_Map = MethodTypeDesc.of(CD_Map); /** - * MTD_MemoryLayout_PathElement + * MTD_MemoryLayout$PathElement */ - public static final MethodTypeDesc MTD_MemoryLayout_PathElement = MethodTypeDesc.of(CD_MemoryLayout_PathElement); + public static final MethodTypeDesc MTD_MemoryLayout$PathElement = MethodTypeDesc.of(CD_MemoryLayout$PathElement); /** - * MTD_MemoryLayout_PathElement_String + * MTD_MemoryLayout$PathElement_String */ - public static final MethodTypeDesc MTD_MemoryLayout_PathElement_String = MethodTypeDesc.of(CD_MemoryLayout_PathElement, CD_String); + public static final MethodTypeDesc MTD_MemoryLayout$PathElement_String = MethodTypeDesc.of(CD_MemoryLayout$PathElement, CD_String); /** * MTD_MemorySegment */ @@ -135,10 +228,50 @@ public final class Constants { * MTD_MemorySegment_Arena_Upcall */ public static final MethodTypeDesc MTD_MemorySegment_Arena_Upcall = MethodTypeDesc.of(CD_MemorySegment, CD_Arena, CD_Upcall); + /** + * MTD_MemorySegment_Arena_UpcallArray + */ + public static final MethodTypeDesc MTD_MemorySegment_Arena_UpcallArray = MethodTypeDesc.of(CD_MemorySegment, CD_Arena, CD_Upcall.arrayType()); /** * MTD_MemorySegment_long_long_long */ public static final MethodTypeDesc MTD_MemorySegment_long_long_long = MethodTypeDesc.of(CD_MemorySegment, CD_long, CD_long, CD_long); + /** + * MTD_MemorySegment_SegmentAllocator_booleanArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_booleanArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_boolean.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_charArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_charArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_char.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_byteArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_byteArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_byte.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_shortArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_shortArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_short.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_intArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_intArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_int.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_longArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_longArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_long.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_floatArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_floatArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_float.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_doubleArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_doubleArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_double.arrayType()); + /** + * MTD_MemorySegment_SegmentAllocator_MemorySegmentArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_MemorySegmentArray = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, CD_MemorySegment.arrayType()); /** * MTD_MemorySegment_SegmentAllocator_String */ @@ -152,13 +285,29 @@ public final class Constants { CD_SegmentAllocator, CD_String, CD_Charset); + /** + * MTD_MemorySegment_SegmentAllocator_StringArray + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_StringArray = MethodTypeDesc.of(CD_MemorySegment, + CD_SegmentAllocator, + CD_String.arrayType()); /** * MTD_MemorySegment_SegmentAllocator_StringArray_Charset */ public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_StringArray_Charset = MethodTypeDesc.of(CD_MemorySegment, CD_SegmentAllocator, - CD_StringArray, + CD_String.arrayType(), CD_Charset); + /** + * MTD_MemorySegment_SegmentAllocator_Struct + */ + public static final MethodTypeDesc MTD_MemorySegment_SegmentAllocator_StructArray = MethodTypeDesc.of(CD_MemorySegment, + CD_SegmentAllocator, + CD_Struct.arrayType()); + /** + * MTD_MemorySegment_Struct + */ + public static final MethodTypeDesc MTD_MemorySegment_Struct = MethodTypeDesc.of(CD_MemorySegment, CD_Struct); /** * MTD_MemoryStack */ @@ -182,11 +331,15 @@ public final class Constants { /** * MTD_StringArray_MemorySegment */ - public static final MethodTypeDesc MTD_StringArray_MemorySegment = MethodTypeDesc.of(CD_StringArray, CD_MemorySegment); + public static final MethodTypeDesc MTD_StringArray_MemorySegment = MethodTypeDesc.of(CD_String.arrayType(), CD_MemorySegment); /** * MTD_StringArray_MemorySegment_Charset */ - public static final MethodTypeDesc MTD_StringArray_MemorySegment_Charset = MethodTypeDesc.of(CD_StringArray, CD_MemorySegment, CD_Charset); + public static final MethodTypeDesc MTD_StringArray_MemorySegment_Charset = MethodTypeDesc.of(CD_String.arrayType(), CD_MemorySegment, CD_Charset); + /** + * MTD_StructAllocatorSpec + */ + public static final MethodTypeDesc MTD_StructAllocatorSpec = MethodTypeDesc.of(CD_StructAllocatorSpec); /** * MTD_StructLayout */ @@ -196,29 +349,61 @@ public final class Constants { */ public static final MethodTypeDesc MTD_SymbolLookup = MethodTypeDesc.of(CD_SymbolLookup); /** - * MTD_VarHandle_MemoryLayout_PathElementArray + * MTD_VarHandle_MemoryLayout$PathElementArray */ - public static final MethodTypeDesc MTD_VarHandle_MemoryLayout_PathElementArray = MethodTypeDesc.of(CD_VarHandle, CD_MemoryLayout_PathElement.arrayType()); + public static final MethodTypeDesc MTD_VarHandle_MemoryLayout$PathElementArray = MethodTypeDesc.of(CD_VarHandle, CD_MemoryLayout$PathElement.arrayType()); /** * MTD_void_int_int */ public static final MethodTypeDesc MTD_void_int_int = MethodTypeDesc.of(CD_void, CD_int, CD_int); - /** - * MTD_void_long - */ - public static final MethodTypeDesc MTD_void_long = MethodTypeDesc.of(CD_void, CD_long); /** * MTD_void_MemorySegment_long */ public static final MethodTypeDesc MTD_void_MemorySegment_long = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_long); + /** + * MTD_void_MemorySegment_booleanArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_booleanArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_boolean.arrayType()); + /** + * MTD_void_MemorySegment_charArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_charArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_char.arrayType()); + /** + * MTD_void_MemorySegment_byteArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_byteArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_byte.arrayType()); + /** + * MTD_void_MemorySegment_shortArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_shortArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_short.arrayType()); + /** + * MTD_void_MemorySegment_intArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_intArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_int.arrayType()); + /** + * MTD_void_MemorySegment_longArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_longArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_long.arrayType()); + /** + * MTD_void_MemorySegment_floatArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_floatArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_float.arrayType()); + /** + * MTD_void_MemorySegment_doubleArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_doubleArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_double.arrayType()); + /** + * MTD_void_MemorySegment_MemorySegmentArray + */ + public static final MethodTypeDesc MTD_void_MemorySegment_MemorySegmentArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_MemorySegment.arrayType()); /** * MTD_void_MemorySegment_StringArray */ - public static final MethodTypeDesc MTD_void_MemorySegment_StringArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_StringArray); + public static final MethodTypeDesc MTD_void_MemorySegment_StringArray = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_String.arrayType()); /** * MTD_void_MemorySegment_StringArray_Charset */ - public static final MethodTypeDesc MTD_void_MemorySegment_StringArray_Charset = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_StringArray, CD_Charset); + public static final MethodTypeDesc MTD_void_MemorySegment_StringArray_Charset = MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_String.arrayType(), CD_Charset); /** * MTD_void_String_Throwable */ diff --git a/src/main/java/overrun/marshal/internal/StringCharset.java b/src/main/java/overrun/marshal/internal/StringCharset.java index b8b09a5..016b301 100644 --- a/src/main/java/overrun/marshal/internal/StringCharset.java +++ b/src/main/java/overrun/marshal/internal/StringCharset.java @@ -59,7 +59,10 @@ public static String getCharset(AnnotatedElement element) { * @param codeBuilder codeBuilder * @param charset charset */ - public static void getCharset(CodeBuilder codeBuilder, String charset) { + public static boolean getCharset(CodeBuilder codeBuilder, String charset) { + if (charset == null) { + return false; + } final String upperCase = charset.toUpperCase(Locale.ROOT); switch (upperCase) { case "UTF-8", "ISO-8859-1", "US-ASCII", @@ -72,20 +75,6 @@ public static void getCharset(CodeBuilder codeBuilder, String charset) { default -> codeBuilder.ldc(charset) .invokestatic(CD_Charset, "forName", MTD_Charset_String); } - } - - /** - * {@return getCharset} - * - * @param codeBuilder codeBuilder - * @param element element - */ - public static boolean getCharset(CodeBuilder codeBuilder, AnnotatedElement element) { - final String charset = getCharset(element); - if (charset != null) { - getCharset(codeBuilder, charset); - return true; - } - return false; + return true; } } diff --git a/src/main/java/overrun/marshal/internal/data/DowncallData.java b/src/main/java/overrun/marshal/internal/data/DowncallData.java index 9202355..173e41e 100644 --- a/src/main/java/overrun/marshal/internal/data/DowncallData.java +++ b/src/main/java/overrun/marshal/internal/data/DowncallData.java @@ -16,6 +16,8 @@ package overrun.marshal.internal.data; +import overrun.marshal.gen.processor.ProcessorType; + import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.SymbolLookup; import java.lang.invoke.MethodHandle; @@ -27,12 +29,14 @@ * @param descriptorMap descriptorMap * @param handleMap handleMap * @param symbolLookup symbolLookup + * @param structTypeMap structTypeMap * @author squid233 * @since 0.1.0 */ public record DowncallData( Map descriptorMap, Map handleMap, - SymbolLookup symbolLookup + SymbolLookup symbolLookup, + Map, ProcessorType.Struct> structTypeMap ) { } diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/struct/Struct.java index f42f9c4..3175663 100644 --- a/src/main/java/overrun/marshal/struct/Struct.java +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -16,7 +16,6 @@ package overrun.marshal.struct; -import overrun.marshal.Addressable; import overrun.marshal.Unmarshal; import java.lang.foreign.MemoryLayout; @@ -34,7 +33,7 @@ * @see overrun.marshal.LayoutBuilder LayoutBuilder * @since 0.1.0 */ -public interface Struct> extends Addressable { +public interface Struct> { /** * Estimates the struct count of the given segment. * @@ -67,7 +66,6 @@ static long estimateCount(MemorySegment segment, StructLayout layout) { /** * {@return the segment of this struct} */ - @Override MemorySegment segment(); /** diff --git a/src/main/java/overrun/marshal/struct/StructAllocator.java b/src/main/java/overrun/marshal/struct/StructAllocator.java index 0e1b31b..475c4bb 100644 --- a/src/main/java/overrun/marshal/struct/StructAllocator.java +++ b/src/main/java/overrun/marshal/struct/StructAllocator.java @@ -17,6 +17,7 @@ package overrun.marshal.struct; import overrun.marshal.LayoutBuilder; +import overrun.marshal.gen.processor.ProcessorTypes; import java.lang.classfile.ClassFile; import java.lang.classfile.TypeKind; @@ -134,7 +135,7 @@ * @since 0.1.0 */ @SuppressWarnings("preview") -public final class StructAllocator { +public final class StructAllocator implements StructAllocatorSpec { private final MethodHandle constructor; private final StructLayout layout; @@ -145,6 +146,7 @@ public final class StructAllocator { * @param layout the struct layout */ public StructAllocator(MethodHandles.Lookup lookup, StructLayout layout) { + ProcessorTypes.registerStruct(lookup.lookupClass(), this); this.layout = layout; final byte[] bytes = buildBytecode(lookup, layout); try { @@ -360,11 +362,11 @@ private static byte[] buildBytecode(MethodHandles.Lookup lookup, StructLayout la // var handle codeBuilder.ldc(DCD_classData_StructLayout) .ldc(result.elems().size() + 1) - .anewarray(CD_MemoryLayout_PathElement) + .anewarray(CD_MemoryLayout$PathElement) .dup() .iconst_0() .ldc(name) - .invokestatic(CD_MemoryLayout_PathElement, "groupElement", MTD_MemoryLayout_PathElement_String, true) + .invokestatic(CD_MemoryLayout$PathElement, "groupElement", MTD_MemoryLayout$PathElement_String, true) .aastore(); int i = 0; for (Elem elem : result.elems()) { @@ -373,14 +375,14 @@ private static byte[] buildBytecode(MethodHandles.Lookup lookup, StructLayout la switch (elem) { case GroupElem groupElem -> codeBuilder .ldc(groupElem.name) - .invokestatic(CD_MemoryLayout_PathElement, "groupElement", MTD_MemoryLayout_PathElement_String, true); + .invokestatic(CD_MemoryLayout$PathElement, "groupElement", MTD_MemoryLayout$PathElement_String, true); case SeqElem _ -> codeBuilder - .invokestatic(CD_MemoryLayout_PathElement, "sequenceElement", MTD_MemoryLayout_PathElement, true); + .invokestatic(CD_MemoryLayout$PathElement, "sequenceElement", MTD_MemoryLayout$PathElement, true); } codeBuilder.aastore(); i++; } - codeBuilder.invokeinterface(CD_StructLayout, "varHandle", MTD_VarHandle_MemoryLayout_PathElementArray) + codeBuilder.invokeinterface(CD_StructLayout, "varHandle", MTD_VarHandle_MemoryLayout$PathElementArray) .putstatic(cd_thisClass, "_VH_" + name + result.appendName(), CD_VarHandle); } } @@ -390,13 +392,13 @@ private static byte[] buildBytecode(MethodHandles.Lookup lookup, StructLayout la // var handle codeBuilder.ldc(DCD_classData_StructLayout) .iconst_1() - .anewarray(CD_MemoryLayout_PathElement) + .anewarray(CD_MemoryLayout$PathElement) .dup() .iconst_0() .ldc(name) - .invokestatic(CD_MemoryLayout_PathElement, "groupElement", MTD_MemoryLayout_PathElement_String, true) + .invokestatic(CD_MemoryLayout$PathElement, "groupElement", MTD_MemoryLayout$PathElement_String, true) .aastore() - .invokeinterface(CD_StructLayout, "varHandle", MTD_VarHandle_MemoryLayout_PathElementArray) + .invokeinterface(CD_StructLayout, "varHandle", MTD_VarHandle_MemoryLayout$PathElementArray) .putstatic(cd_thisClass, "_VH_" + name, CD_VarHandle); } case PaddingLayout _ -> { @@ -485,12 +487,7 @@ public T of(MemorySegment segment, long count) { } } - /** - * Creates a struct with the given segment. - * - * @param segment the segment - * @return the instance of the struct - */ + @Override public T of(MemorySegment segment) { return of(segment, Struct.estimateCount(segment, layout)); } @@ -523,9 +520,7 @@ public T nullptr() { return of(MemorySegment.NULL, 0L); } - /** - * {@return the layout of this struct} - */ + @Override public StructLayout layout() { return layout; } diff --git a/src/main/java/overrun/marshal/Addressable.java b/src/main/java/overrun/marshal/struct/StructAllocatorSpec.java similarity index 65% rename from src/main/java/overrun/marshal/Addressable.java rename to src/main/java/overrun/marshal/struct/StructAllocatorSpec.java index 6ff18fd..6a8907d 100644 --- a/src/main/java/overrun/marshal/Addressable.java +++ b/src/main/java/overrun/marshal/struct/StructAllocatorSpec.java @@ -14,19 +14,28 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.struct; import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; /** - * An object that holds a memory segment. + * Specification of {@link StructAllocator}. * * @author squid233 * @since 0.1.0 */ -public interface Addressable { +public interface StructAllocatorSpec { /** - * {@return the memory segment of this object} + * Creates a struct with the given segment. + * + * @param segment the segment + * @return the instance of the struct */ - MemorySegment segment(); + T of(MemorySegment segment); + + /** + * {@return the layout of this struct} + */ + StructLayout layout(); } diff --git a/src/test/java/overrun/marshal/test/GlobalVarTest.java b/src/test/java/overrun/marshal/test/GlobalVarTest.java index f653ba3..e740018 100644 --- a/src/test/java/overrun/marshal/test/GlobalVarTest.java +++ b/src/test/java/overrun/marshal/test/GlobalVarTest.java @@ -20,7 +20,6 @@ import org.junit.jupiter.api.Test; import overrun.marshal.DirectAccess; import overrun.marshal.Downcall; -import overrun.marshal.gen.Skip; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; @@ -40,23 +39,18 @@ public final class GlobalVarTest { private static final SymbolLookup LOOKUP = name -> "globalVar".equals(name) ? Optional.of(globalVar) : Optional.empty(); static { - globalVar.set(ValueLayout.JAVA_INT, 0L, 42 ); + globalVar.set(ValueLayout.JAVA_INT, 0L, 42); } interface I extends DirectAccess { I INSTANCE = Downcall.load(MethodHandles.lookup(), LOOKUP); - - @Skip - default int testGlobalVariable() { - return symbolLookup().find("globalVar") - .orElseThrow() - .reinterpret(ValueLayout.JAVA_INT.byteSize()) - .get(ValueLayout.JAVA_INT, 0L); - } + MemorySegment myGlobalVar = INSTANCE.symbolLookup().findOrThrow("globalVar"); } @Test void testGlobalVariable() { - Assertions.assertEquals(42, I.INSTANCE.testGlobalVariable()); + Assertions.assertEquals(42, I.myGlobalVar + .reinterpret(ValueLayout.JAVA_INT.byteSize()) + .get(ValueLayout.JAVA_INT, 0L)); } } diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java b/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java index f9c5715..1123823 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java @@ -65,7 +65,6 @@ public final class DowncallProvider { seg("testReturnStruct", LOOKUP.findStatic(DowncallProvider.class, "testReturnStruct", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnStructByValue", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructByValue", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(Vector3.OF.layout())); seg("testReturnStructSizedSeg", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructSizedSeg", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); - seg("testReturnStructSized", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructSized", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnIntArray", LOOKUP.findStatic(DowncallProvider.class, "testReturnIntArray", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testSizedIntArray", LOOKUP.findStatic(DowncallProvider.class, "testSizedIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); seg("testReturnSizedSeg", LOOKUP.findStatic(DowncallProvider.class, "testReturnSizedSeg", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); @@ -172,10 +171,6 @@ private static MemorySegment testReturnStructSizedSeg() { return segment; } - private static MemorySegment testReturnStructSized() { - return testReturnStructSizedSeg(); - } - private static MemorySegment testReturnIntArray() { return ARENA.allocateFrom(JAVA_INT, 4, 2); } diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java index d6ad2c6..a3db85f 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java @@ -80,10 +80,8 @@ void testReturnString() { @Test void testReturnUpcall() { - try (Arena arena = Arena.ofConfined()) { - final MemorySegment upcall = d.testReturnUpcall(arena); - assertEquals(84, SimpleUpcall.invoke(upcall, 42)); - } + final MemorySegment upcall = d.testReturnUpcall(); + assertEquals(84, SimpleUpcall.invoke(upcall, 42)); } @Test @@ -103,7 +101,6 @@ void testReturnStruct() { @Test void testReturnStructSized() { assertStructSized(d.testReturnStructSizedSeg()); - assertStructSized(d.testReturnStructSized()); } private void assertStructSized(Vector3 vector3) { diff --git a/src/test/java/overrun/marshal/test/downcall/IDowncall.java b/src/test/java/overrun/marshal/test/downcall/IDowncall.java index 81a323d..5486c71 100644 --- a/src/test/java/overrun/marshal/test/downcall/IDowncall.java +++ b/src/test/java/overrun/marshal/test/downcall/IDowncall.java @@ -18,15 +18,20 @@ import io.github.overrun.memstack.MemoryStack; import overrun.marshal.DowncallOption; +import overrun.marshal.gen.processor.ProcessorType; +import overrun.marshal.internal.Constants; import overrun.marshal.struct.ByValue; import overrun.marshal.Downcall; import overrun.marshal.gen.*; import overrun.marshal.test.struct.Vector3; import overrun.marshal.test.upcall.SimpleUpcall; +import java.io.IOException; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.Map; /** @@ -39,6 +44,15 @@ public interface IDowncall { Map MAP = Map.of("testDefault", FunctionDescriptor.of(ValueLayout.JAVA_INT)); static IDowncall getInstance(boolean testDefaultNull) { + // TODO: 2024/8/23 squid233: + if (Constants.DEBUG) { + byte[] bytes = Downcall.buildBytecode(MethodHandles.lookup(), DowncallProvider.lookup(testDefaultNull), DowncallOption.descriptors(MAP)).getKey(); + try { + Files.write(Path.of("run/IDowncall_" + testDefaultNull + ".class"), bytes); + } catch (IOException e) { + throw new RuntimeException(e); + } + } return Downcall.load(MethodHandles.lookup(), DowncallProvider.lookup(testDefaultNull), DowncallOption.descriptors(MAP)); } @@ -83,7 +97,7 @@ default int testDefault() { @StrCharset("UTF-16") String testReturnUTF16String(); - MemorySegment testReturnUpcall(Arena arena); + MemorySegment testReturnUpcall(); Vector3 testReturnStruct(); @@ -93,9 +107,6 @@ default int testDefault() { @SizedSeg(2L) Vector3 testReturnStructSizedSeg(); - @Sized(2) - Vector3 testReturnStructSized(); - @Sized(2) int[] testReturnIntArray(); @@ -110,8 +121,8 @@ default int testDefault() { void testCriticalTrue(@Ref int[] arr); - @Convert(Type.INT) - boolean testConvertBoolean(@Convert(Type.INT) boolean b); + @Convert(ProcessorType.BoolConvert.INT) + boolean testConvertBoolean(@Convert(ProcessorType.BoolConvert.INT) boolean b); @Entrypoint("testDefault") default MethodHandle mh_testDefaultMethodHandle() { From 2beb24caa1edf9adbb928186b2ed21c191273959 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 24 Aug 2024 14:43:30 +0800 Subject: [PATCH 03/13] Update Processor; remove debug code --- src/main/java/overrun/marshal/Downcall.java | 177 ++++++------------ .../overrun/marshal/DowncallMethodData.java | 2 - src/main/java/overrun/marshal/Upcall.java | 28 ++- .../marshal/gen/processor/BaseProcessor.java | 6 +- .../gen/processor/BeforeInvokeProcessor.java | 2 +- .../gen/processor/MarshalProcessor.java | 8 +- .../marshal/gen/processor/Processor.java | 12 +- .../marshal/gen/processor/ProcessorType.java | 56 +++++- .../marshal/gen/processor/ProcessorTypes.java | 35 ++-- .../gen/processor/UnmarshalProcessor.java | 37 ++-- .../overrun/marshal/internal/Constants.java | 30 ++- .../marshal/internal/data/DowncallData.java | 6 +- .../test/downcall/DowncallProvider.java | 6 +- .../marshal/test/downcall/DowncallTest.java | 3 +- .../marshal/test/downcall/IDowncall.java | 22 +-- .../marshal/test/upcall/SimpleUpcall.java | 5 +- 16 files changed, 230 insertions(+), 205 deletions(-) diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 5a7527f..8f306ab 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -34,11 +34,11 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.*; -import java.nio.file.Files; -import java.nio.file.Path; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; import java.util.*; -import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.UnaryOperator; @@ -175,23 +175,6 @@ private static String getMethodEntrypoint(Method method) { entrypoint.value(); } - private static String unmarshalMethod(Class aClass) { - if (aClass.isArray()) { - final Class componentType = aClass.getComponentType(); - if (componentType == boolean.class) return "unmarshalAsBooleanArray"; - if (componentType == char.class) return "unmarshalAsCharArray"; - if (componentType == byte.class) return "unmarshalAsByteArray"; - if (componentType == short.class) return "unmarshalAsShortArray"; - if (componentType == int.class) return "unmarshalAsIntArray"; - if (componentType == long.class) return "unmarshalAsLongArray"; - if (componentType == float.class) return "unmarshalAsFloatArray"; - if (componentType == double.class) return "unmarshalAsDoubleArray"; - if (componentType == MemorySegment.class) return "unmarshalAsAddressArray"; - if (componentType == String.class) return "unmarshalAsStringArray"; - } - return "unmarshal"; - } - // type private static ValueLayout getValueLayout(Class carrier) { @@ -206,35 +189,7 @@ private static ValueLayout getValueLayout(Class carrier) { return ValueLayout.ADDRESS; } - private static boolean requireAllocator(Class aClass) { - return !aClass.isPrimitive() && - (aClass == String.class || - aClass.isArray() || - Upcall.class.isAssignableFrom(aClass)); - } - - // class desc - - private static ClassDesc convertToDowncallCD(AnnotatedElement element, Class aClass) { - final Convert convert = element.getDeclaredAnnotation(Convert.class); - if (convert != null && aClass == boolean.class) return convert.value().downcallClassDesc(); - if (aClass.isPrimitive()) return aClass.describeConstable().orElseThrow(); - if (SegmentAllocator.class.isAssignableFrom(aClass)) return CD_SegmentAllocator; - if (aClass == Object.class) return CD_Object; - return CD_MemorySegment; - } - - private static ClassDesc convertToMarshalCD(Class aClass) { - if (aClass.isPrimitive() || - aClass == String.class) return aClass.describeConstable().orElseThrow(); - if (Struct.class.isAssignableFrom(aClass)) return CD_Struct; - if (Upcall.class.isAssignableFrom(aClass)) return CD_Upcall; - return CD_MemorySegment; - } - - // TODO - /*private*/ - public static Map.Entry buildBytecode(MethodHandles.Lookup caller, SymbolLookup lookup, DowncallOption... options) { + private static Map.Entry buildBytecode(MethodHandles.Lookup caller, SymbolLookup lookup, DowncallOption... options) { Class _targetClass = null, targetClass; Map _descriptorMap = null, descriptorMap; UnaryOperator _transform = null, transform; @@ -263,13 +218,12 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup final ClassFile cf = of(); final ClassDesc cd_thisClass = ClassDesc.of(lookupClass.getPackageName(), DEFAULT_NAME); - final Map methodDataMap = LinkedHashMap.newLinkedHashMap(methodList.size()); + final Map methodDataMap = HashMap.newHashMap(methodList.size()); + Set methodEntrypointSet = new HashSet<>(); //region method handles - final AtomicInteger handleCount = new AtomicInteger(); for (Method method : methodList) { final String entrypoint = getMethodEntrypoint(method); - final String handleName = "$mh" + handleCount.getAndIncrement(); final var parameters = List.of(method.getParameters()); boolean byValue = method.getDeclaredAnnotation(ByValue.class) != null; @@ -281,7 +235,6 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup ), AllocatorRequirement::stricter); final DowncallMethodData methodData = new DowncallMethodData( entrypoint, - handleName, signatureStringMap.get(method), parameters, !byValue && @@ -291,6 +244,7 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup allocatorRequirement ); methodDataMap.put(method, methodData); + methodEntrypointSet.add(entrypoint); } //endregion @@ -308,9 +262,9 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup } //region method handles - for (DowncallMethodData data : methodDataMap.values()) { - if (downcallData.handleMap().get(data.entrypoint()) != null) { - classBuilder.withField(data.handleName(), + for (String entrypoint : methodEntrypointSet) { + if (downcallData.handleMap().get(entrypoint) != null) { + classBuilder.withField(entrypoint, CD_MethodHandle, ACC_PRIVATE | ACC_FINAL | ACC_STATIC); } @@ -349,13 +303,13 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup mtd_method, Modifier.isPublic(modifiers) ? ACC_PUBLIC : ACC_PROTECTED, codeBuilder -> { - final String handleName = methodData.handleName(); + final String entrypoint = methodData.entrypoint(); final TypeKind returnTypeKind = TypeKind.from(cd_returnType); // returns MethodHandle if (returnType == MethodHandle.class) { if (method.isDefault() && - downcallData.handleMap().get(methodData.entrypoint()) == null) { + downcallData.handleMap().get(entrypoint) == null) { // invoke super interface invokeSuperMethod(codeBuilder, parameters); codeBuilder.invokespecial(cd_targetClass, @@ -364,14 +318,14 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup targetClass.isInterface()) .areturn(); } else { - codeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle) + codeBuilder.getstatic(cd_thisClass, entrypoint, CD_MethodHandle) .areturn(); } return; } if (method.isDefault() && - downcallData.handleMap().get(methodData.entrypoint()) == null) { + downcallData.handleMap().get(entrypoint) == null) { // invoke super interface invokeSuperMethod(codeBuilder, parameters); codeBuilder.invokespecial(cd_targetClass, @@ -418,7 +372,7 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup new BeforeInvokeProcessor.Context(parameters, refSlotMap, allocatorSlot)); // invoke - blockCodeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle); + blockCodeBuilder.getstatic(cd_thisClass, entrypoint, CD_MethodHandle); List downcallClassDescList = new ArrayList<>(); for (int i = methodData.skipFirstParam() ? 1 : 0, size = parameters.size(); i < size; i++) { Parameter parameter = parameters.get(i); @@ -426,7 +380,7 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup if (refSlotMap.containsKey(parameter)) { blockCodeBuilder.aload(refSlotMap.get(parameter)); } else { - MarshalProcessor.getInstance().process(blockCodeBuilder, + MarshalProcessor.getInstance().processAndCheck(blockCodeBuilder, new MarshalProcessor.Context(processorType, StringCharset.getCharset(parameter), blockCodeBuilder.parameterSlot(i), @@ -453,8 +407,9 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup BeforeReturnProcessor.getInstance().process(blockCodeBuilder, beforeReturnContext); // return - UnmarshalProcessor.getInstance().process(blockCodeBuilder, + UnmarshalProcessor.getInstance().processAndCheck(blockCodeBuilder, new UnmarshalProcessor.Context(returnProcessorType, + returnType, StringCharset.getCharset(method), resultSlot)); blockCodeBuilder.return_(returnTypeKind); @@ -513,17 +468,16 @@ public static Map.Entry buildBytecode(MethodHandles.Lookup .astore(handleMapSlot); // method handles - methodDataMap.values().forEach(methodData -> { - final String entrypoint = methodData.entrypoint(); + for (String entrypoint : methodEntrypointSet) { if (downcallData.handleMap().get(entrypoint) != null) { codeBuilder .aload(handleMapSlot) .ldc(entrypoint) .invokeinterface(CD_Map, "get", MTD_Object_Object) .checkcast(CD_MethodHandle) - .putstatic(cd_thisClass, methodData.handleName(), CD_MethodHandle); + .putstatic(cd_thisClass, entrypoint, CD_MethodHandle); } - }); + } codeBuilder.return_(); }); @@ -536,14 +490,6 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look var built = buildBytecode(caller, lookup, options); try { - //TODO - if (DEBUG) { - Path path = Path.of("run/" + caller.lookupClass().getSimpleName() + ".class"); - if (Files.notExists(path.getParent())) { - Files.createDirectories(path.getParent()); - } - Files.write(path, built.getKey()); - } final MethodHandles.Lookup hiddenClass = caller.defineHiddenClassWithClassData( built.getKey(), built.getValue(), @@ -559,12 +505,10 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look private static boolean shouldSkip(Method method) { final Class returnType = method.getReturnType(); - final boolean b = - method.getDeclaredAnnotation(Skip.class) != null || - Modifier.isStatic(method.getModifiers()) || - method.isSynthetic() || - Modifier.isFinal(method.getModifiers()); - if (b) { + if (method.getDeclaredAnnotation(Skip.class) != null || + Modifier.isStatic(method.getModifiers()) || + method.isSynthetic() || + Modifier.isFinal(method.getModifiers())) { return true; } @@ -572,37 +516,33 @@ private static boolean shouldSkip(Method method) { final Class[] types = method.getParameterTypes(); final int length = types.length; - // check method declared by Object - if (length == 0) { - if (returnType == Class.class && "getClass".equals(methodName) || - returnType == int.class && "hashCode".equals(methodName) || - returnType == Object.class && "clone".equals(methodName) || - returnType == String.class && "toString".equals(methodName) || - returnType == void.class && "notify".equals(methodName) || - returnType == void.class && "notifyAll".equals(methodName) || - returnType == void.class && "wait".equals(methodName) || - returnType == void.class && "finalize".equals(methodName) // TODO: no finalize in the future - ) { - return true; - } - } else if ( - returnType == boolean.class && length == 1 && types[0] == Object.class && "equals".equals(methodName) || - returnType == void.class && length == 1 && types[0] == long.class && "wait".equals(methodName) || - returnType == void.class && length == 2 && types[0] == long.class && types[1] == long.class && "wait".equals(methodName) - ) { - return true; - } - - // check method declared by DirectAccess - if (length == 0) { - if (returnType == Map.class) - return "functionDescriptors".equals(methodName) || "methodHandles".equals(methodName); - if (returnType == SymbolLookup.class) return "symbolLookup".equals(methodName); - } - if (returnType == MethodHandle.class && length == 1 && types[0] == String.class) { - return "methodHandle".equals(methodName); - } - return false; + // check method declared by Object or DirectAccess + return switch (length) { + case 0 -> switch (methodName) { + // Object + case "getClass" -> returnType == Class.class; + case "hashCode" -> returnType == int.class; + case "clone" -> returnType == Object.class; + case "toString" -> returnType == String.class; + case "notify", "notifyAll", "wait" -> returnType == void.class; + case "finalize" -> returnType == void.class; // TODO: no finalize in the future + // DirectAccess + case "functionDescriptors", "methodHandles" -> returnType == Map.class; + case "symbolLookup" -> returnType == SymbolLookup.class; + default -> false; + }; + case 1 -> switch (methodName) { + // Object + case "equals" -> returnType == boolean.class && types[0] == Object.class; + case "wait" -> returnType == void.class && types[0] == long.class; + // DirectAccess + case "methodHandle" -> returnType == MethodHandle.class && types[0] == String.class; + default -> false; + }; + case 2 -> // Object + returnType == void.class && types[0] == long.class && types[1] == long.class && "wait".equals(methodName); + default -> false; + }; } private static String createSignatureString(Method method) { @@ -638,9 +578,7 @@ private static void verifyMethods(List list, Map signatu final boolean isFirstArena = types.length > 0 && Arena.class.isAssignableFrom(types[0]); for (Parameter parameter : method.getParameters()) { final Class type = parameter.getType(); - if (Upcall.class.isAssignableFrom(type) && !isFirstArena) { - throw new IllegalStateException("The first parameter of method " + method + " is not an arena; however, the parameter " + parameter + " is an upcall"); - } else if (!isValidParamType(type)) { + if (!isValidParamType(type)) { throw new IllegalStateException("Invalid parameter: " + parameter + " in " + method); } } @@ -780,8 +718,8 @@ private static DowncallData generateData( // argument layouts final var parameters = methodData.parameters(); - final boolean skipFirstParam = methodData.skipFirstParam(); - final int size = skipFirstParam || methodByValue ? + final boolean skipFirstParam = methodData.skipFirstParam() || methodByValue; + final int size = skipFirstParam ? parameters.size() - 1 : parameters.size(); final MemoryLayout[] argLayouts = new MemoryLayout[size]; @@ -823,8 +761,7 @@ private static DowncallData generateData( } return new DowncallData(Collections.unmodifiableMap(descriptorMap1), Collections.unmodifiableMap(map), - lookup, - ProcessorTypes.collect(ProcessorType.Struct.class)); + lookup); } private static Field getStructAllocatorField(Class aClass) { diff --git a/src/main/java/overrun/marshal/DowncallMethodData.java b/src/main/java/overrun/marshal/DowncallMethodData.java index 8c0c107..1b1510c 100644 --- a/src/main/java/overrun/marshal/DowncallMethodData.java +++ b/src/main/java/overrun/marshal/DowncallMethodData.java @@ -25,7 +25,6 @@ * Holds downcall method name * * @param entrypoint entrypoint - * @param handleName handleName * @param signatureString signatureString * @param parameters parameters * @param skipFirstParam skipFirstParam @@ -35,7 +34,6 @@ */ record DowncallMethodData( String entrypoint, - String handleName, String signatureString, List parameters, boolean skipFirstParam, diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index 7109c64..7b37826 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -16,6 +16,9 @@ package overrun.marshal; +import overrun.marshal.gen.processor.ProcessorType; +import overrun.marshal.gen.processor.ProcessorTypes; + import java.lang.foreign.Arena; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; @@ -35,7 +38,11 @@ * @FunctionalInterface * public interface MyCallback extends Upcall { * // Create a type wrapper - * Type TYPE = Upcall.type("invoke", FunctionDescriptor.of(JAVA_INT, JAVA_INT)); + * // (Optional) Register to ProcessorTypes to allow C functions to return the upcall instance + * Type TYPE = Upcall.register( + * Upcall.type("invoke", FunctionDescriptor.of(JAVA_INT, JAVA_INT)), + * stub -> i -> invoke(stub, i) + * ); * * // The function to be invoked in C * int invoke(int i); @@ -76,6 +83,8 @@ public interface Upcall { */ MemorySegment stub(Arena arena); + // caller-sensitive. DO NOT WRAP! + /** * Creates {@link Type} with the caller class. * @@ -111,6 +120,19 @@ static Type type(Class tClass, String targetName, Funct return new Type<>(tClass, targetName, descriptor); } + /** + * Registers the given type to {@link ProcessorTypes}. + * + * @param type the {@link Type} + * @param factory the factory + * @param the type of the upcall + * @return {@code type} + */ + static Type register(Type type, ProcessorType.Upcall.Factory factory) { + ProcessorTypes.registerUpcall(type.typeClass, factory); + return type; + } + /** * The type wrapper of an upcall interface. * The constructor uses heavy reflection, and you should always cache it as a static field. @@ -122,16 +144,18 @@ static Type type(Class tClass, String targetName, Funct */ final class Type { private static final Linker LINKER = Linker.nativeLinker(); + final Class typeClass; private final MethodHandle target; private final FunctionDescriptor descriptor; private final MethodHandle downcallTarget; private Type(Class tClass, String targetName, FunctionDescriptor descriptor) { try { - target = MethodHandles.publicLookup().findVirtual(tClass, targetName, descriptor.toMethodType()); + this.target = MethodHandles.publicLookup().findVirtual(tClass, targetName, descriptor.toMethodType()); } catch (IllegalAccessException | NoSuchMethodException e) { throw new RuntimeException(e); } + this.typeClass = tClass; this.descriptor = descriptor; this.downcallTarget = LINKER.downcallHandle(descriptor); } diff --git a/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java b/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java index cd225a0..64e22d3 100644 --- a/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java @@ -36,11 +36,11 @@ public abstract class BaseProcessor implements Processor { @Override public boolean process(CodeBuilder builder, C context) { for (var processor : processors) { - if (!processor.process(builder, context)) { - return false; + if (processor.process(builder, context)) { + return true; } } - return true; + return false; } public void addProcessor(Processor processor) { diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java index 40623ed..eb6a053 100644 --- a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java @@ -44,7 +44,7 @@ public boolean process(CodeBuilder builder, Context context) { parameter.getDeclaredAnnotation(Ref.class) != null) { int local = builder.allocateLocal(TypeKind.ReferenceType); context.refSlot().put(parameter, local); - MarshalProcessor.getInstance().process(builder, + MarshalProcessor.getInstance().processAndCheck(builder, new MarshalProcessor.Context(ProcessorTypes.fromParameter(parameter), StringCharset.getCharset(parameter), builder.parameterSlot(i), diff --git a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java index 05f6e34..90d59b0 100644 --- a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java @@ -45,12 +45,14 @@ public boolean process(CodeBuilder builder, Context context) { switch (context.type()) { case ProcessorType.Allocator _ -> builder.aload(variableSlot); case ProcessorType.Custom _ -> { + return super.process(builder, context); } case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); case ProcessorType.Array array -> { switch (array.componentType()) { case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _, ProcessorType.Custom _ -> { + return super.process(builder, context); } case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); case ProcessorType.Str _ -> builder @@ -67,7 +69,7 @@ public boolean process(CodeBuilder builder, Context context) { .invokestatic(CD_Marshal, "marshal", MTD_MemorySegment_SegmentAllocator_StructArray); - case ProcessorType.Upcall _ -> builder + case ProcessorType.Upcall _ -> builder .aload(allocatorSlot) .aload(variableSlot) .invokestatic(CD_Marshal, @@ -125,7 +127,7 @@ public boolean process(CodeBuilder builder, Context context) { .invokestatic(CD_Marshal, "marshal", MTD_MemorySegment_Struct); - case ProcessorType.Upcall _ -> builder + case ProcessorType.Upcall _ -> builder .aload(allocatorSlot) .aload(variableSlot) .invokestatic(CD_Marshal, @@ -133,7 +135,7 @@ public boolean process(CodeBuilder builder, Context context) { MTD_MemorySegment_Arena_Upcall); case ProcessorType.Value value -> builder.loadLocal(value.typeKind(), variableSlot); } - return super.process(builder, context); + return true; } public static MarshalProcessor getInstance() { diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/Processor.java index 5987f53..1131882 100644 --- a/src/main/java/overrun/marshal/gen/processor/Processor.java +++ b/src/main/java/overrun/marshal/gen/processor/Processor.java @@ -31,7 +31,17 @@ public interface Processor { * * @param builder the code builder * @param context the context - * @return {@code true} if should continue; {@code false} otherwise + * @return {@code true} if processed; {@code false} otherwise */ boolean process(CodeBuilder builder, C context); + + default void checkProcessed(boolean processed, C context) { + if (!processed) { + throw new IllegalStateException(this.getClass().getSimpleName() + " not processed: " + context); + } + } + + default void processAndCheck(CodeBuilder builder, C context) { + checkProcessed(process(builder, context), context); + } } diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java index d712b0d..aba0bad 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -16,6 +16,7 @@ package overrun.marshal.gen.processor; +import org.jetbrains.annotations.Nullable; import overrun.marshal.struct.StructAllocatorSpec; import java.lang.classfile.TypeKind; @@ -53,10 +54,22 @@ public sealed interface ProcessorType { * @param allocatorSpec the struct allocator * @return a new processor type */ - static Struct struct(Class typeClass, StructAllocatorSpec allocatorSpec) { + static Struct struct(Class typeClass, @Nullable StructAllocatorSpec allocatorSpec) { return new Struct(typeClass, allocatorSpec); } + /** + * Creates an upcall processor type with the given type class and factory. + * + * @param typeClass the type class + * @param factory the factory + * @param the upcall type + * @return a new processor type + */ + static Upcall upcall(Class typeClass, @Nullable Upcall.Factory factory) { + return new Upcall<>(typeClass, factory); + } + /** * {@code void} type */ @@ -275,9 +288,10 @@ public AllocatorRequirement allocationRequirement() { */ final class Struct implements ProcessorType { private final Class typeClass; + @Nullable private final StructAllocatorSpec allocatorSpec; - private Struct(Class typeClass, StructAllocatorSpec allocatorSpec) { + private Struct(Class typeClass, @Nullable StructAllocatorSpec allocatorSpec) { this.typeClass = typeClass; this.allocatorSpec = allocatorSpec; } @@ -286,12 +300,13 @@ public Class typeClass() { return typeClass; } + @Nullable public StructAllocatorSpec allocatorSpec() { return allocatorSpec; } - public void checkAllocator() { - Objects.requireNonNull(allocatorSpec); + public StructAllocatorSpec checkAllocator() { + return Objects.requireNonNull(allocatorSpec(), "No allocator for struct " + typeClass); } @Override @@ -307,14 +322,35 @@ public AllocatorRequirement allocationRequirement() { /** * {@link overrun.marshal.Upcall Upcall} + * + * @param upcall type */ - final class Upcall implements ProcessorType { - /** - * The instance - */ - public static final Upcall INSTANCE = new Upcall(); + final class Upcall implements ProcessorType { + private final Class typeClass; + @Nullable + private final Factory factory; + + private Upcall(Class typeClass, @Nullable Factory factory) { + this.typeClass = typeClass; + this.factory = factory; + } + + @FunctionalInterface + public interface Factory { + T create(MemorySegment stub); + } + + public Class typeClass() { + return typeClass; + } + + @Nullable + public Factory factory() { + return factory; + } - private Upcall() { + public Factory checkFactory() { + return Objects.requireNonNull(factory(), "No factory for upcall " + typeClass()); } @Override diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java index 9d8b3d7..6c156eb 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java @@ -25,10 +25,10 @@ import java.lang.foreign.SegmentAllocator; import java.lang.reflect.Method; import java.lang.reflect.Parameter; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.NoSuchElementException; -import java.util.stream.Collectors; /** * Processor types @@ -38,6 +38,7 @@ */ public final class ProcessorTypes { private static final Map, ProcessorType> map = new LinkedHashMap<>(); + private static final Map, ProcessorType> mapImmutable = Collections.unmodifiableMap(map); static { register(void.class, ProcessorType.Void.INSTANCE); @@ -53,7 +54,7 @@ public final class ProcessorTypes { register(String.class, ProcessorType.Str.INSTANCE); register(SegmentAllocator.class, ProcessorType.Allocator.INSTANCE); registerStruct(Struct.class, null); - register(Upcall.class, ProcessorType.Upcall.INSTANCE); + registerUpcall(Upcall.class, null); } private ProcessorTypes() { @@ -134,11 +135,22 @@ public static void register(Class aClass, ProcessorType type) { /** * Registers a processor type for the given struct class. * - * @param aClass the class - * @param type the processor type + * @param aClass the class + * @param allocatorSpec the allocator */ - public static void registerStruct(Class aClass, StructAllocatorSpec type) { - register(aClass, ProcessorType.struct(aClass, type)); + public static void registerStruct(Class aClass, StructAllocatorSpec allocatorSpec) { + register(aClass, ProcessorType.struct(aClass, allocatorSpec)); + } + + /** + * Registers a processor type for the given upcall class. + * + * @param aClass the class + * @param factory the factory + * @param the upcall type + */ + public static void registerUpcall(Class aClass, ProcessorType.Upcall.Factory factory) { + register(aClass, ProcessorType.upcall(aClass, factory)); } /** @@ -156,15 +168,4 @@ public static boolean isRegistered(Class aClass) { } return false; } - - @SuppressWarnings("unchecked") - public static Map, T> collect(Class instanceType) { - return map.entrySet() - .stream() - .filter(e -> instanceType.isInstance(e.getValue())) - .collect(Collectors.toUnmodifiableMap( - Map.Entry::getKey, - entry -> (T) entry.getValue() - )); - } } diff --git a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java index 17b7706..1cf6724 100644 --- a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java @@ -21,8 +21,6 @@ import java.lang.classfile.CodeBuilder; import java.lang.constant.ClassDesc; -import static java.lang.constant.ConstantDescs.CD_Map; -import static java.lang.constant.ConstantDescs.MTD_void; import static overrun.marshal.internal.Constants.*; /** @@ -32,7 +30,7 @@ * @since 0.1.0 */ public final class UnmarshalProcessor extends BaseProcessor { - public record Context(ProcessorType type, String charset, int variableSlot) { + public record Context(ProcessorType type, Class originalType, String charset, int variableSlot) { } @SuppressWarnings("preview") @@ -40,12 +38,16 @@ public record Context(ProcessorType type, String charset, int variableSlot) { public boolean process(CodeBuilder builder, Context context) { int variableSlot = context.variableSlot(); switch (context.type()) { - case ProcessorType.Allocator _, ProcessorType.Custom _, ProcessorType.Void _ -> { + case ProcessorType.Allocator _, ProcessorType.Custom _ -> { + return super.process(builder, context); + } + case ProcessorType.Void _ -> { } case ProcessorType.Array array -> { switch (array.componentType()) { case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _, - ProcessorType.Custom _, ProcessorType.Struct _, ProcessorType.Upcall _ -> { + ProcessorType.Custom _, ProcessorType.Struct _, ProcessorType.Upcall _ -> { + return super.process(builder, context); } case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); case ProcessorType.Str _ -> builder @@ -102,24 +104,23 @@ public boolean process(CodeBuilder builder, Context context) { StringCharset.getCharset(builder, context.charset()) ? MTD_String_MemorySegment_Charset : MTD_String_MemorySegment); - case ProcessorType.Struct struct -> builder - .ldc(DCD_classData_DowncallData) - .invokevirtual(CD_DowncallData, "structTypeMap", MTD_Map) - .ldc(ClassDesc.ofDescriptor(struct.typeClass().descriptorString())) - .invokeinterface(CD_Map, "get", MTD_Object_Object) + case ProcessorType.Struct _ -> builder + .ldc(ClassDesc.ofDescriptor(context.originalType().descriptorString())) + .invokestatic(CD_ProcessorTypes, "fromClass", MTD_ProcessorType_Class) .checkcast(CD_ProcessorType$Struct) - .dup() - .invokevirtual(CD_ProcessorType$Struct, "checkAllocator", MTD_void) - .invokevirtual(CD_ProcessorType$Struct, "allocatorSpec", MTD_StructAllocatorSpec) + .invokevirtual(CD_ProcessorType$Struct, "checkAllocator", MTD_StructAllocatorSpec) .aload(variableSlot) .invokeinterface(CD_StructAllocatorSpec, "of", MTD_Object_MemorySegment); - case ProcessorType.Upcall upcall -> { - // TODO: 2024/8/24 squid233: return upcall - builder.aconst_null(); - } + case ProcessorType.Upcall _ -> builder + .ldc(ClassDesc.ofDescriptor(context.originalType().descriptorString())) + .invokestatic(CD_ProcessorTypes, "fromClass", MTD_ProcessorType_Class) + .checkcast(CD_ProcessorType$Upcall) + .invokevirtual(CD_ProcessorType$Upcall, "checkFactory", MTD_ProcessorType$Upcall$Factory) + .aload(variableSlot) + .invokeinterface(CD_ProcessorType$Upcall$Factory, "create", MTD_Upcall_MemorySegment); case ProcessorType.Value value -> builder.loadLocal(value.typeKind(), variableSlot); } - return super.process(builder, context); + return true; } public static UnmarshalProcessor getInstance() { diff --git a/src/main/java/overrun/marshal/internal/Constants.java b/src/main/java/overrun/marshal/internal/Constants.java index c0e24b0..668f54f 100644 --- a/src/main/java/overrun/marshal/internal/Constants.java +++ b/src/main/java/overrun/marshal/internal/Constants.java @@ -29,8 +29,6 @@ * @since 0.1.0 */ public final class Constants { - //TODO - public static final boolean DEBUG = true; /** * CD_Arena */ @@ -67,10 +65,26 @@ public final class Constants { * CD_MemoryStack */ public static final ClassDesc CD_MemoryStack = ClassDesc.of("io.github.overrun.memstack.MemoryStack"); + /** + * CD_ProcessorType + */ + public static final ClassDesc CD_ProcessorType = ClassDesc.of("overrun.marshal.gen.processor.ProcessorType"); /** * CD_ProcessorType$Struct */ public static final ClassDesc CD_ProcessorType$Struct = ClassDesc.of("overrun.marshal.gen.processor.ProcessorType$Struct"); + /** + * CD_ProcessorType$Upcall + */ + public static final ClassDesc CD_ProcessorType$Upcall = ClassDesc.of("overrun.marshal.gen.processor.ProcessorType$Upcall"); + /** + * CD_ProcessorType$Upcall$Factory + */ + public static final ClassDesc CD_ProcessorType$Upcall$Factory = ClassDesc.of("overrun.marshal.gen.processor.ProcessorType$Upcall$Factory"); + /** + * CD_ProcessorTypes + */ + public static final ClassDesc CD_ProcessorTypes = ClassDesc.of("overrun.marshal.gen.processor.ProcessorTypes"); /** * CD_SegmentAllocator */ @@ -320,6 +334,14 @@ public final class Constants { * MTD_Object_Object */ public static final MethodTypeDesc MTD_Object_Object = MethodTypeDesc.of(CD_Object, CD_Object); + /** + * MTD_ProcessorType$Upcall$Factory + */ + public static final MethodTypeDesc MTD_ProcessorType$Upcall$Factory = MethodTypeDesc.of(CD_ProcessorType$Upcall$Factory); + /** + * MTD_ProcessorType_Class + */ + public static final MethodTypeDesc MTD_ProcessorType_Class = MethodTypeDesc.of(CD_ProcessorType, CD_Class); /** * MTD_String_MemorySegment */ @@ -348,6 +370,10 @@ public final class Constants { * MTD_SymbolLookup */ public static final MethodTypeDesc MTD_SymbolLookup = MethodTypeDesc.of(CD_SymbolLookup); + /** + * MTD_Upcall_MemorySegment + */ + public static final MethodTypeDesc MTD_Upcall_MemorySegment = MethodTypeDesc.of(CD_Upcall, CD_MemorySegment); /** * MTD_VarHandle_MemoryLayout$PathElementArray */ diff --git a/src/main/java/overrun/marshal/internal/data/DowncallData.java b/src/main/java/overrun/marshal/internal/data/DowncallData.java index 173e41e..9202355 100644 --- a/src/main/java/overrun/marshal/internal/data/DowncallData.java +++ b/src/main/java/overrun/marshal/internal/data/DowncallData.java @@ -16,8 +16,6 @@ package overrun.marshal.internal.data; -import overrun.marshal.gen.processor.ProcessorType; - import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.SymbolLookup; import java.lang.invoke.MethodHandle; @@ -29,14 +27,12 @@ * @param descriptorMap descriptorMap * @param handleMap handleMap * @param symbolLookup symbolLookup - * @param structTypeMap structTypeMap * @author squid233 * @since 0.1.0 */ public record DowncallData( Map descriptorMap, Map handleMap, - SymbolLookup symbolLookup, - Map, ProcessorType.Struct> structTypeMap + SymbolLookup symbolLookup ) { } diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java b/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java index 1123823..87b14fe 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java @@ -43,7 +43,7 @@ public final class DowncallProvider { private static final Linker LINKER = Linker.nativeLinker(); private static final Arena ARENA = Arena.ofAuto(); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - private static final Map table = HashMap.newHashMap(1); + private static final Map table = new HashMap<>(); static { try { @@ -63,7 +63,7 @@ public final class DowncallProvider { seg("testReturnCEnum", LOOKUP.findStatic(DowncallProvider.class, "testReturnCEnum", MethodType.methodType(int.class)), FunctionDescriptor.of(JAVA_INT)); seg("testReturnUpcall", LOOKUP.findStatic(DowncallProvider.class, "testReturnUpcall", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnStruct", LOOKUP.findStatic(DowncallProvider.class, "testReturnStruct", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); - seg("testReturnStructByValue", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructByValue", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(Vector3.OF.layout())); + seg("testReturnStructByValue", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructByValue", MethodType.methodType(MemorySegment.class, int.class)), FunctionDescriptor.of(Vector3.OF.layout(), JAVA_INT)); seg("testReturnStructSizedSeg", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructSizedSeg", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnIntArray", LOOKUP.findStatic(DowncallProvider.class, "testReturnIntArray", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testSizedIntArray", LOOKUP.findStatic(DowncallProvider.class, "testSizedIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); @@ -158,7 +158,7 @@ private static MemorySegment testReturnStruct() { return segment; } - private static MemorySegment testReturnStructByValue() { + private static MemorySegment testReturnStructByValue(int i) { final MemorySegment segment = ARENA.allocate(Vector3.OF.layout()); writeVector3(segment, 7, 8, 9); return segment; diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java index a3db85f..a7dd640 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java @@ -82,6 +82,7 @@ void testReturnString() { void testReturnUpcall() { final MemorySegment upcall = d.testReturnUpcall(); assertEquals(84, SimpleUpcall.invoke(upcall, 42)); + assertEquals(84, d.testReturnUpcallObject().invoke(42)); } @Test @@ -91,7 +92,7 @@ void testReturnStruct() { assertEquals(4, returnStruct.x()); assertEquals(5, returnStruct.y()); assertEquals(6, returnStruct.z()); - final Vector3 returnStructByValue = d.testReturnStructByValue(arena); + final Vector3 returnStructByValue = d.testReturnStructByValue(arena, 0); assertEquals(7, returnStructByValue.x()); assertEquals(8, returnStructByValue.y()); assertEquals(9, returnStructByValue.z()); diff --git a/src/test/java/overrun/marshal/test/downcall/IDowncall.java b/src/test/java/overrun/marshal/test/downcall/IDowncall.java index 5486c71..57bc4f3 100644 --- a/src/test/java/overrun/marshal/test/downcall/IDowncall.java +++ b/src/test/java/overrun/marshal/test/downcall/IDowncall.java @@ -17,21 +17,17 @@ package overrun.marshal.test.downcall; import io.github.overrun.memstack.MemoryStack; +import overrun.marshal.Downcall; import overrun.marshal.DowncallOption; +import overrun.marshal.gen.*; import overrun.marshal.gen.processor.ProcessorType; -import overrun.marshal.internal.Constants; import overrun.marshal.struct.ByValue; -import overrun.marshal.Downcall; -import overrun.marshal.gen.*; import overrun.marshal.test.struct.Vector3; import overrun.marshal.test.upcall.SimpleUpcall; -import java.io.IOException; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.Map; /** @@ -44,15 +40,6 @@ public interface IDowncall { Map MAP = Map.of("testDefault", FunctionDescriptor.of(ValueLayout.JAVA_INT)); static IDowncall getInstance(boolean testDefaultNull) { - // TODO: 2024/8/23 squid233: - if (Constants.DEBUG) { - byte[] bytes = Downcall.buildBytecode(MethodHandles.lookup(), DowncallProvider.lookup(testDefaultNull), DowncallOption.descriptors(MAP)).getKey(); - try { - Files.write(Path.of("run/IDowncall_" + testDefaultNull + ".class"), bytes); - } catch (IOException e) { - throw new RuntimeException(e); - } - } return Downcall.load(MethodHandles.lookup(), DowncallProvider.lookup(testDefaultNull), DowncallOption.descriptors(MAP)); } @@ -99,10 +86,13 @@ default int testDefault() { MemorySegment testReturnUpcall(); + @Entrypoint("testReturnUpcall") + SimpleUpcall testReturnUpcallObject(); + Vector3 testReturnStruct(); @ByValue - Vector3 testReturnStructByValue(SegmentAllocator allocator); + Vector3 testReturnStructByValue(SegmentAllocator allocator, int i); @SizedSeg(2L) Vector3 testReturnStructSizedSeg(); diff --git a/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java b/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java index 2fdc7d4..4892708 100644 --- a/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java +++ b/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java @@ -31,7 +31,10 @@ */ @FunctionalInterface public interface SimpleUpcall extends Upcall { - Type TYPE = Upcall.type("invoke", FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)); + Type TYPE = Upcall.register( + Upcall.type("invoke", FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)), + stub -> i -> invoke(stub, i) + ); int invoke(int i); From aede17aa3254d73df68e04f0b8e9ea7740bf4dbb Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 24 Aug 2024 16:00:01 +0800 Subject: [PATCH 04/13] Update ProcessorTypes --- src/main/java/overrun/marshal/Downcall.java | 24 +++++++++---------- src/main/java/overrun/marshal/Upcall.java | 24 +++++-------------- .../marshal/gen/processor/ProcessorType.java | 19 ++++++++++++--- .../marshal/gen/processor/ProcessorTypes.java | 15 ++++++++++-- .../java/overrun/marshal/struct/Struct.java | 6 +++++ .../marshal/struct/StructAllocator.java | 2 -- .../marshal/test/downcall/IDowncall.java | 3 +++ .../marshal/test/upcall/SimpleUpcall.java | 5 +--- 8 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 8f306ab..e5d08f9 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -97,6 +97,12 @@ * MethodHandle mh_glClear(); * default void glClear(int mask) throws Throwable { mh_glClear().invokeExact(mask); } * } + *

Processor Types

+ * Processor types are used to process builtin and custom types. + * You can {@linkplain ProcessorTypes#register(Class, ProcessorType) register} a type + * and {@linkplain BaseProcessor#addProcessor(Processor) add} your own processor. + *

+ * For custom types, you must add processors to {@link MarshalProcessor} and {@link UnmarshalProcessor}. *

Example

*
{@code
  * public interface GL {
@@ -177,6 +183,7 @@ private static String getMethodEntrypoint(Method method) {
 
     // type
 
+    @Deprecated
     private static ValueLayout getValueLayout(Class carrier) {
         if (carrier == boolean.class) return ValueLayout.JAVA_BOOLEAN;
         if (carrier == char.class) return ValueLayout.JAVA_CHAR;
@@ -555,20 +562,10 @@ private static String createSignatureString(Method method) {
     private static void verifyMethods(List list, Map signatureStringMap) {
         for (Method method : list) {
             String signature = signatureStringMap.get(method);
+
             // check method return type
             final Class returnType = method.getReturnType();
-            if (Struct.class.isAssignableFrom(returnType)) {
-                boolean foundAllocator = false;
-                for (Field field : returnType.getDeclaredFields()) {
-                    if (Modifier.isStatic(field.getModifiers()) && field.getType() == StructAllocator.class) {
-                        foundAllocator = true;
-                        break;
-                    }
-                }
-                if (!foundAllocator) {
-                    throw new IllegalStateException("The struct " + returnType + " must contain one public static field that is StructAllocator");
-                }
-            } else if (!isValidReturnType(returnType)) {
+            if (!isValidReturnType(returnType)) {
                 throw new IllegalStateException("Invalid return type: " + signature);
             }
 
@@ -637,7 +634,7 @@ private static boolean isValidParamType(Class aClass) {
     }
 
     private static boolean isValidReturnType(Class aClass) {
-        return aClass == MethodHandle.class || ProcessorTypes.isRegistered(aClass);
+        return aClass == MethodHandle.class || ProcessorTypes.isRegisteredExactly(aClass);
     }
 
     private static DowncallData generateData(
@@ -764,6 +761,7 @@ private static DowncallData generateData(
             lookup);
     }
 
+    @Deprecated
     private static Field getStructAllocatorField(Class aClass) {
         for (Field field : aClass.getDeclaredFields()) {
             if (Modifier.isStatic(field.getModifiers()) && field.getType() == StructAllocator.class) {
diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java
index 7b37826..553c045 100644
--- a/src/main/java/overrun/marshal/Upcall.java
+++ b/src/main/java/overrun/marshal/Upcall.java
@@ -18,6 +18,7 @@
 
 import overrun.marshal.gen.processor.ProcessorType;
 import overrun.marshal.gen.processor.ProcessorTypes;
+import overrun.marshal.gen.processor.UnmarshalProcessor;
 
 import java.lang.foreign.Arena;
 import java.lang.foreign.FunctionDescriptor;
@@ -31,6 +32,10 @@
  * 

* The target method must NOT throw any exception. * Otherwise, the JVM might crash. + *

+ * Returning an {@code Upcall} from a downcall method requires a + * {@linkplain ProcessorTypes#registerUpcall(Class, ProcessorType.Upcall.Factory) registration} to tell + * {@link UnmarshalProcessor} how to create an instance of the {@code Upcall}. *

Example

*
{@code
  * // The implementation must be public if you use Type
@@ -38,11 +43,7 @@
  * @FunctionalInterface
  * public interface MyCallback extends Upcall {
  *     // Create a type wrapper
- *     // (Optional) Register to ProcessorTypes to allow C functions to return the upcall instance
- *     Type TYPE = Upcall.register(
- *         Upcall.type("invoke", FunctionDescriptor.of(JAVA_INT, JAVA_INT)),
- *         stub -> i -> invoke(stub, i)
- *     );
+ *     Type TYPE = Upcall.type("invoke", FunctionDescriptor.of(JAVA_INT, JAVA_INT));
  *
  *     // The function to be invoked in C
  *     int invoke(int i);
@@ -120,19 +121,6 @@ static  Type type(Class tClass, String targetName, Funct
         return new Type<>(tClass, targetName, descriptor);
     }
 
-    /**
-     * Registers the given type to {@link ProcessorTypes}.
-     *
-     * @param type    the {@link Type}
-     * @param factory the factory
-     * @param      the type of the upcall
-     * @return {@code type}
-     */
-    static  Type register(Type type, ProcessorType.Upcall.Factory factory) {
-        ProcessorTypes.registerUpcall(type.typeClass, factory);
-        return type;
-    }
-
     /**
      * The type wrapper of an upcall interface.
      * The constructor uses heavy reflection, and you should always cache it as a static field.
diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java
index aba0bad..1e7e5d5 100644
--- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java
+++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java
@@ -24,7 +24,6 @@
 import java.lang.foreign.MemorySegment;
 import java.lang.foreign.SegmentAllocator;
 import java.lang.foreign.ValueLayout;
-import java.util.Objects;
 
 import static java.lang.constant.ConstantDescs.*;
 import static overrun.marshal.internal.Constants.CD_MemorySegment;
@@ -296,6 +295,10 @@ private Struct(Class typeClass, @Nullable StructAllocatorSpec allocatorSpe
             this.allocatorSpec = allocatorSpec;
         }
 
+        public static IllegalStateException noAllocatorException(Class typeClass) {
+            return new IllegalStateException("No allocator registered for struct " + typeClass);
+        }
+
         public Class typeClass() {
             return typeClass;
         }
@@ -306,7 +309,10 @@ public StructAllocatorSpec allocatorSpec() {
         }
 
         public StructAllocatorSpec checkAllocator() {
-            return Objects.requireNonNull(allocatorSpec(), "No allocator for struct " + typeClass);
+            if (allocatorSpec() != null) {
+                return allocatorSpec();
+            }
+            throw noAllocatorException(typeClass());
         }
 
         @Override
@@ -335,6 +341,10 @@ private Upcall(Class typeClass, @Nullable Factory factory) {
             this.factory = factory;
         }
 
+        public static IllegalStateException noFactoryException(Class typeClass) {
+            return new IllegalStateException("No factory registered for upcall " + typeClass);
+        }
+
         @FunctionalInterface
         public interface Factory {
             T create(MemorySegment stub);
@@ -350,7 +360,10 @@ public Factory factory() {
         }
 
         public Factory checkFactory() {
-            return Objects.requireNonNull(factory(), "No factory for upcall " + typeClass());
+            if (factory() != null) {
+                return factory();
+            }
+            throw noFactoryException(typeClass());
         }
 
         @Override
diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java
index 6c156eb..82fd500 100644
--- a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java
+++ b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java
@@ -25,7 +25,6 @@
 import java.lang.foreign.SegmentAllocator;
 import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
-import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.NoSuchElementException;
@@ -38,7 +37,6 @@
  */
 public final class ProcessorTypes {
     private static final Map, ProcessorType> map = new LinkedHashMap<>();
-    private static final Map, ProcessorType> mapImmutable = Collections.unmodifiableMap(map);
 
     static {
         register(void.class, ProcessorType.Void.INSTANCE);
@@ -168,4 +166,17 @@ public static boolean isRegistered(Class aClass) {
         }
         return false;
     }
+
+    /**
+     * {@return {@code true} if the given class is registered}
+     * For an array type, returns {@code true} if its component type is registered.
+     * 

+ * Unlike {@link #isRegistered(Class)}, this method disallows superclasses. + * + * @param aClass the class + */ + public static boolean isRegisteredExactly(Class aClass) { + if (aClass.isArray()) return isRegisteredExactly(aClass.componentType()); + return map.containsKey(aClass); + } } diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/struct/Struct.java index 3175663..b8cca7b 100644 --- a/src/main/java/overrun/marshal/struct/Struct.java +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -17,6 +17,8 @@ package overrun.marshal.struct; import overrun.marshal.Unmarshal; +import overrun.marshal.gen.processor.ProcessorTypes; +import overrun.marshal.gen.processor.UnmarshalProcessor; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; @@ -26,6 +28,10 @@ /** * The representation of a C structure. + *

+ * Returning a {@code Struct} from a downcall method requires a + * {@linkplain ProcessorTypes#registerStruct(Class, StructAllocatorSpec) registration} to tell + * {@link UnmarshalProcessor} how to create an instance of the {@code Struct}. * * @param the type of the actual structure interface * @author squid233 diff --git a/src/main/java/overrun/marshal/struct/StructAllocator.java b/src/main/java/overrun/marshal/struct/StructAllocator.java index 475c4bb..7f7965b 100644 --- a/src/main/java/overrun/marshal/struct/StructAllocator.java +++ b/src/main/java/overrun/marshal/struct/StructAllocator.java @@ -17,7 +17,6 @@ package overrun.marshal.struct; import overrun.marshal.LayoutBuilder; -import overrun.marshal.gen.processor.ProcessorTypes; import java.lang.classfile.ClassFile; import java.lang.classfile.TypeKind; @@ -146,7 +145,6 @@ public final class StructAllocator implements StructAllocatorSpec { * @param layout the struct layout */ public StructAllocator(MethodHandles.Lookup lookup, StructLayout layout) { - ProcessorTypes.registerStruct(lookup.lookupClass(), this); this.layout = layout; final byte[] bytes = buildBytecode(lookup, layout); try { diff --git a/src/test/java/overrun/marshal/test/downcall/IDowncall.java b/src/test/java/overrun/marshal/test/downcall/IDowncall.java index 57bc4f3..ff56b24 100644 --- a/src/test/java/overrun/marshal/test/downcall/IDowncall.java +++ b/src/test/java/overrun/marshal/test/downcall/IDowncall.java @@ -21,6 +21,7 @@ import overrun.marshal.DowncallOption; import overrun.marshal.gen.*; import overrun.marshal.gen.processor.ProcessorType; +import overrun.marshal.gen.processor.ProcessorTypes; import overrun.marshal.struct.ByValue; import overrun.marshal.test.struct.Vector3; import overrun.marshal.test.upcall.SimpleUpcall; @@ -40,6 +41,8 @@ public interface IDowncall { Map MAP = Map.of("testDefault", FunctionDescriptor.of(ValueLayout.JAVA_INT)); static IDowncall getInstance(boolean testDefaultNull) { + ProcessorTypes.registerStruct(Vector3.class, Vector3.OF); + ProcessorTypes.registerUpcall(SimpleUpcall.class, stub -> i -> SimpleUpcall.invoke(stub, i)); return Downcall.load(MethodHandles.lookup(), DowncallProvider.lookup(testDefaultNull), DowncallOption.descriptors(MAP)); } diff --git a/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java b/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java index 4892708..2fdc7d4 100644 --- a/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java +++ b/src/test/java/overrun/marshal/test/upcall/SimpleUpcall.java @@ -31,10 +31,7 @@ */ @FunctionalInterface public interface SimpleUpcall extends Upcall { - Type TYPE = Upcall.register( - Upcall.type("invoke", FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)), - stub -> i -> invoke(stub, i) - ); + Type TYPE = Upcall.type("invoke", FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)); int invoke(int i); From b349a908788298d3a847d812776375303cc5b91e Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 24 Aug 2024 17:41:48 +0800 Subject: [PATCH 05/13] Update Processor --- src/main/java/overrun/marshal/Downcall.java | 62 ++++++++----------- .../gen/processor/AfterInvokeProcessor.java | 11 +++- .../marshal/gen/processor/BaseProcessor.java | 5 +- .../gen/processor/BeforeInvokeProcessor.java | 25 +++++--- .../gen/processor/BeforeReturnProcessor.java | 8 +-- .../marshal/gen/processor/CheckProcessor.java | 10 +-- .../gen/processor/MarshalProcessor.java | 8 ++- .../marshal/gen/processor/Processor.java | 9 +-- .../gen/processor/UnmarshalProcessor.java | 15 +++-- 9 files changed, 80 insertions(+), 73 deletions(-) diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index e5d08f9..a8e23a4 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -23,7 +23,6 @@ import overrun.marshal.internal.data.DowncallData; import overrun.marshal.struct.ByValue; import overrun.marshal.struct.Struct; -import overrun.marshal.struct.StructAllocator; import java.lang.classfile.ClassFile; import java.lang.classfile.CodeBuilder; @@ -34,7 +33,6 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Parameter; @@ -353,9 +351,7 @@ private static Map.Entry buildBytecode(MethodHandles.Looku case STACK -> !isFirstAllocator; }; - var beforeReturnContext = new BeforeReturnProcessor.Context(hasMemoryStack); - - CheckProcessor.getInstance().process(codeBuilder, new CheckProcessor.Context(parameters)); + CheckProcessor.getInstance().process(new CheckProcessor.Context(codeBuilder, parameters)); int allocatorSlot; if (isFirstAllocator) { @@ -375,8 +371,12 @@ private static Map.Entry buildBytecode(MethodHandles.Looku blockCodeBuilder -> { // before invoke Map refSlotMap = new HashMap<>(); - BeforeInvokeProcessor.getInstance().process(blockCodeBuilder, - new BeforeInvokeProcessor.Context(parameters, refSlotMap, allocatorSlot)); + BeforeInvokeProcessor.getInstance().process( + new BeforeInvokeProcessor.Context(blockCodeBuilder, + parameters, + refSlotMap, + allocatorSlot) + ); // invoke blockCodeBuilder.getstatic(cd_thisClass, entrypoint, CD_MethodHandle); @@ -387,11 +387,13 @@ private static Map.Entry buildBytecode(MethodHandles.Looku if (refSlotMap.containsKey(parameter)) { blockCodeBuilder.aload(refSlotMap.get(parameter)); } else { - MarshalProcessor.getInstance().processAndCheck(blockCodeBuilder, - new MarshalProcessor.Context(processorType, - StringCharset.getCharset(parameter), - blockCodeBuilder.parameterSlot(i), - allocatorSlot)); + MarshalProcessor marshalProcessor = MarshalProcessor.getInstance(); + MarshalProcessor.Context context = new MarshalProcessor.Context(blockCodeBuilder, + processorType, + StringCharset.getCharset(parameter), + blockCodeBuilder.parameterSlot(i), + allocatorSlot); + marshalProcessor.checkProcessed(marshalProcessor.process(context), context); } downcallClassDescList.add(processorType.downcallClassDesc()); } @@ -408,21 +410,23 @@ private static Map.Entry buildBytecode(MethodHandles.Looku } // after invoke - AfterInvokeProcessor.getInstance().process(blockCodeBuilder, new AfterInvokeProcessor.Context(parameters, refSlotMap)); + AfterInvokeProcessor.getInstance().process(new AfterInvokeProcessor.Context(blockCodeBuilder, parameters, refSlotMap)); // before return - BeforeReturnProcessor.getInstance().process(blockCodeBuilder, beforeReturnContext); + BeforeReturnProcessor.getInstance().process(new BeforeReturnProcessor.Context(blockCodeBuilder, hasMemoryStack)); // return - UnmarshalProcessor.getInstance().processAndCheck(blockCodeBuilder, - new UnmarshalProcessor.Context(returnProcessorType, - returnType, - StringCharset.getCharset(method), - resultSlot)); + UnmarshalProcessor unmarshalProcessor = UnmarshalProcessor.getInstance(); + UnmarshalProcessor.Context returnContext = new UnmarshalProcessor.Context(blockCodeBuilder, + returnProcessorType, + returnType, + StringCharset.getCharset(method), + resultSlot); + unmarshalProcessor.checkProcessed(unmarshalProcessor.process(returnContext), returnContext); blockCodeBuilder.return_(returnTypeKind); }, catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> { - BeforeReturnProcessor.getInstance().process(blockCodeBuilder, beforeReturnContext); + BeforeReturnProcessor.getInstance().process(new BeforeReturnProcessor.Context(blockCodeBuilder, hasMemoryStack)); // rethrow the exception int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); blockCodeBuilder.astore(slot) @@ -674,13 +678,7 @@ private static DowncallData generateData( final boolean isSizedSeg = sizedSeg != null; final boolean isSized = sized != null; if (Struct.class.isAssignableFrom(returnType)) { - final var structAllocatorField = Objects.requireNonNull(getStructAllocatorField(returnType)); - final StructLayout structLayout; - try { - structLayout = ((StructAllocator) structAllocatorField.get(null)).layout(); - } catch (IllegalAccessException e) { - throw new RuntimeException(e); - } + final StructLayout structLayout = ((ProcessorType.Struct) ProcessorTypes.fromClass(returnType)).checkAllocator().layout(); if (methodByValue) { retLayout = structLayout; } else { @@ -760,14 +758,4 @@ private static DowncallData generateData( Collections.unmodifiableMap(map), lookup); } - - @Deprecated - private static Field getStructAllocatorField(Class aClass) { - for (Field field : aClass.getDeclaredFields()) { - if (Modifier.isStatic(field.getModifiers()) && field.getType() == StructAllocator.class) { - return field; - } - } - return null; - } } diff --git a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java index edb5446..c2ec558 100644 --- a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java @@ -32,11 +32,16 @@ * @since 0.1.0 */ public final class AfterInvokeProcessor extends BaseProcessor { - public record Context(List parameters, Map refSlotMap) { + public record Context( + CodeBuilder builder, + List parameters, + Map refSlotMap + ) { } @Override - public boolean process(CodeBuilder builder, Context context) { + public boolean process(Context context) { + CodeBuilder builder = context.builder(); List parameters = context.parameters(); var refSlotMap = context.refSlotMap(); for (int i = 0, size = parameters.size(); i < size; i++) { @@ -73,7 +78,7 @@ public boolean process(CodeBuilder builder, Context context) { } } } - return super.process(builder, context); + return super.process(context); } public static AfterInvokeProcessor getInstance() { diff --git a/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java b/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java index 64e22d3..66896ce 100644 --- a/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java @@ -16,7 +16,6 @@ package overrun.marshal.gen.processor; -import java.lang.classfile.CodeBuilder; import java.util.ArrayList; import java.util.List; @@ -34,9 +33,9 @@ public abstract class BaseProcessor implements Processor { protected final List> processors = new ArrayList<>(); @Override - public boolean process(CodeBuilder builder, C context) { + public boolean process(C context) { for (var processor : processors) { - if (processor.process(builder, context)) { + if (processor.process(context)) { return true; } } diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java index eb6a053..ad3e234 100644 --- a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java @@ -32,11 +32,17 @@ * @since 0.1.0 */ public final class BeforeInvokeProcessor extends BaseProcessor { - public record Context(List parameters, Map refSlot, int allocatorSlot) { + public record Context( + CodeBuilder builder, + List parameters, + Map refSlot, + int allocatorSlot + ) { } @Override - public boolean process(CodeBuilder builder, Context context) { + public boolean process(Context context) { + CodeBuilder builder = context.builder(); List parameters = context.parameters(); for (int i = 0, size = parameters.size(); i < size; i++) { Parameter parameter = parameters.get(i); @@ -44,15 +50,18 @@ public boolean process(CodeBuilder builder, Context context) { parameter.getDeclaredAnnotation(Ref.class) != null) { int local = builder.allocateLocal(TypeKind.ReferenceType); context.refSlot().put(parameter, local); - MarshalProcessor.getInstance().processAndCheck(builder, - new MarshalProcessor.Context(ProcessorTypes.fromParameter(parameter), - StringCharset.getCharset(parameter), - builder.parameterSlot(i), - context.allocatorSlot())); + MarshalProcessor marshalProcessor = MarshalProcessor.getInstance(); + MarshalProcessor.Context context1 = new MarshalProcessor.Context(builder, + ProcessorTypes.fromParameter(parameter), + StringCharset.getCharset(parameter), + builder.parameterSlot(i), + context.allocatorSlot() + ); + marshalProcessor.checkProcessed(marshalProcessor.process(context1), context1); builder.astore(local); } } - return super.process(builder, context); + return super.process(context); } public static BeforeInvokeProcessor getInstance() { diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java index 1d0334f..54c3b46 100644 --- a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java @@ -28,18 +28,18 @@ * @since 0.1.0 */ public final class BeforeReturnProcessor extends BaseProcessor { - public record Context(boolean hasMemoryStack) { + public record Context(CodeBuilder builder, boolean hasMemoryStack) { } @Override - public boolean process(CodeBuilder builder, Context context) { + public boolean process(Context context) { if (context.hasMemoryStack()) { - builder.invokestatic(CD_MemoryStack, + context.builder().invokestatic(CD_MemoryStack, "popLocal", MTD_void, true); } - return super.process(builder, context); + return super.process(context); } public static BeforeReturnProcessor getInstance() { diff --git a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java index 01bb7e9..ea02bbf 100644 --- a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java @@ -32,20 +32,20 @@ * @since 0.1.0 */ public final class CheckProcessor extends BaseProcessor { - public record Context(List parameters) { + public record Context(CodeBuilder builder, List parameters) { } @Override - public boolean process(CodeBuilder builder, Context context) { + public boolean process(Context context) { + CodeBuilder builder = context.builder(); List parameters = context.parameters(); for (int i = 0, size = parameters.size(); i < size; i++) { Parameter parameter = parameters.get(i); if (parameter.getType().isArray()) { Sized sized = parameter.getDeclaredAnnotation(Sized.class); if (sized != null) { - int slot = builder.parameterSlot(i); builder.ldc(sized.value()) - .aload(slot) + .aload(builder.parameterSlot(i)) .arraylength() .invokestatic(CD_Checks, "checkArraySize", @@ -53,7 +53,7 @@ public boolean process(CodeBuilder builder, Context context) { } } } - return super.process(builder, context); + return super.process(context); } public static CheckProcessor getInstance() { diff --git a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java index 90d59b0..c459447 100644 --- a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java @@ -30,6 +30,7 @@ */ public final class MarshalProcessor extends BaseProcessor { public record Context( + CodeBuilder builder, ProcessorType type, String charset, int variableSlot, @@ -39,20 +40,21 @@ public record Context( @SuppressWarnings("preview") @Override - public boolean process(CodeBuilder builder, Context context) { + public boolean process(Context context) { + CodeBuilder builder = context.builder(); final int allocatorSlot = context.allocatorSlot(); final int variableSlot = context.variableSlot(); switch (context.type()) { case ProcessorType.Allocator _ -> builder.aload(variableSlot); case ProcessorType.Custom _ -> { - return super.process(builder, context); + return super.process(context); } case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); case ProcessorType.Array array -> { switch (array.componentType()) { case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _, ProcessorType.Custom _ -> { - return super.process(builder, context); + return super.process(context); } case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); case ProcessorType.Str _ -> builder diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/Processor.java index 1131882..733baa8 100644 --- a/src/main/java/overrun/marshal/gen/processor/Processor.java +++ b/src/main/java/overrun/marshal/gen/processor/Processor.java @@ -16,8 +16,6 @@ package overrun.marshal.gen.processor; -import java.lang.classfile.CodeBuilder; - /** * Processor * @@ -29,11 +27,10 @@ public interface Processor { /** * Processes with the context * - * @param builder the code builder * @param context the context * @return {@code true} if processed; {@code false} otherwise */ - boolean process(CodeBuilder builder, C context); + boolean process(C context); default void checkProcessed(boolean processed, C context) { if (!processed) { @@ -41,7 +38,7 @@ default void checkProcessed(boolean processed, C context) { } } - default void processAndCheck(CodeBuilder builder, C context) { - checkProcessed(process(builder, context), context); + default void processAndCheck(C context) { + checkProcessed(process(context), context); } } diff --git a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java index 1cf6724..ac355bd 100644 --- a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java @@ -30,16 +30,23 @@ * @since 0.1.0 */ public final class UnmarshalProcessor extends BaseProcessor { - public record Context(ProcessorType type, Class originalType, String charset, int variableSlot) { + public record Context( + CodeBuilder builder, + ProcessorType type, + Class originalType, + String charset, + int variableSlot + ) { } @SuppressWarnings("preview") @Override - public boolean process(CodeBuilder builder, Context context) { + public boolean process(Context context) { + CodeBuilder builder = context.builder(); int variableSlot = context.variableSlot(); switch (context.type()) { case ProcessorType.Allocator _, ProcessorType.Custom _ -> { - return super.process(builder, context); + return super.process(context); } case ProcessorType.Void _ -> { } @@ -47,7 +54,7 @@ public boolean process(CodeBuilder builder, Context context) { switch (array.componentType()) { case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _, ProcessorType.Custom _, ProcessorType.Struct _, ProcessorType.Upcall _ -> { - return super.process(builder, context); + return super.process(context); } case ProcessorType.Void _ -> throw new AssertionError("should not reach here"); case ProcessorType.Str _ -> builder From 50a671b7426d69790bcbe4dde803bfc407353bec Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 24 Aug 2024 18:08:25 +0800 Subject: [PATCH 06/13] Add CodeInserter and TypedCodeProcessor --- src/main/java/overrun/marshal/Downcall.java | 140 +++++++++--------- .../gen/processor/AfterInvokeProcessor.java | 12 +- .../gen/processor/BeforeInvokeProcessor.java | 15 +- .../gen/processor/BeforeReturnProcessor.java | 10 +- .../marshal/gen/processor/CheckProcessor.java | 9 +- .../{Processor.java => CodeInserter.java} | 27 ++-- .../gen/processor/MarshalProcessor.java | 13 +- .../marshal/gen/processor/ProcessorType.java | 63 ++++++-- ...Processor.java => TypedCodeProcessor.java} | 24 ++- .../gen/processor/UnmarshalProcessor.java | 13 +- 10 files changed, 169 insertions(+), 157 deletions(-) rename src/main/java/overrun/marshal/gen/processor/{Processor.java => CodeInserter.java} (57%) rename src/main/java/overrun/marshal/gen/processor/{BaseProcessor.java => TypedCodeProcessor.java} (61%) diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index a8e23a4..2909d8a 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -97,10 +97,10 @@ * }

*

Processor Types

* Processor types are used to process builtin and custom types. - * You can {@linkplain ProcessorTypes#register(Class, ProcessorType) register} a type - * and {@linkplain BaseProcessor#addProcessor(Processor) add} your own processor. + * You can {@linkplain ProcessorTypes#register(Class, ProcessorType) register} a type and your own processor + * as builtin processors may require an additional processor. *

- * For custom types, you must add processors to {@link MarshalProcessor} and {@link UnmarshalProcessor}. + * For custom types, you should add processors to subclasses of {@link TypedCodeProcessor}. *

Example

*
{@code
  * public interface GL {
@@ -350,8 +350,9 @@ private static Map.Entry buildBytecode(MethodHandles.Looku
                             case NONE, ALLOCATOR, ARENA -> false;
                             case STACK -> !isFirstAllocator;
                         };
+                        var beforeReturnContext = new BeforeReturnProcessor.Context(hasMemoryStack);
 
-                        CheckProcessor.getInstance().process(new CheckProcessor.Context(codeBuilder, parameters));
+                        CheckProcessor.getInstance().process(codeBuilder, new CheckProcessor.Context(parameters));
 
                         int allocatorSlot;
                         if (isFirstAllocator) {
@@ -367,76 +368,71 @@ private static Map.Entry buildBytecode(MethodHandles.Looku
                             allocatorSlot = -1;
                         }
 
-                        codeBuilder.trying(
-                            blockCodeBuilder -> {
-                                // before invoke
-                                Map refSlotMap = new HashMap<>();
-                                BeforeInvokeProcessor.getInstance().process(
-                                    new BeforeInvokeProcessor.Context(blockCodeBuilder,
-                                        parameters,
-                                        refSlotMap,
-                                        allocatorSlot)
-                                );
-
-                                // invoke
-                                blockCodeBuilder.getstatic(cd_thisClass, entrypoint, CD_MethodHandle);
-                                List downcallClassDescList = new ArrayList<>();
-                                for (int i = methodData.skipFirstParam() ? 1 : 0, size = parameters.size(); i < size; i++) {
-                                    Parameter parameter = parameters.get(i);
-                                    ProcessorType processorType = ProcessorTypes.fromParameter(parameter);
-                                    if (refSlotMap.containsKey(parameter)) {
-                                        blockCodeBuilder.aload(refSlotMap.get(parameter));
-                                    } else {
-                                        MarshalProcessor marshalProcessor = MarshalProcessor.getInstance();
-                                        MarshalProcessor.Context context = new MarshalProcessor.Context(blockCodeBuilder,
-                                            processorType,
-                                            StringCharset.getCharset(parameter),
-                                            blockCodeBuilder.parameterSlot(i),
-                                            allocatorSlot);
-                                        marshalProcessor.checkProcessed(marshalProcessor.process(context), context);
-                                    }
-                                    downcallClassDescList.add(processorType.downcallClassDesc());
-                                }
-                                ProcessorType returnProcessorType = ProcessorTypes.fromMethod(method);
-                                ClassDesc cd_returnDowncall = returnProcessorType.downcallClassDesc();
-                                TypeKind returnDowncallTypeKind = TypeKind.from(cd_returnDowncall);
-                                blockCodeBuilder.invokevirtual(CD_MethodHandle,
-                                    "invokeExact",
-                                    MethodTypeDesc.of(cd_returnDowncall, downcallClassDescList));
-                                boolean returnVoid = returnType == void.class;
-                                int resultSlot = returnVoid ? -1 : blockCodeBuilder.allocateLocal(returnDowncallTypeKind);
-                                if (!returnVoid) {
-                                    blockCodeBuilder.storeLocal(returnDowncallTypeKind, resultSlot);
+                        codeBuilder.trying(blockCodeBuilder -> {
+                            // before invoke
+                            Map refSlotMap = new HashMap<>();
+                            BeforeInvokeProcessor.getInstance().process(blockCodeBuilder,
+                                new BeforeInvokeProcessor.Context(
+                                    parameters,
+                                    refSlotMap,
+                                    allocatorSlot)
+                            );
+
+                            // invoke
+                            blockCodeBuilder.getstatic(cd_thisClass, entrypoint, CD_MethodHandle);
+                            List downcallClassDescList = new ArrayList<>();
+                            for (int i = methodData.skipFirstParam() ? 1 : 0, size = parameters.size(); i < size; i++) {
+                                Parameter parameter = parameters.get(i);
+                                ProcessorType processorType = ProcessorTypes.fromParameter(parameter);
+                                if (refSlotMap.containsKey(parameter)) {
+                                    blockCodeBuilder.aload(refSlotMap.get(parameter));
+                                } else {
+                                    MarshalProcessor.getInstance().process(blockCodeBuilder, processorType, new MarshalProcessor.Context(
+                                        StringCharset.getCharset(parameter),
+                                        blockCodeBuilder.parameterSlot(i),
+                                        allocatorSlot
+                                    ));
                                 }
+                                downcallClassDescList.add(processorType.downcallClassDesc());
+                            }
+                            ProcessorType returnProcessorType = ProcessorTypes.fromMethod(method);
+                            ClassDesc cd_returnDowncall = returnProcessorType.downcallClassDesc();
+                            TypeKind returnDowncallTypeKind = TypeKind.from(cd_returnDowncall);
+                            blockCodeBuilder.invokevirtual(CD_MethodHandle,
+                                "invokeExact",
+                                MethodTypeDesc.of(cd_returnDowncall, downcallClassDescList));
+                            boolean returnVoid = returnType == void.class;
+                            int resultSlot = returnVoid ? -1 : blockCodeBuilder.allocateLocal(returnDowncallTypeKind);
+                            if (!returnVoid) {
+                                blockCodeBuilder.storeLocal(returnDowncallTypeKind, resultSlot);
+                            }
 
-                                // after invoke
-                                AfterInvokeProcessor.getInstance().process(new AfterInvokeProcessor.Context(blockCodeBuilder, parameters, refSlotMap));
-
-                                // before return
-                                BeforeReturnProcessor.getInstance().process(new BeforeReturnProcessor.Context(blockCodeBuilder, hasMemoryStack));
-
-                                // return
-                                UnmarshalProcessor unmarshalProcessor = UnmarshalProcessor.getInstance();
-                                UnmarshalProcessor.Context returnContext = new UnmarshalProcessor.Context(blockCodeBuilder,
-                                    returnProcessorType,
-                                    returnType,
-                                    StringCharset.getCharset(method),
-                                    resultSlot);
-                                unmarshalProcessor.checkProcessed(unmarshalProcessor.process(returnContext), returnContext);
-                                blockCodeBuilder.return_(returnTypeKind);
-                            },
-                            catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> {
-                                BeforeReturnProcessor.getInstance().process(new BeforeReturnProcessor.Context(blockCodeBuilder, hasMemoryStack));
-                                // rethrow the exception
-                                int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType);
-                                blockCodeBuilder.astore(slot)
-                                    .new_(CD_IllegalStateException)
-                                    .dup()
-                                    .ldc(methodData.signatureString())
-                                    .aload(slot)
-                                    .invokespecial(CD_IllegalStateException, INIT_NAME, MTD_void_String_Throwable)
-                                    .athrow();
-                            }));
+                            // after invoke
+                            AfterInvokeProcessor.getInstance().process(blockCodeBuilder,
+                                new AfterInvokeProcessor.Context(parameters, refSlotMap));
+
+                            // before return
+                            BeforeReturnProcessor.getInstance().process(blockCodeBuilder, beforeReturnContext);
+
+                            // return
+                            UnmarshalProcessor.getInstance().process(blockCodeBuilder, returnProcessorType, new UnmarshalProcessor.Context(
+                                returnType,
+                                StringCharset.getCharset(method),
+                                resultSlot
+                            ));
+                            blockCodeBuilder.return_(returnTypeKind);
+                        }, catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> {
+                            BeforeReturnProcessor.getInstance().process(blockCodeBuilder, beforeReturnContext);
+                            // rethrow the exception
+                            int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType);
+                            blockCodeBuilder.astore(slot)
+                                .new_(CD_IllegalStateException)
+                                .dup()
+                                .ldc(methodData.signatureString())
+                                .aload(slot)
+                                .invokespecial(CD_IllegalStateException, INIT_NAME, MTD_void_String_Throwable)
+                                .athrow();
+                        }));
 
                         //endregion
                     });
diff --git a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java
index c2ec558..45b974a 100644
--- a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java
@@ -31,17 +31,15 @@
  * @author squid233
  * @since 0.1.0
  */
-public final class AfterInvokeProcessor extends BaseProcessor {
+public final class AfterInvokeProcessor extends CodeInserter {
     public record Context(
-        CodeBuilder builder,
         List parameters,
         Map refSlotMap
     ) {
     }
 
     @Override
-    public boolean process(Context context) {
-        CodeBuilder builder = context.builder();
+    public void process(CodeBuilder builder, Context context) {
         List parameters = context.parameters();
         var refSlotMap = context.refSlotMap();
         for (int i = 0, size = parameters.size(); i < size; i++) {
@@ -55,9 +53,7 @@ public boolean process(Context context) {
                     builder
                         .aload(refSlot)
                         .aload(parameterSlot)
-                        .invokestatic(CD_Unmarshal,
-                        "copy",
-                        switch (array.componentType()) {
+                        .invokestatic(CD_Unmarshal, "copy", switch (array.componentType()) {
                             case ProcessorType.Str _ ->
                                 StringCharset.getCharset(builder, StringCharset.getCharset(parameter)) ?
                                     MTD_void_MemorySegment_StringArray_Charset :
@@ -78,7 +74,7 @@ public boolean process(Context context) {
                 }
             }
         }
-        return super.process(context);
+        super.process(builder, context);
     }
 
     public static AfterInvokeProcessor getInstance() {
diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java
index ad3e234..681d221 100644
--- a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java
@@ -31,9 +31,8 @@
  * @author squid233
  * @since 0.1.0
  */
-public final class BeforeInvokeProcessor extends BaseProcessor {
+public final class BeforeInvokeProcessor extends CodeInserter {
     public record Context(
-        CodeBuilder builder,
         List parameters,
         Map refSlot,
         int allocatorSlot
@@ -41,8 +40,7 @@ public record Context(
     }
 
     @Override
-    public boolean process(Context context) {
-        CodeBuilder builder = context.builder();
+    public void process(CodeBuilder builder, Context context) {
         List parameters = context.parameters();
         for (int i = 0, size = parameters.size(); i < size; i++) {
             Parameter parameter = parameters.get(i);
@@ -50,18 +48,15 @@ public boolean process(Context context) {
                 parameter.getDeclaredAnnotation(Ref.class) != null) {
                 int local = builder.allocateLocal(TypeKind.ReferenceType);
                 context.refSlot().put(parameter, local);
-                MarshalProcessor marshalProcessor = MarshalProcessor.getInstance();
-                MarshalProcessor.Context context1 = new MarshalProcessor.Context(builder,
-                    ProcessorTypes.fromParameter(parameter),
+                MarshalProcessor.getInstance().process(builder, ProcessorTypes.fromParameter(parameter), new MarshalProcessor.Context(
                     StringCharset.getCharset(parameter),
                     builder.parameterSlot(i),
                     context.allocatorSlot()
-                );
-                marshalProcessor.checkProcessed(marshalProcessor.process(context1), context1);
+                ));
                 builder.astore(local);
             }
         }
-        return super.process(context);
+        super.process(builder, context);
     }
 
     public static BeforeInvokeProcessor getInstance() {
diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java
index 54c3b46..47f4f24 100644
--- a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java
@@ -27,19 +27,19 @@
  * @author squid233
  * @since 0.1.0
  */
-public final class BeforeReturnProcessor extends BaseProcessor {
-    public record Context(CodeBuilder builder, boolean hasMemoryStack) {
+public final class BeforeReturnProcessor extends CodeInserter {
+    public record Context(boolean hasMemoryStack) {
     }
 
     @Override
-    public boolean process(Context context) {
+    public void process(CodeBuilder builder, Context context) {
         if (context.hasMemoryStack()) {
-            context.builder().invokestatic(CD_MemoryStack,
+            builder.invokestatic(CD_MemoryStack,
                 "popLocal",
                 MTD_void,
                 true);
         }
-        return super.process(context);
+        super.process(builder, context);
     }
 
     public static BeforeReturnProcessor getInstance() {
diff --git a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java
index ea02bbf..82e1406 100644
--- a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java
@@ -31,13 +31,12 @@
  * @author squid233
  * @since 0.1.0
  */
-public final class CheckProcessor extends BaseProcessor {
-    public record Context(CodeBuilder builder, List parameters) {
+public final class CheckProcessor extends CodeInserter {
+    public record Context(List parameters) {
     }
 
     @Override
-    public boolean process(Context context) {
-        CodeBuilder builder = context.builder();
+    public void process(CodeBuilder builder, Context context) {
         List parameters = context.parameters();
         for (int i = 0, size = parameters.size(); i < size; i++) {
             Parameter parameter = parameters.get(i);
@@ -53,7 +52,7 @@ public boolean process(Context context) {
                 }
             }
         }
-        return super.process(context);
+        super.process(builder, context);
     }
 
     public static CheckProcessor getInstance() {
diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java
similarity index 57%
rename from src/main/java/overrun/marshal/gen/processor/Processor.java
rename to src/main/java/overrun/marshal/gen/processor/CodeInserter.java
index 733baa8..770aed4 100644
--- a/src/main/java/overrun/marshal/gen/processor/Processor.java
+++ b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java
@@ -16,29 +16,24 @@
 
 package overrun.marshal.gen.processor;
 
+import java.lang.classfile.CodeBuilder;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
- * Processor
- *
- * @param  context type
  * @author squid233
  * @since 0.1.0
  */
-public interface Processor {
-    /**
-     * Processes with the context
-     *
-     * @param context the context
-     * @return {@code true} if processed; {@code false} otherwise
-     */
-    boolean process(C context);
+public abstract class CodeInserter {
+    private final List> list = new ArrayList<>();
 
-    default void checkProcessed(boolean processed, C context) {
-        if (!processed) {
-            throw new IllegalStateException(this.getClass().getSimpleName() + " not processed: " + context);
+    public void process(CodeBuilder builder, T context) {
+        for (CodeInserter processor : list) {
+            processor.process(builder, context);
         }
     }
 
-    default void processAndCheck(C context) {
-        checkProcessed(process(context), context);
+    public void addProcessor(CodeInserter processor) {
+        list.add(processor);
     }
 }
diff --git a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java
index c459447..69896c8 100644
--- a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java
@@ -28,10 +28,8 @@
  * @author squid233
  * @since 0.1.0
  */
-public final class MarshalProcessor extends BaseProcessor {
+public final class MarshalProcessor extends TypedCodeProcessor {
     public record Context(
-        CodeBuilder builder,
-        ProcessorType type,
         String charset,
         int variableSlot,
         int allocatorSlot
@@ -40,21 +38,20 @@ public record Context(
 
     @SuppressWarnings("preview")
     @Override
-    public boolean process(Context context) {
-        CodeBuilder builder = context.builder();
+    public boolean process(CodeBuilder builder, ProcessorType type, Context context) {
         final int allocatorSlot = context.allocatorSlot();
         final int variableSlot = context.variableSlot();
-        switch (context.type()) {
+        switch (type) {
             case ProcessorType.Allocator _ -> builder.aload(variableSlot);
             case ProcessorType.Custom _ -> {
-                return super.process(context);
+                return super.process(builder, type, context);
             }
             case ProcessorType.Void _ -> throw new AssertionError("should not reach here");
             case ProcessorType.Array array -> {
                 switch (array.componentType()) {
                     case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _,
                          ProcessorType.Custom _ -> {
-                        return super.process(context);
+                        return super.process(builder, type, context);
                     }
                     case ProcessorType.Void _ -> throw new AssertionError("should not reach here");
                     case ProcessorType.Str _ -> builder
diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java
index 1e7e5d5..315bed1 100644
--- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java
+++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java
@@ -24,6 +24,7 @@
 import java.lang.foreign.MemorySegment;
 import java.lang.foreign.SegmentAllocator;
 import java.lang.foreign.ValueLayout;
+import java.util.Locale;
 
 import static java.lang.constant.ConstantDescs.*;
 import static overrun.marshal.internal.Constants.CD_MemorySegment;
@@ -90,6 +91,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.NONE;
         }
+
+        @Override
+        public String toString() {
+            return "void";
+        }
     }
 
     /**
@@ -99,48 +105,50 @@ enum Value implements ProcessorType {
         /**
          * {@code boolean} type
          */
-        BOOLEAN(CD_boolean, ValueLayout.JAVA_BOOLEAN),
+        BOOLEAN(CD_boolean, ValueLayout.JAVA_BOOLEAN, "boolean"),
         /**
          * {@code char} type
          */
-        CHAR(CD_char, ValueLayout.JAVA_CHAR),
+        CHAR(CD_char, ValueLayout.JAVA_CHAR, "char"),
         /**
          * {@code byte} type
          */
-        BYTE(CD_byte, ValueLayout.JAVA_BYTE),
+        BYTE(CD_byte, ValueLayout.JAVA_BYTE, "byte"),
         /**
          * {@code short} type
          */
-        SHORT(CD_short, ValueLayout.JAVA_SHORT),
+        SHORT(CD_short, ValueLayout.JAVA_SHORT, "short"),
         /**
          * {@code int} type
          */
-        INT(CD_int, ValueLayout.JAVA_INT),
+        INT(CD_int, ValueLayout.JAVA_INT, "int"),
         /**
          * {@code long} type
          */
-        LONG(CD_long, ValueLayout.JAVA_LONG),
+        LONG(CD_long, ValueLayout.JAVA_LONG, "long"),
         /**
          * {@code float} type
          */
-        FLOAT(CD_float, ValueLayout.JAVA_FLOAT),
+        FLOAT(CD_float, ValueLayout.JAVA_FLOAT, "float"),
         /**
          * {@code double} type
          */
-        DOUBLE(CD_double, ValueLayout.JAVA_DOUBLE),
+        DOUBLE(CD_double, ValueLayout.JAVA_DOUBLE, "double"),
         /**
          * {@link MemorySegment} type
          */
-        ADDRESS(CD_MemorySegment, ValueLayout.ADDRESS);
+        ADDRESS(CD_MemorySegment, ValueLayout.ADDRESS, MemorySegment.class.getSimpleName());
 
         private final ClassDesc classDesc;
         private final TypeKind typeKind;
         private final ValueLayout layout;
+        private final String toStringValue;
 
-        Value(ClassDesc classDesc, ValueLayout layout) {
+        Value(ClassDesc classDesc, ValueLayout layout, String toStringValue) {
             this.classDesc = classDesc;
             this.typeKind = TypeKind.from(classDesc);
             this.layout = layout;
+            this.toStringValue = toStringValue;
         }
 
         /**
@@ -166,6 +174,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.NONE;
         }
+
+        @Override
+        public String toString() {
+            return toStringValue;
+        }
     }
 
     /**
@@ -234,6 +247,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.NONE;
         }
+
+        @Override
+        public String toString() {
+            return name().toLowerCase(Locale.ROOT);
+        }
     }
 
     /**
@@ -257,6 +275,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.NONE; // this is invalid
         }
+
+        @Override
+        public String toString() {
+            return SegmentAllocator.class.getSimpleName();
+        }
     }
 
     /**
@@ -280,6 +303,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.STACK;
         }
+
+        @Override
+        public String toString() {
+            return String.class.getSimpleName();
+        }
     }
 
     /**
@@ -324,6 +352,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.NONE;
         }
+
+        @Override
+        public String toString() {
+            return "Struct(" + typeClass + ")";
+        }
     }
 
     /**
@@ -375,6 +408,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.ARENA;
         }
+
+        @Override
+        public String toString() {
+            return "Upcall(" + typeClass + ")";
+        }
     }
 
     /**
@@ -392,6 +430,11 @@ public ClassDesc downcallClassDesc() {
         public AllocatorRequirement allocationRequirement() {
             return AllocatorRequirement.stricter(AllocatorRequirement.STACK, componentType.allocationRequirement());
         }
+
+        @Override
+        public String toString() {
+            return "Array(" + componentType + ")";
+        }
     }
 
     /**
diff --git a/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java b/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java
similarity index 61%
rename from src/main/java/overrun/marshal/gen/processor/BaseProcessor.java
rename to src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java
index 66896ce..d757731 100644
--- a/src/main/java/overrun/marshal/gen/processor/BaseProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java
@@ -16,33 +16,27 @@
 
 package overrun.marshal.gen.processor;
 
+import java.lang.classfile.CodeBuilder;
 import java.util.ArrayList;
 import java.util.List;
 
 /**
- * A processor with a list of processors
- *
- * @param  context type
  * @author squid233
  * @since 0.1.0
  */
-public abstract class BaseProcessor implements Processor {
-    /**
-     * processors
-     */
-    protected final List> processors = new ArrayList<>();
+public abstract class TypedCodeProcessor {
+    private final List> list = new ArrayList<>();
 
-    @Override
-    public boolean process(C context) {
-        for (var processor : processors) {
-            if (processor.process(context)) {
+    public boolean process(CodeBuilder builder, ProcessorType type, T context) {
+        for (var processor : list) {
+            if (processor.process(builder, type, context)) {
                 return true;
             }
         }
-        return false;
+        throw new IllegalStateException(this.getClass().getSimpleName() + ": type '" + type + "' was not processed");
     }
 
-    public void addProcessor(Processor processor) {
-        processors.add(processor);
+    public void addProcessor(TypedCodeProcessor processor) {
+        list.add(processor);
     }
 }
diff --git a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java
index ac355bd..05ae34e 100644
--- a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java
@@ -29,10 +29,8 @@
  * @author squid233
  * @since 0.1.0
  */
-public final class UnmarshalProcessor extends BaseProcessor {
+public final class UnmarshalProcessor extends TypedCodeProcessor {
     public record Context(
-        CodeBuilder builder,
-        ProcessorType type,
         Class originalType,
         String charset,
         int variableSlot
@@ -41,12 +39,11 @@ public record Context(
 
     @SuppressWarnings("preview")
     @Override
-    public boolean process(Context context) {
-        CodeBuilder builder = context.builder();
+    public boolean process(CodeBuilder builder, ProcessorType type, Context context) {
         int variableSlot = context.variableSlot();
-        switch (context.type()) {
+        switch (type) {
             case ProcessorType.Allocator _, ProcessorType.Custom _ -> {
-                return super.process(context);
+                return super.process(builder, type, context);
             }
             case ProcessorType.Void _ -> {
             }
@@ -54,7 +51,7 @@ public boolean process(Context context) {
                 switch (array.componentType()) {
                     case ProcessorType.Allocator _, ProcessorType.Array _, ProcessorType.BoolConvert _,
                          ProcessorType.Custom _, ProcessorType.Struct _, ProcessorType.Upcall _ -> {
-                        return super.process(context);
+                        return super.process(builder, type, context);
                     }
                     case ProcessorType.Void _ -> throw new AssertionError("should not reach here");
                     case ProcessorType.Str _ -> builder

From dc5af9d3c49ec77ec9afe571ae5df7e6759e5dc4 Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Sat, 24 Aug 2024 20:04:37 +0800
Subject: [PATCH 07/13] Add DescriptorTransformer; update ByValue

---
 src/main/java/overrun/marshal/Checks.java     |   2 +-
 src/main/java/overrun/marshal/Downcall.java   | 142 +++---------------
 .../overrun/marshal/DowncallMethodData.java   |  14 +-
 src/main/java/overrun/marshal/gen/Sized.java  |  11 +-
 .../java/overrun/marshal/gen/SizedSeg.java    |   1 +
 src/main/java/overrun/marshal/gen/Type.java   |  92 ------------
 .../marshal/gen/processor/CheckProcessor.java |   5 +-
 .../marshal/gen/processor/CodeInserter.java   |   3 +-
 .../gen/processor/DescriptorTransformer.java  |  96 ++++++++++++
 .../marshal/gen/processor/Processor.java      |  25 +++
 .../marshal/gen/processor/ProcessorType.java  |  63 +++++---
 .../gen/processor/TypeTransformer.java        |  43 ++++++
 .../gen/processor/TypedCodeProcessor.java     |   3 +-
 .../overrun/marshal/internal/Constants.java   |   4 +-
 .../java/overrun/marshal/struct/ByValue.java  |   7 +-
 .../java/overrun/marshal/struct/Struct.java   |   6 +-
 .../test/downcall/DowncallProvider.java       |   7 +
 .../marshal/test/downcall/DowncallTest.java   |  18 ++-
 .../marshal/test/downcall/IDowncall.java      |   6 +-
 19 files changed, 298 insertions(+), 250 deletions(-)
 delete mode 100644 src/main/java/overrun/marshal/gen/Type.java
 create mode 100644 src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java
 create mode 100644 src/main/java/overrun/marshal/gen/processor/Processor.java
 create mode 100644 src/main/java/overrun/marshal/gen/processor/TypeTransformer.java

diff --git a/src/main/java/overrun/marshal/Checks.java b/src/main/java/overrun/marshal/Checks.java
index 02007cb..2c78455 100644
--- a/src/main/java/overrun/marshal/Checks.java
+++ b/src/main/java/overrun/marshal/Checks.java
@@ -32,7 +32,7 @@ private Checks() {
      * @param expected the expected size
      * @param actual   the actual size
      */
-    public static void checkArraySize(int expected, int actual) {
+    public static void checkArraySize(long expected, int actual) {
         if (MarshalConfigs.CHECK_ARRAY_SIZE.get() && expected != actual) {
             throw new IllegalArgumentException("Expected array of size " + expected + ", got " + actual);
         }
diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java
index 2909d8a..7a5067b 100644
--- a/src/main/java/overrun/marshal/Downcall.java
+++ b/src/main/java/overrun/marshal/Downcall.java
@@ -22,7 +22,6 @@
 import overrun.marshal.internal.StringCharset;
 import overrun.marshal.internal.data.DowncallData;
 import overrun.marshal.struct.ByValue;
-import overrun.marshal.struct.Struct;
 
 import java.lang.classfile.ClassFile;
 import java.lang.classfile.CodeBuilder;
@@ -73,8 +72,8 @@
  * instead, it will return {@code null} and automatically invokes the original method declared in the target class.
  * 

* {@link Critical @Critical} indicates that the annotated method is {@linkplain Linker.Option#critical(boolean) critical}. - *

Parameter Annotations

- * See {@link Ref @Ref}, {@link Sized @Sized} {@link SizedSeg @SizedSeg} and {@link StrCharset @StrCharset}. + *

Annotations

+ * See {@link Convert @Convert}, {@link Ref @Ref}, {@link Sized @Sized} and {@link StrCharset @StrCharset}. *

Direct Access

* You can get direct access by implement the class with {@link DirectAccess}, * which allows you to get the function descriptor and the method handle for a given method. @@ -100,7 +99,11 @@ * You can {@linkplain ProcessorTypes#register(Class, ProcessorType) register} a type and your own processor * as builtin processors may require an additional processor. *

- * For custom types, you should add processors to subclasses of {@link TypedCodeProcessor}. + * For custom types, you should {@linkplain Processor#addProcessor(Processor) add} processors to subclasses of + * {@link TypedCodeProcessor} and {@link TypeTransformer}. + *

+ * Builtin types include 8 primitive types, {@link MemorySegment}, {@link String}, + * {@link overrun.marshal.struct.Struct Struct} without allocator and {@link Upcall} without factory. *

Example

*
{@code
  * public interface GL {
@@ -111,12 +114,12 @@
  * }
* * @author squid233 + * @see Convert * @see Critical * @see DirectAccess * @see Entrypoint * @see Ref * @see Sized - * @see SizedSeg * @see Skip * @see StrCharset * @since 0.1.0 @@ -179,21 +182,6 @@ private static String getMethodEntrypoint(Method method) { entrypoint.value(); } - // type - - @Deprecated - private static ValueLayout getValueLayout(Class carrier) { - if (carrier == boolean.class) return ValueLayout.JAVA_BOOLEAN; - if (carrier == char.class) return ValueLayout.JAVA_CHAR; - if (carrier == byte.class) return ValueLayout.JAVA_BYTE; - if (carrier == short.class) return ValueLayout.JAVA_SHORT; - if (carrier == int.class) return ValueLayout.JAVA_INT; - if (carrier == long.class) return ValueLayout.JAVA_LONG; - if (carrier == float.class) return ValueLayout.JAVA_FLOAT; - if (carrier == double.class) return ValueLayout.JAVA_DOUBLE; - return ValueLayout.ADDRESS; - } - private static Map.Entry buildBytecode(MethodHandles.Lookup caller, SymbolLookup lookup, DowncallOption... options) { Class _targetClass = null, targetClass; Map _descriptorMap = null, descriptorMap; @@ -236,16 +224,19 @@ private static Map.Entry buildBytecode(MethodHandles.Looku .reduce(byValue ? AllocatorRequirement.ALLOCATOR : AllocatorRequirement.NONE, (requirement, parameter) -> AllocatorRequirement.stricter( requirement, - ProcessorTypes.fromClass(parameter.getType()).allocationRequirement() + ProcessorTypes.fromParameter(parameter).allocationRequirement() ), AllocatorRequirement::stricter); + boolean descriptorSkipFirstParameter = + !parameters.isEmpty() && + allocatorRequirement != AllocatorRequirement.NONE && + SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()); + boolean invokeSkipFirstParameter = !byValue && descriptorSkipFirstParameter; final DowncallMethodData methodData = new DowncallMethodData( entrypoint, signatureStringMap.get(method), parameters, - !byValue && - !parameters.isEmpty() && - allocatorRequirement != AllocatorRequirement.NONE && - SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()), + invokeSkipFirstParameter, + descriptorSkipFirstParameter, allocatorRequirement ); methodDataMap.put(method, methodData); @@ -381,7 +372,7 @@ private static Map.Entry buildBytecode(MethodHandles.Looku // invoke blockCodeBuilder.getstatic(cd_thisClass, entrypoint, CD_MethodHandle); List downcallClassDescList = new ArrayList<>(); - for (int i = methodData.skipFirstParam() ? 1 : 0, size = parameters.size(); i < size; i++) { + for (int i = methodData.invokeSkipFirstParameter() ? 1 : 0, size = parameters.size(); i < size; i++) { Parameter parameter = parameters.get(i); ProcessorType processorType = ProcessorTypes.fromParameter(parameter); if (refSlotMap.containsKey(parameter)) { @@ -587,8 +578,7 @@ private static void verifyMethods(List list, Map signatu AllocatorRequirement.NONE; Object strictObject = byValue ? "annotation @ByValue" : null; for (Parameter parameter : method.getParameters()) { - final Class type = parameter.getType(); - ProcessorType processorType = ProcessorTypes.fromClass(type); + ProcessorType processorType = ProcessorTypes.fromParameter(parameter); AllocatorRequirement previous = allocatorRequirement; allocatorRequirement = AllocatorRequirement.stricter(allocatorRequirement, processorType.allocationRequirement()); if (allocatorRequirement != previous) { @@ -610,22 +600,6 @@ private static void verifyMethods(List list, Map signatu } default -> throw new IllegalStateException("Invalid allocator requirement: " + allocatorRequirement); } - - // check Sized annotation - boolean sized = method.getDeclaredAnnotation(Sized.class) != null; - boolean sizedSeg = method.getDeclaredAnnotation(SizedSeg.class) != null; - if (sized && sizedSeg) { - throw new IllegalStateException("Cannot be annotated with both @Sized and @SizedSeg: " + signature); - } - if (sized) { - if (!returnType.isArray()) { - throw new IllegalStateException("Return type annotated with @Sized must be an array: " + signature); - } - } else if (sizedSeg) { - if (returnType != MemorySegment.class && !Struct.class.isAssignableFrom(returnType)) { - throw new IllegalStateException("Return type annotated with @SizedSeg must be MemorySegment or Struct: " + signature); - } - } } } @@ -651,82 +625,12 @@ private static DowncallData generateData( final String entrypoint = methodData.entrypoint(); // function descriptor - final FunctionDescriptor descriptor; final FunctionDescriptor get = descriptorMap.get(entrypoint); - if (get != null) { - descriptor = get; - } else { - final var returnType = method.getReturnType(); - final boolean returnVoid = returnType == void.class; - final boolean methodByValue = method.getDeclaredAnnotation(ByValue.class) != null; - - // return layout - final MemoryLayout retLayout; - if (!returnVoid) { - final Convert convert = method.getDeclaredAnnotation(Convert.class); - if (convert != null && returnType == boolean.class) { - retLayout = convert.value().layout(); - } else if (returnType.isPrimitive()) { - retLayout = getValueLayout(returnType); - } else { - final SizedSeg sizedSeg = method.getDeclaredAnnotation(SizedSeg.class); - final Sized sized = method.getDeclaredAnnotation(Sized.class); - final boolean isSizedSeg = sizedSeg != null; - final boolean isSized = sized != null; - if (Struct.class.isAssignableFrom(returnType)) { - final StructLayout structLayout = ((ProcessorType.Struct) ProcessorTypes.fromClass(returnType)).checkAllocator().layout(); - if (methodByValue) { - retLayout = structLayout; - } else { - final MemoryLayout targetLayout; - if (isSizedSeg) { - targetLayout = MemoryLayout.sequenceLayout(sizedSeg.value(), structLayout); - } else if (isSized) { - targetLayout = MemoryLayout.sequenceLayout(sized.value(), structLayout); - } else { - targetLayout = structLayout; - } - retLayout = ValueLayout.ADDRESS.withTargetLayout(targetLayout); - } - } else { - final ValueLayout valueLayout = getValueLayout(returnType); - if ((valueLayout instanceof AddressLayout addressLayout) && (isSizedSeg || isSized)) { - if (isSizedSeg) { - retLayout = addressLayout.withTargetLayout(MemoryLayout.sequenceLayout(sizedSeg.value(), - ValueLayout.JAVA_BYTE)); - } else { - retLayout = addressLayout.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), - returnType.isArray() ? getValueLayout(returnType.getComponentType()) : ValueLayout.JAVA_BYTE)); - } - } else { - retLayout = valueLayout; - } - } - } - } else { - retLayout = null; - } - - // argument layouts - final var parameters = methodData.parameters(); - final boolean skipFirstParam = methodData.skipFirstParam() || methodByValue; - final int size = skipFirstParam ? - parameters.size() - 1 : - parameters.size(); - final MemoryLayout[] argLayouts = new MemoryLayout[size]; - for (int i = 0; i < size; i++) { - final Parameter parameter = parameters.get(skipFirstParam ? i + 1 : i); - final Class type = parameter.getType(); - final Convert convert = parameter.getDeclaredAnnotation(Convert.class); - if (convert != null && type == boolean.class) { - argLayouts[i] = convert.value().layout(); - } else { - argLayouts[i] = getValueLayout(type); - } - } - - descriptor = returnVoid ? FunctionDescriptor.ofVoid(argLayouts) : FunctionDescriptor.of(retLayout, argLayouts); - } + final FunctionDescriptor descriptor = get != null ? + get : + DescriptorTransformer.getInstance().process(new DescriptorTransformer.Context(method, + methodData.descriptorSkipFirstParameter(), + methodData.parameters())); descriptorMap1.put(entrypoint, descriptor); diff --git a/src/main/java/overrun/marshal/DowncallMethodData.java b/src/main/java/overrun/marshal/DowncallMethodData.java index 1b1510c..a6df1e1 100644 --- a/src/main/java/overrun/marshal/DowncallMethodData.java +++ b/src/main/java/overrun/marshal/DowncallMethodData.java @@ -24,11 +24,12 @@ /** * Holds downcall method name * - * @param entrypoint entrypoint - * @param signatureString signatureString - * @param parameters parameters - * @param skipFirstParam skipFirstParam - * @param allocatorRequirement allocatorRequirement + * @param entrypoint entrypoint + * @param signatureString signatureString + * @param parameters parameters + * @param invokeSkipFirstParameter invokeSkipFirstParameter + * @param descriptorSkipFirstParameter descriptorSkipFirstParameter + * @param allocatorRequirement allocatorRequirement * @author squid233 * @since 0.1.0 */ @@ -36,7 +37,8 @@ record DowncallMethodData( String entrypoint, String signatureString, List parameters, - boolean skipFirstParam, + boolean invokeSkipFirstParameter, + boolean descriptorSkipFirstParameter, AllocatorRequirement allocatorRequirement ) { } diff --git a/src/main/java/overrun/marshal/gen/Sized.java b/src/main/java/overrun/marshal/gen/Sized.java index 146f270..ae6077a 100644 --- a/src/main/java/overrun/marshal/gen/Sized.java +++ b/src/main/java/overrun/marshal/gen/Sized.java @@ -21,12 +21,21 @@ import java.lang.annotation.*; /** + *

Array

* Marks an array parameter as a fixed size array. *

* The generated code will try to check the size of a passing array. + *

Memory segment

+ * Marks a memory segment to reinterpret its size. *

Example

*
{@code
  * void set(@Sized(3) int[] vec);
+ *
+ * @Sized(3)
+ * int[] get();
+ *
+ * @Sized(3)
+ * MemorySegment getSegment();
  * }
* * @author squid233 @@ -40,5 +49,5 @@ /** * {@return the size of the array} */ - int value(); + long value(); } diff --git a/src/main/java/overrun/marshal/gen/SizedSeg.java b/src/main/java/overrun/marshal/gen/SizedSeg.java index 55e496c..06c536b 100644 --- a/src/main/java/overrun/marshal/gen/SizedSeg.java +++ b/src/main/java/overrun/marshal/gen/SizedSeg.java @@ -35,6 +35,7 @@ @Documented @Target({ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) +@Deprecated public @interface SizedSeg { /** * {@return the size of the memory segment} diff --git a/src/main/java/overrun/marshal/gen/Type.java b/src/main/java/overrun/marshal/gen/Type.java deleted file mode 100644 index d695372..0000000 --- a/src/main/java/overrun/marshal/gen/Type.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import overrun.marshal.gen.processor.ProcessorType; - -import java.lang.constant.ClassDesc; -import java.lang.foreign.ValueLayout; - -import static java.lang.constant.ConstantDescs.*; - -/** - * Primitive types that are convertible with {@code boolean}. - * - * @author squid233 - * @since 0.1.0 - */ -public enum Type { - /** - * {@code char} type - */ - CHAR(CD_char, ValueLayout.JAVA_CHAR, ProcessorType.Value.CHAR), - /** - * {@code byte} type - */ - BYTE(CD_byte, ValueLayout.JAVA_BYTE, ProcessorType.Value.BYTE), - /** - * {@code short} type - */ - SHORT(CD_short, ValueLayout.JAVA_SHORT, ProcessorType.Value.SHORT), - /** - * {@code int} type - */ - INT(CD_int, ValueLayout.JAVA_INT, ProcessorType.Value.INT), - /** - * {@code long} type - */ - LONG(CD_long, ValueLayout.JAVA_LONG, ProcessorType.Value.LONG), - /** - * {@code float} type - */ - FLOAT(CD_float, ValueLayout.JAVA_FLOAT, ProcessorType.Value.FLOAT), - /** - * {@code double} type - */ - DOUBLE(CD_double, ValueLayout.JAVA_DOUBLE, ProcessorType.Value.DOUBLE); - - private final ClassDesc classDesc; - private final ValueLayout layout; - private final ProcessorType.Value processorType; - - Type(ClassDesc classDesc, ValueLayout layout, ProcessorType.Value processorType) { - this.classDesc = classDesc; - this.layout = layout; - this.processorType = processorType; - } - - /** - * {@return the class desc of this type} - */ - public ClassDesc classDesc() { - return classDesc; - } - - /** - * {@return the layout of this type} - */ - public ValueLayout layout() { - return layout; - } - - /** - * {@return the processor type of this type} - */ - public ProcessorType.Value processorType() { - return processorType; - } -} diff --git a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java index 82e1406..b4a7c47 100644 --- a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java @@ -22,8 +22,7 @@ import java.lang.reflect.Parameter; import java.util.List; -import static overrun.marshal.internal.Constants.CD_Checks; -import static overrun.marshal.internal.Constants.MTD_void_int_int; +import static overrun.marshal.internal.Constants.*; /** * insert check codes @@ -48,7 +47,7 @@ public void process(CodeBuilder builder, Context context) { .arraylength() .invokestatic(CD_Checks, "checkArraySize", - MTD_void_int_int); + MTD_void_long_int); } } } diff --git a/src/main/java/overrun/marshal/gen/processor/CodeInserter.java b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java index 770aed4..9d585f4 100644 --- a/src/main/java/overrun/marshal/gen/processor/CodeInserter.java +++ b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java @@ -24,7 +24,7 @@ * @author squid233 * @since 0.1.0 */ -public abstract class CodeInserter { +public abstract class CodeInserter implements Processor> { private final List> list = new ArrayList<>(); public void process(CodeBuilder builder, T context) { @@ -33,6 +33,7 @@ public void process(CodeBuilder builder, T context) { } } + @Override public void addProcessor(CodeInserter processor) { list.add(processor); } diff --git a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java new file mode 100644 index 0000000..c31242f --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java @@ -0,0 +1,96 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.gen.Sized; +import overrun.marshal.struct.ByValue; + +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.ValueLayout; +import java.lang.reflect.Method; +import java.lang.reflect.Parameter; +import java.util.ArrayList; +import java.util.List; + +/** + * @author squid233 + * @since 0.1.0 + */ +public class DescriptorTransformer extends TypeTransformer { + public record Context( + Method method, + boolean descriptorSkipFirstParameter, + List parameters + ) { + } + + @Override + public FunctionDescriptor process(Context context) { + Method method = context.method(); + ProcessorType returnType = ProcessorTypes.fromMethod(method); + List argLayouts = new ArrayList<>(); + Sized sized = method.getDeclaredAnnotation(Sized.class); + + MemoryLayout returnLayout = switch (returnType) { + case ProcessorType.Allocator allocator -> allocator.downcallLayout(); + case ProcessorType.Array array -> sized != null ? + ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), array.componentType().downcallLayout())) : + array.downcallLayout(); + case ProcessorType.BoolConvert boolConvert -> boolConvert.downcallLayout(); + case ProcessorType.Custom custom -> custom.downcallLayout(); + case ProcessorType.Str str -> str.downcallLayout(); + case ProcessorType.Struct struct -> { + if (method.getDeclaredAnnotation(ByValue.class) != null) { + yield struct.downcallLayout(); + } + if (sized != null) { + yield ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), struct.downcallLayout())); + } + yield ValueLayout.ADDRESS.withTargetLayout(struct.downcallLayout()); + } + case ProcessorType.Upcall upcall -> upcall.downcallLayout(); + case ProcessorType.Value value -> value == ProcessorType.Value.ADDRESS && sized != null ? + ValueLayout.ADDRESS.withTargetLayout(MemoryLayout.sequenceLayout(sized.value(), ValueLayout.JAVA_BYTE)) : + value.downcallLayout(); + case ProcessorType.Void _ -> null; + }; + + var parameters = context.parameters(); + for (int i = context.descriptorSkipFirstParameter() ? 1 : 0, size = parameters.size(); i < size; i++) { + Parameter parameter = parameters.get(i); + ProcessorType type = ProcessorTypes.fromParameter(parameter); + argLayouts.add(switch (type) { + case ProcessorType.Struct struct -> parameter.getDeclaredAnnotation(ByValue.class) != null ? + struct.downcallLayout() : + ValueLayout.ADDRESS; + default -> type.downcallLayout(); + }); + } + + return returnLayout == null ? + FunctionDescriptor.ofVoid(argLayouts.toArray(new MemoryLayout[0])) : + FunctionDescriptor.of(returnLayout, argLayouts.toArray(new MemoryLayout[0])); + } + + public static DescriptorTransformer getInstance() { + class Holder { + static final DescriptorTransformer INSTANCE = new DescriptorTransformer(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/Processor.java new file mode 100644 index 0000000..e7a303a --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/Processor.java @@ -0,0 +1,25 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +/** + * @author squid233 + * @since 0.1.0 + */ +public interface Processor> { + void addProcessor(T processor); +} diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java index 315bed1..63d2a85 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -21,9 +21,7 @@ import java.lang.classfile.TypeKind; import java.lang.constant.ClassDesc; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.foreign.ValueLayout; +import java.lang.foreign.*; import java.util.Locale; import static java.lang.constant.ConstantDescs.*; @@ -42,6 +40,11 @@ public sealed interface ProcessorType { */ ClassDesc downcallClassDesc(); + /** + * {@return the memory layout for functions in C} + */ + MemoryLayout downcallLayout(); + /** * {@return the allocator requirement} */ @@ -87,6 +90,11 @@ public ClassDesc downcallClassDesc() { return CD_void; } + @Override + public MemoryLayout downcallLayout() { + throw new UnsupportedOperationException(); + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.NONE; @@ -158,18 +166,16 @@ public TypeKind typeKind() { return typeKind; } - /** - * {@return the layout of this type} - */ - public ValueLayout layout() { - return layout; - } - @Override public ClassDesc downcallClassDesc() { return classDesc; } + @Override + public ValueLayout downcallLayout() { + return layout; + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.NONE; @@ -231,18 +237,16 @@ public TypeKind typeKind() { return typeKind; } - /** - * {@return the layout of this type} - */ - public ValueLayout layout() { - return layout; - } - @Override public ClassDesc downcallClassDesc() { return classDesc; } + @Override + public ValueLayout downcallLayout() { + return layout; + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.NONE; @@ -271,6 +275,11 @@ public ClassDesc downcallClassDesc() { return CD_SegmentAllocator; } + @Override + public MemoryLayout downcallLayout() { + throw new UnsupportedOperationException(); + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.NONE; // this is invalid @@ -299,6 +308,11 @@ public ClassDesc downcallClassDesc() { return CD_MemorySegment; } + @Override + public ValueLayout downcallLayout() { + return ValueLayout.ADDRESS; + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.STACK; @@ -348,6 +362,11 @@ public ClassDesc downcallClassDesc() { return CD_MemorySegment; } + @Override + public MemoryLayout downcallLayout() { + return allocatorSpec != null ? allocatorSpec.layout() : ValueLayout.ADDRESS; + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.NONE; @@ -404,6 +423,11 @@ public ClassDesc downcallClassDesc() { return CD_MemorySegment; } + @Override + public ValueLayout downcallLayout() { + return ValueLayout.ADDRESS; + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.ARENA; @@ -426,6 +450,11 @@ public ClassDesc downcallClassDesc() { return CD_MemorySegment; } + @Override + public ValueLayout downcallLayout() { + return ValueLayout.ADDRESS; + } + @Override public AllocatorRequirement allocationRequirement() { return AllocatorRequirement.stricter(AllocatorRequirement.STACK, componentType.allocationRequirement()); diff --git a/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java b/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java new file mode 100644 index 0000000..e7ef510 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author squid233 + * @since 0.1.0 + */ +public abstract class TypeTransformer implements Processor> { + private final List> list = new ArrayList<>(); + + public R process(C context) { + for (var transformer : list) { + R r = transformer.process(context); + if (r != null) { + return r; + } + } + return null; + } + + @Override + public void addProcessor(TypeTransformer transformer) { + list.add(transformer); + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java b/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java index d757731..ab31894 100644 --- a/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java @@ -24,7 +24,7 @@ * @author squid233 * @since 0.1.0 */ -public abstract class TypedCodeProcessor { +public abstract class TypedCodeProcessor implements Processor> { private final List> list = new ArrayList<>(); public boolean process(CodeBuilder builder, ProcessorType type, T context) { @@ -36,6 +36,7 @@ public boolean process(CodeBuilder builder, ProcessorType type, T context) { throw new IllegalStateException(this.getClass().getSimpleName() + ": type '" + type + "' was not processed"); } + @Override public void addProcessor(TypedCodeProcessor processor) { list.add(processor); } diff --git a/src/main/java/overrun/marshal/internal/Constants.java b/src/main/java/overrun/marshal/internal/Constants.java index 668f54f..b73cf14 100644 --- a/src/main/java/overrun/marshal/internal/Constants.java +++ b/src/main/java/overrun/marshal/internal/Constants.java @@ -379,9 +379,9 @@ public final class Constants { */ public static final MethodTypeDesc MTD_VarHandle_MemoryLayout$PathElementArray = MethodTypeDesc.of(CD_VarHandle, CD_MemoryLayout$PathElement.arrayType()); /** - * MTD_void_int_int + * MTD_void_long_int */ - public static final MethodTypeDesc MTD_void_int_int = MethodTypeDesc.of(CD_void, CD_int, CD_int); + public static final MethodTypeDesc MTD_void_long_int = MethodTypeDesc.of(CD_void, CD_long, CD_int); /** * MTD_void_MemorySegment_long */ diff --git a/src/main/java/overrun/marshal/struct/ByValue.java b/src/main/java/overrun/marshal/struct/ByValue.java index 7437c88..f5d284c 100644 --- a/src/main/java/overrun/marshal/struct/ByValue.java +++ b/src/main/java/overrun/marshal/struct/ByValue.java @@ -19,20 +19,25 @@ import java.lang.annotation.*; /** + *

Returning-by-value structure

* Marks a method that returns a struct by value. *

* The annotated method must contain a segment allocator as the first parameter. + *

Passing-by-value structure

+ * Marks a parameter that passes to C function by value. *

Example

*
{@code
  * @ByValue
  * MyStruct returnStruct(SegmentAllocator allocator);
+ *
+ * void passByValue(@ByValue MyStruct struct);
  * }
* * @author squid233 * @since 0.1.0 */ @Documented -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface ByValue { } diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/struct/Struct.java index b8cca7b..61fb2c0 100644 --- a/src/main/java/overrun/marshal/struct/Struct.java +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -17,6 +17,7 @@ package overrun.marshal.struct; import overrun.marshal.Unmarshal; +import overrun.marshal.gen.processor.DescriptorTransformer; import overrun.marshal.gen.processor.ProcessorTypes; import overrun.marshal.gen.processor.UnmarshalProcessor; @@ -29,9 +30,10 @@ /** * The representation of a C structure. *

- * Returning a {@code Struct} from a downcall method requires a + * Returning a {@code Struct} from a downcall method or pass a {@code Struct} by value requires a * {@linkplain ProcessorTypes#registerStruct(Class, StructAllocatorSpec) registration} to tell - * {@link UnmarshalProcessor} how to create an instance of the {@code Struct}. + * {@link UnmarshalProcessor} how to create an instance of the {@code Struct} and {@link DescriptorTransformer} + * the layout of the structure. * * @param the type of the actual structure interface * @author squid233 diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java b/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java index 87b14fe..bd21b4a 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallProvider.java @@ -57,6 +57,7 @@ public final class DowncallProvider { seg("testIntArray", LOOKUP.findStatic(DowncallProvider.class, "testIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); seg("testVarArgsJava", LOOKUP.findStatic(DowncallProvider.class, "testVarArgsJava", MethodType.methodType(void.class, int.class, MemorySegment.class)), FunctionDescriptor.ofVoid(JAVA_INT, ADDRESS)); seg("testStruct", LOOKUP.findStatic(DowncallProvider.class, "testStruct", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("testStructByValue", LOOKUP.findStatic(DowncallProvider.class, "testStructByValue", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(Vector3.OF.layout())); seg("testReturnInt", LOOKUP.findStatic(DowncallProvider.class, "testReturnInt", MethodType.methodType(int.class)), FunctionDescriptor.of(JAVA_INT)); seg("testReturnString", LOOKUP.findStatic(DowncallProvider.class, "testReturnString", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnUTF16String", LOOKUP.findStatic(DowncallProvider.class, "testReturnUTF16String", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); @@ -132,6 +133,12 @@ private static void testStruct(MemorySegment vector3) { writeVector3(vector3.reinterpret(Vector3.OF.layout().byteSize()), 1, 2, 3); } + private static void testStructByValue(MemorySegment vector3) { + // simulates writing. + // the content in the original structure cannot be modified because it is passed-by-value + writeVector3(vector3, 1, 2, 3); + } + private static int testReturnInt() { return 42; } diff --git a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java index a7dd640..787a50e 100644 --- a/src/test/java/overrun/marshal/test/downcall/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/downcall/DowncallTest.java @@ -16,7 +16,6 @@ package overrun.marshal.test.downcall; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import overrun.marshal.test.TestUtil; @@ -67,6 +66,21 @@ void testStruct() { } } + @Test + void testStructByValue() { + try (Arena arena = Arena.ofConfined()) { + final Vector3 vector3 = Vector3.OF.of(arena); + MemorySegment segment = vector3.segment(); + segment.set(ValueLayout.JAVA_INT, 0, 42); + segment.set(ValueLayout.JAVA_INT, 4, 43); + segment.set(ValueLayout.JAVA_INT, 8, 44); + d.testStructByValue(vector3); + assertEquals(42, vector3.x()); + assertEquals(43, vector3.y()); + assertEquals(44, vector3.z()); + } + } + @Test void testReturnInt() { assertEquals(42, d.testReturnInt()); @@ -74,7 +88,7 @@ void testReturnInt() { @Test void testReturnString() { - Assertions.assertEquals(TestUtil.TEST_STRING, d.testReturnString()); + assertEquals(TestUtil.TEST_STRING, d.testReturnString()); assertEquals(TestUtil.TEST_UTF16_STRING, d.testReturnUTF16String()); } diff --git a/src/test/java/overrun/marshal/test/downcall/IDowncall.java b/src/test/java/overrun/marshal/test/downcall/IDowncall.java index ff56b24..0f83d50 100644 --- a/src/test/java/overrun/marshal/test/downcall/IDowncall.java +++ b/src/test/java/overrun/marshal/test/downcall/IDowncall.java @@ -80,6 +80,8 @@ default int testDefault() { void testStruct(Vector3 vector3); + void testStructByValue(@ByValue Vector3 vector3); + int testReturnInt(); String testReturnString(); @@ -97,7 +99,7 @@ default int testDefault() { @ByValue Vector3 testReturnStructByValue(SegmentAllocator allocator, int i); - @SizedSeg(2L) + @Sized(2L) Vector3 testReturnStructSizedSeg(); @Sized(2) @@ -105,7 +107,7 @@ default int testDefault() { void testSizedIntArray(@Sized(2) int[] arr); - @SizedSeg(4L) + @Sized(4L) MemorySegment testReturnSizedSeg(); void testRefIntArray(@Ref int[] arr); From 105f49fc3200b9645a9b8035d491f68f600e68ac Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 24 Aug 2024 20:58:27 +0800 Subject: [PATCH 08/13] Add RefCopyProcessor and RefTypeTransformer --- src/main/java/overrun/marshal/Downcall.java | 6 +- .../gen/processor/AfterInvokeProcessor.java | 33 ++------ .../gen/processor/BeforeInvokeProcessor.java | 12 ++- .../gen/processor/DescriptorTransformer.java | 2 +- .../marshal/gen/processor/ProcessorTypes.java | 1 - .../gen/processor/RefCopyProcessor.java | 82 +++++++++++++++++++ .../gen/processor/RefTypeTransformer.java | 38 +++++++++ 7 files changed, 136 insertions(+), 38 deletions(-) create mode 100644 src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java create mode 100644 src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 7a5067b..7a4930b 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -562,8 +562,6 @@ private static void verifyMethods(List list, Map signatu // check method parameter final Class[] types = method.getParameterTypes(); - final boolean isFirstAllocator = types.length > 0 && SegmentAllocator.class.isAssignableFrom(types[0]); - final boolean isFirstArena = types.length > 0 && Arena.class.isAssignableFrom(types[0]); for (Parameter parameter : method.getParameters()) { final Class type = parameter.getType(); if (!isValidParamType(type)) { @@ -589,12 +587,12 @@ private static void verifyMethods(List list, Map signatu case NONE, STACK -> { } case ALLOCATOR -> { - if (!isFirstAllocator) { + if (types.length == 0 || !SegmentAllocator.class.isAssignableFrom(types[0])) { throw new IllegalStateException("A segment allocator is required by " + strictObject + ": " + signature); } } case ARENA -> { - if (!isFirstArena) { + if (types.length == 0 || !Arena.class.isAssignableFrom(types[0])) { throw new IllegalStateException("An arena is required by " + strictObject + ": " + signature); } } diff --git a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java index 45b974a..0aa61f9 100644 --- a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java @@ -23,8 +23,6 @@ import java.util.List; import java.util.Map; -import static overrun.marshal.internal.Constants.*; - /** * insert code after invoke * @@ -46,32 +44,11 @@ public void process(CodeBuilder builder, Context context) { Parameter parameter = parameters.get(i); if (refSlotMap.containsKey(parameter)) { ProcessorType type = ProcessorTypes.fromParameter(parameter); - // TODO: ref processor - if (type instanceof ProcessorType.Array array) { - int parameterSlot = builder.parameterSlot(i); - int refSlot = refSlotMap.get(parameter); - builder - .aload(refSlot) - .aload(parameterSlot) - .invokestatic(CD_Unmarshal, "copy", switch (array.componentType()) { - case ProcessorType.Str _ -> - StringCharset.getCharset(builder, StringCharset.getCharset(parameter)) ? - MTD_void_MemorySegment_StringArray_Charset : - MTD_void_MemorySegment_StringArray; - case ProcessorType.Value value -> switch (value) { - case BOOLEAN -> MTD_void_MemorySegment_booleanArray; - case CHAR -> MTD_void_MemorySegment_charArray; - case BYTE -> MTD_void_MemorySegment_byteArray; - case SHORT -> MTD_void_MemorySegment_shortArray; - case INT -> MTD_void_MemorySegment_intArray; - case LONG -> MTD_void_MemorySegment_longArray; - case FLOAT -> MTD_void_MemorySegment_floatArray; - case DOUBLE -> MTD_void_MemorySegment_doubleArray; - case ADDRESS -> MTD_void_MemorySegment_MemorySegmentArray; - }; - default -> throw new IllegalStateException("Unexpected value: " + array.componentType()); - }); - } + RefCopyProcessor.getInstance().process(builder, + type, + new RefCopyProcessor.Context(refSlotMap.get(parameter), + builder.parameterSlot(i), + StringCharset.getCharset(parameter))); } } super.process(builder, context); diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java index 681d221..c4db4cb 100644 --- a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java @@ -39,6 +39,7 @@ public record Context( ) { } + @SuppressWarnings("preview") @Override public void process(CodeBuilder builder, Context context) { List parameters = context.parameters(); @@ -46,14 +47,17 @@ public void process(CodeBuilder builder, Context context) { Parameter parameter = parameters.get(i); if (parameter.getType().isArray() && parameter.getDeclaredAnnotation(Ref.class) != null) { - int local = builder.allocateLocal(TypeKind.ReferenceType); - context.refSlot().put(parameter, local); - MarshalProcessor.getInstance().process(builder, ProcessorTypes.fromParameter(parameter), new MarshalProcessor.Context( + ProcessorType type = ProcessorTypes.fromParameter(parameter); + ProcessorType refType = RefTypeTransformer.getInstance().process(type); + TypeKind refTypeKind = TypeKind.from(refType.downcallClassDesc()); + int local = builder.allocateLocal(refTypeKind); + MarshalProcessor.getInstance().process(builder, type, new MarshalProcessor.Context( StringCharset.getCharset(parameter), builder.parameterSlot(i), context.allocatorSlot() )); - builder.astore(local); + builder.storeLocal(refTypeKind, local); + context.refSlot().put(parameter, local); } } super.process(builder, context); diff --git a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java index c31242f..f75befc 100644 --- a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java +++ b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java @@ -31,7 +31,7 @@ * @author squid233 * @since 0.1.0 */ -public class DescriptorTransformer extends TypeTransformer { +public final class DescriptorTransformer extends TypeTransformer { public record Context( Method method, boolean descriptorSkipFirstParameter, diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java index 82fd500..28a9fa3 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java @@ -112,7 +112,6 @@ public static ProcessorType fromParameter(Parameter parameter) { return convert.value(); } } - // TODO: ref processor return fromClass(type); } diff --git a/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java b/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java new file mode 100644 index 0000000..da1ff9b --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java @@ -0,0 +1,82 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.internal.StringCharset; + +import java.lang.classfile.CodeBuilder; + +import static overrun.marshal.internal.Constants.*; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class RefCopyProcessor extends TypedCodeProcessor { + public record Context( + int srcSegmentSlot, + int dstArraySlot, + String charset + ) { + } + + @SuppressWarnings("preview") + @Override + public boolean process(CodeBuilder builder, ProcessorType type, Context context) { + if (!(type instanceof ProcessorType.Array array)) { + return true; + } + return switch (array.componentType()) { + case ProcessorType.Str _ -> { + builder.aload(context.srcSegmentSlot()) + .aload(context.dstArraySlot()) + .invokestatic(CD_Unmarshal, + "copy", + StringCharset.getCharset(builder, context.charset()) ? + MTD_void_MemorySegment_StringArray_Charset : + MTD_void_MemorySegment_StringArray); + yield true; + } + case ProcessorType.Value value -> { + builder.aload(context.srcSegmentSlot()) + .aload(context.dstArraySlot()) + .invokestatic(CD_Unmarshal, + "copy", + switch (value) { + case BOOLEAN -> MTD_void_MemorySegment_booleanArray; + case CHAR -> MTD_void_MemorySegment_charArray; + case BYTE -> MTD_void_MemorySegment_byteArray; + case SHORT -> MTD_void_MemorySegment_shortArray; + case INT -> MTD_void_MemorySegment_intArray; + case LONG -> MTD_void_MemorySegment_longArray; + case FLOAT -> MTD_void_MemorySegment_floatArray; + case DOUBLE -> MTD_void_MemorySegment_doubleArray; + case ADDRESS -> MTD_void_MemorySegment_MemorySegmentArray; + }); + yield true; + } + default -> super.process(builder, type, context); + }; + } + + public static RefCopyProcessor getInstance() { + class Holder { + static final RefCopyProcessor INSTANCE = new RefCopyProcessor(); + } + return Holder.INSTANCE; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java b/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java new file mode 100644 index 0000000..0807051 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java @@ -0,0 +1,38 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class RefTypeTransformer extends TypeTransformer { + @Override + public ProcessorType process(ProcessorType context) { + return context instanceof ProcessorType.Array array ? switch (array.componentType()) { + case ProcessorType.Str _, ProcessorType.Value _ -> ProcessorType.Value.ADDRESS; + default -> super.process(context); + } : context; + } + + public static RefTypeTransformer getInstance() { + class Holder { + static final RefTypeTransformer INSTANCE = new RefTypeTransformer(); + } + return Holder.INSTANCE; + } +} From 73428641903e927b29043cc0257a5b3bca31ee70 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 24 Aug 2024 21:48:49 +0800 Subject: [PATCH 09/13] Cleanup --- .../java/overrun/marshal/MarshalConfigs.java | 39 -------- .../java/overrun/marshal/StackWalkUtil.java | 96 ------------------- .../java/overrun/marshal/gen/SizedSeg.java | 44 --------- 3 files changed, 179 deletions(-) delete mode 100644 src/main/java/overrun/marshal/StackWalkUtil.java delete mode 100644 src/main/java/overrun/marshal/gen/SizedSeg.java diff --git a/src/main/java/overrun/marshal/MarshalConfigs.java b/src/main/java/overrun/marshal/MarshalConfigs.java index 723fc69..ec86e9f 100644 --- a/src/main/java/overrun/marshal/MarshalConfigs.java +++ b/src/main/java/overrun/marshal/MarshalConfigs.java @@ -18,7 +18,6 @@ import overrun.marshal.gen.Sized; -import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -28,12 +27,6 @@ * @since 0.1.0 */ public final class MarshalConfigs { - /** - * Enable checks. - *

- * The default value is {@code true}. - */ - public static final Entry CHECKS = new Entry<>(() -> true); /** * Check the size of a fixed size array argument. *

@@ -42,43 +35,11 @@ public final class MarshalConfigs { * @see Sized */ public static final Entry CHECK_ARRAY_SIZE = new Entry<>(() -> true); - /** - * Enable debug messages and prints to {@link #apiLogger()}. - *

- * The default value is {@code false}. - */ - public static final Entry DEBUG = new Entry<>(() -> false); - private static Consumer apiLogger = System.err::println; private MarshalConfigs() { //no instance } - /** - * Sets the API logger. - * - * @param logger the logger - */ - public static void setApiLogger(Consumer logger) { - apiLogger = logger != null ? logger : System.err::println; - } - - /** - * {@return the API logger} - */ - public static Consumer apiLogger() { - return apiLogger; - } - - /** - * Logs the given message. - * - * @param log the message - */ - public static void apiLog(String log) { - apiLogger().accept(log); - } - /** * A check entry * diff --git a/src/main/java/overrun/marshal/StackWalkUtil.java b/src/main/java/overrun/marshal/StackWalkUtil.java deleted file mode 100644 index 7dd6df9..0000000 --- a/src/main/java/overrun/marshal/StackWalkUtil.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal; - -/** - * The stack walker util - * - * @author lwjgl3 - * @author squid233 - * @since 0.1.0 - */ -final class StackWalkUtil { - private static final StackWalker STACKWALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); - - private StackWalkUtil() { - } - - static Object stackWalkGetMethod(Class after) { - return STACKWALKER.walk(s -> { - var iter = s.iterator(); - iter.next(); // skip this method - iter.next(); // skip MemoryStack::pop - - StackWalker.StackFrame frame; - do { - frame = iter.next(); - } while (frame.getDeclaringClass() == after && iter.hasNext()); - - return frame; - }); - } - - private static boolean isSameMethod(StackWalker.StackFrame a, StackWalker.StackFrame b) { - return isSameMethod(a, b, b.getMethodName()); - } - - private static boolean isSameMethod(StackWalker.StackFrame a, StackWalker.StackFrame b, String methodName) { - return a.getDeclaringClass() == b.getDeclaringClass() && - a.getMethodName().equals(methodName); - } - - private static boolean isAutoCloseable(StackWalker.StackFrame element, StackWalker.StackFrame pushed) { - // Java 9 try-with-resources: synthetic $closeResource - if (isSameMethod(element, pushed, "$closeResource")) { - return true; - } - - // Kotlin T.use: kotlin.AutoCloseable::closeFinally - return "kotlin.jdk7.AutoCloseableKt".equals(element.getClassName()) && "closeFinally".equals(element.getMethodName()); - } - - static Object stackWalkCheckPop(Class after, Object pushedObj) { - StackWalker.StackFrame pushed = (StackWalker.StackFrame) pushedObj; - - return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(s -> { - var iter = s.iterator(); - iter.next(); - iter.next(); - - StackWalker.StackFrame element; - do { - element = iter.next(); - } while (element.getDeclaringClass() == after && iter.hasNext()); - - if (isSameMethod(element, pushed)) { - return null; - } - - if (iter.hasNext() && isAutoCloseable(element, pushed)) { - // Some runtimes use a separate method to call AutoCloseable::close in try-with-resources blocks. - // That method suppresses any exceptions thrown by close if necessary. - // When that happens, the pop is 1 level deeper than expected. - element = iter.next(); - if (isSameMethod(element, pushed)) { - return null; - } - } - - return element; - }); - } -} diff --git a/src/main/java/overrun/marshal/gen/SizedSeg.java b/src/main/java/overrun/marshal/gen/SizedSeg.java deleted file mode 100644 index 06c536b..0000000 --- a/src/main/java/overrun/marshal/gen/SizedSeg.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import overrun.marshal.MarshalConfigs; - -import java.lang.annotation.*; - -/** - * Marks a memory segment as fix-sized. - *

Example

- *
{@code
- * @SizedSeg(0x7FFFFFFFFFFFFFFFL)
- * MemorySegment segment;
- * }
- * - * @author squid233 - * @see MarshalConfigs#CHECK_ARRAY_SIZE - * @since 0.1.0 - */ -@Documented -@Target({ElementType.METHOD, ElementType.PARAMETER}) -@Retention(RetentionPolicy.RUNTIME) -@Deprecated -public @interface SizedSeg { - /** - * {@return the size of the memory segment} - */ - long value(); -} From a817ea097d1df792886d433a8ffd129537471442 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 26 Aug 2024 18:43:04 +0800 Subject: [PATCH 10/13] Adding Javadoc --- src/main/java/overrun/marshal/Downcall.java | 3 +- .../gen/processor/AfterInvokeProcessor.java | 6 ++ .../gen/processor/AllocatorRequirement.java | 13 +++- .../gen/processor/BeforeInvokeProcessor.java | 6 ++ .../gen/processor/BeforeReturnProcessor.java | 6 ++ .../marshal/gen/processor/CheckProcessor.java | 6 ++ .../marshal/gen/processor/CodeInserter.java | 7 +++ .../gen/processor/DescriptorTransformer.java | 6 ++ .../gen/processor/MarshalProcessor.java | 6 ++ .../marshal/gen/processor/Processor.java | 10 +++ .../marshal/gen/processor/ProcessorType.java | 63 ++++++++++++++++++- .../marshal/gen/processor/ProcessorTypes.java | 50 ++++++++++++--- .../gen/processor/RefCopyProcessor.java | 6 ++ .../gen/processor/RefTypeTransformer.java | 6 ++ .../gen/processor/TypeTransformer.java | 8 +++ .../gen/processor/TypedCodeProcessor.java | 7 +++ .../gen/processor/UnmarshalProcessor.java | 6 ++ .../marshal/internal/StringCharset.java | 9 ++- .../marshal/struct/StructAllocatorSpec.java | 1 + 19 files changed, 207 insertions(+), 18 deletions(-) diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 7a4930b..00c801d 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -102,8 +102,7 @@ * For custom types, you should {@linkplain Processor#addProcessor(Processor) add} processors to subclasses of * {@link TypedCodeProcessor} and {@link TypeTransformer}. *

- * Builtin types include 8 primitive types, {@link MemorySegment}, {@link String}, - * {@link overrun.marshal.struct.Struct Struct} without allocator and {@link Upcall} without factory. + * Builtin types are described in {@link ProcessorType}. *

Example

*
{@code
  * public interface GL {
diff --git a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java
index 0aa61f9..671b3b9 100644
--- a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java
@@ -30,6 +30,9 @@
  * @since 0.1.0
  */
 public final class AfterInvokeProcessor extends CodeInserter {
+    private AfterInvokeProcessor() {
+    }
+
     public record Context(
         List parameters,
         Map refSlotMap
@@ -54,6 +57,9 @@ public void process(CodeBuilder builder, Context context) {
         super.process(builder, context);
     }
 
+    /**
+     * {@return the instance}
+     */
     public static AfterInvokeProcessor getInstance() {
         class Holder {
             static final AfterInvokeProcessor INSTANCE = new AfterInvokeProcessor();
diff --git a/src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java b/src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java
index 2f1b64e..6fcd050 100644
--- a/src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java
+++ b/src/main/java/overrun/marshal/gen/processor/AllocatorRequirement.java
@@ -17,7 +17,7 @@
 package overrun.marshal.gen.processor;
 
 /**
- * allocator requirement
+ * Allocator requirement. This tells {@link overrun.marshal.Downcall Downcall} whether a segment allocator is required.
  *
  * @author squid233
  * @since 0.1.0
@@ -46,10 +46,21 @@ public enum AllocatorRequirement {
         this.level = level;
     }
 
+    /**
+     * {@return the stricter allocator requirement between {@code first} and {@code second}}
+     *
+     * @param first  the first requirement
+     * @param second the second requirement
+     */
     public static AllocatorRequirement stricter(AllocatorRequirement first, AllocatorRequirement second) {
         return first.isStricter(second) ? first : second;
     }
 
+    /**
+     * {@return {@code true} if this requirement is stricter than {@code other}}
+     *
+     * @param other the other requirement
+     */
     public boolean isStricter(AllocatorRequirement other) {
         return this.level > other.level;
     }
diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java
index c4db4cb..25e6b32 100644
--- a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java
@@ -32,6 +32,9 @@
  * @since 0.1.0
  */
 public final class BeforeInvokeProcessor extends CodeInserter {
+    private BeforeInvokeProcessor() {
+    }
+
     public record Context(
         List parameters,
         Map refSlot,
@@ -63,6 +66,9 @@ public void process(CodeBuilder builder, Context context) {
         super.process(builder, context);
     }
 
+    /**
+     * {@return the instance}
+     */
     public static BeforeInvokeProcessor getInstance() {
         class Holder {
             static final BeforeInvokeProcessor INSTANCE = new BeforeInvokeProcessor();
diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java
index 47f4f24..bbce0e8 100644
--- a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java
@@ -28,6 +28,9 @@
  * @since 0.1.0
  */
 public final class BeforeReturnProcessor extends CodeInserter {
+    private BeforeReturnProcessor() {
+    }
+
     public record Context(boolean hasMemoryStack) {
     }
 
@@ -42,6 +45,9 @@ public void process(CodeBuilder builder, Context context) {
         super.process(builder, context);
     }
 
+    /**
+     * {@return the instance}
+     */
     public static BeforeReturnProcessor getInstance() {
         class Holder {
             static final BeforeReturnProcessor INSTANCE = new BeforeReturnProcessor();
diff --git a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java
index b4a7c47..c13df6a 100644
--- a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java
@@ -31,6 +31,9 @@
  * @since 0.1.0
  */
 public final class CheckProcessor extends CodeInserter {
+    private CheckProcessor() {
+    }
+
     public record Context(List parameters) {
     }
 
@@ -54,6 +57,9 @@ public void process(CodeBuilder builder, Context context) {
         super.process(builder, context);
     }
 
+    /**
+     * {@return the instance}
+     */
     public static CheckProcessor getInstance() {
         class Holder {
             static final CheckProcessor INSTANCE = new CheckProcessor();
diff --git a/src/main/java/overrun/marshal/gen/processor/CodeInserter.java b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java
index 9d585f4..3d74f9e 100644
--- a/src/main/java/overrun/marshal/gen/processor/CodeInserter.java
+++ b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java
@@ -21,12 +21,19 @@
 import java.util.List;
 
 /**
+ * @param  the type of the context
  * @author squid233
  * @since 0.1.0
  */
 public abstract class CodeInserter implements Processor> {
     private final List> list = new ArrayList<>();
 
+    /**
+     * constructor
+     */
+    protected CodeInserter() {
+    }
+
     public void process(CodeBuilder builder, T context) {
         for (CodeInserter processor : list) {
             processor.process(builder, context);
diff --git a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java
index f75befc..2f814c9 100644
--- a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java
+++ b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java
@@ -32,6 +32,9 @@
  * @since 0.1.0
  */
 public final class DescriptorTransformer extends TypeTransformer {
+    private DescriptorTransformer() {
+    }
+
     public record Context(
         Method method,
         boolean descriptorSkipFirstParameter,
@@ -87,6 +90,9 @@ public FunctionDescriptor process(Context context) {
             FunctionDescriptor.of(returnLayout, argLayouts.toArray(new MemoryLayout[0]));
     }
 
+    /**
+     * {@return the instance}
+     */
     public static DescriptorTransformer getInstance() {
         class Holder {
             static final DescriptorTransformer INSTANCE = new DescriptorTransformer();
diff --git a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java
index 69896c8..a69b040 100644
--- a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java
+++ b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java
@@ -29,6 +29,9 @@
  * @since 0.1.0
  */
 public final class MarshalProcessor extends TypedCodeProcessor {
+    private MarshalProcessor() {
+    }
+
     public record Context(
         String charset,
         int variableSlot,
@@ -137,6 +140,9 @@ public boolean process(CodeBuilder builder, ProcessorType type, Context context)
         return true;
     }
 
+    /**
+     * {@return the instance}
+     */
     public static MarshalProcessor getInstance() {
         class Holder {
             static final MarshalProcessor INSTANCE = new MarshalProcessor();
diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/Processor.java
index e7a303a..ecbe399 100644
--- a/src/main/java/overrun/marshal/gen/processor/Processor.java
+++ b/src/main/java/overrun/marshal/gen/processor/Processor.java
@@ -17,9 +17,19 @@
 package overrun.marshal.gen.processor;
 
 /**
+ * The superinterface of processors.
+ *
+ * @param  the type of this
  * @author squid233
  * @since 0.1.0
  */
 public interface Processor> {
+    /**
+     * Adds an alternative processor to this.
+     * 

+ * Check {@code process} method of subclasses to see how alternative processors are used. + * + * @param processor the processor + */ void addProcessor(T processor); } diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java index 63d2a85..1bd1ffe 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -21,7 +21,10 @@ import java.lang.classfile.TypeKind; import java.lang.constant.ClassDesc; -import java.lang.foreign.*; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.lang.foreign.ValueLayout; import java.util.Locale; import static java.lang.constant.ConstantDescs.*; @@ -29,7 +32,20 @@ import static overrun.marshal.internal.Constants.CD_SegmentAllocator; /** - * Types to be processed + * Processor types are used to indicate how to process a type in {@link overrun.marshal.Downcall Downcall}. + *

Builtin type

+ * Builtin types include 8 primitive types, {@link MemorySegment}, {@link String}, + * {@link overrun.marshal.struct.Struct Struct} without allocator and {@link overrun.marshal.Upcall Upcall} + * without factory. + *

Custom type

+ * For a custom type, you should implement the general superinterface {@link Custom} and register it via + * {@link ProcessorTypes#register(Class, ProcessorType) ProcessorTypes::register}. + * Common custom types include subclasses of {@link overrun.marshal.struct.Struct Struct} + * and {@link overrun.marshal.Upcall Upcall}. + *

+ * For {@code Struct} and {@code Upcall}, {@code ProcessorTypes} also provides + * {@link ProcessorTypes#registerStruct(Class, StructAllocatorSpec) registerStruct} and + * {@link ProcessorTypes#registerUpcall(Class, Upcall.Factory) registerUpcall} to conveniently register them. * * @author squid233 * @since 0.1.0 @@ -337,19 +353,34 @@ private Struct(Class typeClass, @Nullable StructAllocatorSpec allocatorSpe this.allocatorSpec = allocatorSpec; } + /** + * {@return an exception with "No allocator" message} + * + * @param typeClass the type of the struct + */ public static IllegalStateException noAllocatorException(Class typeClass) { return new IllegalStateException("No allocator registered for struct " + typeClass); } + /** + * {@return the type of the struct} + */ public Class typeClass() { return typeClass; } + /** + * {@return the allocator} + */ @Nullable public StructAllocatorSpec allocatorSpec() { return allocatorSpec; } + /** + * {@return the allocator} + * Also checks whether the allocator is null or not. + */ public StructAllocatorSpec checkAllocator() { if (allocatorSpec() != null) { return allocatorSpec(); @@ -393,24 +424,50 @@ private Upcall(Class typeClass, @Nullable Factory factory) { this.factory = factory; } + /** + * {@return an exception with "No factory" message} + * + * @param typeClass the type of the upcall + */ public static IllegalStateException noFactoryException(Class typeClass) { return new IllegalStateException("No factory registered for upcall " + typeClass); } + /** + * A factory that creates the upcall instance with a given upcall stub. + * + * @param the type of the upcall + */ @FunctionalInterface public interface Factory { + /** + * Creates an upcall instance with the given stub. + * + * @param stub the memory segment + * @return the upcall instance + */ T create(MemorySegment stub); } + /** + * {@return the type of the upcall} + */ public Class typeClass() { return typeClass; } + /** + * {@return the factory} + */ @Nullable public Factory factory() { return factory; } + /** + * {@return the factory} + * Also checks whether the factory is null or not. + */ public Factory checkFactory() { if (factory() != null) { return factory(); @@ -467,7 +524,7 @@ public String toString() { } /** - * Custom type + * General superinterface of custom processor types */ non-sealed interface Custom extends ProcessorType { } diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java index 28a9fa3..92beeff 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java @@ -25,18 +25,34 @@ import java.lang.foreign.SegmentAllocator; import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.NoSuchElementException; +import java.util.*; /** - * Processor types + * This class stores a map of processor types. + *

+ * You cannot register or unregister a builtin type. Builtin types are described in {@link ProcessorType}. * * @author squid233 * @since 0.1.0 */ public final class ProcessorTypes { private static final Map, ProcessorType> map = new LinkedHashMap<>(); + private static final Set> builtinTypes = Set.of( + void.class, + boolean.class, + char.class, + byte.class, + short.class, + int.class, + long.class, + float.class, + double.class, + MemorySegment.class, + String.class, + SegmentAllocator.class, + Struct.class, + Upcall.class + ); static { register(void.class, ProcessorType.Void.INSTANCE); @@ -122,11 +138,9 @@ public static ProcessorType fromParameter(Parameter parameter) { * @param type the processor type */ public static void register(Class aClass, ProcessorType type) { - if (type != null) { - map.put(aClass, type); - } else { - map.remove(aClass); - } + Objects.requireNonNull(aClass); + Objects.requireNonNull(type); + map.putIfAbsent(aClass, type); } /** @@ -150,6 +164,20 @@ public static void registerUpcall(Class aClass, ProcessorT register(aClass, ProcessorType.upcall(aClass, factory)); } + /** + * Unregisters the given class. + * + * @param aClass the class + * @return the previous registered type + */ + public static ProcessorType unregister(Class aClass) { + Objects.requireNonNull(aClass); + if (isBuiltinType(aClass)) { + throw new IllegalArgumentException("Cannot unregister builtin type " + aClass); + } + return map.remove(aClass); + } + /** * {@return {@code true} if the given class is registered} * For an array type, returns {@code true} if its component type is registered. @@ -178,4 +206,8 @@ public static boolean isRegisteredExactly(Class aClass) { if (aClass.isArray()) return isRegisteredExactly(aClass.componentType()); return map.containsKey(aClass); } + + private static boolean isBuiltinType(Class aClass) { + return builtinTypes.contains(aClass); + } } diff --git a/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java b/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java index da1ff9b..3aa29b8 100644 --- a/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java @@ -27,6 +27,9 @@ * @since 0.1.0 */ public final class RefCopyProcessor extends TypedCodeProcessor { + private RefCopyProcessor() { + } + public record Context( int srcSegmentSlot, int dstArraySlot, @@ -73,6 +76,9 @@ public boolean process(CodeBuilder builder, ProcessorType type, Context context) }; } + /** + * {@return the instance} + */ public static RefCopyProcessor getInstance() { class Holder { static final RefCopyProcessor INSTANCE = new RefCopyProcessor(); diff --git a/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java b/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java index 0807051..e198196 100644 --- a/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java +++ b/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java @@ -21,6 +21,9 @@ * @since 0.1.0 */ public final class RefTypeTransformer extends TypeTransformer { + private RefTypeTransformer() { + } + @Override public ProcessorType process(ProcessorType context) { return context instanceof ProcessorType.Array array ? switch (array.componentType()) { @@ -29,6 +32,9 @@ public ProcessorType process(ProcessorType context) { } : context; } + /** + * {@return the instance} + */ public static RefTypeTransformer getInstance() { class Holder { static final RefTypeTransformer INSTANCE = new RefTypeTransformer(); diff --git a/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java b/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java index e7ef510..dd65fb3 100644 --- a/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java +++ b/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java @@ -20,12 +20,20 @@ import java.util.List; /** + * @param the type of the return value + * @param the type of the context * @author squid233 * @since 0.1.0 */ public abstract class TypeTransformer implements Processor> { private final List> list = new ArrayList<>(); + /** + * constructor + */ + protected TypeTransformer() { + } + public R process(C context) { for (var transformer : list) { R r = transformer.process(context); diff --git a/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java b/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java index ab31894..bd41c3e 100644 --- a/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/TypedCodeProcessor.java @@ -21,12 +21,19 @@ import java.util.List; /** + * @param the type of the context * @author squid233 * @since 0.1.0 */ public abstract class TypedCodeProcessor implements Processor> { private final List> list = new ArrayList<>(); + /** + * constructor + */ + protected TypedCodeProcessor() { + } + public boolean process(CodeBuilder builder, ProcessorType type, T context) { for (var processor : list) { if (processor.process(builder, type, context)) { diff --git a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java index 05ae34e..39097e1 100644 --- a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java @@ -30,6 +30,9 @@ * @since 0.1.0 */ public final class UnmarshalProcessor extends TypedCodeProcessor { + private UnmarshalProcessor() { + } + public record Context( Class originalType, String charset, @@ -127,6 +130,9 @@ public boolean process(CodeBuilder builder, ProcessorType type, Context context) return true; } + /** + * {@return the instance} + */ public static UnmarshalProcessor getInstance() { class Holder { static final UnmarshalProcessor INSTANCE = new UnmarshalProcessor(); diff --git a/src/main/java/overrun/marshal/internal/StringCharset.java b/src/main/java/overrun/marshal/internal/StringCharset.java index 016b301..91acc88 100644 --- a/src/main/java/overrun/marshal/internal/StringCharset.java +++ b/src/main/java/overrun/marshal/internal/StringCharset.java @@ -35,18 +35,20 @@ private StringCharset() { } /** - * {@return hasCharset} + * Checks the given annotation * * @param strCharset strCharset + * @return {@code true} if {@code strCharset} is not null and has value */ public static boolean hasCharset(StrCharset strCharset) { return strCharset != null && !strCharset.value().isBlank(); } /** - * {@return getCharset} + * Gets the charset from the given element * * @param element element + * @return the charset */ public static String getCharset(AnnotatedElement element) { final StrCharset strCharset = element.getDeclaredAnnotation(StrCharset.class); @@ -54,10 +56,11 @@ public static String getCharset(AnnotatedElement element) { } /** - * getCharset + * Converts the given charset name to bytecode representing a {@code Charset} instance * * @param codeBuilder codeBuilder * @param charset charset + * @return {@code true} if {@code charset} is not null */ public static boolean getCharset(CodeBuilder codeBuilder, String charset) { if (charset == null) { diff --git a/src/main/java/overrun/marshal/struct/StructAllocatorSpec.java b/src/main/java/overrun/marshal/struct/StructAllocatorSpec.java index 6a8907d..0a84e37 100644 --- a/src/main/java/overrun/marshal/struct/StructAllocatorSpec.java +++ b/src/main/java/overrun/marshal/struct/StructAllocatorSpec.java @@ -22,6 +22,7 @@ /** * Specification of {@link StructAllocator}. * + * @param the type of the struct * @author squid233 * @since 0.1.0 */ From cd60812ab19c1643905727b80bbb9b9255ddb9f8 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:23:44 +0800 Subject: [PATCH 11/13] Add Javadoc --- src/main/java/overrun/marshal/Downcall.java | 27 ++++++--- .../gen/processor/AfterInvokeProcessor.java | 10 +++- .../gen/processor/BeforeInvokeProcessor.java | 19 ++++-- .../gen/processor/BeforeReturnProcessor.java | 9 ++- .../gen/processor/CharsetProcessor.java | 59 +++++++++++++++++++ .../marshal/gen/processor/CheckProcessor.java | 10 +++- .../marshal/gen/processor/CodeInserter.java | 10 ++++ .../gen/processor/DescriptorTransformer.java | 9 +++ .../gen/processor/MarshalProcessor.java | 21 ++++--- .../marshal/gen/processor/Processor.java | 4 +- .../marshal/gen/processor/ProcessorType.java | 2 +- .../gen/processor/RefCopyProcessor.java | 15 ++++- .../gen/processor/RefTypeTransformer.java | 2 + .../gen/processor/TypeTransformer.java | 9 +++ .../gen/processor/TypedCodeProcessor.java | 17 ++++++ .../gen/processor/UnmarshalProcessor.java | 22 ++++--- .../marshal/internal/StringCharset.java | 30 ---------- 17 files changed, 210 insertions(+), 65 deletions(-) create mode 100644 src/main/java/overrun/marshal/gen/processor/CharsetProcessor.java diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 00c801d..beab9de 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -117,6 +117,8 @@ * @see Critical * @see DirectAccess * @see Entrypoint + * @see Processor + * @see ProcessorType * @see Ref * @see Sized * @see Skip @@ -378,9 +380,9 @@ private static Map.Entry buildBytecode(MethodHandles.Looku blockCodeBuilder.aload(refSlotMap.get(parameter)); } else { MarshalProcessor.getInstance().process(blockCodeBuilder, processorType, new MarshalProcessor.Context( - StringCharset.getCharset(parameter), + allocatorSlot, blockCodeBuilder.parameterSlot(i), - allocatorSlot + StringCharset.getCharset(parameter) )); } downcallClassDescList.add(processorType.downcallClassDesc()); @@ -407,8 +409,8 @@ private static Map.Entry buildBytecode(MethodHandles.Looku // return UnmarshalProcessor.getInstance().process(blockCodeBuilder, returnProcessorType, new UnmarshalProcessor.Context( returnType, - StringCharset.getCharset(method), - resultSlot + resultSlot, + StringCharset.getCharset(method) )); blockCodeBuilder.return_(returnTypeKind); }, catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> { @@ -553,6 +555,12 @@ private static void verifyMethods(List list, Map signatu for (Method method : list) { String signature = signatureStringMap.get(method); + // check method annotation + Sized sized = method.getDeclaredAnnotation(Sized.class); + if (sized != null && sized.value() < 0) { + throw new IllegalStateException("Invalid value of @Sized annotation: " + sized.value() + " in method " + signature); + } + // check method return type final Class returnType = method.getReturnType(); if (!isValidReturnType(returnType)) { @@ -562,8 +570,7 @@ private static void verifyMethods(List list, Map signatu // check method parameter final Class[] types = method.getParameterTypes(); for (Parameter parameter : method.getParameters()) { - final Class type = parameter.getType(); - if (!isValidParamType(type)) { + if (!isValidParameter(parameter)) { throw new IllegalStateException("Invalid parameter: " + parameter + " in " + method); } } @@ -600,8 +607,12 @@ private static void verifyMethods(List list, Map signatu } } - private static boolean isValidParamType(Class aClass) { - return ProcessorTypes.isRegistered(aClass); + private static boolean isValidParameter(Parameter parameter) { + if (ProcessorTypes.isRegistered(parameter.getType())) { + Sized sized = parameter.getDeclaredAnnotation(Sized.class); + return sized == null || sized.value() >= 0; + } + return false; } private static boolean isValidReturnType(Class aClass) { diff --git a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java index 671b3b9..659f693 100644 --- a/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/AfterInvokeProcessor.java @@ -24,7 +24,9 @@ import java.util.Map; /** - * insert code after invoke + * Insert code after invoking the downcall handle. + *

+ * The default operation runs {@link RefCopyProcessor}. * * @author squid233 * @since 0.1.0 @@ -33,6 +35,12 @@ public final class AfterInvokeProcessor extends CodeInserter parameters, Map refSlotMap diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java index 25e6b32..ccb1d0f 100644 --- a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java @@ -26,7 +26,9 @@ import java.util.Map; /** - * insert codes before invoke + * Insert codes before invoking the downcall handle. + *

+ * The default operation transforms {@link Ref @Ref} annotated arrays with {@link RefTypeTransformer}. * * @author squid233 * @since 0.1.0 @@ -35,9 +37,16 @@ public final class BeforeInvokeProcessor extends CodeInserter parameters, - Map refSlot, + Map refSlotMap, int allocatorSlot ) { } @@ -55,12 +64,12 @@ public void process(CodeBuilder builder, Context context) { TypeKind refTypeKind = TypeKind.from(refType.downcallClassDesc()); int local = builder.allocateLocal(refTypeKind); MarshalProcessor.getInstance().process(builder, type, new MarshalProcessor.Context( - StringCharset.getCharset(parameter), + context.allocatorSlot(), builder.parameterSlot(i), - context.allocatorSlot() + StringCharset.getCharset(parameter) )); builder.storeLocal(refTypeKind, local); - context.refSlot().put(parameter, local); + context.refSlotMap().put(parameter, local); } } super.process(builder, context); diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java index bbce0e8..5d19f71 100644 --- a/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BeforeReturnProcessor.java @@ -22,7 +22,9 @@ import static overrun.marshal.internal.Constants.CD_MemoryStack; /** - * insert code before return + * Insert codes before returning the value. + *

+ * The default operation pops the memory stack. * * @author squid233 * @since 0.1.0 @@ -31,6 +33,11 @@ public final class BeforeReturnProcessor extends CodeInserter + builder.getstatic(CD_StandardCharsets, upperCase.replace('-', '_'), CD_Charset); + case "UTF_8", "ISO_8859_1", "US_ASCII", + "UTF_16", "UTF_16BE", "UTF_16LE", + "UTF_32", "UTF_32BE", "UTF_32LE" -> builder.getstatic(CD_StandardCharsets, upperCase, CD_Charset); + default -> builder.ldc(upperCase) + .invokestatic(CD_Charset, "forName", MTD_Charset_String); + } + return true; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java index c13df6a..2c4e1c0 100644 --- a/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/CheckProcessor.java @@ -25,7 +25,10 @@ import static overrun.marshal.internal.Constants.*; /** - * insert check codes + * Insert check methods at the beginning of the method body. + *

+ * The default operation inserts {@link overrun.marshal.Checks#checkArraySize(long, int) Checks::checkArraySize} + * for arrays annotated with {@link Sized @Sized}. * * @author squid233 * @since 0.1.0 @@ -34,6 +37,11 @@ public final class CheckProcessor extends CodeInserter { private CheckProcessor() { } + /** + * The context. + * + * @param parameters the parameters + */ public record Context(List parameters) { } diff --git a/src/main/java/overrun/marshal/gen/processor/CodeInserter.java b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java index 3d74f9e..fde500f 100644 --- a/src/main/java/overrun/marshal/gen/processor/CodeInserter.java +++ b/src/main/java/overrun/marshal/gen/processor/CodeInserter.java @@ -21,6 +21,10 @@ import java.util.List; /** + * Code inserters insert codes at a given position. + *

+ * The inserted bytecode must not return a non-void value. For example, subclasses can invoke a void method. + * * @param the type of the context * @author squid233 * @since 0.1.0 @@ -34,6 +38,12 @@ public abstract class CodeInserter implements Processor> { protected CodeInserter() { } + /** + * Sequentially runs all alternative processors. + * + * @param builder the code builder + * @param context the context + */ public void process(CodeBuilder builder, T context) { for (CodeInserter processor : list) { processor.process(builder, context); diff --git a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java index 2f814c9..acc0612 100644 --- a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java +++ b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java @@ -28,6 +28,8 @@ import java.util.List; /** + * Transforms the method and parameters into a function descriptor. + * * @author squid233 * @since 0.1.0 */ @@ -35,6 +37,13 @@ public final class DescriptorTransformer extends TypeTransformer + * The inserted bytecode must represent a {@link java.lang.foreign.MemorySegment MemorySegment}. * * @author squid233 * @since 0.1.0 @@ -32,10 +32,17 @@ public final class MarshalProcessor extends TypedCodeProcessor builder @@ -121,7 +128,7 @@ public boolean process(CodeBuilder builder, ProcessorType type, Context context) .aload(variableSlot) .invokestatic(CD_Marshal, "marshal", - StringCharset.getCharset(builder, context.charset()) ? + CharsetProcessor.process(builder, context.charset()) ? MTD_MemorySegment_SegmentAllocator_String_Charset : MTD_MemorySegment_SegmentAllocator_String); case ProcessorType.Struct _ -> builder diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/Processor.java index ecbe399..b8b8172 100644 --- a/src/main/java/overrun/marshal/gen/processor/Processor.java +++ b/src/main/java/overrun/marshal/gen/processor/Processor.java @@ -17,7 +17,9 @@ package overrun.marshal.gen.processor; /** - * The superinterface of processors. + * Processors are used to indicate how to process a value in {@link overrun.marshal.Downcall Downcall}. + *

+ * See subclasses for more information. * * @param the type of this * @author squid233 diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java index 1bd1ffe..1842d07 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -32,7 +32,7 @@ import static overrun.marshal.internal.Constants.CD_SegmentAllocator; /** - * Processor types are used to indicate how to process a type in {@link overrun.marshal.Downcall Downcall}. + * Processor types are used to remember the type of value to be processed in {@link Processor}. *

Builtin type

* Builtin types include 8 primitive types, {@link MemorySegment}, {@link String}, * {@link overrun.marshal.struct.Struct Struct} without allocator and {@link overrun.marshal.Upcall Upcall} diff --git a/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java b/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java index 3aa29b8..ccb23ad 100644 --- a/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/RefCopyProcessor.java @@ -16,13 +16,15 @@ package overrun.marshal.gen.processor; -import overrun.marshal.internal.StringCharset; - import java.lang.classfile.CodeBuilder; import static overrun.marshal.internal.Constants.*; /** + * Insert code to copy from a segment to an array. + *

+ * The inserted code represents void type. + * * @author squid233 * @since 0.1.0 */ @@ -30,6 +32,13 @@ public final class RefCopyProcessor extends TypedCodeProcessor the type of the return value * @param the type of the context * @author squid233 @@ -34,6 +36,13 @@ public abstract class TypeTransformer implements Processor + * The inserted bytecode represents a specific type. + * See subclasses for more information. + * * @param the type of the context * @author squid233 * @since 0.1.0 @@ -34,6 +39,18 @@ public abstract class TypedCodeProcessor implements Processor + * Except for Marshal's classes, subclasses should not directly call this method + * if they failed to or did not process; instead, they should return {@code false}. + * + * @param builder the code builder + * @param type the type of the value to be processed + * @param context the context + * @return {@code true} if successfully processed the value; otherwise {@code false} + * @throws IllegalStateException if the value of {@code type} was not processed + */ public boolean process(CodeBuilder builder, ProcessorType type, T context) { for (var processor : list) { if (processor.process(builder, type, context)) { diff --git a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java index 39097e1..b4fea9b 100644 --- a/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/UnmarshalProcessor.java @@ -16,15 +16,15 @@ package overrun.marshal.gen.processor; -import overrun.marshal.internal.StringCharset; - import java.lang.classfile.CodeBuilder; import java.lang.constant.ClassDesc; import static overrun.marshal.internal.Constants.*; /** - * insert unmarshal (C-to-Java) method + * Insert unmarshal (C-to-Java) method. + *

+ * The inserted code must represent the original type in the context. * * @author squid233 * @since 0.1.0 @@ -33,10 +33,18 @@ public final class UnmarshalProcessor extends TypedCodeProcessor originalType, - String charset, - int variableSlot + int variableSlot, + String charset ) { } @@ -61,7 +69,7 @@ public boolean process(CodeBuilder builder, ProcessorType type, Context context) .aload(variableSlot) .invokestatic(CD_Unmarshal, "unmarshalAsStringArray", - StringCharset.getCharset(builder, context.charset()) ? + CharsetProcessor.process(builder, context.charset()) ? MTD_StringArray_MemorySegment_Charset : MTD_StringArray_MemorySegment); case ProcessorType.Value value -> builder @@ -108,7 +116,7 @@ public boolean process(CodeBuilder builder, ProcessorType type, Context context) .aload(variableSlot) .invokestatic(CD_Unmarshal, "unboundString", - StringCharset.getCharset(builder, context.charset()) ? + CharsetProcessor.process(builder, context.charset()) ? MTD_String_MemorySegment_Charset : MTD_String_MemorySegment); case ProcessorType.Struct _ -> builder diff --git a/src/main/java/overrun/marshal/internal/StringCharset.java b/src/main/java/overrun/marshal/internal/StringCharset.java index 91acc88..b9aff89 100644 --- a/src/main/java/overrun/marshal/internal/StringCharset.java +++ b/src/main/java/overrun/marshal/internal/StringCharset.java @@ -18,11 +18,7 @@ import overrun.marshal.gen.StrCharset; -import java.lang.classfile.CodeBuilder; import java.lang.reflect.AnnotatedElement; -import java.util.Locale; - -import static overrun.marshal.internal.Constants.*; /** * String charsets @@ -54,30 +50,4 @@ public static String getCharset(AnnotatedElement element) { final StrCharset strCharset = element.getDeclaredAnnotation(StrCharset.class); return hasCharset(strCharset) ? strCharset.value() : null; } - - /** - * Converts the given charset name to bytecode representing a {@code Charset} instance - * - * @param codeBuilder codeBuilder - * @param charset charset - * @return {@code true} if {@code charset} is not null - */ - public static boolean getCharset(CodeBuilder codeBuilder, String charset) { - if (charset == null) { - return false; - } - final String upperCase = charset.toUpperCase(Locale.ROOT); - switch (upperCase) { - case "UTF-8", "ISO-8859-1", "US-ASCII", - "UTF-16", "UTF-16BE", "UTF-16LE", - "UTF-32", "UTF-32BE", "UTF-32LE" -> - codeBuilder.getstatic(CD_StandardCharsets, upperCase.replace('-', '_'), CD_Charset); - case "UTF_8", "ISO_8859_1", "US_ASCII", - "UTF_16", "UTF_16BE", "UTF_16LE", - "UTF_32", "UTF_32BE", "UTF_32LE" -> codeBuilder.getstatic(CD_StandardCharsets, upperCase, CD_Charset); - default -> codeBuilder.ldc(charset) - .invokestatic(CD_Charset, "forName", MTD_Charset_String); - } - return true; - } } From 951f947bfa669839d77d639055ed4f6509827c89 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Tue, 27 Aug 2024 20:44:34 +0800 Subject: [PATCH 12/13] Remove RefTypeTransformer.java; add CEnumSimulateTest --- README.md | 2 +- src/main/java/overrun/marshal/Downcall.java | 25 +- .../java/overrun/marshal/DowncallOption.java | 12 +- src/main/java/overrun/marshal/Unmarshal.java | 2 +- .../gen/processor/BeforeInvokeProcessor.java | 8 +- .../gen/processor/DescriptorTransformer.java | 2 +- .../gen/processor/MarshalProcessor.java | 3 +- .../marshal/gen/processor/ProcessorType.java | 2 +- .../gen/processor/RefTypeTransformer.java | 46 --- .../gen/processor/TypeTransformer.java | 10 +- .../java/overrun/marshal/struct/ByValue.java | 2 +- .../test/downcall/CEnumSimulateTest.java | 298 ++++++++++++++++++ 12 files changed, 338 insertions(+), 74 deletions(-) delete mode 100644 src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java create mode 100644 src/test/java/overrun/marshal/test/downcall/CEnumSimulateTest.java diff --git a/README.md b/README.md index a55d69c..3473014 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Marshal allows you to conveniently create native library bindings with [FFM API] ~~See [wiki](https://github.com/Over-Run/marshal/wiki) for more information.~~ -This library requires JDK 23 or newer. +This library requires JDK 23 ~~or newer~~. ## Overview diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index beab9de..3de35f1 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -99,8 +99,11 @@ * You can {@linkplain ProcessorTypes#register(Class, ProcessorType) register} a type and your own processor * as builtin processors may require an additional processor. *

- * For custom types, you should {@linkplain Processor#addProcessor(Processor) add} processors to subclasses of - * {@link TypedCodeProcessor} and {@link TypeTransformer}. + * For custom types, you must register them before + * {@linkplain #load(MethodHandles.Lookup, SymbolLookup, DowncallOption...) loading}, + * and you should {@linkplain Processor#addProcessor(Processor) add} processors to subclasses of + * {@link TypedCodeProcessor} and {@link TypeTransformer} (especially {@link MarshalProcessor} and + * {@link UnmarshalProcessor}). *

* Builtin types are described in {@link ProcessorType}. *

Example

@@ -149,7 +152,8 @@ public static T load(MethodHandles.Lookup caller, SymbolLookup lookup, Downc } /** - * Loads a class with the given library name and options. + * Loads a class with the given library name and options using + * {@link SymbolLookup#libraryLookup(String, Arena) SymbolLookup::libraryLookup}. * * @param caller the lookup object for the caller * @param libPath the path of the library @@ -643,6 +647,7 @@ private static DowncallData generateData( descriptorMap1.put(entrypoint, descriptor); final Optional optional = lookup.find(entrypoint); + MethodHandle handle; if (optional.isPresent()) { // linker options final Linker.Option[] options; @@ -653,14 +658,16 @@ private static DowncallData generateData( options = NO_OPTION; } - if (!map.containsKey(entrypoint)) { - map.put(entrypoint, transform.apply(LINKER.downcallHandle(optional.get(), descriptor, options))); - } - } else if (method.isDefault()) { - map.putIfAbsent(entrypoint, null); + handle = transform.apply(LINKER.downcallHandle(optional.get(), descriptor, options)); } else { - throw new NoSuchElementException("Symbol not found: " + entrypoint + " (" + descriptor + "): " + methodData.signatureString()); + MethodHandle apply = transform.apply(null); + if (apply != null || method.isDefault()) { + handle = apply; + } else { + throw new NoSuchElementException("Symbol not found: " + entrypoint + " (" + descriptor + "): " + methodData.signatureString()); + } } + map.putIfAbsent(entrypoint, handle); } return new DowncallData(Collections.unmodifiableMap(descriptorMap1), Collections.unmodifiableMap(map), diff --git a/src/main/java/overrun/marshal/DowncallOption.java b/src/main/java/overrun/marshal/DowncallOption.java index 26886cf..5de534b 100644 --- a/src/main/java/overrun/marshal/DowncallOption.java +++ b/src/main/java/overrun/marshal/DowncallOption.java @@ -16,11 +16,13 @@ package overrun.marshal; +import org.jetbrains.annotations.Nullable; import overrun.marshal.internal.DowncallOptions; import java.lang.foreign.FunctionDescriptor; import java.lang.invoke.MethodHandle; import java.util.Map; +import java.util.Objects; import java.util.function.UnaryOperator; /** @@ -37,7 +39,7 @@ public sealed interface DowncallOption * @param aClass the target class. use {@code null} for caller class * @return the option instance */ - static DowncallOption targetClass(Class aClass) { + static DowncallOption targetClass(@Nullable Class aClass) { return new DowncallOptions.TargetClass(aClass); } @@ -48,16 +50,20 @@ static DowncallOption targetClass(Class aClass) { * @return the option instance */ static DowncallOption descriptors(Map descriptorMap) { + Objects.requireNonNull(descriptorMap); return new DowncallOptions.Descriptors(descriptorMap); } /** * Specifies the method handle transformer. + *

+ * The transformer will be used when each downcall handle is generated in {@link Downcall}. * - * @param transform the transforming function + * @param transform the transforming function. the argument of the function might be null * @return the option instance */ - static DowncallOption transform(UnaryOperator transform) { + static DowncallOption transform(UnaryOperator<@Nullable MethodHandle> transform) { + Objects.requireNonNull(transform); return new DowncallOptions.Transform(transform); } } diff --git a/src/main/java/overrun/marshal/Unmarshal.java b/src/main/java/overrun/marshal/Unmarshal.java index 67fa835..4daaeba 100644 --- a/src/main/java/overrun/marshal/Unmarshal.java +++ b/src/main/java/overrun/marshal/Unmarshal.java @@ -373,7 +373,7 @@ public static boolean unmarshalAsBoolean(double v) { * @param elementLayout the source element layout * @param segment the segment * @param generator a function which produces a new array of the desired type and the provided length - * @param function a function to apply to each element + * @param function a function to apply to each element. the argument of the function is a slice of {@code segment} * @param the type of the element * @return the array */ diff --git a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java index ccb1d0f..c7a3fdb 100644 --- a/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/BeforeInvokeProcessor.java @@ -28,7 +28,7 @@ /** * Insert codes before invoking the downcall handle. *

- * The default operation transforms {@link Ref @Ref} annotated arrays with {@link RefTypeTransformer}. + * The default operation transforms {@link Ref @Ref} annotated arrays with {@link MarshalProcessor}. * * @author squid233 * @since 0.1.0 @@ -60,15 +60,13 @@ public void process(CodeBuilder builder, Context context) { if (parameter.getType().isArray() && parameter.getDeclaredAnnotation(Ref.class) != null) { ProcessorType type = ProcessorTypes.fromParameter(parameter); - ProcessorType refType = RefTypeTransformer.getInstance().process(type); - TypeKind refTypeKind = TypeKind.from(refType.downcallClassDesc()); - int local = builder.allocateLocal(refTypeKind); + int local = builder.allocateLocal(TypeKind.ReferenceType); MarshalProcessor.getInstance().process(builder, type, new MarshalProcessor.Context( context.allocatorSlot(), builder.parameterSlot(i), StringCharset.getCharset(parameter) )); - builder.storeLocal(refTypeKind, local); + builder.astore(local); context.refSlotMap().put(parameter, local); } } diff --git a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java index acc0612..9d92ee2 100644 --- a/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java +++ b/src/main/java/overrun/marshal/gen/processor/DescriptorTransformer.java @@ -33,7 +33,7 @@ * @author squid233 * @since 0.1.0 */ -public final class DescriptorTransformer extends TypeTransformer { +public final class DescriptorTransformer extends TypeTransformer { private DescriptorTransformer() { } diff --git a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java index a625ffa..13b7a3f 100644 --- a/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java +++ b/src/main/java/overrun/marshal/gen/processor/MarshalProcessor.java @@ -23,7 +23,8 @@ /** * Insert marshal (Java-to-C) method. *

- * The inserted bytecode must represent a {@link java.lang.foreign.MemorySegment MemorySegment}. + * The inserted bytecode must represent a type specified by the given processor type with + * {@link ProcessorType#downcallClassDesc() downcallClassDesc}. * * @author squid233 * @since 0.1.0 diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java index 1842d07..5f958e7 100644 --- a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -519,7 +519,7 @@ public AllocatorRequirement allocationRequirement() { @Override public String toString() { - return "Array(" + componentType + ")"; + return componentType + "[]"; } } diff --git a/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java b/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java deleted file mode 100644 index 79b99d2..0000000 --- a/src/main/java/overrun/marshal/gen/processor/RefTypeTransformer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen.processor; - -/** - * Transforms array type to ref storage type ({@link java.lang.foreign.MemorySegment MemorySegment}). - * - * @author squid233 - * @since 0.1.0 - */ -public final class RefTypeTransformer extends TypeTransformer { - private RefTypeTransformer() { - } - - @Override - public ProcessorType process(ProcessorType context) { - return context instanceof ProcessorType.Array array ? switch (array.componentType()) { - case ProcessorType.Str _, ProcessorType.Value _ -> ProcessorType.Value.ADDRESS; - default -> super.process(context); - } : context; - } - - /** - * {@return the instance} - */ - public static RefTypeTransformer getInstance() { - class Holder { - static final RefTypeTransformer INSTANCE = new RefTypeTransformer(); - } - return Holder.INSTANCE; - } -} diff --git a/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java b/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java index 6eec401..096a31c 100644 --- a/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java +++ b/src/main/java/overrun/marshal/gen/processor/TypeTransformer.java @@ -22,13 +22,13 @@ /** * Type transformers transforms the given context to a specific type. * + * @param the type of the context * @param the type of the return value - * @param the type of the context * @author squid233 * @since 0.1.0 */ -public abstract class TypeTransformer implements Processor> { - private final List> list = new ArrayList<>(); +public abstract class TypeTransformer implements Processor> { + private final List> list = new ArrayList<>(); /** * constructor @@ -43,7 +43,7 @@ protected TypeTransformer() { * @param context the context * @return a value */ - public R process(C context) { + public R process(T context) { for (var transformer : list) { R r = transformer.process(context); if (r != null) { @@ -54,7 +54,7 @@ public R process(C context) { } @Override - public void addProcessor(TypeTransformer transformer) { + public void addProcessor(TypeTransformer transformer) { list.add(transformer); } } diff --git a/src/main/java/overrun/marshal/struct/ByValue.java b/src/main/java/overrun/marshal/struct/ByValue.java index f5d284c..b451d3a 100644 --- a/src/main/java/overrun/marshal/struct/ByValue.java +++ b/src/main/java/overrun/marshal/struct/ByValue.java @@ -24,7 +24,7 @@ *

* The annotated method must contain a segment allocator as the first parameter. *

Passing-by-value structure

- * Marks a parameter that passes to C function by value. + * Marks a parameter that passes struct to C function by value. *

Example

*
{@code
  * @ByValue
diff --git a/src/test/java/overrun/marshal/test/downcall/CEnumSimulateTest.java b/src/test/java/overrun/marshal/test/downcall/CEnumSimulateTest.java
new file mode 100644
index 0000000..d3290a9
--- /dev/null
+++ b/src/test/java/overrun/marshal/test/downcall/CEnumSimulateTest.java
@@ -0,0 +1,298 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2024 Overrun Organization
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package overrun.marshal.test.downcall;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import overrun.marshal.Downcall;
+import overrun.marshal.DowncallOption;
+import overrun.marshal.Unmarshal;
+import overrun.marshal.gen.Ref;
+import overrun.marshal.gen.Sized;
+import overrun.marshal.gen.processor.*;
+
+import java.lang.classfile.CodeBuilder;
+import java.lang.constant.ClassDesc;
+import java.lang.constant.ConstantDescs;
+import java.lang.constant.MethodTypeDesc;
+import java.lang.foreign.*;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Optional;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * This test simulates the removed {@code CEnum} interface.
+ *
+ * @author squid233
+ * @since 0.1.0
+ */
+@SuppressWarnings("preview")
+public class CEnumSimulateTest {
+    private static final ClassDesc CD_MyEnum = ClassDesc.of("overrun.marshal.test.downcall.CEnumSimulateTest$MyEnum");
+    private static Functions instance;
+
+    enum MyEnum {
+        A, B, C;
+
+        // from int
+        static MyEnum byValue(int value) {
+            return switch (value) {
+                case 0 -> A;
+                case 1 -> B;
+                case 2 -> C;
+                default -> throw new IllegalStateException("Unexpected value: " + value);
+            };
+        }
+
+        // to int
+        int value() {
+            return ordinal();
+        }
+
+        static MemorySegment marshal(SegmentAllocator allocator, MyEnum[] values) {
+            if (values == null) {
+                return MemorySegment.NULL;
+            }
+            MemorySegment segment = allocator.allocate(ValueLayout.JAVA_INT, values.length);
+            for (int i = 0; i < values.length; i++) {
+                MyEnum anEnum = values[i];
+                segment.setAtIndex(ValueLayout.JAVA_INT, i, anEnum != null ? anEnum.value() : 0);
+            }
+            return segment;
+        }
+
+        static MyEnum[] unmarshal(MemorySegment segment) {
+            return Unmarshal.unmarshal(ValueLayout.JAVA_INT, segment, MyEnum[]::new, segment1 -> MyEnum.byValue(segment1.get(ValueLayout.JAVA_INT, 0)));
+        }
+
+        static void copy(MemorySegment src, MyEnum[] dst) {
+            if (Unmarshal.isNullPointer(src) || dst == null) return;
+            for (int i = 0; i < dst.length; i++) {
+                dst[i] = MyEnum.byValue(src.getAtIndex(ValueLayout.JAVA_INT, i));
+            }
+        }
+
+        /**
+         * processor type definition
+         */
+        static class Type implements ProcessorType.Custom {
+            public static final Type INSTANCE = new Type();
+
+            @Override
+            public ClassDesc downcallClassDesc() {
+                return ConstantDescs.CD_int;
+            }
+
+            @Override
+            public MemoryLayout downcallLayout() {
+                return ValueLayout.JAVA_INT;
+            }
+
+            @Override
+            public AllocatorRequirement allocationRequirement() {
+                return AllocatorRequirement.NONE;
+            }
+        }
+    }
+
+    /**
+     * functions to be tested
+     */
+    interface Functions {
+        int func(MyEnum myEnum);
+
+        MyEnum funcReturn(int value);
+
+        @Sized(3)
+        MyEnum[] funcReturnArray();
+
+        int funcArray(MyEnum... values);
+
+        void funcRef(@Ref MyEnum[] values);
+
+        /**
+         * implementation (simulates C functions)
+         */
+        class Impl {
+            static final MemorySegment returnArray = Arena.global().allocateFrom(ValueLayout.JAVA_INT, 2, 1, 0);
+
+            static int func(int myEnum) {
+                return myEnum * 2;
+            }
+
+            static int funcReturn(int value) {
+                return value;
+            }
+
+            static MemorySegment funcReturnArray() {
+                return returnArray;
+            }
+
+            static int funcArray(MemorySegment values) {
+                MemorySegment segment = values.reinterpret(ValueLayout.JAVA_INT.scale(0, 3));
+                return segment.get(ValueLayout.JAVA_INT, 0) + segment.get(ValueLayout.JAVA_INT, 4) + segment.get(ValueLayout.JAVA_INT, 8);
+            }
+
+            static void funcRef(MemorySegment values) {
+                MemorySegment segment = values.reinterpret(ValueLayout.JAVA_INT.scale(0, 3));
+                segment.set(ValueLayout.JAVA_INT, 0, 2);
+                segment.set(ValueLayout.JAVA_INT, 4, 1);
+                segment.set(ValueLayout.JAVA_INT, 8, 0);
+            }
+        }
+    }
+
+    @BeforeAll
+    static void beforeAll() throws Exception {
+        Linker linker = Linker.nativeLinker();
+        MethodHandles.Lookup handles = MethodHandles.lookup();
+        Arena arena = Arena.ofAuto();
+
+        MemorySegment _func = linker.upcallStub(handles.findStatic(Functions.Impl.class, "func", MethodType.methodType(int.class, int.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT), arena);
+        MemorySegment _funcReturn = linker.upcallStub(handles.findStatic(Functions.Impl.class, "funcReturn", MethodType.methodType(int.class, int.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT), arena);
+        MemorySegment _funcReturnArray = linker.upcallStub(handles.findStatic(Functions.Impl.class, "funcReturnArray", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ValueLayout.ADDRESS), arena);
+        MemorySegment _funcArray = linker.upcallStub(handles.findStatic(Functions.Impl.class, "funcArray", MethodType.methodType(int.class, MemorySegment.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS), arena);
+        MemorySegment _funcRef = linker.upcallStub(handles.findStatic(Functions.Impl.class, "funcRef", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ValueLayout.ADDRESS), arena);
+
+        SymbolLookup lookup = name -> switch (name) {
+            case "func" -> Optional.of(_func);
+            case "funcReturn" -> Optional.of(_funcReturn);
+            case "funcReturnArray" -> Optional.of(_funcReturnArray);
+            case "funcArray" -> Optional.of(_funcArray);
+            case "funcRef" -> Optional.of(_funcRef);
+            default -> Optional.empty();
+        };
+
+        // Must register processor types before generating bytecode
+        ProcessorTypes.register(MyEnum.class, MyEnum.Type.INSTANCE);
+        MarshalProcessor.getInstance().addProcessor(new TypedCodeProcessor<>() {
+            @Override
+            public boolean process(CodeBuilder builder, ProcessorType type, MarshalProcessor.Context context) {
+                return switch (type) {
+                    case MyEnum.Type _ -> {
+                        builder.aload(context.variableSlot())
+                            .invokevirtual(CD_MyEnum,
+                                "value",
+                                MethodTypeDesc.of(ConstantDescs.CD_int));
+                        yield true;
+                    }
+                    case ProcessorType.Array array -> switch (array.componentType()) {
+                        case MyEnum.Type _ -> {
+                            builder.aload(context.allocatorSlot())
+                                .aload(context.variableSlot())
+                                .invokestatic(CD_MyEnum,
+                                    "marshal",
+                                    MethodTypeDesc.of(MemorySegment.class.describeConstable().orElseThrow(),
+                                        SegmentAllocator.class.describeConstable().orElseThrow(),
+                                        CD_MyEnum.arrayType()));
+                            yield true;
+                        }
+                        default -> false;
+                    };
+                    default -> false;
+                };
+            }
+        });
+        UnmarshalProcessor.getInstance().addProcessor(new TypedCodeProcessor<>() {
+            @Override
+            public boolean process(CodeBuilder builder, ProcessorType type, UnmarshalProcessor.Context context) {
+                return switch (type) {
+                    case MyEnum.Type _ -> {
+                        builder.iload(context.variableSlot())
+                            .invokestatic(CD_MyEnum,
+                                "byValue",
+                                MethodTypeDesc.of(CD_MyEnum, ConstantDescs.CD_int));
+                        yield true;
+                    }
+                    case ProcessorType.Array array -> switch (array.componentType()) {
+                        case MyEnum.Type _ -> {
+                            builder.aload(context.variableSlot())
+                                .invokestatic(CD_MyEnum,
+                                    "unmarshal",
+                                    MethodTypeDesc.of(CD_MyEnum.arrayType(), MemorySegment.class.describeConstable().orElseThrow()));
+                            yield true;
+                        }
+                        default -> false;
+                    };
+                    default -> false;
+                };
+            }
+        });
+        RefCopyProcessor.getInstance().addProcessor(new TypedCodeProcessor<>() {
+            @Override
+            public boolean process(CodeBuilder builder, ProcessorType type, RefCopyProcessor.Context context) {
+                if (!(type instanceof ProcessorType.Array array)) {
+                    return false;
+                }
+                if (array.componentType() instanceof MyEnum.Type) {
+                    builder.aload(context.srcSegmentSlot())
+                        .aload(context.dstArraySlot())
+                        .invokestatic(CD_MyEnum,
+                            "copy",
+                            MethodTypeDesc.of(ConstantDescs.CD_void,
+                                MemorySegment.class.describeConstable().orElseThrow(),
+                                CD_MyEnum.arrayType()));
+                    return true;
+                }
+                return false;
+            }
+        });
+
+        instance = Downcall.load(handles, lookup, DowncallOption.targetClass(Functions.class));
+    }
+
+    @Test
+    void testCEnumValue() {
+        assertEquals(0, MyEnum.A.value());
+        assertEquals(1, MyEnum.B.value());
+        assertEquals(2, MyEnum.C.value());
+    }
+
+    @Test
+    void testCEnumParam() {
+        assertEquals(0, instance.func(MyEnum.A));
+        assertEquals(2, instance.func(MyEnum.B));
+        assertEquals(4, instance.func(MyEnum.C));
+    }
+
+    @Test
+    void testCEnumReturn() {
+        assertEquals(MyEnum.A, instance.funcReturn(0));
+        assertEquals(MyEnum.B, instance.funcReturn(1));
+        assertEquals(MyEnum.C, instance.funcReturn(2));
+    }
+
+    @Test
+    void testCEnumReturnArray() {
+        assertArrayEquals(new MyEnum[]{MyEnum.C, MyEnum.B, MyEnum.A}, instance.funcReturnArray());
+    }
+
+    @Test
+    void testCEnumArray() {
+        assertEquals(3, instance.funcArray(MyEnum.A, MyEnum.B, MyEnum.C));
+    }
+
+    @Test
+    void testCEnumRef() {
+        MyEnum[] arr = new MyEnum[3];
+        instance.funcRef(arr);
+        assertArrayEquals(new MyEnum[]{MyEnum.C, MyEnum.B, MyEnum.A}, arr);
+    }
+}

From c4bea051e3cd9a12847a6552aa5921a6e2ac3ac4 Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Tue, 27 Aug 2024 22:30:21 +0800
Subject: [PATCH 13/13] Add DowncallOption::skipClass

---
 src/main/java/overrun/marshal/Downcall.java   | 85 +++++++++----------
 .../java/overrun/marshal/DowncallOption.java  | 16 +++-
 .../marshal/internal/DowncallOptions.java     |  8 ++
 3 files changed, 64 insertions(+), 45 deletions(-)

diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java
index 3de35f1..2a80a66 100644
--- a/src/main/java/overrun/marshal/Downcall.java
+++ b/src/main/java/overrun/marshal/Downcall.java
@@ -37,7 +37,6 @@
 import java.lang.reflect.Parameter;
 import java.util.*;
 import java.util.function.Function;
-import java.util.function.Predicate;
 import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 
@@ -60,8 +59,6 @@
  * 

Methods

* The loader finds method from the target class and its superclasses. *

- * The loader skips static methods and methods annotated with {@link Skip @Skip} while generating. - *

* {@link Entrypoint @Entrypoint} specifies the entrypoint of the annotated method. * An entrypoint is a symbol name * that locates to the function when {@linkplain SymbolLookup#find(String) finding} the address. @@ -72,6 +69,14 @@ * instead, it will return {@code null} and automatically invokes the original method declared in the target class. *

* {@link Critical @Critical} indicates that the annotated method is {@linkplain Linker.Option#critical(boolean) critical}. + *

Skipping

+ * The loader skips the following type of method: + *
    + *
  • {@link Skip @Skip} annotated method
  • + *
  • static, final, {@linkplain Method#isSynthetic() synthetic} or {@linkplain Method#isBridge() bridge} method
  • + *
  • methods in {@link Object} and {@link DirectAccess}
  • + *
  • methods in classes specified by {@link DowncallOption#skipClass(Class) DowncallOption::skipClass}
  • + *
*

Annotations

* See {@link Convert @Convert}, {@link Ref @Ref}, {@link Sized @Sized} and {@link StrCharset @StrCharset}. *

Direct Access

@@ -180,6 +185,20 @@ private static void invokeSuperMethod(CodeBuilder codeBuilder, List p // method + private record SkipMethodSignature(String methodName, Class[] parameterTypes) { + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof SkipMethodSignature that)) return false; + return Objects.equals(methodName, that.methodName) && Objects.deepEquals(parameterTypes, that.parameterTypes); + } + + @Override + public int hashCode() { + return Objects.hash(methodName, Arrays.hashCode(parameterTypes)); + } + } + private static String getMethodEntrypoint(Method method) { final Entrypoint entrypoint = method.getDeclaredAnnotation(Entrypoint.class); return (entrypoint == null || entrypoint.value().isBlank()) ? @@ -191,14 +210,17 @@ private static Map.Entry buildBytecode(MethodHandles.Looku Class _targetClass = null, targetClass; Map _descriptorMap = null, descriptorMap; UnaryOperator _transform = null, transform; + Set> skipClasses = new HashSet<>(); + skipClasses.add(Object.class); + skipClasses.add(DirectAccess.class); for (DowncallOption option : options) { switch (option) { case DowncallOptions.TargetClass(var aClass) -> _targetClass = aClass; case DowncallOptions.Descriptors(var map) -> _descriptorMap = map; case DowncallOptions.Transform(var operator) -> _transform = operator; - case null, default -> { - } + case DowncallOptions.SkipClass(var clazz) -> skipClasses.add(clazz); + case null -> throw new IllegalArgumentException("No null option in Downcall::load"); } } @@ -207,8 +229,16 @@ private static Map.Entry buildBytecode(MethodHandles.Looku descriptorMap = _descriptorMap != null ? _descriptorMap : Map.of(); transform = _transform != null ? _transform : UnaryOperator.identity(); + var skipSignatures = skipClasses.stream() + .mapMulti((aClass, consumer) -> { + for (Method method : aClass.getDeclaredMethods()) { + consumer.accept(new SkipMethodSignature(method.getName(), method.getParameterTypes())); + } + }) + .toList(); + final List methodList = Arrays.stream(targetClass.getMethods()) - .filter(Predicate.not(Downcall::shouldSkip)) + .filter(method -> !shouldSkip(skipSignatures, method)) .toList(); final Map signatureStringMap = methodList.stream() .collect(Collectors.toUnmodifiableMap(Function.identity(), Downcall::createSignatureString)); @@ -506,46 +536,13 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look } } - private static boolean shouldSkip(Method method) { - final Class returnType = method.getReturnType(); - if (method.getDeclaredAnnotation(Skip.class) != null || + private static boolean shouldSkip(List skipMethodSignatures, Method method) { + return method.getDeclaredAnnotation(Skip.class) != null || Modifier.isStatic(method.getModifiers()) || + Modifier.isFinal(method.getModifiers()) || method.isSynthetic() || - Modifier.isFinal(method.getModifiers())) { - return true; - } - - final String methodName = method.getName(); - final Class[] types = method.getParameterTypes(); - final int length = types.length; - - // check method declared by Object or DirectAccess - return switch (length) { - case 0 -> switch (methodName) { - // Object - case "getClass" -> returnType == Class.class; - case "hashCode" -> returnType == int.class; - case "clone" -> returnType == Object.class; - case "toString" -> returnType == String.class; - case "notify", "notifyAll", "wait" -> returnType == void.class; - case "finalize" -> returnType == void.class; // TODO: no finalize in the future - // DirectAccess - case "functionDescriptors", "methodHandles" -> returnType == Map.class; - case "symbolLookup" -> returnType == SymbolLookup.class; - default -> false; - }; - case 1 -> switch (methodName) { - // Object - case "equals" -> returnType == boolean.class && types[0] == Object.class; - case "wait" -> returnType == void.class && types[0] == long.class; - // DirectAccess - case "methodHandle" -> returnType == MethodHandle.class && types[0] == String.class; - default -> false; - }; - case 2 -> // Object - returnType == void.class && types[0] == long.class && types[1] == long.class && "wait".equals(methodName); - default -> false; - }; + method.isBridge() || + skipMethodSignatures.contains(new SkipMethodSignature(method.getName(), method.getParameterTypes())); } private static String createSignatureString(Method method) { diff --git a/src/main/java/overrun/marshal/DowncallOption.java b/src/main/java/overrun/marshal/DowncallOption.java index 5de534b..c6760b9 100644 --- a/src/main/java/overrun/marshal/DowncallOption.java +++ b/src/main/java/overrun/marshal/DowncallOption.java @@ -32,7 +32,7 @@ * @since 0.1.0 */ public sealed interface DowncallOption - permits DowncallOptions.Descriptors, DowncallOptions.TargetClass, DowncallOptions.Transform { + permits DowncallOptions.Descriptors, DowncallOptions.SkipClass, DowncallOptions.TargetClass, DowncallOptions.Transform { /** * Specifies the target class. * @@ -66,4 +66,18 @@ static DowncallOption transform(UnaryOperator<@Nullable MethodHandle> transform) Objects.requireNonNull(transform); return new DowncallOptions.Transform(transform); } + + /** + * Adds a class to skip. + * Methods {@linkplain Class#getDeclaredMethods() declared} in the added class will be skipped. + *

+ * There might be more than one this option added. + * + * @param clazz the class + * @return the option instance + */ + static DowncallOption skipClass(Class clazz) { + Objects.requireNonNull(clazz); + return new DowncallOptions.SkipClass(clazz); + } } diff --git a/src/main/java/overrun/marshal/internal/DowncallOptions.java b/src/main/java/overrun/marshal/internal/DowncallOptions.java index 5c0cd42..56a080a 100644 --- a/src/main/java/overrun/marshal/internal/DowncallOptions.java +++ b/src/main/java/overrun/marshal/internal/DowncallOptions.java @@ -57,4 +57,12 @@ public record Descriptors(Map descriptorMap) impleme */ public record Transform(UnaryOperator operator) implements DowncallOption { } + + /** + * specify a class to skip + * + * @param clazz the class + */ + public record SkipClass(Class clazz) implements DowncallOption { + } }