From a2c6a69782d71ad13102f357446fbc7f57ef5b1e Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 16 Dec 2023 23:21:04 +0800 Subject: [PATCH 1/7] Added annotation Struct --- build.gradle.kts | 14 +- demo/build.gradle.kts | 6 +- .../overrun/marshal/test/CStructTest.java | 37 +++ src/main/java/module-info.java | 1 + .../overrun/marshal/DowncallProcessor.java | 12 +- .../java/overrun/marshal/StructProcessor.java | 252 ++++++++++++++++++ .../java/overrun/marshal/gen/ClassSpec.java | 35 ++- .../java/overrun/marshal/gen/MethodSpec.java | 15 +- src/main/java/overrun/marshal/gen/Spec.java | 52 +++- .../marshal/gen/VariableStatement.java | 8 +- .../java/overrun/marshal/internal/Util.java | 49 +++- .../java/overrun/marshal/package-info.java | 1 + .../java/overrun/marshal/struct/Const.java | 41 +++ .../java/overrun/marshal/struct/IStruct.java | 43 +++ .../java/overrun/marshal/struct/Padding.java | 40 +++ .../java/overrun/marshal/struct/Struct.java | 48 ++++ .../overrun/marshal/struct/package-info.java | 24 ++ .../javax.annotation.processing.Processor | 1 + 18 files changed, 626 insertions(+), 53 deletions(-) create mode 100644 demo/src/main/java/overrun/marshal/test/CStructTest.java create mode 100644 src/main/java/overrun/marshal/StructProcessor.java create mode 100644 src/main/java/overrun/marshal/struct/Const.java create mode 100644 src/main/java/overrun/marshal/struct/IStruct.java create mode 100644 src/main/java/overrun/marshal/struct/Padding.java create mode 100644 src/main/java/overrun/marshal/struct/Struct.java create mode 100644 src/main/java/overrun/marshal/struct/package-info.java diff --git a/build.gradle.kts b/build.gradle.kts index 549e163..c305c37 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -126,14 +126,14 @@ allprojects { //"Main-Class" to "org.example.Main" ) } +} - tasks.withType { - archiveBaseName = projArtifactId - from(rootProject.file(projLicenseFileName)).rename( - projLicenseFileName, - "${projLicenseFileName}_$projArtifactId" - ) - } +tasks.withType { + archiveBaseName = projArtifactId + from(rootProject.file(projLicenseFileName)).rename( + projLicenseFileName, + "${projLicenseFileName}_$projArtifactId" + ) } tasks.withType { diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index ace47e6..373efa1 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -1,6 +1,8 @@ -plugins { `java-library` } - dependencies { implementation(rootProject) annotationProcessor(rootProject) } + +tasks.withType { + archiveBaseName = "marshal-demo" +} diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java new file mode 100644 index 0000000..63c8347 --- /dev/null +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -0,0 +1,37 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.struct.Padding; +import overrun.marshal.struct.Struct; + +import java.lang.foreign.MemorySegment; + +/** + * @author squid233 + * @since 0.1.0 + */ +@Struct +final class CStructTest { + int x, y; + long stamp; + byte b; + @Padding(7) + int padding; + MemorySegment segment; + String name; +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index da2528d..4b0bb75 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -22,6 +22,7 @@ */ module io.github.overrun.marshal { exports overrun.marshal; + exports overrun.marshal.struct; requires java.compiler; } diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index ce1db00..334d23b 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -91,9 +91,9 @@ private void writeFile( printError(""" Class name must start with C if the name is not specified. Current name: %s Possible solutions: - 1) Add C as a prefix. For example: %s - 2) Specify name in @Downcall and rename this file. For example: @Downcall(%1$s)""" - .formatted(string, 'C' + string)); + 1) Add C as a prefix. For example: C%1$s + 2) Specify name in @Downcall and rename this file. For example: @Downcall(name = "%1$s")""" + .formatted(string)); return; } } else { @@ -197,7 +197,7 @@ private void writeFile( } if (shouldInsertArena || shouldInsertAllocator) { methodSpec.addParameter( - shouldInsertArena ? Arena.class.getSimpleName() : SegmentAllocator.class.getSimpleName(), + shouldInsertArena ? Arena.class : SegmentAllocator.class, allocatorParamName ); } @@ -390,7 +390,7 @@ private void writeFile( .filter(method -> method.getAnnotation(Upcall.Wrapper.class) != null) .filter(method -> { final var list = method.getParameters(); - return list.size() == 1 && isMemorySegment(list.get(0).asType()); + return list.size() == 1 && isMemorySegment(list.getFirst().asType()); }) .findFirst(); if (wrapMethod.isPresent()) { @@ -607,7 +607,7 @@ private static List collectConflictedMethodSignature(ExecutableElement e if (t.getKind() == TypeKind.VOID) { return ".VOID"; } - if (isSupportedType(t)) { + if (isValueType(t)) { return toValueLayout(t); } return t.toString(); diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java new file mode 100644 index 0000000..dbf897c --- /dev/null +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -0,0 +1,252 @@ +/* + * MIT License + * + * Copyright (c) 2023 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 overrun.marshal.gen.*; +import overrun.marshal.internal.Processor; +import overrun.marshal.internal.Util; +import overrun.marshal.struct.Const; +import overrun.marshal.struct.IStruct; +import overrun.marshal.struct.Padding; +import overrun.marshal.struct.Struct; + +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.tools.JavaFileObject; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.foreign.*; +import java.lang.invoke.VarHandle; +import java.util.List; +import java.util.Set; + +import static overrun.marshal.internal.Util.*; + +/** + * Struct annotation processor + * + * @author squid233 + * @since 0.1.0 + */ +public final class StructProcessor extends Processor { + /** + * constructor + */ + public StructProcessor() { + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + try { + processClasses(roundEnv); + } catch (Exception e) { + printStackTrace(e); + } + return false; + } + + private void processClasses(RoundEnvironment roundEnv) { + ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Struct.class)).forEach(e -> { + final var enclosed = e.getEnclosedElements(); + try { + writeFile(e, ElementFilter.fieldsIn(enclosed)); + } catch (IOException ex) { + printStackTrace(ex); + } + }); + } + + private void writeFile( + TypeElement type, + List fields + ) throws IOException { + final Struct struct = type.getAnnotation(Struct.class); + final String className = type.getQualifiedName().toString(); + final int lastDot = className.lastIndexOf('.'); + final String packageName = lastDot > 0 ? className.substring(0, lastDot) : null; + final String simpleClassName; + if (struct.name().isBlank()) { + final String string = className.substring(lastDot + 1); + if (string.startsWith("C")) { + simpleClassName = string.substring(1); + } else { + printError(""" + Class name must start with C if the name is not specified. Current name: %s + Possible solutions: + 1) Add C as a prefix. For example: C%1$s + 2) Specify name in @Struct and rename this file. For example: @Struct(name = "%1$s")""" + .formatted(string)); + return; + } + } else { + simpleClassName = struct.name(); + } + + final SourceFile file = new SourceFile(packageName); + file.addImport("java.lang.foreign.*"); + file.addImports( + MemoryLayout.PathElement.class, + VarHandle.class + ); + file.addClass(simpleClassName, classSpec -> { + final Const typeConst = type.getAnnotation(Const.class); + + classSpec.setDocument(getDocument(type)); + classSpec.setFinal(!struct.nonFinal()); + classSpec.addSuperinterface(IStruct.class.getCanonicalName()); + + // layout + classSpec.addField(new VariableStatement(StructLayout.class, "LAYOUT", + new InvokeSpec(MemoryLayout.class, "structLayout").also(invokeSpec -> fields.forEach(e -> { + final TypeMirror eType = e.asType(); + final Padding padding = e.getAnnotation(Padding.class); + if (padding != null) { + invokeSpec.addArgument(new InvokeSpec(MemoryLayout.class, "paddingLayout") + .addArgument(getConstExp(padding.value()))); + } else if (Util.isValueType(eType)) { + invokeSpec.addArgument(new InvokeSpec(toValueLayout(eType), "withName") + .addArgument(getConstExp(e.getSimpleName().toString()))); + } else { + printError("Unsupported field: " + eType + e.getSimpleName()); + // TODO: 2023/12/16 squid233: struct, upcall + } + }))) + .setDocument(" The layout of this struct.") + .setStatic(true) + .setFinal(true)); + classSpec.addField(new VariableStatement(MemoryLayout.PathElement.class, "_SEQUENCE_ELEMENT", + new InvokeSpec(MemoryLayout.PathElement.class, "sequenceElement"))); + fields.forEach(e -> classSpec.addField(new VariableStatement(MemoryLayout.PathElement.class, "_PE_" + e.getSimpleName(), + new InvokeSpec(MemoryLayout.PathElement.class, "groupElement").addArgument(getConstExp(e.getSimpleName().toString()))) + .setAccessModifier(AccessModifier.PRIVATE) + .setStatic(true) + .setFinal(true))); + + classSpec.addField(new VariableStatement(MemorySegment.class, "_memorySegment", null) + .setAccessModifier(AccessModifier.PRIVATE) + .setFinal(true)); + classSpec.addField(new VariableStatement(SequenceLayout.class, "_sequenceLayout", null) + .setAccessModifier(AccessModifier.PRIVATE) + .setFinal(true)); + + // var handles + fields.forEach(e -> classSpec.addField(new VariableStatement(VarHandle.class, e.getSimpleName().toString(), null) + .setAccessModifier(AccessModifier.PRIVATE) + .setFinal(true))); + + // constructor + classSpec.addMethod(new MethodSpec(null, simpleClassName), methodSpec -> { + methodSpec.setDocument(""" + Creates {@code %s} with the given segment and element count. + + @param segment the segment + @param count the element count\ + """.formatted(simpleClassName)); + methodSpec.addParameter(MemorySegment.class, "segment"); + methodSpec.addParameter(long.class, "count"); + methodSpec.addStatement(Spec.assignStatement("this._memorySegment", Spec.literal("segment"))); + methodSpec.addStatement(Spec.assignStatement("this._sequenceLayout", new InvokeSpec(MemoryLayout.class, "sequenceLayout") + .addArgument("count") + .addArgument("LAYOUT"))); + fields.forEach(e -> { + final var name = e.getSimpleName(); + methodSpec.addStatement(Spec.assignStatement(Spec.accessSpec("this", name.toString()), + new InvokeSpec(Spec.literal("this._sequenceLayout"), "varHandle") + .addArgument("_SEQUENCE_ELEMENT") + .addArgument("_PE_" + name))); + }); + }); + + // allocator + classSpec.addMethod(new MethodSpec(simpleClassName, "create"), methodSpec -> { + methodSpec.setStatic(true); + methodSpec.addParameter(SegmentAllocator.class, "allocator"); + methodSpec.addStatement(Spec.returnStatement(new ConstructSpec(simpleClassName) + .addArgument(new InvokeSpec("allocator", "allocate") + .addArgument("LAYOUT")) + .addArgument(Spec.literal("1")))); + }); + classSpec.addMethod(new MethodSpec(simpleClassName, "create"), methodSpec -> { + methodSpec.setStatic(true); + methodSpec.addParameter(SegmentAllocator.class, "allocator"); + methodSpec.addParameter(long.class, "count"); + methodSpec.addStatement(Spec.returnStatement(new ConstructSpec(simpleClassName) + .addArgument(new InvokeSpec("allocator", "allocate") + .addArgument("LAYOUT") + .addArgument("count")) + .addArgument(Spec.literal("count")))); + }); + + + // member + fields.forEach(e -> { + if (e.getAnnotation(Padding.class) == null) { + final TypeMirror eType = e.asType(); + final String nameString = e.getSimpleName().toString(); + if (isValueType(eType)) { + final boolean canConvertToAddress = canConvertToAddress(eType); + final String returnType = canConvertToAddress ? MemorySegment.class.getSimpleName() : eType.toString(); + classSpec.addMethod(new MethodSpec( + returnType, + (canConvertToAddress ? "nget" : "get") + capitalize(nameString) + "At" + ), methodSpec -> { + methodSpec.addParameter(long.class, "index"); + methodSpec.addStatement(Spec.returnStatement(Spec.cast( + returnType, + new InvokeSpec(Spec.accessSpec("this", nameString), "get") + .addArgument("_memorySegment")))); + }); + if (canConvertToAddress && !isMemorySegment(eType)) { + classSpec.addMethod(new MethodSpec( + simplify(eType.toString()), + "get" + capitalize(nameString) + "At" + ), methodSpec -> { + methodSpec.addParameter(long.class, "index"); + methodSpec.addStatement(Spec.returnStatement(Spec.literal("null"))); + }); + } + } + } + }); + + // override + addIStructImpl(classSpec, MemorySegment.class, "segment", Spec.literal("_memorySegment")); + addIStructImpl(classSpec, StructLayout.class, "layout", Spec.literal("LAYOUT")); + addIStructImpl(classSpec, long.class, "elementCount", new InvokeSpec("_sequenceLayout", "elementCount")); + }); + + final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + '.' + simpleClassName); + try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) { + file.write(out); + } + } + + private static void addIStructImpl(ClassSpec classSpec, Class aClass, String name, Spec spec) { + classSpec.addMethod(new MethodSpec(aClass.getSimpleName(), name), methodSpec -> { + methodSpec.addAnnotation(new AnnotationSpec(Override.class)); + methodSpec.addStatement(Spec.returnStatement(spec)); + }); + } + + @Override + public Set getSupportedAnnotationTypes() { + return Set.of(Struct.class.getCanonicalName()); + } +} diff --git a/src/main/java/overrun/marshal/gen/ClassSpec.java b/src/main/java/overrun/marshal/gen/ClassSpec.java index f22c34f..30b1f06 100644 --- a/src/main/java/overrun/marshal/gen/ClassSpec.java +++ b/src/main/java/overrun/marshal/gen/ClassSpec.java @@ -46,6 +46,7 @@ public final class ClassSpec implements Spec { private final List methodSpecs = new ArrayList<>(); private final List annotationSpecs = new ArrayList<>(); private final List superclasses = new ArrayList<>(); + private final List superinterfaces = new ArrayList<>(); /** * Constructor @@ -141,6 +142,15 @@ public void addSuperclass(String className) { superclasses.add(className); } + /** + * Add superinterface + * + * @param className class name + */ + public void addSuperinterface(String className) { + superinterfaces.add(className); + } + @Override public void append(StringBuilder builder, int indent) { final String indentString = Spec.indentString(indent); @@ -164,16 +174,8 @@ public void append(StringBuilder builder, int indent) { throw new IllegalStateException("Unsupported class type for " + className + ": " + classType); }) .append(' ').append(className); - if (!superclasses.isEmpty()) { - builder.append(" extends "); - for (int i = 0; i < superclasses.size(); i++) { - final String superclass = superclasses.get(i); - if (i != 0) { - builder.append(", "); - } - builder.append(superclass); - } - } + addAfterClass(builder, "extends", superclasses); + addAfterClass(builder, "implements", superinterfaces); builder.append(" {\n"); // body fieldSpecs.forEach(variableStatement -> variableStatement.append(builder, indent + 4)); @@ -182,4 +184,17 @@ public void append(StringBuilder builder, int indent) { // end builder.append(indentString).append("}\n"); } + + private void addAfterClass(StringBuilder builder, String keyword, List list) { + if (!list.isEmpty()) { + builder.append(" ").append(keyword).append(" "); + for (int i = 0; i < list.size(); i++) { + final String s = list.get(i); + if (i != 0) { + builder.append(", "); + } + builder.append(s); + } + } + } } diff --git a/src/main/java/overrun/marshal/gen/MethodSpec.java b/src/main/java/overrun/marshal/gen/MethodSpec.java index 6828ede..e250f13 100644 --- a/src/main/java/overrun/marshal/gen/MethodSpec.java +++ b/src/main/java/overrun/marshal/gen/MethodSpec.java @@ -86,6 +86,16 @@ public void addParameter(ParameterSpec parameterSpec) { parameters.add(parameterSpec); } + /** + * Add a parameter + * + * @param type type + * @param name name + */ + public void addParameter(Class type, String name) { + addParameter(type.getSimpleName(), name); + } + /** * Add a parameter * @@ -155,7 +165,10 @@ public void append(StringBuilder builder, int indent) { if (isDefault) { builder.append("default "); } - builder.append(returnType).append(' ').append(name).append('('); + if (returnType != null) { + builder.append(returnType).append(' '); + } + builder.append(name).append('('); if (separateLine) { builder.append('\n').append(indentString4); } diff --git a/src/main/java/overrun/marshal/gen/Spec.java b/src/main/java/overrun/marshal/gen/Spec.java index 2989d61..f820fa8 100644 --- a/src/main/java/overrun/marshal/gen/Spec.java +++ b/src/main/java/overrun/marshal/gen/Spec.java @@ -25,6 +25,16 @@ * @since 0.1.0 */ public interface Spec { + /** + * Create a literal string + * + * @param s the string + * @return the spec + */ + static Spec literal(String s) { + return (builder, indent) -> builder.append(s); + } + /** * Create simple class name spec * @@ -45,6 +55,38 @@ static Spec className(Class clazz) { return literal(clazz.getCanonicalName()); } + /** + * Create an assign statement + * + * @param left left + * @param right right + * @return statement + */ + static Spec assignStatement(Spec left, Spec right) { + return (builder, indent) -> { + builder.append(indentString(indent)); + left.append(builder, indent); + builder.append(" = "); + right.append(builder, indent); + builder.append(";\n"); + }; + } + + /** + * Create an assign statement + * + * @param left left + * @param right right + * @return statement + */ + static Spec assignStatement(String left, Spec right) { + return (builder, indent) -> { + builder.append(indentString(indent)).append(left).append(" = "); + right.append(builder, indent); + builder.append(";\n"); + }; + } + /** * Create access spec * @@ -164,16 +206,6 @@ static Spec statement(Spec spec) { }; } - /** - * Create a literal string - * - * @param s the string - * @return the spec - */ - static Spec literal(String s) { - return (builder, indent) -> builder.append(s); - } - /** * Create an indented literal string * diff --git a/src/main/java/overrun/marshal/gen/VariableStatement.java b/src/main/java/overrun/marshal/gen/VariableStatement.java index 32a47a9..ad105e1 100644 --- a/src/main/java/overrun/marshal/gen/VariableStatement.java +++ b/src/main/java/overrun/marshal/gen/VariableStatement.java @@ -115,9 +115,11 @@ public void append(StringBuilder builder, int indent) { if (isFinal) { builder.append("final "); } - builder.append(type).append(' ').append(name) - .append(" = "); - value.append(builder, indent); + builder.append(type).append(' ').append(name); + if (value != null) { + builder.append(" = "); + value.append(builder, indent); + } builder.append(";\n"); } } diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index 95664d7..2ce0da9 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -32,6 +32,22 @@ private Util() { //no instance } + /** + * capitalize + * + * @param str str + * @return capitalize + */ + public static String capitalize(String str) { + final int len = str == null ? 0 : str.length(); + if (len == 0) return str; + final int codePoint0 = str.codePointAt(0); + final int titleCase = Character.toTitleCase(codePoint0); + if (codePoint0 == titleCase) return str; + if (len > 1) return (char) titleCase + str.substring(1); + return String.valueOf((char) titleCase); + } + /** * Invalid type * @@ -139,21 +155,32 @@ public static TypeMirror getArrayComponentType(TypeMirror typeMirror) { } /** - * isSupportedType + * canConvertToAddress * * @param typeMirror typeMirror - * @return isSupportedType + * @return canConvertToAddress */ - public static boolean isSupportedType(TypeMirror typeMirror) { + public static boolean canConvertToAddress(TypeMirror typeMirror) { return switch (typeMirror.getKind()) { - case BOOLEAN, BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE -> true; case ARRAY -> isPrimitiveArray(typeMirror); - case DECLARED -> // TODO: 2023/12/15 Add support to struct - isMemorySegment(typeMirror) || isString(typeMirror); + case DECLARED -> isMemorySegment(typeMirror) || isString(typeMirror); default -> false; }; } + /** + * isValueType + * + * @param typeMirror typeMirror + * @return isValueType + */ + public static boolean isValueType(TypeMirror typeMirror) { + return switch (typeMirror.getKind()) { + case BOOLEAN, BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE -> true; + default -> canConvertToAddress(typeMirror); + }; + } + /** * toValueLayout * @@ -170,16 +197,10 @@ public static String toValueLayout(TypeMirror typeMirror) { case CHAR -> "ValueLayout.JAVA_CHAR"; case FLOAT -> "ValueLayout.JAVA_FLOAT"; case DOUBLE -> "ValueLayout.JAVA_DOUBLE"; - case ARRAY -> { - if (isPrimitiveArray(typeMirror)) yield "ValueLayout.ADDRESS"; - else throw invalidType(typeMirror); - } - case DECLARED -> { - if (isMemorySegment(typeMirror) || isString(typeMirror)) yield "ValueLayout.ADDRESS"; - // TODO: 2023/12/15 Add support to struct + default -> { + if (canConvertToAddress(typeMirror)) yield "ValueLayout.ADDRESS"; throw invalidType(typeMirror); } - default -> throw invalidType(typeMirror); }; } } diff --git a/src/main/java/overrun/marshal/package-info.java b/src/main/java/overrun/marshal/package-info.java index d8a7776..a670d2e 100644 --- a/src/main/java/overrun/marshal/package-info.java +++ b/src/main/java/overrun/marshal/package-info.java @@ -20,6 +20,7 @@ * @author squid233 * @see overrun.marshal.Downcall * @see overrun.marshal.Upcall + * @see overrun.marshal.struct * @since 0.1.0 */ package overrun.marshal; diff --git a/src/main/java/overrun/marshal/struct/Const.java b/src/main/java/overrun/marshal/struct/Const.java new file mode 100644 index 0000000..18fb77d --- /dev/null +++ b/src/main/java/overrun/marshal/struct/Const.java @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.struct; + +import java.lang.annotation.*; + +/** + * Marks a struct or its member as const. + *

+ * A const struct or its member does not generate setter. + *

Example

+ *
{@code
+ * @Const
+ * @Struct
+ * class Vector2 {
+ *     int x, y;
+ * }
+ * }
+ * + * @author squid233 + * @since 0.1.0 + */ +@Documented +@Target({ElementType.TYPE,ElementType.FIELD}) +@Retention(RetentionPolicy.SOURCE) +public @interface Const { +} diff --git a/src/main/java/overrun/marshal/struct/IStruct.java b/src/main/java/overrun/marshal/struct/IStruct.java new file mode 100644 index 0000000..8c9acad --- /dev/null +++ b/src/main/java/overrun/marshal/struct/IStruct.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.struct; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.StructLayout; + +/** + * A struct provider. + * + * @author squid233 + * @since 0.1.0 + */ +public interface IStruct { + /** + * {@return the memory segment of this struct} + */ + MemorySegment segment(); + + /** + * {@return the layout of this struct} + */ + StructLayout layout(); + + /** + * {@return the element count of this struct} + */ + long elementCount(); +} diff --git a/src/main/java/overrun/marshal/struct/Padding.java b/src/main/java/overrun/marshal/struct/Padding.java new file mode 100644 index 0000000..d965ef4 --- /dev/null +++ b/src/main/java/overrun/marshal/struct/Padding.java @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.struct; + +import java.lang.annotation.*; + +/** + * Marks a field as padding bytes. The marked field can be of any type. + *

Example

+ *
{@code
+ * @Padding(4)
+ * byte padding0;
+ * }
+ * + * @author squid233 + * @since 0.1.0 + */ +@Documented +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface Padding { + /** + * {@return the padding size (expressed in bytes)} + */ + long value(); +} diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/struct/Struct.java new file mode 100644 index 0000000..b9f2a75 --- /dev/null +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -0,0 +1,48 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.struct; + +import java.lang.annotation.*; + +/** + * Marks a class or interface as a struct provider. + *

Example

+ *
{@code
+ * @Struct
+ * class Point {
+ *     int x, y;
+ * }
+ * }
+ * + * @author squid233 + * @see Const + * @since 0.1.0 + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface Struct { + /** + * {@return the name of the generated class} + */ + String name() default ""; + + /** + * {@return {@code true} if the generated class should not be {@code final}; {@code false} otherwise} + */ + boolean nonFinal() default false; +} diff --git a/src/main/java/overrun/marshal/struct/package-info.java b/src/main/java/overrun/marshal/struct/package-info.java new file mode 100644 index 0000000..0167995 --- /dev/null +++ b/src/main/java/overrun/marshal/struct/package-info.java @@ -0,0 +1,24 @@ +/* + * MIT License + * + * Copyright (c) 2023 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. + */ + +/** + * The struct package of marshal. + * + * @author squid233 + * @see overrun.marshal.struct.Struct + * @since 0.1.0 + */ +package overrun.marshal.struct; diff --git a/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/src/main/resources/META-INF/services/javax.annotation.processing.Processor index 6a48f28..20d181f 100644 --- a/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1 +1,2 @@ overrun.marshal.DowncallProcessor +overrun.marshal.StructProcessor From cd8a3f97aba0c1dda2548afe952f3aed02b6c41a Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sun, 17 Dec 2023 16:15:33 +0800 Subject: [PATCH 2/7] Struct member getter --- README.md | 6 +- .../marshal/test/CConstStructTest.java | 30 +++ .../overrun/marshal/test/CDowncallTest.java | 12 +- .../overrun/marshal/test/CStructTest.java | 25 +- src/main/java/overrun/marshal/Checks.java | 2 +- src/main/java/overrun/marshal/Downcall.java | 4 +- .../overrun/marshal/DowncallProcessor.java | 143 ++++++------ src/main/java/overrun/marshal/LongSized.java | 43 ++++ .../marshal/{FixedSize.java => Sized.java} | 6 +- src/main/java/overrun/marshal/Skip.java | 36 +++ src/main/java/overrun/marshal/StrCharset.java | 2 +- .../java/overrun/marshal/StructProcessor.java | 219 ++++++++++++++---- .../java/overrun/marshal/gen/Annotatable.java | 32 +++ .../java/overrun/marshal/gen/ClassSpec.java | 3 +- .../java/overrun/marshal/gen/MethodSpec.java | 3 +- .../overrun/marshal/gen/ParameterSpec.java | 3 +- src/main/java/overrun/marshal/gen/Spec.java | 32 ++- .../overrun/marshal/internal/Processor.java | 58 ++++- .../java/overrun/marshal/internal/Util.java | 42 +++- .../java/overrun/marshal/struct/Const.java | 2 +- 20 files changed, 553 insertions(+), 150 deletions(-) create mode 100644 demo/src/main/java/overrun/marshal/test/CConstStructTest.java create mode 100644 src/main/java/overrun/marshal/LongSized.java rename src/main/java/overrun/marshal/{FixedSize.java => Sized.java} (89%) create mode 100644 src/main/java/overrun/marshal/Skip.java create mode 100644 src/main/java/overrun/marshal/gen/Annotatable.java diff --git a/README.md b/README.md index 8397378..6749ba9 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ interface CGLFW { double getTime(); /** - * Fixed size array. + * Sized array. * Note: this method doesn't exist in GLFW *

* You can mark methods with Critical @@ -64,7 +64,7 @@ interface CGLFW { * @param arr The array */ @Critical(allowHeapAccess = true) - void fixedSizeArray(@FixedSize(2) int[] arr); + void sizedArray(@Sized(2) int[] arr); /** * A simple method @@ -101,7 +101,7 @@ class Main { GLFW.glfwSwapInterval(1); GLFW.glfwEnableVSync(); double time = GLFW.getTime(); - GLFW.fixedSizeArray(new int[]{4, 2}); + GLFW.sizedArray(new int[]{4, 2}); MemorySegment windowHandle = /*...*/createWindow(); // MemoryStack is a placeholder type try (MemoryStack stack = /*...*/stackPush()) { diff --git a/demo/src/main/java/overrun/marshal/test/CConstStructTest.java b/demo/src/main/java/overrun/marshal/test/CConstStructTest.java new file mode 100644 index 0000000..9c80a39 --- /dev/null +++ b/demo/src/main/java/overrun/marshal/test/CConstStructTest.java @@ -0,0 +1,30 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.struct.Const; +import overrun.marshal.struct.Struct; + +/** + * @author squid233 + * @since 0.1.0 + */ +@Const +@Struct +public class CConstStructTest { + int x, y, z; +} diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index ff61db6..fb5beb6 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -81,7 +81,7 @@ interface CDowncallTest { void testWithRefArray(MemorySegment arr0, MemorySegment arr1, MemorySegment arr2, MemorySegment arr3, MemorySegment arr4, MemorySegment arr5); @Overload - void testWithRefArray(int[] arr0, @Ref int[] arr1, @Ref(nullable = true) int[] arr2, boolean[] arr3, @Ref boolean[] arr4, @FixedSize(3) int[] arr5); + void testWithRefArray(int[] arr0, @Ref int[] arr1, @Ref(nullable = true) int[] arr2, boolean[] arr3, @Ref boolean[] arr4, @Sized(3) int[] arr5); void testWithString(MemorySegment str1, MemorySegment str2); @@ -123,6 +123,16 @@ interface CDowncallTest { @Critical(allowHeapAccess = false) void testCriticalFalse(); + @Entrypoint("testReturnSizedArr") + MemorySegment ntestReturnSizedArr(); + + @Overload("ntestReturnSizedArr") + @Sized(4) + int[] testReturnSizedArr(); + + @LongSized(4L) + MemorySegment testReturnLongSizedSeg(); + void testUpcall(MemorySegment cb); @Overload diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java index 63c8347..3862f3b 100644 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -16,22 +16,45 @@ package overrun.marshal.test; +import overrun.marshal.LongSized; +import overrun.marshal.Sized; +import overrun.marshal.Skip; +import overrun.marshal.StrCharset; +import overrun.marshal.struct.Const; import overrun.marshal.struct.Padding; import overrun.marshal.struct.Struct; import java.lang.foreign.MemorySegment; /** + * {@linkplain #_LAYOUT Layout} + * * @author squid233 * @since 0.1.0 */ @Struct final class CStructTest { - int x, y; + @Skip + int _LAYOUT; + int x; + @Const + int y; + /** + * the timestamp + */ long stamp; byte b; @Padding(7) int padding; MemorySegment segment; + @LongSized(16L) + MemorySegment longSizedSegment; String name; + @StrCharset("UTF-16") + String utf16Name; + int[] arr; + @Sized(4) + int[] sizedArr; + boolean[] boolArr; + String[] strArr; } diff --git a/src/main/java/overrun/marshal/Checks.java b/src/main/java/overrun/marshal/Checks.java index 6b970b6..cd6ab00 100644 --- a/src/main/java/overrun/marshal/Checks.java +++ b/src/main/java/overrun/marshal/Checks.java @@ -28,7 +28,7 @@ public final class Checks { /** * Check size of a fixed size array argument. Default value: {@code true} * - * @see FixedSize + * @see Sized */ public static final Entry CHECK_ARRAY_SIZE = new Entry<>(() -> true); diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 92d7d89..e0d6144 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -42,7 +42,7 @@ * Methods without {@link Overload @Overload} marked * converts parameters of {@link String} and array types into {@link java.lang.foreign.MemorySegment MemorySegment}. *

Parameter Annotations

- * See {@link FixedSize @FixedSize} and {@link Ref @Ref}. + * See {@link Sized @Sized} and {@link Ref @Ref}. *

Example

*
{@code
  * @Downcall(libname = "libGL.so", name = "GL")
@@ -58,7 +58,7 @@
  * @see Default
  * @see Entrypoint
  * @see Overload
- * @see FixedSize
+ * @see Sized
  * @see Ref
  * @since 0.1.0
  */
diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java
index 334d23b..0f3da2d 100644
--- a/src/main/java/overrun/marshal/DowncallProcessor.java
+++ b/src/main/java/overrun/marshal/DowncallProcessor.java
@@ -29,7 +29,6 @@
 import java.io.PrintWriter;
 import java.lang.foreign.*;
 import java.lang.invoke.MethodHandle;
-import java.nio.charset.Charset;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Set;
@@ -101,17 +100,8 @@ private void writeFile(
         }
 
         final SourceFile file = new SourceFile(packageName);
-        file.addImports(
-            BoolHelper.class,
-            Checks.class,
-            Critical.class,
-            Default.class,
-            FixedSize.class,
-            Ref.class,
-            StrHelper.class,
-            MethodHandle.class
-        );
-        file.addImport("java.lang.foreign.*");
+        file.addImports("overrun.marshal.*", "java.lang.foreign.*");
+        file.addImport(MethodHandle.class);
         file.addClass(simpleClassName, classSpec -> {
             classSpec.setDocument(getDocument(type));
             classSpec.setFinal(!downcall.nonFinal());
@@ -145,20 +135,13 @@ private void writeFile(
 
                 classSpec.addMethod(new MethodSpec(javaReturnType, methodName), methodSpec -> {
                     final var parameters = e.getParameters();
-                    final boolean shouldInsertArena = parameters.stream().anyMatch(p -> {
-                        final TypeMirror pType = p.asType();
-                        return pType.getKind() == TypeKind.DECLARED &&
-                               isUpcall(pType);
-                    });
+                    final boolean shouldInsertArena = parameters.stream().map(VariableElement::asType).anyMatch(this::isUpcall);
                     final boolean shouldInsertAllocator = !shouldInsertArena && parameters.stream().anyMatch(p -> {
                         final TypeMirror t = p.asType();
-                        return isArray(t) || (t.getKind() == TypeKind.DECLARED && isString(t));
+                        return isArray(t) || isString(t);
                         // TODO: 2023/12/9 Struct
                     });
                     final boolean notVoid = returnType.getKind() != TypeKind.VOID;
-                    final boolean returnArray = isArray(returnType);
-                    final boolean returnBooleanArray = returnArray && isBooleanArray(returnType);
-                    final boolean returnStringArray = returnArray && isString(getArrayComponentType(returnType));
                     final boolean shouldStoreResult = notVoid &&
                                                       parameters.stream().anyMatch(p -> p.getAnnotation(Ref.class) != null);
                     final Access access = e.getAnnotation(Access.class);
@@ -166,6 +149,8 @@ private void writeFile(
                     final Custom custom = e.getAnnotation(Custom.class);
                     final Default defaultAnnotation = e.getAnnotation(Default.class);
                     final boolean isDefaulted = defaultAnnotation != null;
+                    final LongSized eLongSized = e.getAnnotation(LongSized.class);
+                    final Sized eSized = e.getAnnotation(Sized.class);
 
                     final String allocatorParamName = shouldInsertArena ? "_arena" : "_segmentAllocator";
 
@@ -191,6 +176,8 @@ private void writeFile(
                             }
                         }));
                     }
+                    addAnnotationValue(methodSpec, eLongSized, LongSized.class, LongSized::value);
+                    addAnnotationValue(methodSpec, eSized, Sized.class, Sized::value);
 
                     if (access != null) {
                         methodSpec.setAccessModifier(access.value());
@@ -210,12 +197,9 @@ private void writeFile(
                     }
                     parameters.forEach(p -> methodSpec.addParameter(new ParameterSpec(parameterTypeFunction.apply(p.asType()), p.getSimpleName().toString())
                         .also(parameterSpec -> {
-                            final FixedSize fixedSize = p.getAnnotation(FixedSize.class);
+                            addAnnotationValue(parameterSpec, p.getAnnotation(LongSized.class), LongSized.class, LongSized::value);
+                            addAnnotationValue(parameterSpec, p.getAnnotation(Sized.class), Sized.class, Sized::value);
                             final Ref ref = p.getAnnotation(Ref.class);
-                            if (fixedSize != null) {
-                                parameterSpec.addAnnotation(new AnnotationSpec(FixedSize.class)
-                                    .addArgument("value", getConstExp(fixedSize.value())));
-                            }
                             if (ref != null) {
                                 parameterSpec.addAnnotation(new AnnotationSpec(Ref.class).also(annotationSpec -> {
                                     if (ref.nullable()) {
@@ -251,7 +235,17 @@ private void writeFile(
                                 .addArguments(e.getParameters().stream()
                                     .map(p -> Spec.literal(p.getSimpleName().toString()))
                                     .collect(Collectors.toList()));
-                            targetStatement.addStatement(notVoid ? Spec.returnStatement(Spec.cast(javaReturnType, invokeExact)) : Spec.statement(invokeExact));
+                            if (notVoid) {
+                                final Spec castSpec = Spec.cast(javaReturnType, invokeExact);
+                                if (eLongSized != null || eSized != null) {
+                                    targetStatement.addStatement(Spec.returnStatement(new InvokeSpec(Spec.parentheses(castSpec), "reinterpret")
+                                        .addArgument(getConstExp(eLongSized != null ? eLongSized.value() : eSized.value()))));
+                                } else {
+                                    targetStatement.addStatement(Spec.returnStatement(castSpec));
+                                }
+                            } else {
+                                targetStatement.addStatement(Spec.statement(invokeExact));
+                            }
                             tryCatchStatement.addCatchClause(new CatchClause(Throwable.class.getSimpleName(), "_ex"), catchClause ->
                                 catchClause.addStatement(Spec.throwStatement(new ConstructSpec(AssertionError.class.getSimpleName())
                                     .addArgument(Spec.literal(catchClause.name()))))
@@ -262,11 +256,11 @@ private void writeFile(
 
                     // check array size
                     parameters.stream()
-                        .filter(p -> p.getAnnotation(FixedSize.class) != null)
+                        .filter(p -> p.getAnnotation(Sized.class) != null)
                         .forEach(p -> {
-                            final FixedSize fixedSize = p.getAnnotation(FixedSize.class);
+                            final Sized sized = p.getAnnotation(Sized.class);
                             methodSpec.addStatement(Spec.statement(new InvokeSpec(Checks.class.getSimpleName(), "checkArraySize")
-                                .addArgument(getConstExp(fixedSize.value()))
+                                .addArgument(getConstExp(sized.value()))
                                 .addArgument(Spec.accessSpec(p.getSimpleName().toString(), "length"))));
                         });
 
@@ -275,29 +269,27 @@ private void writeFile(
                         .filter(p -> p.getAnnotation(Ref.class) != null)
                         .forEach(p -> {
                             final TypeMirror pType = p.asType();
+                            if (!isArray(pType)) {
+                                return;
+                            }
                             final TypeMirror arrayComponentType = getArrayComponentType(pType);
-                            final TypeKind typeKind = arrayComponentType.getKind();
                             final Name name = p.getSimpleName();
                             final String nameString = name.toString();
                             final Ref ref = p.getAnnotation(Ref.class);
                             final Spec invokeSpec;
-                            if (typeKind == TypeKind.BOOLEAN) {
+                            if (isBooleanArray(pType)) {
                                 invokeSpec = new InvokeSpec(BoolHelper.class, "of")
                                     .addArgument(allocatorParamName)
                                     .addArgument(nameString);
-                            } else if (typeKind.isPrimitive()) {
+                            } else if (isPrimitiveArray(pType)) {
                                 invokeSpec = new InvokeSpec(allocatorParamName, "allocateFrom")
                                     .addArgument(toValueLayout(arrayComponentType))
                                     .addArgument(nameString);
-                            } else if (typeKind == TypeKind.DECLARED) {
-                                if (isString(arrayComponentType)) {
-                                    invokeSpec = new InvokeSpec(StrHelper.class, "of")
-                                        .addArgument(allocatorParamName)
-                                        .addArgument(nameString)
-                                        .addArgument(createCharset(file, getCustomCharset(p)));
-                                } else {
-                                    invokeSpec = Spec.literal(nameString);
-                                }
+                            } else if (isStringArray(pType)) {
+                                invokeSpec = new InvokeSpec(StrHelper.class, "of")
+                                    .addArgument(allocatorParamName)
+                                    .addArgument(nameString)
+                                    .addArgument(createCharset(file, getCustomCharset(p)));
                             } else {
                                 invokeSpec = Spec.literal(nameString);
                             }
@@ -321,13 +313,16 @@ private void writeFile(
                         if (pTypeKind.isPrimitive()) {
                             invocation.addArgument(getConstExp(nameString));
                         } else {
-                            final String pCustomCharset = getCustomCharset(p);
                             switch (pTypeKind) {
                                 case DECLARED -> {
                                     if (isString(pType)) {
                                         invocation.addArgument(new InvokeSpec(allocatorParamName, "allocateFrom")
                                             .addArgument(nameString)
-                                            .addArgument(createCharset(file, pCustomCharset)));
+                                            .also(invokeSpec -> {
+                                                if (p.getAnnotation(StrCharset.class) != null) {
+                                                    invokeSpec.addArgument(createCharset(file, getCustomCharset(p)));
+                                                }
+                                            }));
                                     } else if (isUpcall(pType)) {
                                         invocation.addArgument(new InvokeSpec(nameString, "stub")
                                             .addArgument(allocatorParamName));
@@ -340,20 +335,18 @@ private void writeFile(
                                     if (p.getAnnotation(Ref.class) != null) {
                                         invocation.addArgument('_' + nameString);
                                     } else {
-                                        final TypeMirror arrayComponentType = getArrayComponentType(pType);
-                                        if (arrayComponentType.getKind() == TypeKind.BOOLEAN) {
+                                        if (isBooleanArray(pType)) {
                                             invocation.addArgument(new InvokeSpec(BoolHelper.class, "of")
                                                 .addArgument(allocatorParamName)
                                                 .addArgument(nameString));
-                                        } else if (arrayComponentType.getKind() == TypeKind.DECLARED &&
-                                                   isString(arrayComponentType)) {
+                                        } else if (isStringArray(pType)) {
                                             invocation.addArgument(new InvokeSpec(StrHelper.class, "of")
                                                 .addArgument(allocatorParamName)
                                                 .addArgument(nameString)
-                                                .addArgument(createCharset(file, pCustomCharset)));
+                                                .addArgument(createCharset(file, getCustomCharset(p))));
                                         } else {
                                             invocation.addArgument(new InvokeSpec(allocatorParamName, "allocateFrom")
-                                                .addArgument(toValueLayout(arrayComponentType))
+                                                .addArgument(toValueLayout(getArrayComponentType(pType)))
                                                 .addArgument(nameString));
                                         }
                                     }
@@ -363,24 +356,32 @@ private void writeFile(
                         }
                     });
                     // converting return value
-                    final String customCharset = getCustomCharset(e);
-                    if (returnArray) {
-                        if (returnBooleanArray) {
+                    if (isArray(returnType)) {
+                        final TypeMirror arrayComponentType = getArrayComponentType(returnType);
+                        if (eSized != null) {
+                            finalInvocation = new InvokeSpec(finalInvocation, "reinterpret")
+                                .addArgument(Spec.operatorSpec("*",
+                                    Spec.literal(getConstExp(eSized.value())),
+                                    new InvokeSpec(toValueLayout(arrayComponentType), "byteSize")));
+                        }
+                        if (isBooleanArray(returnType)) {
                             finalInvocation = new InvokeSpec(BoolHelper.class, "toArray")
-                                .addArgument(invocation);
-                        } else if (returnStringArray) {
+                                .addArgument(finalInvocation);
+                        } else if (isStringArray(returnType)) {
                             finalInvocation = new InvokeSpec(StrHelper.class, "toArray")
-                                .addArgument(invocation)
-                                .addArgument(createCharset(file, customCharset));
+                                .addArgument(finalInvocation)
+                                .addArgument(createCharset(file, getCustomCharset(e)));
                         } else {
-                            finalInvocation = new InvokeSpec(invocation, "toArray")
-                                .addArgument(toValueLayout(returnType));
+                            finalInvocation = new InvokeSpec(finalInvocation, "toArray")
+                                .addArgument(toValueLayout(arrayComponentType));
                         }
-                    } else if (returnType.getKind() == TypeKind.DECLARED) {
+                    } else if (isDeclared(returnType)) {
                         if (isString(returnType)) {
                             finalInvocation = new InvokeSpec(invocation, "getString")
-                                .addArgument("0L")
-                                .addArgument(createCharset(file, customCharset));
+                                .addArgument("0L");
+                            if (e.getAnnotation(StrCharset.class) != null) {
+                                finalInvocation.addArgument(createCharset(file, getCustomCharset(e)));
+                            }
                         } else if (isUpcall(returnType)) {
                             // find wrap method
                             final var wrapMethod = ElementFilter.methodsIn(processingEnv.getTypeUtils()
@@ -427,24 +428,22 @@ private void writeFile(
                             final String nameString = p.getSimpleName().toString();
                             final Ref ref = p.getAnnotation(Ref.class);
                             final boolean nullable = ref.nullable();
-                            if (pType.getKind() == TypeKind.ARRAY) {
+                            if (isArray(pType)) {
                                 final Spec spec;
                                 if (isBooleanArray(pType)) {
                                     spec = Spec.statement(new InvokeSpec(BoolHelper.class, "copy")
                                         .addArgument('_' + nameString)
                                         .addArgument(nameString));
                                 } else {
-                                    final TypeMirror arrayComponentType = getArrayComponentType(pType);
                                     if (isPrimitiveArray(pType)) {
                                         spec = Spec.statement(new InvokeSpec(MemorySegment.class, "copy")
                                             .addArgument('_' + nameString)
-                                            .addArgument(toValueLayout(arrayComponentType))
+                                            .addArgument(toValueLayout(getArrayComponentType(pType)))
                                             .addArgument("0L")
                                             .addArgument(nameString)
                                             .addArgument("0")
                                             .addArgument(Spec.accessSpec(nameString, "length")));
-                                    } else if (arrayComponentType.getKind() == TypeKind.DECLARED &&
-                                               isString(arrayComponentType)) {
+                                    } else if (isStringArray(pType)) {
                                         spec = Spec.statement(new InvokeSpec(StrHelper.class, "copy")
                                             .addArgument('_' + nameString)
                                             .addArgument(nameString)
@@ -592,16 +591,6 @@ private void addMethodHandles(TypeElement type, List methods,
         });
     }
 
-    private InvokeSpec createCharset(SourceFile file, String name) {
-        file.addImport(Charset.class);
-        return new InvokeSpec(Charset.class, "forName").addArgument(getConstExp(name));
-    }
-
-    private static String getCustomCharset(Element e) {
-        final StrCharset strCharset = e.getAnnotation(StrCharset.class);
-        return strCharset != null ? strCharset.value() : "UTF-8";
-    }
-
     private static List collectConflictedMethodSignature(ExecutableElement e) {
         return Stream.concat(Stream.of(e.getReturnType()), e.getParameters().stream().map(VariableElement::asType)).map(t -> {
             if (t.getKind() == TypeKind.VOID) {
diff --git a/src/main/java/overrun/marshal/LongSized.java b/src/main/java/overrun/marshal/LongSized.java
new file mode 100644
index 0000000..fcd4500
--- /dev/null
+++ b/src/main/java/overrun/marshal/LongSized.java
@@ -0,0 +1,43 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 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.*;
+
+/**
+ * Just like {@link Sized} with long size.
+ * 

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

Example

+ *
{@code
+ * @LongSized(0x7FFFFFFFFFFFFFFFL)
+ * MemorySegment segment;
+ * }
+ * + * @author squid233 + * @see Checks#CHECK_ARRAY_SIZE + * @since 0.1.0 + */ +@Documented +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.SOURCE) +public @interface LongSized { + /** + * {@return the size of the memory segment} + */ + long value(); +} diff --git a/src/main/java/overrun/marshal/FixedSize.java b/src/main/java/overrun/marshal/Sized.java similarity index 89% rename from src/main/java/overrun/marshal/FixedSize.java rename to src/main/java/overrun/marshal/Sized.java index 806efac..19b2e06 100644 --- a/src/main/java/overrun/marshal/FixedSize.java +++ b/src/main/java/overrun/marshal/Sized.java @@ -24,7 +24,7 @@ * The generated code will try to check the size of a passing array. *

Example

*
{@code
- * void set(@FixedSize(3) int[] vec);
+ * void set(@Sized(3) int[] vec);
  * }
* * @author squid233 @@ -32,9 +32,9 @@ * @since 0.1.0 */ @Documented -@Target(ElementType.PARAMETER) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.SOURCE) -public @interface FixedSize { +public @interface Sized { /** * {@return the size of the array} */ diff --git a/src/main/java/overrun/marshal/Skip.java b/src/main/java/overrun/marshal/Skip.java new file mode 100644 index 0000000..74d617a --- /dev/null +++ b/src/main/java/overrun/marshal/Skip.java @@ -0,0 +1,36 @@ +/* + * MIT License + * + * Copyright (c) 2023 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.*; + +/** + * Skips generating a marked field in {@linkplain overrun.marshal.struct.Struct struct}. + *

Example

+ *
{@code
+ * @Skip
+ * int LAYOUT;
+ * }
+ * + * @author squid233 + * @since 0.1.0 + */ +@Documented +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.SOURCE) +public @interface Skip { +} diff --git a/src/main/java/overrun/marshal/StrCharset.java b/src/main/java/overrun/marshal/StrCharset.java index c6f9300..ee5e8f4 100644 --- a/src/main/java/overrun/marshal/StrCharset.java +++ b/src/main/java/overrun/marshal/StrCharset.java @@ -31,7 +31,7 @@ * @since 0.1.0 */ @Documented -@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.SOURCE) public @interface StrCharset { /** diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index dbf897c..61a64f9 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -18,7 +18,6 @@ import overrun.marshal.gen.*; import overrun.marshal.internal.Processor; -import overrun.marshal.internal.Util; import overrun.marshal.struct.Const; import overrun.marshal.struct.IStruct; import overrun.marshal.struct.Padding; @@ -36,6 +35,7 @@ import java.lang.invoke.VarHandle; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import static overrun.marshal.internal.Util.*; @@ -100,40 +100,94 @@ private void writeFile( } final SourceFile file = new SourceFile(packageName); - file.addImport("java.lang.foreign.*"); + file.addImports("overrun.marshal.*", "java.lang.foreign.*"); file.addImports( MemoryLayout.PathElement.class, VarHandle.class ); file.addClass(simpleClassName, classSpec -> { - final Const typeConst = type.getAnnotation(Const.class); + final boolean typeConst = type.getAnnotation(Const.class) != null; classSpec.setDocument(getDocument(type)); + if (typeConst) { + classSpec.addAnnotation(new AnnotationSpec(Const.class.getCanonicalName())); + } classSpec.setFinal(!struct.nonFinal()); classSpec.addSuperinterface(IStruct.class.getCanonicalName()); // layout - classSpec.addField(new VariableStatement(StructLayout.class, "LAYOUT", - new InvokeSpec(MemoryLayout.class, "structLayout").also(invokeSpec -> fields.forEach(e -> { + classSpec.addField(new VariableStatement(StructLayout.class, "_LAYOUT", + new InvokeSpec(MemoryLayout.class, "structLayout").also(invokeSpec -> forEach(fields, true, e -> { final TypeMirror eType = e.asType(); final Padding padding = e.getAnnotation(Padding.class); if (padding != null) { invokeSpec.addArgument(new InvokeSpec(MemoryLayout.class, "paddingLayout") .addArgument(getConstExp(padding.value()))); - } else if (Util.isValueType(eType)) { - invokeSpec.addArgument(new InvokeSpec(toValueLayout(eType), "withName") - .addArgument(getConstExp(e.getSimpleName().toString()))); + } else if (isValueType(eType)) { + InvokeSpec member = new InvokeSpec(toValueLayout(eType), "withName") + .addArgument(getConstExp(e.getSimpleName().toString())); + if (isArray(eType)) { + final Sized sized = e.getAnnotation(Sized.class); + if (sized != null) { + member = new InvokeSpec(member, "withTargetLayout") + .addArgument(new InvokeSpec(MemoryLayout.class, "sequenceLayout") + .addArgument(getConstExp(sized.value())) + .addArgument(toValueLayout(getArrayComponentType(eType)))); + } + } + invokeSpec.addArgument(member); } else { - printError("Unsupported field: " + eType + e.getSimpleName()); + printError("Unsupported field: " + eType + ' ' + e.getSimpleName()); // TODO: 2023/12/16 squid233: struct, upcall } }))) - .setDocument(" The layout of this struct.") .setStatic(true) - .setFinal(true)); + .setFinal(true), variableStatement -> { + // document + final StringBuilder sb = new StringBuilder(512); + sb.append(" The layout of this struct.\n
{@code\n struct ").append(simpleClassName).append(" {\n");
+                forEach(fields, false, e -> {
+                    final String string = e.asType().toString();
+                    final String substring = string.substring(string.lastIndexOf('.') + 1);
+                    final StrCharset strCharset = e.getAnnotation(StrCharset.class);
+                    sb.append("     ");
+                    if (strCharset != null) {
+                        sb.append('(').append(strCharset.value()).append(") ");
+                    }
+                    if (typeConst || e.getAnnotation(Const.class) != null) {
+                        sb.append("final ");
+                    }
+                    if (MemorySegment.class.getSimpleName().equals(substring)) {
+                        final LongSized longSized = e.getAnnotation(LongSized.class);
+                        sb.append("void");
+                        if (longSized != null) {
+                            sb.append('[').append(getConstExp(longSized.value())).append(']');
+                        } else {
+                            sb.append('*');
+                        }
+                    } else {
+                        final Sized sized = e.getAnnotation(Sized.class);
+                        if (sized != null && substring.endsWith("[]")) {
+                            sb.append(substring, 0, substring.length() - 1).append(getConstExp(sized.value())).append(']');
+                        } else {
+                            sb.append(substring);
+                        }
+                    }
+                    sb.append(' ')
+                        .append(e.getSimpleName())
+                        .append(";\n");
+                });
+                sb.append(" }\n }
"); + variableStatement.setDocument(sb.toString()); + }); + + // path elements classSpec.addField(new VariableStatement(MemoryLayout.PathElement.class, "_SEQUENCE_ELEMENT", - new InvokeSpec(MemoryLayout.PathElement.class, "sequenceElement"))); - fields.forEach(e -> classSpec.addField(new VariableStatement(MemoryLayout.PathElement.class, "_PE_" + e.getSimpleName(), + new InvokeSpec(MemoryLayout.PathElement.class, "sequenceElement")) + .setAccessModifier(AccessModifier.PRIVATE) + .setStatic(true) + .setFinal(true)); + forEach(fields, false, e -> classSpec.addField(new VariableStatement(MemoryLayout.PathElement.class, "_PE_" + e.getSimpleName(), new InvokeSpec(MemoryLayout.PathElement.class, "groupElement").addArgument(getConstExp(e.getSimpleName().toString()))) .setAccessModifier(AccessModifier.PRIVATE) .setStatic(true) @@ -147,7 +201,7 @@ private void writeFile( .setFinal(true)); // var handles - fields.forEach(e -> classSpec.addField(new VariableStatement(VarHandle.class, e.getSimpleName().toString(), null) + forEach(fields, false, e -> classSpec.addField(new VariableStatement(VarHandle.class, e.getSimpleName().toString(), null) .setAccessModifier(AccessModifier.PRIVATE) .setFinal(true))); @@ -164,8 +218,8 @@ private void writeFile( methodSpec.addStatement(Spec.assignStatement("this._memorySegment", Spec.literal("segment"))); methodSpec.addStatement(Spec.assignStatement("this._sequenceLayout", new InvokeSpec(MemoryLayout.class, "sequenceLayout") .addArgument("count") - .addArgument("LAYOUT"))); - fields.forEach(e -> { + .addArgument("_LAYOUT"))); + forEach(fields, false, e -> { final var name = e.getSimpleName(); methodSpec.addStatement(Spec.assignStatement(Spec.accessSpec("this", name.toString()), new InvokeSpec(Spec.literal("this._sequenceLayout"), "varHandle") @@ -176,59 +230,107 @@ private void writeFile( // allocator classSpec.addMethod(new MethodSpec(simpleClassName, "create"), methodSpec -> { + methodSpec.setDocument(""" + Allocates {@code %s}. + + @param allocator the allocator + @return the allocated {@code %1$s}\ + """.formatted(simpleClassName)); methodSpec.setStatic(true); methodSpec.addParameter(SegmentAllocator.class, "allocator"); methodSpec.addStatement(Spec.returnStatement(new ConstructSpec(simpleClassName) .addArgument(new InvokeSpec("allocator", "allocate") - .addArgument("LAYOUT")) + .addArgument("_LAYOUT")) .addArgument(Spec.literal("1")))); }); classSpec.addMethod(new MethodSpec(simpleClassName, "create"), methodSpec -> { + methodSpec.setDocument(""" + Allocates {@code %s} with the given count. + + @param allocator the allocator + @param count the element count + @return the allocated {@code %1$s}\ + """.formatted(simpleClassName)); methodSpec.setStatic(true); methodSpec.addParameter(SegmentAllocator.class, "allocator"); methodSpec.addParameter(long.class, "count"); methodSpec.addStatement(Spec.returnStatement(new ConstructSpec(simpleClassName) .addArgument(new InvokeSpec("allocator", "allocate") - .addArgument("LAYOUT") + .addArgument("_LAYOUT") .addArgument("count")) .addArgument(Spec.literal("count")))); }); - // member - fields.forEach(e -> { - if (e.getAnnotation(Padding.class) == null) { - final TypeMirror eType = e.asType(); - final String nameString = e.getSimpleName().toString(); - if (isValueType(eType)) { - final boolean canConvertToAddress = canConvertToAddress(eType); - final String returnType = canConvertToAddress ? MemorySegment.class.getSimpleName() : eType.toString(); - classSpec.addMethod(new MethodSpec( - returnType, - (canConvertToAddress ? "nget" : "get") + capitalize(nameString) + "At" - ), methodSpec -> { - methodSpec.addParameter(long.class, "index"); - methodSpec.addStatement(Spec.returnStatement(Spec.cast( - returnType, - new InvokeSpec(Spec.accessSpec("this", nameString), "get") - .addArgument("_memorySegment")))); - }); - if (canConvertToAddress && !isMemorySegment(eType)) { - classSpec.addMethod(new MethodSpec( - simplify(eType.toString()), - "get" + capitalize(nameString) + "At" - ), methodSpec -> { - methodSpec.addParameter(long.class, "index"); - methodSpec.addStatement(Spec.returnStatement(Spec.literal("null"))); - }); + forEach(fields, false, e -> { + final TypeMirror eType = e.asType(); + final String nameString = e.getSimpleName().toString(); + if (isValueType(eType)) { + final LongSized longSized = e.getAnnotation(LongSized.class); + final Sized sized = e.getAnnotation(Sized.class); + final boolean canConvertToAddress = canConvertToAddress(eType); + final String returnType = canConvertToAddress ? MemorySegment.class.getSimpleName() : eType.toString(); + final String capitalized = capitalize(nameString); + final boolean isMemorySegment = isMemorySegment(eType); + final boolean isArray = isArray(eType); + final boolean generateN = canConvertToAddress && !isMemorySegment; + + // getter + final Spec castSpec = Spec.cast(returnType, + new InvokeSpec(Spec.accessSpec("this", nameString), "get") + .addArgument("_memorySegment")); + addGetterAt(classSpec, e, generateN ? "nget" : "get", returnType, + (isMemorySegment && longSized != null || isArray && sized != null) ? + new InvokeSpec(Spec.parentheses(castSpec), "reinterpret") + .addArgument(longSized != null ? + Spec.literal(getConstExp(longSized.value())) : + Spec.operatorSpec("*", + Spec.literal(getConstExp(sized.value())), + new InvokeSpec(toValueLayout(getArrayComponentType(eType)), "byteSize"))) : + castSpec); + if (generateN) { + Spec returnValue = new InvokeSpec("this", "nget" + capitalized + "At") + .addArgument("index"); + if (isString(eType)) { + final StrCharset strCharset = e.getAnnotation(StrCharset.class); + final InvokeSpec invokeSpec = new InvokeSpec(returnValue, "getString") + .addArgument("0"); + if (strCharset != null) { + invokeSpec.addArgument(createCharset(file, strCharset.value())); + } + returnValue = invokeSpec; + } else if (isArray) { + final TypeMirror arrayComponentType = getArrayComponentType(eType); + if (sized == null) { + returnValue = new InvokeSpec(returnValue, "reinterpret") + .addArgument(Spec.operatorSpec("*", Spec.literal("count"), + new InvokeSpec(toValueLayout(arrayComponentType), "byteSize"))); + } + if (isBooleanArray(eType)) { + returnValue = new InvokeSpec(BoolHelper.class.getCanonicalName(), "toArray") + .addArgument(returnValue); + } else if (isStringArray(eType)) { + returnValue = new InvokeSpec(StrHelper.class.getCanonicalName(), "toArray") + .addArgument(returnValue) + .addArgument(createCharset(file, getCustomCharset(e))); + } else { + returnValue = new InvokeSpec(returnValue, "toArray") + .addArgument(toValueLayout(arrayComponentType)); + } } + addGetterAt(classSpec, e, "get", simplify(eType.toString()), returnValue); + } + + // setter + if (!typeConst && e.getAnnotation(Const.class) == null) { } } + // TODO: 2023/12/17 squid233: struct, upcall }); // override addIStructImpl(classSpec, MemorySegment.class, "segment", Spec.literal("_memorySegment")); - addIStructImpl(classSpec, StructLayout.class, "layout", Spec.literal("LAYOUT")); + addIStructImpl(classSpec, StructLayout.class, "layout", Spec.literal("_LAYOUT")); addIStructImpl(classSpec, long.class, "elementCount", new InvokeSpec("_sequenceLayout", "elementCount")); }); @@ -238,6 +340,28 @@ private void writeFile( } } + private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, String returnType, Spec returnValue) { + classSpec.addMethod(new MethodSpec(returnType, getType + capitalize(e.getSimpleName().toString()) + "At"), methodSpec -> { + final LongSized longSized = e.getAnnotation(LongSized.class); + final Sized sized = e.getAnnotation(Sized.class); + final boolean insertCount = "get".equals(getType) && sized == null && isArray(e.asType()); + final String eDoc = getDocument(e); + methodSpec.setDocument(""" + Gets %s at the given index. + + %s@param index the index + @return %1$s\ + """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertCount ? "@param count the length of the array\n " : "")); + addAnnotationValue(methodSpec, longSized, LongSized.class, LongSized::value); + addAnnotationValue(methodSpec, sized, Sized.class, Sized::value); + if (insertCount) { + methodSpec.addParameter(int.class, "count"); + } + methodSpec.addParameter(long.class, "index"); + methodSpec.addStatement(Spec.returnStatement(returnValue)); + }); + } + private static void addIStructImpl(ClassSpec classSpec, Class aClass, String name, Spec spec) { classSpec.addMethod(new MethodSpec(aClass.getSimpleName(), name), methodSpec -> { methodSpec.addAnnotation(new AnnotationSpec(Override.class)); @@ -245,6 +369,11 @@ private static void addIStructImpl(ClassSpec classSpec, Class aClass, String }); } + private static void forEach(List fields, boolean allowPadding, Consumer action) { + fields.stream().filter(e -> e.getAnnotation(Skip.class) == null && + (allowPadding || e.getAnnotation(Padding.class) == null)).forEach(action); + } + @Override public Set getSupportedAnnotationTypes() { return Set.of(Struct.class.getCanonicalName()); diff --git a/src/main/java/overrun/marshal/gen/Annotatable.java b/src/main/java/overrun/marshal/gen/Annotatable.java new file mode 100644 index 0000000..9fb14da --- /dev/null +++ b/src/main/java/overrun/marshal/gen/Annotatable.java @@ -0,0 +1,32 @@ +/* + * MIT License + * + * Copyright (c) 2023 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; + +/** + * Annotatable + * + * @author squid233 + * @since 0.1.0 + */ +public interface Annotatable { + /** + * Add an annotation + * + * @param annotationSpec annotation + */ + void addAnnotation(AnnotationSpec annotationSpec); +} diff --git a/src/main/java/overrun/marshal/gen/ClassSpec.java b/src/main/java/overrun/marshal/gen/ClassSpec.java index 30b1f06..5780e21 100644 --- a/src/main/java/overrun/marshal/gen/ClassSpec.java +++ b/src/main/java/overrun/marshal/gen/ClassSpec.java @@ -28,7 +28,7 @@ * @author squid233 * @since 0.1.0 */ -public final class ClassSpec implements Spec { +public final class ClassSpec implements Annotatable, Spec { /** * Class */ @@ -129,6 +129,7 @@ public void addMethod(MethodSpec methodSpec, Consumer consumer) { * * @param annotationSpec annotation */ + @Override public void addAnnotation(AnnotationSpec annotationSpec) { annotationSpecs.add(annotationSpec); } diff --git a/src/main/java/overrun/marshal/gen/MethodSpec.java b/src/main/java/overrun/marshal/gen/MethodSpec.java index e250f13..e650cbb 100644 --- a/src/main/java/overrun/marshal/gen/MethodSpec.java +++ b/src/main/java/overrun/marshal/gen/MethodSpec.java @@ -27,7 +27,7 @@ * @author squid233 * @since 0.1.0 */ -public final class MethodSpec implements Spec, StatementBlock { +public final class MethodSpec implements Annotatable, Spec, StatementBlock { private final String returnType; private final String name; private String document = null; @@ -64,6 +64,7 @@ public void setDocument(String document) { * * @param annotationSpec annotation */ + @Override public void addAnnotation(AnnotationSpec annotationSpec) { annotations.add(annotationSpec); } diff --git a/src/main/java/overrun/marshal/gen/ParameterSpec.java b/src/main/java/overrun/marshal/gen/ParameterSpec.java index 6c02d4c..7cb2b15 100644 --- a/src/main/java/overrun/marshal/gen/ParameterSpec.java +++ b/src/main/java/overrun/marshal/gen/ParameterSpec.java @@ -26,7 +26,7 @@ * @author squid233 * @since 0.1.0 */ -public final class ParameterSpec implements Spec { +public final class ParameterSpec implements Annotatable, Spec { private final String type; private final String name; private final List annotations = new ArrayList<>(); @@ -47,6 +47,7 @@ public ParameterSpec(String type, String name) { * * @param annotationSpec annotation */ + @Override public void addAnnotation(AnnotationSpec annotationSpec) { annotations.add(annotationSpec); } diff --git a/src/main/java/overrun/marshal/gen/Spec.java b/src/main/java/overrun/marshal/gen/Spec.java index f820fa8..bb09f73 100644 --- a/src/main/java/overrun/marshal/gen/Spec.java +++ b/src/main/java/overrun/marshal/gen/Spec.java @@ -32,7 +32,37 @@ public interface Spec { * @return the spec */ static Spec literal(String s) { - return (builder, indent) -> builder.append(s); + return (builder, _) -> builder.append(s); + } + + /** + * Create parentheses spec + * + * @param spec spec + * @return spec + */ + static Spec parentheses(Spec spec) { + return (builder, indent) -> { + builder.append('('); + spec.append(builder, indent); + builder.append(')'); + }; + } + + /** + * Create operator spec + * + * @param operator operator + * @param a a + * @param b b + * @return spec + */ + static Spec operatorSpec(String operator, Spec a, Spec b) { + return (builder, indent) -> { + a.append(builder, indent); + builder.append(' ').append(operator).append(' '); + b.append(builder, indent); + }; } /** diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java index 8b57cec..d0e1394 100644 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ b/src/main/java/overrun/marshal/internal/Processor.java @@ -16,7 +16,9 @@ package overrun.marshal.internal; +import overrun.marshal.StrCharset; import overrun.marshal.Upcall; +import overrun.marshal.gen.*; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; @@ -25,6 +27,11 @@ import javax.lang.model.type.TypeMirror; import java.io.PrintWriter; import java.io.Writer; +import java.lang.annotation.Annotation; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.function.Function; /** * Annotation processor @@ -115,8 +122,55 @@ protected String getConstExp(Object value) { * @param typeMirror typeMirror * @return isUpcall */ - public boolean isUpcall(TypeMirror typeMirror) { - return processingEnv.getTypeUtils().isAssignable(typeMirror, upcallTypeMirror); + protected boolean isUpcall(TypeMirror typeMirror) { + return Util.isDeclared(typeMirror) && + processingEnv.getTypeUtils().isAssignable(typeMirror, upcallTypeMirror); + } + + /** + * Add an annotation + * + * @param annotation annotation + */ + protected void addAnnotationValue(Annotatable annotatable, T annotation, Class tClass, Function function) { + if (annotation != null) { + annotatable.addAnnotation(new AnnotationSpec(tClass) + .addArgument("value", getConstExp(function.apply(annotation)))); + } + } + + /** + * getCustomCharset + * + * @param e element + * @return getCustomCharset + */ + protected static String getCustomCharset(Element e) { + final StrCharset strCharset = e.getAnnotation(StrCharset.class); + return strCharset != null ? strCharset.value() : "UTF-8"; + } + + /** + * Create charset + * + * @param file file + * @param name name + * @return charset + */ + protected Spec createCharset(SourceFile file, String name) { + final String upperCase = name.toUpperCase(Locale.ROOT); + return switch (upperCase) { + case "UTF-8", "ISO-8859-1", "US-ASCII", + "UTF-16", "UTF-16BE", "UTF-16LE", + "UTF-32", "UTF-32BE", "UTF-32LE" -> { + file.addImport(StandardCharsets.class); + yield Spec.accessSpec(StandardCharsets.class, upperCase.replace('-', '_')); + } + default -> { + file.addImport(Charset.class); + yield new InvokeSpec(Charset.class, "forName").addArgument(getConstExp(name)); + } + }; } @Override diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index 2ce0da9..a2536b5 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -74,6 +74,16 @@ public static String simplify(String rawClassName) { return rawClassName; } + /** + * isDeclared + * + * @param typeMirror typeMirror + * @return isDeclared + */ + public static boolean isDeclared(TypeMirror typeMirror) { + return typeMirror.getKind() == TypeKind.DECLARED; + } + /** * isMemorySegment * @@ -91,7 +101,8 @@ public static boolean isMemorySegment(String clazzName) { * @return isMemorySegment */ public static boolean isMemorySegment(TypeMirror typeMirror) { - return isMemorySegment(typeMirror.toString()); + return isDeclared(typeMirror) && + isMemorySegment(typeMirror.toString()); } /** @@ -111,7 +122,8 @@ public static boolean isString(String clazzName) { * @return isString */ public static boolean isString(TypeMirror typeMirror) { - return isString(typeMirror.toString()); + return isDeclared(typeMirror) && + isString(typeMirror.toString()); } /** @@ -131,7 +143,8 @@ public static boolean isArray(TypeMirror typeMirror) { * @return isBooleanArray */ public static boolean isBooleanArray(TypeMirror typeMirror) { - return getArrayComponentType(typeMirror).getKind() == TypeKind.BOOLEAN; + return isArray(typeMirror) && + getArrayComponentType(typeMirror).getKind() == TypeKind.BOOLEAN; } /** @@ -141,7 +154,18 @@ public static boolean isBooleanArray(TypeMirror typeMirror) { * @return isPrimitiveArray */ public static boolean isPrimitiveArray(TypeMirror typeMirror) { - return getArrayComponentType(typeMirror).getKind().isPrimitive(); + return isArray(typeMirror) && + getArrayComponentType(typeMirror).getKind().isPrimitive(); + } + + /** + * isStringArray + * + * @param typeMirror typeMirror + * @return isStringArray + */ + public static boolean isStringArray(TypeMirror typeMirror) { + return isArray(typeMirror) && isString(getArrayComponentType(typeMirror)); } /** @@ -162,7 +186,7 @@ public static TypeMirror getArrayComponentType(TypeMirror typeMirror) { */ public static boolean canConvertToAddress(TypeMirror typeMirror) { return switch (typeMirror.getKind()) { - case ARRAY -> isPrimitiveArray(typeMirror); + case ARRAY -> isPrimitiveArray(typeMirror) || isBooleanArray(typeMirror) || isStringArray(typeMirror); case DECLARED -> isMemorySegment(typeMirror) || isString(typeMirror); default -> false; }; @@ -175,10 +199,10 @@ public static boolean canConvertToAddress(TypeMirror typeMirror) { * @return isValueType */ public static boolean isValueType(TypeMirror typeMirror) { - return switch (typeMirror.getKind()) { - case BOOLEAN, BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE -> true; - default -> canConvertToAddress(typeMirror); - }; + if (typeMirror.getKind().isPrimitive()) { + return true; + } + return canConvertToAddress(typeMirror); } /** diff --git a/src/main/java/overrun/marshal/struct/Const.java b/src/main/java/overrun/marshal/struct/Const.java index 18fb77d..40f7399 100644 --- a/src/main/java/overrun/marshal/struct/Const.java +++ b/src/main/java/overrun/marshal/struct/Const.java @@ -35,7 +35,7 @@ * @since 0.1.0 */ @Documented -@Target({ElementType.TYPE,ElementType.FIELD}) +@Target({ElementType.FIELD, ElementType.TYPE}) @Retention(RetentionPolicy.SOURCE) public @interface Const { } From 3f1dca96f0bbae0a3986e383b90d09ba84eb13a6 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sun, 17 Dec 2023 17:43:23 +0800 Subject: [PATCH 3/7] Update struct setter --- .../marshal/test/CConstStructTest.java | 3 +- .../java/overrun/marshal/StructProcessor.java | 125 +++++++++++++++++- .../java/overrun/marshal/struct/Const.java | 3 +- .../java/overrun/marshal/struct/Struct.java | 15 ++- 4 files changed, 133 insertions(+), 13 deletions(-) diff --git a/demo/src/main/java/overrun/marshal/test/CConstStructTest.java b/demo/src/main/java/overrun/marshal/test/CConstStructTest.java index 9c80a39..3e0d17a 100644 --- a/demo/src/main/java/overrun/marshal/test/CConstStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CConstStructTest.java @@ -25,6 +25,5 @@ */ @Const @Struct -public class CConstStructTest { - int x, y, z; +public record CConstStructTest(int x, int y, int z) { } diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 61a64f9..e073eac 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -261,6 +261,23 @@ private void writeFile( .addArgument(Spec.literal("count")))); }); + // slice + classSpec.addMethod(new MethodSpec(simpleClassName, "get"), methodSpec -> { + methodSpec.setDocument(""" + {@return a slice of this struct} + + @param index the index of the slice\ + """); + methodSpec.addParameter(long.class, "index"); + methodSpec.addStatement(Spec.returnStatement(new ConstructSpec(simpleClassName) + .addArgument(new InvokeSpec("this._memorySegment", "asSlice") + .addArgument(Spec.operatorSpec("*", + Spec.literal("index"), + new InvokeSpec("_LAYOUT", "byteSize"))) + .addArgument("_LAYOUT")) + .addArgument(Spec.literal("1L")))); + }); + // member forEach(fields, false, e -> { final TypeMirror eType = e.asType(); @@ -278,7 +295,9 @@ private void writeFile( // getter final Spec castSpec = Spec.cast(returnType, new InvokeSpec(Spec.accessSpec("this", nameString), "get") - .addArgument("_memorySegment")); + .addArgument("this._memorySegment") + .addArgument("0L") + .addArgument("index")); addGetterAt(classSpec, e, generateN ? "nget" : "get", returnType, (isMemorySegment && longSized != null || isArray && sized != null) ? new InvokeSpec(Spec.parentheses(castSpec), "reinterpret") @@ -323,15 +342,38 @@ private void writeFile( // setter if (!typeConst && e.getAnnotation(Const.class) == null) { + addSetterAt(classSpec, e, generateN ? "nset" : "set", returnType, + Spec.statement(new InvokeSpec(Spec.accessSpec("this", nameString), "set") + .addArgument("this._memorySegment") + .addArgument("0L") + .addArgument("index") + .addArgument("value"))); + if (generateN) { + final InvokeSpec invokeSpec = new InvokeSpec("this", "nset" + capitalized + "At"); + if (isString(eType)) { + final StrCharset strCharset = e.getAnnotation(StrCharset.class); + final InvokeSpec alloc = new InvokeSpec("_segmentAllocator", "allocateFrom") + .addArgument("value"); + if (strCharset != null) { + alloc.addArgument(createCharset(file, strCharset.value())); + } + invokeSpec.addArgument(alloc); + } else if (isArray) { + } else { + invokeSpec.addArgument("value"); + } + invokeSpec.addArgument("index"); + addSetterAt(classSpec, e, "set", simplify(eType.toString()), Spec.statement(invokeSpec)); + } } } // TODO: 2023/12/17 squid233: struct, upcall }); // override - addIStructImpl(classSpec, MemorySegment.class, "segment", Spec.literal("_memorySegment")); + addIStructImpl(classSpec, MemorySegment.class, "segment", Spec.literal("this._memorySegment")); addIStructImpl(classSpec, StructLayout.class, "layout", Spec.literal("_LAYOUT")); - addIStructImpl(classSpec, long.class, "elementCount", new InvokeSpec("_sequenceLayout", "elementCount")); + addIStructImpl(classSpec, long.class, "elementCount", new InvokeSpec("this._sequenceLayout", "elementCount")); }); final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + '.' + simpleClassName); @@ -340,11 +382,57 @@ private void writeFile( } } + private void addSetterAt(ClassSpec classSpec, VariableElement e, String setType, String valueType, Spec setter) { + final String member = e.getSimpleName().toString(); + final TypeMirror eType = e.asType(); + final boolean insertAllocator = "set".equals(setType) && (isString(eType) || isArray(eType)); + classSpec.addMethod(new MethodSpec("void", setType + capitalize(member) + "At"), methodSpec -> { + final String eDoc = getDocument(e); + methodSpec.setDocument(""" + Sets %s at the given index. + + %s@param value the value + @param index the index\ + """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertAllocator ? "@param _segmentAllocator the allocator\n " : "")); + if (insertAllocator) { + methodSpec.addParameter(SegmentAllocator.class, "_segmentAllocator"); + } + methodSpec.addParameter(valueType, "value"); + methodSpec.addParameter(long.class, "index"); + methodSpec.addStatement(setter); + }); + addSetter(classSpec, e, setType, valueType, insertAllocator); + } + + private void addSetter(ClassSpec classSpec, VariableElement e, String setType, String valueType, boolean insertAllocator) { + final String name = setType + capitalize(e.getSimpleName().toString()); + classSpec.addMethod(new MethodSpec("void", name), methodSpec -> { + final String eDoc = getDocument(e); + methodSpec.setDocument(""" + Sets the first %s. + + %s@param value the value\ + """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertAllocator ? "@param _allocator the allocator\n " : "")); + if (insertAllocator) { + methodSpec.addParameter(SegmentAllocator.class, "_segmentAllocator"); + } + methodSpec.addParameter(valueType, "value"); + methodSpec.addStatement(Spec.statement(new InvokeSpec("this", name + "At") + .also(invokeSpec -> { + if (insertAllocator) { + invokeSpec.addArgument("_segmentAllocator"); + } + }) + .addArgument("value") + .addArgument("0L"))); + }); + } + private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, String returnType, Spec returnValue) { + final Sized sized = e.getAnnotation(Sized.class); + final boolean insertCount = "get".equals(getType) && sized == null && isArray(e.asType()); classSpec.addMethod(new MethodSpec(returnType, getType + capitalize(e.getSimpleName().toString()) + "At"), methodSpec -> { final LongSized longSized = e.getAnnotation(LongSized.class); - final Sized sized = e.getAnnotation(Sized.class); - final boolean insertCount = "get".equals(getType) && sized == null && isArray(e.asType()); final String eDoc = getDocument(e); methodSpec.setDocument(""" Gets %s at the given index. @@ -360,6 +448,33 @@ private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, methodSpec.addParameter(long.class, "index"); methodSpec.addStatement(Spec.returnStatement(returnValue)); }); + addGetter(classSpec, e, getType, returnType, insertCount); + } + + private void addGetter(ClassSpec classSpec, VariableElement e, String getType, String returnType, boolean insertCount) { + final String name = getType + capitalize(e.getSimpleName().toString()); + classSpec.addMethod(new MethodSpec(returnType, name), methodSpec -> { + final LongSized longSized = e.getAnnotation(LongSized.class); + final Sized sized = e.getAnnotation(Sized.class); + final String eDoc = getDocument(e); + methodSpec.setDocument(""" + Gets the first %s. + + %s@return %1$s\ + """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertCount ? "@param count the length of the array\n " : "")); + addAnnotationValue(methodSpec, longSized, LongSized.class, LongSized::value); + addAnnotationValue(methodSpec, sized, Sized.class, Sized::value); + if (insertCount) { + methodSpec.addParameter(int.class, "count"); + } + methodSpec.addStatement(Spec.returnStatement(new InvokeSpec("this", name + "At") + .also(invokeSpec -> { + if (insertCount) { + invokeSpec.addArgument("count"); + } + }) + .addArgument("0L"))); + }); } private static void addIStructImpl(ClassSpec classSpec, Class aClass, String name, Spec spec) { diff --git a/src/main/java/overrun/marshal/struct/Const.java b/src/main/java/overrun/marshal/struct/Const.java index 40f7399..ead6528 100644 --- a/src/main/java/overrun/marshal/struct/Const.java +++ b/src/main/java/overrun/marshal/struct/Const.java @@ -26,8 +26,7 @@ *
{@code
  * @Const
  * @Struct
- * class Vector2 {
- *     int x, y;
+ * record Vector2(int x, int y) {
  * }
  * }
* diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/struct/Struct.java index b9f2a75..8fee744 100644 --- a/src/main/java/overrun/marshal/struct/Struct.java +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -20,13 +20,20 @@ /** * Marks a class or interface as a struct provider. + *

+ * The generated class contains a {@link java.lang.foreign.StructLayout StructLayout} with name "{@code _LAYOUT}". + * You can link it in the documentation. *

Example

- *
{@code
- * @Struct
+ * 

+ * /**
+ *  * {@linkplain _LAYOUT Layout}
+ *  */
+ * @Struct
  * class Point {
+ *     @Skip
+ *     int _LAYOUT;
  *     int x, y;
- * }
- * }
+ * }
* * @author squid233 * @see Const From 25dab4cf0dbcc0a0bd1d26510d4efb24a2ba3562 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Wed, 20 Dec 2023 00:10:48 +0800 Subject: [PATCH 4/7] Renamed LongSized to SizedSeg; add null verify --- build.gradle.kts | 78 ++++---- .../overrun/marshal/test/CDowncallTest.java | 4 +- .../overrun/marshal/test/CStructTest.java | 6 +- .../{CConstStructTest.java => CVector3.java} | 2 +- .../marshal/test/GLFWErrorCallback.java | 4 +- .../overrun/marshal/DowncallProcessor.java | 148 ++++++++------- .../marshal/{LongSized.java => SizedSeg.java} | 8 +- .../java/overrun/marshal/StructProcessor.java | 177 +++++++++++++----- src/main/java/overrun/marshal/Upcall.java | 4 +- .../java/overrun/marshal/gen/SourceFile.java | 2 +- src/main/java/overrun/marshal/gen/Spec.java | 15 ++ .../overrun/marshal/internal/Processor.java | 2 +- .../java/overrun/marshal/internal/Util.java | 26 ++- 13 files changed, 300 insertions(+), 176 deletions(-) rename demo/src/main/java/overrun/marshal/test/{CConstStructTest.java => CVector3.java} (93%) rename src/main/java/overrun/marshal/{LongSized.java => SizedSeg.java} (85%) diff --git a/build.gradle.kts b/build.gradle.kts index c305c37..a54ff6e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,45 +5,6 @@ plugins { //application } -data class PublicationRepo( - val name: String, - val usernameFrom: List, - val passwordFrom: List, - val snapshotRepo: String, - val releaseRepo: String, - val snapshotPredicate: (String) -> Boolean = { it.endsWith("-SNAPSHOT") } -) - -val hasPublication: String by rootProject -val publicationSigning: String by rootProject -val publicationRepo: PublicationRepo? = if (hasPublication.toBoolean()) PublicationRepo( - name = "OSSRH", - usernameFrom = listOf("OSSRH_USERNAME", "ossrhUsername"), - passwordFrom = listOf("OSSRH_PASSWORD", "ossrhPassword"), - snapshotRepo = "https://s01.oss.sonatype.org/content/repositories/snapshots/", - releaseRepo = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" -) else null - -data class Organization( - val name: String, - val url: String -) - -data class Developer( - val id: String, - val name: String? = null, - val email: String? = null, - val url: String? = null, - val organization: Organization? = projOrg, - val roles: Set? = null, - val timezone: String? = null, - val properties: Map? = null -) - -val projDevelopers = arrayOf( - Developer("squid233") -) - val hasJavadocJar: String by rootProject val hasSourcesJar: String by rootProject @@ -69,6 +30,45 @@ val jdkEarlyAccessDoc: String? by rootProject val targetJavaVersion = jdkVersion.toInt() +val projDevelopers = arrayOf( + Developer("squid233") +) + +data class Organization( + val name: String, + val url: String +) + +data class Developer( + val id: String, + val name: String? = null, + val email: String? = null, + val url: String? = null, + val organization: Organization? = projOrg, + val roles: Set? = null, + val timezone: String? = null, + val properties: Map? = null +) + +data class PublicationRepo( + val name: String, + val usernameFrom: List, + val passwordFrom: List, + val snapshotRepo: String, + val releaseRepo: String, + val snapshotPredicate: (String) -> Boolean = { it.endsWith("-SNAPSHOT") } +) + +val hasPublication: String by rootProject +val publicationSigning: String by rootProject +val publicationRepo: PublicationRepo? = if (hasPublication.toBoolean()) PublicationRepo( + name = "OSSRH", + usernameFrom = listOf("OSSRH_USERNAME", "ossrhUsername"), + passwordFrom = listOf("OSSRH_PASSWORD", "ossrhPassword"), + snapshotRepo = "https://s01.oss.sonatype.org/content/repositories/snapshots/", + releaseRepo = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" +) else null + allprojects { apply(plugin = "java-library") diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index fb5beb6..25a437c 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -130,8 +130,8 @@ interface CDowncallTest { @Sized(4) int[] testReturnSizedArr(); - @LongSized(4L) - MemorySegment testReturnLongSizedSeg(); + @SizedSeg(4L) + MemorySegment testReturnSizedSeg(); void testUpcall(MemorySegment cb); diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java index 3862f3b..1e92744 100644 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.LongSized; +import overrun.marshal.SizedSeg; import overrun.marshal.Sized; import overrun.marshal.Skip; import overrun.marshal.StrCharset; @@ -47,8 +47,8 @@ final class CStructTest { @Padding(7) int padding; MemorySegment segment; - @LongSized(16L) - MemorySegment longSizedSegment; + @SizedSeg(16L) + MemorySegment sizedSegment; String name; @StrCharset("UTF-16") String utf16Name; diff --git a/demo/src/main/java/overrun/marshal/test/CConstStructTest.java b/demo/src/main/java/overrun/marshal/test/CVector3.java similarity index 93% rename from demo/src/main/java/overrun/marshal/test/CConstStructTest.java rename to demo/src/main/java/overrun/marshal/test/CVector3.java index 3e0d17a..e4bb971 100644 --- a/demo/src/main/java/overrun/marshal/test/CConstStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CVector3.java @@ -25,5 +25,5 @@ */ @Const @Struct -public record CConstStructTest(int x, int y, int z) { +public record CVector3(int x, int y, int z) { } diff --git a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java index 59697f3..3bb6f04 100644 --- a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java +++ b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java @@ -46,8 +46,8 @@ default MemorySegment stub(Arena arena) { @Wrapper static GLFWErrorCallback wrap(MemorySegment stub) { return (error, description) -> { - try { - TYPE.downcall(stub).invokeExact(error, description); + try (Arena arena = Arena.ofConfined()) { + TYPE.downcall(stub).invokeExact(error, arena.allocateFrom(description)); } catch (Throwable e) { throw new RuntimeException(e); } diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index 0f3da2d..249dfa1 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -143,13 +143,16 @@ private void writeFile( }); final boolean notVoid = returnType.getKind() != TypeKind.VOID; final boolean shouldStoreResult = notVoid && - parameters.stream().anyMatch(p -> p.getAnnotation(Ref.class) != null); + (isArray(returnType) || + isString(returnType) || + isUpcall(returnType) || + parameters.stream().anyMatch(p -> p.getAnnotation(Ref.class) != null)); final Access access = e.getAnnotation(Access.class); final Critical critical = e.getAnnotation(Critical.class); final Custom custom = e.getAnnotation(Custom.class); final Default defaultAnnotation = e.getAnnotation(Default.class); final boolean isDefaulted = defaultAnnotation != null; - final LongSized eLongSized = e.getAnnotation(LongSized.class); + final SizedSeg eSizedSeg = e.getAnnotation(SizedSeg.class); final Sized eSized = e.getAnnotation(Sized.class); final String allocatorParamName = shouldInsertArena ? "_arena" : "_segmentAllocator"; @@ -176,7 +179,7 @@ private void writeFile( } })); } - addAnnotationValue(methodSpec, eLongSized, LongSized.class, LongSized::value); + addAnnotationValue(methodSpec, eSizedSeg, SizedSeg.class, SizedSeg::value); addAnnotationValue(methodSpec, eSized, Sized.class, Sized::value); if (access != null) { @@ -197,7 +200,7 @@ private void writeFile( } parameters.forEach(p -> methodSpec.addParameter(new ParameterSpec(parameterTypeFunction.apply(p.asType()), p.getSimpleName().toString()) .also(parameterSpec -> { - addAnnotationValue(parameterSpec, p.getAnnotation(LongSized.class), LongSized.class, LongSized::value); + addAnnotationValue(parameterSpec, p.getAnnotation(SizedSeg.class), SizedSeg.class, SizedSeg::value); addAnnotationValue(parameterSpec, p.getAnnotation(Sized.class), Sized.class, Sized::value); final Ref ref = p.getAnnotation(Ref.class); if (ref != null) { @@ -237,9 +240,9 @@ private void writeFile( .collect(Collectors.toList())); if (notVoid) { final Spec castSpec = Spec.cast(javaReturnType, invokeExact); - if (eLongSized != null || eSized != null) { + if (eSizedSeg != null || eSized != null) { targetStatement.addStatement(Spec.returnStatement(new InvokeSpec(Spec.parentheses(castSpec), "reinterpret") - .addArgument(getConstExp(eLongSized != null ? eLongSized.value() : eSized.value())))); + .addArgument(getConstExp(eSizedSeg != null ? eSizedSeg.value() : eSized.value())))); } else { targetStatement.addStatement(Spec.returnStatement(castSpec)); } @@ -283,7 +286,7 @@ private void writeFile( .addArgument(nameString); } else if (isPrimitiveArray(pType)) { invokeSpec = new InvokeSpec(allocatorParamName, "allocateFrom") - .addArgument(toValueLayout(arrayComponentType)) + .addArgument(toValueLayoutStr(arrayComponentType)) .addArgument(nameString); } else if (isStringArray(pType)) { invokeSpec = new InvokeSpec(StrHelper.class, "of") @@ -304,7 +307,6 @@ private void writeFile( // invoke final InvokeSpec invocation = new InvokeSpec(simpleClassName, overloadValue); - InvokeSpec finalInvocation = invocation; // add argument to overload invocation parameters.forEach(p -> { final TypeMirror pType = p.asType(); @@ -346,7 +348,7 @@ private void writeFile( .addArgument(createCharset(file, getCustomCharset(p)))); } else { invocation.addArgument(new InvokeSpec(allocatorParamName, "allocateFrom") - .addArgument(toValueLayout(getArrayComponentType(pType))) + .addArgument(toValueLayoutStr(getArrayComponentType(pType))) .addArgument(nameString)); } } @@ -355,67 +357,17 @@ private void writeFile( } } }); - // converting return value - if (isArray(returnType)) { - final TypeMirror arrayComponentType = getArrayComponentType(returnType); - if (eSized != null) { - finalInvocation = new InvokeSpec(finalInvocation, "reinterpret") - .addArgument(Spec.operatorSpec("*", - Spec.literal(getConstExp(eSized.value())), - new InvokeSpec(toValueLayout(arrayComponentType), "byteSize"))); - } - if (isBooleanArray(returnType)) { - finalInvocation = new InvokeSpec(BoolHelper.class, "toArray") - .addArgument(finalInvocation); - } else if (isStringArray(returnType)) { - finalInvocation = new InvokeSpec(StrHelper.class, "toArray") - .addArgument(finalInvocation) - .addArgument(createCharset(file, getCustomCharset(e))); - } else { - finalInvocation = new InvokeSpec(finalInvocation, "toArray") - .addArgument(toValueLayout(arrayComponentType)); - } - } else if (isDeclared(returnType)) { - if (isString(returnType)) { - finalInvocation = new InvokeSpec(invocation, "getString") - .addArgument("0L"); - if (e.getAnnotation(StrCharset.class) != null) { - finalInvocation.addArgument(createCharset(file, getCustomCharset(e))); - } - } else if (isUpcall(returnType)) { - // find wrap method - final var wrapMethod = ElementFilter.methodsIn(processingEnv.getTypeUtils() - .asElement(returnType) - .getEnclosedElements()) - .stream() - .filter(method -> method.getAnnotation(Upcall.Wrapper.class) != null) - .filter(method -> { - final var list = method.getParameters(); - return list.size() == 1 && isMemorySegment(list.getFirst().asType()); - }) - .findFirst(); - if (wrapMethod.isPresent()) { - finalInvocation = new InvokeSpec(returnType.toString(), wrapMethod.get().getSimpleName().toString()) - .addArgument(invocation); - } else { - printError(""" - Couldn't find any wrap method in %s while %s::%s required - Possible solution: Mark wrap method with @Upcall.Wrapper""" - .formatted(returnType, type, e)); - return; - } - } - } Spec finalSpec; if (notVoid) { if (shouldStoreResult) { - finalSpec = new VariableStatement("var", "$_marshalResult", finalInvocation) - .setAccessModifier(AccessModifier.PACKAGE_PRIVATE); + finalSpec = new VariableStatement(MemorySegment.class, "$_marshalResult", invocation) + .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) + .setFinal(true); } else { - finalSpec = Spec.returnStatement(finalInvocation); + finalSpec = Spec.returnStatement(invocation); } } else { - finalSpec = Spec.statement(finalInvocation); + finalSpec = Spec.statement(invocation); } methodSpec.addStatement(finalSpec); @@ -438,7 +390,7 @@ private void writeFile( if (isPrimitiveArray(pType)) { spec = Spec.statement(new InvokeSpec(MemorySegment.class, "copy") .addArgument('_' + nameString) - .addArgument(toValueLayout(getArrayComponentType(pType))) + .addArgument(toValueLayoutStr(getArrayComponentType(pType))) .addArgument("0L") .addArgument(nameString) .addArgument("0") @@ -467,7 +419,65 @@ private void writeFile( // return if (shouldStoreResult) { - methodSpec.addStatement(Spec.returnStatement(Spec.literal("$_marshalResult"))); + // converting return value + Spec finalInvocation = Spec.literal("$_marshalResult"); + if (isArray(returnType)) { + final TypeMirror arrayComponentType = getArrayComponentType(returnType); + if (eSized != null) { + finalInvocation = new InvokeSpec(finalInvocation, "reinterpret") + .addArgument(Spec.operatorSpec("*", + Spec.literal(getConstExp(eSized.value())), + new InvokeSpec(toValueLayoutStr(arrayComponentType), "byteSize"))); + } + if (isBooleanArray(returnType)) { + finalInvocation = new InvokeSpec(BoolHelper.class, "toArray") + .addArgument(finalInvocation); + } else if (isStringArray(returnType)) { + finalInvocation = new InvokeSpec(StrHelper.class, "toArray") + .addArgument(finalInvocation) + .addArgument(createCharset(file, getCustomCharset(e))); + } else { + finalInvocation = new InvokeSpec(finalInvocation, "toArray") + .addArgument(toValueLayoutStr(arrayComponentType)); + } + } else if (isDeclared(returnType)) { + if (isString(returnType)) { + final InvokeSpec getString = new InvokeSpec(finalInvocation, "getString") + .addArgument("0L"); + if (e.getAnnotation(StrCharset.class) != null) { + getString.addArgument(createCharset(file, getCustomCharset(e))); + } + finalInvocation = getString; + } else if (isUpcall(returnType)) { + // find wrap method + final var wrapMethod = ElementFilter.methodsIn(processingEnv.getTypeUtils() + .asElement(returnType) + .getEnclosedElements()) + .stream() + .filter(method -> method.getAnnotation(Upcall.Wrapper.class) != null) + .filter(method -> { + final var list = method.getParameters(); + return list.size() == 1 && isMemorySegment(list.getFirst().asType()); + }) + .findFirst(); + if (wrapMethod.isPresent()) { + finalInvocation = new InvokeSpec(returnType.toString(), wrapMethod.get().getSimpleName().toString()) + .addArgument(finalInvocation); + } else { + printError(""" + Couldn't find any wrap method in %s while %s::%s required + Possible solution: Mark wrap method with @Upcall.Wrapper""" + .formatted(returnType, type, e)); + return; + } + } + } + + methodSpec.addStatement(Spec.returnStatement( + Spec.ternaryOp(Spec.neqSpec(new InvokeSpec("$_marshalResult", "address"), Spec.literal("0L")), + finalInvocation, + Spec.literal("null")) + )); } }); }); @@ -570,9 +580,9 @@ private void addMethodHandles(TypeElement type, List methods, .addArgument(new InvokeSpec(FunctionDescriptor.class, returnType.getKind() == TypeKind.VOID ? "ofVoid" : "of").also(invokeSpec -> { if (returnType.getKind() != TypeKind.VOID) { - invokeSpec.addArgument(toValueLayout(returnType)); + invokeSpec.addArgument(toValueLayoutStr(returnType)); } - v.getParameters().forEach(e -> invokeSpec.addArgument(toValueLayout(e.asType()))); + v.getParameters().forEach(e -> invokeSpec.addArgument(toValueLayoutStr(e.asType()))); })).also(invokeSpec -> { if (critical != null) { invokeSpec.addArgument(new InvokeSpec(Spec.accessSpec(Linker.class, Linker.Option.class), "critical") @@ -597,7 +607,7 @@ private static List collectConflictedMethodSignature(ExecutableElement e return ".VOID"; } if (isValueType(t)) { - return toValueLayout(t); + return toValueLayoutStr(t); } return t.toString(); }).toList(); diff --git a/src/main/java/overrun/marshal/LongSized.java b/src/main/java/overrun/marshal/SizedSeg.java similarity index 85% rename from src/main/java/overrun/marshal/LongSized.java rename to src/main/java/overrun/marshal/SizedSeg.java index fcd4500..1910bcd 100644 --- a/src/main/java/overrun/marshal/LongSized.java +++ b/src/main/java/overrun/marshal/SizedSeg.java @@ -19,12 +19,10 @@ import java.lang.annotation.*; /** - * Just like {@link Sized} with long size. - *

- * The generated code will try to check the size of a passing array. + * Marks a memory segment as fix-sized. *

Example

*
{@code
- * @LongSized(0x7FFFFFFFFFFFFFFFL)
+ * @SizedSeg(0x7FFFFFFFFFFFFFFFL)
  * MemorySegment segment;
  * }
* @@ -35,7 +33,7 @@ @Documented @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.SOURCE) -public @interface LongSized { +public @interface SizedSeg { /** * {@return the size of the memory segment} */ diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index e073eac..9a79943 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -116,7 +116,7 @@ private void writeFile( classSpec.addSuperinterface(IStruct.class.getCanonicalName()); // layout - classSpec.addField(new VariableStatement(StructLayout.class, "_LAYOUT", + classSpec.addField(new VariableStatement(StructLayout.class, "LAYOUT", new InvokeSpec(MemoryLayout.class, "structLayout").also(invokeSpec -> forEach(fields, true, e -> { final TypeMirror eType = e.asType(); final Padding padding = e.getAnnotation(Padding.class); @@ -124,7 +124,7 @@ private void writeFile( invokeSpec.addArgument(new InvokeSpec(MemoryLayout.class, "paddingLayout") .addArgument(getConstExp(padding.value()))); } else if (isValueType(eType)) { - InvokeSpec member = new InvokeSpec(toValueLayout(eType), "withName") + InvokeSpec member = new InvokeSpec(toValueLayoutStr(eType), "withName") .addArgument(getConstExp(e.getSimpleName().toString())); if (isArray(eType)) { final Sized sized = e.getAnnotation(Sized.class); @@ -132,7 +132,15 @@ private void writeFile( member = new InvokeSpec(member, "withTargetLayout") .addArgument(new InvokeSpec(MemoryLayout.class, "sequenceLayout") .addArgument(getConstExp(sized.value())) - .addArgument(toValueLayout(getArrayComponentType(eType)))); + .addArgument(toValueLayoutStr(getArrayComponentType(eType)))); + } + } else if (isMemorySegment(eType)) { + final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); + if (sizedSeg != null) { + member = new InvokeSpec(member, "withTargetLayout") + .addArgument(new InvokeSpec(MemoryLayout.class, "sequenceLayout") + .addArgument(getConstExp(sizedSeg.value())) + .addArgument(Spec.accessSpec(ValueLayout.class, "JAVA_BYTE"))); } } invokeSpec.addArgument(member); @@ -152,16 +160,16 @@ private void writeFile( final StrCharset strCharset = e.getAnnotation(StrCharset.class); sb.append(" "); if (strCharset != null) { - sb.append('(').append(strCharset.value()).append(") "); + sb.append('(').append(getCustomCharset(e)).append(") "); } if (typeConst || e.getAnnotation(Const.class) != null) { sb.append("final "); } - if (MemorySegment.class.getSimpleName().equals(substring)) { - final LongSized longSized = e.getAnnotation(LongSized.class); + if (isMemorySegmentSimple(substring)) { + final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); sb.append("void"); - if (longSized != null) { - sb.append('[').append(getConstExp(longSized.value())).append(']'); + if (sizedSeg != null) { + sb.append('[').append(getConstExp(sizedSeg.value())).append(']'); } else { sb.append('*'); } @@ -218,7 +226,7 @@ private void writeFile( methodSpec.addStatement(Spec.assignStatement("this._memorySegment", Spec.literal("segment"))); methodSpec.addStatement(Spec.assignStatement("this._sequenceLayout", new InvokeSpec(MemoryLayout.class, "sequenceLayout") .addArgument("count") - .addArgument("_LAYOUT"))); + .addArgument("LAYOUT"))); forEach(fields, false, e -> { final var name = e.getSimpleName(); methodSpec.addStatement(Spec.assignStatement(Spec.accessSpec("this", name.toString()), @@ -240,7 +248,7 @@ private void writeFile( methodSpec.addParameter(SegmentAllocator.class, "allocator"); methodSpec.addStatement(Spec.returnStatement(new ConstructSpec(simpleClassName) .addArgument(new InvokeSpec("allocator", "allocate") - .addArgument("_LAYOUT")) + .addArgument("LAYOUT")) .addArgument(Spec.literal("1")))); }); classSpec.addMethod(new MethodSpec(simpleClassName, "create"), methodSpec -> { @@ -256,7 +264,7 @@ private void writeFile( methodSpec.addParameter(long.class, "count"); methodSpec.addStatement(Spec.returnStatement(new ConstructSpec(simpleClassName) .addArgument(new InvokeSpec("allocator", "allocate") - .addArgument("_LAYOUT") + .addArgument("LAYOUT") .addArgument("count")) .addArgument(Spec.literal("count")))); }); @@ -273,8 +281,8 @@ private void writeFile( .addArgument(new InvokeSpec("this._memorySegment", "asSlice") .addArgument(Spec.operatorSpec("*", Spec.literal("index"), - new InvokeSpec("_LAYOUT", "byteSize"))) - .addArgument("_LAYOUT")) + new InvokeSpec("LAYOUT", "byteSize"))) + .addArgument("LAYOUT")) .addArgument(Spec.literal("1L")))); }); @@ -283,7 +291,7 @@ private void writeFile( final TypeMirror eType = e.asType(); final String nameString = e.getSimpleName().toString(); if (isValueType(eType)) { - final LongSized longSized = e.getAnnotation(LongSized.class); + final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); final Sized sized = e.getAnnotation(Sized.class); final boolean canConvertToAddress = canConvertToAddress(eType); final String returnType = canConvertToAddress ? MemorySegment.class.getSimpleName() : eType.toString(); @@ -298,46 +306,62 @@ private void writeFile( .addArgument("this._memorySegment") .addArgument("0L") .addArgument("index")); - addGetterAt(classSpec, e, generateN ? "nget" : "get", returnType, - (isMemorySegment && longSized != null || isArray && sized != null) ? + addGetterAt(classSpec, e, generateN ? "nget" : "get", returnType, null, + (isMemorySegment && sizedSeg != null || isArray && sized != null) ? new InvokeSpec(Spec.parentheses(castSpec), "reinterpret") - .addArgument(longSized != null ? - Spec.literal(getConstExp(longSized.value())) : + .addArgument(sizedSeg != null ? + Spec.literal(getConstExp(sizedSeg.value())) : Spec.operatorSpec("*", Spec.literal(getConstExp(sized.value())), - new InvokeSpec(toValueLayout(getArrayComponentType(eType)), "byteSize"))) : + new InvokeSpec(toValueLayoutStr(getArrayComponentType(eType)), "byteSize"))) : castSpec); if (generateN) { - Spec returnValue = new InvokeSpec("this", "nget" + capitalized + "At") + Spec storeResult = null; + final Spec returnValue = new InvokeSpec("this", "nget" + capitalized + "At") .addArgument("index"); - if (isString(eType)) { + Spec finalReturnValue; + final boolean isString = isString(eType); + if (isString || isArray) { + storeResult = new VariableStatement(MemorySegment.class, "$_marshalResult", returnValue) + .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) + .setFinal(true); + finalReturnValue = Spec.literal("$_marshalResult"); + } else { + finalReturnValue = returnValue; + } + if (isString) { final StrCharset strCharset = e.getAnnotation(StrCharset.class); - final InvokeSpec invokeSpec = new InvokeSpec(returnValue, "getString") + final InvokeSpec invokeSpec = new InvokeSpec(finalReturnValue, "getString") .addArgument("0"); if (strCharset != null) { - invokeSpec.addArgument(createCharset(file, strCharset.value())); + invokeSpec.addArgument(createCharset(file, getCustomCharset(e))); } - returnValue = invokeSpec; + finalReturnValue = invokeSpec; } else if (isArray) { final TypeMirror arrayComponentType = getArrayComponentType(eType); if (sized == null) { - returnValue = new InvokeSpec(returnValue, "reinterpret") + finalReturnValue = new InvokeSpec(finalReturnValue, "reinterpret") .addArgument(Spec.operatorSpec("*", Spec.literal("count"), - new InvokeSpec(toValueLayout(arrayComponentType), "byteSize"))); + new InvokeSpec(toValueLayoutStr(arrayComponentType), "byteSize"))); } if (isBooleanArray(eType)) { - returnValue = new InvokeSpec(BoolHelper.class.getCanonicalName(), "toArray") - .addArgument(returnValue); + finalReturnValue = new InvokeSpec(BoolHelper.class.getCanonicalName(), "toArray") + .addArgument(finalReturnValue); } else if (isStringArray(eType)) { - returnValue = new InvokeSpec(StrHelper.class.getCanonicalName(), "toArray") - .addArgument(returnValue) + finalReturnValue = new InvokeSpec(StrHelper.class.getCanonicalName(), "toArray") + .addArgument(finalReturnValue) .addArgument(createCharset(file, getCustomCharset(e))); } else { - returnValue = new InvokeSpec(returnValue, "toArray") - .addArgument(toValueLayout(arrayComponentType)); + finalReturnValue = new InvokeSpec(finalReturnValue, "toArray") + .addArgument(toValueLayoutStr(arrayComponentType)); } } - addGetterAt(classSpec, e, "get", simplify(eType.toString()), returnValue); + if (storeResult != null) { + finalReturnValue = Spec.ternaryOp(Spec.neqSpec(new InvokeSpec("$_marshalResult", "address"), Spec.literal("0L")), + finalReturnValue, + Spec.literal("null")); + } + addGetterAt(classSpec, e, "get", simplify(eType.toString()), storeResult, finalReturnValue); } // setter @@ -349,20 +373,34 @@ private void writeFile( .addArgument("index") .addArgument("value"))); if (generateN) { - final InvokeSpec invokeSpec = new InvokeSpec("this", "nset" + capitalized + "At"); + final InvokeSpec invokeSpec = new InvokeSpec("this", "nset" + capitalized + "At") + .addArgument("index"); if (isString(eType)) { final StrCharset strCharset = e.getAnnotation(StrCharset.class); final InvokeSpec alloc = new InvokeSpec("_segmentAllocator", "allocateFrom") .addArgument("value"); if (strCharset != null) { - alloc.addArgument(createCharset(file, strCharset.value())); + alloc.addArgument(createCharset(file, getCustomCharset(e))); } invokeSpec.addArgument(alloc); } else if (isArray) { + if (isBooleanArray(eType)) { + invokeSpec.addArgument(new InvokeSpec(BoolHelper.class.getCanonicalName(), "of") + .addArgument("_segmentAllocator") + .addArgument("value")); + } else if (isStringArray(eType)) { + invokeSpec.addArgument(new InvokeSpec(StrHelper.class.getCanonicalName(), "of") + .addArgument("_segmentAllocator") + .addArgument("value") + .addArgument(createCharset(file, getCustomCharset(e)))); + } else { + invokeSpec.addArgument(new InvokeSpec("_segmentAllocator", "allocateFrom") + .addArgument(toValueLayoutStr(getArrayComponentType(eType))) + .addArgument("value")); + } } else { invokeSpec.addArgument("value"); } - invokeSpec.addArgument("index"); addSetterAt(classSpec, e, "set", simplify(eType.toString()), Spec.statement(invokeSpec)); } } @@ -372,7 +410,7 @@ private void writeFile( // override addIStructImpl(classSpec, MemorySegment.class, "segment", Spec.literal("this._memorySegment")); - addIStructImpl(classSpec, StructLayout.class, "layout", Spec.literal("_LAYOUT")); + addIStructImpl(classSpec, StructLayout.class, "layout", Spec.literal("LAYOUT")); addIStructImpl(classSpec, long.class, "elementCount", new InvokeSpec("this._sequenceLayout", "elementCount")); }); @@ -391,14 +429,14 @@ private void addSetterAt(ClassSpec classSpec, VariableElement e, String setType, methodSpec.setDocument(""" Sets %s at the given index. - %s@param value the value - @param index the index\ + %s@param index the index + @param value the value\ """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertAllocator ? "@param _segmentAllocator the allocator\n " : "")); if (insertAllocator) { methodSpec.addParameter(SegmentAllocator.class, "_segmentAllocator"); } - methodSpec.addParameter(valueType, "value"); methodSpec.addParameter(long.class, "index"); + addSetterValue(e, valueType, methodSpec); methodSpec.addStatement(setter); }); addSetter(classSpec, e, setType, valueType, insertAllocator); @@ -416,23 +454,23 @@ private void addSetter(ClassSpec classSpec, VariableElement e, String setType, S if (insertAllocator) { methodSpec.addParameter(SegmentAllocator.class, "_segmentAllocator"); } - methodSpec.addParameter(valueType, "value"); + addSetterValue(e, valueType, methodSpec); methodSpec.addStatement(Spec.statement(new InvokeSpec("this", name + "At") .also(invokeSpec -> { if (insertAllocator) { invokeSpec.addArgument("_segmentAllocator"); } }) - .addArgument("value") - .addArgument("0L"))); + .addArgument("0L") + .addArgument("value"))); }); } - private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, String returnType, Spec returnValue) { + private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, String returnType, Spec storeResult, Spec returnValue) { final Sized sized = e.getAnnotation(Sized.class); final boolean insertCount = "get".equals(getType) && sized == null && isArray(e.asType()); classSpec.addMethod(new MethodSpec(returnType, getType + capitalize(e.getSimpleName().toString()) + "At"), methodSpec -> { - final LongSized longSized = e.getAnnotation(LongSized.class); + final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); final String eDoc = getDocument(e); methodSpec.setDocument(""" Gets %s at the given index. @@ -440,12 +478,14 @@ private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, %s@param index the index @return %1$s\ """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertCount ? "@param count the length of the array\n " : "")); - addAnnotationValue(methodSpec, longSized, LongSized.class, LongSized::value); - addAnnotationValue(methodSpec, sized, Sized.class, Sized::value); + addGetterAnnotation(e, returnType, methodSpec, sizedSeg, sized); if (insertCount) { methodSpec.addParameter(int.class, "count"); } methodSpec.addParameter(long.class, "index"); + if (storeResult != null) { + methodSpec.addStatement(storeResult); + } methodSpec.addStatement(Spec.returnStatement(returnValue)); }); addGetter(classSpec, e, getType, returnType, insertCount); @@ -454,7 +494,7 @@ private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, private void addGetter(ClassSpec classSpec, VariableElement e, String getType, String returnType, boolean insertCount) { final String name = getType + capitalize(e.getSimpleName().toString()); classSpec.addMethod(new MethodSpec(returnType, name), methodSpec -> { - final LongSized longSized = e.getAnnotation(LongSized.class); + final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); final Sized sized = e.getAnnotation(Sized.class); final String eDoc = getDocument(e); methodSpec.setDocument(""" @@ -462,8 +502,7 @@ private void addGetter(ClassSpec classSpec, VariableElement e, String getType, S %s@return %1$s\ """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertCount ? "@param count the length of the array\n " : "")); - addAnnotationValue(methodSpec, longSized, LongSized.class, LongSized::value); - addAnnotationValue(methodSpec, sized, Sized.class, Sized::value); + addGetterAnnotation(e, returnType, methodSpec, sizedSeg, sized); if (insertCount) { methodSpec.addParameter(int.class, "count"); } @@ -477,6 +516,40 @@ private void addGetter(ClassSpec classSpec, VariableElement e, String getType, S }); } + private void addSetterValue(VariableElement e, String valueType, MethodSpec methodSpec) { + methodSpec.addParameter(new ParameterSpec(valueType, "value").also(parameterSpec -> { + if (isMemorySegmentSimple(valueType)) { + final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); + if (sizedSeg != null) { + addAnnotationValue(parameterSpec, sizedSeg, SizedSeg.class, SizedSeg::value); + } else { + final Sized sized = e.getAnnotation(Sized.class); + if (sized != null) { + parameterSpec.addAnnotation(new AnnotationSpec(SizedSeg.class) + .addArgument("value", getConstExp(sized.value() * toValueLayout(e.asType()).byteSize()))); + } + } + } else { + addAnnotationValue(parameterSpec, e.getAnnotation(Sized.class), Sized.class, Sized::value); + } + })); + } + + private void addGetterAnnotation(VariableElement e, String returnType, MethodSpec methodSpec, SizedSeg sizedSeg, Sized sized) { + if (isMemorySegmentSimple(returnType)) { + if (sizedSeg != null) { + addAnnotationValue(methodSpec, sizedSeg, SizedSeg.class, SizedSeg::value); + } else { + if (sized != null) { + methodSpec.addAnnotation(new AnnotationSpec(SizedSeg.class) + .addArgument("value", getConstExp(sized.value() * toValueLayout(e.asType()).byteSize()))); + } + } + } else { + addAnnotationValue(methodSpec, sized, Sized.class, Sized::value); + } + } + private static void addIStructImpl(ClassSpec classSpec, Class aClass, String name, Spec spec) { classSpec.addMethod(new MethodSpec(aClass.getSimpleName(), name), methodSpec -> { methodSpec.addAnnotation(new AnnotationSpec(Override.class)); @@ -489,6 +562,10 @@ private static void forEach(List fields, boolean allowPadding, (allowPadding || e.getAnnotation(Padding.class) == null)).forEach(action); } + private static boolean isMemorySegmentSimple(String name) { + return MemorySegment.class.getSimpleName().equals(name); + } + @Override public Set getSupportedAnnotationTypes() { return Set.of(Struct.class.getCanonicalName()); diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index 26705ff..256fbb0 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -57,8 +57,8 @@ * @Wrapper * static MyCallback wrap(MemorySegment stub) { * return (i, p) -> { - * try { - * return TYPE.downcall(stub).invokeExact(i, p); + * try (var arena = ofConfined()) { + * return TYPE.downcall(stub).invokeExact(i, arena.allocateFrom(p)); * } catch (Throwable e) { * throw new RuntimeException(e); * } diff --git a/src/main/java/overrun/marshal/gen/SourceFile.java b/src/main/java/overrun/marshal/gen/SourceFile.java index 0b94020..87a2f92 100644 --- a/src/main/java/overrun/marshal/gen/SourceFile.java +++ b/src/main/java/overrun/marshal/gen/SourceFile.java @@ -96,7 +96,7 @@ public void addClass(String className, Consumer consumer) { * @throws IOException If an I/O error occurs */ public void write(Writer writer) throws IOException { - final StringBuilder sb = new StringBuilder(16384); + final StringBuilder sb = new StringBuilder(32768); // header sb.append("// This file is auto-generated. DO NOT EDIT!\n"); // package diff --git a/src/main/java/overrun/marshal/gen/Spec.java b/src/main/java/overrun/marshal/gen/Spec.java index bb09f73..75225d0 100644 --- a/src/main/java/overrun/marshal/gen/Spec.java +++ b/src/main/java/overrun/marshal/gen/Spec.java @@ -180,6 +180,21 @@ static Spec notNullSpec(String value) { return literal(value + " != null"); } + /** + * Create non-equal spec + * + * @param left left + * @param right right + * @return spec + */ + static Spec neqSpec(Spec left, Spec right) { + return (builder, indent) -> { + left.append(builder, indent); + builder.append(" != "); + right.append(builder, indent); + }; + } + /** * Create an expression casting * diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java index d0e1394..d8cf38a 100644 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ b/src/main/java/overrun/marshal/internal/Processor.java @@ -147,7 +147,7 @@ protected void addAnnotationValue(Annotatable annotata */ protected static String getCustomCharset(Element e) { final StrCharset strCharset = e.getAnnotation(StrCharset.class); - return strCharset != null ? strCharset.value() : "UTF-8"; + return strCharset != null && !strCharset.value().isBlank() ? strCharset.value() : "UTF-8"; } /** diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index a2536b5..d45e63b 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -20,6 +20,7 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; /** * Util @@ -211,7 +212,30 @@ public static boolean isValueType(TypeMirror typeMirror) { * @param typeMirror typeMirror * @return toValueLayout */ - public static String toValueLayout(TypeMirror typeMirror) { + public static ValueLayout toValueLayout(TypeMirror typeMirror) { + return switch (typeMirror.getKind()) { + case BOOLEAN -> ValueLayout.JAVA_BOOLEAN; + case BYTE -> ValueLayout.JAVA_BYTE; + case SHORT -> ValueLayout.JAVA_SHORT; + case INT -> ValueLayout.JAVA_INT; + case LONG -> ValueLayout.JAVA_LONG; + case CHAR -> ValueLayout.JAVA_CHAR; + case FLOAT -> ValueLayout.JAVA_FLOAT; + case DOUBLE -> ValueLayout.JAVA_DOUBLE; + default -> { + if (canConvertToAddress(typeMirror)) yield ValueLayout.ADDRESS; + throw invalidType(typeMirror); + } + }; + } + + /** + * toValueLayoutStr + * + * @param typeMirror typeMirror + * @return toValueLayoutStr + */ + public static String toValueLayoutStr(TypeMirror typeMirror) { return switch (typeMirror.getKind()) { case BOOLEAN -> "ValueLayout.JAVA_BOOLEAN"; case BYTE -> "ValueLayout.JAVA_BYTE"; From 19a491fd65e35e172e794088042386869fea2a60 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 1 Jan 2024 12:08:52 +0800 Subject: [PATCH 5/7] Upcall in struct --- .../overrun/marshal/test/CStructTest.java | 4 + .../marshal/test/GLFWErrorCallback.java | 8 +- .../overrun/marshal/DowncallProcessor.java | 17 +- .../java/overrun/marshal/StructProcessor.java | 148 +++++++++++++----- src/main/java/overrun/marshal/Upcall.java | 22 ++- .../overrun/marshal/internal/Processor.java | 98 ++++++++++++ .../java/overrun/marshal/internal/Util.java | 74 --------- 7 files changed, 233 insertions(+), 138 deletions(-) diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java index 1e92744..3ea9708 100644 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -39,6 +39,9 @@ final class CStructTest { int x; @Const int y; + int index; + int[] segmentAllocator; + GLFWErrorCallback arena; /** * the timestamp */ @@ -57,4 +60,5 @@ final class CStructTest { int[] sizedArr; boolean[] boolArr; String[] strArr; + GLFWErrorCallback upcall; } diff --git a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java index 3bb6f04..5a598e7 100644 --- a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java +++ b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java @@ -45,12 +45,12 @@ default MemorySegment stub(Arena arena) { @Wrapper static GLFWErrorCallback wrap(MemorySegment stub) { - return (error, description) -> { - try (Arena arena = Arena.ofConfined()) { - TYPE.downcall(stub).invokeExact(error, arena.allocateFrom(description)); + return TYPE.wrap(stub, (arena, methodHandle) -> (error, description) -> { + try { + methodHandle.invokeExact(error, arena.get().allocateFrom(description)); } catch (Throwable e) { throw new RuntimeException(e); } - }; + }); } } diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index 249dfa1..7eda6aa 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -450,23 +450,14 @@ private void writeFile( finalInvocation = getString; } else if (isUpcall(returnType)) { // find wrap method - final var wrapMethod = ElementFilter.methodsIn(processingEnv.getTypeUtils() - .asElement(returnType) - .getEnclosedElements()) - .stream() - .filter(method -> method.getAnnotation(Upcall.Wrapper.class) != null) - .filter(method -> { - final var list = method.getParameters(); - return list.size() == 1 && isMemorySegment(list.getFirst().asType()); - }) - .findFirst(); + final var wrapMethod = findWrapperMethod(returnType); if (wrapMethod.isPresent()) { finalInvocation = new InvokeSpec(returnType.toString(), wrapMethod.get().getSimpleName().toString()) .addArgument(finalInvocation); } else { printError(""" - Couldn't find any wrap method in %s while %s::%s required - Possible solution: Mark wrap method with @Upcall.Wrapper""" + Couldn't find any wrap method in %s while %s::%s required + Possible solution: Mark the wrap method with @Upcall.Wrapper""" .formatted(returnType, type, e)); return; } @@ -601,7 +592,7 @@ private void addMethodHandles(TypeElement type, List methods, }); } - private static List collectConflictedMethodSignature(ExecutableElement e) { + private List collectConflictedMethodSignature(ExecutableElement e) { return Stream.concat(Stream.of(e.getReturnType()), e.getParameters().stream().map(VariableElement::asType)).map(t -> { if (t.getKind() == TypeKind.VOID) { return ".VOID"; diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 9a79943..3e548b8 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -228,8 +228,8 @@ private void writeFile( .addArgument("count") .addArgument("LAYOUT"))); forEach(fields, false, e -> { - final var name = e.getSimpleName(); - methodSpec.addStatement(Spec.assignStatement(Spec.accessSpec("this", name.toString()), + final var name = e.getSimpleName().toString(); + methodSpec.addStatement(Spec.assignStatement(Spec.accessSpec("this", name), new InvokeSpec(Spec.literal("this._sequenceLayout"), "varHandle") .addArgument("_SEQUENCE_ELEMENT") .addArgument("_PE_" + name))); @@ -291,13 +291,15 @@ private void writeFile( final TypeMirror eType = e.asType(); final String nameString = e.getSimpleName().toString(); if (isValueType(eType)) { + final String eTypeString = eType.toString(); final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); final Sized sized = e.getAnnotation(Sized.class); final boolean canConvertToAddress = canConvertToAddress(eType); - final String returnType = canConvertToAddress ? MemorySegment.class.getSimpleName() : eType.toString(); + final String returnType = canConvertToAddress ? MemorySegment.class.getSimpleName() : eTypeString; final String capitalized = capitalize(nameString); final boolean isMemorySegment = isMemorySegment(eType); final boolean isArray = isArray(eType); + final boolean upcall = isUpcall(eType); final boolean generateN = canConvertToAddress && !isMemorySegment; // getter @@ -355,30 +357,47 @@ private void writeFile( finalReturnValue = new InvokeSpec(finalReturnValue, "toArray") .addArgument(toValueLayoutStr(arrayComponentType)); } + } else if (upcall) { + // find wrap method + final var wrapMethod = findWrapperMethod(eType); + if (wrapMethod.isPresent()) { + finalReturnValue = new InvokeSpec(eTypeString, wrapMethod.get().getSimpleName().toString()) + .addArgument(finalReturnValue); + } else { + printError(""" + Couldn't find any wrap method in %s while %s.%s required + Possible solution: Mark the wrap method with @Upcall.Wrapper""" + .formatted(eType, type, nameString)); + return; + } } if (storeResult != null) { finalReturnValue = Spec.ternaryOp(Spec.neqSpec(new InvokeSpec("$_marshalResult", "address"), Spec.literal("0L")), finalReturnValue, Spec.literal("null")); } - addGetterAt(classSpec, e, "get", simplify(eType.toString()), storeResult, finalReturnValue); + addGetterAt(classSpec, e, "get", simplify(eTypeString), storeResult, finalReturnValue); } // setter if (!typeConst && e.getAnnotation(Const.class) == null) { + final String paramIndex = convertParamIndex(nameString); + final String paramSegmentAllocator = convertParamSegmentAllocator(nameString); addSetterAt(classSpec, e, generateN ? "nset" : "set", returnType, Spec.statement(new InvokeSpec(Spec.accessSpec("this", nameString), "set") .addArgument("this._memorySegment") .addArgument("0L") - .addArgument("index") - .addArgument("value"))); + .also(invokeSpec -> { + invokeSpec.addArgument(paramIndex); + invokeSpec.addArgument(nameString); + }))); if (generateN) { - final InvokeSpec invokeSpec = new InvokeSpec("this", "nset" + capitalized + "At") - .addArgument("index"); + final InvokeSpec invokeSpec = new InvokeSpec("this", "nset" + capitalized + "At"); + invokeSpec.addArgument(paramIndex); if (isString(eType)) { final StrCharset strCharset = e.getAnnotation(StrCharset.class); - final InvokeSpec alloc = new InvokeSpec("_segmentAllocator", "allocateFrom") - .addArgument("value"); + final InvokeSpec alloc = new InvokeSpec(paramSegmentAllocator, "allocateFrom") + .addArgument(nameString); if (strCharset != null) { alloc.addArgument(createCharset(file, getCustomCharset(e))); } @@ -386,26 +405,29 @@ private void writeFile( } else if (isArray) { if (isBooleanArray(eType)) { invokeSpec.addArgument(new InvokeSpec(BoolHelper.class.getCanonicalName(), "of") - .addArgument("_segmentAllocator") - .addArgument("value")); + .addArgument(paramSegmentAllocator) + .addArgument(nameString)); } else if (isStringArray(eType)) { invokeSpec.addArgument(new InvokeSpec(StrHelper.class.getCanonicalName(), "of") - .addArgument("_segmentAllocator") - .addArgument("value") + .addArgument(paramSegmentAllocator) + .addArgument(nameString) .addArgument(createCharset(file, getCustomCharset(e)))); } else { - invokeSpec.addArgument(new InvokeSpec("_segmentAllocator", "allocateFrom") + invokeSpec.addArgument(new InvokeSpec(paramSegmentAllocator, "allocateFrom") .addArgument(toValueLayoutStr(getArrayComponentType(eType))) - .addArgument("value")); + .addArgument(nameString)); } + } else if (isUpcall(eType)) { + invokeSpec.addArgument(new InvokeSpec(nameString, "stub") + .addArgument(convertParamArena(nameString))); } else { - invokeSpec.addArgument("value"); + invokeSpec.addArgument(nameString); } - addSetterAt(classSpec, e, "set", simplify(eType.toString()), Spec.statement(invokeSpec)); + addSetterAt(classSpec, e, "set", simplify(eTypeString), Spec.statement(invokeSpec)); } } } - // TODO: 2023/12/17 squid233: struct, upcall + // TODO: 2023/12/17 squid233: struct }); // override @@ -421,55 +443,78 @@ private void writeFile( } private void addSetterAt(ClassSpec classSpec, VariableElement e, String setType, String valueType, Spec setter) { - final String member = e.getSimpleName().toString(); + final String nameString = e.getSimpleName().toString(); final TypeMirror eType = e.asType(); - final boolean insertAllocator = "set".equals(setType) && (isString(eType) || isArray(eType)); - classSpec.addMethod(new MethodSpec("void", setType + capitalize(member) + "At"), methodSpec -> { + final boolean insertArena = "set".equals(setType) && isUpcall(eType); + final boolean insertAllocator = !insertArena && "set".equals(setType) && (isString(eType) || isArray(eType)); + classSpec.addMethod(new MethodSpec("void", setType + capitalize(nameString) + "At"), methodSpec -> { final String eDoc = getDocument(e); + final String paramIndex = convertParamIndex(nameString); + final String paramArena = convertParamArena(nameString); + final String paramSegmentAllocator = convertParamSegmentAllocator(nameString); methodSpec.setDocument(""" Sets %s at the given index. - %s@param index the index - @param value the value\ - """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertAllocator ? "@param _segmentAllocator the allocator\n " : "")); - if (insertAllocator) { - methodSpec.addParameter(SegmentAllocator.class, "_segmentAllocator"); + %s@param %s the index + @param %s the value\ + """.formatted(eDoc != null ? eDoc : "{@code " + nameString + '}', + insertArena ? + "@param " + paramArena + " the arena\n " : + (insertAllocator ? "@param " + paramSegmentAllocator + " the allocator\n " : ""), + paramIndex, + nameString)); + if (insertArena) { + methodSpec.addParameter(Arena.class, paramArena); + } else if (insertAllocator) { + methodSpec.addParameter(SegmentAllocator.class, paramSegmentAllocator); } - methodSpec.addParameter(long.class, "index"); + methodSpec.addParameter(long.class, paramIndex); addSetterValue(e, valueType, methodSpec); methodSpec.addStatement(setter); }); - addSetter(classSpec, e, setType, valueType, insertAllocator); + addSetter(classSpec, e, setType, valueType, insertArena, insertAllocator); } - private void addSetter(ClassSpec classSpec, VariableElement e, String setType, String valueType, boolean insertAllocator) { - final String name = setType + capitalize(e.getSimpleName().toString()); + private void addSetter(ClassSpec classSpec, VariableElement e, String setType, String valueType, boolean insertArena, boolean insertAllocator) { + final String nameString = e.getSimpleName().toString(); + final String name = setType + capitalize(nameString); classSpec.addMethod(new MethodSpec("void", name), methodSpec -> { final String eDoc = getDocument(e); + final String paramArena = convertParamArena(nameString); + final String paramSegmentAllocator = convertParamSegmentAllocator(nameString); methodSpec.setDocument(""" Sets the first %s. - %s@param value the value\ - """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertAllocator ? "@param _allocator the allocator\n " : "")); - if (insertAllocator) { - methodSpec.addParameter(SegmentAllocator.class, "_segmentAllocator"); + %s@param %s the value\ + """.formatted(eDoc != null ? eDoc : "{@code " + nameString + '}', + insertArena ? + ("@param " + paramArena + " the arena\n ") : + (insertAllocator ? ("@param " + paramSegmentAllocator + " the allocator\n ") : ""), + nameString)); + if (insertArena) { + methodSpec.addParameter(Arena.class, paramArena); + } else if (insertAllocator) { + methodSpec.addParameter(SegmentAllocator.class, paramSegmentAllocator); } addSetterValue(e, valueType, methodSpec); methodSpec.addStatement(Spec.statement(new InvokeSpec("this", name + "At") .also(invokeSpec -> { - if (insertAllocator) { - invokeSpec.addArgument("_segmentAllocator"); + if (insertArena) { + invokeSpec.addArgument(paramArena); + } else if (insertAllocator) { + invokeSpec.addArgument(paramSegmentAllocator); } }) .addArgument("0L") - .addArgument("value"))); + .addArgument(nameString))); }); } private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, String returnType, Spec storeResult, Spec returnValue) { + final String nameString = e.getSimpleName().toString(); final Sized sized = e.getAnnotation(Sized.class); final boolean insertCount = "get".equals(getType) && sized == null && isArray(e.asType()); - classSpec.addMethod(new MethodSpec(returnType, getType + capitalize(e.getSimpleName().toString()) + "At"), methodSpec -> { + classSpec.addMethod(new MethodSpec(returnType, getType + capitalize(nameString) + "At"), methodSpec -> { final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); final String eDoc = getDocument(e); methodSpec.setDocument(""" @@ -477,7 +522,7 @@ private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, %s@param index the index @return %1$s\ - """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertCount ? "@param count the length of the array\n " : "")); + """.formatted(eDoc != null ? eDoc : "{@code " + nameString + '}', insertCount ? "@param count the length of the array\n " : "")); addGetterAnnotation(e, returnType, methodSpec, sizedSeg, sized); if (insertCount) { methodSpec.addParameter(int.class, "count"); @@ -492,7 +537,8 @@ private void addGetterAt(ClassSpec classSpec, VariableElement e, String getType, } private void addGetter(ClassSpec classSpec, VariableElement e, String getType, String returnType, boolean insertCount) { - final String name = getType + capitalize(e.getSimpleName().toString()); + final String nameString = e.getSimpleName().toString(); + final String name = getType + capitalize(nameString); classSpec.addMethod(new MethodSpec(returnType, name), methodSpec -> { final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); final Sized sized = e.getAnnotation(Sized.class); @@ -501,7 +547,7 @@ private void addGetter(ClassSpec classSpec, VariableElement e, String getType, S Gets the first %s. %s@return %1$s\ - """.formatted(eDoc != null ? eDoc : "{@code " + e.getSimpleName() + '}', insertCount ? "@param count the length of the array\n " : "")); + """.formatted(eDoc != null ? eDoc : "{@code " + nameString + '}', insertCount ? "@param count the length of the array\n " : "")); addGetterAnnotation(e, returnType, methodSpec, sizedSeg, sized); if (insertCount) { methodSpec.addParameter(int.class, "count"); @@ -517,7 +563,7 @@ private void addGetter(ClassSpec classSpec, VariableElement e, String getType, S } private void addSetterValue(VariableElement e, String valueType, MethodSpec methodSpec) { - methodSpec.addParameter(new ParameterSpec(valueType, "value").also(parameterSpec -> { + methodSpec.addParameter(new ParameterSpec(valueType, e.getSimpleName().toString()).also(parameterSpec -> { if (isMemorySegmentSimple(valueType)) { final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); if (sizedSeg != null) { @@ -550,6 +596,22 @@ private void addGetterAnnotation(VariableElement e, String returnType, MethodSpe } } + private static String convertParamIndex(String nameString) { + return insertUnderline("index", nameString); + } + + private static String convertParamSegmentAllocator(String nameString) { + return insertUnderline("segmentAllocator", nameString); + } + + private static String convertParamArena(String nameString) { + return insertUnderline("arena", nameString); + } + + private static String insertUnderline(String builtinName, String nameString) { + return builtinName.equals(nameString) ? "_" + builtinName : builtinName; + } + private static void addIStructImpl(ClassSpec classSpec, Class aClass, String name, Spec spec) { classSpec.addMethod(new MethodSpec(aClass.getSimpleName(), name), methodSpec -> { methodSpec.addAnnotation(new AnnotationSpec(Override.class)); diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index 256fbb0..b3e83b0 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -22,6 +22,8 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.function.BiFunction; +import java.util.function.Supplier; /** * An upcall interface. @@ -56,13 +58,13 @@ * // Create a wrap method * @Wrapper * static MyCallback wrap(MemorySegment stub) { - * return (i, p) -> { - * try (var arena = ofConfined()) { - * return TYPE.downcall(stub).invokeExact(i, arena.allocateFrom(p)); + * return TYPE.wrap(stub, (arena, mh) -> (i, p) -> { + * try { + * return mh.invokeExact(i, arena.get().allocateFrom(p)); * } catch (Throwable e) { * throw new RuntimeException(e); * } - * } + * }); * } * } * @@ -201,6 +203,18 @@ public MethodHandle downcall(MemorySegment stub) { return LINKER.downcallHandle(stub, descriptor); } + /** + * Wraps the given upcall stub segment. + * + * @param stub the upcall stub segment + * @param function the function that transforms the given method handle into the downcall type. + * The {@link Arena} is wrapped in a {@link Supplier} and you should store it with a variable + * @return the downcall type + */ + public T wrap(MemorySegment stub, BiFunction, MethodHandle, T> function) { + return function.apply(Arena::ofAuto, downcall(stub)); + } + /** * {@return the descriptor of this type} */ diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java index d8cf38a..ee66db3 100644 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ b/src/main/java/overrun/marshal/internal/Processor.java @@ -24,15 +24,21 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; import java.io.PrintWriter; import java.io.Writer; import java.lang.annotation.Annotation; +import java.lang.foreign.ValueLayout; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.Locale; +import java.util.Optional; import java.util.function.Function; +import static overrun.marshal.internal.Util.*; + /** * Annotation processor * @@ -173,6 +179,98 @@ protected Spec createCharset(SourceFile file, String name) { }; } + /** + * canConvertToAddress + * + * @param typeMirror typeMirror + * @return canConvertToAddress + */ + protected boolean canConvertToAddress(TypeMirror typeMirror) { + return switch (typeMirror.getKind()) { + case ARRAY -> isPrimitiveArray(typeMirror) || isBooleanArray(typeMirror) || isStringArray(typeMirror); + case DECLARED -> isMemorySegment(typeMirror) || isString(typeMirror) || isUpcall(typeMirror); + default -> false; + }; + } + + /** + * isValueType + * + * @param typeMirror typeMirror + * @return isValueType + */ + protected boolean isValueType(TypeMirror typeMirror) { + if (typeMirror.getKind().isPrimitive()) { + return true; + } + return canConvertToAddress(typeMirror); + } + + /** + * toValueLayout + * + * @param typeMirror typeMirror + * @return toValueLayout + */ + protected ValueLayout toValueLayout(TypeMirror typeMirror) { + return switch (typeMirror.getKind()) { + case BOOLEAN -> ValueLayout.JAVA_BOOLEAN; + case BYTE -> ValueLayout.JAVA_BYTE; + case SHORT -> ValueLayout.JAVA_SHORT; + case INT -> ValueLayout.JAVA_INT; + case LONG -> ValueLayout.JAVA_LONG; + case CHAR -> ValueLayout.JAVA_CHAR; + case FLOAT -> ValueLayout.JAVA_FLOAT; + case DOUBLE -> ValueLayout.JAVA_DOUBLE; + default -> { + if (canConvertToAddress(typeMirror)) yield ValueLayout.ADDRESS; + throw invalidType(typeMirror); + } + }; + } + + /** + * toValueLayoutStr + * + * @param typeMirror typeMirror + * @return toValueLayoutStr + */ + protected String toValueLayoutStr(TypeMirror typeMirror) { + return switch (typeMirror.getKind()) { + case BOOLEAN -> "ValueLayout.JAVA_BOOLEAN"; + case BYTE -> "ValueLayout.JAVA_BYTE"; + case SHORT -> "ValueLayout.JAVA_SHORT"; + case INT -> "ValueLayout.JAVA_INT"; + case LONG -> "ValueLayout.JAVA_LONG"; + case CHAR -> "ValueLayout.JAVA_CHAR"; + case FLOAT -> "ValueLayout.JAVA_FLOAT"; + case DOUBLE -> "ValueLayout.JAVA_DOUBLE"; + default -> { + if (canConvertToAddress(typeMirror)) yield "ValueLayout.ADDRESS"; + throw invalidType(typeMirror); + } + }; + } + + /** + * Find wrapper method + * + * @param typeMirror the type + * @return the wrapper method + */ + protected Optional findWrapperMethod(TypeMirror typeMirror) { + return ElementFilter.methodsIn(processingEnv.getTypeUtils() + .asElement(typeMirror) + .getEnclosedElements()) + .stream() + .filter(method -> method.getAnnotation(Upcall.Wrapper.class) != null) + .filter(method -> { + final var list = method.getParameters(); + return list.size() == 1 && isMemorySegment(list.getFirst().asType()); + }) + .findFirst(); + } + @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.RELEASE_22; diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index d45e63b..44135ee 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -20,7 +20,6 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; /** * Util @@ -178,77 +177,4 @@ public static boolean isStringArray(TypeMirror typeMirror) { public static TypeMirror getArrayComponentType(TypeMirror typeMirror) { return ((ArrayType) typeMirror).getComponentType(); } - - /** - * canConvertToAddress - * - * @param typeMirror typeMirror - * @return canConvertToAddress - */ - public static boolean canConvertToAddress(TypeMirror typeMirror) { - return switch (typeMirror.getKind()) { - case ARRAY -> isPrimitiveArray(typeMirror) || isBooleanArray(typeMirror) || isStringArray(typeMirror); - case DECLARED -> isMemorySegment(typeMirror) || isString(typeMirror); - default -> false; - }; - } - - /** - * isValueType - * - * @param typeMirror typeMirror - * @return isValueType - */ - public static boolean isValueType(TypeMirror typeMirror) { - if (typeMirror.getKind().isPrimitive()) { - return true; - } - return canConvertToAddress(typeMirror); - } - - /** - * toValueLayout - * - * @param typeMirror typeMirror - * @return toValueLayout - */ - public static ValueLayout toValueLayout(TypeMirror typeMirror) { - return switch (typeMirror.getKind()) { - case BOOLEAN -> ValueLayout.JAVA_BOOLEAN; - case BYTE -> ValueLayout.JAVA_BYTE; - case SHORT -> ValueLayout.JAVA_SHORT; - case INT -> ValueLayout.JAVA_INT; - case LONG -> ValueLayout.JAVA_LONG; - case CHAR -> ValueLayout.JAVA_CHAR; - case FLOAT -> ValueLayout.JAVA_FLOAT; - case DOUBLE -> ValueLayout.JAVA_DOUBLE; - default -> { - if (canConvertToAddress(typeMirror)) yield ValueLayout.ADDRESS; - throw invalidType(typeMirror); - } - }; - } - - /** - * toValueLayoutStr - * - * @param typeMirror typeMirror - * @return toValueLayoutStr - */ - public static String toValueLayoutStr(TypeMirror typeMirror) { - return switch (typeMirror.getKind()) { - case BOOLEAN -> "ValueLayout.JAVA_BOOLEAN"; - case BYTE -> "ValueLayout.JAVA_BYTE"; - case SHORT -> "ValueLayout.JAVA_SHORT"; - case INT -> "ValueLayout.JAVA_INT"; - case LONG -> "ValueLayout.JAVA_LONG"; - case CHAR -> "ValueLayout.JAVA_CHAR"; - case FLOAT -> "ValueLayout.JAVA_FLOAT"; - case DOUBLE -> "ValueLayout.JAVA_DOUBLE"; - default -> { - if (canConvertToAddress(typeMirror)) yield "ValueLayout.ADDRESS"; - throw invalidType(typeMirror); - } - }; - } } From ae3e5aaff8636b0c077b1b1b92663910a3ce5a12 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 1 Jan 2024 12:16:26 +0800 Subject: [PATCH 6/7] Update license year --- LICENSE | 2 +- demo/src/main/java/overrun/marshal/test/CDowncallTest.java | 2 +- demo/src/main/java/overrun/marshal/test/CStructTest.java | 2 +- demo/src/main/java/overrun/marshal/test/CVector3.java | 2 +- demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java | 2 +- src/main/java/module-info.java | 2 +- src/main/java/overrun/marshal/Checks.java | 2 +- src/main/java/overrun/marshal/Downcall.java | 2 +- src/main/java/overrun/marshal/DowncallProcessor.java | 2 +- src/main/java/overrun/marshal/Sized.java | 2 +- src/main/java/overrun/marshal/SizedSeg.java | 2 +- src/main/java/overrun/marshal/Skip.java | 2 +- src/main/java/overrun/marshal/StrCharset.java | 2 +- src/main/java/overrun/marshal/StructProcessor.java | 2 +- src/main/java/overrun/marshal/Upcall.java | 2 +- src/main/java/overrun/marshal/gen/Annotatable.java | 2 +- src/main/java/overrun/marshal/gen/ClassSpec.java | 2 +- src/main/java/overrun/marshal/gen/MethodSpec.java | 2 +- src/main/java/overrun/marshal/gen/ParameterSpec.java | 2 +- src/main/java/overrun/marshal/gen/SourceFile.java | 2 +- src/main/java/overrun/marshal/gen/Spec.java | 2 +- src/main/java/overrun/marshal/gen/VariableStatement.java | 2 +- src/main/java/overrun/marshal/internal/Processor.java | 2 +- src/main/java/overrun/marshal/internal/Util.java | 2 +- src/main/java/overrun/marshal/package-info.java | 2 +- src/main/java/overrun/marshal/struct/Const.java | 2 +- src/main/java/overrun/marshal/struct/IStruct.java | 2 +- src/main/java/overrun/marshal/struct/Padding.java | 2 +- src/main/java/overrun/marshal/struct/Struct.java | 2 +- src/main/java/overrun/marshal/struct/package-info.java | 2 +- 30 files changed, 30 insertions(+), 30 deletions(-) diff --git a/LICENSE b/LICENSE index 5fc79d7..1ff3f04 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Overrun Organization +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 diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 25a437c..066ebbf 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java index 3ea9708..c13aeda 100644 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/demo/src/main/java/overrun/marshal/test/CVector3.java b/demo/src/main/java/overrun/marshal/test/CVector3.java index e4bb971..c3a7134 100644 --- a/demo/src/main/java/overrun/marshal/test/CVector3.java +++ b/demo/src/main/java/overrun/marshal/test/CVector3.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java index 5a598e7..1b3e57d 100644 --- a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java +++ b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4b0bb75..ccf7a7c 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/Checks.java b/src/main/java/overrun/marshal/Checks.java index cd6ab00..051cd6d 100644 --- a/src/main/java/overrun/marshal/Checks.java +++ b/src/main/java/overrun/marshal/Checks.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index e0d6144..e2e0134 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index 7eda6aa..36575a9 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/Sized.java b/src/main/java/overrun/marshal/Sized.java index 19b2e06..74898a1 100644 --- a/src/main/java/overrun/marshal/Sized.java +++ b/src/main/java/overrun/marshal/Sized.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/SizedSeg.java b/src/main/java/overrun/marshal/SizedSeg.java index 1910bcd..4170034 100644 --- a/src/main/java/overrun/marshal/SizedSeg.java +++ b/src/main/java/overrun/marshal/SizedSeg.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/Skip.java b/src/main/java/overrun/marshal/Skip.java index 74d617a..1799b73 100644 --- a/src/main/java/overrun/marshal/Skip.java +++ b/src/main/java/overrun/marshal/Skip.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/StrCharset.java b/src/main/java/overrun/marshal/StrCharset.java index ee5e8f4..fe3b1b5 100644 --- a/src/main/java/overrun/marshal/StrCharset.java +++ b/src/main/java/overrun/marshal/StrCharset.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 3e548b8..296f969 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index b3e83b0..b2a9e82 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/gen/Annotatable.java b/src/main/java/overrun/marshal/gen/Annotatable.java index 9fb14da..34e88ff 100644 --- a/src/main/java/overrun/marshal/gen/Annotatable.java +++ b/src/main/java/overrun/marshal/gen/Annotatable.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/gen/ClassSpec.java b/src/main/java/overrun/marshal/gen/ClassSpec.java index 5780e21..c188418 100644 --- a/src/main/java/overrun/marshal/gen/ClassSpec.java +++ b/src/main/java/overrun/marshal/gen/ClassSpec.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/gen/MethodSpec.java b/src/main/java/overrun/marshal/gen/MethodSpec.java index e650cbb..ee2feeb 100644 --- a/src/main/java/overrun/marshal/gen/MethodSpec.java +++ b/src/main/java/overrun/marshal/gen/MethodSpec.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/gen/ParameterSpec.java b/src/main/java/overrun/marshal/gen/ParameterSpec.java index 7cb2b15..565ff85 100644 --- a/src/main/java/overrun/marshal/gen/ParameterSpec.java +++ b/src/main/java/overrun/marshal/gen/ParameterSpec.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/gen/SourceFile.java b/src/main/java/overrun/marshal/gen/SourceFile.java index 87a2f92..1133960 100644 --- a/src/main/java/overrun/marshal/gen/SourceFile.java +++ b/src/main/java/overrun/marshal/gen/SourceFile.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/gen/Spec.java b/src/main/java/overrun/marshal/gen/Spec.java index 75225d0..ed0d9cb 100644 --- a/src/main/java/overrun/marshal/gen/Spec.java +++ b/src/main/java/overrun/marshal/gen/Spec.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/gen/VariableStatement.java b/src/main/java/overrun/marshal/gen/VariableStatement.java index ad105e1..af223b3 100644 --- a/src/main/java/overrun/marshal/gen/VariableStatement.java +++ b/src/main/java/overrun/marshal/gen/VariableStatement.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java index ee66db3..4dd168f 100644 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ b/src/main/java/overrun/marshal/internal/Processor.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index 44135ee..6c70fd3 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/package-info.java b/src/main/java/overrun/marshal/package-info.java index a670d2e..a0cee5c 100644 --- a/src/main/java/overrun/marshal/package-info.java +++ b/src/main/java/overrun/marshal/package-info.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/struct/Const.java b/src/main/java/overrun/marshal/struct/Const.java index ead6528..b4c31fd 100644 --- a/src/main/java/overrun/marshal/struct/Const.java +++ b/src/main/java/overrun/marshal/struct/Const.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/struct/IStruct.java b/src/main/java/overrun/marshal/struct/IStruct.java index 8c9acad..18034a7 100644 --- a/src/main/java/overrun/marshal/struct/IStruct.java +++ b/src/main/java/overrun/marshal/struct/IStruct.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/struct/Padding.java b/src/main/java/overrun/marshal/struct/Padding.java index d965ef4..40687c9 100644 --- a/src/main/java/overrun/marshal/struct/Padding.java +++ b/src/main/java/overrun/marshal/struct/Padding.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/struct/Struct.java index 8fee744..4c926dd 100644 --- a/src/main/java/overrun/marshal/struct/Struct.java +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 diff --git a/src/main/java/overrun/marshal/struct/package-info.java b/src/main/java/overrun/marshal/struct/package-info.java index 0167995..081d628 100644 --- a/src/main/java/overrun/marshal/struct/package-info.java +++ b/src/main/java/overrun/marshal/struct/package-info.java @@ -1,7 +1,7 @@ /* * MIT License * - * Copyright (c) 2023 Overrun Organization + * 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 From 2878858bf625649f7553f4ee94c33c9fb3a3cd17 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 1 Jan 2024 14:52:21 +0800 Subject: [PATCH 7/7] Add support to struct in struct and downcall --- .../overrun/marshal/test/CDowncallTest.java | 18 ++++- .../overrun/marshal/test/CStructTest.java | 5 ++ .../overrun/marshal/DowncallProcessor.java | 64 +++++++++++----- .../java/overrun/marshal/StructProcessor.java | 73 +++++++++++++------ src/main/java/overrun/marshal/Upcall.java | 1 - .../java/overrun/marshal/gen/InvokeSpec.java | 20 +++-- .../java/overrun/marshal/struct/IStruct.java | 11 +++ .../overrun/marshal/struct/StructRef.java | 40 ++++++++++ 8 files changed, 184 insertions(+), 48 deletions(-) create mode 100644 src/main/java/overrun/marshal/struct/StructRef.java diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 066ebbf..e58db5f 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -17,6 +17,7 @@ package overrun.marshal.test; import overrun.marshal.*; +import overrun.marshal.struct.StructRef; import java.lang.foreign.MemorySegment; @@ -63,10 +64,10 @@ interface CDowncallTest { @Access(AccessModifier.PRIVATE) int testWithPrivate(int i); - void testWithArray(MemorySegment arr); + void testWithArray(int i, MemorySegment arr); @Overload - void testWithArray(int[] arr); + void testWithArray(int i, int[] arr); void testWithOneRef(@Ref MemorySegment arr); @@ -144,6 +145,19 @@ interface CDowncallTest { @Overload("ntestReturnUpcall") GLFWErrorCallback testReturnUpcall(); + void testStruct(MemorySegment struct); + + @Overload + void testStruct(@StructRef("overrun.marshal.test.Vector3") Object struct); + + @Entrypoint("testReturnStruct") + @StructRef("overrun.marshal.test.StructTest") + MemorySegment ntestReturnStruct(); + + @Overload("ntestReturnStruct") + @StructRef("overrun.marshal.test.StructTest") + MemorySegment testReturnStruct(); + /** * This is a test that tests all features. * diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java index c13aeda..a5bde4d 100644 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -23,6 +23,7 @@ import overrun.marshal.struct.Const; import overrun.marshal.struct.Padding; import overrun.marshal.struct.Struct; +import overrun.marshal.struct.StructRef; import java.lang.foreign.MemorySegment; @@ -61,4 +62,8 @@ final class CStructTest { boolean[] boolArr; String[] strArr; GLFWErrorCallback upcall; + @StructRef("overrun.marshal.test.StructTest") + MemorySegment myStruct; + @StructRef("overrun.marshal.test.Vector3") + int anotherStruct; } diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index 36575a9..1ff2ece 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -18,9 +18,13 @@ import overrun.marshal.gen.*; import overrun.marshal.internal.Processor; +import overrun.marshal.struct.StructRef; import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.*; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; @@ -124,6 +128,8 @@ private void writeFile( final TypeMirror returnType = e.getReturnType(); final String methodName = e.getSimpleName().toString(); final Overload overload = e.getAnnotation(Overload.class); + final StructRef eStructRef = e.getAnnotation(StructRef.class); + final boolean eIsStruct = eStructRef != null; final String overloadValue; if (overload != null) { final String value = overload.value(); @@ -131,7 +137,9 @@ private void writeFile( } else { overloadValue = null; } - final String javaReturnType = toTargetType(returnType, overloadValue); + final String javaReturnType = (overloadValue != null && eIsStruct) ? + eStructRef.value() : + toTargetType(returnType, overloadValue); classSpec.addMethod(new MethodSpec(javaReturnType, methodName), methodSpec -> { final var parameters = e.getParameters(); @@ -139,13 +147,13 @@ private void writeFile( final boolean shouldInsertAllocator = !shouldInsertArena && parameters.stream().anyMatch(p -> { final TypeMirror t = p.asType(); return isArray(t) || isString(t); - // TODO: 2023/12/9 Struct }); final boolean notVoid = returnType.getKind() != TypeKind.VOID; final boolean shouldStoreResult = notVoid && (isArray(returnType) || isString(returnType) || isUpcall(returnType) || + eIsStruct || parameters.stream().anyMatch(p -> p.getAnnotation(Ref.class) != null)); final Access access = e.getAnnotation(Access.class); final Critical critical = e.getAnnotation(Critical.class); @@ -192,13 +200,8 @@ private void writeFile( ); } - final Function parameterTypeFunction; - if (custom != null) { - parameterTypeFunction = TypeMirror::toString; - } else { - parameterTypeFunction = t -> toTargetType(t, overloadValue); - } - parameters.forEach(p -> methodSpec.addParameter(new ParameterSpec(parameterTypeFunction.apply(p.asType()), p.getSimpleName().toString()) + final var parameterTypeFunction = parameterTypeFunction(custom, overloadValue); + parameters.forEach(p -> methodSpec.addParameter(new ParameterSpec(parameterTypeFunction.apply(p), p.getSimpleName().toString()) .also(parameterSpec -> { addAnnotationValue(parameterSpec, p.getAnnotation(SizedSeg.class), SizedSeg.class, SizedSeg::value); addAnnotationValue(parameterSpec, p.getAnnotation(Sized.class), Sized.class, Sized::value); @@ -240,7 +243,10 @@ private void writeFile( .collect(Collectors.toList())); if (notVoid) { final Spec castSpec = Spec.cast(javaReturnType, invokeExact); - if (eSizedSeg != null || eSized != null) { + if (eIsStruct) { + targetStatement.addStatement(Spec.returnStatement(new InvokeSpec(Spec.parentheses(castSpec), "reinterpret") + .addArgument(new InvokeSpec(Spec.accessSpec(eStructRef.value(), "LAYOUT"), "byteSize")))); + } else if (eSizedSeg != null || eSized != null) { targetStatement.addStatement(Spec.returnStatement(new InvokeSpec(Spec.parentheses(castSpec), "reinterpret") .addArgument(getConstExp(eSizedSeg != null ? eSizedSeg.value() : eSized.value())))); } else { @@ -312,8 +318,10 @@ private void writeFile( final TypeMirror pType = p.asType(); final TypeKind pTypeKind = pType.getKind(); final String nameString = p.getSimpleName().toString(); - if (pTypeKind.isPrimitive()) { - invocation.addArgument(getConstExp(nameString)); + if (p.getAnnotation(StructRef.class) != null) { + invocation.addArgument(new InvokeSpec(nameString, "segment")); + } else if (pTypeKind.isPrimitive()) { + invocation.addArgument(nameString); } else { switch (pTypeKind) { case DECLARED -> { @@ -330,7 +338,6 @@ private void writeFile( .addArgument(allocatorParamName)); } else { invocation.addArgument(nameString); - // TODO: 2023/12/10 Struct } } case ARRAY -> { @@ -421,7 +428,10 @@ private void writeFile( if (shouldStoreResult) { // converting return value Spec finalInvocation = Spec.literal("$_marshalResult"); - if (isArray(returnType)) { + if (eIsStruct) { + finalInvocation = new ConstructSpec(eStructRef.value()) + .addArgument(finalInvocation); + } else if (isArray(returnType)) { final TypeMirror arrayComponentType = getArrayComponentType(returnType); if (eSized != null) { finalInvocation = new InvokeSpec(finalInvocation, "reinterpret") @@ -480,6 +490,22 @@ private void writeFile( } } + private Function parameterTypeFunction(Custom custom, String overloadValue) { + if (custom != null) { + return p -> p.asType().toString(); + } + return p -> { + final StructRef structRef = p.getAnnotation(StructRef.class); + if (structRef != null) { + if (overloadValue != null) { + return structRef.value(); + } + return MemorySegment.class.getSimpleName(); + } + return toTargetType(p.asType(), overloadValue); + }; + } + private void addFields(List fields, ClassSpec classSpec) { fields.forEach(e -> { final Object constantValue = e.getConstantValue(); @@ -583,7 +609,12 @@ private void addMethodHandles(TypeElement type, List methods, if (defaulted) { invokeSpec.addArgument("null"); } - })).setStatic(true).setFinal(true), variableStatement -> { + })) + .setStatic(true) + .setFinal(true) + .setDocument(""" + The method handle of {@code %s}.\ + """.formatted(v.getSimpleName())), variableStatement -> { final Access access = v.getAnnotation(Access.class); if (access != null) { variableStatement.setAccessModifier(access.value()); @@ -632,7 +663,6 @@ private String toTargetType(TypeMirror typeMirror, String overload) { if (isUpcall(typeMirror)) { yield overload == null ? MemorySegment.class.getSimpleName() : typeMirror.toString(); } - // TODO: 2023/12/2 Add support to struct throw invalidType(typeMirror); } case ARRAY -> { diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 296f969..d17dfc8 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -18,10 +18,7 @@ import overrun.marshal.gen.*; import overrun.marshal.internal.Processor; -import overrun.marshal.struct.Const; -import overrun.marshal.struct.IStruct; -import overrun.marshal.struct.Padding; -import overrun.marshal.struct.Struct; +import overrun.marshal.struct.*; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.TypeElement; @@ -124,7 +121,9 @@ private void writeFile( invokeSpec.addArgument(new InvokeSpec(MemoryLayout.class, "paddingLayout") .addArgument(getConstExp(padding.value()))); } else if (isValueType(eType)) { - InvokeSpec member = new InvokeSpec(toValueLayoutStr(eType), "withName") + InvokeSpec member = new InvokeSpec(e.getAnnotation(StructRef.class) != null ? + "ValueLayout.ADDRESS" : + toValueLayoutStr(eType), "withName") .addArgument(getConstExp(e.getSimpleName().toString())); if (isArray(eType)) { final Sized sized = e.getAnnotation(Sized.class); @@ -146,7 +145,6 @@ private void writeFile( invokeSpec.addArgument(member); } else { printError("Unsupported field: " + eType + ' ' + e.getSimpleName()); - // TODO: 2023/12/16 squid233: struct, upcall } }))) .setStatic(true) @@ -155,8 +153,11 @@ private void writeFile( final StringBuilder sb = new StringBuilder(512); sb.append(" The layout of this struct.\n
{@code\n struct ").append(simpleClassName).append(" {\n");
                 forEach(fields, false, e -> {
-                    final String string = e.asType().toString();
-                    final String substring = string.substring(string.lastIndexOf('.') + 1);
+                    final String eTypeString = e.asType().toString();
+                    final boolean endsWithArray = eTypeString.endsWith("[]");
+                    final String simplifiedString = simplify(endsWithArray ?
+                        eTypeString.substring(0, eTypeString.length() - 2) :
+                        eTypeString) + (endsWithArray ? "[]" : "");
                     final StrCharset strCharset = e.getAnnotation(StrCharset.class);
                     sb.append("     ");
                     if (strCharset != null) {
@@ -165,7 +166,10 @@ private void writeFile(
                     if (typeConst || e.getAnnotation(Const.class) != null) {
                         sb.append("final ");
                     }
-                    if (isMemorySegmentSimple(substring)) {
+                    final StructRef structRef = e.getAnnotation(StructRef.class);
+                    if (structRef != null) {
+                        sb.append("struct ").append(structRef.value());
+                    } else if (isMemorySegmentSimple(simplifiedString)) {
                         final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class);
                         sb.append("void");
                         if (sizedSeg != null) {
@@ -175,10 +179,10 @@ private void writeFile(
                         }
                     } else {
                         final Sized sized = e.getAnnotation(Sized.class);
-                        if (sized != null && substring.endsWith("[]")) {
-                            sb.append(substring, 0, substring.length() - 1).append(getConstExp(sized.value())).append(']');
+                        if (sized != null && simplifiedString.endsWith("[]")) {
+                            sb.append(simplifiedString, 0, simplifiedString.length() - 1).append(getConstExp(sized.value())).append(']');
                         } else {
-                            sb.append(substring);
+                            sb.append(simplifiedString);
                         }
                     }
                     sb.append(' ')
@@ -235,6 +239,19 @@ private void writeFile(
                             .addArgument("_PE_" + name)));
                 });
             });
+            classSpec.addMethod(new MethodSpec(null, simpleClassName), methodSpec -> {
+                methodSpec.setDocument("""
+                     Creates {@code %s} with the given segment. The count is auto-inferred.
+
+                     @param segment the segment\
+                    """.formatted(simpleClassName));
+                methodSpec.addParameter(MemorySegment.class, "segment");
+                methodSpec.addStatement(Spec.statement(InvokeSpec.invokeThis()
+                    .addArgument("segment")
+                    .addArgument(new InvokeSpec(IStruct.class.getCanonicalName(), "inferCount")
+                        .addArgument("LAYOUT")
+                        .addArgument("segment"))));
+            });
 
             // allocator
             classSpec.addMethod(new MethodSpec(simpleClassName, "create"), methodSpec -> {
@@ -294,13 +311,16 @@ private void writeFile(
                     final String eTypeString = eType.toString();
                     final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class);
                     final Sized sized = e.getAnnotation(Sized.class);
-                    final boolean canConvertToAddress = canConvertToAddress(eType);
+                    final StructRef structRef = e.getAnnotation(StructRef.class);
+                    final boolean isStruct = structRef != null;
+                    final boolean canConvertToAddress = isStruct || canConvertToAddress(eType);
                     final String returnType = canConvertToAddress ? MemorySegment.class.getSimpleName() : eTypeString;
+                    final String overloadReturnType = isStruct ? structRef.value() : simplify(eTypeString);
                     final String capitalized = capitalize(nameString);
                     final boolean isMemorySegment = isMemorySegment(eType);
                     final boolean isArray = isArray(eType);
-                    final boolean upcall = isUpcall(eType);
-                    final boolean generateN = canConvertToAddress && !isMemorySegment;
+                    final boolean isUpcall = isUpcall(eType);
+                    final boolean generateN = canConvertToAddress && (isStruct || !isMemorySegment);
 
                     // getter
                     final Spec castSpec = Spec.cast(returnType,
@@ -316,14 +336,17 @@ private void writeFile(
                                     Spec.operatorSpec("*",
                                         Spec.literal(getConstExp(sized.value())),
                                         new InvokeSpec(toValueLayoutStr(getArrayComponentType(eType)), "byteSize"))) :
-                            castSpec);
+                            (isStruct ?
+                                new InvokeSpec(Spec.parentheses(castSpec), "reinterpret")
+                                    .addArgument(new InvokeSpec(Spec.accessSpec(structRef.value(), "LAYOUT"), "byteSize")) :
+                                castSpec));
                     if (generateN) {
                         Spec storeResult = null;
                         final Spec returnValue = new InvokeSpec("this", "nget" + capitalized + "At")
                             .addArgument("index");
                         Spec finalReturnValue;
                         final boolean isString = isString(eType);
-                        if (isString || isArray) {
+                        if (isString || isArray || isUpcall || isStruct) {
                             storeResult = new VariableStatement(MemorySegment.class, "$_marshalResult", returnValue)
                                 .setAccessModifier(AccessModifier.PACKAGE_PRIVATE)
                                 .setFinal(true);
@@ -331,7 +354,10 @@ private void writeFile(
                         } else {
                             finalReturnValue = returnValue;
                         }
-                        if (isString) {
+                        if (isStruct) {
+                            finalReturnValue = new ConstructSpec(overloadReturnType)
+                                .addArgument(finalReturnValue);
+                        } else if (isString) {
                             final StrCharset strCharset = e.getAnnotation(StrCharset.class);
                             final InvokeSpec invokeSpec = new InvokeSpec(finalReturnValue, "getString")
                                 .addArgument("0");
@@ -357,7 +383,7 @@ private void writeFile(
                                 finalReturnValue = new InvokeSpec(finalReturnValue, "toArray")
                                     .addArgument(toValueLayoutStr(arrayComponentType));
                             }
-                        } else if (upcall) {
+                        } else if (isUpcall) {
                             // find wrap method
                             final var wrapMethod = findWrapperMethod(eType);
                             if (wrapMethod.isPresent()) {
@@ -376,7 +402,7 @@ private void writeFile(
                                 finalReturnValue,
                                 Spec.literal("null"));
                         }
-                        addGetterAt(classSpec, e, "get", simplify(eTypeString), storeResult, finalReturnValue);
+                        addGetterAt(classSpec, e, "get", overloadReturnType, storeResult, finalReturnValue);
                     }
 
                     // setter
@@ -417,17 +443,18 @@ private void writeFile(
                                         .addArgument(toValueLayoutStr(getArrayComponentType(eType)))
                                         .addArgument(nameString));
                                 }
-                            } else if (isUpcall(eType)) {
+                            } else if (isUpcall) {
                                 invokeSpec.addArgument(new InvokeSpec(nameString, "stub")
                                     .addArgument(convertParamArena(nameString)));
+                            } else if (isStruct) {
+                                invokeSpec.addArgument(new InvokeSpec(nameString, "segment"));
                             } else {
                                 invokeSpec.addArgument(nameString);
                             }
-                            addSetterAt(classSpec, e, "set", simplify(eTypeString), Spec.statement(invokeSpec));
+                            addSetterAt(classSpec, e, "set", overloadReturnType, Spec.statement(invokeSpec));
                         }
                     }
                 }
-                // TODO: 2023/12/17 squid233: struct
             });
 
             // override
diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java
index b2a9e82..43495dd 100644
--- a/src/main/java/overrun/marshal/Upcall.java
+++ b/src/main/java/overrun/marshal/Upcall.java
@@ -120,7 +120,6 @@ static  Type create(Class tClass) {
         return new Type<>(tClass);
     }
 
-
     /**
      * Marks a method as an upcall stub provider.
      *
diff --git a/src/main/java/overrun/marshal/gen/InvokeSpec.java b/src/main/java/overrun/marshal/gen/InvokeSpec.java
index a2e1ffb..5a76c3a 100644
--- a/src/main/java/overrun/marshal/gen/InvokeSpec.java
+++ b/src/main/java/overrun/marshal/gen/InvokeSpec.java
@@ -1,7 +1,7 @@
 /*
  * MIT License
  *
- * Copyright (c) 2023 Overrun Organization
+ * 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
@@ -63,6 +63,13 @@ public InvokeSpec(String object, String method) {
         this(Spec.literal(object), method);
     }
 
+    /**
+     * {@return invoke this}
+     */
+    public static InvokeSpec invokeThis() {
+        return new InvokeSpec((Spec) null, "this");
+    }
+
     /**
      * Add argument
      *
@@ -109,11 +116,14 @@ public InvokeSpec also(Consumer consumer) {
     @Override
     public void append(StringBuilder builder, int indent) {
         final String indentString = Spec.indentString(indent + 4);
-        object.append(builder, indent);
-        if (object instanceof InvokeSpec) {
-            builder.append('\n').append(indentString);
+        if (object != null) {
+            object.append(builder, indent);
+            if (object instanceof InvokeSpec) {
+                builder.append('\n').append(indentString);
+            }
+            builder.append('.');
         }
-        builder.append('.').append(method).append('(');
+        builder.append(method).append('(');
         for (int i = 0, size = arguments.size(); i < size; i++) {
             Spec spec = arguments.get(i);
             if (i != 0) {
diff --git a/src/main/java/overrun/marshal/struct/IStruct.java b/src/main/java/overrun/marshal/struct/IStruct.java
index 18034a7..7371bb3 100644
--- a/src/main/java/overrun/marshal/struct/IStruct.java
+++ b/src/main/java/overrun/marshal/struct/IStruct.java
@@ -40,4 +40,15 @@ public interface IStruct {
      * {@return the element count of this struct}
      */
     long elementCount();
+
+    /**
+     * Infers how many this struct is there in the given segment.
+     *
+     * @param layout  the struct layout
+     * @param segment the segment
+     * @return the count
+     */
+    static long inferCount(StructLayout layout, MemorySegment segment) {
+        return segment.byteSize() / layout.byteSize();
+    }
 }
diff --git a/src/main/java/overrun/marshal/struct/StructRef.java b/src/main/java/overrun/marshal/struct/StructRef.java
new file mode 100644
index 0000000..5e189c0
--- /dev/null
+++ b/src/main/java/overrun/marshal/struct/StructRef.java
@@ -0,0 +1,40 @@
+/*
+ * 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.struct;
+
+import java.lang.annotation.*;
+
+/**
+ * Marks a field as a reference of a struct.
+ * 

Example

+ *
{@code
+ * @StructRef("org.example.Vector3")
+ * int vec;
+ * }
+ * + * @author squid233 + * @since 0.1.0 + */ +@Documented +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.SOURCE) +public @interface StructRef { + /** + * {@return the full class name of the target struct} + */ + String value(); +}