From 23e6ca20ad3c9e6a3db82cba4598d75705c4077c Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:14:06 +0800 Subject: [PATCH 01/28] New generator --- .../overrun/marshal/DowncallProcessor.java | 11 +- .../java/overrun/marshal/gen/InvokeSpec.java | 1 + .../overrun/marshal/gen2/ArrayTypeData.java | 27 ++ .../marshal/gen2/DeclaredTypeData.java | 32 +++ .../overrun/marshal/gen2/DowncallData.java | 245 ++++++++++++++++++ .../java/overrun/marshal/gen2/FieldData.java | 43 +++ .../java/overrun/marshal/gen2/ImportData.java | 68 +++++ .../marshal/gen2/PrimitiveTypeData.java | 31 +++ .../java/overrun/marshal/gen2/TypeData.java | 75 ++++++ .../java/overrun/marshal/gen2/TypeUse.java | 35 +++ .../java/overrun/marshal/internal/Util.java | 72 ++++- 11 files changed, 634 insertions(+), 6 deletions(-) create mode 100644 src/main/java/overrun/marshal/gen2/ArrayTypeData.java create mode 100644 src/main/java/overrun/marshal/gen2/DeclaredTypeData.java create mode 100644 src/main/java/overrun/marshal/gen2/DowncallData.java create mode 100644 src/main/java/overrun/marshal/gen2/FieldData.java create mode 100644 src/main/java/overrun/marshal/gen2/ImportData.java create mode 100644 src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java create mode 100644 src/main/java/overrun/marshal/gen2/TypeData.java create mode 100644 src/main/java/overrun/marshal/gen2/TypeUse.java diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index eb3d282..d41b98a 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -17,6 +17,7 @@ package overrun.marshal; import overrun.marshal.gen.*; +import overrun.marshal.gen2.DowncallData; import overrun.marshal.internal.Processor; import overrun.marshal.struct.ByValue; import overrun.marshal.struct.StructRef; @@ -67,10 +68,16 @@ public boolean process(Set annotations, RoundEnvironment } private void processClasses(RoundEnvironment roundEnv) { + // TODO: 2024/1/7 squid233: rewrite + final boolean debug = true; ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Downcall.class)).forEach(e -> { - final var enclosed = e.getEnclosedElements(); try { - writeFile(e, ElementFilter.fieldsIn(enclosed), ElementFilter.methodsIn(enclosed)); + if (debug) { + new DowncallData(processingEnv).generate(e); + } else { + final var enclosed = e.getEnclosedElements(); + writeFile(e, ElementFilter.fieldsIn(enclosed), ElementFilter.methodsIn(enclosed)); + } } catch (IOException ex) { printStackTrace(ex); } diff --git a/src/main/java/overrun/marshal/gen/InvokeSpec.java b/src/main/java/overrun/marshal/gen/InvokeSpec.java index 5a76c3a..625f5c5 100644 --- a/src/main/java/overrun/marshal/gen/InvokeSpec.java +++ b/src/main/java/overrun/marshal/gen/InvokeSpec.java @@ -38,6 +38,7 @@ public final class InvokeSpec implements Spec { * @param object the caller object * @param method the method */ + @Deprecated(since = "0.1.0") public InvokeSpec(Class object, String method) { this(Spec.simpleClassName(object), method); } diff --git a/src/main/java/overrun/marshal/gen2/ArrayTypeData.java b/src/main/java/overrun/marshal/gen2/ArrayTypeData.java new file mode 100644 index 0000000..c816852 --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/ArrayTypeData.java @@ -0,0 +1,27 @@ +/* + * 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.gen2; + +/** + * Holds array type name + * + * @param componentType the component type + * @author squid233 + * @since 0.1.0 + */ +public record ArrayTypeData(TypeData componentType) implements TypeData { +} diff --git a/src/main/java/overrun/marshal/gen2/DeclaredTypeData.java b/src/main/java/overrun/marshal/gen2/DeclaredTypeData.java new file mode 100644 index 0000000..953df14 --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/DeclaredTypeData.java @@ -0,0 +1,32 @@ +/* + * 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.gen2; + +/** + * Holds declared type package and name + * + * @param packageName the package name + * @param name the name + * @author squid233 + * @since 0.1.0 + */ +public record DeclaredTypeData(String packageName, String name) implements TypeData { + @Override + public String toString() { + return packageName() + "." + name(); + } +} diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java new file mode 100644 index 0000000..574fdb0 --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -0,0 +1,245 @@ +/* + * 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.gen2; + +import overrun.marshal.AccessModifier; +import overrun.marshal.Downcall; +import overrun.marshal.Loader; +import overrun.marshal.gen.*; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; +import javax.tools.JavaFileObject; +import java.io.IOException; +import java.io.PrintWriter; +import java.lang.foreign.Arena; +import java.lang.foreign.Linker; +import java.lang.foreign.SymbolLookup; +import java.util.*; + +import static overrun.marshal.internal.Util.*; + +/** + * Holds downcall fields and functions + * + * @author squid233 + * @since 0.1.0 + */ +public final class DowncallData { + private final ProcessingEnvironment processingEnv; + private final ImportData importData = new ImportData(new ArrayList<>()); + private final List fields = new ArrayList<>(); + private String document = null; + private boolean nonFinal = false; + + /** + * Construct + * + * @param processingEnv the processing environment + */ + public DowncallData(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + } + + private void processDowncallType(TypeElement typeElement) { + final var enclosedElements = typeElement.getEnclosedElements(); + + // annotations + final Downcall downcall = typeElement.getAnnotation(Downcall.class); + + document = getDocComment(processingEnv, typeElement); + nonFinal = downcall.nonFinal(); + + // process fields + ElementFilter.fieldsIn(enclosedElements).forEach(element -> { + final Object constantValue = element.getConstantValue(); + if (constantValue == null) { + return; + } + fields.add(new FieldData( + getDocComment(processingEnv, element), + AccessModifier.PUBLIC, + true, + true, + TypeData.detectType(processingEnv, element.asType()), + element.getSimpleName().toString(), + _ -> Spec.literal(getConstExp(processingEnv, constantValue)) + )); + }); + + final var methods = ElementFilter.methodsIn(enclosedElements); + if (!methods.isEmpty()) { + // collect method handles + + // add linker and lookup + fields.add(new FieldData( + null, + AccessModifier.PRIVATE, + true, + true, + TypeData.fromClass(Linker.class), + "_LINKER", // TODO: 2024/1/8 squid233: conflict + importData -> new InvokeSpec(importData.simplifyOrImport(Linker.class), "nativeLinker") + )); + + addLookup(typeElement); + + // add method handles + } + } + + /** + * Generates the file + * + * @param typeElement the type element + * @throws IOException if an I/O error occurred + */ + public void generate(TypeElement typeElement) throws IOException { + final Downcall downcall = typeElement.getAnnotation(Downcall.class); + final DeclaredTypeData declaredTypeDataOfTypeElement = (DeclaredTypeData) TypeData.detectType(processingEnv, typeElement.asType()); + final String simpleClassName; + if (downcall.name().isBlank()) { + final String string = declaredTypeDataOfTypeElement.name(); + if (string.startsWith("C")) { + simpleClassName = string.substring(1); + } else { + processingEnv.getMessager().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 the name in @Downcall. For example: @Downcall(name = "%1$s")""" + .formatted(string)); + return; + } + } else { + simpleClassName = downcall.name(); + } + + processDowncallType(typeElement); + + final SourceFile file = new SourceFile(declaredTypeDataOfTypeElement.packageName()); + file.addClass(simpleClassName, classSpec -> { + if (document != null) { + classSpec.setDocument(document); + } + classSpec.setFinal(!nonFinal); + + addFields(classSpec); + }); + importData.imports() + .stream() + .filter(declaredTypeData -> !"java.lang".equals(declaredTypeData.packageName())) + .map(DeclaredTypeData::toString) + .sorted((s1, s2) -> { + final boolean s1Java = s1.startsWith("java."); + final boolean s2Java = s2.startsWith("java."); + if (s1Java && !s2Java) { + return -1; + } + if (!s1Java && s2Java) { + return 1; + } + return s1.compareTo(s2); + }) + .forEach(file::addImport); + + final JavaFileObject sourceFile = processingEnv.getFiler() + .createSourceFile(declaredTypeDataOfTypeElement.packageName() + "." + simpleClassName); + try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) { + file.write(out); + } + } + + private void addLookup(TypeElement typeElement) { + final Downcall downcall = typeElement.getAnnotation(Downcall.class); + final String loader = typeElement.getAnnotationMirrors().stream() + .filter(m -> Downcall.class.getCanonicalName().equals(m.getAnnotationType().toString())) + .findFirst() + .orElseThrow() + .getElementValues().entrySet().stream() + .filter(e -> "loader()".equals(e.getKey().toString())) + .findFirst() + .map(e -> e.getValue().getValue().toString()) + .orElse(null); + final String libname = downcall.libname(); + final String libnameConstExp = getConstExp(processingEnv, libname); + final TypeElement loaderTypeElement; + final Optional annotatedMethod; + if (loader == null) { + loaderTypeElement = null; + annotatedMethod = Optional.empty(); + } else { + loaderTypeElement = processingEnv.getElementUtils().getTypeElement(loader); + annotatedMethod = findAnnotatedMethod(loaderTypeElement, + Loader.class, + executableElement -> { + if (isSameClass(executableElement.getReturnType(), SymbolLookup.class)) { + final var parameters = executableElement.getParameters(); + return parameters.size() == 1 && isString(parameters.getFirst().asType()); + } + return false; + }); + if (annotatedMethod.isEmpty()) { + processingEnv.getMessager().printError(""" + Couldn't find loader method in %s while %s required. Please mark it with @Loader""" + .formatted(loader, typeElement)); + return; + } + } + fields.add(new FieldData( + null, + AccessModifier.PRIVATE, + true, + true, + TypeData.fromClass(SymbolLookup.class), + "_LOOKUP", + loader == null ? + importData -> new InvokeSpec(importData.simplifyOrImport(SymbolLookup.class), "libraryLookup") + .addArgument(libnameConstExp) + .addArgument(new InvokeSpec(importData.simplifyOrImport(Arena.class), "global")) : + importData -> new InvokeSpec(importData.simplifyOrImport( + TypeData.detectType(processingEnv, loaderTypeElement.asType()) + ), annotatedMethod.get().getSimpleName().toString()) + .addArgument(libnameConstExp) + )); + } + + private void addFields(ClassSpec spec) { + fields.forEach(fieldData -> { + final TypeData type = fieldData.type(); + spec.addField(new VariableStatement( + importData.simplifyOrImport(type), + fieldData.name(), + fieldData.value().apply(importData) + ).setDocument(fieldData.document()) + .setAccessModifier(fieldData.accessModifier()) + .setStatic(fieldData.staticField()) + .setFinal(fieldData.finalField())); + }); + } + + private String getDocComment(ProcessingEnvironment env, Element e) { + return env.getElementUtils().getDocComment(e); + } + + private String getConstExp(ProcessingEnvironment env, Object v) { + return env.getElementUtils().getConstantExpression(v); + } +} diff --git a/src/main/java/overrun/marshal/gen2/FieldData.java b/src/main/java/overrun/marshal/gen2/FieldData.java new file mode 100644 index 0000000..7bee21f --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/FieldData.java @@ -0,0 +1,43 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen2; + +import overrun.marshal.AccessModifier; + +/** + * Holds field + * + * @param document the document + * @param accessModifier the access modifier + * @param staticField the static field + * @param finalField the final field + * @param type the type + * @param name the name + * @param value the value + * @author squid233 + * @since 0.1.0 + */ +public record FieldData( + String document, + AccessModifier accessModifier, + boolean staticField, + boolean finalField, + TypeData type, + String name, + TypeUse value +) { +} diff --git a/src/main/java/overrun/marshal/gen2/ImportData.java b/src/main/java/overrun/marshal/gen2/ImportData.java new file mode 100644 index 0000000..f37e96d --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/ImportData.java @@ -0,0 +1,68 @@ +/* + * 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.gen2; + +import java.util.List; + +/** + * Holds imports + * + * @param imports the imports + * @author squid233 + * @since 0.1.0 + */ +public record ImportData(List imports) { + private boolean isImported(TypeData typeData) { + return typeData instanceof DeclaredTypeData declaredTypeData && + imports.stream() + .map(DeclaredTypeData::name) + .anyMatch(name -> declaredTypeData.name().equals(name)); + } + + private boolean addImport(TypeData typeData) { + return switch (typeData) { + case ArrayTypeData arrayTypeData -> addImport(arrayTypeData.componentType()); + case DeclaredTypeData declaredTypeData when !isImported(typeData) -> imports().add(declaredTypeData); + default -> false; + }; + } + + /** + * Simplifies type name or import + * + * @param typeData the type data + * @return the name + */ + public String simplifyOrImport(TypeData typeData) { + return switch (typeData) { + case ArrayTypeData arrayTypeData -> simplifyOrImport(arrayTypeData) + "[]"; + case DeclaredTypeData declaredTypeData when (isImported(typeData) || addImport(typeData)) -> + declaredTypeData.name(); + default -> typeData.toString(); + }; + } + + /** + * Simplifies type name or import + * + * @param aClass the class + * @return the name + */ + public String simplifyOrImport(Class aClass) { + return simplifyOrImport(TypeData.fromClass(aClass)); + } +} diff --git a/src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java b/src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java new file mode 100644 index 0000000..bba70f9 --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java @@ -0,0 +1,31 @@ +/* + * 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.gen2; + +/** + * Holds primitive type name + * + * @param name the name + * @author squid233 + * @since 0.1.0 + */ +public record PrimitiveTypeData(String name) implements TypeData { + @Override + public String toString() { + return name(); + } +} diff --git a/src/main/java/overrun/marshal/gen2/TypeData.java b/src/main/java/overrun/marshal/gen2/TypeData.java new file mode 100644 index 0000000..58acfb7 --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/TypeData.java @@ -0,0 +1,75 @@ +/* + * 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.gen2; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +/** + * Holds type name + * + * @author squid233 + * @since 0.1.0 + */ +public sealed interface TypeData permits ArrayTypeData, DeclaredTypeData, PrimitiveTypeData { + /** + * Detects type + * + * @param env the processing environment + * @param type the type + * @return the type data + */ + static TypeData detectType(ProcessingEnvironment env, TypeMirror type) { + final TypeKind typeKind = type.getKind(); + if (typeKind.isPrimitive()) { + return new PrimitiveTypeData(type.toString()); + } + if (typeKind == TypeKind.ARRAY && + type instanceof ArrayType arrayType) { + return new ArrayTypeData(detectType(env, arrayType.getComponentType())); + } + if (typeKind == TypeKind.DECLARED && + env.getTypeUtils().asElement(type) instanceof TypeElement typeElement) { + final String qualifiedName = typeElement.getQualifiedName().toString(); + final String simpleName = typeElement.getSimpleName().toString(); + return new DeclaredTypeData(qualifiedName.substring(0, qualifiedName.lastIndexOf(simpleName) - 1), simpleName); + } + throw new IllegalArgumentException("Unknown type: " + type); + } + + /** + * Create type data from class + * + * @param aClass the class + * @return the type data + */ + static TypeData fromClass(Class aClass) { + if (aClass.isPrimitive()) { + return new PrimitiveTypeData(aClass.getCanonicalName()); + } + if (aClass.isArray()) { + return new ArrayTypeData(fromClass(aClass.arrayType())); + } + return new DeclaredTypeData(aClass.getPackageName(), aClass.getSimpleName()); + } + + @Override + String toString(); +} diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java new file mode 100644 index 0000000..20b668a --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/TypeUse.java @@ -0,0 +1,35 @@ +/* + * 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.gen2; + +import overrun.marshal.gen.Spec; + +/** + * Holds spec with type data + * + * @author squid233 + * @since 0.1.0 + */ +public interface TypeUse { + /** + * Applies the imports + * + * @param importData the import data + * @return the spec + */ + Spec apply(ImportData importData); +} diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index 611e3ae..fc23578 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -16,10 +16,17 @@ package overrun.marshal.internal; +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import java.lang.annotation.Annotation; import java.lang.foreign.MemorySegment; +import java.util.Optional; +import java.util.function.Predicate; /** * Util @@ -90,6 +97,7 @@ public static boolean isDeclared(TypeMirror typeMirror) { * @param clazzName clazzName * @return isMemorySegment */ + @Deprecated(since = "0.1.0") public static boolean isMemorySegment(String clazzName) { return MemorySegment.class.getCanonicalName().equals(clazzName); } @@ -101,8 +109,7 @@ public static boolean isMemorySegment(String clazzName) { * @return isMemorySegment */ public static boolean isMemorySegment(TypeMirror typeMirror) { - return isDeclared(typeMirror) && - isMemorySegment(typeMirror.toString()); + return isSameClass(typeMirror, MemorySegment.class); } /** @@ -111,6 +118,7 @@ public static boolean isMemorySegment(TypeMirror typeMirror) { * @param clazzName clazzName * @return isString */ + @Deprecated(since = "0.1.0") public static boolean isString(String clazzName) { return String.class.getCanonicalName().equals(clazzName); } @@ -122,8 +130,7 @@ public static boolean isString(String clazzName) { * @return isString */ public static boolean isString(TypeMirror typeMirror) { - return isDeclared(typeMirror) && - isString(typeMirror.toString()); + return isSameClass(typeMirror, String.class); } /** @@ -188,4 +195,61 @@ public static TypeMirror getArrayComponentType(TypeMirror typeMirror) { public static String insertUnderline(String builtinName, String nameString) { return builtinName.equals(nameString) ? "_" + builtinName : builtinName; } + + /** + * Finds annotated method + * + * @param typeElement the type element + * @param aClass the class + * @param predicate the filter + * @return the method + */ + public static Optional findAnnotatedMethod( + TypeElement typeElement, + Class aClass, + Predicate predicate + ) { + return ElementFilter.methodsIn(typeElement.getEnclosedElements()) + .stream() + .filter(executableElement -> executableElement.getAnnotation(aClass) != null) + .filter(predicate) + .findFirst(); + } + + /** + * Gets the type element from class + * + * @param env the processing environment + * @param aClass the class + * @return the type element + */ + public static TypeElement getTypeElementFromClass(ProcessingEnvironment env, Class aClass) { + return env.getElementUtils().getTypeElement(aClass.getCanonicalName()); + } + + /** + * {@return is A extends B} + * + * @param env the processing environment + * @param t1 A + * @param t2 B + */ + public static boolean isAExtendsB(ProcessingEnvironment env, TypeMirror t1, TypeMirror t2) { + return isDeclared(t1) && + isDeclared(t2) && + env.getTypeUtils().isAssignable(t1, t2); + } + + /** + * {@return is same class} + * + * @param typeMirror the type mirror + * @param aClass the class + */ + public static boolean isSameClass(TypeMirror typeMirror, Class aClass) { + return aClass.getCanonicalName().equals(typeMirror.toString()) && + ((typeMirror.getKind().isPrimitive() && aClass.isPrimitive()) || + (typeMirror.getKind() == TypeKind.ARRAY && aClass.isArray()) || + isDeclared(typeMirror)); + } } From 4805a6fa48938e5e4ae5613fc4726cf0f47a23eb Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:16:46 +0800 Subject: [PATCH 02/28] Rename .gen to .gen1 --- src/main/java/overrun/marshal/DowncallProcessor.java | 2 +- src/main/java/overrun/marshal/StructProcessor.java | 2 +- src/main/java/overrun/marshal/{gen => gen1}/Annotatable.java | 2 +- .../java/overrun/marshal/{gen => gen1}/AnnotationSpec.java | 4 ++-- src/main/java/overrun/marshal/{gen => gen1}/CatchClause.java | 4 ++-- src/main/java/overrun/marshal/{gen => gen1}/ClassSpec.java | 2 +- .../java/overrun/marshal/{gen => gen1}/ConstructSpec.java | 4 ++-- src/main/java/overrun/marshal/{gen => gen1}/ElseClause.java | 4 ++-- src/main/java/overrun/marshal/{gen => gen1}/IfStatement.java | 4 ++-- src/main/java/overrun/marshal/{gen => gen1}/InvokeSpec.java | 2 +- src/main/java/overrun/marshal/{gen => gen1}/LambdaSpec.java | 4 ++-- src/main/java/overrun/marshal/{gen => gen1}/MethodSpec.java | 2 +- .../java/overrun/marshal/{gen => gen1}/ParameterSpec.java | 2 +- src/main/java/overrun/marshal/{gen => gen1}/SourceFile.java | 2 +- src/main/java/overrun/marshal/{gen => gen1}/Spec.java | 2 +- .../java/overrun/marshal/{gen => gen1}/StatementBlock.java | 4 ++-- .../java/overrun/marshal/{gen => gen1}/TryCatchStatement.java | 4 ++-- .../java/overrun/marshal/{gen => gen1}/VariableStatement.java | 2 +- src/main/java/overrun/marshal/gen2/DowncallData.java | 2 +- src/main/java/overrun/marshal/gen2/TypeUse.java | 2 +- src/main/java/overrun/marshal/internal/Processor.java | 2 +- 21 files changed, 29 insertions(+), 29 deletions(-) rename src/main/java/overrun/marshal/{gen => gen1}/Annotatable.java (96%) rename src/main/java/overrun/marshal/{gen => gen1}/AnnotationSpec.java (96%) rename src/main/java/overrun/marshal/{gen => gen1}/CatchClause.java (95%) rename src/main/java/overrun/marshal/{gen => gen1}/ClassSpec.java (99%) rename src/main/java/overrun/marshal/{gen => gen1}/ConstructSpec.java (95%) rename src/main/java/overrun/marshal/{gen => gen1}/ElseClause.java (96%) rename src/main/java/overrun/marshal/{gen => gen1}/IfStatement.java (96%) rename src/main/java/overrun/marshal/{gen => gen1}/InvokeSpec.java (99%) rename src/main/java/overrun/marshal/{gen => gen1}/LambdaSpec.java (96%) rename src/main/java/overrun/marshal/{gen => gen1}/MethodSpec.java (99%) rename src/main/java/overrun/marshal/{gen => gen1}/ParameterSpec.java (98%) rename src/main/java/overrun/marshal/{gen => gen1}/SourceFile.java (99%) rename src/main/java/overrun/marshal/{gen => gen1}/Spec.java (99%) rename src/main/java/overrun/marshal/{gen => gen1}/StatementBlock.java (91%) rename src/main/java/overrun/marshal/{gen => gen1}/TryCatchStatement.java (96%) rename src/main/java/overrun/marshal/{gen => gen1}/VariableStatement.java (99%) diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index d41b98a..064a659 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -16,7 +16,7 @@ package overrun.marshal; -import overrun.marshal.gen.*; +import overrun.marshal.gen1.*; import overrun.marshal.gen2.DowncallData; import overrun.marshal.internal.Processor; import overrun.marshal.struct.ByValue; diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 4d48b98..c86fb04 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -16,7 +16,7 @@ package overrun.marshal; -import overrun.marshal.gen.*; +import overrun.marshal.gen1.*; import overrun.marshal.internal.Processor; import overrun.marshal.struct.*; diff --git a/src/main/java/overrun/marshal/gen/Annotatable.java b/src/main/java/overrun/marshal/gen1/Annotatable.java similarity index 96% rename from src/main/java/overrun/marshal/gen/Annotatable.java rename to src/main/java/overrun/marshal/gen1/Annotatable.java index 34e88ff..267230b 100644 --- a/src/main/java/overrun/marshal/gen/Annotatable.java +++ b/src/main/java/overrun/marshal/gen1/Annotatable.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; /** * Annotatable diff --git a/src/main/java/overrun/marshal/gen/AnnotationSpec.java b/src/main/java/overrun/marshal/gen1/AnnotationSpec.java similarity index 96% rename from src/main/java/overrun/marshal/gen/AnnotationSpec.java rename to src/main/java/overrun/marshal/gen1/AnnotationSpec.java index 2494a16..3d73cda 100644 --- a/src/main/java/overrun/marshal/gen/AnnotationSpec.java +++ b/src/main/java/overrun/marshal/gen1/AnnotationSpec.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.LinkedHashMap; import java.util.Map; diff --git a/src/main/java/overrun/marshal/gen/CatchClause.java b/src/main/java/overrun/marshal/gen1/CatchClause.java similarity index 95% rename from src/main/java/overrun/marshal/gen/CatchClause.java rename to src/main/java/overrun/marshal/gen1/CatchClause.java index 27b0d64..24a2fc1 100644 --- a/src/main/java/overrun/marshal/gen/CatchClause.java +++ b/src/main/java/overrun/marshal/gen1/CatchClause.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen/ClassSpec.java b/src/main/java/overrun/marshal/gen1/ClassSpec.java similarity index 99% rename from src/main/java/overrun/marshal/gen/ClassSpec.java rename to src/main/java/overrun/marshal/gen1/ClassSpec.java index c188418..1dc6dec 100644 --- a/src/main/java/overrun/marshal/gen/ClassSpec.java +++ b/src/main/java/overrun/marshal/gen1/ClassSpec.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import overrun.marshal.AccessModifier; diff --git a/src/main/java/overrun/marshal/gen/ConstructSpec.java b/src/main/java/overrun/marshal/gen1/ConstructSpec.java similarity index 95% rename from src/main/java/overrun/marshal/gen/ConstructSpec.java rename to src/main/java/overrun/marshal/gen1/ConstructSpec.java index 5e67eb1..2827c55 100644 --- a/src/main/java/overrun/marshal/gen/ConstructSpec.java +++ b/src/main/java/overrun/marshal/gen1/ConstructSpec.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen/ElseClause.java b/src/main/java/overrun/marshal/gen1/ElseClause.java similarity index 96% rename from src/main/java/overrun/marshal/gen/ElseClause.java rename to src/main/java/overrun/marshal/gen1/ElseClause.java index 5fcd8c9..6d49edb 100644 --- a/src/main/java/overrun/marshal/gen/ElseClause.java +++ b/src/main/java/overrun/marshal/gen1/ElseClause.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen/IfStatement.java b/src/main/java/overrun/marshal/gen1/IfStatement.java similarity index 96% rename from src/main/java/overrun/marshal/gen/IfStatement.java rename to src/main/java/overrun/marshal/gen1/IfStatement.java index 766f57c..741abdb 100644 --- a/src/main/java/overrun/marshal/gen/IfStatement.java +++ b/src/main/java/overrun/marshal/gen1/IfStatement.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen/InvokeSpec.java b/src/main/java/overrun/marshal/gen1/InvokeSpec.java similarity index 99% rename from src/main/java/overrun/marshal/gen/InvokeSpec.java rename to src/main/java/overrun/marshal/gen1/InvokeSpec.java index 625f5c5..d36bad4 100644 --- a/src/main/java/overrun/marshal/gen/InvokeSpec.java +++ b/src/main/java/overrun/marshal/gen1/InvokeSpec.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.Collection; diff --git a/src/main/java/overrun/marshal/gen/LambdaSpec.java b/src/main/java/overrun/marshal/gen1/LambdaSpec.java similarity index 96% rename from src/main/java/overrun/marshal/gen/LambdaSpec.java rename to src/main/java/overrun/marshal/gen1/LambdaSpec.java index 86b0c6b..4e42426 100644 --- a/src/main/java/overrun/marshal/gen/LambdaSpec.java +++ b/src/main/java/overrun/marshal/gen1/LambdaSpec.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen/MethodSpec.java b/src/main/java/overrun/marshal/gen1/MethodSpec.java similarity index 99% rename from src/main/java/overrun/marshal/gen/MethodSpec.java rename to src/main/java/overrun/marshal/gen1/MethodSpec.java index ee2feeb..09ad5c3 100644 --- a/src/main/java/overrun/marshal/gen/MethodSpec.java +++ b/src/main/java/overrun/marshal/gen1/MethodSpec.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import overrun.marshal.AccessModifier; diff --git a/src/main/java/overrun/marshal/gen/ParameterSpec.java b/src/main/java/overrun/marshal/gen1/ParameterSpec.java similarity index 98% rename from src/main/java/overrun/marshal/gen/ParameterSpec.java rename to src/main/java/overrun/marshal/gen1/ParameterSpec.java index 565ff85..ec81465 100644 --- a/src/main/java/overrun/marshal/gen/ParameterSpec.java +++ b/src/main/java/overrun/marshal/gen1/ParameterSpec.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen/SourceFile.java b/src/main/java/overrun/marshal/gen1/SourceFile.java similarity index 99% rename from src/main/java/overrun/marshal/gen/SourceFile.java rename to src/main/java/overrun/marshal/gen1/SourceFile.java index 1133960..40d8b9f 100644 --- a/src/main/java/overrun/marshal/gen/SourceFile.java +++ b/src/main/java/overrun/marshal/gen1/SourceFile.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.io.IOException; import java.io.Writer; diff --git a/src/main/java/overrun/marshal/gen/Spec.java b/src/main/java/overrun/marshal/gen1/Spec.java similarity index 99% rename from src/main/java/overrun/marshal/gen/Spec.java rename to src/main/java/overrun/marshal/gen1/Spec.java index ed0d9cb..01a8768 100644 --- a/src/main/java/overrun/marshal/gen/Spec.java +++ b/src/main/java/overrun/marshal/gen1/Spec.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.stream.Collectors; diff --git a/src/main/java/overrun/marshal/gen/StatementBlock.java b/src/main/java/overrun/marshal/gen1/StatementBlock.java similarity index 91% rename from src/main/java/overrun/marshal/gen/StatementBlock.java rename to src/main/java/overrun/marshal/gen1/StatementBlock.java index bd50101..519c420 100644 --- a/src/main/java/overrun/marshal/gen/StatementBlock.java +++ b/src/main/java/overrun/marshal/gen1/StatementBlock.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; /** * Statement block diff --git a/src/main/java/overrun/marshal/gen/TryCatchStatement.java b/src/main/java/overrun/marshal/gen1/TryCatchStatement.java similarity index 96% rename from src/main/java/overrun/marshal/gen/TryCatchStatement.java rename to src/main/java/overrun/marshal/gen1/TryCatchStatement.java index 6186ff3..5cf88db 100644 --- a/src/main/java/overrun/marshal/gen/TryCatchStatement.java +++ b/src/main/java/overrun/marshal/gen1/TryCatchStatement.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen/VariableStatement.java b/src/main/java/overrun/marshal/gen1/VariableStatement.java similarity index 99% rename from src/main/java/overrun/marshal/gen/VariableStatement.java rename to src/main/java/overrun/marshal/gen1/VariableStatement.java index af223b3..8977238 100644 --- a/src/main/java/overrun/marshal/gen/VariableStatement.java +++ b/src/main/java/overrun/marshal/gen1/VariableStatement.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal.gen1; import overrun.marshal.AccessModifier; diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index 574fdb0..2ca7cc0 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -19,7 +19,7 @@ import overrun.marshal.AccessModifier; import overrun.marshal.Downcall; import overrun.marshal.Loader; -import overrun.marshal.gen.*; +import overrun.marshal.gen1.*; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java index 20b668a..3aa69bf 100644 --- a/src/main/java/overrun/marshal/gen2/TypeUse.java +++ b/src/main/java/overrun/marshal/gen2/TypeUse.java @@ -16,7 +16,7 @@ package overrun.marshal.gen2; -import overrun.marshal.gen.Spec; +import overrun.marshal.gen1.Spec; /** * Holds spec with type data diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java index 2e84dcc..2a253de 100644 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ b/src/main/java/overrun/marshal/internal/Processor.java @@ -20,7 +20,7 @@ import overrun.marshal.Loader; import overrun.marshal.StrCharset; import overrun.marshal.Upcall; -import overrun.marshal.gen.*; +import overrun.marshal.gen1.*; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.ProcessingEnvironment; From 21d217b79ed54703b36c896e726ac0a453f03566 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:24:01 +0800 Subject: [PATCH 03/28] Move related classes to .gen --- .../src/main/java/overrun/marshal/test/CDowncallTest.java | 2 +- .../overrun/marshal/test/CDowncallTestWithLoader.java | 2 +- demo/src/main/java/overrun/marshal/test/CStructTest.java | 8 ++++---- .../main/java/overrun/marshal/test/GLFWErrorCallback.java | 2 +- demo/src/main/java/overrun/marshal/test/MyEnum.java | 2 +- .../main/java/overrun/marshal/test/NativeLibLoader.java | 2 +- demo/src/main/java/overrun/marshal/test/Upcall2.java | 2 +- src/main/java/module-info.java | 1 + src/main/java/overrun/marshal/Configurations.java | 2 ++ src/main/java/overrun/marshal/DowncallProcessor.java | 1 + src/main/java/overrun/marshal/StructProcessor.java | 1 + src/main/java/overrun/marshal/Upcall.java | 2 ++ src/main/java/overrun/marshal/{ => gen}/Access.java | 4 ++-- .../java/overrun/marshal/{ => gen}/AccessModifier.java | 4 ++-- src/main/java/overrun/marshal/{ => gen}/CEnum.java | 2 +- src/main/java/overrun/marshal/{ => gen}/Critical.java | 4 ++-- src/main/java/overrun/marshal/{ => gen}/Custom.java | 4 ++-- src/main/java/overrun/marshal/{ => gen}/Default.java | 4 ++-- src/main/java/overrun/marshal/{ => gen}/Downcall.java | 2 +- src/main/java/overrun/marshal/{ => gen}/Entrypoint.java | 4 ++-- src/main/java/overrun/marshal/{ => gen}/Loader.java | 2 +- src/main/java/overrun/marshal/{ => gen}/NullableRef.java | 2 +- src/main/java/overrun/marshal/{ => gen}/Overload.java | 4 ++-- src/main/java/overrun/marshal/{ => gen}/Ref.java | 4 ++-- src/main/java/overrun/marshal/{ => gen}/Sized.java | 4 +++- src/main/java/overrun/marshal/{ => gen}/SizedSeg.java | 4 +++- src/main/java/overrun/marshal/{ => gen}/Skip.java | 2 +- src/main/java/overrun/marshal/{ => gen}/StrCharset.java | 2 +- src/main/java/overrun/marshal/gen1/ClassSpec.java | 2 +- src/main/java/overrun/marshal/gen1/MethodSpec.java | 2 +- src/main/java/overrun/marshal/gen1/VariableStatement.java | 2 +- src/main/java/overrun/marshal/gen2/DowncallData.java | 6 +++--- src/main/java/overrun/marshal/gen2/FieldData.java | 2 +- src/main/java/overrun/marshal/internal/Processor.java | 6 +++--- src/main/java/overrun/marshal/package-info.java | 2 +- 35 files changed, 56 insertions(+), 45 deletions(-) rename src/main/java/overrun/marshal/{ => gen}/Access.java (93%) rename src/main/java/overrun/marshal/{ => gen}/AccessModifier.java (94%) rename src/main/java/overrun/marshal/{ => gen}/CEnum.java (98%) rename src/main/java/overrun/marshal/{ => gen}/Critical.java (93%) rename src/main/java/overrun/marshal/{ => gen}/Custom.java (93%) rename src/main/java/overrun/marshal/{ => gen}/Default.java (94%) rename src/main/java/overrun/marshal/{ => gen}/Downcall.java (99%) rename src/main/java/overrun/marshal/{ => gen}/Entrypoint.java (93%) rename src/main/java/overrun/marshal/{ => gen}/Loader.java (97%) rename src/main/java/overrun/marshal/{ => gen}/NullableRef.java (97%) rename src/main/java/overrun/marshal/{ => gen}/Overload.java (94%) rename src/main/java/overrun/marshal/{ => gen}/Ref.java (93%) rename src/main/java/overrun/marshal/{ => gen}/Sized.java (94%) rename src/main/java/overrun/marshal/{ => gen}/SizedSeg.java (94%) rename src/main/java/overrun/marshal/{ => gen}/Skip.java (97%) rename src/main/java/overrun/marshal/{ => gen}/StrCharset.java (97%) diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 89ae598..4da7c40 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.*; +import overrun.marshal.gen.*; import overrun.marshal.struct.ByValue; import overrun.marshal.struct.StructRef; diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java b/demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java index 5bb9238..3cd8f3e 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.Downcall; +import overrun.marshal.gen.Downcall; import java.lang.foreign.MemorySegment; diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java index a26b64c..576d680 100644 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -16,10 +16,10 @@ package overrun.marshal.test; -import overrun.marshal.SizedSeg; -import overrun.marshal.Sized; -import overrun.marshal.Skip; -import overrun.marshal.StrCharset; +import overrun.marshal.gen.SizedSeg; +import overrun.marshal.gen.Sized; +import overrun.marshal.gen.Skip; +import overrun.marshal.gen.StrCharset; import overrun.marshal.struct.Const; import overrun.marshal.struct.Padding; import overrun.marshal.struct.Struct; diff --git a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java index 279341b..30d981b 100644 --- a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java +++ b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.SizedSeg; +import overrun.marshal.gen.SizedSeg; import overrun.marshal.Upcall; import java.lang.foreign.Arena; diff --git a/demo/src/main/java/overrun/marshal/test/MyEnum.java b/demo/src/main/java/overrun/marshal/test/MyEnum.java index 31895fb..f1291d0 100644 --- a/demo/src/main/java/overrun/marshal/test/MyEnum.java +++ b/demo/src/main/java/overrun/marshal/test/MyEnum.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.CEnum; +import overrun.marshal.gen.CEnum; /** * @author squid233 diff --git a/demo/src/main/java/overrun/marshal/test/NativeLibLoader.java b/demo/src/main/java/overrun/marshal/test/NativeLibLoader.java index 460fe4f..fc0e1ea 100644 --- a/demo/src/main/java/overrun/marshal/test/NativeLibLoader.java +++ b/demo/src/main/java/overrun/marshal/test/NativeLibLoader.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.Loader; +import overrun.marshal.gen.Loader; import java.lang.foreign.Arena; import java.lang.foreign.SymbolLookup; diff --git a/demo/src/main/java/overrun/marshal/test/Upcall2.java b/demo/src/main/java/overrun/marshal/test/Upcall2.java index 754c550..9873d3f 100644 --- a/demo/src/main/java/overrun/marshal/test/Upcall2.java +++ b/demo/src/main/java/overrun/marshal/test/Upcall2.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.SizedSeg; +import overrun.marshal.gen.SizedSeg; import overrun.marshal.Upcall; import java.lang.foreign.Arena; diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ccf7a7c..d9a058c 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.gen; exports overrun.marshal.struct; requires java.compiler; diff --git a/src/main/java/overrun/marshal/Configurations.java b/src/main/java/overrun/marshal/Configurations.java index 3e692c6..ff18beb 100644 --- a/src/main/java/overrun/marshal/Configurations.java +++ b/src/main/java/overrun/marshal/Configurations.java @@ -16,6 +16,8 @@ package overrun.marshal; +import overrun.marshal.gen.Sized; + import java.util.function.Supplier; /** diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index 064a659..c6c501d 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -16,6 +16,7 @@ package overrun.marshal; +import overrun.marshal.gen.*; import overrun.marshal.gen1.*; import overrun.marshal.gen2.DowncallData; import overrun.marshal.internal.Processor; diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index c86fb04..89711d5 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -16,6 +16,7 @@ package overrun.marshal; +import overrun.marshal.gen.*; import overrun.marshal.gen1.*; import overrun.marshal.internal.Processor; import overrun.marshal.struct.*; diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index 7b7bb0f..b9d3f83 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -16,6 +16,8 @@ package overrun.marshal; +import overrun.marshal.gen.SizedSeg; + import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/src/main/java/overrun/marshal/Access.java b/src/main/java/overrun/marshal/gen/Access.java similarity index 93% rename from src/main/java/overrun/marshal/Access.java rename to src/main/java/overrun/marshal/gen/Access.java index 9c30e4c..e13560b 100644 --- a/src/main/java/overrun/marshal/Access.java +++ b/src/main/java/overrun/marshal/gen/Access.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/AccessModifier.java b/src/main/java/overrun/marshal/gen/AccessModifier.java similarity index 94% rename from src/main/java/overrun/marshal/AccessModifier.java rename to src/main/java/overrun/marshal/gen/AccessModifier.java index a0b113f..dfd4ef2 100644 --- a/src/main/java/overrun/marshal/AccessModifier.java +++ b/src/main/java/overrun/marshal/gen/AccessModifier.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; /** * The access modifier. diff --git a/src/main/java/overrun/marshal/CEnum.java b/src/main/java/overrun/marshal/gen/CEnum.java similarity index 98% rename from src/main/java/overrun/marshal/CEnum.java rename to src/main/java/overrun/marshal/gen/CEnum.java index a4b9443..351fe31 100644 --- a/src/main/java/overrun/marshal/CEnum.java +++ b/src/main/java/overrun/marshal/gen/CEnum.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/overrun/marshal/Critical.java b/src/main/java/overrun/marshal/gen/Critical.java similarity index 93% rename from src/main/java/overrun/marshal/Critical.java rename to src/main/java/overrun/marshal/gen/Critical.java index 1fdd09c..d5ff1e4 100644 --- a/src/main/java/overrun/marshal/Critical.java +++ b/src/main/java/overrun/marshal/gen/Critical.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Custom.java b/src/main/java/overrun/marshal/gen/Custom.java similarity index 93% rename from src/main/java/overrun/marshal/Custom.java rename to src/main/java/overrun/marshal/gen/Custom.java index f87ee19..9238367 100644 --- a/src/main/java/overrun/marshal/Custom.java +++ b/src/main/java/overrun/marshal/gen/Custom.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Default.java b/src/main/java/overrun/marshal/gen/Default.java similarity index 94% rename from src/main/java/overrun/marshal/Default.java rename to src/main/java/overrun/marshal/gen/Default.java index bdfc5a2..f81298b 100644 --- a/src/main/java/overrun/marshal/Default.java +++ b/src/main/java/overrun/marshal/gen/Default.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/gen/Downcall.java similarity index 99% rename from src/main/java/overrun/marshal/Downcall.java rename to src/main/java/overrun/marshal/gen/Downcall.java index 27b6147..da6c767 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/gen/Downcall.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Entrypoint.java b/src/main/java/overrun/marshal/gen/Entrypoint.java similarity index 93% rename from src/main/java/overrun/marshal/Entrypoint.java rename to src/main/java/overrun/marshal/gen/Entrypoint.java index e3aadf7..f6127b9 100644 --- a/src/main/java/overrun/marshal/Entrypoint.java +++ b/src/main/java/overrun/marshal/gen/Entrypoint.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Loader.java b/src/main/java/overrun/marshal/gen/Loader.java similarity index 97% rename from src/main/java/overrun/marshal/Loader.java rename to src/main/java/overrun/marshal/gen/Loader.java index 4e4e6f9..8cc9c48 100644 --- a/src/main/java/overrun/marshal/Loader.java +++ b/src/main/java/overrun/marshal/gen/Loader.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; import java.lang.foreign.SymbolLookup; diff --git a/src/main/java/overrun/marshal/NullableRef.java b/src/main/java/overrun/marshal/gen/NullableRef.java similarity index 97% rename from src/main/java/overrun/marshal/NullableRef.java rename to src/main/java/overrun/marshal/gen/NullableRef.java index dad0a50..1954503 100644 --- a/src/main/java/overrun/marshal/NullableRef.java +++ b/src/main/java/overrun/marshal/gen/NullableRef.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Overload.java b/src/main/java/overrun/marshal/gen/Overload.java similarity index 94% rename from src/main/java/overrun/marshal/Overload.java rename to src/main/java/overrun/marshal/gen/Overload.java index 06b77f4..d7fafe4 100644 --- a/src/main/java/overrun/marshal/Overload.java +++ b/src/main/java/overrun/marshal/gen/Overload.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Ref.java b/src/main/java/overrun/marshal/gen/Ref.java similarity index 93% rename from src/main/java/overrun/marshal/Ref.java rename to src/main/java/overrun/marshal/gen/Ref.java index dc762ad..b28ebd2 100644 --- a/src/main/java/overrun/marshal/Ref.java +++ b/src/main/java/overrun/marshal/gen/Ref.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 @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Sized.java b/src/main/java/overrun/marshal/gen/Sized.java similarity index 94% rename from src/main/java/overrun/marshal/Sized.java rename to src/main/java/overrun/marshal/gen/Sized.java index f7d1f8f..d2d6871 100644 --- a/src/main/java/overrun/marshal/Sized.java +++ b/src/main/java/overrun/marshal/gen/Sized.java @@ -14,7 +14,9 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; + +import overrun.marshal.Configurations; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/SizedSeg.java b/src/main/java/overrun/marshal/gen/SizedSeg.java similarity index 94% rename from src/main/java/overrun/marshal/SizedSeg.java rename to src/main/java/overrun/marshal/gen/SizedSeg.java index 4958b4c..925b9af 100644 --- a/src/main/java/overrun/marshal/SizedSeg.java +++ b/src/main/java/overrun/marshal/gen/SizedSeg.java @@ -14,7 +14,9 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; + +import overrun.marshal.Configurations; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/Skip.java b/src/main/java/overrun/marshal/gen/Skip.java similarity index 97% rename from src/main/java/overrun/marshal/Skip.java rename to src/main/java/overrun/marshal/gen/Skip.java index 1799b73..c23dded 100644 --- a/src/main/java/overrun/marshal/Skip.java +++ b/src/main/java/overrun/marshal/gen/Skip.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/StrCharset.java b/src/main/java/overrun/marshal/gen/StrCharset.java similarity index 97% rename from src/main/java/overrun/marshal/StrCharset.java rename to src/main/java/overrun/marshal/gen/StrCharset.java index fe3b1b5..12c69b6 100644 --- a/src/main/java/overrun/marshal/StrCharset.java +++ b/src/main/java/overrun/marshal/gen/StrCharset.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal; +package overrun.marshal.gen; import java.lang.annotation.*; diff --git a/src/main/java/overrun/marshal/gen1/ClassSpec.java b/src/main/java/overrun/marshal/gen1/ClassSpec.java index 1dc6dec..2755cc1 100644 --- a/src/main/java/overrun/marshal/gen1/ClassSpec.java +++ b/src/main/java/overrun/marshal/gen1/ClassSpec.java @@ -16,7 +16,7 @@ package overrun.marshal.gen1; -import overrun.marshal.AccessModifier; +import overrun.marshal.gen.AccessModifier; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen1/MethodSpec.java b/src/main/java/overrun/marshal/gen1/MethodSpec.java index 09ad5c3..31813a4 100644 --- a/src/main/java/overrun/marshal/gen1/MethodSpec.java +++ b/src/main/java/overrun/marshal/gen1/MethodSpec.java @@ -16,7 +16,7 @@ package overrun.marshal.gen1; -import overrun.marshal.AccessModifier; +import overrun.marshal.gen.AccessModifier; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/overrun/marshal/gen1/VariableStatement.java b/src/main/java/overrun/marshal/gen1/VariableStatement.java index 8977238..8081687 100644 --- a/src/main/java/overrun/marshal/gen1/VariableStatement.java +++ b/src/main/java/overrun/marshal/gen1/VariableStatement.java @@ -16,7 +16,7 @@ package overrun.marshal.gen1; -import overrun.marshal.AccessModifier; +import overrun.marshal.gen.AccessModifier; /** * Variable statement diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index 2ca7cc0..73ffb6a 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -16,9 +16,9 @@ package overrun.marshal.gen2; -import overrun.marshal.AccessModifier; -import overrun.marshal.Downcall; -import overrun.marshal.Loader; +import overrun.marshal.gen.AccessModifier; +import overrun.marshal.gen.Downcall; +import overrun.marshal.gen.Loader; import overrun.marshal.gen1.*; import javax.annotation.processing.ProcessingEnvironment; diff --git a/src/main/java/overrun/marshal/gen2/FieldData.java b/src/main/java/overrun/marshal/gen2/FieldData.java index 7bee21f..3e0236e 100644 --- a/src/main/java/overrun/marshal/gen2/FieldData.java +++ b/src/main/java/overrun/marshal/gen2/FieldData.java @@ -16,7 +16,7 @@ package overrun.marshal.gen2; -import overrun.marshal.AccessModifier; +import overrun.marshal.gen.AccessModifier; /** * Holds field diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java index 2a253de..34fa1ca 100644 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ b/src/main/java/overrun/marshal/internal/Processor.java @@ -16,9 +16,9 @@ package overrun.marshal.internal; -import overrun.marshal.CEnum; -import overrun.marshal.Loader; -import overrun.marshal.StrCharset; +import overrun.marshal.gen.CEnum; +import overrun.marshal.gen.Loader; +import overrun.marshal.gen.StrCharset; import overrun.marshal.Upcall; import overrun.marshal.gen1.*; diff --git a/src/main/java/overrun/marshal/package-info.java b/src/main/java/overrun/marshal/package-info.java index a0cee5c..c7deb6b 100644 --- a/src/main/java/overrun/marshal/package-info.java +++ b/src/main/java/overrun/marshal/package-info.java @@ -18,7 +18,7 @@ * The main package of marshal. * * @author squid233 - * @see overrun.marshal.Downcall + * @see overrun.marshal.gen.Downcall * @see overrun.marshal.Upcall * @see overrun.marshal.struct * @since 0.1.0 From 0189e585ee1eba21bec567b5432c556073fdb3a7 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 8 Jan 2024 23:25:09 +0800 Subject: [PATCH 04/28] Move struct related classes to .gen.struct --- .../src/main/java/overrun/marshal/test/CDowncallTest.java | 4 ++-- demo/src/main/java/overrun/marshal/test/CStructTest.java | 8 ++++---- demo/src/main/java/overrun/marshal/test/CVector3.java | 4 ++-- src/main/java/module-info.java | 2 +- src/main/java/overrun/marshal/DowncallProcessor.java | 4 ++-- src/main/java/overrun/marshal/StructProcessor.java | 1 + src/main/java/overrun/marshal/gen/Skip.java | 4 +++- .../java/overrun/marshal/{ => gen}/struct/ByValue.java | 2 +- src/main/java/overrun/marshal/{ => gen}/struct/Const.java | 2 +- .../java/overrun/marshal/{ => gen}/struct/IStruct.java | 2 +- .../java/overrun/marshal/{ => gen}/struct/Padding.java | 2 +- .../java/overrun/marshal/{ => gen}/struct/Struct.java | 2 +- .../java/overrun/marshal/{ => gen}/struct/StructRef.java | 2 +- .../overrun/marshal/{ => gen}/struct/package-info.java | 4 ++-- 14 files changed, 23 insertions(+), 20 deletions(-) rename src/main/java/overrun/marshal/{ => gen}/struct/ByValue.java (97%) rename src/main/java/overrun/marshal/{ => gen}/struct/Const.java (96%) rename src/main/java/overrun/marshal/{ => gen}/struct/IStruct.java (97%) rename src/main/java/overrun/marshal/{ => gen}/struct/Padding.java (96%) rename src/main/java/overrun/marshal/{ => gen}/struct/Struct.java (97%) rename src/main/java/overrun/marshal/{ => gen}/struct/StructRef.java (96%) rename src/main/java/overrun/marshal/{ => gen}/struct/package-info.java (90%) diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 4da7c40..ea0e809 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -17,8 +17,8 @@ package overrun.marshal.test; import overrun.marshal.gen.*; -import overrun.marshal.struct.ByValue; -import overrun.marshal.struct.StructRef; +import overrun.marshal.gen.struct.ByValue; +import overrun.marshal.gen.struct.StructRef; import java.lang.foreign.MemorySegment; diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java index 576d680..58cbae4 100644 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java @@ -20,10 +20,10 @@ import overrun.marshal.gen.Sized; import overrun.marshal.gen.Skip; import overrun.marshal.gen.StrCharset; -import overrun.marshal.struct.Const; -import overrun.marshal.struct.Padding; -import overrun.marshal.struct.Struct; -import overrun.marshal.struct.StructRef; +import overrun.marshal.gen.struct.Const; +import overrun.marshal.gen.struct.Padding; +import overrun.marshal.gen.struct.Struct; +import overrun.marshal.gen.struct.StructRef; import java.lang.foreign.MemorySegment; diff --git a/demo/src/main/java/overrun/marshal/test/CVector3.java b/demo/src/main/java/overrun/marshal/test/CVector3.java index c3a7134..e2065bf 100644 --- a/demo/src/main/java/overrun/marshal/test/CVector3.java +++ b/demo/src/main/java/overrun/marshal/test/CVector3.java @@ -16,8 +16,8 @@ package overrun.marshal.test; -import overrun.marshal.struct.Const; -import overrun.marshal.struct.Struct; +import overrun.marshal.gen.struct.Const; +import overrun.marshal.gen.struct.Struct; /** * @author squid233 diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index d9a058c..fc4d9e7 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -23,7 +23,7 @@ module io.github.overrun.marshal { exports overrun.marshal; exports overrun.marshal.gen; - exports overrun.marshal.struct; + exports overrun.marshal.gen.struct; requires java.compiler; } diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index c6c501d..316ff25 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -20,8 +20,8 @@ import overrun.marshal.gen1.*; import overrun.marshal.gen2.DowncallData; import overrun.marshal.internal.Processor; -import overrun.marshal.struct.ByValue; -import overrun.marshal.struct.StructRef; +import overrun.marshal.gen.struct.ByValue; +import overrun.marshal.gen.struct.StructRef; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.ExecutableElement; diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 89711d5..568c078 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -17,6 +17,7 @@ package overrun.marshal; import overrun.marshal.gen.*; +import overrun.marshal.gen.struct.*; import overrun.marshal.gen1.*; import overrun.marshal.internal.Processor; import overrun.marshal.struct.*; diff --git a/src/main/java/overrun/marshal/gen/Skip.java b/src/main/java/overrun/marshal/gen/Skip.java index c23dded..d473481 100644 --- a/src/main/java/overrun/marshal/gen/Skip.java +++ b/src/main/java/overrun/marshal/gen/Skip.java @@ -16,10 +16,12 @@ package overrun.marshal.gen; +import overrun.marshal.gen.struct.Struct; + import java.lang.annotation.*; /** - * Skips generating a marked field in {@linkplain overrun.marshal.struct.Struct struct}. + * Skips generating a marked field in {@linkplain Struct struct}. *

Example

*
{@code
  * @Skip
diff --git a/src/main/java/overrun/marshal/struct/ByValue.java b/src/main/java/overrun/marshal/gen/struct/ByValue.java
similarity index 97%
rename from src/main/java/overrun/marshal/struct/ByValue.java
rename to src/main/java/overrun/marshal/gen/struct/ByValue.java
index 269b6f1..0ee29f9 100644
--- a/src/main/java/overrun/marshal/struct/ByValue.java
+++ b/src/main/java/overrun/marshal/gen/struct/ByValue.java
@@ -14,7 +14,7 @@
  * copies or substantial portions of the Software.
  */
 
-package overrun.marshal.struct;
+package overrun.marshal.gen.struct;
 
 import java.lang.annotation.*;
 
diff --git a/src/main/java/overrun/marshal/struct/Const.java b/src/main/java/overrun/marshal/gen/struct/Const.java
similarity index 96%
rename from src/main/java/overrun/marshal/struct/Const.java
rename to src/main/java/overrun/marshal/gen/struct/Const.java
index b4c31fd..1866ef8 100644
--- a/src/main/java/overrun/marshal/struct/Const.java
+++ b/src/main/java/overrun/marshal/gen/struct/Const.java
@@ -14,7 +14,7 @@
  * copies or substantial portions of the Software.
  */
 
-package overrun.marshal.struct;
+package overrun.marshal.gen.struct;
 
 import java.lang.annotation.*;
 
diff --git a/src/main/java/overrun/marshal/struct/IStruct.java b/src/main/java/overrun/marshal/gen/struct/IStruct.java
similarity index 97%
rename from src/main/java/overrun/marshal/struct/IStruct.java
rename to src/main/java/overrun/marshal/gen/struct/IStruct.java
index 7371bb3..67d0182 100644
--- a/src/main/java/overrun/marshal/struct/IStruct.java
+++ b/src/main/java/overrun/marshal/gen/struct/IStruct.java
@@ -14,7 +14,7 @@
  * copies or substantial portions of the Software.
  */
 
-package overrun.marshal.struct;
+package overrun.marshal.gen.struct;
 
 import java.lang.foreign.MemorySegment;
 import java.lang.foreign.StructLayout;
diff --git a/src/main/java/overrun/marshal/struct/Padding.java b/src/main/java/overrun/marshal/gen/struct/Padding.java
similarity index 96%
rename from src/main/java/overrun/marshal/struct/Padding.java
rename to src/main/java/overrun/marshal/gen/struct/Padding.java
index 40687c9..27a783b 100644
--- a/src/main/java/overrun/marshal/struct/Padding.java
+++ b/src/main/java/overrun/marshal/gen/struct/Padding.java
@@ -14,7 +14,7 @@
  * copies or substantial portions of the Software.
  */
 
-package overrun.marshal.struct;
+package overrun.marshal.gen.struct;
 
 import java.lang.annotation.*;
 
diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/gen/struct/Struct.java
similarity index 97%
rename from src/main/java/overrun/marshal/struct/Struct.java
rename to src/main/java/overrun/marshal/gen/struct/Struct.java
index 6ad53cf..fbf9ac2 100644
--- a/src/main/java/overrun/marshal/struct/Struct.java
+++ b/src/main/java/overrun/marshal/gen/struct/Struct.java
@@ -14,7 +14,7 @@
  * copies or substantial portions of the Software.
  */
 
-package overrun.marshal.struct;
+package overrun.marshal.gen.struct;
 
 import java.lang.annotation.*;
 
diff --git a/src/main/java/overrun/marshal/struct/StructRef.java b/src/main/java/overrun/marshal/gen/struct/StructRef.java
similarity index 96%
rename from src/main/java/overrun/marshal/struct/StructRef.java
rename to src/main/java/overrun/marshal/gen/struct/StructRef.java
index 5e189c0..a149581 100644
--- a/src/main/java/overrun/marshal/struct/StructRef.java
+++ b/src/main/java/overrun/marshal/gen/struct/StructRef.java
@@ -14,7 +14,7 @@
  * copies or substantial portions of the Software.
  */
 
-package overrun.marshal.struct;
+package overrun.marshal.gen.struct;
 
 import java.lang.annotation.*;
 
diff --git a/src/main/java/overrun/marshal/struct/package-info.java b/src/main/java/overrun/marshal/gen/struct/package-info.java
similarity index 90%
rename from src/main/java/overrun/marshal/struct/package-info.java
rename to src/main/java/overrun/marshal/gen/struct/package-info.java
index 081d628..8e9317d 100644
--- a/src/main/java/overrun/marshal/struct/package-info.java
+++ b/src/main/java/overrun/marshal/gen/struct/package-info.java
@@ -18,7 +18,7 @@
  * The struct package of marshal.
  *
  * @author squid233
- * @see overrun.marshal.struct.Struct
+ * @see overrun.marshal.gen.struct.Struct
  * @since 0.1.0
  */
-package overrun.marshal.struct;
+package overrun.marshal.gen.struct;

From a76bb2d2fd330e5430c39b9aab4eb8e60fa3bf93 Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Mon, 8 Jan 2024 23:26:52 +0800
Subject: [PATCH 05/28] Change import sorting

---
 README.md                                            | 2 +-
 src/main/java/overrun/marshal/gen2/DowncallData.java | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/README.md b/README.md
index e01cd53..71020df 100644
--- a/README.md
+++ b/README.md
@@ -11,7 +11,7 @@ This library requires JDK 22 or newer.
 ## Overview
 
 ```java
-import overrun.marshal.*;
+import overrun.marshal.gen.*;
 
 import java.lang.foreign.MemorySegment;
 import java.lang.foreign.SegmentAllocator;
diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java
index 73ffb6a..c42eeb3 100644
--- a/src/main/java/overrun/marshal/gen2/DowncallData.java
+++ b/src/main/java/overrun/marshal/gen2/DowncallData.java
@@ -151,10 +151,10 @@ public void generate(TypeElement typeElement) throws IOException {
                 final boolean s1Java = s1.startsWith("java.");
                 final boolean s2Java = s2.startsWith("java.");
                 if (s1Java && !s2Java) {
-                    return -1;
+                    return 1;
                 }
                 if (!s1Java && s2Java) {
-                    return 1;
+                    return -1;
                 }
                 return s1.compareTo(s2);
             })

From 47e00d2467678d88d319ff5ac11fdf460420a0d4 Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Fri, 12 Jan 2024 00:08:30 +0800
Subject: [PATCH 06/28] Update generator

---
 .../overrun/marshal/test/CDowncallTest.java   |   4 +
 .../java/overrun/marshal/StructProcessor.java |  10 +-
 src/main/java/overrun/marshal/gen/Skip.java   |   6 +-
 .../overrun/marshal/gen1/CatchClause.java     |  18 +-
 .../overrun/marshal/gen1/ConstructSpec.java   |  17 +-
 .../java/overrun/marshal/gen1/MethodSpec.java |  17 +-
 .../overrun/marshal/gen1/ParameterSpec.java   |  17 +-
 src/main/java/overrun/marshal/gen1/Spec.java  |  18 ++
 .../marshal/gen1/VariableStatement.java       |   1 +
 .../overrun/marshal/gen2/AnnotationData.java  |  34 +++
 .../overrun/marshal/gen2/DowncallData.java    | 232 ++++++++++++++++--
 .../java/overrun/marshal/gen2/FieldData.java  |   4 +
 .../overrun/marshal/gen2/FunctionData.java    |  45 ++++
 .../java/overrun/marshal/gen2/ImportData.java |  15 +-
 .../marshal/gen2/MethodHandleData.java        |  45 ++++
 .../overrun/marshal/gen2/ParameterData.java   |  37 +++
 .../java/overrun/marshal/gen2/TypeData.java   |   5 +-
 .../java/overrun/marshal/gen2/TypeUse.java    |  70 ++++++
 .../overrun/marshal/gen2/VoidTypeData.java    |  32 +++
 .../java/overrun/marshal/internal/Util.java   |  16 ++
 20 files changed, 604 insertions(+), 39 deletions(-)
 create mode 100644 src/main/java/overrun/marshal/gen2/AnnotationData.java
 create mode 100644 src/main/java/overrun/marshal/gen2/FunctionData.java
 create mode 100644 src/main/java/overrun/marshal/gen2/MethodHandleData.java
 create mode 100644 src/main/java/overrun/marshal/gen2/ParameterData.java
 create mode 100644 src/main/java/overrun/marshal/gen2/VoidTypeData.java

diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java
index ea0e809..9457e34 100644
--- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java
+++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java
@@ -36,6 +36,8 @@ interface CDowncallTest {
      * A string value
      */
     String STR_VALUE = "Hello world";
+    @Skip
+    int SKIPPED = -1;
 
     void test();
 
@@ -191,6 +193,8 @@ interface CDowncallTest {
     @Overload
     void testAnotherEntrypoint(int[] segment);
 
+    void testWithoutOverload(int[] arr);
+
     /**
      * This is a test that tests all features.
      *
diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java
index 568c078..3085905 100644
--- a/src/main/java/overrun/marshal/StructProcessor.java
+++ b/src/main/java/overrun/marshal/StructProcessor.java
@@ -20,7 +20,6 @@
 import overrun.marshal.gen.struct.*;
 import overrun.marshal.gen1.*;
 import overrun.marshal.internal.Processor;
-import overrun.marshal.struct.*;
 
 import javax.annotation.processing.RoundEnvironment;
 import javax.lang.model.element.TypeElement;
@@ -99,7 +98,10 @@ private void writeFile(
         }
 
         final SourceFile file = new SourceFile(packageName);
-        file.addImports("overrun.marshal.*", "java.lang.foreign.*");
+        file.addImports("overrun.marshal.*",
+            "overrun.marshal.gen.*",
+            "overrun.marshal.gen.struct.*",
+            "java.lang.foreign.*");
         file.addImports(
             MemoryLayout.PathElement.class,
             VarHandle.class
@@ -220,7 +222,7 @@ private void writeFile(
                 .setFinal(true)));
 
             // constructor
-            classSpec.addMethod(new MethodSpec(null, simpleClassName), methodSpec -> {
+            classSpec.addMethod(new MethodSpec((Spec) null, simpleClassName), methodSpec -> {
                 methodSpec.setDocument("""
                      Creates {@code %s} with the given segment and element count.
 
@@ -241,7 +243,7 @@ private void writeFile(
                             .addArgument("_PE_" + name)));
                 });
             });
-            classSpec.addMethod(new MethodSpec(null, simpleClassName), methodSpec -> {
+            classSpec.addMethod(new MethodSpec((Spec) null, simpleClassName), methodSpec -> {
                 methodSpec.setDocument("""
                      Creates {@code %s} with the given segment. The count is auto-inferred.
 
diff --git a/src/main/java/overrun/marshal/gen/Skip.java b/src/main/java/overrun/marshal/gen/Skip.java
index d473481..497dbf6 100644
--- a/src/main/java/overrun/marshal/gen/Skip.java
+++ b/src/main/java/overrun/marshal/gen/Skip.java
@@ -16,12 +16,10 @@
 
 package overrun.marshal.gen;
 
-import overrun.marshal.gen.struct.Struct;
-
 import java.lang.annotation.*;
 
 /**
- * Skips generating a marked field in {@linkplain Struct struct}.
+ * Skips generating a marked field, parameter or method.
  * 

Example

*
{@code
  * @Skip
@@ -32,7 +30,7 @@
  * @since 0.1.0
  */
 @Documented
-@Target(ElementType.FIELD)
+@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
 @Retention(RetentionPolicy.SOURCE)
 public @interface Skip {
 }
diff --git a/src/main/java/overrun/marshal/gen1/CatchClause.java b/src/main/java/overrun/marshal/gen1/CatchClause.java
index 24a2fc1..dc00821 100644
--- a/src/main/java/overrun/marshal/gen1/CatchClause.java
+++ b/src/main/java/overrun/marshal/gen1/CatchClause.java
@@ -26,7 +26,7 @@
  * @since 0.1.0
  */
 public final class CatchClause implements Spec, StatementBlock {
-    private final String type;
+    private final Spec type;
     private final String name;
     private final List statements = new ArrayList<>();
 
@@ -36,11 +36,21 @@ public final class CatchClause implements Spec, StatementBlock {
      * @param type exception type
      * @param name variable name
      */
-    public CatchClause(String type, String name) {
+    public CatchClause(Spec type, String name) {
         this.type = type;
         this.name = name;
     }
 
+    /**
+     * Constructor
+     *
+     * @param type exception type
+     * @param name variable name
+     */
+    public CatchClause(String type, String name) {
+        this(Spec.literal(type), name);
+    }
+
     /**
      * Add a statement
      *
@@ -61,7 +71,9 @@ public String name() {
     @Override
     public void append(StringBuilder builder, int indent) {
         final String indentString = Spec.indentString(indent);
-        builder.append("catch (").append(type).append(' ').append(name).append(") {\n");
+        builder.append("catch (");
+        type.append(builder, indent);
+        builder.append(' ').append(name).append(") {\n");
         statements.forEach(spec -> spec.append(builder, indent + 4));
         builder.append(indentString).append('}');
     }
diff --git a/src/main/java/overrun/marshal/gen1/ConstructSpec.java b/src/main/java/overrun/marshal/gen1/ConstructSpec.java
index 2827c55..1f64fb9 100644
--- a/src/main/java/overrun/marshal/gen1/ConstructSpec.java
+++ b/src/main/java/overrun/marshal/gen1/ConstructSpec.java
@@ -26,7 +26,7 @@
  * @since 0.1.0
  */
 public final class ConstructSpec implements Spec {
-    private final String object;
+    private final Spec object;
     private final List arguments = new ArrayList<>();
 
     /**
@@ -34,10 +34,19 @@ public final class ConstructSpec implements Spec {
      *
      * @param object the caller object
      */
-    public ConstructSpec(String object) {
+    public ConstructSpec(Spec object) {
         this.object = object;
     }
 
+    /**
+     * Constructor
+     *
+     * @param object the caller object
+     */
+    public ConstructSpec(String object) {
+        this(Spec.literal(object));
+    }
+
     /**
      * Add argument
      *
@@ -51,7 +60,9 @@ public ConstructSpec addArgument(Spec spec) {
 
     @Override
     public void append(StringBuilder builder, int indent) {
-        builder.append("new ").append(object).append('(');
+        builder.append("new ");
+        object.append(builder, indent);
+        builder.append('(');
         for (int i = 0; i < arguments.size(); i++) {
             Spec spec = arguments.get(i);
             if (i != 0) {
diff --git a/src/main/java/overrun/marshal/gen1/MethodSpec.java b/src/main/java/overrun/marshal/gen1/MethodSpec.java
index 31813a4..d4c409c 100644
--- a/src/main/java/overrun/marshal/gen1/MethodSpec.java
+++ b/src/main/java/overrun/marshal/gen1/MethodSpec.java
@@ -28,7 +28,7 @@
  * @since 0.1.0
  */
 public final class MethodSpec implements Annotatable, Spec, StatementBlock {
-    private final String returnType;
+    private final Spec returnType;
     private final String name;
     private String document = null;
     private final List annotations = new ArrayList<>();
@@ -45,11 +45,21 @@ public final class MethodSpec implements Annotatable, Spec, StatementBlock {
      * @param returnType return type
      * @param name       name
      */
-    public MethodSpec(String returnType, String name) {
+    public MethodSpec(Spec returnType, String name) {
         this.returnType = returnType;
         this.name = name;
     }
 
+    /**
+     * Constructor
+     *
+     * @param returnType return type
+     * @param name       name
+     */
+    public MethodSpec(String returnType, String name) {
+        this(Spec.literal(returnType), name);
+    }
+
     /**
      * Set document
      *
@@ -167,7 +177,8 @@ public void append(StringBuilder builder, int indent) {
             builder.append("default ");
         }
         if (returnType != null) {
-            builder.append(returnType).append(' ');
+            returnType.append(builder, indent);
+            builder.append(' ');
         }
         builder.append(name).append('(');
         if (separateLine) {
diff --git a/src/main/java/overrun/marshal/gen1/ParameterSpec.java b/src/main/java/overrun/marshal/gen1/ParameterSpec.java
index ec81465..9dcd2cf 100644
--- a/src/main/java/overrun/marshal/gen1/ParameterSpec.java
+++ b/src/main/java/overrun/marshal/gen1/ParameterSpec.java
@@ -27,7 +27,7 @@
  * @since 0.1.0
  */
 public final class ParameterSpec implements Annotatable, Spec {
-    private final String type;
+    private final Spec type;
     private final String name;
     private final List annotations = new ArrayList<>();
 
@@ -37,11 +37,21 @@ public final class ParameterSpec implements Annotatable, Spec {
      * @param type type
      * @param name name
      */
-    public ParameterSpec(String type, String name) {
+    public ParameterSpec(Spec type, String name) {
         this.type = type;
         this.name = name;
     }
 
+    /**
+     * Constructor
+     *
+     * @param type type
+     * @param name name
+     */
+    public ParameterSpec(String type, String name) {
+        this(Spec.literal(type), name);
+    }
+
     /**
      * Add an annotation
      *
@@ -69,6 +79,7 @@ public void append(StringBuilder builder, int indent) {
             annotationSpec.append(builder, indent);
             builder.append(' ');
         });
-        builder.append(type).append(' ').append(name);
+        type.append(builder, indent);
+        builder.append(' ').append(name);
     }
 }
diff --git a/src/main/java/overrun/marshal/gen1/Spec.java b/src/main/java/overrun/marshal/gen1/Spec.java
index 01a8768..be0752c 100644
--- a/src/main/java/overrun/marshal/gen1/Spec.java
+++ b/src/main/java/overrun/marshal/gen1/Spec.java
@@ -124,6 +124,7 @@ static Spec assignStatement(String left, Spec right) {
      * @param member member
      * @return spec
      */
+    @Deprecated(since = "0.1.0")
     static Spec accessSpec(Class object, String member) {
         return literal(object.getSimpleName() + '.' + member);
     }
@@ -135,6 +136,7 @@ static Spec accessSpec(Class object, String member) {
      * @param member member
      * @return spec
      */
+    @Deprecated(since = "0.1.0")
     static Spec accessSpec(Class object, Class member) {
         return literal(object.getSimpleName() + '.' + member.getSimpleName());
     }
@@ -195,6 +197,22 @@ static Spec neqSpec(Spec left, Spec right) {
         };
     }
 
+    /**
+     * Create an expression casting
+     *
+     * @param type type
+     * @param exp  expression
+     * @return cast
+     */
+    static Spec cast(Spec type, Spec exp) {
+        return (builder, indent) -> {
+            builder.append('(');
+            type.append(builder, indent);
+            builder.append(") ");
+            exp.append(builder, indent);
+        };
+    }
+
     /**
      * Create an expression casting
      *
diff --git a/src/main/java/overrun/marshal/gen1/VariableStatement.java b/src/main/java/overrun/marshal/gen1/VariableStatement.java
index 8081687..a23525c 100644
--- a/src/main/java/overrun/marshal/gen1/VariableStatement.java
+++ b/src/main/java/overrun/marshal/gen1/VariableStatement.java
@@ -40,6 +40,7 @@ public final class VariableStatement implements Spec {
      * @param name  name
      * @param value value
      */
+    @Deprecated
     public VariableStatement(Class type, String name, Spec value) {
         this(type.getSimpleName(), name, value);
     }
diff --git a/src/main/java/overrun/marshal/gen2/AnnotationData.java b/src/main/java/overrun/marshal/gen2/AnnotationData.java
new file mode 100644
index 0000000..a0cb8df
--- /dev/null
+++ b/src/main/java/overrun/marshal/gen2/AnnotationData.java
@@ -0,0 +1,34 @@
+/*
+ * 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.gen2;
+
+import javax.lang.model.element.AnnotationMirror;
+import java.lang.annotation.Annotation;
+
+/**
+ * Holds annotation
+ *
+ * @param annotation the annotation
+ * @param mirror     the annotation mirror
+ * @author squid233
+ * @since 0.1.0
+ */
+public record AnnotationData(
+    Annotation annotation,
+    AnnotationMirror mirror
+) {
+}
diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java
index c42eeb3..a073914 100644
--- a/src/main/java/overrun/marshal/gen2/DowncallData.java
+++ b/src/main/java/overrun/marshal/gen2/DowncallData.java
@@ -16,12 +16,12 @@
 
 package overrun.marshal.gen2;
 
-import overrun.marshal.gen.AccessModifier;
-import overrun.marshal.gen.Downcall;
-import overrun.marshal.gen.Loader;
+import overrun.marshal.gen.*;
+import overrun.marshal.gen.struct.ByValue;
 import overrun.marshal.gen1.*;
 
 import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.AnnotationMirror;
 import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
@@ -29,10 +29,15 @@
 import javax.tools.JavaFileObject;
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.lang.annotation.Annotation;
 import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
 import java.lang.foreign.Linker;
 import java.lang.foreign.SymbolLookup;
+import java.lang.invoke.MethodHandle;
 import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
 
 import static overrun.marshal.internal.Util.*;
 
@@ -43,9 +48,25 @@
  * @since 0.1.0
  */
 public final class DowncallData {
+    private static final List> METHOD_ANNOTATIONS = List.of(
+        ByValue.class,
+        Critical.class,
+        Default.class,
+        SizedSeg.class,
+        Sized.class,
+        StrCharset.class
+    );
+    private static final List> PARAMETER_ANNOTATIONS = List.of(
+        NullableRef.class,
+        Ref.class,
+        SizedSeg.class,
+        Sized.class,
+        StrCharset.class
+    );
     private final ProcessingEnvironment processingEnv;
-    private final ImportData importData = new ImportData(new ArrayList<>());
+    private final ImportData imports = new ImportData(new ArrayList<>());
     private final List fields = new ArrayList<>();
+    private final List functionDataList = new ArrayList<>();
     private String document = null;
     private boolean nonFinal = false;
 
@@ -69,12 +90,16 @@ private void processDowncallType(TypeElement typeElement) {
 
         // process fields
         ElementFilter.fieldsIn(enclosedElements).forEach(element -> {
+            if (element.getAnnotation(Skip.class) != null) {
+                return;
+            }
             final Object constantValue = element.getConstantValue();
             if (constantValue == null) {
                 return;
             }
             fields.add(new FieldData(
                 getDocComment(processingEnv, element),
+                List.of(),
                 AccessModifier.PUBLIC,
                 true,
                 true,
@@ -87,21 +112,137 @@ private void processDowncallType(TypeElement typeElement) {
         final var methods = ElementFilter.methodsIn(enclosedElements);
         if (!methods.isEmpty()) {
             // collect method handles
+            final Map methodHandleDataList = LinkedHashMap.newLinkedHashMap(methods.size());
+            methods.forEach(executableElement -> {
+                if (executableElement.getAnnotation(Skip.class) != null ||
+                    executableElement.getAnnotation(Overload.class) != null) {
+                    return;
+                }
+                final Access access = executableElement.getAnnotation(Access.class);
+                final String entrypoint = getMethodEntrypoint(executableElement);
+                methodHandleDataList.put(entrypoint,
+                    new MethodHandleData(
+                        executableElement,
+                        access != null ? access.value() : AccessModifier.PUBLIC,
+                        entrypoint,
+                        TypeUse.valueLayout(executableElement.getReturnType()),
+                        executableElement.getParameters()
+                            .stream()
+                            .filter(element -> element.getAnnotation(Skip.class) == null)
+                            .map(TypeUse::valueLayout)
+                            .map(Optional::orElseThrow)
+                            .toList(),
+                        executableElement.getAnnotation(Default.class) != null
+                    ));
+            });
 
             // add linker and lookup
+            final Predicate containsKey = methodHandleDataList::containsKey;
+            final String lookupName = addLookup(typeElement, containsKey);
+            final String linkerName = tryInsertUnderline("LINKER", containsKey);
             fields.add(new FieldData(
                 null,
+                List.of(),
                 AccessModifier.PRIVATE,
                 true,
                 true,
                 TypeData.fromClass(Linker.class),
-                "_LINKER", // TODO: 2024/1/8 squid233: conflict
+                linkerName,
                 importData -> new InvokeSpec(importData.simplifyOrImport(Linker.class), "nativeLinker")
             ));
 
-            addLookup(typeElement);
+            // add methods
+            final var fieldNames = fields.stream().map(FieldData::name).toList();
+            methodHandleDataList.values().forEach(methodHandleData -> {
+                final String name = methodHandleData.name();
+                fields.add(new FieldData(
+                    " The method handle of {@code " + name + "}.",
+                    List.of(),
+                    methodHandleData.accessModifier(),
+                    true,
+                    true,
+                    TypeData.fromClass(MethodHandle.class),
+                    name,
+                    importData -> {
+                        final InvokeSpec invokeSpec = new InvokeSpec(
+                            new InvokeSpec(lookupName, "find")
+                                .addArgument(getConstExp(processingEnv, name)), "map"
+                        ).addArgument(new LambdaSpec("s")
+                            .addStatementThis(new InvokeSpec(linkerName, "downcallHandle")
+                                .addArgument("s")
+                                .also(invokeSpec1 -> {
+                                    final var returnType = methodHandleData.returnType();
+                                    final String methodName = returnType.isPresent() ? "of" : "ofVoid";
+                                    invokeSpec1.addArgument(new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName)
+                                        .also(invokeSpec2 -> {
+                                            returnType.ifPresent(typeUse -> invokeSpec2.addArgument(typeUse.apply(importData)));
+                                            methodHandleData.parameterTypes().forEach(typeUse ->
+                                                invokeSpec2.addArgument(typeUse.apply(importData)));
+                                        }));
+                                })));
+                        if (methodHandleData.optional()) {
+                            return new InvokeSpec(invokeSpec, "orElse").addArgument("null");
+                        }
+                        return new InvokeSpec(
+                            invokeSpec, "orElseThrow"
+                        );
+                    }
+                ));
 
-            // add method handles
+                final ExecutableElement executableElement = methodHandleData.executableElement();
+                final var returnType = TypeUse.toDowncallType(executableElement.getReturnType());
+                final var parameters = executableElement.getParameters().stream()
+                    .filter(element -> element.getAnnotation(Skip.class) == null)
+                    .map(element -> new ParameterData(
+                        null,
+                        getElementAnnotations(PARAMETER_ANNOTATIONS, element),
+                        TypeUse.toDowncallType(element.asType()).orElseThrow(),
+                        tryInsertUnderline(element.getSimpleName().toString(), fieldNames::contains)
+                    ))
+                    .toList();
+                final var parameterNames = parameters.stream().map(ParameterData::name).toList();
+                functionDataList.add(new FunctionData(
+                    getDocComment(processingEnv, executableElement),
+                    getElementAnnotations(METHOD_ANNOTATIONS, executableElement),
+                    methodHandleData.accessModifier(),
+                    returnType.orElseGet(() -> _ -> Spec.literal("void")),
+                    executableElement.getSimpleName().toString(),
+                    parameters,
+                    List.of(
+                        importData -> new TryCatchStatement().also(tryCatchStatement -> {
+                            final String exceptionName = tryInsertUnderline("e", s -> fieldNames.contains(s) || parameterNames.contains(s));
+                            final String methodHandleName = methodHandleData.name();
+                            final InvokeSpec invokeSpec = new InvokeSpec(methodHandleName, "invokeExact");
+                            parameterNames.forEach(invokeSpec::addArgument);
+                            final Spec returnSpec = returnType
+                                .map(typeUse -> Spec.returnStatement(Spec.cast(typeUse.apply(importData), invokeSpec)))
+                                .orElseGet(() -> Spec.statement(invokeSpec));
+                            final Spec optionalSpec;
+                            final Default defaultAnnotation = executableElement.getAnnotation(Default.class);
+                            if (defaultAnnotation != null) {
+                                final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(methodHandleName));
+                                ifStatement.addStatement(returnSpec);
+                                final String defaultValue = defaultAnnotation.value();
+                                if (!defaultValue.isBlank()) {
+                                    ifStatement.addElseClause(ElseClause.of(), elseClause ->
+                                        elseClause.addStatement(Spec.returnStatement(Spec.literal(defaultValue)))
+                                    );
+                                }
+                                optionalSpec = ifStatement;
+                            } else {
+                                optionalSpec = returnSpec;
+                            }
+                            tryCatchStatement.addStatement(optionalSpec);
+                            tryCatchStatement.addCatchClause(new CatchClause(
+                                importData.simplifyOrImport(Throwable.class),
+                                exceptionName
+                            ), catchClause ->
+                                catchClause.addStatement(Spec.throwStatement(new ConstructSpec(importData.simplifyOrImport(RuntimeException.class))
+                                    .addArgument(Spec.literal(exceptionName)))));
+                        })
+                    )
+                ));
+            });
         }
     }
 
@@ -123,8 +264,8 @@ public void generate(TypeElement typeElement) throws IOException {
                 processingEnv.getMessager().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 the name in @Downcall. For example: @Downcall(name = "%1$s")"""
+                         1) Add C as a prefix e.g. C%1$s
+                         2) Specify the name in @Downcall e.g. @Downcall(name = "%1$s")"""
                     .formatted(string));
                 return;
             }
@@ -142,8 +283,9 @@ public void generate(TypeElement typeElement) throws IOException {
             classSpec.setFinal(!nonFinal);
 
             addFields(classSpec);
+            addMethods(classSpec);
         });
-        importData.imports()
+        imports.imports()
             .stream()
             .filter(declaredTypeData -> !"java.lang".equals(declaredTypeData.packageName()))
             .map(DeclaredTypeData::toString)
@@ -167,14 +309,14 @@ public void generate(TypeElement typeElement) throws IOException {
         }
     }
 
-    private void addLookup(TypeElement typeElement) {
+    private String addLookup(TypeElement typeElement, Predicate insertUnderlineTest) {
         final Downcall downcall = typeElement.getAnnotation(Downcall.class);
         final String loader = typeElement.getAnnotationMirrors().stream()
             .filter(m -> Downcall.class.getCanonicalName().equals(m.getAnnotationType().toString()))
             .findFirst()
             .orElseThrow()
             .getElementValues().entrySet().stream()
-            .filter(e -> "loader()".equals(e.getKey().toString()))
+            .filter(e -> "loader".equals(e.getKey().getSimpleName().toString()))
             .findFirst()
             .map(e -> e.getValue().getValue().toString())
             .orElse(null);
@@ -200,34 +342,68 @@ private void addLookup(TypeElement typeElement) {
                 processingEnv.getMessager().printError("""
                     Couldn't find loader method in %s while %s required. Please mark it with @Loader"""
                     .formatted(loader, typeElement));
-                return;
+                return null;
             }
         }
+        final String s = tryInsertUnderline("LOOKUP", insertUnderlineTest);
         fields.add(new FieldData(
             null,
+            List.of(),
             AccessModifier.PRIVATE,
             true,
             true,
             TypeData.fromClass(SymbolLookup.class),
-            "_LOOKUP",
+            s,
             loader == null ?
                 importData -> new InvokeSpec(importData.simplifyOrImport(SymbolLookup.class), "libraryLookup")
                     .addArgument(libnameConstExp)
                     .addArgument(new InvokeSpec(importData.simplifyOrImport(Arena.class), "global")) :
                 importData -> new InvokeSpec(importData.simplifyOrImport(
-                    TypeData.detectType(processingEnv, loaderTypeElement.asType())
+                    processingEnv, loaderTypeElement.asType()
                 ), annotatedMethod.get().getSimpleName().toString())
                     .addArgument(libnameConstExp)
         ));
+        return s;
+    }
+
+    private Stream findElementAnnotations(
+        List annotationMirrors,
+        Class aClass
+    ) {
+        return annotationMirrors.stream()
+            .filter(mirror -> isAExtendsB(processingEnv,
+                mirror.getAnnotationType(),
+                getTypeElementFromClass(processingEnv, aClass).asType()));
+    }
+
+    private List getElementAnnotations(List> list, Element e) {
+        final var mirrors = e.getAnnotationMirrors();
+        return list.stream()
+            .filter(aClass -> e.getAnnotation(aClass) != null)
+            .mapMulti((aClass, consumer) -> findElementAnnotations(mirrors, aClass)
+                .map(mirror -> new AnnotationData(e.getAnnotation(aClass), mirror))
+                .forEach(consumer))
+            .toList();
+    }
+
+    private String getMethodEntrypoint(ExecutableElement executableElement) {
+        final Entrypoint entrypoint = executableElement.getAnnotation(Entrypoint.class);
+        if (entrypoint != null) {
+            final String value = entrypoint.value();
+            if (!value.isBlank()) {
+                return value;
+            }
+        }
+        return executableElement.getSimpleName().toString();
     }
 
     private void addFields(ClassSpec spec) {
         fields.forEach(fieldData -> {
             final TypeData type = fieldData.type();
             spec.addField(new VariableStatement(
-                importData.simplifyOrImport(type),
+                imports.simplifyOrImport(type),
                 fieldData.name(),
-                fieldData.value().apply(importData)
+                fieldData.value().apply(imports)
             ).setDocument(fieldData.document())
                 .setAccessModifier(fieldData.accessModifier())
                 .setStatic(fieldData.staticField())
@@ -235,6 +411,28 @@ private void addFields(ClassSpec spec) {
         });
     }
 
+    private void addMethods(ClassSpec spec) {
+        functionDataList.forEach(functionData ->
+            spec.addMethod(new MethodSpec(functionData.returnType().apply(imports), functionData.name()), methodSpec -> {
+                methodSpec.setDocument(functionData.document());
+                functionData.annotations().forEach(annotationData -> {
+                    final AnnotationMirror annotationMirror = annotationData.mirror();
+                    methodSpec.addAnnotation(new AnnotationSpec(
+                        imports.simplifyOrImport(processingEnv, annotationMirror.getAnnotationType())
+                    ).also(annotationSpec ->
+                        annotationMirror.getElementValues().forEach((executableElement, annotationValue) ->
+                            annotationSpec.addArgument(executableElement.getSimpleName().toString(), annotationValue.toString()))));
+                });
+                methodSpec.setAccessModifier(functionData.accessModifier());
+                methodSpec.setStatic(true);
+                functionData.parameters().forEach(parameterData ->
+                    methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()))
+                );
+                functionData.statements().forEach(typeUse ->
+                    methodSpec.addStatement(typeUse.apply(imports)));
+            }));
+    }
+
     private String getDocComment(ProcessingEnvironment env, Element e) {
         return env.getElementUtils().getDocComment(e);
     }
diff --git a/src/main/java/overrun/marshal/gen2/FieldData.java b/src/main/java/overrun/marshal/gen2/FieldData.java
index 3e0236e..cdae6d9 100644
--- a/src/main/java/overrun/marshal/gen2/FieldData.java
+++ b/src/main/java/overrun/marshal/gen2/FieldData.java
@@ -18,10 +18,13 @@
 
 import overrun.marshal.gen.AccessModifier;
 
+import java.util.List;
+
 /**
  * Holds field
  *
  * @param document       the document
+ * @param annotations    the annotations
  * @param accessModifier the access modifier
  * @param staticField    the static field
  * @param finalField     the final field
@@ -33,6 +36,7 @@
  */
 public record FieldData(
     String document,
+    List annotations,
     AccessModifier accessModifier,
     boolean staticField,
     boolean finalField,
diff --git a/src/main/java/overrun/marshal/gen2/FunctionData.java b/src/main/java/overrun/marshal/gen2/FunctionData.java
new file mode 100644
index 0000000..099c129
--- /dev/null
+++ b/src/main/java/overrun/marshal/gen2/FunctionData.java
@@ -0,0 +1,45 @@
+/*
+ * 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.gen2;
+
+import overrun.marshal.gen.AccessModifier;
+
+import java.util.List;
+
+/**
+ * Holds downcall function
+ *
+ * @param document       the document
+ * @param annotations    the annotations
+ * @param accessModifier the access modifier
+ * @param returnType     the return type
+ * @param name           the name
+ * @param parameters     the parameters
+ * @param statements     the statement
+ * @author squid233
+ * @since 0.1.0
+ */
+public record FunctionData(
+    String document,
+    List annotations,
+    AccessModifier accessModifier,
+    TypeUse returnType,
+    String name,
+    List parameters,
+    List statements
+) {
+}
diff --git a/src/main/java/overrun/marshal/gen2/ImportData.java b/src/main/java/overrun/marshal/gen2/ImportData.java
index f37e96d..774a4b9 100644
--- a/src/main/java/overrun/marshal/gen2/ImportData.java
+++ b/src/main/java/overrun/marshal/gen2/ImportData.java
@@ -16,6 +16,8 @@
 
 package overrun.marshal.gen2;
 
+import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.type.TypeMirror;
 import java.util.List;
 
 /**
@@ -49,7 +51,7 @@ private boolean addImport(TypeData typeData) {
      */
     public String simplifyOrImport(TypeData typeData) {
         return switch (typeData) {
-            case ArrayTypeData arrayTypeData -> simplifyOrImport(arrayTypeData) + "[]";
+            case ArrayTypeData arrayTypeData -> simplifyOrImport(arrayTypeData.componentType()) + "[]";
             case DeclaredTypeData declaredTypeData when (isImported(typeData) || addImport(typeData)) ->
                 declaredTypeData.name();
             default -> typeData.toString();
@@ -65,4 +67,15 @@ case DeclaredTypeData declaredTypeData when (isImported(typeData) || addImport(t
     public String simplifyOrImport(Class aClass) {
         return simplifyOrImport(TypeData.fromClass(aClass));
     }
+
+    /**
+     * Simplifies type name or import
+     *
+     * @param env        the processing environment
+     * @param typeMirror the type mirror
+     * @return the name
+     */
+    public String simplifyOrImport(ProcessingEnvironment env, TypeMirror typeMirror) {
+        return simplifyOrImport(TypeData.detectType(env, typeMirror));
+    }
 }
diff --git a/src/main/java/overrun/marshal/gen2/MethodHandleData.java b/src/main/java/overrun/marshal/gen2/MethodHandleData.java
new file mode 100644
index 0000000..5b366fc
--- /dev/null
+++ b/src/main/java/overrun/marshal/gen2/MethodHandleData.java
@@ -0,0 +1,45 @@
+/*
+ * 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.gen2;
+
+import overrun.marshal.gen.AccessModifier;
+
+import javax.lang.model.element.ExecutableElement;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Holds method handle
+ *
+ * @param executableElement the executable element
+ * @param accessModifier    the access modifier
+ * @param name              the name
+ * @param returnType        the return type
+ * @param parameterTypes    the parameter types
+ * @param optional          optional
+ * @author squid233
+ * @since 0.1.0
+ */
+public record MethodHandleData(
+    ExecutableElement executableElement,
+    AccessModifier accessModifier,
+    String name,
+    Optional returnType,
+    List parameterTypes,
+    boolean optional
+) {
+}
diff --git a/src/main/java/overrun/marshal/gen2/ParameterData.java b/src/main/java/overrun/marshal/gen2/ParameterData.java
new file mode 100644
index 0000000..1f41c9f
--- /dev/null
+++ b/src/main/java/overrun/marshal/gen2/ParameterData.java
@@ -0,0 +1,37 @@
+/*
+ * 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.gen2;
+
+import java.util.List;
+
+/**
+ * Holds parameter
+ *
+ * @param document    the document
+ * @param annotations the annotations
+ * @param type        the type
+ * @param name        the name
+ * @author squid233
+ * @since 0.1.0
+ */
+public record ParameterData(
+    String document,
+    List annotations,
+    TypeUse type,
+    String name
+) {
+}
diff --git a/src/main/java/overrun/marshal/gen2/TypeData.java b/src/main/java/overrun/marshal/gen2/TypeData.java
index 58acfb7..34057f3 100644
--- a/src/main/java/overrun/marshal/gen2/TypeData.java
+++ b/src/main/java/overrun/marshal/gen2/TypeData.java
@@ -28,7 +28,7 @@
  * @author squid233
  * @since 0.1.0
  */
-public sealed interface TypeData permits ArrayTypeData, DeclaredTypeData, PrimitiveTypeData {
+public sealed interface TypeData permits ArrayTypeData, DeclaredTypeData, PrimitiveTypeData, VoidTypeData {
     /**
      * Detects type
      *
@@ -51,6 +51,9 @@ static TypeData detectType(ProcessingEnvironment env, TypeMirror type) {
             final String simpleName = typeElement.getSimpleName().toString();
             return new DeclaredTypeData(qualifiedName.substring(0, qualifiedName.lastIndexOf(simpleName) - 1), simpleName);
         }
+        if (typeKind == TypeKind.VOID) {
+            return VoidTypeData.INSTANCE;
+        }
         throw new IllegalArgumentException("Unknown type: " + type);
     }
 
diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java
index 3aa69bf..0777690 100644
--- a/src/main/java/overrun/marshal/gen2/TypeUse.java
+++ b/src/main/java/overrun/marshal/gen2/TypeUse.java
@@ -16,8 +16,16 @@
 
 package overrun.marshal.gen2;
 
+import overrun.marshal.gen.struct.StructRef;
 import overrun.marshal.gen1.Spec;
 
+import javax.lang.model.element.Element;
+import javax.lang.model.type.TypeKind;
+import javax.lang.model.type.TypeMirror;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.util.Optional;
+
 /**
  * Holds spec with type data
  *
@@ -25,6 +33,68 @@
  * @since 0.1.0
  */
 public interface TypeUse {
+    private static String valueLayoutString(TypeKind typeKind) {
+        return switch (typeKind) {
+            case BOOLEAN -> "JAVA_BOOLEAN";
+            case CHAR -> "JAVA_CHAR";
+            case BYTE -> "JAVA_BYTE";
+            case SHORT -> "JAVA_SHORT";
+            case INT -> "JAVA_INT";
+            case LONG -> "JAVA_LONG";
+            case FLOAT -> "JAVA_FLOAT";
+            case DOUBLE -> "JAVA_DOUBLE";
+            case ARRAY, DECLARED -> "ADDRESS";
+            default -> null;
+        };
+    }
+
+    private static Optional valueLayout(String layout) {
+        if (layout == null) {
+            return Optional.empty();
+        }
+        return Optional.of(importData ->
+            Spec.accessSpec(importData.simplifyOrImport(ValueLayout.class), layout));
+    }
+
+    /**
+     * Gets the value layout
+     *
+     * @param typeMirror the type mirror
+     * @return the value layout
+     */
+    static Optional valueLayout(TypeMirror typeMirror) {
+        return valueLayout(valueLayoutString(typeMirror.getKind()));
+    }
+
+    /**
+     * Gets the value layout
+     *
+     * @param element the element
+     * @return the value layout
+     */
+    static Optional valueLayout(Element element) {
+        return element.getAnnotation(StructRef.class) != null ?
+            valueLayout("ADDRESS") :
+            valueLayout(element.asType());
+    }
+
+    /**
+     * Converts to the downcall type
+     *
+     * @param typeMirror the type mirror
+     * @return the downcall type
+     */
+    static Optional toDowncallType(TypeMirror typeMirror) {
+        final TypeKind typeKind = typeMirror.getKind();
+        if (typeKind.isPrimitive()) {
+            return Optional.of(_ -> Spec.literal(typeMirror.toString()));
+        }
+        return switch (typeKind) {
+            case ARRAY, DECLARED -> Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class)));
+            default -> Optional.empty();
+        };
+    }
+
     /**
      * Applies the imports
      *
diff --git a/src/main/java/overrun/marshal/gen2/VoidTypeData.java b/src/main/java/overrun/marshal/gen2/VoidTypeData.java
new file mode 100644
index 0000000..6a4c657
--- /dev/null
+++ b/src/main/java/overrun/marshal/gen2/VoidTypeData.java
@@ -0,0 +1,32 @@
+/*
+ * 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.gen2;
+
+/**
+ * Holds void
+ *
+ * @author squid233
+ * @since 0.1.0
+ */
+public final class VoidTypeData implements TypeData {
+    static final VoidTypeData INSTANCE = new VoidTypeData();
+
+    @Override
+    public String toString() {
+        return "void";
+    }
+}
diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java
index fc23578..fcc24b9 100644
--- a/src/main/java/overrun/marshal/internal/Util.java
+++ b/src/main/java/overrun/marshal/internal/Util.java
@@ -55,6 +55,20 @@ public static String capitalize(String str) {
         return String.valueOf((char) titleCase);
     }
 
+    /**
+     * try insert underline
+     *
+     * @param name      the name
+     * @param predicate the predicate
+     * @return the string
+     */
+    public static String tryInsertUnderline(String name, Predicate predicate) {
+        if (predicate.test(name)) {
+            return tryInsertUnderline("_" + name, predicate);
+        }
+        return name;
+    }
+
     /**
      * Invalid type
      *
@@ -71,6 +85,7 @@ public static IllegalStateException invalidType(TypeMirror typeMirror) {
      * @param rawClassName rawClassName
      * @return name
      */
+    @Deprecated(since = "0.1.0")
     public static String simplify(String rawClassName) {
         if (isString(rawClassName)) {
             return String.class.getSimpleName();
@@ -192,6 +207,7 @@ public static TypeMirror getArrayComponentType(TypeMirror typeMirror) {
      * @param nameString  nameString
      * @return insertUnderline
      */
+    @Deprecated(since = "0.1.0")
     public static String insertUnderline(String builtinName, String nameString) {
         return builtinName.equals(nameString) ? "_" + builtinName : builtinName;
     }

From 30e8a76c0116f63f0c53109354265f4186fa8b15 Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Fri, 12 Jan 2024 22:25:26 +0800
Subject: [PATCH 07/28] Update downcall generator

---
 README.md                                     |  92 ++---
 .../overrun/marshal/test/CDowncallTest.java   |  12 +
 src/main/java/module-info.java                |   1 +
 .../java/overrun/marshal/StructProcessor.java |   1 +
 src/main/java/overrun/marshal/gen/CEnum.java  |   1 +
 .../java/overrun/marshal/gen/Overload.java    |   2 +-
 src/main/java/overrun/marshal/gen1/Spec.java  |  14 +
 .../overrun/marshal/gen2/DowncallData.java    | 338 ++++++++++++------
 .../java/overrun/marshal/gen2/TypeData.java   |   9 +-
 .../java/overrun/marshal/gen2/TypeUse.java    |  69 +++-
 .../java/overrun/marshal/internal/Util.java   |  24 +-
 .../marshal/{gen => }/struct/IStruct.java     |   2 +-
 12 files changed, 367 insertions(+), 198 deletions(-)
 rename src/main/java/overrun/marshal/{gen => }/struct/IStruct.java (97%)

diff --git a/README.md b/README.md
index 71020df..c7833de 100644
--- a/README.md
+++ b/README.md
@@ -13,8 +13,10 @@ This library requires JDK 22 or newer.
 ```java
 import overrun.marshal.gen.*;
 
+import java.lang.foreign.Arena;
 import java.lang.foreign.MemorySegment;
 import java.lang.foreign.SegmentAllocator;
+import java.lang.foreign.ValueLayout;
 
 /**
  * GLFW constants and functions
@@ -33,86 +35,60 @@ interface CGLFW {
 
     /**
      * Sets the swap interval.
-     * 

- * You can set the access modifier. * * @param interval the interval */ - @Access(AccessModifier.PROTECTED) void glfwSwapInterval(int interval); /** - * Custom method body - */ - @Custom(""" - glfwSwapInterval(1);""") - void glfwEnableVSync(); - - /** - * {@return default value if the function was not found} - */ - @Default("0") - @Entrypoint("glfwGetTime") - double getTime(); - - /** - * Sized array. - * Note: this method doesn't exist in GLFW - *

- * You can mark methods with Critical - * - * @param arr The array - */ - @Critical(allowHeapAccess = true) - void sizedArray(@Sized(2) int[] arr); - - /** - * A simple method - *

- * Note: annotation Ref in this method is unnecessary; - * however, you can use it to mark + * Gets the window position. + * Marks the array parameter as a place to store the value returned by C * * @param window the window * @param posX the position x * @param posY the position y */ - void glfwGetWindowPos(MemorySegment window, @Ref MemorySegment posX, @Ref MemorySegment posY); - - /** - * Overload another method with the same name - * - * @param window the window - * @param posX the array where to store the position x - * @param posY the array where to store the position y - */ - @Overload void glfwGetWindowPos(MemorySegment window, @Ref int[] posX, @Ref int[] posY); /** - * {@return a UTF-16 string} + * Creates a window. + * Requires a segment allocator to allocate the string; + * if the first parameter is not segment allocator, then the memory stack is used + * + * @param allocator the segment allocator + * @param width the width + * @param height the height + * @param title the title + * @param monitor the monitor + * @param share the share + * @return the window */ - @StrCharset("UTF-16") - String returnString(); + MemorySegment glfwCreateWindow(SegmentAllocator allocator, int width, int height, String title, MemorySegment monitor, MemorySegment share); } class Main { public static void main(String[] args) { int key = GLFW.GLFW_KEY_A; GLFW.glfwSwapInterval(1); - GLFW.glfwEnableVSync(); - double time = GLFW.getTime(); - GLFW.sizedArray(new int[]{4, 2}); - MemorySegment windowHandle = /*...*/createWindow(); - // MemoryStack is a placeholder type - try (MemoryStack stack = /*...*/stackPush()) { - MemorySegment bufX1 = stack.callocInt(1); - MemorySegment bufY1 = stack.callocInt(1); - int[] bufX2 = {0}; - int[] bufY2 = {0}; - GLFW.glfwGetWindowPos(windowHandle, bufX1, bufY1); - GLFW.glfwGetWindowPos(windowHandle, bufX2, bufY2); + + // Arena + try (var arena = Arena.ofConfined()) { + MemorySegment windowHandle = glfwCreateWindow(arena, + 800, + 600, + "The title", + MemorySegment.NULL, + MemorySegment.NULL); + + // ref + // 1. by array + int[] ax = {0}, ay = {0}; + glfwGetWindowPos(windowHandle, ax, ay); + // 2. by segment + var sx = arena.allocate(ValueLayout.JAVA_INT), + sy = arena.allocate(ValueLayout.JAVA_INT); + glfwGetWindowPos(windowHandle, sx, sy); } - String s = GLFW.returnString(); } } ``` diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 9457e34..4437281 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -162,6 +162,11 @@ interface CDowncallTest { @StructRef("overrun.marshal.test.StructTest") Object testReturnStruct(); + /** + * A document + * + * @return the segment + */ @ByValue @Entrypoint("returnByValueStruct") @StructRef("overrun.marshal.test.StructTest") @@ -195,6 +200,13 @@ interface CDowncallTest { void testWithoutOverload(int[] arr); + void testDuplicateName(int testDuplicateName); + + MyEnum testReturnEnumWithoutOverload(); + + @Sized(4) + int[] testReturnSizedArrWithoutOverload(); + /** * This is a test that tests all features. * diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index fc4d9e7..3ff7ff0 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -24,6 +24,7 @@ exports overrun.marshal; exports overrun.marshal.gen; exports overrun.marshal.gen.struct; + exports overrun.marshal.struct; requires java.compiler; } diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 3085905..78cd5ee 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -20,6 +20,7 @@ import overrun.marshal.gen.struct.*; import overrun.marshal.gen1.*; import overrun.marshal.internal.Processor; +import overrun.marshal.struct.IStruct; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.TypeElement; diff --git a/src/main/java/overrun/marshal/gen/CEnum.java b/src/main/java/overrun/marshal/gen/CEnum.java index 351fe31..8ebf454 100644 --- a/src/main/java/overrun/marshal/gen/CEnum.java +++ b/src/main/java/overrun/marshal/gen/CEnum.java @@ -52,6 +52,7 @@ public interface CEnum { * {@return the value of the enum} */ int value(); + /** * Marks a static method as an enum value wrapper. *

diff --git a/src/main/java/overrun/marshal/gen/Overload.java b/src/main/java/overrun/marshal/gen/Overload.java index d7fafe4..6947501 100644 --- a/src/main/java/overrun/marshal/gen/Overload.java +++ b/src/main/java/overrun/marshal/gen/Overload.java @@ -28,7 +28,7 @@ *

{@code
  * void nset(MemorySegment vec);
  *
- * @Overload
+ * @Overload("nset")
  * void set(int[] vec);
  * }
* diff --git a/src/main/java/overrun/marshal/gen1/Spec.java b/src/main/java/overrun/marshal/gen1/Spec.java index be0752c..a5d84d5 100644 --- a/src/main/java/overrun/marshal/gen1/Spec.java +++ b/src/main/java/overrun/marshal/gen1/Spec.java @@ -152,6 +152,20 @@ static Spec accessSpec(String object, String member) { return literal(object + '.' + member); } + /** + * Create access spec + * + * @param object object + * @param member member + * @return spec + */ + static Spec accessSpec(Spec object, String member) { + return (builder, indent) -> { + object.append(builder, indent); + builder.append('.').append(member); + }; + } + /** * Create ternary operator spec * diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index a073914..041c151 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -18,25 +18,24 @@ import overrun.marshal.gen.*; import overrun.marshal.gen.struct.ByValue; +import overrun.marshal.gen.struct.StructRef; import overrun.marshal.gen1.*; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; +import javax.lang.model.element.*; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.TypeKind; +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.annotation.Annotation; -import java.lang.foreign.Arena; -import java.lang.foreign.FunctionDescriptor; -import java.lang.foreign.Linker; -import java.lang.foreign.SymbolLookup; +import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.util.*; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; import static overrun.marshal.internal.Util.*; @@ -79,13 +78,13 @@ public DowncallData(ProcessingEnvironment processingEnv) { this.processingEnv = processingEnv; } - private void processDowncallType(TypeElement typeElement) { + private void processDowncallType(TypeElement typeElement, String simpleClassName) { final var enclosedElements = typeElement.getEnclosedElements(); // annotations final Downcall downcall = typeElement.getAnnotation(Downcall.class); - document = getDocComment(processingEnv, typeElement); + document = getDocComment(typeElement); nonFinal = downcall.nonFinal(); // process fields @@ -98,21 +97,21 @@ private void processDowncallType(TypeElement typeElement) { return; } fields.add(new FieldData( - getDocComment(processingEnv, element), + getDocComment(element), List.of(), AccessModifier.PUBLIC, true, true, TypeData.detectType(processingEnv, element.asType()), element.getSimpleName().toString(), - _ -> Spec.literal(getConstExp(processingEnv, constantValue)) + _ -> Spec.literal(getConstExp(constantValue)) )); }); final var methods = ElementFilter.methodsIn(enclosedElements); if (!methods.isEmpty()) { // collect method handles - final Map methodHandleDataList = LinkedHashMap.newLinkedHashMap(methods.size()); + final Map methodHandleDataMap = LinkedHashMap.newLinkedHashMap(methods.size()); methods.forEach(executableElement -> { if (executableElement.getAnnotation(Skip.class) != null || executableElement.getAnnotation(Overload.class) != null) { @@ -120,24 +119,62 @@ private void processDowncallType(TypeElement typeElement) { } final Access access = executableElement.getAnnotation(Access.class); final String entrypoint = getMethodEntrypoint(executableElement); - methodHandleDataList.put(entrypoint, + final TypeMirror returnType = executableElement.getReturnType(); + methodHandleDataMap.put(entrypoint, new MethodHandleData( executableElement, access != null ? access.value() : AccessModifier.PUBLIC, entrypoint, - TypeUse.valueLayout(executableElement.getReturnType()), + TypeUse.valueLayout(processingEnv, returnType) + .map(typeUse -> importData -> { + final ByValue byValue = executableElement.getAnnotation(ByValue.class); + final StructRef structRef = executableElement.getAnnotation(StructRef.class); + final SizedSeg sizedSeg = executableElement.getAnnotation(SizedSeg.class); + final Sized sized = executableElement.getAnnotation(Sized.class); + final boolean structRefNotNull = structRef != null; + final boolean sizedSegNotNull = sizedSeg != null; + final boolean sizedNotNull = sized != null; + final Spec spec = typeUse.apply(importData); + if (structRefNotNull || sizedSegNotNull || sizedNotNull) { + final InvokeSpec invokeSpec = new InvokeSpec(spec, "withTargetLayout"); + if (structRefNotNull) { + final Spec layout = Spec.accessSpec(structRef.value(), "LAYOUT"); + return byValue != null ? layout : invokeSpec.addArgument(layout); + } + if (sizedSegNotNull) { + return invokeSpec.addArgument(new InvokeSpec( + importData.simplifyOrImport(MemoryLayout.class), + "sequenceLayout" + ).addArgument(getConstExp(sizedSeg.value())) + .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData))); + } + return invokeSpec.addArgument(new InvokeSpec( + importData.simplifyOrImport(MemoryLayout.class), + "sequenceLayout" + ).also(invokeSpec1 -> { + invokeSpec1.addArgument(getConstExp(sized.value())); + final Optional seqLayout; + if (returnType instanceof ArrayType arrayType) { + seqLayout = TypeUse.valueLayout(processingEnv, arrayType.getComponentType()); + } else { + seqLayout = TypeUse.valueLayout(byte.class); + } + invokeSpec1.addArgument(seqLayout.orElseThrow().apply(importData)); + })); + } + return spec; + }), executableElement.getParameters() .stream() .filter(element -> element.getAnnotation(Skip.class) == null) - .map(TypeUse::valueLayout) - .map(Optional::orElseThrow) + .map(element -> TypeUse.valueLayout(processingEnv, element).orElseThrow()) .toList(), executableElement.getAnnotation(Default.class) != null )); }); // add linker and lookup - final Predicate containsKey = methodHandleDataList::containsKey; + final Predicate containsKey = methodHandleDataMap::containsKey; final String lookupName = addLookup(typeElement, containsKey); final String linkerName = tryInsertUnderline("LINKER", containsKey); fields.add(new FieldData( @@ -151,97 +188,69 @@ private void processDowncallType(TypeElement typeElement) { importData -> new InvokeSpec(importData.simplifyOrImport(Linker.class), "nativeLinker") )); - // add methods - final var fieldNames = fields.stream().map(FieldData::name).toList(); - methodHandleDataList.values().forEach(methodHandleData -> { - final String name = methodHandleData.name(); - fields.add(new FieldData( - " The method handle of {@code " + name + "}.", - List.of(), - methodHandleData.accessModifier(), - true, - true, - TypeData.fromClass(MethodHandle.class), - name, - importData -> { - final InvokeSpec invokeSpec = new InvokeSpec( - new InvokeSpec(lookupName, "find") - .addArgument(getConstExp(processingEnv, name)), "map" - ).addArgument(new LambdaSpec("s") - .addStatementThis(new InvokeSpec(linkerName, "downcallHandle") - .addArgument("s") - .also(invokeSpec1 -> { - final var returnType = methodHandleData.returnType(); - final String methodName = returnType.isPresent() ? "of" : "ofVoid"; - invokeSpec1.addArgument(new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName) - .also(invokeSpec2 -> { - returnType.ifPresent(typeUse -> invokeSpec2.addArgument(typeUse.apply(importData))); - methodHandleData.parameterTypes().forEach(typeUse -> - invokeSpec2.addArgument(typeUse.apply(importData))); - })); - }))); - if (methodHandleData.optional()) { - return new InvokeSpec(invokeSpec, "orElse").addArgument("null"); - } - return new InvokeSpec( - invokeSpec, "orElseThrow" - ); - } - )); - - final ExecutableElement executableElement = methodHandleData.executableElement(); - final var returnType = TypeUse.toDowncallType(executableElement.getReturnType()); - final var parameters = executableElement.getParameters().stream() - .filter(element -> element.getAnnotation(Skip.class) == null) - .map(element -> new ParameterData( - null, - getElementAnnotations(PARAMETER_ANNOTATIONS, element), - TypeUse.toDowncallType(element.asType()).orElseThrow(), - tryInsertUnderline(element.getSimpleName().toString(), fieldNames::contains) - )) - .toList(); - final var parameterNames = parameters.stream().map(ParameterData::name).toList(); - functionDataList.add(new FunctionData( - getDocComment(processingEnv, executableElement), - getElementAnnotations(METHOD_ANNOTATIONS, executableElement), - methodHandleData.accessModifier(), - returnType.orElseGet(() -> _ -> Spec.literal("void")), - executableElement.getSimpleName().toString(), - parameters, - List.of( - importData -> new TryCatchStatement().also(tryCatchStatement -> { - final String exceptionName = tryInsertUnderline("e", s -> fieldNames.contains(s) || parameterNames.contains(s)); - final String methodHandleName = methodHandleData.name(); - final InvokeSpec invokeSpec = new InvokeSpec(methodHandleName, "invokeExact"); - parameterNames.forEach(invokeSpec::addArgument); - final Spec returnSpec = returnType - .map(typeUse -> Spec.returnStatement(Spec.cast(typeUse.apply(importData), invokeSpec))) - .orElseGet(() -> Spec.statement(invokeSpec)); - final Spec optionalSpec; - final Default defaultAnnotation = executableElement.getAnnotation(Default.class); - if (defaultAnnotation != null) { - final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(methodHandleName)); - ifStatement.addStatement(returnSpec); - final String defaultValue = defaultAnnotation.value(); - if (!defaultValue.isBlank()) { - ifStatement.addElseClause(ElseClause.of(), elseClause -> - elseClause.addStatement(Spec.returnStatement(Spec.literal(defaultValue))) - ); + // adds methods + // method handles + methodHandleDataMap.forEach((name, methodHandleData) -> fields.add(new FieldData( + " The method handle of {@code " + name + "}.", + List.of(), + methodHandleData.accessModifier(), + true, + true, + TypeData.fromClass(MethodHandle.class), + methodHandleData.name(), + importData -> { + final InvokeSpec invokeSpec = new InvokeSpec( + new InvokeSpec(lookupName, "find") + .addArgument(getConstExp(name)), "map" + ).addArgument(new LambdaSpec("s") + .addStatementThis(new InvokeSpec(linkerName, "downcallHandle") + .addArgument("s") + .also(downcallHandle -> { + final var returnType = methodHandleData.returnType(); + final String methodName = returnType.isPresent() ? "of" : "ofVoid"; + downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName) + .also(fdOf -> { + returnType.ifPresent(typeUse -> { + final Spec spec = typeUse.apply(importData); + fdOf.addArgument(spec); + }); + methodHandleData.parameterTypes().forEach(typeUse -> + fdOf.addArgument(typeUse.apply(importData))); + })); + final Critical critical = methodHandleData.executableElement().getAnnotation(Critical.class); + if (critical != null) { + downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(Linker.Option.class), "critical") + .addArgument(getConstExp(critical.allowHeapAccess()))); } - optionalSpec = ifStatement; - } else { - optionalSpec = returnSpec; - } - tryCatchStatement.addStatement(optionalSpec); - tryCatchStatement.addCatchClause(new CatchClause( - importData.simplifyOrImport(Throwable.class), - exceptionName - ), catchClause -> - catchClause.addStatement(Spec.throwStatement(new ConstructSpec(importData.simplifyOrImport(RuntimeException.class)) - .addArgument(Spec.literal(exceptionName))))); - }) - ) - )); + }))); + if (methodHandleData.optional()) { + return new InvokeSpec(invokeSpec, "orElse").addArgument("null"); + } + return new InvokeSpec( + invokeSpec, "orElseThrow" + ); + } + ))); + + // methods + final var fieldNames = fields.stream().map(FieldData::name).toList(); + methods.forEach(executableElement -> { + if (executableElement.getAnnotation(Skip.class) != null) { + return; + } + final Overload overload = executableElement.getAnnotation(Overload.class); + final boolean isOverload = overload != null || containsNonDowncallType(executableElement); + final MethodHandleData methodHandleData = methodHandleDataMap.get(getMethodEntrypoint(executableElement)); + if (methodHandleData == null) { + return; + } + + if (isOverload) { + // TODO: 2024/1/12 squid233: Overload + return; + } + + addDowncallMethod(simpleClassName, executableElement, fieldNames, methodHandleData); }); } } @@ -273,7 +282,7 @@ public void generate(TypeElement typeElement) throws IOException { simpleClassName = downcall.name(); } - processDowncallType(typeElement); + processDowncallType(typeElement, simpleClassName); final SourceFile file = new SourceFile(declaredTypeDataOfTypeElement.packageName()); file.addClass(simpleClassName, classSpec -> { @@ -309,6 +318,93 @@ public void generate(TypeElement typeElement) throws IOException { } } + private void addDowncallMethod(String simpleClassName, + ExecutableElement executableElement, + List fieldNames, + MethodHandleData methodHandleData) { + final String methodHandleName = methodHandleData.name(); + final var returnType = TypeUse.toDowncallType(processingEnv, executableElement.getReturnType()); + + final var parameters = executableElement.getParameters().stream() + .filter(element -> element.getAnnotation(Skip.class) == null) + .map(element -> new ParameterData( + null, + getElementAnnotations(PARAMETER_ANNOTATIONS, element), + TypeUse.toDowncallType(processingEnv, element.asType()).orElseThrow(), + element.getSimpleName().toString() + )) + .collect(Collectors.toList()); + final var parameterNames = parameters.stream() + .map(ParameterData::name) + .collect(Collectors.toList()); + + if (executableElement.getAnnotation(ByValue.class) != null) { + final String allocatorName = tryInsertUnderline("segmentAllocator", parameterNames::contains); + parameters.addFirst(new ParameterData( + "the segment allocator", + List.of(), + importData -> Spec.literal(importData.simplifyOrImport(SegmentAllocator.class)), + allocatorName + )); + parameterNames.addFirst(allocatorName); + } + + final boolean shouldAddInvoker = parameterNames.stream().anyMatch(fieldNames::contains); + + String docComment = getDocComment(executableElement); + if (docComment != null) { + docComment += parameters.stream() + .filter(parameterData -> parameterData.document() != null) + .map(parameterData -> " @param " + parameterData.name() + " " + parameterData.document()) + .collect(Collectors.joining("\n")); + } + functionDataList.add(new FunctionData( + docComment, + getElementAnnotations(METHOD_ANNOTATIONS, executableElement), + methodHandleData.accessModifier(), + returnType.orElseGet(() -> _ -> Spec.literal("void")), + executableElement.getSimpleName().toString(), + parameters, + List.of( + importData -> new TryCatchStatement().also(tryCatchStatement -> { + final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ? + Spec.accessSpec(simpleClassName, methodHandleName) : + Spec.literal(methodHandleName), "invokeExact"); + parameterNames.forEach(invokeSpec::addArgument); + + final Spec returnSpec = returnType + .map(typeUse -> Spec.returnStatement(Spec.cast(typeUse.apply(importData), invokeSpec))) + .orElseGet(() -> Spec.statement(invokeSpec)); + + final Spec optionalSpec; + final Default defaultAnnotation = executableElement.getAnnotation(Default.class); + if (defaultAnnotation != null) { + final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(methodHandleName)); + ifStatement.addStatement(returnSpec); + final String defaultValue = defaultAnnotation.value(); + if (!defaultValue.isBlank()) { + ifStatement.addElseClause(ElseClause.of(), elseClause -> + elseClause.addStatement(Spec.returnStatement(Spec.literal(defaultValue))) + ); + } + optionalSpec = ifStatement; + } else { + optionalSpec = returnSpec; + } + + final String exceptionName = tryInsertUnderline("e", s -> fieldNames.contains(s) || parameterNames.contains(s)); + tryCatchStatement.addStatement(optionalSpec); + tryCatchStatement.addCatchClause(new CatchClause( + importData.simplifyOrImport(Throwable.class), + exceptionName + ), catchClause -> + catchClause.addStatement(Spec.throwStatement(new ConstructSpec(importData.simplifyOrImport(RuntimeException.class)) + .addArgument(Spec.literal(exceptionName))))); + }) + ) + )); + } + private String addLookup(TypeElement typeElement, Predicate insertUnderlineTest) { final Downcall downcall = typeElement.getAnnotation(Downcall.class); final String loader = typeElement.getAnnotationMirrors().stream() @@ -321,14 +417,14 @@ private String addLookup(TypeElement typeElement, Predicate insertUnderl .map(e -> e.getValue().getValue().toString()) .orElse(null); final String libname = downcall.libname(); - final String libnameConstExp = getConstExp(processingEnv, libname); + final String libnameConstExp = getConstExp(libname); final TypeElement loaderTypeElement; final Optional annotatedMethod; if (loader == null) { loaderTypeElement = null; annotatedMethod = Optional.empty(); } else { - loaderTypeElement = processingEnv.getElementUtils().getTypeElement(loader); + loaderTypeElement = getTypeElementFromClass(processingEnv, loader); annotatedMethod = findAnnotatedMethod(loaderTypeElement, Loader.class, executableElement -> { @@ -373,7 +469,7 @@ private Stream findElementAnnotations( return annotationMirrors.stream() .filter(mirror -> isAExtendsB(processingEnv, mirror.getAnnotationType(), - getTypeElementFromClass(processingEnv, aClass).asType())); + aClass)); } private List getElementAnnotations(List> list, Element e) { @@ -397,6 +493,16 @@ private String getMethodEntrypoint(ExecutableElement executableElement) { return executableElement.getSimpleName().toString(); } + private boolean containsNonDowncallType(ExecutableElement executableElement) { + return executableElement.getParameters().stream() + .anyMatch(element -> { + final TypeMirror type = element.asType(); + final TypeKind typeKind = type.getKind(); + return !(typeKind.isPrimitive() || + typeKind == TypeKind.DECLARED && isSameClass(type, MemorySegment.class)); + }); + } + private void addFields(ClassSpec spec) { fields.forEach(fieldData -> { final TypeData type = fieldData.type(); @@ -433,11 +539,11 @@ private void addMethods(ClassSpec spec) { })); } - private String getDocComment(ProcessingEnvironment env, Element e) { - return env.getElementUtils().getDocComment(e); + private String getDocComment(Element e) { + return processingEnv.getElementUtils().getDocComment(e); } - private String getConstExp(ProcessingEnvironment env, Object v) { - return env.getElementUtils().getConstantExpression(v); + private String getConstExp(Object v) { + return processingEnv.getElementUtils().getConstantExpression(v); } } diff --git a/src/main/java/overrun/marshal/gen2/TypeData.java b/src/main/java/overrun/marshal/gen2/TypeData.java index 34057f3..b76742f 100644 --- a/src/main/java/overrun/marshal/gen2/TypeData.java +++ b/src/main/java/overrun/marshal/gen2/TypeData.java @@ -41,8 +41,7 @@ static TypeData detectType(ProcessingEnvironment env, TypeMirror type) { if (typeKind.isPrimitive()) { return new PrimitiveTypeData(type.toString()); } - if (typeKind == TypeKind.ARRAY && - type instanceof ArrayType arrayType) { + if (type instanceof ArrayType arrayType) { return new ArrayTypeData(detectType(env, arrayType.getComponentType())); } if (typeKind == TypeKind.DECLARED && @@ -70,6 +69,12 @@ static TypeData fromClass(Class aClass) { if (aClass.isArray()) { return new ArrayTypeData(fromClass(aClass.arrayType())); } + if (aClass.isMemberClass()) { + final TypeData typeData = fromClass(aClass.getEnclosingClass()); + if (typeData instanceof DeclaredTypeData(String packageName, String name)) { + return new DeclaredTypeData(packageName, name + "." + aClass.getSimpleName()); + } + } return new DeclaredTypeData(aClass.getPackageName(), aClass.getSimpleName()); } diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java index 0777690..64096bd 100644 --- a/src/main/java/overrun/marshal/gen2/TypeUse.java +++ b/src/main/java/overrun/marshal/gen2/TypeUse.java @@ -16,9 +16,12 @@ package overrun.marshal.gen2; +import overrun.marshal.gen.CEnum; import overrun.marshal.gen.struct.StructRef; import overrun.marshal.gen1.Spec; +import overrun.marshal.internal.Util; +import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; @@ -33,18 +36,18 @@ * @since 0.1.0 */ public interface TypeUse { - private static String valueLayoutString(TypeKind typeKind) { + private static Optional valueLayout(TypeKind typeKind) { return switch (typeKind) { - case BOOLEAN -> "JAVA_BOOLEAN"; - case CHAR -> "JAVA_CHAR"; - case BYTE -> "JAVA_BYTE"; - case SHORT -> "JAVA_SHORT"; - case INT -> "JAVA_INT"; - case LONG -> "JAVA_LONG"; - case FLOAT -> "JAVA_FLOAT"; - case DOUBLE -> "JAVA_DOUBLE"; - case ARRAY, DECLARED -> "ADDRESS"; - default -> null; + case BOOLEAN -> valueLayout(boolean.class); + case CHAR -> valueLayout(char.class); + case BYTE -> valueLayout(byte.class); + case SHORT -> valueLayout(short.class); + case INT -> valueLayout(int.class); + case LONG -> valueLayout(long.class); + case FLOAT -> valueLayout(float.class); + case DOUBLE -> valueLayout(double.class); + case ARRAY, DECLARED -> valueLayout(MemorySegment.class); + default -> Optional.empty(); }; } @@ -59,38 +62,66 @@ private static Optional valueLayout(String layout) { /** * Gets the value layout * + * @param env the processing environment * @param typeMirror the type mirror * @return the value layout */ - static Optional valueLayout(TypeMirror typeMirror) { - return valueLayout(valueLayoutString(typeMirror.getKind())); + static Optional valueLayout(ProcessingEnvironment env, TypeMirror typeMirror) { + if (Util.isAExtendsB(env, typeMirror, CEnum.class)) { + return valueLayout(int.class); + } + return valueLayout(typeMirror.getKind()); } /** * Gets the value layout * + * @param env the processing environment * @param element the element * @return the value layout */ - static Optional valueLayout(Element element) { - return element.getAnnotation(StructRef.class) != null ? - valueLayout("ADDRESS") : - valueLayout(element.asType()); + static Optional valueLayout(ProcessingEnvironment env, Element element) { + if (element.getAnnotation(StructRef.class) != null) { + return valueLayout(MemorySegment.class); + } + return valueLayout(env, element.asType()); + } + + /** + * Gets the value layout + * + * @param carrier the carrier + * @return the value layout + */ + static Optional valueLayout(Class carrier) { + if (carrier == boolean.class) return valueLayout("JAVA_BOOLEAN"); + if (carrier == char.class) return valueLayout("JAVA_CHAR"); + if (carrier == byte.class) return valueLayout("JAVA_BYTE"); + if (carrier == short.class) return valueLayout("JAVA_SHORT"); + if (carrier == int.class) return valueLayout("JAVA_INT"); + if (carrier == long.class) return valueLayout("JAVA_LONG"); + if (carrier == float.class) return valueLayout("JAVA_FLOAT"); + if (carrier == double.class) return valueLayout("JAVA_DOUBLE"); + if (carrier == MemorySegment.class) return valueLayout("ADDRESS"); + return Optional.empty(); } /** * Converts to the downcall type * + * @param env the processing environment * @param typeMirror the type mirror * @return the downcall type */ - static Optional toDowncallType(TypeMirror typeMirror) { + static Optional toDowncallType(ProcessingEnvironment env, TypeMirror typeMirror) { final TypeKind typeKind = typeMirror.getKind(); if (typeKind.isPrimitive()) { return Optional.of(_ -> Spec.literal(typeMirror.toString())); } return switch (typeKind) { - case ARRAY, DECLARED -> Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class))); + case ARRAY, DECLARED -> typeKind == TypeKind.DECLARED && Util.isAExtendsB(env, typeMirror, CEnum.class) ? + Optional.of(_ -> Spec.literal("int")) : + Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class))); default -> Optional.empty(); }; } diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index fcc24b9..e3f80e3 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -232,6 +232,17 @@ public static Optional findAnnotatedMethod( .findFirst(); } + /** + * Gets the type element from class name + * + * @param env the processing environment + * @param aClass the class + * @return the type element + */ + public static TypeElement getTypeElementFromClass(ProcessingEnvironment env, String aClass) { + return env.getElementUtils().getTypeElement(aClass); + } + /** * Gets the type element from class * @@ -240,7 +251,7 @@ public static Optional findAnnotatedMethod( * @return the type element */ public static TypeElement getTypeElementFromClass(ProcessingEnvironment env, Class aClass) { - return env.getElementUtils().getTypeElement(aClass.getCanonicalName()); + return getTypeElementFromClass(env, aClass.getCanonicalName()); } /** @@ -256,6 +267,17 @@ public static boolean isAExtendsB(ProcessingEnvironment env, TypeMirror t1, Type env.getTypeUtils().isAssignable(t1, t2); } + /** + * {@return is A extends B} + * + * @param env the processing environment + * @param t1 A + * @param t2 B + */ + public static boolean isAExtendsB(ProcessingEnvironment env, TypeMirror t1, Class t2) { + return isAExtendsB(env, t1, getTypeElementFromClass(env, t2).asType()); + } + /** * {@return is same class} * diff --git a/src/main/java/overrun/marshal/gen/struct/IStruct.java b/src/main/java/overrun/marshal/struct/IStruct.java similarity index 97% rename from src/main/java/overrun/marshal/gen/struct/IStruct.java rename to src/main/java/overrun/marshal/struct/IStruct.java index 67d0182..7371bb3 100644 --- a/src/main/java/overrun/marshal/gen/struct/IStruct.java +++ b/src/main/java/overrun/marshal/struct/IStruct.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen.struct; +package overrun.marshal.struct; import java.lang.foreign.MemorySegment; import java.lang.foreign.StructLayout; From 79e2c2b31b7d6b13d021e1b484820bd947c9d72b Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 13 Jan 2024 22:29:50 +0800 Subject: [PATCH 08/28] Update downcall generator; added MemoryStack --- .../overrun/marshal/test/CDowncallTest.java | 44 +- src/main/java/overrun/marshal/Checks.java | 3 + .../java/overrun/marshal/Configurations.java | 58 ++ .../overrun/marshal/DowncallProcessor.java | 13 +- .../java/overrun/marshal/MemoryStack.java | 786 +++++++++++++++++ .../java/overrun/marshal/StackWalkUtil.java | 96 ++ .../gen1/TryWithResourceStatement.java | 67 ++ .../marshal/gen1/VariableStatement.java | 20 +- .../overrun/marshal/gen2/DowncallData.java | 833 ++++++++++++++---- .../overrun/marshal/gen2/ParameterData.java | 5 +- .../java/overrun/marshal/gen2/TypeData.java | 3 +- .../java/overrun/marshal/gen2/TypeUse.java | 47 +- .../overrun/marshal/gen2/VoidTypeData.java | 2 +- .../java/overrun/marshal/internal/Util.java | 27 +- 14 files changed, 1808 insertions(+), 196 deletions(-) create mode 100644 src/main/java/overrun/marshal/MemoryStack.java create mode 100644 src/main/java/overrun/marshal/StackWalkUtil.java create mode 100644 src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 4437281..34a08de 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -16,11 +16,14 @@ package overrun.marshal.test; +import overrun.marshal.MemoryStack; import overrun.marshal.gen.*; import overrun.marshal.gen.struct.ByValue; import overrun.marshal.gen.struct.StructRef; +import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; /** * Test basic features @@ -110,6 +113,9 @@ interface CDowncallTest { @Overload String[] testStringArray(String[] arr, @Ref String[] refArr); + @StrCharset("UTF-16") + String[] testStringArrayUTF16(@StrCharset("UTF-16") String[] arr, @Ref @StrCharset("UTF-16") String[] refArr); + @Entrypoint("testWithReturnArray") MemorySegment ntestWithReturnArray(); @@ -141,41 +147,29 @@ interface CDowncallTest { void testUpcall(MemorySegment cb, MemorySegment nullableCb); @Overload - void testUpcall(GLFWErrorCallback cb, @NullableRef GLFWErrorCallback nullableCb); + void testUpcall(Arena arena, GLFWErrorCallback cb, @NullableRef GLFWErrorCallback nullableCb); @Entrypoint("testReturnUpcall") MemorySegment ntestReturnUpcall(); @Overload("ntestReturnUpcall") - GLFWErrorCallback testReturnUpcall(); + GLFWErrorCallback testReturnUpcall(Arena arena); void testStruct(MemorySegment struct, MemorySegment nullableStruct); @Overload void testStruct(@StructRef("overrun.marshal.test.Vector3") Object struct, @NullableRef @StructRef("overrun.marshal.test.Vector3") Object nullableStruct); - @Entrypoint("testReturnStruct") - @StructRef("overrun.marshal.test.StructTest") - MemorySegment ntestReturnStruct(); - - @Overload("ntestReturnStruct") @StructRef("overrun.marshal.test.StructTest") Object testReturnStruct(); - /** - * A document - * - * @return the segment - */ @ByValue - @Entrypoint("returnByValueStruct") @StructRef("overrun.marshal.test.StructTest") - MemorySegment nreturnByValueStruct(); + Object returnByValueStruct(SegmentAllocator allocator); @ByValue - @Overload("nreturnByValueStruct") @StructRef("overrun.marshal.test.StructTest") - Object returnByValueStruct(); + Object returnByValueStructWithArena(Arena arena); int testEnumValue(int value); @@ -187,10 +181,10 @@ interface CDowncallTest { @Overload MyEnum testEnumValueWithRef(MyEnum value, @Ref int[] ref); - void testNameSegmentAllocator(MemorySegment segmentAllocator, MemorySegment arr); + void testNameMemoryStack(MemorySegment stack, MemorySegment arr); @Overload - void testNameSegmentAllocator(MemorySegment segmentAllocator, int[] arr); + void testNameMemoryStack(MemorySegment stack, int[] arr); @Entrypoint("_testAnotherEntrypoint") void testAnotherEntrypoint(MemorySegment segment); @@ -200,13 +194,25 @@ interface CDowncallTest { void testWithoutOverload(int[] arr); - void testDuplicateName(int testDuplicateName); + void testDuplicateName(int[] testDuplicateName); MyEnum testReturnEnumWithoutOverload(); @Sized(4) int[] testReturnSizedArrWithoutOverload(); + void testWithArrWithoutOverload(int[] arr); + + void testWithArrWithoutOverload(SegmentAllocator allocator, int[] arr); + + void testWithArrWithoutOverload(Arena arena, int[] arr); + + void testWithArrWithoutOverload(MemoryStack stack, int[] arr); + + void testStructWithoutOverload(@StructRef("overrun.marshal.test.StructTest") Object struct); + + void testUpcallWithoutOverload(Arena arena, GLFWErrorCallback cb); + /** * This is a test that tests all features. * diff --git a/src/main/java/overrun/marshal/Checks.java b/src/main/java/overrun/marshal/Checks.java index 383e4a2..b1fdc3f 100644 --- a/src/main/java/overrun/marshal/Checks.java +++ b/src/main/java/overrun/marshal/Checks.java @@ -23,6 +23,9 @@ * @since 0.1.0 */ public final class Checks { + private Checks() { + } + /** * Checks the array size. * diff --git a/src/main/java/overrun/marshal/Configurations.java b/src/main/java/overrun/marshal/Configurations.java index ff18beb..3e97588 100644 --- a/src/main/java/overrun/marshal/Configurations.java +++ b/src/main/java/overrun/marshal/Configurations.java @@ -18,6 +18,8 @@ import overrun.marshal.gen.Sized; +import java.util.Objects; +import java.util.function.Consumer; import java.util.function.Supplier; /** @@ -27,6 +29,12 @@ * @since 0.1.0 */ public final class Configurations { + /** + * Enable checks. + *

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

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

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

+ * The default value is {@code false}. + */ + public static final Entry DEBUG_STACK = new Entry<>(() -> false); + /** + * The default stack size in KiB of {@link MemoryStack}. + *

+ * The default value is {@code 64}. + */ + public static final Entry STACK_SIZE = new Entry<>(() -> 64L); + /** + * The default stack frames of {@link MemoryStack}. + *

+ * The default value is {@code 8}. + */ + public static final Entry STACK_FRAMES = new Entry<>(() -> 8); + private static Consumer apiLogger = System.err::println; private Configurations() { //no instance } + /** + * Sets the API logger. + * + * @param logger the logger + */ + public static void setApiLogger(Consumer logger) { + apiLogger = Objects.requireNonNullElseGet(logger, () -> System.err::println); + } + + /** + * {@return the API logger} + */ + public static Consumer apiLogger() { + return apiLogger; + } + + /** + * Logs the given message. + * + * @param log the message + */ + public static void apiLog(String log) { + apiLogger().accept(log); + } + /** * A check entry * diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index 316ff25..19c09fe 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -17,11 +17,11 @@ package overrun.marshal; import overrun.marshal.gen.*; +import overrun.marshal.gen.struct.ByValue; +import overrun.marshal.gen.struct.StructRef; import overrun.marshal.gen1.*; import overrun.marshal.gen2.DowncallData; import overrun.marshal.internal.Processor; -import overrun.marshal.gen.struct.ByValue; -import overrun.marshal.gen.struct.StructRef; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.element.ExecutableElement; @@ -69,16 +69,9 @@ public boolean process(Set annotations, RoundEnvironment } private void processClasses(RoundEnvironment roundEnv) { - // TODO: 2024/1/7 squid233: rewrite - final boolean debug = true; ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Downcall.class)).forEach(e -> { try { - if (debug) { - new DowncallData(processingEnv).generate(e); - } else { - final var enclosed = e.getEnclosedElements(); - writeFile(e, ElementFilter.fieldsIn(enclosed), ElementFilter.methodsIn(enclosed)); - } + new DowncallData(processingEnv).generate(e); } catch (IOException ex) { printStackTrace(ex); } diff --git a/src/main/java/overrun/marshal/MemoryStack.java b/src/main/java/overrun/marshal/MemoryStack.java new file mode 100644 index 0000000..963a033 --- /dev/null +++ b/src/main/java/overrun/marshal/MemoryStack.java @@ -0,0 +1,786 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.util.Arrays; +import java.util.Objects; + +import static java.lang.foreign.ValueLayout.*; + +/** + * An off-heap memory stack. + * + *

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

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

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

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

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

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

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

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

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

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

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

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

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

+ * + *

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

+ *
    + *
  • match every push with a pop
  • + *
  • not call pop before push has been called at least once
  • + *
  • not nest push calls to more than the maximum supported depth
  • + *
+ * + * @return this stack + */ + public MemoryStack push() { + if (frameIndex == frames.length) { + frameOverflow(); + } + frames[frameIndex++] = pointer; + return this; + } + + /** + * Pops the current stack frame and moves the stack pointer to the end of the previous stack frame. + * + * @return this stack + */ + public MemoryStack pop() { + pointer = frames[--frameIndex]; + return this; + } + + /** + * {@return the backing segment} + */ + public MemorySegment segment() { + return segment; + } + + /** + * {@return the current frame index} + * + *

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

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

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

+ * + *

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

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

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

+ */ + public void setPointer(long pointer) { + if (CHECKS) { + checkPointer(pointer); + } + + this.pointer = pointer; + } + + private void checkPointer(long pointer) { + if (pointer < 0 || segment().byteSize() < pointer) { + throw new IndexOutOfBoundsException("Invalid stack pointer"); + } + } + + /** + * Allocates a block of {@code size} bytes of memory on the stack. + * The content of the newly allocated block of memory is not initialized, remaining with + * indeterminate values. + * + * @param byteSize the allocation size + * @param byteAlignment the required alignment + * @return the memory segment on the stack for the requested allocation + */ + public MemorySegment malloc(long byteSize, long byteAlignment) { + if (CHECKS) { + checkByteSize(byteSize); + checkAlignment(byteAlignment); + } + + // Align address to the specified alignment + long rawLong = segment().address(); + long address = (rawLong + pointer - byteSize) & -byteAlignment; + + pointer = address - rawLong; + if (CHECKS && pointer < 0) { + throw new OutOfMemoryError("Out of stack space."); + } + + return segment().asSlice(pointer, byteSize); + } + + private MemorySegment malloc(long byteSize, ValueLayout valueLayout) { + return malloc(byteSize, valueLayout.byteAlignment()); + } + + private static void checkByteSize(long byteSize) { + if (byteSize < 0) { + throw new IllegalArgumentException("byteSize must be >= 0."); + } + } + + private static void checkAlignment(long alignment) { + if (alignment <= 0) { + throw new IllegalArgumentException("Alignment must be > 0."); + } + if (Long.bitCount(alignment) != 1) { + throw new IllegalArgumentException("Alignment must be a power-of-two value."); + } + } + + /** + * Allocates a block of memory on the stack for an array of {@code num} elements, + * each of them {@code size} bytes long, and initializes all its bits to + * zero. + * + * @param byteSize the allocation size + * @param byteAlignment the required element alignment + * @return the memory segment on the stack for the requested allocation + */ + @Override + public MemorySegment allocate(long byteSize, long byteAlignment) { + return malloc(byteSize, byteAlignment).fill((byte) 0); + } + + @Override + public MemorySegment allocateFrom(OfByte layout, byte value) { + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(OfChar layout, char value) { + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(OfShort layout, short value) { + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(OfInt layout, int value) { + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(OfFloat layout, float value) { + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(OfLong layout, long value) { + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(OfDouble layout, double value) { + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(AddressLayout layout, MemorySegment value) { + Objects.requireNonNull(value); + Objects.requireNonNull(layout); + final var segment = malloc(layout.byteSize(), layout); + segment.set(layout, 0, value); + return segment; + } + + @Override + public MemorySegment allocateFrom(ValueLayout elementLayout, MemorySegment source, ValueLayout sourceElementLayout, long sourceOffset, long elementCount) { + Objects.requireNonNull(source); + Objects.requireNonNull(sourceElementLayout); + Objects.requireNonNull(elementLayout); + final var segment = malloc(elementLayout.byteSize() * elementCount, elementLayout); + MemorySegment.copy(source, sourceElementLayout, sourceOffset, segment, elementLayout, 0, elementCount); + return segment; + } + + @Override + public MemorySegment.Scope scope() { + return segment().scope(); + } + + @Override + public void close() { + pop(); + } + + /** + * Single value version of {@link #malloc}. + */ + public MemorySegment bytes(byte x) { + return allocateFrom(JAVA_BYTE, x); + } + + /** + * Two value version of {@link #malloc}. + */ + public MemorySegment bytes(byte x, byte y) { + final var segment = malloc(2, JAVA_BYTE); + segment.set(JAVA_BYTE, 0, x); + segment.set(JAVA_BYTE, 1, y); + return segment; + } + + /** + * Three value version of {@link #malloc}. + */ + public MemorySegment bytes(byte x, byte y, byte z) { + final var segment = malloc(3, JAVA_BYTE); + segment.set(JAVA_BYTE, 0, x); + segment.set(JAVA_BYTE, 1, y); + segment.set(JAVA_BYTE, 2, z); + return segment; + } + + /** + * Four value version of {@link #malloc}. + */ + public MemorySegment bytes(byte x, byte y, byte z, byte w) { + final var segment = malloc(4, JAVA_BYTE); + segment.set(JAVA_BYTE, 0, x); + segment.set(JAVA_BYTE, 1, y); + segment.set(JAVA_BYTE, 2, z); + segment.set(JAVA_BYTE, 3, w); + return segment; + } + + /** + * Vararg version of {@link #malloc}. + */ + public MemorySegment bytes(byte... values) { + return allocateFrom(JAVA_BYTE, values); + } + + // ------------------------------------------------- + + /** + * Single value version of {@link #malloc}. + */ + public MemorySegment shorts(short x) { + return allocateFrom(JAVA_SHORT, x); + } + + /** + * Two value version of {@link #malloc}. + */ + public MemorySegment shorts(short x, short y) { + final var segment = malloc(4, JAVA_SHORT); + segment.set(JAVA_SHORT, 0, x); + segment.set(JAVA_SHORT, 2, y); + return segment; + } + + /** + * Three value version of {@link #malloc}. + */ + public MemorySegment shorts(short x, short y, short z) { + final var segment = malloc(6, JAVA_SHORT); + segment.set(JAVA_SHORT, 0, x); + segment.set(JAVA_SHORT, 2, y); + segment.set(JAVA_SHORT, 4, z); + return segment; + } + + /** + * Four value version of {@link #malloc}. + */ + public MemorySegment shorts(short x, short y, short z, short w) { + final var segment = malloc(8, JAVA_SHORT); + segment.set(JAVA_SHORT, 0, x); + segment.set(JAVA_SHORT, 2, y); + segment.set(JAVA_SHORT, 4, z); + segment.set(JAVA_SHORT, 6, w); + return segment; + } + + /** + * Vararg version of {@link #malloc}. + */ + public MemorySegment shorts(short... values) { + return allocateFrom(JAVA_SHORT, values); + } + + // ------------------------------------------------- + + /** + * Single value version of {@link #malloc}. + */ + public MemorySegment ints(int x) { + return allocateFrom(JAVA_INT, x); + } + + /** + * Two value version of {@link #malloc}. + */ + public MemorySegment ints(int x, int y) { + final var segment = malloc(8, JAVA_INT); + segment.set(JAVA_INT, 0, x); + segment.set(JAVA_INT, 4, y); + return segment; + } + + /** + * Three value version of {@link #malloc}. + */ + public MemorySegment ints(int x, int y, int z) { + final var segment = malloc(12, JAVA_INT); + segment.set(JAVA_INT, 0, x); + segment.set(JAVA_INT, 4, y); + segment.set(JAVA_INT, 8, z); + return segment; + } + + /** + * Four value version of {@link #malloc}. + */ + public MemorySegment ints(int x, int y, int z, int w) { + final var segment = malloc(16, JAVA_INT); + segment.set(JAVA_INT, 0, x); + segment.set(JAVA_INT, 4, y); + segment.set(JAVA_INT, 8, z); + segment.set(JAVA_INT, 12, w); + return segment; + } + + /** + * Vararg version of {@link #malloc}. + */ + public MemorySegment ints(int... values) { + return allocateFrom(JAVA_INT, values); + } + + // ------------------------------------------------- + + /** + * Single value version of {@link #malloc}. + */ + public MemorySegment longs(long x) { + return allocateFrom(JAVA_LONG, x); + } + + /** + * Two value version of {@link #malloc}. + */ + public MemorySegment longs(long x, long y) { + final var segment = malloc(16, JAVA_LONG); + segment.set(JAVA_LONG, 0, x); + segment.set(JAVA_LONG, 8, y); + return segment; + } + + /** + * Three value version of {@link #malloc}. + */ + public MemorySegment longs(long x, long y, long z) { + final var segment = malloc(24, JAVA_LONG); + segment.set(JAVA_LONG, 0, x); + segment.set(JAVA_LONG, 8, y); + segment.set(JAVA_LONG, 16, z); + return segment; + } + + /** + * Four value version of {@link #malloc}. + */ + public MemorySegment longs(long x, long y, long z, long w) { + final var segment = malloc(32, JAVA_LONG); + segment.set(JAVA_LONG, 0, x); + segment.set(JAVA_LONG, 8, y); + segment.set(JAVA_LONG, 16, z); + segment.set(JAVA_LONG, 24, w); + return segment; + } + + /** + * Vararg version of {@link #malloc}. + */ + public MemorySegment longs(long... values) { + return allocateFrom(JAVA_LONG, values); + } + + // ------------------------------------------------- + + /** + * Single value version of {@link #malloc}. + */ + public MemorySegment floats(float x) { + return allocateFrom(JAVA_FLOAT, x); + } + + /** + * Two value version of {@link #malloc}. + */ + public MemorySegment floats(float x, float y) { + final var segment = malloc(8, JAVA_FLOAT); + segment.set(JAVA_FLOAT, 0, x); + segment.set(JAVA_FLOAT, 4, y); + return segment; + } + + /** + * Three value version of {@link #malloc}. + */ + public MemorySegment floats(float x, float y, float z) { + final var segment = malloc(12, JAVA_FLOAT); + segment.set(JAVA_FLOAT, 0, x); + segment.set(JAVA_FLOAT, 4, y); + segment.set(JAVA_FLOAT, 8, z); + return segment; + } + + /** + * Four value version of {@link #malloc}. + */ + public MemorySegment floats(float x, float y, float z, float w) { + final var segment = malloc(16, JAVA_FLOAT); + segment.set(JAVA_FLOAT, 0, x); + segment.set(JAVA_FLOAT, 4, y); + segment.set(JAVA_FLOAT, 8, z); + segment.set(JAVA_FLOAT, 12, w); + return segment; + } + + /** + * Vararg version of {@link #malloc}. + */ + public MemorySegment floats(float... values) { + return allocateFrom(JAVA_FLOAT, values); + } + + // ------------------------------------------------- + + /** + * Single value version of {@link #malloc}. + */ + public MemorySegment doubles(double x) { + return allocateFrom(JAVA_DOUBLE, x); + } + + /** + * Two value version of {@link #malloc}. + */ + public MemorySegment doubles(double x, double y) { + final var segment = malloc(16, JAVA_DOUBLE); + segment.set(JAVA_DOUBLE, 0, x); + segment.set(JAVA_DOUBLE, 8, y); + return segment; + } + + /** + * Three value version of {@link #malloc}. + */ + public MemorySegment doubles(double x, double y, double z) { + final var segment = malloc(24, JAVA_DOUBLE); + segment.set(JAVA_DOUBLE, 0, x); + segment.set(JAVA_DOUBLE, 8, y); + segment.set(JAVA_DOUBLE, 16, z); + return segment; + } + + /** + * Four value version of {@link #malloc}. + */ + public MemorySegment doubles(double x, double y, double z, double w) { + final var segment = malloc(32, JAVA_DOUBLE); + segment.set(JAVA_DOUBLE, 0, x); + segment.set(JAVA_DOUBLE, 8, y); + segment.set(JAVA_DOUBLE, 16, z); + segment.set(JAVA_DOUBLE, 24, w); + return segment; + } + + /** + * Vararg version of {@link #malloc}. + */ + public MemorySegment doubles(double... values) { + return allocateFrom(JAVA_DOUBLE, values); + } + + // ------------------------------------------------- + + /** + * Single value version of {@link #malloc}. + */ + public MemorySegment segments(MemorySegment x) { + return allocateFrom(ADDRESS, x); + } + + /** + * Two value version of {@link #malloc}. + */ + public MemorySegment segments(MemorySegment x, MemorySegment y) { + final var segment = malloc(ADDRESS.byteSize() * 2, ADDRESS); + segment.set(ADDRESS, 0, x); + segment.setAtIndex(ADDRESS, 1, y); + return segment; + } + + /** + * Three value version of {@link #malloc}. + */ + public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z) { + final var segment = malloc(ADDRESS.byteSize() * 3, ADDRESS); + segment.set(ADDRESS, 0, x); + segment.setAtIndex(ADDRESS, 1, y); + segment.setAtIndex(ADDRESS, 2, z); + return segment; + } + + /** + * Four value version of {@link #malloc}. + */ + public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z, MemorySegment w) { + final var segment = malloc(ADDRESS.byteSize() * 4, ADDRESS); + segment.set(ADDRESS, 0, x); + segment.setAtIndex(ADDRESS, 1, y); + segment.setAtIndex(ADDRESS, 2, z); + segment.setAtIndex(ADDRESS, 3, w); + return segment; + } + + /** + * Vararg version of {@link #malloc}. + */ + public MemorySegment segments(MemorySegment... values) { + final var segment = malloc(ADDRESS.byteSize() * values.length, ADDRESS); + for (int i = 0; i < values.length; i++) { + segment.setAtIndex(ADDRESS, i, values[i]); + } + return segment; + } + + // ----------------------------------------------------- + // ----------------------------------------------------- + // ----------------------------------------------------- + + /** + * {@return the stack of the current thread} + */ + public static MemoryStack stackGet() { + return TLS.get(); + } + + /** + * Calls {@link #push} on the stack of the current thread. + * + * @return the stack of the current thread. + */ + public static MemoryStack stackPush() { + return stackGet().push(); + } + + /** + * Calls {@link #pop} on the stack of the current thread. + * + * @return the stack of the current thread. + */ + public static MemoryStack stackPop() { + return stackGet().pop(); + } + + /** + * Stores the method that pushed a frame and checks if it is the same method when the frame is popped. + * + * @since 0.1.0 + */ + private static final class DebugMemoryStack extends MemoryStack { + private Object[] debugFrames; + + private DebugMemoryStack(MemorySegment address) { + super(address); + debugFrames = new Object[DEFAULT_STACK_FRAMES]; + } + + @Override + public MemoryStack push() { + if (frameIndex == debugFrames.length) { + frameOverflow(); + } + + debugFrames[frameIndex] = StackWalkUtil.stackWalkGetMethod(MemoryStack.class); + + return super.push(); + } + + private void frameOverflow() { + debugFrames = Arrays.copyOf(debugFrames, debugFrames.length * 3 / 2); + } + + @Override + public MemoryStack pop() { + Object pushed = debugFrames[frameIndex - 1]; + Object popped = StackWalkUtil.stackWalkCheckPop(MemoryStack.class, pushed); + if (popped != null) { + reportAsymmetricPop(pushed, popped); + } + + debugFrames[frameIndex - 1] = null; + return super.pop(); + } + + // No need to check pop in try-with-resources + @Override + public void close() { + debugFrames[frameIndex - 1] = null; + super.pop(); + } + + private static void reportAsymmetricPop(Object pushed, Object popped) { + Configurations.apiLog(String.format( + "[overrun.marshal] Asymmetric pop detected:\n\tPUSHED: %s\n\tPOPPED: %s\n\tTHREAD: %s\n", + pushed, + popped, + Thread.currentThread() + )); + } + } +} diff --git a/src/main/java/overrun/marshal/StackWalkUtil.java b/src/main/java/overrun/marshal/StackWalkUtil.java new file mode 100644 index 0000000..7dd6df9 --- /dev/null +++ b/src/main/java/overrun/marshal/StackWalkUtil.java @@ -0,0 +1,96 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal; + +/** + * The stack walker util + * + * @author lwjgl3 + * @author squid233 + * @since 0.1.0 + */ +final class StackWalkUtil { + private static final StackWalker STACKWALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE); + + private StackWalkUtil() { + } + + static Object stackWalkGetMethod(Class after) { + return STACKWALKER.walk(s -> { + var iter = s.iterator(); + iter.next(); // skip this method + iter.next(); // skip MemoryStack::pop + + StackWalker.StackFrame frame; + do { + frame = iter.next(); + } while (frame.getDeclaringClass() == after && iter.hasNext()); + + return frame; + }); + } + + private static boolean isSameMethod(StackWalker.StackFrame a, StackWalker.StackFrame b) { + return isSameMethod(a, b, b.getMethodName()); + } + + private static boolean isSameMethod(StackWalker.StackFrame a, StackWalker.StackFrame b, String methodName) { + return a.getDeclaringClass() == b.getDeclaringClass() && + a.getMethodName().equals(methodName); + } + + private static boolean isAutoCloseable(StackWalker.StackFrame element, StackWalker.StackFrame pushed) { + // Java 9 try-with-resources: synthetic $closeResource + if (isSameMethod(element, pushed, "$closeResource")) { + return true; + } + + // Kotlin T.use: kotlin.AutoCloseable::closeFinally + return "kotlin.jdk7.AutoCloseableKt".equals(element.getClassName()) && "closeFinally".equals(element.getMethodName()); + } + + static Object stackWalkCheckPop(Class after, Object pushedObj) { + StackWalker.StackFrame pushed = (StackWalker.StackFrame) pushedObj; + + return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(s -> { + var iter = s.iterator(); + iter.next(); + iter.next(); + + StackWalker.StackFrame element; + do { + element = iter.next(); + } while (element.getDeclaringClass() == after && iter.hasNext()); + + if (isSameMethod(element, pushed)) { + return null; + } + + if (iter.hasNext() && isAutoCloseable(element, pushed)) { + // Some runtimes use a separate method to call AutoCloseable::close in try-with-resources blocks. + // That method suppresses any exceptions thrown by close if necessary. + // When that happens, the pop is 1 level deeper than expected. + element = iter.next(); + if (isSameMethod(element, pushed)) { + return null; + } + } + + return element; + }); + } +} diff --git a/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java b/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java new file mode 100644 index 0000000..f1551b4 --- /dev/null +++ b/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java @@ -0,0 +1,67 @@ +/* + * 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.gen1; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +/** + * try-with-resource + * + * @author squid233 + * @since 0.1.0 + */ +public final class TryWithResourceStatement implements Spec, StatementBlock { + private final Spec resource; + private final List statements = new ArrayList<>(); + + /** + * Construct + * + * @param resource resource + */ + public TryWithResourceStatement(Spec resource) { + this.resource = resource; + } + + @Override + public void addStatement(Spec spec) { + statements.add(spec); + } + + /** + * Also runs the action + * + * @param consumer the action + * @return this + */ + public TryWithResourceStatement also(Consumer consumer) { + consumer.accept(this); + return this; + } + + @Override + public void append(StringBuilder builder, int indent) { + final String indentString = Spec.indentString(indent); + builder.append(indentString).append("try ("); + resource.append(builder, indent); + builder.append(") {\n"); + statements.forEach(spec -> spec.append(builder, indent + 4)); + builder.append(indentString).append("}\n"); + } +} diff --git a/src/main/java/overrun/marshal/gen1/VariableStatement.java b/src/main/java/overrun/marshal/gen1/VariableStatement.java index a23525c..ee4901f 100644 --- a/src/main/java/overrun/marshal/gen1/VariableStatement.java +++ b/src/main/java/overrun/marshal/gen1/VariableStatement.java @@ -32,6 +32,7 @@ public final class VariableStatement implements Spec { private AccessModifier accessModifier = AccessModifier.PUBLIC; private boolean isStatic = false; private boolean isFinal = false; + private boolean addSemicolon = true; /** * Constructor @@ -102,11 +103,24 @@ public VariableStatement setFinal(boolean isFinal) { return this; } + /** + * setAddSemicolon + * + * @param addSemicolon addSemicolon + */ + public VariableStatement setAddSemicolon(boolean addSemicolon) { + this.addSemicolon = addSemicolon; + return this; + } + @Override public void append(StringBuilder builder, int indent) { final String indentString = Spec.indentString(indent); Spec.appendDocument(builder, document, indentString); - builder.append(indentString).append(accessModifier); + if (addSemicolon) { + builder.append(indentString); + } + builder.append(accessModifier); if (accessModifier != AccessModifier.PACKAGE_PRIVATE) { builder.append(' '); } @@ -121,6 +135,8 @@ public void append(StringBuilder builder, int indent) { builder.append(" = "); value.append(builder, indent); } - builder.append(";\n"); + if (addSemicolon) { + builder.append(";\n"); + } } } diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index 041c151..d9fe4b4 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -16,6 +16,7 @@ package overrun.marshal.gen2; +import overrun.marshal.*; import overrun.marshal.gen.*; import overrun.marshal.gen.struct.ByValue; import overrun.marshal.gen.struct.StructRef; @@ -33,9 +34,11 @@ import java.lang.annotation.Annotation; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; import java.util.stream.Stream; import static overrun.marshal.internal.Util.*; @@ -88,10 +91,7 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName nonFinal = downcall.nonFinal(); // process fields - ElementFilter.fieldsIn(enclosedElements).forEach(element -> { - if (element.getAnnotation(Skip.class) != null) { - return; - } + skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).forEach(element -> { final Object constantValue = element.getConstantValue(); if (constantValue == null) { return; @@ -109,14 +109,14 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName }); final var methods = ElementFilter.methodsIn(enclosedElements); - if (!methods.isEmpty()) { - // collect method handles - final Map methodHandleDataMap = LinkedHashMap.newLinkedHashMap(methods.size()); - methods.forEach(executableElement -> { - if (executableElement.getAnnotation(Skip.class) != null || - executableElement.getAnnotation(Overload.class) != null) { - return; - } + if (methods.isEmpty()) { + return; + } + // collect method handles + final Map methodHandleDataMap = LinkedHashMap.newLinkedHashMap(methods.size()); + skipAnnotated(methods) + .filter(executableElement -> executableElement.getAnnotation(Overload.class) == null) + .forEach(executableElement -> { final Access access = executableElement.getAnnotation(Access.class); final String entrypoint = getMethodEntrypoint(executableElement); final TypeMirror returnType = executableElement.getReturnType(); @@ -154,7 +154,8 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName ).also(invokeSpec1 -> { invokeSpec1.addArgument(getConstExp(sized.value())); final Optional seqLayout; - if (returnType instanceof ArrayType arrayType) { + if (returnType.getKind() == TypeKind.ARRAY && + returnType instanceof ArrayType arrayType) { seqLayout = TypeUse.valueLayout(processingEnv, arrayType.getComponentType()); } else { seqLayout = TypeUse.valueLayout(byte.class); @@ -164,95 +165,138 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName } return spec; }), - executableElement.getParameters() - .stream() - .filter(element -> element.getAnnotation(Skip.class) == null) - .map(element -> TypeUse.valueLayout(processingEnv, element).orElseThrow()) + skipAnnotated(executableElement.getParameters()) + .map(element -> TypeUse.valueLayout(processingEnv, element)) + .filter(Optional::isPresent) + .map(Optional::get) .toList(), executableElement.getAnnotation(Default.class) != null )); }); - // add linker and lookup - final Predicate containsKey = methodHandleDataMap::containsKey; - final String lookupName = addLookup(typeElement, containsKey); - final String linkerName = tryInsertUnderline("LINKER", containsKey); - fields.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - true, - true, - TypeData.fromClass(Linker.class), - linkerName, - importData -> new InvokeSpec(importData.simplifyOrImport(Linker.class), "nativeLinker") - )); + // add linker and lookup + final Predicate containsKey = methodHandleDataMap::containsKey; + final String lookupName = addLookup(typeElement, containsKey); + final String linkerName = tryInsertUnderline("LINKER", containsKey); + fields.add(new FieldData( + null, + List.of(), + AccessModifier.PRIVATE, + true, + true, + TypeData.fromClass(Linker.class), + linkerName, + importData -> new InvokeSpec(importData.simplifyOrImport(Linker.class), "nativeLinker") + )); - // adds methods - // method handles - methodHandleDataMap.forEach((name, methodHandleData) -> fields.add(new FieldData( - " The method handle of {@code " + name + "}.", - List.of(), - methodHandleData.accessModifier(), - true, - true, - TypeData.fromClass(MethodHandle.class), - methodHandleData.name(), - importData -> { - final InvokeSpec invokeSpec = new InvokeSpec( - new InvokeSpec(lookupName, "find") - .addArgument(getConstExp(name)), "map" - ).addArgument(new LambdaSpec("s") - .addStatementThis(new InvokeSpec(linkerName, "downcallHandle") - .addArgument("s") - .also(downcallHandle -> { - final var returnType = methodHandleData.returnType(); - final String methodName = returnType.isPresent() ? "of" : "ofVoid"; - downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName) - .also(fdOf -> { - returnType.ifPresent(typeUse -> { - final Spec spec = typeUse.apply(importData); - fdOf.addArgument(spec); - }); - methodHandleData.parameterTypes().forEach(typeUse -> - fdOf.addArgument(typeUse.apply(importData))); - })); - final Critical critical = methodHandleData.executableElement().getAnnotation(Critical.class); - if (critical != null) { - downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(Linker.Option.class), "critical") - .addArgument(getConstExp(critical.allowHeapAccess()))); - } - }))); - if (methodHandleData.optional()) { - return new InvokeSpec(invokeSpec, "orElse").addArgument("null"); - } - return new InvokeSpec( - invokeSpec, "orElseThrow" - ); + // adds methods + // method handles + methodHandleDataMap.forEach((name, methodHandleData) -> fields.add(new FieldData( + " The method handle of {@code " + name + "}.", + List.of(), + methodHandleData.accessModifier(), + true, + true, + TypeData.fromClass(MethodHandle.class), + methodHandleData.name(), + importData -> { + final InvokeSpec invokeSpec = new InvokeSpec( + new InvokeSpec(lookupName, "find") + .addArgument(getConstExp(name)), "map" + ).addArgument(new LambdaSpec("s") + .addStatementThis(new InvokeSpec(linkerName, "downcallHandle") + .addArgument("s") + .also(downcallHandle -> { + final var returnType = methodHandleData.returnType(); + final String methodName = returnType.isPresent() ? "of" : "ofVoid"; + downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName) + .also(fdOf -> { + returnType.ifPresent(typeUse -> { + final Spec spec = typeUse.apply(importData); + fdOf.addArgument(spec); + }); + methodHandleData.parameterTypes().forEach(typeUse -> + fdOf.addArgument(typeUse.apply(importData))); + })); + final Critical critical = methodHandleData.executableElement().getAnnotation(Critical.class); + if (critical != null) { + downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(Linker.Option.class), "critical") + .addArgument(getConstExp(critical.allowHeapAccess()))); + } + }))); + if (methodHandleData.optional()) { + return new InvokeSpec(invokeSpec, "orElse").addArgument("null"); } - ))); + return new InvokeSpec( + invokeSpec, "orElseThrow" + ); + } + ))); - // methods - final var fieldNames = fields.stream().map(FieldData::name).toList(); - methods.forEach(executableElement -> { - if (executableElement.getAnnotation(Skip.class) != null) { - return; - } - final Overload overload = executableElement.getAnnotation(Overload.class); - final boolean isOverload = overload != null || containsNonDowncallType(executableElement); - final MethodHandleData methodHandleData = methodHandleDataMap.get(getMethodEntrypoint(executableElement)); - if (methodHandleData == null) { - return; - } + // methods + final var fieldNames = fields.stream().map(FieldData::name).toList(); + final List addedMethodHandleInvoker = new ArrayList<>(methodHandleDataMap.size()); + skipAnnotated(methods).forEach(executableElement -> { + final Overload overload = executableElement.getAnnotation(Overload.class); + final MethodHandleData methodHandleData = methodHandleDataMap.get(getMethodEntrypoint(executableElement)); + if (methodHandleData == null) { + return; + } - if (isOverload) { - // TODO: 2024/1/12 squid233: Overload - return; + final Custom custom = executableElement.getAnnotation(Custom.class); + if (custom != null) { + addCustomMethod(executableElement, methodHandleData, custom); + } else { + final String methodName = executableElement.getSimpleName().toString(); + final boolean containsNonDowncallType = containsNonDowncallType(executableElement); + final boolean shouldGenOverload = overload != null || containsNonDowncallType; + + String finalMethodName = methodName; + if (overload == null && !addedMethodHandleInvoker.contains(methodHandleData)) { + addedMethodHandleInvoker.add(methodHandleData); + if (containsNonDowncallType) { + boolean shouldAddPrefix = true; + if (parameterContainsNonDowncallType(executableElement)) { + shouldAddPrefix = false; + } else { + final var rawParameterList = skipAnnotated(executableElement.getParameters()).toList(); + if (executableElement.getAnnotation(ByValue.class) == null && + !rawParameterList.isEmpty() && + isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), SegmentAllocator.class)) { + shouldAddPrefix = false; + } + } + if (shouldAddPrefix) { + final String prefixed = "n" + methodName; + finalMethodName = "n" + tryInsertUnderline(methodName, _ -> functionDataList.stream() + .anyMatch(functionData -> prefixed.equals(functionData.name()))); + } + } + + addDowncallMethod(simpleClassName, + executableElement, + finalMethodName, + fieldNames, + methodHandleData); } - addDowncallMethod(simpleClassName, executableElement, fieldNames, methodHandleData); - }); - } + if (shouldGenOverload) { + final String downcallName; + if (overload == null) { + downcallName = finalMethodName; + } else { + downcallName = getMethodAnnotationString(executableElement, Overload.class, Overload::value); + } + + addOverloadMethod(simpleClassName, + executableElement, + methodName, + downcallName, + fieldNames, + methodHandleData); + } + } + }); } /** @@ -275,7 +319,7 @@ public void generate(TypeElement typeElement) throws IOException { Possible solutions: 1) Add C as a prefix e.g. C%1$s 2) Specify the name in @Downcall e.g. @Downcall(name = "%1$s")""" - .formatted(string)); + .formatted(string), typeElement); return; } } else { @@ -318,59 +362,208 @@ public void generate(TypeElement typeElement) throws IOException { } } - private void addDowncallMethod(String simpleClassName, - ExecutableElement executableElement, - List fieldNames, - MethodHandleData methodHandleData) { + private void addOverloadMethod( + String simpleClassName, + ExecutableElement executableElement, + String methodName, + String downcallName, + List fieldNames, + MethodHandleData methodHandleData) { + final var rawParameterList = skipAnnotated(executableElement.getParameters()).toList(); + final var parameterDataList = parametersToParameterDataList(rawParameterList); + final var parameterNames = parameterDataList.stream() + .map(ParameterData::name) + .toList(); + + final int skipFirst = shouldSkipFirstParameter(executableElement, rawParameterList, false); + if (skipFirst < 0) { + return; + } + + final List variablesInScope = new ArrayList<>(parameterNames); + + final String memoryStackName = tryInsertUnderline("stack", s -> variablesInScope.stream().anyMatch(s::equals)); + final boolean hasAllocatorParameter = + !parameterDataList.isEmpty() && + isAExtendsB(processingEnv, parameterDataList.getFirst().element().asType(), SegmentAllocator.class); + final String allocatorParameter = hasAllocatorParameter ? + parameterDataList.getFirst().name() : + memoryStackName; + final boolean shouldWrapWithMemoryStack = + rawParameterList.stream().anyMatch(element -> useAllocator(element.asType())) && + !hasAllocatorParameter; + if (shouldWrapWithMemoryStack) { + variablesInScope.add(memoryStackName); + } + + final boolean shouldAddInvoker = variablesInScope.stream().anyMatch(fieldNames::contains); + final TypeMirror returnType = executableElement.getReturnType(); + final boolean returnVoid = returnType.getKind() == TypeKind.VOID; + + final List statements = new ArrayList<>(); + + // check array size of @Sized array + rawParameterList.stream() + .filter(element -> element.getAnnotation(Sized.class) != null) + .forEach(element -> { + final Sized sized = element.getAnnotation(Sized.class); + statements.add(importData -> + Spec.statement(new InvokeSpec(importData.simplifyOrImport(Checks.class), "checkArraySize") + .addArgument(getConstExp(sized.value())) + .addArgument(Spec.accessSpec(element.getSimpleName().toString(), "length")))); + }); + + // ref segments + final Map refNameMap = HashMap.newHashMap(Math.toIntExact(rawParameterList.stream() + .filter(element -> element.getAnnotation(Ref.class) != null) + .count())); + rawParameterList.stream() + .filter(element -> element.getAnnotation(Ref.class) != null) + .forEach(element -> statements.add(importData -> { + final String elementName = element.getSimpleName().toString(); + final String refSegName = tryInsertUnderline(elementName, + s -> variablesInScope.stream().anyMatch(s::equals)); + variablesInScope.add(refSegName); + refNameMap.put(elementName, refSegName); + + final Spec allocateSpec = wrapParameter(importData, allocatorParameter, element, false, null); + + return new VariableStatement("var", refSegName, allocateSpec) + .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) + .setFinal(true); + })); + + // invocation + final TypeUse invokeTypeUse = importData -> { + final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ? Spec.literal(simpleClassName) : null, downcallName); + // wrap parameters + var stream = parameterDataList.stream(); + if (skipFirst > 0) { + stream = stream.skip(1); + } + stream.map(ParameterData::element) + .map(element -> wrapParameter(importData, allocatorParameter, element, true, refNameMap::get)) + .forEach(invokeSpec::addArgument); + return invokeSpec; + }; + + // store result + final boolean shouldStoreResult = + canConvertToAddress(executableElement, ExecutableElement::getReturnType) || + (!returnVoid && + rawParameterList.stream().anyMatch(element -> element.getAnnotation(Ref.class) != null)); + final String resultVarName; + if (shouldStoreResult) { + resultVarName = tryInsertUnderline("result", s -> variablesInScope.stream().anyMatch(s::equals)); + variablesInScope.add(resultVarName); + statements.add(importData -> new VariableStatement("var", resultVarName, invokeTypeUse.apply(importData)) + .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) + .setFinal(true)); + } else { + resultVarName = null; + if (returnVoid) { + statements.add(importData -> Spec.statement(invokeTypeUse.apply(importData))); + } else { + statements.add(importData -> Spec.returnStatement(wrapReturnValue(importData, + invokeTypeUse.apply(importData), + executableElement))); + } + } + + // passing to ref + rawParameterList.stream() + .filter(element -> element.getAnnotation(Ref.class) != null) + .forEach(element -> statements.add(importData -> + copyRefResult(importData, element, refNameMap::get))); + + // return result + if (shouldStoreResult) { + statements.add(importData -> Spec.returnStatement(wrapReturnValue(importData, + Spec.literal(resultVarName), + executableElement))); + } + + final StructRef structRef = executableElement.getAnnotation(StructRef.class); + functionDataList.add(new FunctionData( + getDocComment(executableElement), + getElementAnnotations(METHOD_ANNOTATIONS, executableElement), + methodHandleData.accessModifier(), + structRef != null ? + _ -> Spec.literal(structRef.value()) : + importData -> Spec.literal(importData.simplifyOrImport(processingEnv, returnType)), + methodName, + parameterDataList, + shouldWrapWithMemoryStack ? + List.of(importData -> new TryWithResourceStatement( + new VariableStatement("var", + memoryStackName, + new InvokeSpec(importData.simplifyOrImport(MemoryStack.class), "stackPush")) + .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) + .setFinal(true) + .setAddSemicolon(false) + ).also(tryWithResourceStatement -> + statements.forEach(typeUse -> + tryWithResourceStatement.addStatement(typeUse.apply(importData))))) : + statements + )); + } + + private void addDowncallMethod( + String simpleClassName, + ExecutableElement executableElement, + String methodName, + List fieldNames, + MethodHandleData methodHandleData) { final String methodHandleName = methodHandleData.name(); - final var returnType = TypeUse.toDowncallType(processingEnv, executableElement.getReturnType()); + final var returnType = TypeUse.toDowncallType(processingEnv, executableElement, ExecutableElement::getReturnType); + final boolean returnByValue = executableElement.getAnnotation(ByValue.class) != null; - final var parameters = executableElement.getParameters().stream() - .filter(element -> element.getAnnotation(Skip.class) == null) + final var rawParameterList = skipAnnotated(executableElement.getParameters()).toList(); + final int skipFirst = shouldSkipFirstParameter(executableElement, rawParameterList, true); + if (skipFirst < 0) { + return; + } + var stream = rawParameterList.stream(); + if (skipFirst > 0) { + stream = stream.skip(1); + } + final var parameters = stream.toList(); + final var parameterDataList = parameters.stream() .map(element -> new ParameterData( - null, + element, getElementAnnotations(PARAMETER_ANNOTATIONS, element), - TypeUse.toDowncallType(processingEnv, element.asType()).orElseThrow(), + TypeUse.toDowncallType(processingEnv, element).orElseThrow(), element.getSimpleName().toString() )) - .collect(Collectors.toList()); - final var parameterNames = parameters.stream() + .toList(); + final var parameterNames = parameterDataList.stream() .map(ParameterData::name) - .collect(Collectors.toList()); - - if (executableElement.getAnnotation(ByValue.class) != null) { - final String allocatorName = tryInsertUnderline("segmentAllocator", parameterNames::contains); - parameters.addFirst(new ParameterData( - "the segment allocator", - List.of(), - importData -> Spec.literal(importData.simplifyOrImport(SegmentAllocator.class)), - allocatorName - )); - parameterNames.addFirst(allocatorName); - } + .toList(); final boolean shouldAddInvoker = parameterNames.stream().anyMatch(fieldNames::contains); - String docComment = getDocComment(executableElement); - if (docComment != null) { - docComment += parameters.stream() - .filter(parameterData -> parameterData.document() != null) - .map(parameterData -> " @param " + parameterData.name() + " " + parameterData.document()) - .collect(Collectors.joining("\n")); - } functionDataList.add(new FunctionData( - docComment, + getDocComment(executableElement), getElementAnnotations(METHOD_ANNOTATIONS, executableElement), methodHandleData.accessModifier(), returnType.orElseGet(() -> _ -> Spec.literal("void")), - executableElement.getSimpleName().toString(), - parameters, + methodName, + parameterDataList, List.of( importData -> new TryCatchStatement().also(tryCatchStatement -> { final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ? Spec.accessSpec(simpleClassName, methodHandleName) : Spec.literal(methodHandleName), "invokeExact"); - parameterNames.forEach(invokeSpec::addArgument); + var stream1 = parameterNames.stream(); + if (returnByValue) { + final ParameterData first = parameterDataList.getFirst(); + if (!isSameClass(first.element().asType(), SegmentAllocator.class)) { + invokeSpec.addArgument(Spec.cast(importData.simplifyOrImport(SegmentAllocator.class), + Spec.literal(first.name()))); + stream1 = stream1.skip(1); + } + } + stream1.forEach(invokeSpec::addArgument); final Spec returnSpec = returnType .map(typeUse -> Spec.returnStatement(Spec.cast(typeUse.apply(importData), invokeSpec))) @@ -405,10 +598,58 @@ private void addDowncallMethod(String simpleClassName, )); } + private int shouldSkipFirstParameter(ExecutableElement executableElement, + List rawParameterList, + boolean printError) { + final boolean returnByValue = executableElement.getAnnotation(ByValue.class) != null; + final boolean rawParamListNotEmpty = !rawParameterList.isEmpty(); + final boolean firstParamArena = + rawParamListNotEmpty && + isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), Arena.class); + final boolean firstParamSegmentAllocator = + firstParamArena || + (rawParamListNotEmpty && + isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), SegmentAllocator.class)); + if (rawParameterList.stream() + .anyMatch(element -> isAExtendsB(processingEnv, element.asType(), Upcall.class))) { + if (firstParamArena) { + return 1; + } + if (printError) { + processingEnv.getMessager().printError("The first parameter of method with upcall must be Arena", executableElement); + } + return -1; + } + if (returnByValue) { + if (firstParamSegmentAllocator) { + return 0; + } + if (printError) { + processingEnv.getMessager().printError("The first parameter of method marked as @ByValue must be SegmentAllocator", executableElement); + } + return -1; + } + return firstParamSegmentAllocator ? 1 : 0; + } + + private void addCustomMethod(ExecutableElement executableElement, + MethodHandleData methodHandleData, + Custom custom) { + functionDataList.add(new FunctionData( + getDocComment(executableElement), + getElementAnnotations(METHOD_ANNOTATIONS, executableElement), + methodHandleData.accessModifier(), + importData -> Spec.literal(importData.simplifyOrImport(processingEnv, executableElement.getReturnType())), + executableElement.getSimpleName().toString(), + parametersToParameterDataList(skipAnnotated(executableElement.getParameters()).toList()), + List.of(_ -> Spec.indented(custom.value())) + )); + } + private String addLookup(TypeElement typeElement, Predicate insertUnderlineTest) { final Downcall downcall = typeElement.getAnnotation(Downcall.class); final String loader = typeElement.getAnnotationMirrors().stream() - .filter(m -> Downcall.class.getCanonicalName().equals(m.getAnnotationType().toString())) + .filter(m -> isSameClass(m.getAnnotationType(), Downcall.class)) .findFirst() .orElseThrow() .getElementValues().entrySet().stream() @@ -430,14 +671,14 @@ private String addLookup(TypeElement typeElement, Predicate insertUnderl executableElement -> { if (isSameClass(executableElement.getReturnType(), SymbolLookup.class)) { final var parameters = executableElement.getParameters(); - return parameters.size() == 1 && isString(parameters.getFirst().asType()); + return parameters.size() == 1 && isSameClass(parameters.getFirst().asType(), String.class); } return false; }); if (annotatedMethod.isEmpty()) { - processingEnv.getMessager().printError(""" - Couldn't find loader method in %s while %s required. Please mark it with @Loader""" - .formatted(loader, typeElement)); + processingEnv.getMessager().printError("Couldn't find loader method in %s. Please mark it with @Loader" + .formatted(loader), + typeElement); return null; } } @@ -462,6 +703,207 @@ private String addLookup(TypeElement typeElement, Predicate insertUnderl return s; } + private Spec copyRefResult( + ImportData importData, + Element element, + Function refFunction + ) { + final TypeMirror typeMirror = element.asType(); + final String name = element.getSimpleName().toString(); + final Spec spec; + if (isBooleanArray(typeMirror)) { + spec = new InvokeSpec(importData.simplifyOrImport(BoolHelper.class), "copy") + .addArgument(refFunction.apply(name)) + .addArgument(name); + } else if (isPrimitiveArray(typeMirror)) { + spec = new InvokeSpec(importData.simplifyOrImport(MemorySegment.class), "copy") + .addArgument(refFunction.apply(name)) + .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(typeMirror)) + .orElseThrow() + .apply(importData)) + .addArgument(getConstExp(0L)) + .addArgument(name) + .addArgument(getConstExp(0)) + .addArgument(Spec.accessSpec(name, "length")); + } else if (isStringArray(typeMirror)) { + spec = new InvokeSpec(importData.simplifyOrImport(StrHelper.class), "copy") + .addArgument(refFunction.apply(name)) + .addArgument(name) + .addArgument(createCharset(getCharset(element)).apply(importData)); + } else { + processingEnv.getMessager().printWarning("Using @Ref on unknown type %s".formatted(typeMirror), element); + spec = null; + } + + final Spec statement = spec != null ? Spec.statement(spec) : Spec.literal(""); + if (element.getAnnotation(NullableRef.class) != null) { + final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(name)); + ifStatement.addStatement(statement); + return ifStatement; + } + return statement; + } + + private Spec wrapReturnValue( + ImportData importData, + Spec resultSpec, + ExecutableElement executableElement + ) { + final Spec spec; + final StructRef structRef = executableElement.getAnnotation(StructRef.class); + if (structRef != null) { + spec = new ConstructSpec(structRef.value()).addArgument(resultSpec); + } else { + final TypeMirror typeMirror = executableElement.getReturnType(); + if (isBooleanArray(typeMirror)) { + spec = new InvokeSpec(importData.simplifyOrImport(BoolHelper.class), "toArray").addArgument(resultSpec); + } else if (isPrimitiveArray(typeMirror)) { + spec = new InvokeSpec(resultSpec, "toArray") + .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(typeMirror)) + .orElseThrow() + .apply(importData)); + } else if (isStringArray(typeMirror)) { + spec = new InvokeSpec(importData.simplifyOrImport(StrHelper.class), "toArray") + .addArgument(resultSpec) + .addArgument(createCharset(getCharset(executableElement)).apply(importData)); + } else if (isSameClass(typeMirror, String.class)) { + final InvokeSpec invokeSpec = new InvokeSpec(resultSpec, "getString").addArgument(getConstExp(0L)); + if (executableElement.getAnnotation(StrCharset.class) != null) { + invokeSpec.addArgument(createCharset(getCharset(executableElement)).apply(importData)); + } + spec = invokeSpec; + } else if (isAExtendsB(processingEnv, typeMirror, Upcall.class)) { + final var annotatedMethod = findAnnotatedMethod( + (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror), + Upcall.Wrapper.class, + executableElement1 -> { + if (isAExtendsB(processingEnv, executableElement1.getReturnType(), typeMirror)) { + final var parameters = executableElement1.getParameters(); + return parameters.size() == 1 && isSameClass(parameters.getFirst().asType(), MemorySegment.class); + } + return false; + }); + spec = annotatedMethod + .map(executableElement1 -> new InvokeSpec(importData.simplifyOrImport(processingEnv, typeMirror), + executableElement1.getSimpleName().toString()) + .addArgument(resultSpec)) + .orElseGet(() -> { + processingEnv.getMessager().printError( + "Couldn't find wrapper in %s. Please mark it with @Wrapper".formatted(typeMirror), + executableElement + ); + return resultSpec; + }); + } else if (isAExtendsB(processingEnv, typeMirror, CEnum.class)) { + final var annotatedMethod = findAnnotatedMethod( + (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror), + CEnum.Wrapper.class, + executableElement1 -> { + if (isAExtendsB(processingEnv, executableElement1.getReturnType(), typeMirror)) { + final var parameters = executableElement1.getParameters(); + return parameters.size() == 1 && isSameClass(parameters.getFirst().asType(), int.class); + } + return false; + }); + spec = annotatedMethod + .map(executableElement1 -> new InvokeSpec(importData.simplifyOrImport(processingEnv, typeMirror), + executableElement1.getSimpleName().toString()) + .addArgument(resultSpec)) + .orElseGet(() -> { + processingEnv.getMessager().printError( + "Couldn't find wrapper in %s. Please mark it with @Wrapper".formatted(typeMirror), + executableElement + ); + return resultSpec; + }); + } else { + spec = resultSpec; + } + } + return canConvertToAddress(executableElement, ExecutableElement::getReturnType) ? + Spec.ternaryOp(Spec.neqSpec(new InvokeSpec(resultSpec, "address"), Spec.literal("0L")), + spec, + Spec.literal("null")) : + spec; + } + + private Spec wrapParameter( + ImportData importData, + String allocatorParameter, + Element element, + boolean usingRef, + Function refFunction + ) { + final Spec spec; + final String name = element.getSimpleName().toString(); + if (element.getAnnotation(StructRef.class) != null) { + spec = new InvokeSpec(name, "segment"); + } else if (usingRef && element.getAnnotation(Ref.class) != null) { + spec = Spec.literal(refFunction.apply(name)); + } else { + final TypeMirror typeMirror = element.asType(); + if (isBooleanArray(typeMirror)) { + spec = new InvokeSpec(importData.simplifyOrImport(BoolHelper.class), "of") + .addArgument(allocatorParameter) + .addArgument(name); + } else if (isPrimitiveArray(typeMirror)) { + spec = new InvokeSpec(allocatorParameter, "allocateFrom") + .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(typeMirror)) + .orElseThrow() + .apply(importData)) + .addArgument(name); + } else if (isStringArray(typeMirror)) { + spec = new InvokeSpec(importData.simplifyOrImport(StrHelper.class), "of") + .addArgument(allocatorParameter) + .addArgument(name) + .addArgument(createCharset(getCharset(element)).apply(importData)); + } else if (isSameClass(typeMirror, String.class)) { + final InvokeSpec invokeSpec = new InvokeSpec(allocatorParameter, "allocateFrom") + .addArgument(name); + if (element.getAnnotation(StrCharset.class) != null) { + invokeSpec.addArgument(createCharset(getCharset(element)).apply(importData)); + } + spec = invokeSpec; + } else if (isAExtendsB(processingEnv, typeMirror, Upcall.class)) { + spec = new InvokeSpec(name, "stub").addArgument(allocatorParameter); + } else if (isAExtendsB(processingEnv, typeMirror, CEnum.class)) { + spec = new InvokeSpec(name, "value"); + } else { + spec = Spec.literal(name); + } + } + return element.getAnnotation(NullableRef.class) != null ? + Spec.ternaryOp(Spec.notNullSpec(name), + spec, + Spec.accessSpec(importData.simplifyOrImport(MemorySegment.class), "NULL")) : + spec; + } + + private boolean useAllocator(TypeMirror typeMirror) { + return isBooleanArray(typeMirror) || + isPrimitiveArray(typeMirror) || + isStringArray(typeMirror) || + isSameClass(typeMirror, String.class) || + isAExtendsB(processingEnv, typeMirror, Upcall.class); + } + + private List parametersToParameterDataList(List parameters) { + return parameters.stream() + .map(element -> new ParameterData( + element, + getElementAnnotations(PARAMETER_ANNOTATIONS, element), + importData -> { + final StructRef structRef = element.getAnnotation(StructRef.class); + if (structRef != null) { + return Spec.literal(structRef.value()); + } + return Spec.literal(importData.simplifyOrImport(processingEnv, element.asType())); + }, + element.getSimpleName().toString() + )) + .toList(); + } + private Stream findElementAnnotations( List annotationMirrors, Class aClass @@ -482,10 +924,46 @@ private List getElementAnnotations(List { + 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" -> + Spec.accessSpec(importData.simplifyOrImport(StandardCharsets.class), upperCase.replace('-', '_')); + case "UTF_8", "ISO_8859_1", "US_ASCII", + "UTF_16", "UTF_16BE", "UTF_16LE", + "UTF_32", "UTF_32BE", "UTF_32LE" -> + Spec.accessSpec(importData.simplifyOrImport(StandardCharsets.class), upperCase); + default -> new InvokeSpec(importData.simplifyOrImport(Charset.class), "forName") + .addArgument(getConstExp(name)); + }; + }; + } + + private static String getCharset(Element element) { + final StrCharset strCharset = element.getAnnotation(StrCharset.class); + if (strCharset != null) { + final String value = strCharset.value(); + if (!value.isBlank()) { + return value; + } + } + return "UTF-8"; + } + + private static String getMethodEntrypoint(ExecutableElement executableElement) { + return getMethodAnnotationString(executableElement, Entrypoint.class, Entrypoint::value); + } + + private static String getMethodAnnotationString( + ExecutableElement executableElement, + Class aClass, + Function function) { + final T annotation = executableElement.getAnnotation(aClass); + if (annotation != null) { + final String value = function.apply(annotation); if (!value.isBlank()) { return value; } @@ -493,14 +971,59 @@ private String getMethodEntrypoint(ExecutableElement executableElement) { return executableElement.getSimpleName().toString(); } - private boolean containsNonDowncallType(ExecutableElement executableElement) { + private boolean canConvertToAddress(T element, Function function) { + if (element.getAnnotation(StructRef.class) != null) { + return true; + } + final TypeMirror type = function.apply(element); + final TypeKind typeKind = type.getKind(); + final boolean nonAddressType = + (typeKind.isPrimitive()) || + (typeKind == TypeKind.VOID) || + (typeKind == TypeKind.DECLARED && + (isSameClass(type, MemorySegment.class) || + isAExtendsB(processingEnv, type, SegmentAllocator.class) || + isAExtendsB(processingEnv, type, CEnum.class)) + ); + return !nonAddressType; + } + + private boolean isNotDowncallType(T element, Function function) { + if (element.getAnnotation(StructRef.class) != null) { + return true; + } + final TypeMirror type = function.apply(element); + final TypeKind typeKind = type.getKind(); + final boolean downcallType = + (typeKind.isPrimitive()) || + (typeKind == TypeKind.VOID) || + (typeKind == TypeKind.DECLARED && + (isSameClass(type, MemorySegment.class) || + isAExtendsB(processingEnv, type, SegmentAllocator.class)) + ); + return !downcallType; + } + + private boolean returnNonDowncallType(ExecutableElement executableElement) { + return isNotDowncallType(executableElement, ExecutableElement::getReturnType); + } + + private boolean parameterContainsNonDowncallType(ExecutableElement executableElement) { return executableElement.getParameters().stream() - .anyMatch(element -> { - final TypeMirror type = element.asType(); - final TypeKind typeKind = type.getKind(); - return !(typeKind.isPrimitive() || - typeKind == TypeKind.DECLARED && isSameClass(type, MemorySegment.class)); - }); + .anyMatch(element -> isNotDowncallType(element, VariableElement::asType)); + } + + private boolean containsNonDowncallType(ExecutableElement executableElement) { + return returnNonDowncallType(executableElement) || + parameterContainsNonDowncallType(executableElement); + } + + private static Stream skipAnnotated(Stream stream) { + return stream.filter(t -> t.getAnnotation(Skip.class) == null); + } + + private static Stream skipAnnotated(Collection collection) { + return skipAnnotated(collection.stream()); } private void addFields(ClassSpec spec) { @@ -521,24 +1044,30 @@ private void addMethods(ClassSpec spec) { functionDataList.forEach(functionData -> spec.addMethod(new MethodSpec(functionData.returnType().apply(imports), functionData.name()), methodSpec -> { methodSpec.setDocument(functionData.document()); - functionData.annotations().forEach(annotationData -> { - final AnnotationMirror annotationMirror = annotationData.mirror(); - methodSpec.addAnnotation(new AnnotationSpec( - imports.simplifyOrImport(processingEnv, annotationMirror.getAnnotationType()) - ).also(annotationSpec -> - annotationMirror.getElementValues().forEach((executableElement, annotationValue) -> - annotationSpec.addArgument(executableElement.getSimpleName().toString(), annotationValue.toString())))); - }); + addAnnotationSpec(functionData.annotations(), methodSpec); methodSpec.setAccessModifier(functionData.accessModifier()); methodSpec.setStatic(true); functionData.parameters().forEach(parameterData -> - methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name())) + methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()).also(parameterSpec -> { + addAnnotationSpec(parameterData.annotations(), parameterSpec); + })) ); functionData.statements().forEach(typeUse -> methodSpec.addStatement(typeUse.apply(imports))); })); } + private void addAnnotationSpec(List list, Annotatable annotatable) { + list.forEach(annotationData -> { + final AnnotationMirror annotationMirror = annotationData.mirror(); + annotatable.addAnnotation(new AnnotationSpec( + imports.simplifyOrImport(processingEnv, annotationMirror.getAnnotationType()) + ).also(annotationSpec -> + annotationMirror.getElementValues().forEach((executableElement, annotationValue) -> + annotationSpec.addArgument(executableElement.getSimpleName().toString(), annotationValue.toString())))); + }); + } + private String getDocComment(Element e) { return processingEnv.getElementUtils().getDocComment(e); } diff --git a/src/main/java/overrun/marshal/gen2/ParameterData.java b/src/main/java/overrun/marshal/gen2/ParameterData.java index 1f41c9f..81d2de3 100644 --- a/src/main/java/overrun/marshal/gen2/ParameterData.java +++ b/src/main/java/overrun/marshal/gen2/ParameterData.java @@ -16,12 +16,13 @@ package overrun.marshal.gen2; +import javax.lang.model.element.VariableElement; import java.util.List; /** * Holds parameter * - * @param document the document + * @param element the element * @param annotations the annotations * @param type the type * @param name the name @@ -29,7 +30,7 @@ * @since 0.1.0 */ public record ParameterData( - String document, + VariableElement element, List annotations, TypeUse type, String name diff --git a/src/main/java/overrun/marshal/gen2/TypeData.java b/src/main/java/overrun/marshal/gen2/TypeData.java index b76742f..7bff73d 100644 --- a/src/main/java/overrun/marshal/gen2/TypeData.java +++ b/src/main/java/overrun/marshal/gen2/TypeData.java @@ -41,7 +41,8 @@ static TypeData detectType(ProcessingEnvironment env, TypeMirror type) { if (typeKind.isPrimitive()) { return new PrimitiveTypeData(type.toString()); } - if (type instanceof ArrayType arrayType) { + if (typeKind == TypeKind.ARRAY && + type instanceof ArrayType arrayType) { return new ArrayTypeData(detectType(env, arrayType.getComponentType())); } if (typeKind == TypeKind.DECLARED && diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java index 64096bd..755d96e 100644 --- a/src/main/java/overrun/marshal/gen2/TypeUse.java +++ b/src/main/java/overrun/marshal/gen2/TypeUse.java @@ -26,8 +26,10 @@ import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; import java.lang.foreign.ValueLayout; import java.util.Optional; +import java.util.function.Function; /** * Holds spec with type data @@ -67,6 +69,9 @@ private static Optional valueLayout(String layout) { * @return the value layout */ static Optional valueLayout(ProcessingEnvironment env, TypeMirror typeMirror) { + if (Util.isAExtendsB(env, typeMirror, SegmentAllocator.class)) { + return Optional.empty(); + } if (Util.isAExtendsB(env, typeMirror, CEnum.class)) { return valueLayout(int.class); } @@ -119,13 +124,49 @@ static Optional toDowncallType(ProcessingEnvironment env, TypeMirror ty return Optional.of(_ -> Spec.literal(typeMirror.toString())); } return switch (typeKind) { - case ARRAY, DECLARED -> typeKind == TypeKind.DECLARED && Util.isAExtendsB(env, typeMirror, CEnum.class) ? - Optional.of(_ -> Spec.literal("int")) : - Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class))); + case ARRAY, DECLARED -> { + if (typeKind == TypeKind.DECLARED) { + if (Util.isAExtendsB(env, typeMirror, SegmentAllocator.class)) { + yield Optional.of(importData -> Spec.literal(importData.simplifyOrImport(env, typeMirror))); + } + if (Util.isAExtendsB(env, typeMirror, CEnum.class)) { + yield Optional.of(_ -> Spec.literal("int")); + } + } + yield Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class))); + } default -> Optional.empty(); }; } + /** + * Converts to the downcall type + * + * @param env the processing environment + * @param element the element + * @return the downcall type + */ + static Optional toDowncallType(ProcessingEnvironment env, Element element) { + return toDowncallType(env, element, Element::asType); + } + + /** + * Converts to the downcall type + * + * @param env the processing environment + * @param element the element + * @param function the function + * @param the element type + * @return the downcall type + */ + static Optional toDowncallType(ProcessingEnvironment env, T element, Function function) { + final StructRef structRef = element.getAnnotation(StructRef.class); + if (structRef != null) { + return Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class))); + } + return toDowncallType(env, function.apply(element)); + } + /** * Applies the imports * diff --git a/src/main/java/overrun/marshal/gen2/VoidTypeData.java b/src/main/java/overrun/marshal/gen2/VoidTypeData.java index 6a4c657..8c7bdde 100644 --- a/src/main/java/overrun/marshal/gen2/VoidTypeData.java +++ b/src/main/java/overrun/marshal/gen2/VoidTypeData.java @@ -22,7 +22,7 @@ * @author squid233 * @since 0.1.0 */ -public final class VoidTypeData implements TypeData { +public record VoidTypeData() implements TypeData { static final VoidTypeData INSTANCE = new VoidTypeData(); @Override diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index e3f80e3..b041601 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -56,19 +56,31 @@ public static String capitalize(String str) { } /** - * try insert underline + * try insert prefix * + * @param prefix the prefix * @param name the name * @param predicate the predicate * @return the string */ - public static String tryInsertUnderline(String name, Predicate predicate) { + public static String tryInsertPrefix(String prefix, String name, Predicate predicate) { if (predicate.test(name)) { - return tryInsertUnderline("_" + name, predicate); + return tryInsertPrefix(prefix, prefix + name, predicate); } return name; } + /** + * try insert underline + * + * @param name the name + * @param predicate the predicate + * @return the string + */ + public static String tryInsertUnderline(String name, Predicate predicate) { + return tryInsertPrefix("_", name, predicate); + } + /** * Invalid type * @@ -123,6 +135,7 @@ public static boolean isMemorySegment(String clazzName) { * @param typeMirror typeMirror * @return isMemorySegment */ + @Deprecated(since = "0.1.0") public static boolean isMemorySegment(TypeMirror typeMirror) { return isSameClass(typeMirror, MemorySegment.class); } @@ -144,6 +157,7 @@ public static boolean isString(String clazzName) { * @param typeMirror typeMirror * @return isString */ + @Deprecated(since = "0.1.0") public static boolean isString(TypeMirror typeMirror) { return isSameClass(typeMirror, String.class); } @@ -154,6 +168,7 @@ public static boolean isString(TypeMirror typeMirror) { * @param typeMirror typeMirror * @return isArray */ + @Deprecated(since = "0.1.0") public static boolean isArray(TypeMirror typeMirror) { return typeMirror.getKind() == TypeKind.ARRAY; } @@ -285,9 +300,9 @@ public static boolean isAExtendsB(ProcessingEnvironment env, TypeMirror t1, Clas * @param aClass the class */ public static boolean isSameClass(TypeMirror typeMirror, Class aClass) { - return aClass.getCanonicalName().equals(typeMirror.toString()) && - ((typeMirror.getKind().isPrimitive() && aClass.isPrimitive()) || + return ((typeMirror.getKind().isPrimitive() && aClass.isPrimitive()) || (typeMirror.getKind() == TypeKind.ARRAY && aClass.isArray()) || - isDeclared(typeMirror)); + isDeclared(typeMirror)) && + aClass.getCanonicalName().equals(typeMirror.toString()); } } From 3ad1cdebe55dc8414e170c0189a4002c6fc78540 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 13 Jan 2024 22:57:48 +0800 Subject: [PATCH 09/28] Clean code --- .../overrun/marshal/test/CDowncallTest.java | 11 + .../overrun/marshal/DowncallProcessor.java | 697 +----------------- src/main/java/overrun/marshal/gen/Ref.java | 4 - .../java/overrun/marshal/gen1/InvokeSpec.java | 12 - src/main/java/overrun/marshal/gen1/Spec.java | 47 +- .../overrun/marshal/gen2/DowncallData.java | 9 +- .../overrun/marshal/internal/Processor.java | 60 +- 7 files changed, 28 insertions(+), 812 deletions(-) diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 34a08de..1db3e80 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -59,6 +59,12 @@ interface CDowncallTest { @Default void testWithOptional(); + @Default(""" + Math.sqrt( + 2 + )""") + double testDefaultWithMultiline(); + void testWithArgument(int i, MemorySegment holder); MemorySegment testWithArgAndReturnValue(MemorySegment segment); @@ -67,6 +73,11 @@ interface CDowncallTest { return testWithArgAndReturnValue(MemorySegment.NULL);""") MemorySegment testWithCustomBody(); + @Custom(""" + System.out.println("Hello world"); + return testWithArgAndReturnValue(MemorySegment.NULL);""") + MemorySegment testWithCustomBodyMultiline(); + @Access(AccessModifier.PRIVATE) int testWithPrivate(int i); diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java index 19c09fe..dc55fce 100644 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ b/src/main/java/overrun/marshal/DowncallProcessor.java @@ -16,34 +16,15 @@ package overrun.marshal; -import overrun.marshal.gen.*; -import overrun.marshal.gen.struct.ByValue; -import overrun.marshal.gen.struct.StructRef; -import overrun.marshal.gen1.*; +import overrun.marshal.gen.Downcall; import overrun.marshal.gen2.DowncallData; import overrun.marshal.internal.Processor; import javax.annotation.processing.RoundEnvironment; -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; -import javax.tools.JavaFileObject; import java.io.IOException; -import java.io.PrintWriter; -import java.lang.foreign.*; -import java.lang.invoke.MethodHandle; -import java.util.LinkedHashMap; -import java.util.List; import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static overrun.marshal.internal.Util.*; /** * Downcall annotation processor @@ -78,682 +59,6 @@ private void processClasses(RoundEnvironment roundEnv) { }); } - private void writeFile( - TypeElement type, - List fields, - List methods - ) throws IOException { - final Downcall downcall = type.getAnnotation(Downcall.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 (downcall.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 @Downcall and rename this file. For example: @Downcall(name = "%1$s")""" - .formatted(string)); - return; - } - } else { - simpleClassName = downcall.name(); - } - - final SourceFile file = new SourceFile(packageName); - file.addImports("overrun.marshal.*", "java.lang.foreign.*"); - file.addImport(MethodHandle.class); - file.addClass(simpleClassName, classSpec -> { - classSpec.setDocument(getDocument(type)); - classSpec.setFinal(!downcall.nonFinal()); - - // fields - addFields(fields, classSpec); - - if (methods.isEmpty()) { - return; - } - - // loader - addLoader(type, classSpec); - - // method handles - addMethodHandles(type, methods, classSpec); - - // method declarations - methods.forEach(e -> { - 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(); - overloadValue = value.isBlank() ? methodName : value; - } else { - overloadValue = null; - } - final String javaReturnType; - if (eIsStruct) { - javaReturnType = overloadValue != null ? eStructRef.value() : MemorySegment.class.getSimpleName(); - } else { - javaReturnType = toTargetType(returnType, overloadValue); - } - - classSpec.addMethod(new MethodSpec(javaReturnType, methodName), methodSpec -> { - final ByValue byValue = e.getAnnotation(ByValue.class); - final var parameters = e.getParameters(); - 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) || isString(t); - }) || byValue != null); - 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)); - // annotations - 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 SizedSeg eSizedSeg = e.getAnnotation(SizedSeg.class); - final Sized eSized = e.getAnnotation(Sized.class); - - final String arenaParameter = parameters.stream() - .map(VariableElement::getSimpleName) - .filter("arena"::contentEquals) - .findAny() - .map(Object::toString) - .orElse(null); - final String segmentAllocatorParameter = parameters.stream() - .map(VariableElement::getSimpleName) - .filter("segmentAllocator"::contentEquals) - .findAny() - .map(Object::toString) - .orElse(null); - final String allocatorParamName = shouldInsertArena ? - insertUnderline("arena", arenaParameter) : - insertUnderline("segmentAllocator", segmentAllocatorParameter); - - String document = getDocument(e); - if (document != null) { - if (shouldInsertArena) { - document += " @param " + allocatorParamName + " an arena that allocates arguments"; - } else if (shouldInsertAllocator) { - document += " @param " + allocatorParamName + " a segment allocator that allocates arguments"; - } - } - methodSpec.setDocument(document); - methodSpec.setStatic(true); - - if (critical != null) { - methodSpec.addAnnotation(new AnnotationSpec(Critical.class) - .addArgument("allowHeapAccess", getConstExp(critical.allowHeapAccess()))); - } - if (isDefaulted) { - methodSpec.addAnnotation(new AnnotationSpec(Default.class).also(annotationSpec -> { - if (!defaultAnnotation.value().isBlank()) { - annotationSpec.addArgument("value", getConstExp(defaultAnnotation.value())); - } - })); - } - addAnnotationValue(methodSpec, eSizedSeg, SizedSeg.class, SizedSeg::value); - addAnnotationValue(methodSpec, eSized, Sized.class, Sized::value); - - if (access != null) { - methodSpec.setAccessModifier(access.value()); - } - if (shouldInsertArena || shouldInsertAllocator) { - methodSpec.addParameter( - shouldInsertArena ? Arena.class : SegmentAllocator.class, - allocatorParamName - ); - } - - final var parameterTypeFunction = parameterTypeFunction(custom, overloadValue); - parameters.forEach(p -> methodSpec.addParameter(new ParameterSpec(parameterTypeFunction.apply(p), p.getSimpleName().toString()) - .also(parameterSpec -> { - if (p.getAnnotation(NullableRef.class) != null) { - parameterSpec.addAnnotation(new AnnotationSpec(NullableRef.class)); - } - if (p.getAnnotation(Ref.class) != null) { - parameterSpec.addAnnotation(new AnnotationSpec(Ref.class)); - } - addAnnotationValue(parameterSpec, p.getAnnotation(SizedSeg.class), SizedSeg.class, SizedSeg::value); - addAnnotationValue(parameterSpec, p.getAnnotation(Sized.class), Sized.class, Sized::value); - }))); - - if (custom != null) { - methodSpec.addStatement(Spec.indented(custom.value())); - return; - } - - if (overload == null) { - if (isDefaulted && notVoid && defaultAnnotation.value().isBlank()) { - printError(type + "::" + e + ": Default non-void method must have a default return value"); - } - final String entrypoint = methodEntrypoint(e); - methodSpec.addStatement(new TryCatchStatement().also(tryCatchStatement -> { - StatementBlock targetStatement = tryCatchStatement; - if (isDefaulted) { - final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(entrypoint)); - if (notVoid) { - ifStatement.addElseClause(ElseClause.of(), elseClause -> - elseClause.addStatement(Spec.returnStatement(Spec.indentedExceptFirstLine(defaultAnnotation.value()))) - ); - } - targetStatement = ifStatement; - tryCatchStatement.addStatement(ifStatement); - } - final InvokeSpec invokeExact = new InvokeSpec(entrypoint, "invokeExact"); - if (byValue != null) { - invokeExact.addArgument(allocatorParamName); - } - invokeExact.addArguments(e.getParameters().stream() - .map(p -> Spec.literal(p.getSimpleName().toString())) - .collect(Collectors.toList())); - if (notVoid) { - targetStatement.addStatement(Spec.returnStatement(Spec.cast(javaReturnType, invokeExact))); - } 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())))) - ); - })); - return; - } - - // check array size - parameters.stream() - .filter(p -> p.getAnnotation(Sized.class) != null) - .forEach(p -> { - final Sized sized = p.getAnnotation(Sized.class); - methodSpec.addStatement(Spec.statement(new InvokeSpec(Checks.class.getSimpleName(), "checkArraySize") - .addArgument(getConstExp(sized.value())) - .addArgument(Spec.accessSpec(p.getSimpleName().toString(), "length")))); - }); - - // ref - parameters.stream() - .filter(p -> p.getAnnotation(Ref.class) != null) - .forEach(p -> { - final TypeMirror pType = p.asType(); - if (!isArray(pType)) { - return; - } - final TypeMirror arrayComponentType = getArrayComponentType(pType); - final Name name = p.getSimpleName(); - final String nameString = name.toString(); - final Spec invokeSpec; - if (isBooleanArray(pType)) { - invokeSpec = new InvokeSpec(BoolHelper.class, "of") - .addArgument(allocatorParamName) - .addArgument(nameString); - } else if (isPrimitiveArray(pType)) { - invokeSpec = new InvokeSpec(allocatorParamName, "allocateFrom") - .addArgument(toValueLayoutStr(arrayComponentType)) - .addArgument(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); - } - methodSpec.addStatement(new VariableStatement(MemorySegment.class, "_" + nameString, - p.getAnnotation(NullableRef.class) != null ? - Spec.ternaryOp(Spec.notNullSpec(nameString), - invokeSpec, - Spec.accessSpec(MemorySegment.class, "NULL")) : - invokeSpec - ).setAccessModifier(AccessModifier.PACKAGE_PRIVATE)); - }); - - // invoke - final InvokeSpec invocation = new InvokeSpec(simpleClassName, overloadValue); - // add argument to overload invocation - if (byValue != null) { - invocation.addArgument(allocatorParamName); - } - parameters.forEach(p -> { - final TypeMirror pType = p.asType(); - final TypeKind pTypeKind = pType.getKind(); - final String nameString = p.getSimpleName().toString(); - final Spec invocationArg; - if (p.getAnnotation(StructRef.class) != null) { - invocationArg = new InvokeSpec(nameString, "segment"); - } else if (pTypeKind.isPrimitive()) { - invocationArg = Spec.literal(nameString); - } else { - invocationArg = switch (pTypeKind) { - case DECLARED -> { - if (isString(pType)) { - yield new InvokeSpec(allocatorParamName, "allocateFrom") - .addArgument(nameString) - .also(invokeSpec -> { - if (p.getAnnotation(StrCharset.class) != null) { - invokeSpec.addArgument(createCharset(file, getCustomCharset(p))); - } - }); - } - if (isUpcall(pType)) { - yield new InvokeSpec(nameString, "stub") - .addArgument(allocatorParamName); - } - if (isCEnum(pType)) { - yield new InvokeSpec(nameString, "value"); - } - yield Spec.literal(nameString); - } - case ARRAY -> { - if (p.getAnnotation(Ref.class) != null) { - yield Spec.literal('_' + nameString); - } - if (isBooleanArray(pType)) { - yield new InvokeSpec(BoolHelper.class, "of") - .addArgument(allocatorParamName) - .addArgument(nameString); - } - if (isStringArray(pType)) { - yield new InvokeSpec(StrHelper.class, "of") - .addArgument(allocatorParamName) - .addArgument(nameString) - .addArgument(createCharset(file, getCustomCharset(p))); - } - yield new InvokeSpec(allocatorParamName, "allocateFrom") - .addArgument(toValueLayoutStr(getArrayComponentType(pType))) - .addArgument(nameString); - } - default -> null; - }; - if (invocationArg == null) { - printError("Invalid type " + pType + " in " + type + "::" + e); - return; - } - } - invocation.addArgument(p.getAnnotation(NullableRef.class) != null ? - Spec.ternaryOp(Spec.notNullSpec(nameString), - invocationArg, - Spec.accessSpec(MemorySegment.class, "NULL")) : - invocationArg); - }); - final Spec finalSpec; - if (notVoid) { - if (shouldStoreResult) { - finalSpec = new VariableStatement("var", "$_marshalResult", invocation) - .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) - .setFinal(true); - } else if (isCEnum(returnType)) { - final var wrapMethod = findCEnumWrapperMethod(returnType); - if (wrapMethod.isPresent()) { - finalSpec = Spec.returnStatement(new InvokeSpec(returnType.toString(), wrapMethod.get().getSimpleName().toString()) - .addArgument(invocation)); - } else { - printError(wrapperNotFound(returnType, type, "::", e, "CEnum.Wrapper")); - return; - } - } else { - finalSpec = Spec.returnStatement(invocation); - } - } else { - finalSpec = Spec.statement(invocation); - } - methodSpec.addStatement(finalSpec); - - // ref result - parameters.stream() - .filter(p -> p.getAnnotation(Ref.class) != null) - .forEach(p -> { - final Spec refReturnSpec; - final TypeMirror pType = p.asType(); - final String nameString = p.getSimpleName().toString(); - final Ref ref = p.getAnnotation(Ref.class); - final boolean nullable = p.getAnnotation(NullableRef.class) != null; - if (isArray(pType)) { - final Spec spec; - if (isBooleanArray(pType)) { - spec = Spec.statement(new InvokeSpec(BoolHelper.class, "copy") - .addArgument('_' + nameString) - .addArgument(nameString)); - } else { - if (isPrimitiveArray(pType)) { - spec = Spec.statement(new InvokeSpec(MemorySegment.class, "copy") - .addArgument('_' + nameString) - .addArgument(toValueLayoutStr(getArrayComponentType(pType))) - .addArgument("0L") - .addArgument(nameString) - .addArgument("0") - .addArgument(Spec.accessSpec(nameString, "length"))); - } else if (isStringArray(pType)) { - spec = Spec.statement(new InvokeSpec(StrHelper.class, "copy") - .addArgument('_' + nameString) - .addArgument(nameString) - .addArgument(createCharset(file, getCustomCharset(p)))); - } else { - spec = null; - } - } - if (nullable) { - final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(nameString)); - if (spec != null) { - ifStatement.addStatement(spec); - } - refReturnSpec = ifStatement; - } else { - refReturnSpec = spec; - } - methodSpec.addStatement(refReturnSpec); - } - }); - - // return - if (shouldStoreResult) { - // converting return value - Spec finalInvocation = Spec.literal("$_marshalResult"); - if (eIsStruct) { - finalInvocation = new ConstructSpec(eStructRef.value()) - .addArgument(finalInvocation); - } else if (isArray(returnType)) { - final TypeMirror arrayComponentType = getArrayComponentType(returnType); - 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 = findUpcallWrapperMethod(returnType); - if (wrapMethod.isPresent()) { - finalInvocation = new InvokeSpec(returnType.toString(), wrapMethod.get().getSimpleName().toString()) - .addArgument(finalInvocation); - } else { - printError(wrapperNotFound(returnType, type, "::", e, "Upcall.Wrapper")); - return; - } - } else if (isCEnum(returnType)) { - final var wrapMethod = findCEnumWrapperMethod(returnType); - if (wrapMethod.isPresent()) { - finalInvocation = new InvokeSpec(returnType.toString(), wrapMethod.get().getSimpleName().toString()) - .addArgument(finalInvocation); - } else { - printError(wrapperNotFound(returnType, type, "::", e, "CEnum.Wrapper")); - return; - } - } - } - - methodSpec.addStatement(Spec.returnStatement( - (eIsStruct || canConvertToAddress(returnType)) ? - Spec.ternaryOp(Spec.neqSpec(new InvokeSpec("$_marshalResult", "address"), Spec.literal("0L")), - finalInvocation, - Spec.literal("null") - ) : - finalInvocation - )); - } - }); - }); - }); - - final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + '.' + simpleClassName); - try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) { - file.write(out); - } - } - - 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(); - if (constantValue == null) { - return; - } - classSpec.addField(new VariableStatement(simplify(e.asType().toString()), - e.getSimpleName().toString(), - Spec.literal(getConstExp(constantValue))) - .setDocument(getDocument(e)) - .setStatic(true) - .setFinal(true)); - }); - } - - private void addLoader(TypeElement type, ClassSpec classSpec) { - final Downcall downcall = type.getAnnotation(Downcall.class); - final String loader = type.getAnnotationMirrors().stream() - .filter(m -> Downcall.class.getCanonicalName().equals(m.getAnnotationType().toString())) - .findFirst() - .orElseThrow() - .getElementValues().entrySet().stream() - .filter(e -> "loader()".equals(e.getKey().toString())) - .findFirst() - .map(e -> e.getValue().getValue().toString()) - .orElse(null); - final String libname = downcall.libname(); - final InvokeSpec invocation; - if (loader == null) { - invocation = new InvokeSpec(SymbolLookup.class, "libraryLookup") - .addArgument(getConstExp(libname)) - .addArgument(new InvokeSpec(Arena.class, "global")); - } else { - final var libLoader = findLibLoaderMethod(processingEnv.getElementUtils().getTypeElement(loader).asType()); - if (libLoader.isPresent()) { - invocation = new InvokeSpec(loader, libLoader.get().getSimpleName().toString()) - .addArgument(getConstExp(libname)); - } else { - printError(specifiedMethodNotFound("loader", loader, type, "", "", "Loader")); - return; - } - } - classSpec.addField(new VariableStatement(SymbolLookup.class, "_LOOKUP", invocation) - .setAccessModifier(AccessModifier.PRIVATE) - .setStatic(true) - .setFinal(true)); - classSpec.addField(new VariableStatement(Linker.class, "_LINKER", - new InvokeSpec(Linker.class, "nativeLinker")) - .setAccessModifier(AccessModifier.PRIVATE) - .setStatic(true) - .setFinal(true)); - } - - private void addMethodHandles(TypeElement type, List methods, ClassSpec classSpec) { - methods.stream().collect(Collectors.toMap(DowncallProcessor::methodEntrypoint, Function.identity(), (e1, e2) -> { - final Overload o1 = e1.getAnnotation(Overload.class); - final Overload o2 = e2.getAnnotation(Overload.class); - // if e1 is not an overload - if (o1 == null) { - // if e2 is an overload - if (o2 != null) { - return e1; - } - final Custom c1 = e1.getAnnotation(Custom.class); - final Custom c2 = e2.getAnnotation(Custom.class); - // if e1 is not custom - if (c1 == null) { - // if e2 is custom - if (c2 != null) { - return e1; - } - // compare function descriptor - final List p1 = collectConflictedMethodSignature(e1); - final List p2 = collectConflictedMethodSignature(e2); - if (!p1.equals(p2)) { - printError("Overload not supported between " + type + "::" + e1 + " and ::" + e2); - } - return e1; - } - // e1 is custom - if (c2 == null) { - return e2; - } - // overwrite it. - return e2; - } - // e1 is an overload - // overwrite it. - return e2; - }, LinkedHashMap::new)).forEach((k, v) -> { - final TypeMirror returnType = v.getReturnType(); - final Critical critical = v.getAnnotation(Critical.class); - final boolean defaulted = v.getAnnotation(Default.class) != null; - classSpec.addField(new VariableStatement(MethodHandle.class, k, - new InvokeSpec(new InvokeSpec( - new InvokeSpec("_LOOKUP", "find").addArgument(getConstExp(k)), - "map" - ).addArgument(new LambdaSpec("_s") - .addStatementThis(new InvokeSpec("_LINKER", "downcallHandle") - .addArgument("_s") - .addArgument(new InvokeSpec(FunctionDescriptor.class, - returnType.getKind() == TypeKind.VOID ? "ofVoid" : "of").also(invokeSpec -> { - if (returnType.getKind() != TypeKind.VOID) { - final StructRef structRef = v.getAnnotation(StructRef.class); - final boolean isStruct = structRef != null; - final String valueLayoutStr = isStruct ? "ValueLayout.ADDRESS" : toValueLayoutStr(returnType); - final SizedSeg sizedSeg = v.getAnnotation(SizedSeg.class); - final Sized sized = v.getAnnotation(Sized.class); - if (isStruct) { - if (v.getAnnotation(ByValue.class) != null) { - invokeSpec.addArgument(Spec.accessSpec(structRef.value(), "LAYOUT")); - } else { - invokeSpec.addArgument(new InvokeSpec(valueLayoutStr, "withTargetLayout") - .addArgument(Spec.accessSpec(structRef.value(), "LAYOUT"))); - } - } else if (sizedSeg != null && isMemorySegment(returnType)) { - invokeSpec.addArgument(new InvokeSpec(valueLayoutStr, "withTargetLayout") - .addArgument(new InvokeSpec(MemoryLayout.class, "sequenceLayout") - .addArgument(getConstExp(sizedSeg.value())) - .addArgument(Spec.accessSpec(ValueLayout.class, "JAVA_BYTE")))); - } else if (sized != null && isArray(returnType)) { - invokeSpec.addArgument(new InvokeSpec(valueLayoutStr, "withTargetLayout") - .addArgument(new InvokeSpec(MemoryLayout.class, "sequenceLayout") - .addArgument(getConstExp(sized.value())) - .addArgument(Spec.accessSpec(ValueLayout.class, toValueLayoutStr(getArrayComponentType(returnType)))))); - } else { - invokeSpec.addArgument(valueLayoutStr); - } - } - 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") - .addArgument(getConstExp(critical.allowHeapAccess()))); - } - }))), defaulted ? "orElse" : "orElseThrow").also(invokeSpec -> { - if (defaulted) { - invokeSpec.addArgument("null"); - } - })) - .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()); - } - }); - }); - } - - 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"; - } - if (isValueType(t)) { - return toValueLayoutStr(t); - } - return t.toString(); - }).toList(); - } - - private static String methodEntrypoint(ExecutableElement method) { - final Entrypoint annotation = method.getAnnotation(Entrypoint.class); - if (annotation != null) { - final String entrypoint = annotation.value(); - if (!entrypoint.isBlank()) { - return entrypoint; - } - } - return method.getSimpleName().toString(); - } - - private String toTargetType(TypeMirror typeMirror, String overload) { - final TypeKind typeKind = typeMirror.getKind(); - if (typeKind.isPrimitive()) { - return typeMirror.toString(); - } - return switch (typeKind) { - case VOID -> typeMirror.toString(); - case DECLARED -> { - if (isMemorySegment(typeMirror)) { - yield MemorySegment.class.getSimpleName(); - } - if (isString(typeMirror)) { - yield overload == null ? MemorySegment.class.getSimpleName() : String.class.getSimpleName(); - } - if (isUpcall(typeMirror)) { - yield overload == null ? MemorySegment.class.getSimpleName() : typeMirror.toString(); - } - if (isCEnum(typeMirror)) { - yield overload == null ? int.class.getSimpleName() : typeMirror.toString(); - } - throw invalidType(typeMirror); - } - case ARRAY -> { - final boolean string = isString(getArrayComponentType(typeMirror)); - if (isPrimitiveArray(typeMirror) || string) { - yield overload == null ? MemorySegment.class.getSimpleName() : (string ? String.class.getSimpleName() + "[]" : typeMirror.toString()); - } - throw invalidType(typeMirror); - } - default -> throw invalidType(typeMirror); - }; - } - @Override public Set getSupportedAnnotationTypes() { return Set.of(Downcall.class.getCanonicalName()); diff --git a/src/main/java/overrun/marshal/gen/Ref.java b/src/main/java/overrun/marshal/gen/Ref.java index b28ebd2..3539bf3 100644 --- a/src/main/java/overrun/marshal/gen/Ref.java +++ b/src/main/java/overrun/marshal/gen/Ref.java @@ -32,8 +32,4 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.SOURCE) public @interface Ref { - /** - * {@return {@code true} if the array is nullable; {@code false} otherwise} - */ - boolean nullable() default false; } diff --git a/src/main/java/overrun/marshal/gen1/InvokeSpec.java b/src/main/java/overrun/marshal/gen1/InvokeSpec.java index d36bad4..7b12532 100644 --- a/src/main/java/overrun/marshal/gen1/InvokeSpec.java +++ b/src/main/java/overrun/marshal/gen1/InvokeSpec.java @@ -17,7 +17,6 @@ package overrun.marshal.gen1; import java.util.ArrayList; -import java.util.Collection; import java.util.List; import java.util.function.Consumer; @@ -92,17 +91,6 @@ public InvokeSpec addArgument(String spec) { return addArgument(Spec.literal(spec)); } - /** - * Add arguments - * - * @param spec the arguments - * @return this - */ - public InvokeSpec addArguments(Collection spec) { - arguments.addAll(spec); - return this; - } - /** * Runs the action * diff --git a/src/main/java/overrun/marshal/gen1/Spec.java b/src/main/java/overrun/marshal/gen1/Spec.java index a5d84d5..846dacf 100644 --- a/src/main/java/overrun/marshal/gen1/Spec.java +++ b/src/main/java/overrun/marshal/gen1/Spec.java @@ -75,16 +75,6 @@ static Spec simpleClassName(Class clazz) { return literal(clazz.getSimpleName()); } - /** - * Create class name spec - * - * @param clazz class - * @return spec - */ - static Spec className(Class clazz) { - return literal(clazz.getCanonicalName()); - } - /** * Create an assign statement * @@ -152,20 +142,6 @@ static Spec accessSpec(String object, String member) { return literal(object + '.' + member); } - /** - * Create access spec - * - * @param object object - * @param member member - * @return spec - */ - static Spec accessSpec(Spec object, String member) { - return (builder, indent) -> { - object.append(builder, indent); - builder.append('.').append(member); - }; - } - /** * Create ternary operator spec * @@ -294,22 +270,21 @@ static Spec indented(String s) { } /** - * Create an indented literal string + * Indent code block * - * @param s the string + * @param s the code * @return the spec */ - static Spec indentedExceptFirstLine(String s) { - return (builder, indent) -> { + static Spec indentCodeBlock(String s) { + return (builder, indent) -> s.lines().findFirst().ifPresent(first -> { final String indentString = indentString(indent); - s.lines().findFirst().ifPresentOrElse(s1 -> { - builder.append(s1); - if (s.lines().count() > 1) { - builder.append(s.lines().skip(1).map(s2 -> indentString + s2).collect(Collectors.joining("\n", "\n", ""))); - } - }, () -> { - }); - }; + builder.append(first) + .append('\n') + .append(s.lines() + .skip(1) + .map(s1 -> indentString + s1) + .collect(Collectors.joining("\n"))); + }); } /** diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index d9fe4b4..b1f9fb8 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -577,7 +577,7 @@ private void addDowncallMethod( final String defaultValue = defaultAnnotation.value(); if (!defaultValue.isBlank()) { ifStatement.addElseClause(ElseClause.of(), elseClause -> - elseClause.addStatement(Spec.returnStatement(Spec.literal(defaultValue))) + elseClause.addStatement(Spec.returnStatement(Spec.indentCodeBlock(defaultValue))) ); } optionalSpec = ifStatement; @@ -592,7 +592,7 @@ private void addDowncallMethod( exceptionName ), catchClause -> catchClause.addStatement(Spec.throwStatement(new ConstructSpec(importData.simplifyOrImport(RuntimeException.class)) - .addArgument(Spec.literal(exceptionName))))); + .addArgument(Spec.literal(catchClause.name()))))); }) ) )); @@ -1048,9 +1048,8 @@ private void addMethods(ClassSpec spec) { methodSpec.setAccessModifier(functionData.accessModifier()); methodSpec.setStatic(true); functionData.parameters().forEach(parameterData -> - methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()).also(parameterSpec -> { - addAnnotationSpec(parameterData.annotations(), parameterSpec); - })) + methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()).also(parameterSpec -> + addAnnotationSpec(parameterData.annotations(), parameterSpec))) ); functionData.statements().forEach(typeUse -> methodSpec.addStatement(typeUse.apply(imports))); diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java index 34fa1ca..d23cc4b 100644 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ b/src/main/java/overrun/marshal/internal/Processor.java @@ -16,10 +16,8 @@ package overrun.marshal.internal; -import overrun.marshal.gen.CEnum; -import overrun.marshal.gen.Loader; -import overrun.marshal.gen.StrCharset; import overrun.marshal.Upcall; +import overrun.marshal.gen.StrCharset; import overrun.marshal.gen1.*; import javax.annotation.processing.AbstractProcessor; @@ -32,7 +30,6 @@ import java.io.PrintWriter; import java.io.Writer; import java.lang.annotation.Annotation; -import java.lang.foreign.SymbolLookup; import java.lang.foreign.ValueLayout; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; @@ -53,10 +50,6 @@ public abstract class Processor extends AbstractProcessor { * upcallTypeMirror */ protected TypeMirror upcallTypeMirror; - /** - * cEnumTypeMirror - */ - protected TypeMirror cEnumTypeMirror; /** * constructor @@ -68,7 +61,6 @@ protected Processor() { public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); upcallTypeMirror = getTypeMirror(Upcall.class); - cEnumTypeMirror = getTypeMirror(CEnum.class); } /** @@ -149,16 +141,6 @@ protected boolean isUpcall(TypeMirror typeMirror) { return isAOfB(typeMirror, upcallTypeMirror); } - /** - * isCEnum - * - * @param typeMirror typeMirror - * @return isCEnum - */ - protected boolean isCEnum(TypeMirror typeMirror) { - return isAOfB(typeMirror, cEnumTypeMirror); - } - /** * Add an annotation * @@ -302,46 +284,6 @@ protected Optional findUpcallWrapperMethod(TypeMirror typeMir .findFirst(); } - /** - * Find CEnum wrapper method - * - * @param typeMirror the type - * @return the CEnum wrapper method - */ - protected Optional findCEnumWrapperMethod(TypeMirror typeMirror) { - return ElementFilter.methodsIn(processingEnv.getTypeUtils() - .asElement(typeMirror) - .getEnclosedElements()) - .stream() - .filter(method -> method.getAnnotation(CEnum.Wrapper.class) != null) - .filter(method -> { - final var list = method.getParameters(); - return list.size() == 1 && int.class.getCanonicalName().equals(list.getFirst().asType().toString()); - }) - .findFirst(); - } - - /** - * Find LibLoader method - * - * @param typeMirror the type - * @return the LibLoader method - */ - protected Optional findLibLoaderMethod(TypeMirror typeMirror) { - return ElementFilter.methodsIn(processingEnv.getTypeUtils() - .asElement(typeMirror) - .getEnclosedElements()) - .stream() - .filter(method -> method.getAnnotation(Loader.class) != null) - .filter(method -> { - final var list = method.getParameters(); - return list.size() == 1 && - isString(list.getFirst().asType()) && - SymbolLookup.class.getCanonicalName().equals(method.getReturnType().toString()); - }) - .findFirst(); - } - /** * specifiedMethodNotFound * From f02b09f524d99d507798399603f703e849659e6d Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sat, 13 Jan 2024 23:34:30 +0800 Subject: [PATCH 10/28] Added StructData --- .../java/overrun/marshal/StructProcessor.java | 6 +- .../java/overrun/marshal/gen2/BaseData.java | 249 ++++++++++++++++++ .../overrun/marshal/gen2/DowncallData.java | 129 +-------- .../marshal/gen2/struct/StructData.java | 70 +++++ 4 files changed, 332 insertions(+), 122 deletions(-) create mode 100644 src/main/java/overrun/marshal/gen2/BaseData.java create mode 100644 src/main/java/overrun/marshal/gen2/struct/StructData.java diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 78cd5ee..7a128d4 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -17,7 +17,10 @@ package overrun.marshal; import overrun.marshal.gen.*; -import overrun.marshal.gen.struct.*; +import overrun.marshal.gen.struct.Const; +import overrun.marshal.gen.struct.Padding; +import overrun.marshal.gen.struct.Struct; +import overrun.marshal.gen.struct.StructRef; import overrun.marshal.gen1.*; import overrun.marshal.internal.Processor; import overrun.marshal.struct.IStruct; @@ -65,6 +68,7 @@ private void processClasses(RoundEnvironment roundEnv) { ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Struct.class)).forEach(e -> { final var enclosed = e.getEnclosedElements(); try { +// new StructData(processingEnv).generate(e); writeFile(e, ElementFilter.fieldsIn(enclosed)); } catch (IOException ex) { printStackTrace(ex); diff --git a/src/main/java/overrun/marshal/gen2/BaseData.java b/src/main/java/overrun/marshal/gen2/BaseData.java new file mode 100644 index 0000000..7b0155b --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/BaseData.java @@ -0,0 +1,249 @@ +/* + * 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.gen2; + +import overrun.marshal.gen.Skip; +import overrun.marshal.gen1.*; +import overrun.marshal.gen2.struct.StructData; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.JavaFileObject; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Stream; + +/** + * Base data + * + * @author squid233 + * @since 0.1.0 + */ +public abstract sealed class BaseData permits DowncallData, StructData { + /** + * the processing environment + */ + protected final ProcessingEnvironment processingEnv; + /** + * imports + */ + protected final ImportData imports = new ImportData(new ArrayList<>()); + /** + * fields + */ + protected final List fields = new ArrayList<>(); + /** + * functionDataList + */ + protected final List functionDataList = new ArrayList<>(); + /** + * document + */ + protected String document = null; + /** + * nonFinal + */ + protected boolean nonFinal = false; + + /** + * Construct + * + * @param processingEnv the processing environment + */ + protected BaseData(ProcessingEnvironment processingEnv) { + this.processingEnv = processingEnv; + } + + /** + * {@return generateClassName} + * + * @param name name + * @param typeElement typeElement + */ + protected DeclaredTypeData generateClassName(String name, TypeElement typeElement) { + final DeclaredTypeData declaredTypeDataOfTypeElement = (DeclaredTypeData) TypeData.detectType(processingEnv, typeElement.asType()); + final String simpleClassName; + if (name.isBlank()) { + final String string = declaredTypeDataOfTypeElement.name(); + if (string.startsWith("C")) { + simpleClassName = string.substring(1); + } else { + processingEnv.getMessager().printError(""" + Class name must start with C if the name is not specified. Current name: %s + Possible solutions: + 1) Add C as a prefix e.g. C%1$s + 2) Specify the name in @Struct e.g. @Struct(name = "%1$s")""" + .formatted(string), typeElement); + return null; + } + } else { + simpleClassName = name; + } + return new DeclaredTypeData(declaredTypeDataOfTypeElement.packageName(), simpleClassName); + } + + /** + * Generates the file + * + * @param declaredTypeData the class name + * @throws IOException if an I/O error occurred + */ + protected void generate(DeclaredTypeData declaredTypeData) throws IOException { + final String packageName = declaredTypeData.packageName(); + final String simpleClassName = declaredTypeData.name(); + final SourceFile file = new SourceFile(packageName); + file.addClass(simpleClassName, classSpec -> { + if (document != null) { + classSpec.setDocument(document); + } + classSpec.setFinal(!nonFinal); + + addFields(classSpec); + addMethods(classSpec); + }); + imports.imports() + .stream() + .filter(typeData -> !"java.lang".equals(typeData.packageName())) + .map(DeclaredTypeData::toString) + .sorted((s1, s2) -> { + final boolean s1Java = s1.startsWith("java."); + final boolean s2Java = s2.startsWith("java."); + if (s1Java && !s2Java) { + return 1; + } + if (!s1Java && s2Java) { + return -1; + } + return s1.compareTo(s2); + }) + .forEach(file::addImport); + + final JavaFileObject sourceFile = processingEnv.getFiler() + .createSourceFile(packageName + "." + simpleClassName); + try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) { + file.write(out); + } + } + + /** + * Generates the file + * + * @param typeElement the type element + * @throws IOException if an I/O error occurred + */ + public abstract void generate(TypeElement typeElement) throws IOException; + + /** + * addFields + * + * @param spec spec + */ + protected void addFields(ClassSpec spec) { + fields.forEach(fieldData -> { + final TypeData type = fieldData.type(); + spec.addField(new VariableStatement( + imports.simplifyOrImport(type), + fieldData.name(), + fieldData.value().apply(imports) + ).setDocument(fieldData.document()) + .setAccessModifier(fieldData.accessModifier()) + .setStatic(fieldData.staticField()) + .setFinal(fieldData.finalField())); + }); + } + + /** + * addMethods + * + * @param spec spec + */ + protected void addMethods(ClassSpec spec) { + functionDataList.forEach(functionData -> + spec.addMethod(new MethodSpec(functionData.returnType().apply(imports), functionData.name()), methodSpec -> { + methodSpec.setDocument(functionData.document()); + addAnnotationSpec(functionData.annotations(), methodSpec); + methodSpec.setAccessModifier(functionData.accessModifier()); + methodSpec.setStatic(true); + functionData.parameters().forEach(parameterData -> + methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()).also(parameterSpec -> + addAnnotationSpec(parameterData.annotations(), parameterSpec))) + ); + functionData.statements().forEach(typeUse -> + methodSpec.addStatement(typeUse.apply(imports))); + })); + } + + /** + * addAnnotationSpec + * + * @param list list + * @param annotatable annotatable + */ + protected void addAnnotationSpec(List list, Annotatable annotatable) { + list.forEach(annotationData -> { + final AnnotationMirror annotationMirror = annotationData.mirror(); + annotatable.addAnnotation(new AnnotationSpec( + imports.simplifyOrImport(processingEnv, annotationMirror.getAnnotationType()) + ).also(annotationSpec -> + annotationMirror.getElementValues().forEach((executableElement, annotationValue) -> + annotationSpec.addArgument(executableElement.getSimpleName().toString(), annotationValue.toString())))); + }); + } + + /** + * {@return getDocComment} + * + * @param e e + */ + protected String getDocComment(Element e) { + return processingEnv.getElementUtils().getDocComment(e); + } + + /** + * {@return getConstExp} + * + * @param v v + */ + protected String getConstExp(Object v) { + return processingEnv.getElementUtils().getConstantExpression(v); + } + + /** + * {@return skipAnnotated} + * + * @param stream stream + * @param type + */ + protected static Stream skipAnnotated(Stream stream) { + return stream.filter(t -> t.getAnnotation(Skip.class) == null); + } + + /** + * {@return skipAnnotated} + * + * @param collection collection + * @param type + */ + protected static Stream skipAnnotated(Collection collection) { + return skipAnnotated(collection.stream()); + } +} diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index b1f9fb8..c4f0dde 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -28,9 +28,7 @@ import javax.lang.model.type.TypeKind; 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.annotation.Annotation; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; @@ -49,7 +47,7 @@ * @author squid233 * @since 0.1.0 */ -public final class DowncallData { +public final class DowncallData extends BaseData { private static final List> METHOD_ANNOTATIONS = List.of( ByValue.class, Critical.class, @@ -65,12 +63,6 @@ public final class DowncallData { Sized.class, StrCharset.class ); - private final ProcessingEnvironment processingEnv; - private final ImportData imports = new ImportData(new ArrayList<>()); - private final List fields = new ArrayList<>(); - private final List functionDataList = new ArrayList<>(); - private String document = null; - private boolean nonFinal = false; /** * Construct @@ -78,17 +70,16 @@ public final class DowncallData { * @param processingEnv the processing environment */ public DowncallData(ProcessingEnvironment processingEnv) { - this.processingEnv = processingEnv; + super(processingEnv); } private void processDowncallType(TypeElement typeElement, String simpleClassName) { final var enclosedElements = typeElement.getEnclosedElements(); - // annotations final Downcall downcall = typeElement.getAnnotation(Downcall.class); - document = getDocComment(typeElement); - nonFinal = downcall.nonFinal(); + this.document = getDocComment(typeElement); + this.nonFinal = downcall.nonFinal(); // process fields skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).forEach(element -> { @@ -305,61 +296,14 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName * @param typeElement the type element * @throws IOException if an I/O error occurred */ + @Override public void generate(TypeElement typeElement) throws IOException { final Downcall downcall = typeElement.getAnnotation(Downcall.class); - final DeclaredTypeData declaredTypeDataOfTypeElement = (DeclaredTypeData) TypeData.detectType(processingEnv, typeElement.asType()); - final String simpleClassName; - if (downcall.name().isBlank()) { - final String string = declaredTypeDataOfTypeElement.name(); - if (string.startsWith("C")) { - simpleClassName = string.substring(1); - } else { - processingEnv.getMessager().printError(""" - Class name must start with C if the name is not specified. Current name: %s - Possible solutions: - 1) Add C as a prefix e.g. C%1$s - 2) Specify the name in @Downcall e.g. @Downcall(name = "%1$s")""" - .formatted(string), typeElement); - return; - } - } else { - simpleClassName = downcall.name(); - } - - processDowncallType(typeElement, simpleClassName); + final DeclaredTypeData generatedClassName = generateClassName(downcall.name(), typeElement); - final SourceFile file = new SourceFile(declaredTypeDataOfTypeElement.packageName()); - file.addClass(simpleClassName, classSpec -> { - if (document != null) { - classSpec.setDocument(document); - } - classSpec.setFinal(!nonFinal); + processDowncallType(typeElement, generatedClassName.name()); - addFields(classSpec); - addMethods(classSpec); - }); - imports.imports() - .stream() - .filter(declaredTypeData -> !"java.lang".equals(declaredTypeData.packageName())) - .map(DeclaredTypeData::toString) - .sorted((s1, s2) -> { - final boolean s1Java = s1.startsWith("java."); - final boolean s2Java = s2.startsWith("java."); - if (s1Java && !s2Java) { - return 1; - } - if (!s1Java && s2Java) { - return -1; - } - return s1.compareTo(s2); - }) - .forEach(file::addImport); - - final JavaFileObject sourceFile = processingEnv.getFiler() - .createSourceFile(declaredTypeDataOfTypeElement.packageName() + "." + simpleClassName); - try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) { - file.write(out); - } + generate(generatedClassName); } private void addOverloadMethod( @@ -1017,61 +961,4 @@ private boolean containsNonDowncallType(ExecutableElement executableElement) { return returnNonDowncallType(executableElement) || parameterContainsNonDowncallType(executableElement); } - - private static Stream skipAnnotated(Stream stream) { - return stream.filter(t -> t.getAnnotation(Skip.class) == null); - } - - private static Stream skipAnnotated(Collection collection) { - return skipAnnotated(collection.stream()); - } - - private void addFields(ClassSpec spec) { - fields.forEach(fieldData -> { - final TypeData type = fieldData.type(); - spec.addField(new VariableStatement( - imports.simplifyOrImport(type), - fieldData.name(), - fieldData.value().apply(imports) - ).setDocument(fieldData.document()) - .setAccessModifier(fieldData.accessModifier()) - .setStatic(fieldData.staticField()) - .setFinal(fieldData.finalField())); - }); - } - - private void addMethods(ClassSpec spec) { - functionDataList.forEach(functionData -> - spec.addMethod(new MethodSpec(functionData.returnType().apply(imports), functionData.name()), methodSpec -> { - methodSpec.setDocument(functionData.document()); - addAnnotationSpec(functionData.annotations(), methodSpec); - methodSpec.setAccessModifier(functionData.accessModifier()); - methodSpec.setStatic(true); - functionData.parameters().forEach(parameterData -> - methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()).also(parameterSpec -> - addAnnotationSpec(parameterData.annotations(), parameterSpec))) - ); - functionData.statements().forEach(typeUse -> - methodSpec.addStatement(typeUse.apply(imports))); - })); - } - - private void addAnnotationSpec(List list, Annotatable annotatable) { - list.forEach(annotationData -> { - final AnnotationMirror annotationMirror = annotationData.mirror(); - annotatable.addAnnotation(new AnnotationSpec( - imports.simplifyOrImport(processingEnv, annotationMirror.getAnnotationType()) - ).also(annotationSpec -> - annotationMirror.getElementValues().forEach((executableElement, annotationValue) -> - annotationSpec.addArgument(executableElement.getSimpleName().toString(), annotationValue.toString())))); - }); - } - - private String getDocComment(Element e) { - return processingEnv.getElementUtils().getDocComment(e); - } - - private String getConstExp(Object v) { - return processingEnv.getElementUtils().getConstantExpression(v); - } } diff --git a/src/main/java/overrun/marshal/gen2/struct/StructData.java b/src/main/java/overrun/marshal/gen2/struct/StructData.java new file mode 100644 index 0000000..bd498df --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/struct/StructData.java @@ -0,0 +1,70 @@ +/* + * 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.gen2.struct; + +import overrun.marshal.gen.struct.Struct; +import overrun.marshal.gen2.BaseData; +import overrun.marshal.gen2.DeclaredTypeData; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.TypeElement; +import javax.lang.model.util.ElementFilter; +import java.io.IOException; + +/** + * Holds struct fields and methods + * + * @author squid233 + * @since 0.1.0 + */ +public final class StructData extends BaseData { + /** + * Construct + * + * @param processingEnv the processing environment + */ + public StructData(ProcessingEnvironment processingEnv) { + super(processingEnv); + } + + private void processStructType(TypeElement typeElement, String simpleClassName) { + final var enclosedElements = typeElement.getEnclosedElements(); + + final Struct struct = typeElement.getAnnotation(Struct.class); + + this.document = getDocComment(typeElement); + this.nonFinal = struct.nonFinal(); + + final var fields = skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).toList(); + } + + /** + * Generates the file + * + * @param typeElement the type element + * @throws IOException if an I/O error occurred + */ + @Override + public void generate(TypeElement typeElement) throws IOException { + final Struct struct = typeElement.getAnnotation(Struct.class); + final DeclaredTypeData generatedClassName = generateClassName(struct.name(), typeElement); + + processStructType(typeElement, generatedClassName.name()); + + generate(generatedClassName); + } +} From 4e2351ff10970f85a5fee7710fa2f1fbf0daa048 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Sun, 14 Jan 2024 16:50:41 +0800 Subject: [PATCH 11/28] Update struct generator --- .../java/overrun/marshal/StructProcessor.java | 5 +- .../overrun/marshal/gen1/AnnotationSpec.java | 24 +- .../java/overrun/marshal/gen1/ClassSpec.java | 5 - .../java/overrun/marshal/gen1/MethodSpec.java | 5 - .../overrun/marshal/gen1/ParameterSpec.java | 5 - .../marshal/gen1/VariableStatement.java | 15 +- .../overrun/marshal/gen2/AnnotationData.java | 11 +- .../java/overrun/marshal/gen2/BaseData.java | 125 ++++-- .../overrun/marshal/gen2/DowncallData.java | 62 +-- .../overrun/marshal/gen2/FunctionData.java | 2 + .../overrun/marshal/gen2/ParameterData.java | 3 - .../java/overrun/marshal/gen2/TypeUse.java | 57 ++- .../marshal/gen2/struct/MemberData.java | 29 ++ .../marshal/gen2/struct/StructData.java | 392 +++++++++++++++++- .../java/overrun/marshal/struct/IStruct.java | 4 +- 15 files changed, 628 insertions(+), 116 deletions(-) create mode 100644 src/main/java/overrun/marshal/gen2/struct/MemberData.java diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java index 7a128d4..6c3c734 100644 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ b/src/main/java/overrun/marshal/StructProcessor.java @@ -22,6 +22,7 @@ import overrun.marshal.gen.struct.Struct; import overrun.marshal.gen.struct.StructRef; import overrun.marshal.gen1.*; +import overrun.marshal.gen2.struct.StructData; import overrun.marshal.internal.Processor; import overrun.marshal.struct.IStruct; @@ -66,10 +67,8 @@ public boolean process(Set annotations, RoundEnvironment private void processClasses(RoundEnvironment roundEnv) { ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Struct.class)).forEach(e -> { - final var enclosed = e.getEnclosedElements(); try { -// new StructData(processingEnv).generate(e); - writeFile(e, ElementFilter.fieldsIn(enclosed)); + new StructData(processingEnv).generate(e); } catch (IOException ex) { printStackTrace(ex); } diff --git a/src/main/java/overrun/marshal/gen1/AnnotationSpec.java b/src/main/java/overrun/marshal/gen1/AnnotationSpec.java index 3d73cda..7b46c3c 100644 --- a/src/main/java/overrun/marshal/gen1/AnnotationSpec.java +++ b/src/main/java/overrun/marshal/gen1/AnnotationSpec.java @@ -30,27 +30,40 @@ public final class AnnotationSpec implements Spec { private final String type; private final Map arguments = new LinkedHashMap<>(); + private final boolean escapeAtCharacter; /** * Constructor * * @param type type */ + @Deprecated(since = "0.1.0") public AnnotationSpec(Class type) { this(type.getSimpleName()); } + /** + * Constructor + * + * @param type type + * @param escapeAtCharacter escapeAtCharacter + */ + public AnnotationSpec(String type, boolean escapeAtCharacter) { + this.type = type; + this.escapeAtCharacter = escapeAtCharacter; + } + /** * Constructor * * @param type type */ public AnnotationSpec(String type) { - this.type = type; + this(type, false); } /** - * Add a argument + * Add an argument * * @param name name * @param value value @@ -74,7 +87,12 @@ public AnnotationSpec also(Consumer consumer) { @Override public void append(StringBuilder builder, int indent) { - builder.append('@').append(type); + if (escapeAtCharacter) { + builder.append("@"); + } else { + builder.append('@'); + } + builder.append(type); if (!arguments.isEmpty()) { builder.append('('); if (arguments.size() == 1 && "value".equals(arguments.keySet().stream().findFirst().orElse(null))) { diff --git a/src/main/java/overrun/marshal/gen1/ClassSpec.java b/src/main/java/overrun/marshal/gen1/ClassSpec.java index 2755cc1..18dd18d 100644 --- a/src/main/java/overrun/marshal/gen1/ClassSpec.java +++ b/src/main/java/overrun/marshal/gen1/ClassSpec.java @@ -124,11 +124,6 @@ public void addMethod(MethodSpec methodSpec, Consumer consumer) { methodSpecs.add(methodSpec); } - /** - * Add annotation - * - * @param annotationSpec annotation - */ @Override public void addAnnotation(AnnotationSpec annotationSpec) { annotationSpecs.add(annotationSpec); diff --git a/src/main/java/overrun/marshal/gen1/MethodSpec.java b/src/main/java/overrun/marshal/gen1/MethodSpec.java index d4c409c..61a4d54 100644 --- a/src/main/java/overrun/marshal/gen1/MethodSpec.java +++ b/src/main/java/overrun/marshal/gen1/MethodSpec.java @@ -69,11 +69,6 @@ public void setDocument(String document) { this.document = document; } - /** - * Add an annotation - * - * @param annotationSpec annotation - */ @Override public void addAnnotation(AnnotationSpec annotationSpec) { annotations.add(annotationSpec); diff --git a/src/main/java/overrun/marshal/gen1/ParameterSpec.java b/src/main/java/overrun/marshal/gen1/ParameterSpec.java index 9dcd2cf..439096e 100644 --- a/src/main/java/overrun/marshal/gen1/ParameterSpec.java +++ b/src/main/java/overrun/marshal/gen1/ParameterSpec.java @@ -52,11 +52,6 @@ public ParameterSpec(String type, String name) { this(Spec.literal(type), name); } - /** - * Add an annotation - * - * @param annotationSpec annotation - */ @Override public void addAnnotation(AnnotationSpec annotationSpec) { annotations.add(annotationSpec); diff --git a/src/main/java/overrun/marshal/gen1/VariableStatement.java b/src/main/java/overrun/marshal/gen1/VariableStatement.java index ee4901f..132474c 100644 --- a/src/main/java/overrun/marshal/gen1/VariableStatement.java +++ b/src/main/java/overrun/marshal/gen1/VariableStatement.java @@ -18,13 +18,16 @@ import overrun.marshal.gen.AccessModifier; +import java.util.ArrayList; +import java.util.List; + /** * Variable statement * * @author squid233 * @since 0.1.0 */ -public final class VariableStatement implements Spec { +public final class VariableStatement implements Annotatable, Spec { private final String type; private final String name; private final Spec value; @@ -33,6 +36,7 @@ public final class VariableStatement implements Spec { private boolean isStatic = false; private boolean isFinal = false; private boolean addSemicolon = true; + private final List annotations = new ArrayList<>(); /** * Constructor @@ -113,6 +117,11 @@ public VariableStatement setAddSemicolon(boolean addSemicolon) { return this; } + @Override + public void addAnnotation(AnnotationSpec annotationSpec) { + annotations.add(annotationSpec); + } + @Override public void append(StringBuilder builder, int indent) { final String indentString = Spec.indentString(indent); @@ -120,6 +129,10 @@ public void append(StringBuilder builder, int indent) { if (addSemicolon) { builder.append(indentString); } + annotations.forEach(annotationSpec -> { + annotationSpec.append(builder, indent); + builder.append(' '); + }); builder.append(accessModifier); if (accessModifier != AccessModifier.PACKAGE_PRIVATE) { builder.append(' '); diff --git a/src/main/java/overrun/marshal/gen2/AnnotationData.java b/src/main/java/overrun/marshal/gen2/AnnotationData.java index a0cb8df..cc03624 100644 --- a/src/main/java/overrun/marshal/gen2/AnnotationData.java +++ b/src/main/java/overrun/marshal/gen2/AnnotationData.java @@ -16,19 +16,18 @@ package overrun.marshal.gen2; -import javax.lang.model.element.AnnotationMirror; -import java.lang.annotation.Annotation; +import java.util.Map; /** * Holds annotation * - * @param annotation the annotation - * @param mirror the annotation mirror + * @param type the type + * @param map the map * @author squid233 * @since 0.1.0 */ public record AnnotationData( - Annotation annotation, - AnnotationMirror mirror + String type, + Map map ) { } diff --git a/src/main/java/overrun/marshal/gen2/BaseData.java b/src/main/java/overrun/marshal/gen2/BaseData.java index 7b0155b..3b2aa67 100644 --- a/src/main/java/overrun/marshal/gen2/BaseData.java +++ b/src/main/java/overrun/marshal/gen2/BaseData.java @@ -21,17 +21,20 @@ import overrun.marshal.gen2.struct.StructData; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.AnnotationMirror; -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; +import javax.lang.model.element.*; import javax.tools.JavaFileObject; import java.io.IOException; import java.io.PrintWriter; +import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import java.util.stream.Stream; +import static overrun.marshal.internal.Util.isAExtendsB; + /** * Base data * @@ -48,9 +51,9 @@ public abstract sealed class BaseData permits DowncallData, StructData { */ protected final ImportData imports = new ImportData(new ArrayList<>()); /** - * fields + * fieldDataList */ - protected final List fields = new ArrayList<>(); + protected final List fieldDataList = new ArrayList<>(); /** * functionDataList */ @@ -63,6 +66,14 @@ public abstract sealed class BaseData permits DowncallData, StructData { * nonFinal */ protected boolean nonFinal = false; + /** + * superclasses + */ + protected final List superclasses = new ArrayList<>(); + /** + * superinterfaces + */ + protected final List superinterfaces = new ArrayList<>(); /** * Construct @@ -110,16 +121,8 @@ protected DeclaredTypeData generateClassName(String name, TypeElement typeElemen protected void generate(DeclaredTypeData declaredTypeData) throws IOException { final String packageName = declaredTypeData.packageName(); final String simpleClassName = declaredTypeData.name(); - final SourceFile file = new SourceFile(packageName); - file.addClass(simpleClassName, classSpec -> { - if (document != null) { - classSpec.setDocument(document); - } - classSpec.setFinal(!nonFinal); + final SourceFile file = createSourceFile(packageName, simpleClassName); - addFields(classSpec); - addMethods(classSpec); - }); imports.imports() .stream() .filter(typeData -> !"java.lang".equals(typeData.packageName())) @@ -144,6 +147,22 @@ protected void generate(DeclaredTypeData declaredTypeData) throws IOException { } } + private SourceFile createSourceFile(String packageName, String simpleClassName) { + final SourceFile file = new SourceFile(packageName); + file.addClass(simpleClassName, classSpec -> { + if (document != null) { + classSpec.setDocument(document); + } + classSpec.setFinal(!nonFinal); + superclasses.forEach(typeData -> classSpec.addSuperclass(imports.simplifyOrImport(typeData))); + superinterfaces.forEach(typeData -> classSpec.addSuperinterface(imports.simplifyOrImport(typeData))); + + addFields(classSpec); + addMethods(classSpec); + }); + return file; + } + /** * Generates the file * @@ -158,12 +177,13 @@ protected void generate(DeclaredTypeData declaredTypeData) throws IOException { * @param spec spec */ protected void addFields(ClassSpec spec) { - fields.forEach(fieldData -> { + fieldDataList.forEach(fieldData -> { final TypeData type = fieldData.type(); + final TypeUse value = fieldData.value(); spec.addField(new VariableStatement( imports.simplifyOrImport(type), fieldData.name(), - fieldData.value().apply(imports) + value != null ? value.apply(imports) : null ).setDocument(fieldData.document()) .setAccessModifier(fieldData.accessModifier()) .setStatic(fieldData.staticField()) @@ -182,7 +202,7 @@ protected void addMethods(ClassSpec spec) { methodSpec.setDocument(functionData.document()); addAnnotationSpec(functionData.annotations(), methodSpec); methodSpec.setAccessModifier(functionData.accessModifier()); - methodSpec.setStatic(true); + methodSpec.setStatic(functionData.staticMethod()); functionData.parameters().forEach(parameterData -> methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()).also(parameterSpec -> addAnnotationSpec(parameterData.annotations(), parameterSpec))) @@ -192,21 +212,29 @@ protected void addMethods(ClassSpec spec) { })); } + /** + * addAnnotationSpec + * + * @param list list + * @param annotatable annotatable + * @param escapeAtCharacter escapeAtCharacter + */ + protected static void addAnnotationSpec(List list, Annotatable annotatable, boolean escapeAtCharacter) { + list.forEach(annotationData -> annotatable.addAnnotation(new AnnotationSpec( + annotationData.type(), + escapeAtCharacter + ).also(annotationSpec -> + annotationData.map().forEach(annotationSpec::addArgument)))); + } + /** * addAnnotationSpec * * @param list list * @param annotatable annotatable */ - protected void addAnnotationSpec(List list, Annotatable annotatable) { - list.forEach(annotationData -> { - final AnnotationMirror annotationMirror = annotationData.mirror(); - annotatable.addAnnotation(new AnnotationSpec( - imports.simplifyOrImport(processingEnv, annotationMirror.getAnnotationType()) - ).also(annotationSpec -> - annotationMirror.getElementValues().forEach((executableElement, annotationValue) -> - annotationSpec.addArgument(executableElement.getSimpleName().toString(), annotationValue.toString())))); - }); + protected static void addAnnotationSpec(List list, Annotatable annotatable) { + addAnnotationSpec(list, annotatable, false); } /** @@ -230,11 +258,12 @@ protected String getConstExp(Object v) { /** * {@return skipAnnotated} * - * @param stream stream - * @param type + * @param collection collection + * @param aClass aClass + * @param type */ - protected static Stream skipAnnotated(Stream stream) { - return stream.filter(t -> t.getAnnotation(Skip.class) == null); + protected static Stream skipAnnotated(Collection collection, Class aClass) { + return collection.stream().filter(t -> t.getAnnotation(aClass) == null); } /** @@ -244,6 +273,40 @@ protected static Stream skipAnnotated(Stream stream) { * @param type */ protected static Stream skipAnnotated(Collection collection) { - return skipAnnotated(collection.stream()); + return skipAnnotated(collection, Skip.class); + } + + private Stream findElementAnnotations( + List annotationMirrors, + Class aClass + ) { + return annotationMirrors.stream() + .filter(mirror -> isAExtendsB(processingEnv, + mirror.getAnnotationType(), + aClass)); + } + + /** + * {@return getElementAnnotations} + * + * @param list list + * @param e e + */ + protected List getElementAnnotations(List> list, Element e) { + final var mirrors = e.getAnnotationMirrors(); + return list.stream() + .filter(aClass -> e.getAnnotation(aClass) != null) + .flatMap(aClass -> findElementAnnotations(mirrors, aClass) + .map(mirror -> new AnnotationData(imports.simplifyOrImport(processingEnv, mirror.getAnnotationType()), + mapAnnotationMirrorValues(mirror.getElementValues())))) + .toList(); + } + + private static Map mapAnnotationMirrorValues(Map map) { + return map.entrySet().stream() + .collect(Collectors.toUnmodifiableMap( + entry -> entry.getKey().getSimpleName().toString(), + entry -> entry.getValue().toString() + )); } } diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index c4f0dde..ada250b 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -37,7 +37,6 @@ import java.util.*; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Stream; import static overrun.marshal.internal.Util.*; @@ -81,13 +80,13 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName this.document = getDocComment(typeElement); this.nonFinal = downcall.nonFinal(); - // process fields + // process fieldDataList skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).forEach(element -> { final Object constantValue = element.getConstantValue(); if (constantValue == null) { return; } - fields.add(new FieldData( + fieldDataList.add(new FieldData( getDocComment(element), List.of(), AccessModifier.PUBLIC, @@ -95,7 +94,7 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName true, TypeData.detectType(processingEnv, element.asType()), element.getSimpleName().toString(), - _ -> Spec.literal(getConstExp(constantValue)) + TypeUse.literal(getConstExp(constantValue)) )); }); @@ -169,7 +168,7 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName final Predicate containsKey = methodHandleDataMap::containsKey; final String lookupName = addLookup(typeElement, containsKey); final String linkerName = tryInsertUnderline("LINKER", containsKey); - fields.add(new FieldData( + fieldDataList.add(new FieldData( null, List.of(), AccessModifier.PRIVATE, @@ -182,7 +181,7 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName // adds methods // method handles - methodHandleDataMap.forEach((name, methodHandleData) -> fields.add(new FieldData( + methodHandleDataMap.forEach((name, methodHandleData) -> fieldDataList.add(new FieldData( " The method handle of {@code " + name + "}.", List.of(), methodHandleData.accessModifier(), @@ -225,7 +224,7 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName ))); // methods - final var fieldNames = fields.stream().map(FieldData::name).toList(); + final var fieldNames = fieldDataList.stream().map(FieldData::name).toList(); final List addedMethodHandleInvoker = new ArrayList<>(methodHandleDataMap.size()); skipAnnotated(methods).forEach(executableElement -> { final Overload overload = executableElement.getAnnotation(Overload.class); @@ -329,7 +328,7 @@ private void addOverloadMethod( final String memoryStackName = tryInsertUnderline("stack", s -> variablesInScope.stream().anyMatch(s::equals)); final boolean hasAllocatorParameter = !parameterDataList.isEmpty() && - isAExtendsB(processingEnv, parameterDataList.getFirst().element().asType(), SegmentAllocator.class); + isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), SegmentAllocator.class); final String allocatorParameter = hasAllocatorParameter ? parameterDataList.getFirst().name() : memoryStackName; @@ -381,12 +380,11 @@ private void addOverloadMethod( final TypeUse invokeTypeUse = importData -> { final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ? Spec.literal(simpleClassName) : null, downcallName); // wrap parameters - var stream = parameterDataList.stream(); + var stream = rawParameterList.stream(); if (skipFirst > 0) { stream = stream.skip(1); } - stream.map(ParameterData::element) - .map(element -> wrapParameter(importData, allocatorParameter, element, true, refNameMap::get)) + stream.map(element -> wrapParameter(importData, allocatorParameter, element, true, refNameMap::get)) .forEach(invokeSpec::addArgument); return invokeSpec; }; @@ -432,9 +430,10 @@ private void addOverloadMethod( getDocComment(executableElement), getElementAnnotations(METHOD_ANNOTATIONS, executableElement), methodHandleData.accessModifier(), + true, structRef != null ? - _ -> Spec.literal(structRef.value()) : - importData -> Spec.literal(importData.simplifyOrImport(processingEnv, returnType)), + TypeUse.literal(structRef.value()) : + TypeUse.of(processingEnv, returnType), methodName, parameterDataList, shouldWrapWithMemoryStack ? @@ -474,7 +473,6 @@ private void addDowncallMethod( final var parameters = stream.toList(); final var parameterDataList = parameters.stream() .map(element -> new ParameterData( - element, getElementAnnotations(PARAMETER_ANNOTATIONS, element), TypeUse.toDowncallType(processingEnv, element).orElseThrow(), element.getSimpleName().toString() @@ -490,7 +488,8 @@ private void addDowncallMethod( getDocComment(executableElement), getElementAnnotations(METHOD_ANNOTATIONS, executableElement), methodHandleData.accessModifier(), - returnType.orElseGet(() -> _ -> Spec.literal("void")), + true, + returnType.orElseGet(() -> TypeUse.literal(void.class)), methodName, parameterDataList, List.of( @@ -500,10 +499,9 @@ private void addDowncallMethod( Spec.literal(methodHandleName), "invokeExact"); var stream1 = parameterNames.stream(); if (returnByValue) { - final ParameterData first = parameterDataList.getFirst(); - if (!isSameClass(first.element().asType(), SegmentAllocator.class)) { + if (!isSameClass(parameters.getFirst().asType(), SegmentAllocator.class)) { invokeSpec.addArgument(Spec.cast(importData.simplifyOrImport(SegmentAllocator.class), - Spec.literal(first.name()))); + Spec.literal(parameterDataList.getFirst().name()))); stream1 = stream1.skip(1); } } @@ -583,7 +581,8 @@ private void addCustomMethod(ExecutableElement executableElement, getDocComment(executableElement), getElementAnnotations(METHOD_ANNOTATIONS, executableElement), methodHandleData.accessModifier(), - importData -> Spec.literal(importData.simplifyOrImport(processingEnv, executableElement.getReturnType())), + true, + TypeUse.of(processingEnv, executableElement.getReturnType()), executableElement.getSimpleName().toString(), parametersToParameterDataList(skipAnnotated(executableElement.getParameters()).toList()), List.of(_ -> Spec.indented(custom.value())) @@ -597,7 +596,7 @@ private String addLookup(TypeElement typeElement, Predicate insertUnderl .findFirst() .orElseThrow() .getElementValues().entrySet().stream() - .filter(e -> "loader".equals(e.getKey().getSimpleName().toString())) + .filter(e -> "loader".contentEquals(e.getKey().getSimpleName())) .findFirst() .map(e -> e.getValue().getValue().toString()) .orElse(null); @@ -627,7 +626,7 @@ private String addLookup(TypeElement typeElement, Predicate insertUnderl } } final String s = tryInsertUnderline("LOOKUP", insertUnderlineTest); - fields.add(new FieldData( + fieldDataList.add(new FieldData( null, List.of(), AccessModifier.PRIVATE, @@ -834,7 +833,6 @@ private boolean useAllocator(TypeMirror typeMirror) { private List parametersToParameterDataList(List parameters) { return parameters.stream() .map(element -> new ParameterData( - element, getElementAnnotations(PARAMETER_ANNOTATIONS, element), importData -> { final StructRef structRef = element.getAnnotation(StructRef.class); @@ -848,26 +846,6 @@ private List parametersToParameterDataList(List findElementAnnotations( - List annotationMirrors, - Class aClass - ) { - return annotationMirrors.stream() - .filter(mirror -> isAExtendsB(processingEnv, - mirror.getAnnotationType(), - aClass)); - } - - private List getElementAnnotations(List> list, Element e) { - final var mirrors = e.getAnnotationMirrors(); - return list.stream() - .filter(aClass -> e.getAnnotation(aClass) != null) - .mapMulti((aClass, consumer) -> findElementAnnotations(mirrors, aClass) - .map(mirror -> new AnnotationData(e.getAnnotation(aClass), mirror)) - .forEach(consumer)) - .toList(); - } - private TypeUse createCharset(String name) { return importData -> { final String upperCase = name.toUpperCase(Locale.ROOT); diff --git a/src/main/java/overrun/marshal/gen2/FunctionData.java b/src/main/java/overrun/marshal/gen2/FunctionData.java index 099c129..74e23ca 100644 --- a/src/main/java/overrun/marshal/gen2/FunctionData.java +++ b/src/main/java/overrun/marshal/gen2/FunctionData.java @@ -26,6 +26,7 @@ * @param document the document * @param annotations the annotations * @param accessModifier the access modifier + * @param staticMethod static method * @param returnType the return type * @param name the name * @param parameters the parameters @@ -37,6 +38,7 @@ public record FunctionData( String document, List annotations, AccessModifier accessModifier, + boolean staticMethod, TypeUse returnType, String name, List parameters, diff --git a/src/main/java/overrun/marshal/gen2/ParameterData.java b/src/main/java/overrun/marshal/gen2/ParameterData.java index 81d2de3..7c1944d 100644 --- a/src/main/java/overrun/marshal/gen2/ParameterData.java +++ b/src/main/java/overrun/marshal/gen2/ParameterData.java @@ -16,13 +16,11 @@ package overrun.marshal.gen2; -import javax.lang.model.element.VariableElement; import java.util.List; /** * Holds parameter * - * @param element the element * @param annotations the annotations * @param type the type * @param name the name @@ -30,7 +28,6 @@ * @since 0.1.0 */ public record ParameterData( - VariableElement element, List annotations, TypeUse type, String name diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java index 755d96e..2dfa8c7 100644 --- a/src/main/java/overrun/marshal/gen2/TypeUse.java +++ b/src/main/java/overrun/marshal/gen2/TypeUse.java @@ -37,6 +37,7 @@ * @author squid233 * @since 0.1.0 */ +@FunctionalInterface public interface TypeUse { private static Optional valueLayout(TypeKind typeKind) { return switch (typeKind) { @@ -121,19 +122,19 @@ static Optional valueLayout(Class carrier) { static Optional toDowncallType(ProcessingEnvironment env, TypeMirror typeMirror) { final TypeKind typeKind = typeMirror.getKind(); if (typeKind.isPrimitive()) { - return Optional.of(_ -> Spec.literal(typeMirror.toString())); + return Optional.of(literal(typeMirror.toString())); } return switch (typeKind) { case ARRAY, DECLARED -> { if (typeKind == TypeKind.DECLARED) { if (Util.isAExtendsB(env, typeMirror, SegmentAllocator.class)) { - yield Optional.of(importData -> Spec.literal(importData.simplifyOrImport(env, typeMirror))); + yield Optional.of(of(env, typeMirror)); } if (Util.isAExtendsB(env, typeMirror, CEnum.class)) { - yield Optional.of(_ -> Spec.literal("int")); + yield Optional.of(literal(int.class)); } } - yield Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class))); + yield Optional.of(of(MemorySegment.class)); } default -> Optional.empty(); }; @@ -162,11 +163,57 @@ static Optional toDowncallType(ProcessingEnvironment env, Element eleme static Optional toDowncallType(ProcessingEnvironment env, T element, Function function) { final StructRef structRef = element.getAnnotation(StructRef.class); if (structRef != null) { - return Optional.of(importData -> Spec.literal(importData.simplifyOrImport(MemorySegment.class))); + return Optional.of(of(MemorySegment.class)); } return toDowncallType(env, function.apply(element)); } + /** + * {@return literal type use} + * + * @param aClass class + */ + static TypeUse of(Class aClass) { + return literal(importData -> importData.simplifyOrImport(aClass)); + } + + /** + * {@return literal type use} + * + * @param env env + * @param typeMirror typeMirror + */ + static TypeUse of(ProcessingEnvironment env, TypeMirror typeMirror) { + return literal(importData -> importData.simplifyOrImport(env, typeMirror)); + } + + /** + * {@return literal type use} + * + * @param s string + */ + static TypeUse literal(String s) { + return _ -> Spec.literal(s); + } + + /** + * {@return literal type use} + * + * @param aClass class + */ + static TypeUse literal(Class aClass) { + return literal(aClass.getCanonicalName()); + } + + /** + * {@return literal type use} + * + * @param function function + */ + static TypeUse literal(Function function) { + return importData -> Spec.literal(function.apply(importData)); + } + /** * Applies the imports * diff --git a/src/main/java/overrun/marshal/gen2/struct/MemberData.java b/src/main/java/overrun/marshal/gen2/struct/MemberData.java new file mode 100644 index 0000000..38c1d0c --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/struct/MemberData.java @@ -0,0 +1,29 @@ +/* + * 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.gen2.struct; + +/** + * Holds member + * + * @author squid233 + * @since 0.1.0 + */ +public record MemberData( + String pathElementName, + String varHandleName +) { +} diff --git a/src/main/java/overrun/marshal/gen2/struct/StructData.java b/src/main/java/overrun/marshal/gen2/struct/StructData.java index bd498df..a8b9a06 100644 --- a/src/main/java/overrun/marshal/gen2/struct/StructData.java +++ b/src/main/java/overrun/marshal/gen2/struct/StructData.java @@ -16,14 +16,38 @@ package overrun.marshal.gen2.struct; +import overrun.marshal.gen.AccessModifier; +import overrun.marshal.gen.Sized; +import overrun.marshal.gen.SizedSeg; +import overrun.marshal.gen.StrCharset; +import overrun.marshal.gen.struct.Const; +import overrun.marshal.gen.struct.Padding; import overrun.marshal.gen.struct.Struct; -import overrun.marshal.gen2.BaseData; -import overrun.marshal.gen2.DeclaredTypeData; +import overrun.marshal.gen.struct.StructRef; +import overrun.marshal.gen1.ConstructSpec; +import overrun.marshal.gen1.InvokeSpec; +import overrun.marshal.gen1.Spec; +import overrun.marshal.gen1.VariableStatement; +import overrun.marshal.gen2.*; +import overrun.marshal.struct.IStruct; import javax.annotation.processing.ProcessingEnvironment; 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 java.io.IOException; +import java.lang.annotation.Annotation; +import java.lang.foreign.*; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +import static overrun.marshal.internal.Util.getArrayComponentType; +import static overrun.marshal.internal.Util.tryInsertUnderline; /** * Holds struct fields and methods @@ -32,6 +56,21 @@ * @since 0.1.0 */ public final class StructData extends BaseData { + private static final String LAYOUT_NAME = "LAYOUT"; + private static final String PARAMETER_ALLOCATOR_NAME = "allocator"; + private static final String PARAMETER_COUNT_NAME = "count"; + private static final String PARAMETER_INDEX_NAME = "index"; + private static final String PARAMETER_SEGMENT_NAME = "segment"; + private static final List> MEMBER_ANNOTATION = List.of( + SizedSeg.class, + Sized.class, + StrCharset.class + ); + private final Predicate insertPredicate = s -> fieldDataList.stream().anyMatch(fieldData -> s.equals(fieldData.name())); + private String peSequenceElementName; + private String segmentName; + private String layoutName; + /** * Construct * @@ -44,12 +83,344 @@ public StructData(ProcessingEnvironment processingEnv) { private void processStructType(TypeElement typeElement, String simpleClassName) { final var enclosedElements = typeElement.getEnclosedElements(); + final var fields = skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).toList(); + final var ignorePadding = skipAnnotated(fields, Padding.class).toList(); + final Map memberPathElementNameMap = LinkedHashMap.newLinkedHashMap(ignorePadding.size()); + final Map memberVarHandleNameMap = LinkedHashMap.newLinkedHashMap(ignorePadding.size()); + final Map memberDataMap = LinkedHashMap.newLinkedHashMap(ignorePadding.size()); + final Struct struct = typeElement.getAnnotation(Struct.class); + final Const structConst = typeElement.getAnnotation(Const.class); - this.document = getDocComment(typeElement); - this.nonFinal = struct.nonFinal(); + document = getDocComment(typeElement); + nonFinal = struct.nonFinal(); + superinterfaces.add(TypeData.fromClass(IStruct.class)); - final var fields = skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).toList(); + addLayout(simpleClassName, fields, ignorePadding, structConst); + addPathElements(ignorePadding, memberPathElementNameMap); + addVarHandles(ignorePadding, memberVarHandleNameMap); + addStructInfo(); + + memberPathElementNameMap.forEach((name, pathElementName) -> memberDataMap.put(name, + new MemberData(pathElementName, memberVarHandleNameMap.get(name)))); + + addConstructors(simpleClassName, memberDataMap); + addAllocators(simpleClassName); + addSlices(simpleClassName); + addStructImpl(); + } + + private void addLayout(String simpleClassName, List fields, List ignorePadding, Const structConst) { + // layout document + final StringBuilder sb = new StringBuilder(1024); + sb.append(""" + The layout of this struct. +

+                 struct \
+                """)
+            .append(simpleClassName)
+            .append(" {\n");
+        ignorePadding.forEach(element -> {
+            final String typeNameString;
+            final StructRef structRef = element.getAnnotation(StructRef.class);
+            if (structRef != null) {
+                typeNameString = "{@link " + structRef.value() + "}";
+            } else {
+                final StringBuilder typeName = new StringBuilder(8);
+                appendMemberType(typeName, TypeData.detectType(processingEnv, element.asType()));
+                typeNameString = typeName.toString();
+            }
+            final VariableStatement variableStatement = new VariableStatement(
+                typeNameString,
+                element.getSimpleName().toString(),
+                null
+            ).setAccessModifier(AccessModifier.PACKAGE_PRIVATE)
+                .setFinal(structConst != null || element.getAnnotation(Const.class) != null);
+            addAnnotationSpec(getElementAnnotations(MEMBER_ANNOTATION, element), variableStatement, true);
+            variableStatement.append(sb, 5);
+        });
+        sb.append(" }
\n"); + // layout field + fieldDataList.add(new FieldData( + sb.toString(), + List.of(), + AccessModifier.PUBLIC, + true, + true, + TypeData.fromClass(StructLayout.class), + LAYOUT_NAME, + importData -> new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "structLayout") + .also(invokeSpec -> fields.forEach(element -> { + final Padding padding = element.getAnnotation(Padding.class); + if (padding != null) { + invokeSpec.addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "paddingLayout") + .addArgument(getConstExp(padding.value()))); + } else { + final SizedSeg sizedSeg = element.getAnnotation(SizedSeg.class); + final Sized sized = element.getAnnotation(Sized.class); + final TypeMirror type = element.asType(); + + final InvokeSpec valueLayout = new InvokeSpec(TypeUse.valueLayout(processingEnv, type) + .orElseThrow() + .apply(importData), "withName" + ).addArgument(getConstExp(element.getSimpleName().toString())); + final Spec finalSpec; + if (sizedSeg != null) { + finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") + .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") + .addArgument(getConstExp(sizedSeg.value())) + .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData))); + } else if (sized != null) { + finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") + .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") + .addArgument(getConstExp(sized.value())) + .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(type)).orElseThrow().apply(importData))); + } else { + finalSpec = valueLayout; + } + invokeSpec.addArgument(finalSpec); + } + })) + )); + } + + private void addPathElements(List ignorePadding, Map memberPathElementNameMap) { + peSequenceElementName = tryInsertUnderline("PE_SEQUENCE_ELEMENT", insertPredicate); + fieldDataList.add(new FieldData( + null, + List.of(), + AccessModifier.PRIVATE, + true, + true, + TypeData.fromClass(MemoryLayout.PathElement.class), + peSequenceElementName, + importData -> new InvokeSpec(importData.simplifyOrImport(MemoryLayout.PathElement.class), "sequenceElement") + )); + ignorePadding.stream() + .map(element -> element.getSimpleName().toString()) + .forEach(s -> { + final String name = tryInsertUnderline("PE_" + s, insertPredicate); + fieldDataList.add(new FieldData( + null, + List.of(), + AccessModifier.PRIVATE, + true, + true, + TypeData.fromClass(MemoryLayout.PathElement.class), + name, + importData -> new InvokeSpec(importData.simplifyOrImport(MemoryLayout.PathElement.class), "groupElement") + .addArgument(getConstExp(s)) + )); + memberPathElementNameMap.put(s, name); + }); + } + + private void addVarHandles(List ignorePadding, Map memberVarHandleNameMap) { + ignorePadding.stream() + .map(element -> element.getSimpleName().toString()) + .forEach(s -> { + final String name = tryInsertUnderline(s, insertPredicate); + fieldDataList.add(new FieldData( + null, + List.of(), + AccessModifier.PRIVATE, + false, + true, + TypeData.fromClass(VarHandle.class), + name, + null + )); + memberVarHandleNameMap.put(s, name); + }); + } + + private void addStructInfo() { + segmentName = tryInsertUnderline("segment", insertPredicate); + layoutName = tryInsertUnderline("layout", insertPredicate); + fieldDataList.add(new FieldData( + null, + List.of(), + AccessModifier.PRIVATE, + false, + true, + TypeData.fromClass(MemorySegment.class), + segmentName, + null + )); + fieldDataList.add(new FieldData( + null, + List.of(), + AccessModifier.PRIVATE, + false, + true, + TypeData.fromClass(SequenceLayout.class), + layoutName, + null + )); + } + + private void addConstructors(String simpleClassName, Map memberDataMap) { + final List statements = new ArrayList<>(memberDataMap.size() + 2); + + statements.add(_ -> Spec.assignStatement(Spec.accessSpec("this", segmentName), Spec.literal(PARAMETER_SEGMENT_NAME))); + statements.add(importData -> Spec.assignStatement(Spec.accessSpec("this", layoutName), + new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") + .addArgument(PARAMETER_COUNT_NAME) + .addArgument(LAYOUT_NAME))); + memberDataMap.forEach((_, memberData) -> statements.add(_ -> + Spec.assignStatement(Spec.accessSpec("this", memberData.varHandleName()), + new InvokeSpec(Spec.accessSpec("this", layoutName), "varHandle") + .addArgument(peSequenceElementName) + .addArgument(memberData.pathElementName())))); + + functionDataList.add(new FunctionData( + """ + Creates {@code %s} with the given segment and element count. + + @param %s the segment + @param %s the element count\ + """.formatted(simpleClassName, PARAMETER_SEGMENT_NAME, PARAMETER_COUNT_NAME), + List.of(), + AccessModifier.PUBLIC, + false, + _ -> null, + simpleClassName, + List.of( + new ParameterData(List.of(), TypeUse.of(MemorySegment.class), PARAMETER_SEGMENT_NAME), + new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_COUNT_NAME) + ), + statements + )); + + functionDataList.add(new FunctionData( + """ + Creates {@code %s} with the given segment. The count is auto-inferred. + + @param %s the segment\ + """.formatted(simpleClassName, PARAMETER_SEGMENT_NAME), + List.of(), + AccessModifier.PUBLIC, + false, + _ -> null, + simpleClassName, + List.of(new ParameterData(List.of(), TypeUse.of(MemorySegment.class), PARAMETER_SEGMENT_NAME)), + List.of(importData -> Spec.statement(new InvokeSpec((Spec) null, "this") + .addArgument(PARAMETER_SEGMENT_NAME) + .addArgument(new InvokeSpec(importData.simplifyOrImport(IStruct.class), "inferCount") + .addArgument(PARAMETER_SEGMENT_NAME) + .addArgument(LAYOUT_NAME)))) + )); + } + + private void addAllocators(String simpleClassName) { + functionDataList.add(new FunctionData( + """ + Allocates {@code %s}. + + @param %s the allocator + @return the allocated {@code %1$s}\ + """.formatted(simpleClassName, PARAMETER_ALLOCATOR_NAME), + List.of(), + AccessModifier.PUBLIC, + true, + TypeUse.literal(simpleClassName), + "create", + List.of(new ParameterData(List.of(), TypeUse.of(SegmentAllocator.class), PARAMETER_ALLOCATOR_NAME)), + List.of(_ -> Spec.returnStatement(new ConstructSpec(simpleClassName) + .addArgument(new InvokeSpec(PARAMETER_ALLOCATOR_NAME, "allocate") + .addArgument(LAYOUT_NAME)) + .addArgument(Spec.literal(getConstExp(1L))))) + )); + functionDataList.add(new FunctionData( + """ + Allocates {@code %s} with the given count.. + + @param %s the allocator + @param %s the element count + @return the allocated {@code %1$s}\ + """.formatted(simpleClassName, PARAMETER_ALLOCATOR_NAME, PARAMETER_COUNT_NAME), + List.of(), + AccessModifier.PUBLIC, + true, + TypeUse.literal(simpleClassName), + "create", + List.of( + new ParameterData(List.of(), TypeUse.of(SegmentAllocator.class), PARAMETER_ALLOCATOR_NAME), + new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_COUNT_NAME) + ), + List.of(_ -> Spec.returnStatement(new ConstructSpec(simpleClassName) + .addArgument(new InvokeSpec(PARAMETER_ALLOCATOR_NAME, "allocate") + .addArgument(LAYOUT_NAME) + .addArgument(PARAMETER_COUNT_NAME)) + .addArgument(Spec.literal(PARAMETER_COUNT_NAME)))) + )); + } + + private void addSlices(String simpleClassName) { + functionDataList.add(new FunctionData( + """ + Returns a slice of this struct, at the given index. + + @param %s The new struct base offset + @param %s The new struct size + @return a slice of this struct\ + """ + .formatted(PARAMETER_INDEX_NAME, PARAMETER_COUNT_NAME), + List.of(), + AccessModifier.PUBLIC, + false, + TypeUse.literal(simpleClassName), + "get", + List.of( + new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_INDEX_NAME), + new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_COUNT_NAME) + ), + List.of(_ -> Spec.returnStatement(new ConstructSpec(simpleClassName) + .addArgument(new InvokeSpec(segmentName, "asSlice") + .addArgument(Spec.operatorSpec("*", Spec.literal(PARAMETER_INDEX_NAME), new InvokeSpec(LAYOUT_NAME, "byteSize"))) + .addArgument(Spec.operatorSpec("*", Spec.literal(PARAMETER_COUNT_NAME), new InvokeSpec(LAYOUT_NAME, "byteSize"))) + .addArgument(new InvokeSpec(LAYOUT_NAME, "byteAlignment"))) + .addArgument(Spec.literal(PARAMETER_COUNT_NAME)))) + )); + functionDataList.add(new FunctionData( + """ + Returns a slice of this struct. + + @param %s The new struct base offset + @return a slice of this struct\ + """ + .formatted(PARAMETER_INDEX_NAME), + List.of(), + AccessModifier.PUBLIC, + false, + TypeUse.literal(simpleClassName), + "get", + List.of( + new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_INDEX_NAME) + ), + List.of(_ -> Spec.returnStatement(new InvokeSpec((Spec) null, "get") + .addArgument(PARAMETER_INDEX_NAME) + .addArgument(getConstExp(1L)))) + )); + } + + private void addStructImpl() { + addStructImpl(TypeUse.of(MemorySegment.class), "segment", Spec.literal(segmentName)); + addStructImpl(TypeUse.of(StructLayout.class), "layout", Spec.literal(LAYOUT_NAME)); + addStructImpl(TypeUse.literal(long.class), "elementCount", new InvokeSpec(layoutName, "elementCount")); + } + + private void addStructImpl(TypeUse returnType, String name, Spec returnValue) { + functionDataList.add(new FunctionData( + null, + List.of(new AnnotationData(imports.simplifyOrImport(Override.class), Map.of())), + AccessModifier.PUBLIC, + false, + returnType, + name, + List.of(), + List.of(_ -> Spec.returnStatement(returnValue)) + )); } /** @@ -67,4 +438,15 @@ public void generate(TypeElement typeElement) throws IOException { generate(generatedClassName); } + + private void appendMemberType(StringBuilder sb, TypeData typeData) { + switch (typeData) { + case ArrayTypeData arrayTypeData -> { + appendMemberType(sb, arrayTypeData.componentType()); + sb.append("[]"); + } + case DeclaredTypeData _ -> sb.append("{@link ").append(imports.simplifyOrImport(typeData)).append("}"); + default -> sb.append(imports.simplifyOrImport(typeData)); + } + } } diff --git a/src/main/java/overrun/marshal/struct/IStruct.java b/src/main/java/overrun/marshal/struct/IStruct.java index 7371bb3..3370638 100644 --- a/src/main/java/overrun/marshal/struct/IStruct.java +++ b/src/main/java/overrun/marshal/struct/IStruct.java @@ -44,11 +44,11 @@ public interface IStruct { /** * Infers how many this struct is there in the given segment. * - * @param layout the struct layout * @param segment the segment + * @param layout the struct layout * @return the count */ - static long inferCount(StructLayout layout, MemorySegment segment) { + static long inferCount(MemorySegment segment, StructLayout layout) { return segment.byteSize() / layout.byteSize(); } } From 5051b8c640f7ec4f9a6992553c43d175b52862cb Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 15 Jan 2024 13:36:20 +0800 Subject: [PATCH 12/28] Clean code --- .../overrun/marshal/gen1/AnnotationSpec.java | 12 -- .../java/overrun/marshal/gen1/InvokeSpec.java | 6 + .../marshal/gen1/TryCatchStatement.java | 11 -- .../gen1/TryWithResourceStatement.java | 12 -- .../java/overrun/marshal/gen2/BaseData.java | 22 +-- .../overrun/marshal/gen2/DowncallData.java | 89 ++++++------ .../marshal/gen2/struct/StructData.java | 129 ++++++++++-------- 7 files changed, 139 insertions(+), 142 deletions(-) diff --git a/src/main/java/overrun/marshal/gen1/AnnotationSpec.java b/src/main/java/overrun/marshal/gen1/AnnotationSpec.java index 7b46c3c..2a8781f 100644 --- a/src/main/java/overrun/marshal/gen1/AnnotationSpec.java +++ b/src/main/java/overrun/marshal/gen1/AnnotationSpec.java @@ -18,7 +18,6 @@ import java.util.LinkedHashMap; import java.util.Map; -import java.util.function.Consumer; import java.util.stream.Collectors; /** @@ -74,17 +73,6 @@ public AnnotationSpec addArgument(String name, String value) { return this; } - /** - * Also runs the action - * - * @param consumer the action - * @return this - */ - public AnnotationSpec also(Consumer consumer) { - consumer.accept(this); - return this; - } - @Override public void append(StringBuilder builder, int indent) { if (escapeAtCharacter) { diff --git a/src/main/java/overrun/marshal/gen1/InvokeSpec.java b/src/main/java/overrun/marshal/gen1/InvokeSpec.java index 7b12532..09d4be5 100644 --- a/src/main/java/overrun/marshal/gen1/InvokeSpec.java +++ b/src/main/java/overrun/marshal/gen1/InvokeSpec.java @@ -77,6 +77,9 @@ public static InvokeSpec invokeThis() { * @return this */ public InvokeSpec addArgument(Spec spec) { + if (spec == null) { + return this; + } arguments.add(spec); return this; } @@ -88,6 +91,9 @@ public InvokeSpec addArgument(Spec spec) { * @return this */ public InvokeSpec addArgument(String spec) { + if (spec == null) { + return this; + } return addArgument(Spec.literal(spec)); } diff --git a/src/main/java/overrun/marshal/gen1/TryCatchStatement.java b/src/main/java/overrun/marshal/gen1/TryCatchStatement.java index 5cf88db..0cf4761 100644 --- a/src/main/java/overrun/marshal/gen1/TryCatchStatement.java +++ b/src/main/java/overrun/marshal/gen1/TryCatchStatement.java @@ -57,17 +57,6 @@ public void addCatchClause(CatchClause catchClause, Consumer consum consumer.accept(catchClause); } - /** - * Also runs the action - * - * @param consumer the action - * @return this - */ - public TryCatchStatement also(Consumer consumer) { - consumer.accept(this); - return this; - } - @Override public void append(StringBuilder builder, int indent) { final String indentString = Spec.indentString(indent); diff --git a/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java b/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java index f1551b4..947259d 100644 --- a/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java +++ b/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java @@ -18,7 +18,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.function.Consumer; /** * try-with-resource @@ -44,17 +43,6 @@ public void addStatement(Spec spec) { statements.add(spec); } - /** - * Also runs the action - * - * @param consumer the action - * @return this - */ - public TryWithResourceStatement also(Consumer consumer) { - consumer.accept(this); - return this; - } - @Override public void append(StringBuilder builder, int indent) { final String indentString = Spec.indentString(indent); diff --git a/src/main/java/overrun/marshal/gen2/BaseData.java b/src/main/java/overrun/marshal/gen2/BaseData.java index 3b2aa67..5c361a5 100644 --- a/src/main/java/overrun/marshal/gen2/BaseData.java +++ b/src/main/java/overrun/marshal/gen2/BaseData.java @@ -203,10 +203,11 @@ protected void addMethods(ClassSpec spec) { addAnnotationSpec(functionData.annotations(), methodSpec); methodSpec.setAccessModifier(functionData.accessModifier()); methodSpec.setStatic(functionData.staticMethod()); - functionData.parameters().forEach(parameterData -> - methodSpec.addParameter(new ParameterSpec(parameterData.type().apply(imports), parameterData.name()).also(parameterSpec -> - addAnnotationSpec(parameterData.annotations(), parameterSpec))) - ); + functionData.parameters().forEach(parameterData -> { + final ParameterSpec parameterSpec = new ParameterSpec(parameterData.type().apply(imports), parameterData.name()); + addAnnotationSpec(parameterData.annotations(), parameterSpec); + methodSpec.addParameter(parameterSpec); + }); functionData.statements().forEach(typeUse -> methodSpec.addStatement(typeUse.apply(imports))); })); @@ -220,11 +221,14 @@ protected void addMethods(ClassSpec spec) { * @param escapeAtCharacter escapeAtCharacter */ protected static void addAnnotationSpec(List list, Annotatable annotatable, boolean escapeAtCharacter) { - list.forEach(annotationData -> annotatable.addAnnotation(new AnnotationSpec( - annotationData.type(), - escapeAtCharacter - ).also(annotationSpec -> - annotationData.map().forEach(annotationSpec::addArgument)))); + list.forEach(annotationData -> { + final AnnotationSpec annotationSpec = new AnnotationSpec( + annotationData.type(), + escapeAtCharacter + ); + annotationData.map().forEach(annotationSpec::addArgument); + annotatable.addAnnotation(annotationSpec); + }); } /** diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index ada250b..4260f3b 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -140,18 +140,20 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName } return invokeSpec.addArgument(new InvokeSpec( importData.simplifyOrImport(MemoryLayout.class), - "sequenceLayout" - ).also(invokeSpec1 -> { - invokeSpec1.addArgument(getConstExp(sized.value())); - final Optional seqLayout; - if (returnType.getKind() == TypeKind.ARRAY && - returnType instanceof ArrayType arrayType) { - seqLayout = TypeUse.valueLayout(processingEnv, arrayType.getComponentType()); - } else { - seqLayout = TypeUse.valueLayout(byte.class); - } - invokeSpec1.addArgument(seqLayout.orElseThrow().apply(importData)); - })); + "sequenceLayout") + .addArgument(getConstExp(sized.value())) + .addArgument(switch (0) { + default -> { + final Optional seqLayout; + if (returnType.getKind() == TypeKind.ARRAY && + returnType instanceof ArrayType arrayType) { + seqLayout = TypeUse.valueLayout(processingEnv, arrayType.getComponentType()); + } else { + seqLayout = TypeUse.valueLayout(byte.class); + } + yield seqLayout.orElseThrow().apply(importData); + } + })); } return spec; }), @@ -196,22 +198,27 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName ).addArgument(new LambdaSpec("s") .addStatementThis(new InvokeSpec(linkerName, "downcallHandle") .addArgument("s") - .also(downcallHandle -> { - final var returnType = methodHandleData.returnType(); - final String methodName = returnType.isPresent() ? "of" : "ofVoid"; - downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName) - .also(fdOf -> { - returnType.ifPresent(typeUse -> { - final Spec spec = typeUse.apply(importData); - fdOf.addArgument(spec); - }); - methodHandleData.parameterTypes().forEach(typeUse -> - fdOf.addArgument(typeUse.apply(importData))); - })); - final Critical critical = methodHandleData.executableElement().getAnnotation(Critical.class); - if (critical != null) { - downcallHandle.addArgument(new InvokeSpec(importData.simplifyOrImport(Linker.Option.class), "critical") - .addArgument(getConstExp(critical.allowHeapAccess()))); + .addArgument(switch (0) { + default -> { + final var returnType = methodHandleData.returnType(); + final String methodName = returnType.isPresent() ? "of" : "ofVoid"; + final InvokeSpec fdOf = new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName); + returnType.ifPresent(typeUse -> { + final Spec spec = typeUse.apply(importData); + fdOf.addArgument(spec); + }); + methodHandleData.parameterTypes().forEach(typeUse -> + fdOf.addArgument(typeUse.apply(importData))); + yield fdOf; + } + }) + .addArgument(switch (0) { + default -> { + final Critical critical = methodHandleData.executableElement().getAnnotation(Critical.class); + yield critical != null ? + new InvokeSpec(importData.simplifyOrImport(Linker.Option.class), "critical") + .addArgument(getConstExp(critical.allowHeapAccess())) : + null; } }))); if (methodHandleData.optional()) { @@ -437,16 +444,18 @@ private void addOverloadMethod( methodName, parameterDataList, shouldWrapWithMemoryStack ? - List.of(importData -> new TryWithResourceStatement( - new VariableStatement("var", - memoryStackName, - new InvokeSpec(importData.simplifyOrImport(MemoryStack.class), "stackPush")) - .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) - .setFinal(true) - .setAddSemicolon(false) - ).also(tryWithResourceStatement -> + List.of(importData -> { + final TryWithResourceStatement tryWithResourceStatement = new TryWithResourceStatement( + new VariableStatement("var", + memoryStackName, + new InvokeSpec(importData.simplifyOrImport(MemoryStack.class), "stackPush")) + .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) + .setAddSemicolon(false) + ); statements.forEach(typeUse -> - tryWithResourceStatement.addStatement(typeUse.apply(importData))))) : + tryWithResourceStatement.addStatement(typeUse.apply(importData))); + return tryWithResourceStatement; + }) : statements )); } @@ -493,7 +502,8 @@ private void addDowncallMethod( methodName, parameterDataList, List.of( - importData -> new TryCatchStatement().also(tryCatchStatement -> { + importData -> { + final TryCatchStatement tryCatchStatement = new TryCatchStatement(); final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ? Spec.accessSpec(simpleClassName, methodHandleName) : Spec.literal(methodHandleName), "invokeExact"); @@ -535,7 +545,8 @@ private void addDowncallMethod( ), catchClause -> catchClause.addStatement(Spec.throwStatement(new ConstructSpec(importData.simplifyOrImport(RuntimeException.class)) .addArgument(Spec.literal(catchClause.name()))))); - }) + return tryCatchStatement; + } ) )); } diff --git a/src/main/java/overrun/marshal/gen2/struct/StructData.java b/src/main/java/overrun/marshal/gen2/struct/StructData.java index a8b9a06..23d69d7 100644 --- a/src/main/java/overrun/marshal/gen2/struct/StructData.java +++ b/src/main/java/overrun/marshal/gen2/struct/StructData.java @@ -149,57 +149,51 @@ private void addLayout(String simpleClassName, List fields, Lis true, TypeData.fromClass(StructLayout.class), LAYOUT_NAME, - importData -> new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "structLayout") - .also(invokeSpec -> fields.forEach(element -> { - final Padding padding = element.getAnnotation(Padding.class); - if (padding != null) { - invokeSpec.addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "paddingLayout") - .addArgument(getConstExp(padding.value()))); - } else { - final SizedSeg sizedSeg = element.getAnnotation(SizedSeg.class); - final Sized sized = element.getAnnotation(Sized.class); - final TypeMirror type = element.asType(); - - final InvokeSpec valueLayout = new InvokeSpec(TypeUse.valueLayout(processingEnv, type) - .orElseThrow() - .apply(importData), "withName" - ).addArgument(getConstExp(element.getSimpleName().toString())); - final Spec finalSpec; - if (sizedSeg != null) { - finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") - .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") - .addArgument(getConstExp(sizedSeg.value())) - .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData))); - } else if (sized != null) { - finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") - .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") - .addArgument(getConstExp(sized.value())) - .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(type)).orElseThrow().apply(importData))); + importData -> switch (0) { + default -> { + final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "structLayout"); + fields.forEach(element -> { + final Padding padding = element.getAnnotation(Padding.class); + if (padding != null) { + invokeSpec.addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "paddingLayout") + .addArgument(getConstExp(padding.value()))); } else { - finalSpec = valueLayout; + final SizedSeg sizedSeg = element.getAnnotation(SizedSeg.class); + final Sized sized = element.getAnnotation(Sized.class); + final TypeMirror type = element.asType(); + + final InvokeSpec valueLayout = new InvokeSpec(TypeUse.valueLayout(processingEnv, type) + .orElseThrow() + .apply(importData), "withName" + ).addArgument(getConstExp(element.getSimpleName().toString())); + final Spec finalSpec; + if (sizedSeg != null) { + finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") + .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") + .addArgument(getConstExp(sizedSeg.value())) + .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData))); + } else if (sized != null) { + finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") + .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") + .addArgument(getConstExp(sized.value())) + .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(type)).orElseThrow().apply(importData))); + } else { + finalSpec = valueLayout; + } + invokeSpec.addArgument(finalSpec); } - invokeSpec.addArgument(finalSpec); - } - })) + }); + yield invokeSpec; + } + } )); } private void addPathElements(List ignorePadding, Map memberPathElementNameMap) { - peSequenceElementName = tryInsertUnderline("PE_SEQUENCE_ELEMENT", insertPredicate); - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - true, - true, - TypeData.fromClass(MemoryLayout.PathElement.class), - peSequenceElementName, - importData -> new InvokeSpec(importData.simplifyOrImport(MemoryLayout.PathElement.class), "sequenceElement") - )); ignorePadding.stream() .map(element -> element.getSimpleName().toString()) .forEach(s -> { - final String name = tryInsertUnderline("PE_" + s, insertPredicate); + final String name = "PE_" + s; fieldDataList.add(new FieldData( null, List.of(), @@ -213,25 +207,42 @@ private void addPathElements(List ignorePadding, Map new InvokeSpec(importData.simplifyOrImport(MemoryLayout.PathElement.class), "sequenceElement") + )); } private void addVarHandles(List ignorePadding, Map memberVarHandleNameMap) { - ignorePadding.stream() - .map(element -> element.getSimpleName().toString()) - .forEach(s -> { - final String name = tryInsertUnderline(s, insertPredicate); - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - false, - true, - TypeData.fromClass(VarHandle.class), - name, - null - )); - memberVarHandleNameMap.put(s, name); - }); + ignorePadding.forEach(element -> { + final String s = element.getSimpleName().toString(); + final StringBuilder vhDocument = new StringBuilder(" The var handle of {@code " + s + "}."); + final String docComment = getDocComment(element); + if (docComment != null) { + vhDocument.append("\n
\n") + .append(docComment) + .append("
"); + } + final String name = "vh_" + s; + fieldDataList.add(new FieldData( + vhDocument.toString(), + List.of(), + AccessModifier.PUBLIC, + false, + true, + TypeData.fromClass(VarHandle.class), + name, + null + )); + memberVarHandleNameMap.put(s, name); + }); } private void addStructInfo() { @@ -304,7 +315,7 @@ private void addConstructors(String simpleClassName, Map mem _ -> null, simpleClassName, List.of(new ParameterData(List.of(), TypeUse.of(MemorySegment.class), PARAMETER_SEGMENT_NAME)), - List.of(importData -> Spec.statement(new InvokeSpec((Spec) null, "this") + List.of(importData -> Spec.statement(InvokeSpec.invokeThis() .addArgument(PARAMETER_SEGMENT_NAME) .addArgument(new InvokeSpec(importData.simplifyOrImport(IStruct.class), "inferCount") .addArgument(PARAMETER_SEGMENT_NAME) From 426de077849d77c81a8a75c1ca5b8908caf8daa2 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Wed, 17 Jan 2024 20:29:22 +0800 Subject: [PATCH 13/28] Added Marshal and Unmarshal --- .../java/overrun/marshal/Addressable.java | 32 ++++ src/main/java/overrun/marshal/BoolHelper.java | 1 + src/main/java/overrun/marshal/Marshal.java | 141 ++++++++++++++++++ src/main/java/overrun/marshal/StrHelper.java | 1 + src/main/java/overrun/marshal/Unmarshal.java | 113 ++++++++++++++ .../java/overrun/marshal/internal/Util.java | 39 ++++- .../java/overrun/marshal/struct/IStruct.java | 5 +- 7 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 src/main/java/overrun/marshal/Addressable.java create mode 100644 src/main/java/overrun/marshal/Marshal.java create mode 100644 src/main/java/overrun/marshal/Unmarshal.java diff --git a/src/main/java/overrun/marshal/Addressable.java b/src/main/java/overrun/marshal/Addressable.java new file mode 100644 index 0000000..6ff18fd --- /dev/null +++ b/src/main/java/overrun/marshal/Addressable.java @@ -0,0 +1,32 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal; + +import java.lang.foreign.MemorySegment; + +/** + * An object that holds a memory segment. + * + * @author squid233 + * @since 0.1.0 + */ +public interface Addressable { + /** + * {@return the memory segment of this object} + */ + MemorySegment segment(); +} diff --git a/src/main/java/overrun/marshal/BoolHelper.java b/src/main/java/overrun/marshal/BoolHelper.java index 31efa75..917b060 100644 --- a/src/main/java/overrun/marshal/BoolHelper.java +++ b/src/main/java/overrun/marshal/BoolHelper.java @@ -26,6 +26,7 @@ * @author squid233 * @since 0.1.0 */ +@Deprecated(since = "0.1.0") public final class BoolHelper { private BoolHelper() { //no instance diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java new file mode 100644 index 0000000..651a17c --- /dev/null +++ b/src/main/java/overrun/marshal/Marshal.java @@ -0,0 +1,141 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal; + +import overrun.marshal.gen.CEnum; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.charset.Charset; +import java.util.function.BiFunction; + +import static java.lang.foreign.ValueLayout.*; + +/** + * Java-to-C helper. + * + * @author squid233 + * @since 0.1.0 + */ +public final class Marshal { + private static final VarHandle vh_addressArray = arrayVarHandle(ADDRESS); + static final VarHandle vh_booleanArray = arrayVarHandle(JAVA_BOOLEAN); + private static final VarHandle vh_intArray = arrayVarHandle(JAVA_INT); + + private Marshal() { + } + + private static VarHandle arrayVarHandle(ValueLayout valueLayout) { + return MethodHandles.insertCoordinates(valueLayout.arrayElementVarHandle(), 1, 0L); + } + + public static MemorySegment marshal(SegmentAllocator allocator, String string) { + return allocator.allocateFrom(string); + } + + public static MemorySegment marshal(SegmentAllocator allocator, String string, Charset charset) { + return allocator.allocateFrom(string, charset); + } + + public static int marshal(CEnum cEnum) { + return cEnum.value(); + } + + public static MemorySegment marshal(Addressable addressable) { + return addressable.segment(); + } + + public static MemorySegment marshal(Arena arena, Upcall upcall) { + return upcall.stub(arena); + } + + public static MemorySegment marshal(SegmentAllocator allocator, boolean[] arr) { + final MemorySegment segment = allocator.allocate(JAVA_BOOLEAN, arr.length); + for (int i = 0, l = arr.length; i < l; i++) { + vh_booleanArray.set(segment, (long) i, segment, arr[i]); + } + return segment; + } + + public static MemorySegment marshal(SegmentAllocator allocator, char[] arr) { + return allocator.allocateFrom(JAVA_CHAR, arr); + } + + public static MemorySegment marshal(SegmentAllocator allocator, byte[] arr) { + return allocator.allocateFrom(JAVA_BYTE, arr); + } + + public static MemorySegment marshal(SegmentAllocator allocator, short[] arr) { + return allocator.allocateFrom(JAVA_SHORT, arr); + } + + public static MemorySegment marshal(SegmentAllocator allocator, int[] arr) { + return allocator.allocateFrom(JAVA_INT, arr); + } + + public static MemorySegment marshal(SegmentAllocator allocator, long[] arr) { + return allocator.allocateFrom(JAVA_LONG, arr); + } + + public static MemorySegment marshal(SegmentAllocator allocator, float[] arr) { + return allocator.allocateFrom(JAVA_FLOAT, arr); + } + + public static MemorySegment marshal(SegmentAllocator allocator, double[] arr) { + return allocator.allocateFrom(JAVA_DOUBLE, arr); + } + + public static MemorySegment marshal(A allocator, T[] arr, BiFunction function) { + final MemorySegment segment = allocator.allocate(ADDRESS, arr.length); + for (int i = 0, l = arr.length; i < l; i++) { + vh_addressArray.set(segment, (long) i, function.apply(allocator, arr[i])); + } + return segment; + } + + public static MemorySegment marshal(SegmentAllocator allocator, MemorySegment[] arr) { + return marshal(allocator, arr, (_, segment) -> segment); + } + + public static MemorySegment marshal(SegmentAllocator allocator, String[] arr) { + return marshal(allocator, arr, SegmentAllocator::allocateFrom); + } + + public static MemorySegment marshal(SegmentAllocator allocator, String[] arr, Charset charset) { + return marshal(allocator, arr, (segmentAllocator, str) -> segmentAllocator.allocateFrom(str, charset)); + } + + public static MemorySegment marshal(SegmentAllocator allocator, CEnum[] arr) { + final MemorySegment segment = allocator.allocate(JAVA_INT, arr.length); + for (int i = 0; i < arr.length; i++) { + vh_intArray.set(segment, (long) i, arr[i].value()); + } + return segment; + } + + public static MemorySegment marshal(SegmentAllocator allocator, Addressable[] arr) { + return marshal(allocator, arr, (_, addressable) -> addressable.segment()); + } + + public static MemorySegment marshal(Arena arena, Upcall[] arr) { + return marshal(arena, arr, (arena1, upcall) -> upcall.stub(arena1)); + } +} diff --git a/src/main/java/overrun/marshal/StrHelper.java b/src/main/java/overrun/marshal/StrHelper.java index 4853642..47532d5 100644 --- a/src/main/java/overrun/marshal/StrHelper.java +++ b/src/main/java/overrun/marshal/StrHelper.java @@ -25,6 +25,7 @@ * @author squid233 * @since 0.1.0 */ +@Deprecated(since = "0.1.0") public final class StrHelper { private static final SequenceLayout STR_LAYOUT = MemoryLayout.sequenceLayout(Integer.MAX_VALUE - 8, ValueLayout.JAVA_BYTE); diff --git a/src/main/java/overrun/marshal/Unmarshal.java b/src/main/java/overrun/marshal/Unmarshal.java new file mode 100644 index 0000000..c3399de --- /dev/null +++ b/src/main/java/overrun/marshal/Unmarshal.java @@ -0,0 +1,113 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal; + +import overrun.marshal.gen.CEnum; + +import java.lang.foreign.AddressLayout; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.Charset; +import java.util.function.Function; +import java.util.function.IntFunction; + +/** + * C-to-Java helper. + * + * @author squid233 + * @since 0.1.0 + */ +public final class Unmarshal { + private Unmarshal() { + } + + public static String unmarshalAsString(MemorySegment segment) { + return segment.getString(0L); + } + + public static String unmarshalAsString(MemorySegment segment, Charset charset) { + return segment.getString(0L, charset); + } + + public static boolean[] unmarshal(ValueLayout.OfBoolean elementLayout, MemorySegment segment) { + final boolean[] arr = new boolean[checkArraySize(boolean[].class.getSimpleName(), segment.byteSize(), (int) elementLayout.byteSize())]; + for (int i = 0, l = arr.length; i < l; i++) { + arr[i] = (boolean) Marshal.vh_booleanArray.get(segment, (long) i); + } + return arr; + } + + public static char[] unmarshal(ValueLayout.OfChar elementLayout, MemorySegment segment) { + return segment.toArray(elementLayout); + } + + public static byte[] unmarshal(ValueLayout.OfByte elementLayout, MemorySegment segment) { + return segment.toArray(elementLayout); + } + + public static short[] unmarshal(ValueLayout.OfShort elementLayout, MemorySegment segment) { + return segment.toArray(elementLayout); + } + + public static int[] unmarshal(ValueLayout.OfInt elementLayout, MemorySegment segment) { + return segment.toArray(elementLayout); + } + + public static long[] unmarshal(ValueLayout.OfLong elementLayout, MemorySegment segment) { + return segment.toArray(elementLayout); + } + + public static float[] unmarshal(ValueLayout.OfFloat elementLayout, MemorySegment segment) { + return segment.toArray(elementLayout); + } + + public static double[] unmarshal(ValueLayout.OfDouble elementLayout, MemorySegment segment) { + return segment.toArray(elementLayout); + } + + public static T[] unmarshal(MemoryLayout elementLayout, MemorySegment segment, IntFunction generator, Function function) { + return segment.elements(elementLayout).map(function).toArray(generator); + } + + public static MemorySegment[] unmarshal(AddressLayout elementLayout, MemorySegment segment) { + return unmarshal(elementLayout, segment, MemorySegment[]::new, Function.identity()); + } + + public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment) { + return unmarshal(elementLayout, segment, String[]::new, s -> s.reinterpret(Long.MAX_VALUE).getString(0L)); + } + + public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment, Charset charset) { + return unmarshal(elementLayout, segment, String[]::new, s -> s.reinterpret(Long.MAX_VALUE).getString(0L, charset)); + } + + public static T[] unmarshalAsCEnum(ValueLayout.OfInt elementLayout, MemorySegment segment, IntFunction generator, IntFunction function) { + return segment.elements(elementLayout).mapToInt(s -> s.get(elementLayout, 0L)).mapToObj(function).toArray(generator); + } + + private static int checkArraySize(String typeName, long byteSize, int elemSize) { + if (!((byteSize & (elemSize - 1)) == 0)) { + throw new IllegalStateException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, byteSize)); + } + long arraySize = byteSize / elemSize; + if (arraySize > (Integer.MAX_VALUE - 8)) { + throw new IllegalStateException(String.format("Segment is too large to wrap as %s. Size: %d", typeName, byteSize)); + } + return (int) arraySize; + } +} diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java index b041601..33e80de 100644 --- a/src/main/java/overrun/marshal/internal/Util.java +++ b/src/main/java/overrun/marshal/internal/Util.java @@ -16,6 +16,8 @@ package overrun.marshal.internal; +import overrun.marshal.Upcall; + import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; @@ -300,9 +302,38 @@ public static boolean isAExtendsB(ProcessingEnvironment env, TypeMirror t1, Clas * @param aClass the class */ public static boolean isSameClass(TypeMirror typeMirror, Class aClass) { - return ((typeMirror.getKind().isPrimitive() && aClass.isPrimitive()) || - (typeMirror.getKind() == TypeKind.ARRAY && aClass.isArray()) || - isDeclared(typeMirror)) && - aClass.getCanonicalName().equals(typeMirror.toString()); + if (((typeMirror.getKind().isPrimitive() && aClass.isPrimitive()) || + isDeclared(typeMirror)) && + aClass.getCanonicalName().equals(typeMirror.toString())) { + return true; + } + if (typeMirror.getKind() == TypeKind.ARRAY && typeMirror instanceof ArrayType arrayType && + aClass.isArray()) { + return isSameClass(arrayType.getComponentType(), aClass.getComponentType()); + } + return false; + } + + /** + * {@return requireArena} + * + * @param env env + * @param typeMirror typeMirror + */ + public static boolean requireArena(ProcessingEnvironment env, TypeMirror typeMirror) { + return isAExtendsB(env, typeMirror, Upcall.class); + } + + /** + * {@return requireAllocator} + * + * @param typeMirror typeMirror + */ + public static boolean requireAllocator(TypeMirror typeMirror) { + return switch (typeMirror.getKind()) { + case ARRAY -> true; + case DECLARED -> isSameClass(typeMirror, String.class); + default -> false; + }; } } diff --git a/src/main/java/overrun/marshal/struct/IStruct.java b/src/main/java/overrun/marshal/struct/IStruct.java index 3370638..0ba9d41 100644 --- a/src/main/java/overrun/marshal/struct/IStruct.java +++ b/src/main/java/overrun/marshal/struct/IStruct.java @@ -16,6 +16,8 @@ package overrun.marshal.struct; +import overrun.marshal.Addressable; + import java.lang.foreign.MemorySegment; import java.lang.foreign.StructLayout; @@ -25,10 +27,11 @@ * @author squid233 * @since 0.1.0 */ -public interface IStruct { +public interface IStruct extends Addressable { /** * {@return the memory segment of this struct} */ + @Override MemorySegment segment(); /** From 655d8f0f617712d314633fc226dd49e17e0f8035 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Wed, 17 Jan 2024 22:41:46 +0800 Subject: [PATCH 14/28] Do not generate separate downcall invoker --- .../overrun/marshal/test/CDowncallTest.java | 44 +- .../java/overrun/marshal/gen/Downcall.java | 24 +- .../java/overrun/marshal/gen/Overload.java | 46 -- src/main/java/overrun/marshal/gen/Skip.java | 4 +- .../overrun/marshal/gen/struct/ByValue.java | 10 +- .../overrun/marshal/gen/struct/StructRef.java | 2 +- src/main/java/overrun/marshal/gen1/Spec.java | 7 +- ...yCatchStatement.java => TryStatement.java} | 51 +- .../gen1/TryWithResourceStatement.java | 55 --- .../overrun/marshal/gen2/DowncallData.java | 455 +++++++----------- .../java/overrun/marshal/gen2/TypeUse.java | 57 ++- 11 files changed, 271 insertions(+), 484 deletions(-) delete mode 100644 src/main/java/overrun/marshal/gen/Overload.java rename src/main/java/overrun/marshal/gen1/{TryCatchStatement.java => TryStatement.java} (55%) delete mode 100644 src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 1db3e80..aeacf0d 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -83,45 +83,44 @@ interface CDowncallTest { void testWithArray(int i, MemorySegment arr, MemorySegment nullableArr); - @Overload void testWithArray(int i, int[] arr, @NullableRef int[] nullableArr); - void testWithOneRef(@Ref MemorySegment arr); + void testWithArray(SegmentAllocator allocator, int i, int[] arr, @NullableRef int[] nullableArr); + + void testWithArray(Arena arena, int i, int[] arr, @NullableRef int[] nullableArr); + + void testWithArray(MemoryStack stack, int i, int[] arr, @NullableRef int[] nullableArr); + + void testWithOneRef(MemorySegment arr); /** * Test with ref * * @param arr arr */ - @Overload void testWithOneRef(@Ref int[] arr); void testWithRefArray(MemorySegment arr0, MemorySegment arr1, MemorySegment arr2, MemorySegment arr3, MemorySegment arr4, MemorySegment arr5); - @Overload void testWithRefArray(int[] arr0, @Ref int[] arr1, @NullableRef @Ref int[] arr2, boolean[] arr3, @Ref boolean[] arr4, @Sized(3) int[] arr5); void testWithString(MemorySegment str1, MemorySegment str2, MemorySegment nullableStr); - @Overload void testWithString(String str1, @StrCharset("UTF-16") String str2, @NullableRef String nullableStr); @Entrypoint("testReturnString") MemorySegment ntestReturnString(); - @Overload("ntestReturnString") String testReturnString(); @Entrypoint("testReturnStringUTF16") MemorySegment ntestReturnStringUTF16(); - @Overload("ntestReturnStringUTF16") @StrCharset("UTF-16") String testReturnStringUTF16(); MemorySegment testStringArray(MemorySegment arr, MemorySegment refArr); - @Overload String[] testStringArray(String[] arr, @Ref String[] refArr); @StrCharset("UTF-16") @@ -130,12 +129,10 @@ interface CDowncallTest { @Entrypoint("testWithReturnArray") MemorySegment ntestWithReturnArray(); - @Overload("ntestWithReturnArray") boolean[] testWithReturnArray(); void testMixArrSeg(MemorySegment segment, MemorySegment arr); - @Overload void testMixArrSeg(MemorySegment segment, @Ref int[] arr); @Critical(allowHeapAccess = true) @@ -148,7 +145,6 @@ interface CDowncallTest { @SizedSeg(4 * Integer.BYTES) MemorySegment ntestReturnSizedArr(); - @Overload("ntestReturnSizedArr") @Sized(4) int[] testReturnSizedArr(); @@ -157,18 +153,15 @@ interface CDowncallTest { void testUpcall(MemorySegment cb, MemorySegment nullableCb); - @Overload void testUpcall(Arena arena, GLFWErrorCallback cb, @NullableRef GLFWErrorCallback nullableCb); @Entrypoint("testReturnUpcall") MemorySegment ntestReturnUpcall(); - @Overload("ntestReturnUpcall") GLFWErrorCallback testReturnUpcall(Arena arena); void testStruct(MemorySegment struct, MemorySegment nullableStruct); - @Overload void testStruct(@StructRef("overrun.marshal.test.Vector3") Object struct, @NullableRef @StructRef("overrun.marshal.test.Vector3") Object nullableStruct); @StructRef("overrun.marshal.test.StructTest") @@ -184,46 +177,23 @@ interface CDowncallTest { int testEnumValue(int value); - @Overload MyEnum testEnumValue(MyEnum value); int testEnumValueWithRef(int value, MemorySegment ref); - @Overload MyEnum testEnumValueWithRef(MyEnum value, @Ref int[] ref); void testNameMemoryStack(MemorySegment stack, MemorySegment arr); - @Overload void testNameMemoryStack(MemorySegment stack, int[] arr); @Entrypoint("_testAnotherEntrypoint") void testAnotherEntrypoint(MemorySegment segment); - @Overload void testAnotherEntrypoint(int[] segment); - void testWithoutOverload(int[] arr); - void testDuplicateName(int[] testDuplicateName); - MyEnum testReturnEnumWithoutOverload(); - - @Sized(4) - int[] testReturnSizedArrWithoutOverload(); - - void testWithArrWithoutOverload(int[] arr); - - void testWithArrWithoutOverload(SegmentAllocator allocator, int[] arr); - - void testWithArrWithoutOverload(Arena arena, int[] arr); - - void testWithArrWithoutOverload(MemoryStack stack, int[] arr); - - void testStructWithoutOverload(@StructRef("overrun.marshal.test.StructTest") Object struct); - - void testUpcallWithoutOverload(Arena arena, GLFWErrorCallback cb); - /** * This is a test that tests all features. * diff --git a/src/main/java/overrun/marshal/gen/Downcall.java b/src/main/java/overrun/marshal/gen/Downcall.java index da6c767..c58a776 100644 --- a/src/main/java/overrun/marshal/gen/Downcall.java +++ b/src/main/java/overrun/marshal/gen/Downcall.java @@ -24,6 +24,8 @@ * The generated file will include constants of primitive * (boolean, byte, short, int, long, float, double) and String types in the marked class or interface. *

Methods

+ * Methods annotated with {@link Skip @Skip} will be skipped while generating. + *

* The javadoc of the original method will be copied. *

* The access modifier of the method can be changed by marking methods with {@link Access @Access}. @@ -31,18 +33,15 @@ * The {@link Custom @Custom} annotation overwrite all generated method body with the custom code. *

* The {@link Default @Default} annotation makes a method - * not to throw an exception if it was not found from the native library. + * not to throw an exception but return {@code null} if it was not found from the native library. + *

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

* The generator tries to generate static methods that invoke native functions, * which has the same name or is specified by {@link Entrypoint @Entrypoint}. - *

Overload Methods

- * An overload method, marked with {@link Overload @Overload}, automatically invokes another method with the same name. - * See the {@linkplain Overload documentation} for more information. - *

- * 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 Sized @Sized}, {@link SizedSeg @SizedSeg} and {@link Ref @Ref}. + * See {@link NullableRef @NullableRef}, {@link Ref @Ref}, {@link Sized @Sized} and {@link SizedSeg @SizedSeg} . *

Example

*
{@code
  * @Downcall(libname = "libGL.so", name = "GL")
@@ -54,13 +53,14 @@
  *
  * @author squid233
  * @see Access
+ * @see Critical
  * @see Custom
  * @see Default
  * @see Entrypoint
- * @see Overload
+ * @see NullableRef
+ * @see Ref
  * @see Sized
  * @see SizedSeg
- * @see Ref
  * @since 0.1.0
  */
 @Documented
@@ -82,8 +82,10 @@
 
     /**
      * {@return the library loader}
+     *
+     * @see Loader
      */
-    Class loader() default Loader.class;
+    Class loader() default Object.class;
 
     /**
      * {@return {@code true} if the generated class should not be {@code final}; {@code false} otherwise}
diff --git a/src/main/java/overrun/marshal/gen/Overload.java b/src/main/java/overrun/marshal/gen/Overload.java
deleted file mode 100644
index 6947501..0000000
--- a/src/main/java/overrun/marshal/gen/Overload.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2023-2024 Overrun Organization
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- */
-
-package overrun.marshal.gen;
-
-import java.lang.annotation.*;
-
-/**
- * Marks a method as an overload.
- * 

- * An overload method keeps {@link String} and array types - * instead of converting them into {@link java.lang.foreign.MemorySegment MemorySegment}. - * It will also invoke another method with the same name or {@linkplain #value() the specified value}. - *

Example

- *
{@code
- * void nset(MemorySegment vec);
- *
- * @Overload("nset")
- * void set(int[] vec);
- * }
- * - * @author squid233 - * @since 0.1.0 - */ -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -public @interface Overload { - /** - * {@return the name of the other method to be overloaded} - */ - String value() default ""; -} diff --git a/src/main/java/overrun/marshal/gen/Skip.java b/src/main/java/overrun/marshal/gen/Skip.java index 497dbf6..9fe8bb8 100644 --- a/src/main/java/overrun/marshal/gen/Skip.java +++ b/src/main/java/overrun/marshal/gen/Skip.java @@ -19,7 +19,7 @@ import java.lang.annotation.*; /** - * Skips generating a marked field, parameter or method. + * Skips generating a marked field or method. *

Example

*
{@code
  * @Skip
@@ -30,7 +30,7 @@
  * @since 0.1.0
  */
 @Documented
-@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})
+@Target({ElementType.FIELD, ElementType.METHOD})
 @Retention(RetentionPolicy.SOURCE)
 public @interface Skip {
 }
diff --git a/src/main/java/overrun/marshal/gen/struct/ByValue.java b/src/main/java/overrun/marshal/gen/struct/ByValue.java
index 0ee29f9..ac00e62 100644
--- a/src/main/java/overrun/marshal/gen/struct/ByValue.java
+++ b/src/main/java/overrun/marshal/gen/struct/ByValue.java
@@ -21,18 +21,12 @@
 /**
  * Marks a method that returns a struct by value.
  * 

- * This makes the generator insert a segment allocator before the first parameter. + * The annotated method must contain a segment allocator as the first parameter. *

Example

*
{@code
  * @ByValue
- * @Entrypoint("returnStruct")
  * @StructRef("org.example.MyStruct")
- * MemorySegment nreturnStruct();
- *
- * @ByValue
- * @Overload("nreturnStruct")
- * @StructRef("org.example.MyStruct")
- * Object returnStruct();
+ * Object returnStruct(SegmentAllocator allocator);
  * }
* * @author squid233 diff --git a/src/main/java/overrun/marshal/gen/struct/StructRef.java b/src/main/java/overrun/marshal/gen/struct/StructRef.java index a149581..e22ea36 100644 --- a/src/main/java/overrun/marshal/gen/struct/StructRef.java +++ b/src/main/java/overrun/marshal/gen/struct/StructRef.java @@ -19,7 +19,7 @@ import java.lang.annotation.*; /** - * Marks a field as a reference of a struct. + * Marks a field, a parameter, or the return type of method as a reference of a struct. *

Example

*
{@code
  * @StructRef("org.example.Vector3")
diff --git a/src/main/java/overrun/marshal/gen1/Spec.java b/src/main/java/overrun/marshal/gen1/Spec.java
index 846dacf..4e13b4b 100644
--- a/src/main/java/overrun/marshal/gen1/Spec.java
+++ b/src/main/java/overrun/marshal/gen1/Spec.java
@@ -278,12 +278,13 @@ static Spec indented(String s) {
     static Spec indentCodeBlock(String s) {
         return (builder, indent) -> s.lines().findFirst().ifPresent(first -> {
             final String indentString = indentString(indent);
-            builder.append(first)
-                .append('\n')
-                .append(s.lines()
+            builder.append(first);
+            if (s.lines().count() > 1) {
+                builder.append('\n').append(s.lines()
                     .skip(1)
                     .map(s1 -> indentString + s1)
                     .collect(Collectors.joining("\n")));
+            }
         });
     }
 
diff --git a/src/main/java/overrun/marshal/gen1/TryCatchStatement.java b/src/main/java/overrun/marshal/gen1/TryStatement.java
similarity index 55%
rename from src/main/java/overrun/marshal/gen1/TryCatchStatement.java
rename to src/main/java/overrun/marshal/gen1/TryStatement.java
index 0cf4761..5325fe5 100644
--- a/src/main/java/overrun/marshal/gen1/TryCatchStatement.java
+++ b/src/main/java/overrun/marshal/gen1/TryStatement.java
@@ -1,7 +1,7 @@
 /*
  * MIT License
  *
- * Copyright (c) 2023-2024 Overrun Organization
+ * 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
@@ -18,52 +18,69 @@
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.function.Consumer;
 
 /**
- * Try-catch
+ * Try
  *
  * @author squid233
  * @since 0.1.0
  */
-public final class TryCatchStatement implements Spec, StatementBlock {
+public final class TryStatement implements Spec, StatementBlock {
+    private final List resources = new ArrayList<>();
     private final List statements = new ArrayList<>();
     private final List catchClauses = new ArrayList<>();
 
     /**
      * Constructor
      */
-    public TryCatchStatement() {
+    public TryStatement() {
     }
 
-    /**
-     * Add a statement
-     *
-     * @param spec statement
-     */
     @Override
     public void addStatement(Spec spec) {
         statements.add(spec);
     }
 
     /**
-     * Add a catch clause and perform the action
+     * Add a resource
+     *
+     * @param resource resource
+     */
+    public void addResource(Spec resource) {
+        resources.add(resource);
+    }
+
+    /**
+     * Add a catch clause
      *
      * @param catchClause catch clause
-     * @param consumer    action
      */
-    public void addCatchClause(CatchClause catchClause, Consumer consumer) {
+    public void addCatchClause(CatchClause catchClause) {
         catchClauses.add(catchClause);
-        consumer.accept(catchClause);
     }
 
     @Override
     public void append(StringBuilder builder, int indent) {
         final String indentString = Spec.indentString(indent);
-        builder.append(indentString).append("try {\n");
+        builder.append(indentString).append("try ");
+        if (!resources.isEmpty()) {
+            builder.append('(');
+            for (int i = 0, s = resources.size(); i < s; i++) {
+                Spec resource = resources.get(i);
+                if (i > 0) {
+                    builder.append(";\n");
+                }
+                resource.append(builder, indent + 4);
+            }
+            builder.append(") ");
+        }
+        builder.append("{\n");
         statements.forEach(spec -> spec.append(builder, indent + 4));
-        builder.append(indentString).append("} ");
-        catchClauses.forEach(catchClause -> catchClause.append(builder, indent));
+        builder.append(indentString).append('}');
+        if (!catchClauses.isEmpty()) {
+            builder.append(' ');
+            catchClauses.forEach(catchClause -> catchClause.append(builder, indent));
+        }
         builder.append('\n');
     }
 }
diff --git a/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java b/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java
deleted file mode 100644
index 947259d..0000000
--- a/src/main/java/overrun/marshal/gen1/TryWithResourceStatement.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * MIT License
- *
- * Copyright (c) 2024 Overrun Organization
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- */
-
-package overrun.marshal.gen1;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * try-with-resource
- *
- * @author squid233
- * @since 0.1.0
- */
-public final class TryWithResourceStatement implements Spec, StatementBlock {
-    private final Spec resource;
-    private final List statements = new ArrayList<>();
-
-    /**
-     * Construct
-     *
-     * @param resource resource
-     */
-    public TryWithResourceStatement(Spec resource) {
-        this.resource = resource;
-    }
-
-    @Override
-    public void addStatement(Spec spec) {
-        statements.add(spec);
-    }
-
-    @Override
-    public void append(StringBuilder builder, int indent) {
-        final String indentString = Spec.indentString(indent);
-        builder.append(indentString).append("try (");
-        resource.append(builder, indent);
-        builder.append(") {\n");
-        statements.forEach(spec -> spec.append(builder, indent + 4));
-        builder.append(indentString).append("}\n");
-    }
-}
diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java
index 4260f3b..1aa0472 100644
--- a/src/main/java/overrun/marshal/gen2/DowncallData.java
+++ b/src/main/java/overrun/marshal/gen2/DowncallData.java
@@ -104,67 +104,65 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName
         }
         // collect method handles
         final Map methodHandleDataMap = LinkedHashMap.newLinkedHashMap(methods.size());
-        skipAnnotated(methods)
-            .filter(executableElement -> executableElement.getAnnotation(Overload.class) == null)
-            .forEach(executableElement -> {
-                final Access access = executableElement.getAnnotation(Access.class);
-                final String entrypoint = getMethodEntrypoint(executableElement);
-                final TypeMirror returnType = executableElement.getReturnType();
-                methodHandleDataMap.put(entrypoint,
-                    new MethodHandleData(
-                        executableElement,
-                        access != null ? access.value() : AccessModifier.PUBLIC,
-                        entrypoint,
-                        TypeUse.valueLayout(processingEnv, returnType)
-                            .map(typeUse -> importData -> {
-                                final ByValue byValue = executableElement.getAnnotation(ByValue.class);
-                                final StructRef structRef = executableElement.getAnnotation(StructRef.class);
-                                final SizedSeg sizedSeg = executableElement.getAnnotation(SizedSeg.class);
-                                final Sized sized = executableElement.getAnnotation(Sized.class);
-                                final boolean structRefNotNull = structRef != null;
-                                final boolean sizedSegNotNull = sizedSeg != null;
-                                final boolean sizedNotNull = sized != null;
-                                final Spec spec = typeUse.apply(importData);
-                                if (structRefNotNull || sizedSegNotNull || sizedNotNull) {
-                                    final InvokeSpec invokeSpec = new InvokeSpec(spec, "withTargetLayout");
-                                    if (structRefNotNull) {
-                                        final Spec layout = Spec.accessSpec(structRef.value(), "LAYOUT");
-                                        return byValue != null ? layout : invokeSpec.addArgument(layout);
-                                    }
-                                    if (sizedSegNotNull) {
-                                        return invokeSpec.addArgument(new InvokeSpec(
-                                            importData.simplifyOrImport(MemoryLayout.class),
-                                            "sequenceLayout"
-                                        ).addArgument(getConstExp(sizedSeg.value()))
-                                            .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData)));
-                                    }
+        skipAnnotated(methods).forEach(executableElement -> {
+            final Access access = executableElement.getAnnotation(Access.class);
+            final String entrypoint = getMethodEntrypoint(executableElement);
+            final TypeMirror returnType = executableElement.getReturnType();
+            methodHandleDataMap.put(entrypoint,
+                new MethodHandleData(
+                    executableElement,
+                    access != null ? access.value() : AccessModifier.PUBLIC,
+                    entrypoint,
+                    TypeUse.valueLayout(processingEnv, returnType)
+                        .map(typeUse -> importData -> {
+                            final ByValue byValue = executableElement.getAnnotation(ByValue.class);
+                            final StructRef structRef = executableElement.getAnnotation(StructRef.class);
+                            final SizedSeg sizedSeg = executableElement.getAnnotation(SizedSeg.class);
+                            final Sized sized = executableElement.getAnnotation(Sized.class);
+                            final boolean structRefNotNull = structRef != null;
+                            final boolean sizedSegNotNull = sizedSeg != null;
+                            final boolean sizedNotNull = sized != null;
+                            final Spec spec = typeUse.apply(importData);
+                            if (structRefNotNull || sizedSegNotNull || sizedNotNull) {
+                                final InvokeSpec invokeSpec = new InvokeSpec(spec, "withTargetLayout");
+                                if (structRefNotNull) {
+                                    final Spec layout = Spec.accessSpec(structRef.value(), "LAYOUT");
+                                    return byValue != null ? layout : invokeSpec.addArgument(layout);
+                                }
+                                if (sizedSegNotNull) {
                                     return invokeSpec.addArgument(new InvokeSpec(
                                         importData.simplifyOrImport(MemoryLayout.class),
-                                        "sequenceLayout")
-                                        .addArgument(getConstExp(sized.value()))
-                                        .addArgument(switch (0) {
-                                            default -> {
-                                                final Optional seqLayout;
-                                                if (returnType.getKind() == TypeKind.ARRAY &&
-                                                    returnType instanceof ArrayType arrayType) {
-                                                    seqLayout = TypeUse.valueLayout(processingEnv, arrayType.getComponentType());
-                                                } else {
-                                                    seqLayout = TypeUse.valueLayout(byte.class);
-                                                }
-                                                yield seqLayout.orElseThrow().apply(importData);
-                                            }
-                                        }));
+                                        "sequenceLayout"
+                                    ).addArgument(getConstExp(sizedSeg.value()))
+                                        .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData)));
                                 }
-                                return spec;
-                            }),
-                        skipAnnotated(executableElement.getParameters())
-                            .map(element -> TypeUse.valueLayout(processingEnv, element))
-                            .filter(Optional::isPresent)
-                            .map(Optional::get)
-                            .toList(),
-                        executableElement.getAnnotation(Default.class) != null
-                    ));
-            });
+                                return invokeSpec.addArgument(new InvokeSpec(
+                                    importData.simplifyOrImport(MemoryLayout.class),
+                                    "sequenceLayout")
+                                    .addArgument(getConstExp(sized.value()))
+                                    .addArgument(switch (0) {
+                                        default -> {
+                                            final Optional seqLayout;
+                                            if (returnType.getKind() == TypeKind.ARRAY &&
+                                                returnType instanceof ArrayType arrayType) {
+                                                seqLayout = TypeUse.valueLayout(processingEnv, arrayType.getComponentType());
+                                            } else {
+                                                seqLayout = TypeUse.valueLayout(byte.class);
+                                            }
+                                            yield seqLayout.orElseThrow().apply(importData);
+                                        }
+                                    }));
+                            }
+                            return spec;
+                        }),
+                    skipAnnotated(executableElement.getParameters())
+                        .map(element -> TypeUse.valueLayout(processingEnv, element))
+                        .filter(Optional::isPresent)
+                        .map(Optional::get)
+                        .toList(),
+                    executableElement.getAnnotation(Default.class) != null
+                ));
+        });
 
         // add linker and lookup
         final Predicate containsKey = methodHandleDataMap::containsKey;
@@ -234,7 +232,6 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName
         final var fieldNames = fieldDataList.stream().map(FieldData::name).toList();
         final List addedMethodHandleInvoker = new ArrayList<>(methodHandleDataMap.size());
         skipAnnotated(methods).forEach(executableElement -> {
-            final Overload overload = executableElement.getAnnotation(Overload.class);
             final MethodHandleData methodHandleData = methodHandleDataMap.get(getMethodEntrypoint(executableElement));
             if (methodHandleData == null) {
                 return;
@@ -244,54 +241,10 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName
             if (custom != null) {
                 addCustomMethod(executableElement, methodHandleData, custom);
             } else {
-                final String methodName = executableElement.getSimpleName().toString();
-                final boolean containsNonDowncallType = containsNonDowncallType(executableElement);
-                final boolean shouldGenOverload = overload != null || containsNonDowncallType;
-
-                String finalMethodName = methodName;
-                if (overload == null && !addedMethodHandleInvoker.contains(methodHandleData)) {
-                    addedMethodHandleInvoker.add(methodHandleData);
-                    if (containsNonDowncallType) {
-                        boolean shouldAddPrefix = true;
-                        if (parameterContainsNonDowncallType(executableElement)) {
-                            shouldAddPrefix = false;
-                        } else {
-                            final var rawParameterList = skipAnnotated(executableElement.getParameters()).toList();
-                            if (executableElement.getAnnotation(ByValue.class) == null &&
-                                !rawParameterList.isEmpty() &&
-                                isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), SegmentAllocator.class)) {
-                                shouldAddPrefix = false;
-                            }
-                        }
-                        if (shouldAddPrefix) {
-                            final String prefixed = "n" + methodName;
-                            finalMethodName = "n" + tryInsertUnderline(methodName, _ -> functionDataList.stream()
-                                .anyMatch(functionData -> prefixed.equals(functionData.name())));
-                        }
-                    }
-
-                    addDowncallMethod(simpleClassName,
-                        executableElement,
-                        finalMethodName,
-                        fieldNames,
-                        methodHandleData);
-                }
-
-                if (shouldGenOverload) {
-                    final String downcallName;
-                    if (overload == null) {
-                        downcallName = finalMethodName;
-                    } else {
-                        downcallName = getMethodAnnotationString(executableElement, Overload.class, Overload::value);
-                    }
-
-                    addOverloadMethod(simpleClassName,
-                        executableElement,
-                        methodName,
-                        downcallName,
-                        fieldNames,
-                        methodHandleData);
-                }
+                addDowncallMethod(simpleClassName,
+                    executableElement,
+                    fieldNames,
+                    methodHandleData);
             }
         });
     }
@@ -312,48 +265,54 @@ public void generate(TypeElement typeElement) throws IOException {
         generate(generatedClassName);
     }
 
-    private void addOverloadMethod(
+    private void addDowncallMethod(
         String simpleClassName,
         ExecutableElement executableElement,
-        String methodName,
-        String downcallName,
         List fieldNames,
         MethodHandleData methodHandleData) {
-        final var rawParameterList = skipAnnotated(executableElement.getParameters()).toList();
-        final var parameterDataList = parametersToParameterDataList(rawParameterList);
-        final var parameterNames = parameterDataList.stream()
-            .map(ParameterData::name)
-            .toList();
+        final var returnType = TypeUse.toProcessedType(processingEnv, executableElement, ExecutableElement::getReturnType);
 
-        final int skipFirst = shouldSkipFirstParameter(executableElement, rawParameterList, false);
+        final var parameters = executableElement.getParameters();
+        final int skipFirst = shouldSkipFirstParameter(executableElement, parameters);
         if (skipFirst < 0) {
             return;
         }
+        final var parameterDataList = parameters.stream()
+            .map(element -> new ParameterData(
+                getElementAnnotations(PARAMETER_ANNOTATIONS, element),
+                TypeUse.toProcessedType(processingEnv, element).orElseThrow(),
+                element.getSimpleName().toString()
+            ))
+            .toList();
+        final var parameterNames = parameterDataList.stream()
+            .skip(skipFirst > 0 ? 1 : 0)
+            .map(ParameterData::name)
+            .toList();
 
         final List variablesInScope = new ArrayList<>(parameterNames);
 
         final String memoryStackName = tryInsertUnderline("stack", s -> variablesInScope.stream().anyMatch(s::equals));
         final boolean hasAllocatorParameter =
             !parameterDataList.isEmpty() &&
-            isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), SegmentAllocator.class);
+            isAExtendsB(processingEnv, parameters.getFirst().asType(), SegmentAllocator.class);
         final String allocatorParameter = hasAllocatorParameter ?
             parameterDataList.getFirst().name() :
             memoryStackName;
         final boolean shouldWrapWithMemoryStack =
-            rawParameterList.stream().anyMatch(element -> useAllocator(element.asType())) &&
+            parameters.stream().anyMatch(element -> useAllocator(element.asType())) &&
             !hasAllocatorParameter;
         if (shouldWrapWithMemoryStack) {
             variablesInScope.add(memoryStackName);
         }
 
-        final boolean shouldAddInvoker = variablesInScope.stream().anyMatch(fieldNames::contains);
-        final TypeMirror returnType = executableElement.getReturnType();
-        final boolean returnVoid = returnType.getKind() == TypeKind.VOID;
+        final boolean shouldAddInvoker = parameterNames.stream().anyMatch(fieldNames::contains);
 
         final List statements = new ArrayList<>();
+        final List tryStatements = new ArrayList<>();
+        final List invocationStatements = new ArrayList<>();
 
         // check array size of @Sized array
-        rawParameterList.stream()
+        parameters.stream()
             .filter(element -> element.getAnnotation(Sized.class) != null)
             .forEach(element -> {
                 final Sized sized = element.getAnnotation(Sized.class);
@@ -364,12 +323,12 @@ private void addOverloadMethod(
             });
 
         // ref segments
-        final Map refNameMap = HashMap.newHashMap(Math.toIntExact(rawParameterList.stream()
+        final Map refNameMap = HashMap.newHashMap((int) parameters.stream()
             .filter(element -> element.getAnnotation(Ref.class) != null)
-            .count()));
-        rawParameterList.stream()
+            .count());
+        parameters.stream()
             .filter(element -> element.getAnnotation(Ref.class) != null)
-            .forEach(element -> statements.add(importData -> {
+            .forEach(element -> invocationStatements.add(importData -> {
                 final String elementName = element.getSimpleName().toString();
                 final String refSegName = tryInsertUnderline(elementName,
                     s -> variablesInScope.stream().anyMatch(s::equals));
@@ -384,12 +343,20 @@ private void addOverloadMethod(
             }));
 
         // invocation
+        final String methodHandleName = methodHandleData.name();
         final TypeUse invokeTypeUse = importData -> {
-            final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ? Spec.literal(simpleClassName) : null, downcallName);
+            final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ?
+                Spec.accessSpec(simpleClassName, methodHandleName) :
+                Spec.literal(methodHandleName), "invokeExact");
             // wrap parameters
-            var stream = rawParameterList.stream();
+            var stream = parameters.stream();
             if (skipFirst > 0) {
                 stream = stream.skip(1);
+            } else if (executableElement.getAnnotation(ByValue.class) != null &&
+                       !isSameClass(parameters.getFirst().asType(), SegmentAllocator.class)) {
+                invokeSpec.addArgument(Spec.cast(importData.simplifyOrImport(SegmentAllocator.class),
+                    Spec.literal(allocatorParameter)));
+                stream = stream.skip(1);
             }
             stream.map(element -> wrapParameter(importData, allocatorParameter, element, true, refNameMap::get))
                 .forEach(invokeSpec::addArgument);
@@ -397,101 +364,91 @@ private void addOverloadMethod(
         };
 
         // store result
+        final var downcallReturnType = TypeUse.toDowncallType(processingEnv, executableElement, ExecutableElement::getReturnType);
         final boolean shouldStoreResult =
-            canConvertToAddress(executableElement, ExecutableElement::getReturnType) ||
-            (!returnVoid &&
-             rawParameterList.stream().anyMatch(element -> element.getAnnotation(Ref.class) != null));
+            downcallReturnType.isPresent() &&
+            (canConvertToAddress(executableElement, ExecutableElement::getReturnType) ||
+             parameters.stream().anyMatch(element -> element.getAnnotation(Ref.class) != null));
         final String resultVarName;
         if (shouldStoreResult) {
             resultVarName = tryInsertUnderline("result", s -> variablesInScope.stream().anyMatch(s::equals));
             variablesInScope.add(resultVarName);
-            statements.add(importData -> new VariableStatement("var", resultVarName, invokeTypeUse.apply(importData))
+            invocationStatements.add(importData -> new VariableStatement("var",
+                resultVarName,
+                Spec.cast(downcallReturnType.get().apply(importData), invokeTypeUse.apply(importData)))
                 .setAccessModifier(AccessModifier.PACKAGE_PRIVATE)
                 .setFinal(true));
         } else {
             resultVarName = null;
-            if (returnVoid) {
-                statements.add(importData -> Spec.statement(invokeTypeUse.apply(importData)));
-            } else {
-                statements.add(importData -> Spec.returnStatement(wrapReturnValue(importData,
-                    invokeTypeUse.apply(importData),
+            if (downcallReturnType.isPresent()) {
+                invocationStatements.add(importData -> Spec.returnStatement(wrapReturnValue(importData,
+                    Spec.cast(downcallReturnType.get().apply(importData), invokeTypeUse.apply(importData)),
                     executableElement)));
+            } else {
+                invocationStatements.add(importData -> Spec.statement(invokeTypeUse.apply(importData)));
             }
         }
 
         // passing to ref
-        rawParameterList.stream()
+        parameters.stream()
             .filter(element -> element.getAnnotation(Ref.class) != null)
-            .forEach(element -> statements.add(importData ->
+            .forEach(element -> invocationStatements.add(importData ->
                 copyRefResult(importData, element, refNameMap::get)));
 
         // return result
         if (shouldStoreResult) {
-            statements.add(importData -> Spec.returnStatement(wrapReturnValue(importData,
+            invocationStatements.add(importData -> Spec.returnStatement(wrapReturnValue(importData,
                 Spec.literal(resultVarName),
                 executableElement)));
         }
 
-        final StructRef structRef = executableElement.getAnnotation(StructRef.class);
-        functionDataList.add(new FunctionData(
-            getDocComment(executableElement),
-            getElementAnnotations(METHOD_ANNOTATIONS, executableElement),
-            methodHandleData.accessModifier(),
-            true,
-            structRef != null ?
-                TypeUse.literal(structRef.value()) :
-                TypeUse.of(processingEnv, returnType),
-            methodName,
-            parameterDataList,
-            shouldWrapWithMemoryStack ?
-                List.of(importData -> {
-                    final TryWithResourceStatement tryWithResourceStatement = new TryWithResourceStatement(
-                        new VariableStatement("var",
-                            memoryStackName,
-                            new InvokeSpec(importData.simplifyOrImport(MemoryStack.class), "stackPush"))
-                            .setAccessModifier(AccessModifier.PACKAGE_PRIVATE)
-                            .setAddSemicolon(false)
-                    );
-                    statements.forEach(typeUse ->
-                        tryWithResourceStatement.addStatement(typeUse.apply(importData)));
-                    return tryWithResourceStatement;
-                }) :
-                statements
-        ));
-    }
+        statements.add(importData -> {
+            final TryStatement tryStatement = new TryStatement();
 
-    private void addDowncallMethod(
-        String simpleClassName,
-        ExecutableElement executableElement,
-        String methodName,
-        List fieldNames,
-        MethodHandleData methodHandleData) {
-        final String methodHandleName = methodHandleData.name();
-        final var returnType = TypeUse.toDowncallType(processingEnv, executableElement, ExecutableElement::getReturnType);
-        final boolean returnByValue = executableElement.getAnnotation(ByValue.class) != null;
+            if (shouldWrapWithMemoryStack) {
+                tryStatement.addResource(new VariableStatement(
+                    "var",
+                    memoryStackName,
+                    new InvokeSpec(importData.simplifyOrImport(MemoryStack.class), "stackPush")
+                ).setAccessModifier(AccessModifier.PACKAGE_PRIVATE)
+                    .setAddSemicolon(false));
+            }
 
-        final var rawParameterList = skipAnnotated(executableElement.getParameters()).toList();
-        final int skipFirst = shouldSkipFirstParameter(executableElement, rawParameterList, true);
-        if (skipFirst < 0) {
-            return;
-        }
-        var stream = rawParameterList.stream();
-        if (skipFirst > 0) {
-            stream = stream.skip(1);
-        }
-        final var parameters = stream.toList();
-        final var parameterDataList = parameters.stream()
-            .map(element -> new ParameterData(
-                getElementAnnotations(PARAMETER_ANNOTATIONS, element),
-                TypeUse.toDowncallType(processingEnv, element).orElseThrow(),
-                element.getSimpleName().toString()
-            ))
-            .toList();
-        final var parameterNames = parameterDataList.stream()
-            .map(ParameterData::name)
-            .toList();
+            final Default defaultAnnotation = executableElement.getAnnotation(Default.class);
+            if (defaultAnnotation != null) {
+                final TypeUse ifTypeUse = importData1 -> {
+                    final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(methodHandleName));
+                    invocationStatements.forEach(typeUse -> ifStatement.addStatement(typeUse.apply(importData1)));
+                    final String defaultValue = defaultAnnotation.value();
+                    if (!defaultValue.isBlank()) {
+                        ifStatement.addElseClause(ElseClause.of(), elseClause ->
+                            elseClause.addStatement(Spec.returnStatement(Spec.indentCodeBlock(defaultValue)))
+                        );
+                    }
+                    return ifStatement;
+                };
+                tryStatements.add(ifTypeUse);
+            } else {
+                tryStatements.addAll(invocationStatements);
+            }
 
-        final boolean shouldAddInvoker = parameterNames.stream().anyMatch(fieldNames::contains);
+            tryStatements.forEach(typeUse -> tryStatement.addStatement(typeUse.apply(importData)));
+
+            tryStatement.addCatchClause(switch (0) {
+                default -> {
+                    final CatchClause catchClause = new CatchClause(
+                        importData.simplifyOrImport(Throwable.class),
+                        tryInsertUnderline("e", s -> fieldNames.contains(s) || parameterNames.contains(s))
+                    );
+                    catchClause.addStatement(Spec.throwStatement(
+                        new ConstructSpec(importData.simplifyOrImport(RuntimeException.class))
+                            .addArgument(Spec.literal(catchClause.name()))
+                    ));
+                    yield catchClause;
+                }
+            });
+            return tryStatement;
+        });
 
         functionDataList.add(new FunctionData(
             getDocComment(executableElement),
@@ -499,61 +456,14 @@ private void addDowncallMethod(
             methodHandleData.accessModifier(),
             true,
             returnType.orElseGet(() -> TypeUse.literal(void.class)),
-            methodName,
+            executableElement.getSimpleName().toString(),
             parameterDataList,
-            List.of(
-                importData -> {
-                    final TryCatchStatement tryCatchStatement = new TryCatchStatement();
-                    final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ?
-                        Spec.accessSpec(simpleClassName, methodHandleName) :
-                        Spec.literal(methodHandleName), "invokeExact");
-                    var stream1 = parameterNames.stream();
-                    if (returnByValue) {
-                        if (!isSameClass(parameters.getFirst().asType(), SegmentAllocator.class)) {
-                            invokeSpec.addArgument(Spec.cast(importData.simplifyOrImport(SegmentAllocator.class),
-                                Spec.literal(parameterDataList.getFirst().name())));
-                            stream1 = stream1.skip(1);
-                        }
-                    }
-                    stream1.forEach(invokeSpec::addArgument);
-
-                    final Spec returnSpec = returnType
-                        .map(typeUse -> Spec.returnStatement(Spec.cast(typeUse.apply(importData), invokeSpec)))
-                        .orElseGet(() -> Spec.statement(invokeSpec));
-
-                    final Spec optionalSpec;
-                    final Default defaultAnnotation = executableElement.getAnnotation(Default.class);
-                    if (defaultAnnotation != null) {
-                        final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(methodHandleName));
-                        ifStatement.addStatement(returnSpec);
-                        final String defaultValue = defaultAnnotation.value();
-                        if (!defaultValue.isBlank()) {
-                            ifStatement.addElseClause(ElseClause.of(), elseClause ->
-                                elseClause.addStatement(Spec.returnStatement(Spec.indentCodeBlock(defaultValue)))
-                            );
-                        }
-                        optionalSpec = ifStatement;
-                    } else {
-                        optionalSpec = returnSpec;
-                    }
-
-                    final String exceptionName = tryInsertUnderline("e", s -> fieldNames.contains(s) || parameterNames.contains(s));
-                    tryCatchStatement.addStatement(optionalSpec);
-                    tryCatchStatement.addCatchClause(new CatchClause(
-                        importData.simplifyOrImport(Throwable.class),
-                        exceptionName
-                    ), catchClause ->
-                        catchClause.addStatement(Spec.throwStatement(new ConstructSpec(importData.simplifyOrImport(RuntimeException.class))
-                            .addArgument(Spec.literal(catchClause.name())))));
-                    return tryCatchStatement;
-                }
-            )
+            statements
         ));
     }
 
     private int shouldSkipFirstParameter(ExecutableElement executableElement,
-                                         List rawParameterList,
-                                         boolean printError) {
+                                         List rawParameterList) {
         final boolean returnByValue = executableElement.getAnnotation(ByValue.class) != null;
         final boolean rawParamListNotEmpty = !rawParameterList.isEmpty();
         final boolean firstParamArena =
@@ -568,18 +478,14 @@ private int shouldSkipFirstParameter(ExecutableElement executableElement,
             if (firstParamArena) {
                 return 1;
             }
-            if (printError) {
-                processingEnv.getMessager().printError("The first parameter of method with upcall must be Arena", executableElement);
-            }
+            processingEnv.getMessager().printError("The first parameter of method with upcall must be Arena", executableElement);
             return -1;
         }
         if (returnByValue) {
             if (firstParamSegmentAllocator) {
                 return 0;
             }
-            if (printError) {
-                processingEnv.getMessager().printError("The first parameter of method marked as @ByValue must be SegmentAllocator", executableElement);
-            }
+            processingEnv.getMessager().printError("The first parameter of method marked as @ByValue must be SegmentAllocator", executableElement);
             return -1;
         }
         return firstParamSegmentAllocator ? 1 : 0;
@@ -887,16 +793,9 @@ private static String getCharset(Element element) {
     }
 
     private static String getMethodEntrypoint(ExecutableElement executableElement) {
-        return getMethodAnnotationString(executableElement, Entrypoint.class, Entrypoint::value);
-    }
-
-    private static  String getMethodAnnotationString(
-        ExecutableElement executableElement,
-        Class aClass,
-        Function function) {
-        final T annotation = executableElement.getAnnotation(aClass);
-        if (annotation != null) {
-            final String value = function.apply(annotation);
+        final Entrypoint entrypoint = executableElement.getAnnotation(Entrypoint.class);
+        if (entrypoint != null) {
+            final String value = entrypoint.value();
             if (!value.isBlank()) {
                 return value;
             }
@@ -920,34 +819,4 @@ private  boolean canConvertToAddress(T element, Function boolean isNotDowncallType(T element, Function function) {
-        if (element.getAnnotation(StructRef.class) != null) {
-            return true;
-        }
-        final TypeMirror type = function.apply(element);
-        final TypeKind typeKind = type.getKind();
-        final boolean downcallType =
-            (typeKind.isPrimitive()) ||
-            (typeKind == TypeKind.VOID) ||
-            (typeKind == TypeKind.DECLARED &&
-             (isSameClass(type, MemorySegment.class) ||
-              isAExtendsB(processingEnv, type, SegmentAllocator.class))
-            );
-        return !downcallType;
-    }
-
-    private boolean returnNonDowncallType(ExecutableElement executableElement) {
-        return isNotDowncallType(executableElement, ExecutableElement::getReturnType);
-    }
-
-    private boolean parameterContainsNonDowncallType(ExecutableElement executableElement) {
-        return executableElement.getParameters().stream()
-            .anyMatch(element -> isNotDowncallType(element, VariableElement::asType));
-    }
-
-    private boolean containsNonDowncallType(ExecutableElement executableElement) {
-        return returnNonDowncallType(executableElement) ||
-               parameterContainsNonDowncallType(executableElement);
-    }
 }
diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java
index 2dfa8c7..24d10cc 100644
--- a/src/main/java/overrun/marshal/gen2/TypeUse.java
+++ b/src/main/java/overrun/marshal/gen2/TypeUse.java
@@ -143,33 +143,68 @@ static Optional toDowncallType(ProcessingEnvironment env, TypeMirror ty
     /**
      * Converts to the downcall type
      *
-     * @param env     the processing environment
-     * @param element the element
+     * @param env      the processing environment
+     * @param element  the element
+     * @param function the function
+     * @param       the element type
      * @return the downcall type
      */
-    static Optional toDowncallType(ProcessingEnvironment env, Element element) {
-        return toDowncallType(env, element, Element::asType);
+    static  Optional toDowncallType(ProcessingEnvironment env, T element, Function function) {
+        final StructRef structRef = element.getAnnotation(StructRef.class);
+        if (structRef != null) {
+            return Optional.of(of(MemorySegment.class));
+        }
+        return toDowncallType(env, function.apply(element));
     }
 
     /**
-     * Converts to the downcall type
+     * Converts to the processed type
+     *
+     * @param env        the processing environment
+     * @param typeMirror the type mirror
+     * @return the processed type
+     */
+    static Optional toProcessedType(ProcessingEnvironment env, TypeMirror typeMirror) {
+        final TypeKind typeKind = typeMirror.getKind();
+        if (typeKind.isPrimitive()) {
+            return Optional.of(literal(typeMirror.toString()));
+        }
+        return switch (typeKind) {
+            case ARRAY, DECLARED -> Optional.of(of(env, typeMirror));
+            default -> Optional.empty();
+        };
+    }
+
+    /**
+     * Converts to the processed type
      *
      * @param env      the processing environment
      * @param element  the element
      * @param function the function
      * @param       the element type
-     * @return the downcall type
+     * @return the processed type
      */
-    static  Optional toDowncallType(ProcessingEnvironment env, T element, Function function) {
+    static  Optional toProcessedType(ProcessingEnvironment env, T element, Function function) {
         final StructRef structRef = element.getAnnotation(StructRef.class);
         if (structRef != null) {
-            return Optional.of(of(MemorySegment.class));
+            return Optional.of(literal(structRef.value()));
         }
-        return toDowncallType(env, function.apply(element));
+        return toProcessedType(env, function.apply(element));
     }
 
     /**
-     * {@return literal type use}
+     * Converts to the processed type
+     *
+     * @param env     the processing environment
+     * @param element the element
+     * @return the processed type
+     */
+    static Optional toProcessedType(ProcessingEnvironment env, Element element) {
+        return toProcessedType(env, element, Element::asType);
+    }
+
+    /**
+     * {@return import type use}
      *
      * @param aClass class
      */
@@ -178,7 +213,7 @@ static TypeUse of(Class aClass) {
     }
 
     /**
-     * {@return literal type use}
+     * {@return import type use}
      *
      * @param env        env
      * @param typeMirror typeMirror

From e80e31dff1f153337ff2c2b35b2f10300e2a3e3c Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Wed, 17 Jan 2024 23:30:14 +0800
Subject: [PATCH 15/28] Update downcall generator

---
 src/main/java/overrun/marshal/BoolHelper.java |  2 +-
 src/main/java/overrun/marshal/Marshal.java    | 21 +++-
 src/main/java/overrun/marshal/StrHelper.java  |  2 +-
 src/main/java/overrun/marshal/Unmarshal.java  | 87 +++++++++++++---
 .../overrun/marshal/gen2/DowncallData.java    | 99 +++++++------------
 .../java/overrun/marshal/internal/Util.java   | 29 ++++--
 6 files changed, 153 insertions(+), 87 deletions(-)

diff --git a/src/main/java/overrun/marshal/BoolHelper.java b/src/main/java/overrun/marshal/BoolHelper.java
index 917b060..d69fbcc 100644
--- a/src/main/java/overrun/marshal/BoolHelper.java
+++ b/src/main/java/overrun/marshal/BoolHelper.java
@@ -26,7 +26,7 @@
  * @author squid233
  * @since 0.1.0
  */
-@Deprecated(since = "0.1.0")
+@Deprecated(since = "0.1.0", forRemoval = true)
 public final class BoolHelper {
     private BoolHelper() {
         //no instance
diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java
index 651a17c..0c74f3e 100644
--- a/src/main/java/overrun/marshal/Marshal.java
+++ b/src/main/java/overrun/marshal/Marshal.java
@@ -43,15 +43,17 @@ public final class Marshal {
     private Marshal() {
     }
 
-    private static VarHandle arrayVarHandle(ValueLayout valueLayout) {
+    static VarHandle arrayVarHandle(ValueLayout valueLayout) {
         return MethodHandles.insertCoordinates(valueLayout.arrayElementVarHandle(), 1, 0L);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, String string) {
+        if (string == null) return MemorySegment.NULL;
         return allocator.allocateFrom(string);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, String string, Charset charset) {
+        if (string == null) return MemorySegment.NULL;
         return allocator.allocateFrom(string, charset);
     }
 
@@ -60,14 +62,17 @@ public static int marshal(CEnum cEnum) {
     }
 
     public static MemorySegment marshal(Addressable addressable) {
+        if (addressable == null) return MemorySegment.NULL;
         return addressable.segment();
     }
 
     public static MemorySegment marshal(Arena arena, Upcall upcall) {
+        if (upcall == null) return MemorySegment.NULL;
         return upcall.stub(arena);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, boolean[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         final MemorySegment segment = allocator.allocate(JAVA_BOOLEAN, arr.length);
         for (int i = 0, l = arr.length; i < l; i++) {
             vh_booleanArray.set(segment, (long) i, segment, arr[i]);
@@ -76,34 +81,42 @@ public static MemorySegment marshal(SegmentAllocator allocator, boolean[] arr) {
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, char[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return allocator.allocateFrom(JAVA_CHAR, arr);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, byte[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return allocator.allocateFrom(JAVA_BYTE, arr);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, short[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return allocator.allocateFrom(JAVA_SHORT, arr);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, int[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return allocator.allocateFrom(JAVA_INT, arr);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, long[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return allocator.allocateFrom(JAVA_LONG, arr);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, float[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return allocator.allocateFrom(JAVA_FLOAT, arr);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, double[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return allocator.allocateFrom(JAVA_DOUBLE, arr);
     }
 
     public static  MemorySegment marshal(A allocator, T[] arr, BiFunction function) {
+        if (arr == null) return MemorySegment.NULL;
         final MemorySegment segment = allocator.allocate(ADDRESS, arr.length);
         for (int i = 0, l = arr.length; i < l; i++) {
             vh_addressArray.set(segment, (long) i, function.apply(allocator, arr[i]));
@@ -112,18 +125,22 @@ public static  MemorySegment marshal(A allocator,
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, MemorySegment[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return marshal(allocator, arr, (_, segment) -> segment);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, String[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return marshal(allocator, arr, SegmentAllocator::allocateFrom);
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, String[] arr, Charset charset) {
+        if (arr == null) return MemorySegment.NULL;
         return marshal(allocator, arr, (segmentAllocator, str) -> segmentAllocator.allocateFrom(str, charset));
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, CEnum[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         final MemorySegment segment = allocator.allocate(JAVA_INT, arr.length);
         for (int i = 0; i < arr.length; i++) {
             vh_intArray.set(segment, (long) i, arr[i].value());
@@ -132,10 +149,12 @@ public static MemorySegment marshal(SegmentAllocator allocator, CEnum[] arr) {
     }
 
     public static MemorySegment marshal(SegmentAllocator allocator, Addressable[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return marshal(allocator, arr, (_, addressable) -> addressable.segment());
     }
 
     public static MemorySegment marshal(Arena arena, Upcall[] arr) {
+        if (arr == null) return MemorySegment.NULL;
         return marshal(arena, arr, (arena1, upcall) -> upcall.stub(arena1));
     }
 }
diff --git a/src/main/java/overrun/marshal/StrHelper.java b/src/main/java/overrun/marshal/StrHelper.java
index 47532d5..fb4aca0 100644
--- a/src/main/java/overrun/marshal/StrHelper.java
+++ b/src/main/java/overrun/marshal/StrHelper.java
@@ -25,7 +25,7 @@
  * @author squid233
  * @since 0.1.0
  */
-@Deprecated(since = "0.1.0")
+@Deprecated(since = "0.1.0", forRemoval = true)
 public final class StrHelper {
     private static final SequenceLayout STR_LAYOUT = MemoryLayout.sequenceLayout(Integer.MAX_VALUE - 8, ValueLayout.JAVA_BYTE);
 
diff --git a/src/main/java/overrun/marshal/Unmarshal.java b/src/main/java/overrun/marshal/Unmarshal.java
index c3399de..b2bf7f4 100644
--- a/src/main/java/overrun/marshal/Unmarshal.java
+++ b/src/main/java/overrun/marshal/Unmarshal.java
@@ -21,11 +21,13 @@
 import java.lang.foreign.AddressLayout;
 import java.lang.foreign.MemoryLayout;
 import java.lang.foreign.MemorySegment;
-import java.lang.foreign.ValueLayout;
+import java.lang.invoke.VarHandle;
 import java.nio.charset.Charset;
 import java.util.function.Function;
 import java.util.function.IntFunction;
 
+import static java.lang.foreign.ValueLayout.*;
+
 /**
  * C-to-Java helper.
  *
@@ -33,6 +35,11 @@
  * @since 0.1.0
  */
 public final class Unmarshal {
+    private static final AddressLayout STRING_LAYOUT = ADDRESS.withTargetLayout(
+        MemoryLayout.sequenceLayout(Integer.MAX_VALUE - 8, JAVA_BYTE)
+    );
+    private static final VarHandle vh_stringArray = Marshal.arrayVarHandle(STRING_LAYOUT);
+
     private Unmarshal() {
     }
 
@@ -44,7 +51,7 @@ public static String unmarshalAsString(MemorySegment segment, Charset charset) {
         return segment.getString(0L, charset);
     }
 
-    public static boolean[] unmarshal(ValueLayout.OfBoolean elementLayout, MemorySegment segment) {
+    public static boolean[] unmarshal(OfBoolean elementLayout, MemorySegment segment) {
         final boolean[] arr = new boolean[checkArraySize(boolean[].class.getSimpleName(), segment.byteSize(), (int) elementLayout.byteSize())];
         for (int i = 0, l = arr.length; i < l; i++) {
             arr[i] = (boolean) Marshal.vh_booleanArray.get(segment, (long) i);
@@ -52,31 +59,31 @@ public static boolean[] unmarshal(ValueLayout.OfBoolean elementLayout, MemorySeg
         return arr;
     }
 
-    public static char[] unmarshal(ValueLayout.OfChar elementLayout, MemorySegment segment) {
+    public static char[] unmarshal(OfChar elementLayout, MemorySegment segment) {
         return segment.toArray(elementLayout);
     }
 
-    public static byte[] unmarshal(ValueLayout.OfByte elementLayout, MemorySegment segment) {
+    public static byte[] unmarshal(OfByte elementLayout, MemorySegment segment) {
         return segment.toArray(elementLayout);
     }
 
-    public static short[] unmarshal(ValueLayout.OfShort elementLayout, MemorySegment segment) {
+    public static short[] unmarshal(OfShort elementLayout, MemorySegment segment) {
         return segment.toArray(elementLayout);
     }
 
-    public static int[] unmarshal(ValueLayout.OfInt elementLayout, MemorySegment segment) {
+    public static int[] unmarshal(OfInt elementLayout, MemorySegment segment) {
         return segment.toArray(elementLayout);
     }
 
-    public static long[] unmarshal(ValueLayout.OfLong elementLayout, MemorySegment segment) {
+    public static long[] unmarshal(OfLong elementLayout, MemorySegment segment) {
         return segment.toArray(elementLayout);
     }
 
-    public static float[] unmarshal(ValueLayout.OfFloat elementLayout, MemorySegment segment) {
+    public static float[] unmarshal(OfFloat elementLayout, MemorySegment segment) {
         return segment.toArray(elementLayout);
     }
 
-    public static double[] unmarshal(ValueLayout.OfDouble elementLayout, MemorySegment segment) {
+    public static double[] unmarshal(OfDouble elementLayout, MemorySegment segment) {
         return segment.toArray(elementLayout);
     }
 
@@ -89,14 +96,14 @@ public static MemorySegment[] unmarshal(AddressLayout elementLayout, MemorySegme
     }
 
     public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment) {
-        return unmarshal(elementLayout, segment, String[]::new, s -> s.reinterpret(Long.MAX_VALUE).getString(0L));
+        return unmarshal(elementLayout, segment, String[]::new, s -> s.get(STRING_LAYOUT, 0L).getString(0L));
     }
 
     public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment, Charset charset) {
-        return unmarshal(elementLayout, segment, String[]::new, s -> s.reinterpret(Long.MAX_VALUE).getString(0L, charset));
+        return unmarshal(elementLayout, segment, String[]::new, s -> s.get(STRING_LAYOUT, 0L).getString(0L, charset));
     }
 
-    public static  T[] unmarshalAsCEnum(ValueLayout.OfInt elementLayout, MemorySegment segment, IntFunction generator, IntFunction function) {
+    public static  T[] unmarshalAsCEnum(OfInt elementLayout, MemorySegment segment, IntFunction generator, IntFunction function) {
         return segment.elements(elementLayout).mapToInt(s -> s.get(elementLayout, 0L)).mapToObj(function).toArray(generator);
     }
 
@@ -110,4 +117,60 @@ private static int checkArraySize(String typeName, long byteSize, int elemSize)
         }
         return (int) arraySize;
     }
+
+    public static void copy(MemorySegment src, boolean[] dst) {
+        if (dst == null) return;
+        for (int i = 0; i < dst.length; i++) {
+            dst[i] = (boolean) Marshal.vh_booleanArray.get(src, (long) i);
+        }
+    }
+
+    public static void copy(MemorySegment src, char[] dst) {
+        if (dst == null) return;
+        MemorySegment.copy(src, JAVA_CHAR, 0L, dst, 0, dst.length);
+    }
+
+    public static void copy(MemorySegment src, byte[] dst) {
+        if (dst == null) return;
+        MemorySegment.copy(src, JAVA_BYTE, 0L, dst, 0, dst.length);
+    }
+
+    public static void copy(MemorySegment src, short[] dst) {
+        if (dst == null) return;
+        MemorySegment.copy(src, JAVA_SHORT, 0L, dst, 0, dst.length);
+    }
+
+    public static void copy(MemorySegment src, int[] dst) {
+        if (dst == null) return;
+        MemorySegment.copy(src, JAVA_INT, 0L, dst, 0, dst.length);
+    }
+
+    public static void copy(MemorySegment src, long[] dst) {
+        if (dst == null) return;
+        MemorySegment.copy(src, JAVA_LONG, 0L, dst, 0, dst.length);
+    }
+
+    public static void copy(MemorySegment src, float[] dst) {
+        if (dst == null) return;
+        MemorySegment.copy(src, JAVA_FLOAT, 0L, dst, 0, dst.length);
+    }
+
+    public static void copy(MemorySegment src, double[] dst) {
+        if (dst == null) return;
+        MemorySegment.copy(src, JAVA_DOUBLE, 0L, dst, 0, dst.length);
+    }
+
+    public static void copy(MemorySegment src, String[] dst) {
+        if (dst == null) return;
+        for (int i = 0; i < dst.length; i++) {
+            dst[i] = ((MemorySegment) vh_stringArray.get(src, (long) i)).getString(0L);
+        }
+    }
+
+    public static void copy(MemorySegment src, String[] dst, Charset charset) {
+        if (dst == null) return;
+        for (int i = 0; i < dst.length; i++) {
+            dst[i] = ((MemorySegment) vh_stringArray.get(src, (long) i)).getString(0L, charset);
+        }
+    }
 }
diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java
index 1aa0472..f1c6031 100644
--- a/src/main/java/overrun/marshal/gen2/DowncallData.java
+++ b/src/main/java/overrun/marshal/gen2/DowncallData.java
@@ -230,7 +230,6 @@ private void processDowncallType(TypeElement typeElement, String simpleClassName
 
         // methods
         final var fieldNames = fieldDataList.stream().map(FieldData::name).toList();
-        final List addedMethodHandleInvoker = new ArrayList<>(methodHandleDataMap.size());
         skipAnnotated(methods).forEach(executableElement -> {
             final MethodHandleData methodHandleData = methodHandleDataMap.get(getMethodEntrypoint(executableElement));
             if (methodHandleData == null) {
@@ -571,37 +570,24 @@ private Spec copyRefResult(
         final TypeMirror typeMirror = element.asType();
         final String name = element.getSimpleName().toString();
         final Spec spec;
-        if (isBooleanArray(typeMirror)) {
-            spec = new InvokeSpec(importData.simplifyOrImport(BoolHelper.class), "copy")
+        if (isBooleanArray(typeMirror) || isPrimitiveArray(typeMirror)) {
+            spec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "copy")
                 .addArgument(refFunction.apply(name))
                 .addArgument(name);
-        } else if (isPrimitiveArray(typeMirror)) {
-            spec = new InvokeSpec(importData.simplifyOrImport(MemorySegment.class), "copy")
-                .addArgument(refFunction.apply(name))
-                .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(typeMirror))
-                    .orElseThrow()
-                    .apply(importData))
-                .addArgument(getConstExp(0L))
-                .addArgument(name)
-                .addArgument(getConstExp(0))
-                .addArgument(Spec.accessSpec(name, "length"));
         } else if (isStringArray(typeMirror)) {
-            spec = new InvokeSpec(importData.simplifyOrImport(StrHelper.class), "copy")
+            final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "copy")
                 .addArgument(refFunction.apply(name))
-                .addArgument(name)
-                .addArgument(createCharset(getCharset(element)).apply(importData));
+                .addArgument(name);
+            if (element.getAnnotation(StrCharset.class) != null) {
+                invokeSpec.addArgument(createCharset(getCharset(element)).apply(importData));
+            }
+            spec = invokeSpec;
         } else {
             processingEnv.getMessager().printWarning("Using @Ref on unknown type %s".formatted(typeMirror), element);
             spec = null;
         }
 
-        final Spec statement = spec != null ? Spec.statement(spec) : Spec.literal("");
-        if (element.getAnnotation(NullableRef.class) != null) {
-            final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(name));
-            ifStatement.addStatement(statement);
-            return ifStatement;
-        }
-        return statement;
+        return spec != null ? Spec.statement(spec) : Spec.literal("");
     }
 
     private Spec wrapReturnValue(
@@ -615,19 +601,24 @@ private Spec wrapReturnValue(
             spec = new ConstructSpec(structRef.value()).addArgument(resultSpec);
         } else {
             final TypeMirror typeMirror = executableElement.getReturnType();
-            if (isBooleanArray(typeMirror)) {
-                spec = new InvokeSpec(importData.simplifyOrImport(BoolHelper.class), "toArray").addArgument(resultSpec);
-            } else if (isPrimitiveArray(typeMirror)) {
-                spec = new InvokeSpec(resultSpec, "toArray")
+            if (isBooleanArray(typeMirror) ||
+                isPrimitiveArray(typeMirror)) {
+                spec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "unmarshal")
                     .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(typeMirror))
                         .orElseThrow()
-                        .apply(importData));
+                        .apply(importData))
+                    .addArgument(resultSpec);
             } else if (isStringArray(typeMirror)) {
-                spec = new InvokeSpec(importData.simplifyOrImport(StrHelper.class), "toArray")
-                    .addArgument(resultSpec)
-                    .addArgument(createCharset(getCharset(executableElement)).apply(importData));
+                final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "unmarshalAsString")
+                    .addArgument(TypeUse.valueLayout(MemorySegment.class).orElseThrow().apply(importData))
+                    .addArgument(resultSpec);
+                spec = invokeSpec;
+                if (executableElement.getAnnotation(StrCharset.class) != null) {
+                    invokeSpec.addArgument(createCharset(getCharset(executableElement)).apply(importData));
+                }
             } else if (isSameClass(typeMirror, String.class)) {
-                final InvokeSpec invokeSpec = new InvokeSpec(resultSpec, "getString").addArgument(getConstExp(0L));
+                final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "unmarshalAsString")
+                    .addArgument(resultSpec);
                 if (executableElement.getAnnotation(StrCharset.class) != null) {
                     invokeSpec.addArgument(createCharset(getCharset(executableElement)).apply(importData));
                 }
@@ -694,49 +685,27 @@ private Spec wrapParameter(
         boolean usingRef,
         Function refFunction
     ) {
-        final Spec spec;
         final String name = element.getSimpleName().toString();
-        if (element.getAnnotation(StructRef.class) != null) {
-            spec = new InvokeSpec(name, "segment");
-        } else if (usingRef && element.getAnnotation(Ref.class) != null) {
+        final Spec spec;
+        if (usingRef && element.getAnnotation(Ref.class) != null) {
             spec = Spec.literal(refFunction.apply(name));
         } else {
             final TypeMirror typeMirror = element.asType();
-            if (isBooleanArray(typeMirror)) {
-                spec = new InvokeSpec(importData.simplifyOrImport(BoolHelper.class), "of")
-                    .addArgument(allocatorParameter)
-                    .addArgument(name);
-            } else if (isPrimitiveArray(typeMirror)) {
-                spec = new InvokeSpec(allocatorParameter, "allocateFrom")
-                    .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(typeMirror))
-                        .orElseThrow()
-                        .apply(importData))
-                    .addArgument(name);
-            } else if (isStringArray(typeMirror)) {
-                spec = new InvokeSpec(importData.simplifyOrImport(StrHelper.class), "of")
-                    .addArgument(allocatorParameter)
-                    .addArgument(name)
-                    .addArgument(createCharset(getCharset(element)).apply(importData));
-            } else if (isSameClass(typeMirror, String.class)) {
-                final InvokeSpec invokeSpec = new InvokeSpec(allocatorParameter, "allocateFrom")
-                    .addArgument(name);
+            if (shouldMarshal(processingEnv, element)) {
+                final InvokeSpec invokeMarshal = new InvokeSpec(importData.simplifyOrImport(Marshal.class), "marshal");
+                if (requireAllocator(processingEnv, typeMirror)) {
+                    invokeMarshal.addArgument(allocatorParameter);
+                }
+                invokeMarshal.addArgument(name);
                 if (element.getAnnotation(StrCharset.class) != null) {
-                    invokeSpec.addArgument(createCharset(getCharset(element)).apply(importData));
+                    invokeMarshal.addArgument(createCharset(getCharset(element)).apply(importData));
                 }
-                spec = invokeSpec;
-            } else if (isAExtendsB(processingEnv, typeMirror, Upcall.class)) {
-                spec = new InvokeSpec(name, "stub").addArgument(allocatorParameter);
-            } else if (isAExtendsB(processingEnv, typeMirror, CEnum.class)) {
-                spec = new InvokeSpec(name, "value");
+                spec = invokeMarshal;
             } else {
                 spec = Spec.literal(name);
             }
         }
-        return element.getAnnotation(NullableRef.class) != null ?
-            Spec.ternaryOp(Spec.notNullSpec(name),
-                spec,
-                Spec.accessSpec(importData.simplifyOrImport(MemorySegment.class), "NULL")) :
-            spec;
+        return spec;
     }
 
     private boolean useAllocator(TypeMirror typeMirror) {
diff --git a/src/main/java/overrun/marshal/internal/Util.java b/src/main/java/overrun/marshal/internal/Util.java
index 33e80de..ca84105 100644
--- a/src/main/java/overrun/marshal/internal/Util.java
+++ b/src/main/java/overrun/marshal/internal/Util.java
@@ -17,8 +17,10 @@
 package overrun.marshal.internal;
 
 import overrun.marshal.Upcall;
+import overrun.marshal.gen.struct.StructRef;
 
 import javax.annotation.processing.ProcessingEnvironment;
+import javax.lang.model.element.Element;
 import javax.lang.model.element.ExecutableElement;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.ArrayType;
@@ -27,6 +29,7 @@
 import javax.lang.model.util.ElementFilter;
 import java.lang.annotation.Annotation;
 import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
 import java.util.Optional;
 import java.util.function.Predicate;
 
@@ -315,24 +318,36 @@ public static boolean isSameClass(TypeMirror typeMirror, Class aClass) {
     }
 
     /**
-     * {@return requireArena}
+     * {@return shouldMarshal}
      *
-     * @param env        env
-     * @param typeMirror typeMirror
+     * @param env     env
+     * @param element element
      */
-    public static boolean requireArena(ProcessingEnvironment env, TypeMirror typeMirror) {
-        return isAExtendsB(env, typeMirror, Upcall.class);
+    public static boolean shouldMarshal(ProcessingEnvironment env, Element element) {
+        if (element.getAnnotation(StructRef.class) != null) {
+            return true;
+        }
+        final TypeMirror typeMirror = element.asType();
+        final TypeKind typeKind = typeMirror.getKind();
+        final boolean shouldNotMarshal =
+            typeKind.isPrimitive() ||
+            (typeKind == TypeKind.DECLARED &&
+             (isSameClass(typeMirror, MemorySegment.class) ||
+             isAExtendsB(env, typeMirror, SegmentAllocator.class)));
+        return !shouldNotMarshal;
     }
 
     /**
      * {@return requireAllocator}
      *
+     * @param env        env
      * @param typeMirror typeMirror
      */
-    public static boolean requireAllocator(TypeMirror typeMirror) {
+    public static boolean requireAllocator(ProcessingEnvironment env, TypeMirror typeMirror) {
         return switch (typeMirror.getKind()) {
             case ARRAY -> true;
-            case DECLARED -> isSameClass(typeMirror, String.class);
+            case DECLARED -> isSameClass(typeMirror, String.class) ||
+                             isAExtendsB(env, typeMirror, Upcall.class);
             default -> false;
         };
     }

From c2baf57dd68352829aac4fd451c2089d2ef26dc3 Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Thu, 18 Jan 2024 12:53:22 +0800
Subject: [PATCH 16/28] Update struct generator

---
 README.md                                     |  7 +---
 .../overrun/marshal/test/CStructTest.java     |  2 ++
 .../java/overrun/marshal/StructProcessor.java |  1 +
 .../marshal/gen2/struct/StructData.java       | 35 +++++++++++++++----
 4 files changed, 33 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md
index c7833de..9a8b4f3 100644
--- a/README.md
+++ b/README.md
@@ -80,14 +80,9 @@ class Main {
                 MemorySegment.NULL,
                 MemorySegment.NULL);
 
-            // ref
-            // 1. by array
+            // ref by array
             int[] ax = {0}, ay = {0};
             glfwGetWindowPos(windowHandle, ax, ay);
-            // 2. by segment
-            var sx = arena.allocate(ValueLayout.JAVA_INT),
-                sy = arena.allocate(ValueLayout.JAVA_INT);
-            glfwGetWindowPos(windowHandle, sx, sy);
         }
     }
 }
diff --git a/demo/src/main/java/overrun/marshal/test/CStructTest.java b/demo/src/main/java/overrun/marshal/test/CStructTest.java
index 58cbae4..c782711 100644
--- a/demo/src/main/java/overrun/marshal/test/CStructTest.java
+++ b/demo/src/main/java/overrun/marshal/test/CStructTest.java
@@ -41,6 +41,8 @@ final class CStructTest {
     @Const
     int y;
     int index;
+    @Padding(4)
+    int padding0;
     int[] segmentAllocator;
     GLFWErrorCallback arena;
     /**
diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java
index 6c3c734..c0b4317 100644
--- a/src/main/java/overrun/marshal/StructProcessor.java
+++ b/src/main/java/overrun/marshal/StructProcessor.java
@@ -69,6 +69,7 @@ private void processClasses(RoundEnvironment roundEnv) {
         ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Struct.class)).forEach(e -> {
             try {
                 new StructData(processingEnv).generate(e);
+//                writeFile(e, ElementFilter.fieldsIn(e.getEnclosedElements()));
             } catch (IOException ex) {
                 printStackTrace(ex);
             }
diff --git a/src/main/java/overrun/marshal/gen2/struct/StructData.java b/src/main/java/overrun/marshal/gen2/struct/StructData.java
index 23d69d7..f01f6e1 100644
--- a/src/main/java/overrun/marshal/gen2/struct/StructData.java
+++ b/src/main/java/overrun/marshal/gen2/struct/StructData.java
@@ -107,6 +107,10 @@ private void processStructType(TypeElement typeElement, String simpleClassName)
         addConstructors(simpleClassName, memberDataMap);
         addAllocators(simpleClassName);
         addSlices(simpleClassName);
+        addGetters(ignorePadding, memberDataMap);
+        if (structConst == null) {
+            addSetters();
+        }
         addStructImpl();
     }
 
@@ -162,10 +166,18 @@ private void addLayout(String simpleClassName, List fields, Lis
                             final Sized sized = element.getAnnotation(Sized.class);
                             final TypeMirror type = element.asType();
 
-                            final InvokeSpec valueLayout = new InvokeSpec(TypeUse.valueLayout(processingEnv, type)
-                                .orElseThrow()
-                                .apply(importData), "withName"
-                            ).addArgument(getConstExp(element.getSimpleName().toString()));
+                            final InvokeSpec valueLayout;
+                            if (element.getAnnotation(StructRef.class) != null) {
+                                valueLayout = new InvokeSpec(TypeUse.valueLayout(MemorySegment.class)
+                                    .orElseThrow()
+                                    .apply(importData), "withName");
+                            } else {
+                                valueLayout = new InvokeSpec(TypeUse.valueLayout(processingEnv, type)
+                                    .orElseThrow()
+                                    .apply(importData), "withName"
+                                );
+                            }
+                            valueLayout.addArgument(getConstExp(element.getSimpleName().toString()));
                             final Spec finalSpec;
                             if (sizedSeg != null) {
                                 finalSpec = new InvokeSpec(valueLayout, "withTargetLayout")
@@ -388,8 +400,12 @@ private void addSlices(String simpleClassName) {
             ),
             List.of(_ -> Spec.returnStatement(new ConstructSpec(simpleClassName)
                 .addArgument(new InvokeSpec(segmentName, "asSlice")
-                    .addArgument(Spec.operatorSpec("*", Spec.literal(PARAMETER_INDEX_NAME), new InvokeSpec(LAYOUT_NAME, "byteSize")))
-                    .addArgument(Spec.operatorSpec("*", Spec.literal(PARAMETER_COUNT_NAME), new InvokeSpec(LAYOUT_NAME, "byteSize")))
+                    .addArgument(new InvokeSpec(LAYOUT_NAME, "scale")
+                        .addArgument(getConstExp(0L))
+                        .addArgument(PARAMETER_INDEX_NAME))
+                    .addArgument(new InvokeSpec(LAYOUT_NAME, "scale")
+                        .addArgument(getConstExp(0L))
+                        .addArgument(PARAMETER_COUNT_NAME))
                     .addArgument(new InvokeSpec(LAYOUT_NAME, "byteAlignment")))
                 .addArgument(Spec.literal(PARAMETER_COUNT_NAME))))
         ));
@@ -415,6 +431,13 @@ private void addSlices(String simpleClassName) {
         ));
     }
 
+    private void addGetters(List ignorePadding, Map memberDataMap) {
+        // indexed
+    }
+
+    private void addSetters() {
+    }
+
     private void addStructImpl() {
         addStructImpl(TypeUse.of(MemorySegment.class), "segment", Spec.literal(segmentName));
         addStructImpl(TypeUse.of(StructLayout.class), "layout", Spec.literal(LAYOUT_NAME));

From 36f9e024796f56873e4b2019c69c97ed584835b6 Mon Sep 17 00:00:00 2001
From: squid233 <60126026+squid233@users.noreply.github.com>
Date: Mon, 22 Jan 2024 19:24:36 +0800
Subject: [PATCH 17/28] Downcall: use Class-File API

---
 .github/workflows/gradle.yml                  |   1 -
 build.gradle.kts                              |   9 +
 gradle.properties                             |   2 +-
 src/main/java/overrun/marshal/Checks.java     |   8 +-
 src/main/java/overrun/marshal/Downcall.java   | 780 ++++++++++++++++++
 .../overrun/marshal/DowncallProcessor.java    |  12 +-
 .../java/overrun/marshal/MemoryStack.java     |   4 +-
 src/main/java/overrun/marshal/Unmarshal.java  |   6 -
 src/main/java/overrun/marshal/Upcall.java     |   2 +-
 src/main/java/overrun/marshal/gen/Access.java |   1 +
 src/main/java/overrun/marshal/gen/CEnum.java  |   2 +-
 .../java/overrun/marshal/gen/Critical.java    |   2 +-
 src/main/java/overrun/marshal/gen/Custom.java |   1 +
 .../java/overrun/marshal/gen/Default.java     |   1 +
 .../java/overrun/marshal/gen/Downcall.java    |   1 +
 .../java/overrun/marshal/gen/Entrypoint.java  |   2 +-
 src/main/java/overrun/marshal/gen/Loader.java |   7 +-
 src/main/java/overrun/marshal/gen/Ref.java    |   2 +-
 src/main/java/overrun/marshal/gen/Sized.java  |   2 +-
 src/main/java/overrun/marshal/gen/Skip.java   |   2 +-
 .../java/overrun/marshal/gen/StrCharset.java  |   2 +-
 .../overrun/marshal/gen/struct/ByValue.java   |   2 +-
 .../overrun/marshal/gen2/DowncallData.java    |   1 +
 .../marshal/gen2/DowncallMethodData.java      |  36 +
 .../javax.annotation.processing.Processor     |   1 -
 .../marshal/test/DowncallProvider.java        |  92 +++
 .../overrun/marshal/test/DowncallTest.java    | 118 +++
 .../java/overrun/marshal/test/IDowncall.java  |  54 ++
 .../java/overrun/marshal/test/MyEnum.java     |  41 +
 29 files changed, 1160 insertions(+), 34 deletions(-)
 create mode 100644 src/main/java/overrun/marshal/Downcall.java
 create mode 100644 src/main/java/overrun/marshal/gen2/DowncallMethodData.java
 create mode 100644 src/test/java/overrun/marshal/test/DowncallProvider.java
 create mode 100644 src/test/java/overrun/marshal/test/DowncallTest.java
 create mode 100644 src/test/java/overrun/marshal/test/IDowncall.java
 create mode 100644 src/test/java/overrun/marshal/test/MyEnum.java

diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 0f0f8f0..a50ca8d 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -35,7 +35,6 @@ jobs:
         with:
           arguments: build --no-daemon
       - name: Upload build reports
-        if: failure()
         uses: actions/upload-artifact@v3
         with:
           name: build-reports
diff --git a/build.gradle.kts b/build.gradle.kts
index a54ff6e..0679614 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -128,6 +128,15 @@ allprojects {
     }
 }
 
+dependencies {
+    testImplementation(platform("org.junit:junit-bom:5.10.1"))
+    testImplementation("org.junit.jupiter:junit-jupiter")
+}
+
+tasks.withType {
+    useJUnitPlatform()
+}
+
 tasks.withType {
     archiveBaseName = projArtifactId
     from(rootProject.file(projLicenseFileName)).rename(
diff --git a/gradle.properties b/gradle.properties
index ee74f53..7a9e125 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -28,7 +28,7 @@ orgUrl=https://over-run.github.io/
 
 # JDK Options
 jdkVersion=22
-jdkEnablePreview=false
+jdkEnablePreview=true
 # javadoc link of JDK early access build
 # https://download.java.net/java/early_access/$jdkEarlyAccessDoc/docs/api/
 # Uncomment it if you need to use EA build of JDK.
diff --git a/src/main/java/overrun/marshal/Checks.java b/src/main/java/overrun/marshal/Checks.java
index b1fdc3f..c4f3cb5 100644
--- a/src/main/java/overrun/marshal/Checks.java
+++ b/src/main/java/overrun/marshal/Checks.java
@@ -30,11 +30,11 @@ private Checks() {
      * Checks the array size.
      *
      * @param expected the expected size
-     * @param got      the got size
+     * @param actual   the actual size
      */
-    public static void checkArraySize(int expected, int got) {
-        if (Configurations.CHECK_ARRAY_SIZE.get() && expected != got) {
-            throw new IllegalArgumentException("Expected array of size " + expected + ", got " + got);
+    public static void checkArraySize(int expected, int actual) {
+        if (Configurations.CHECK_ARRAY_SIZE.get() && expected != actual) {
+            throw new IllegalArgumentException("Expected array of size " + expected + ", got " + actual);
         }
     }
 }
diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java
new file mode 100644
index 0000000..3aa8073
--- /dev/null
+++ b/src/main/java/overrun/marshal/Downcall.java
@@ -0,0 +1,780 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2024 Overrun Organization
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ */
+
+package overrun.marshal;
+
+import overrun.marshal.gen.*;
+import overrun.marshal.gen.struct.ByValue;
+import overrun.marshal.gen2.DowncallMethodData;
+
+import java.lang.annotation.Annotation;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.CodeBuilder;
+import java.lang.classfile.Opcode;
+import java.lang.classfile.TypeKind;
+import java.lang.constant.ClassDesc;
+import java.lang.constant.MethodTypeDesc;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
+import java.lang.foreign.SymbolLookup;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Parameter;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import static java.lang.classfile.ClassFile.*;
+import static java.lang.constant.ConstantDescs.*;
+
+/**
+ * Downcall library loader.
+ *
+ * @author squid233
+ * @since 0.1.0
+ */
+public final class Downcall {
+    private static final StackWalker STACK_WALKER = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+    private static final ClassDesc CD_Addressable = ClassDesc.of("overrun.marshal.Addressable");
+    private static final ClassDesc CD_AddressLayout = ClassDesc.of("java.lang.foreign.AddressLayout");
+    private static final ClassDesc CD_Arena = ClassDesc.of("java.lang.foreign.Arena");
+    private static final ClassDesc CD_CEnum = ClassDesc.of("overrun.marshal.gen.CEnum");
+    private static final ClassDesc CD_Charset = ClassDesc.of("java.nio.charset.Charset");
+    private static final ClassDesc CD_Checks = ClassDesc.of("overrun.marshal.Checks");
+    private static final ClassDesc CD_FunctionDescriptor = ClassDesc.of("java.lang.foreign.FunctionDescriptor");
+    private static final ClassDesc CD_IllegalStateException = ClassDesc.of("java.lang.IllegalStateException");
+    private static final ClassDesc CD_Linker = ClassDesc.of("java.lang.foreign.Linker");
+    private static final ClassDesc CD_Linker_Option = CD_Linker.nested("Option");
+    private static final ClassDesc CD_Marshal = ClassDesc.of("overrun.marshal.Marshal");
+    private static final ClassDesc CD_MemoryLayout = ClassDesc.of("java.lang.foreign.MemoryLayout");
+    private static final ClassDesc CD_MemorySegment = ClassDesc.of("java.lang.foreign.MemorySegment");
+    private static final ClassDesc CD_MemoryStack = ClassDesc.of("overrun.marshal.MemoryStack");
+    private static final ClassDesc CD_Optional = ClassDesc.of("java.util.Optional");
+    private static final ClassDesc CD_SegmentAllocator = ClassDesc.of("java.lang.foreign.SegmentAllocator");
+    private static final ClassDesc CD_SequenceLayout = ClassDesc.of("java.lang.foreign.SequenceLayout");
+    private static final ClassDesc CD_StandardCharsets = ClassDesc.of("java.nio.charset.StandardCharsets");
+    private static final ClassDesc CD_SymbolLookup = ClassDesc.of("java.lang.foreign.SymbolLookup");
+    private static final ClassDesc CD_Unmarshal = ClassDesc.of("overrun.marshal.Unmarshal");
+    private static final ClassDesc CD_Upcall = ClassDesc.of("overrun.marshal.Upcall");
+    private static final ClassDesc CD_ValueLayout = ClassDesc.of("java.lang.foreign.ValueLayout");
+    private static final ClassDesc CD_ValueLayout_OfBoolean = CD_ValueLayout.nested("OfBoolean");
+    private static final ClassDesc CD_ValueLayout_OfChar = CD_ValueLayout.nested("OfChar");
+    private static final ClassDesc CD_ValueLayout_OfByte = CD_ValueLayout.nested("OfByte");
+    private static final ClassDesc CD_ValueLayout_OfShort = CD_ValueLayout.nested("OfShort");
+    private static final ClassDesc CD_ValueLayout_OfInt = CD_ValueLayout.nested("OfInt");
+    private static final ClassDesc CD_ValueLayout_OfLong = CD_ValueLayout.nested("OfLong");
+    private static final ClassDesc CD_ValueLayout_OfFloat = CD_ValueLayout.nested("OfFloat");
+    private static final ClassDesc CD_ValueLayout_OfDouble = CD_ValueLayout.nested("OfDouble");
+    private static final MethodTypeDesc MTD_marshalStringCharset = MethodTypeDesc.of(CD_MemorySegment,
+        CD_SegmentAllocator,
+        CD_String,
+        CD_Charset);
+    private static final MethodTypeDesc MTD_marshalString = MethodTypeDesc.of(CD_MemorySegment,
+        CD_SegmentAllocator,
+        CD_String);
+    private static final MethodTypeDesc MTD_marshalStringCharsetArray = MethodTypeDesc.of(CD_MemorySegment,
+        CD_SegmentAllocator,
+        CD_String.arrayType(),
+        CD_Charset);
+
+    private Downcall() {
+    }
+
+    private static void convertToValueLayout(CodeBuilder codeBuilder, Class aClass) {
+        if (aClass == boolean.class) codeBuilder.getstatic(CD_ValueLayout, "JAVA_BOOLEAN", CD_ValueLayout_OfBoolean);
+        else if (aClass == char.class) codeBuilder.getstatic(CD_ValueLayout, "JAVA_CHAR", CD_ValueLayout_OfChar);
+        else if (aClass == byte.class) codeBuilder.getstatic(CD_ValueLayout, "JAVA_BYTE", CD_ValueLayout_OfByte);
+        else if (aClass == short.class) codeBuilder.getstatic(CD_ValueLayout, "JAVA_SHORT", CD_ValueLayout_OfShort);
+        else if (aClass == int.class || CEnum.class.isAssignableFrom(aClass))
+            codeBuilder.getstatic(CD_ValueLayout, "JAVA_INT", CD_ValueLayout_OfInt);
+        else if (aClass == long.class) codeBuilder.getstatic(CD_ValueLayout, "JAVA_LONG", CD_ValueLayout_OfLong);
+        else if (aClass == float.class) codeBuilder.getstatic(CD_ValueLayout, "JAVA_FLOAT", CD_ValueLayout_OfFloat);
+        else if (aClass == double.class) codeBuilder.getstatic(CD_ValueLayout, "JAVA_DOUBLE", CD_ValueLayout_OfDouble);
+        else codeBuilder.getstatic(CD_ValueLayout, "ADDRESS", CD_AddressLayout);
+    }
+
+    private static boolean hasCharset(StrCharset strCharset) {
+        return strCharset != null && !strCharset.value().isBlank();
+    }
+
+    private static String getCharset(AnnotatedElement element) {
+        final StrCharset strCharset = element.getDeclaredAnnotation(StrCharset.class);
+        return hasCharset(strCharset) ? strCharset.value() : null;
+    }
+
+    private static void getCharset(CodeBuilder codeBuilder, String charset) {
+        final String upperCase = charset.toUpperCase(Locale.ROOT);
+        switch (upperCase) {
+            case "UTF-8", "ISO-8859-1", "US-ASCII",
+                "UTF-16", "UTF-16BE", "UTF-16LE",
+                "UTF-32", "UTF-32BE", "UTF-32LE" ->
+                codeBuilder.getstatic(CD_StandardCharsets, upperCase.replace('-', '_'), CD_Charset);
+            case "UTF_8", "ISO_8859_1", "US_ASCII",
+                "UTF_16", "UTF_16BE", "UTF_16LE",
+                "UTF_32", "UTF_32BE", "UTF_32LE" -> codeBuilder.getstatic(CD_StandardCharsets, upperCase, CD_Charset);
+            default -> codeBuilder.ldc(charset)
+                .invokestatic(CD_Charset, "forName", MethodTypeDesc.of(CD_Charset, CD_String));
+        }
+    }
+
+    private static boolean getCharset(CodeBuilder codeBuilder, AnnotatedElement element) {
+        final String charset = getCharset(element);
+        if (charset != null) {
+            getCharset(codeBuilder, charset);
+            return true;
+        }
+        return false;
+    }
+
+    private static void tryAddLayout(CodeBuilder codeBuilder, Class aClass) {
+        if (aClass.isArray()) {
+            final Class componentType = aClass.getComponentType();
+            if (componentType == String.class) {
+                convertToValueLayout(codeBuilder, MemorySegment.class);
+            } else if (componentType.isPrimitive()) {
+                convertToValueLayout(codeBuilder, componentType);
+            }
+        }
+    }
+
+    private static String getMethodEntrypoint(Method method) {
+        final Entrypoint entrypoint = method.getDeclaredAnnotation(Entrypoint.class);
+        return (entrypoint == null || entrypoint.value().isBlank()) ?
+            method.getName() :
+            entrypoint.value();
+    }
+
+    private static ClassDesc convertToValueLayoutCD(Class aClass) {
+        if (aClass == boolean.class) return CD_ValueLayout_OfBoolean;
+        if (aClass == char.class) return CD_ValueLayout_OfChar;
+        if (aClass == byte.class) return CD_ValueLayout_OfByte;
+        if (aClass == short.class) return CD_ValueLayout_OfShort;
+        if (aClass == int.class || CEnum.class.isAssignableFrom(aClass)) return CD_ValueLayout_OfInt;
+        if (aClass == long.class) return CD_ValueLayout_OfLong;
+        if (aClass == float.class) return CD_ValueLayout_OfFloat;
+        if (aClass == double.class) return CD_ValueLayout_OfDouble;
+        return CD_AddressLayout;
+    }
+
+    private static ClassDesc convertToDowncallCD(Class aClass) {
+        if (aClass.isPrimitive()) return aClass.describeConstable().orElseThrow();
+        if (CEnum.class.isAssignableFrom(aClass)) return CD_int;
+        if (SegmentAllocator.class.isAssignableFrom(aClass)) return CD_SegmentAllocator;
+        return CD_MemorySegment;
+    }
+
+    private static ClassDesc convertToMarshalCD(Class aClass) {
+        if (aClass.isPrimitive() ||
+            aClass == String.class) return aClass.describeConstable().orElseThrow();
+        if (Addressable.class.isAssignableFrom(aClass)) return CD_Addressable;
+        if (CEnum.class.isAssignableFrom(aClass)) return CD_CEnum;
+        if (Upcall.class.isAssignableFrom(aClass)) return CD_Upcall;
+        return CD_MemorySegment;
+    }
+
+    private static boolean shouldStoreResult(Class aClass, List parameters) {
+        return Upcall.class.isAssignableFrom(aClass) ||
+               parameters.stream().anyMatch(parameter -> parameter.getDeclaredAnnotation(Ref.class) != null);
+    }
+
+    private static boolean requireAllocator(Class aClass) {
+        return !aClass.isPrimitive() &&
+               (aClass == String.class ||
+                aClass.isArray() ||
+                Upcall.class.isAssignableFrom(aClass));
+    }
+
+    private static Method findWrapper(
+        Class aClass,
+        Class annotation,
+        Predicate predicate) {
+        return Arrays.stream(aClass.getDeclaredMethods())
+            .filter(method -> method.getDeclaredAnnotation(annotation) != null)
+            .filter(predicate)
+            .findFirst()
+            .orElseThrow(() -> new IllegalStateException(STR.
+                "Couldn't find wrapper method in \{aClass}; mark it with @\{annotation.getSimpleName()}"));
+    }
+
+    private static Method findSingleParamWrapper(
+        Class aClass,
+        Class annotation,
+        Class paramType,
+        Class returnType) {
+        return findWrapper(aClass, annotation, method -> {
+            final var types = method.getParameterTypes();
+            return types.length == 1 && types[0] == paramType && returnType.isAssignableFrom(method.getReturnType());
+        });
+    }
+
+    private static Method findCEnumWrapper(Class aClass) {
+        return findSingleParamWrapper(aClass, CEnum.Wrapper.class, int.class, CEnum.class);
+    }
+
+    private static Method findUpcallWrapper(Class aClass) {
+        return findSingleParamWrapper(aClass, Upcall.Wrapper.class, MemorySegment.class, Upcall.class);
+    }
+
+    @SuppressWarnings("unchecked")
+    private static  T loadBytecode(Class callerClass, SymbolLookup lookup) {
+        final ClassFile cf = ClassFile.of();
+        final ClassDesc cd_thisClass = ClassDesc.of(callerClass.getPackageName(), STR."_\{callerClass.getSimpleName()}");
+        final byte[] bytes = cf.build(cd_thisClass, classBuilder -> {
+            final List methodList = Arrays.stream(callerClass.getMethods())
+                .filter(method ->
+                    method.getDeclaredAnnotation(Skip.class) == null &&
+                    !Modifier.isStatic(method.getModifiers()))
+                .toList();
+            final Map methodDataMap = LinkedHashMap.newLinkedHashMap(methodList.size());
+            final Map handleDataMap = LinkedHashMap.newLinkedHashMap(methodList.size());
+            final List addedHandleList = new ArrayList<>(methodList.size());
+
+            classBuilder.withFlags(ACC_FINAL | ACC_SUPER);
+
+            // interface
+            classBuilder.withInterfaceSymbols(callerClass.describeConstable().orElseThrow());
+
+            // linker
+            classBuilder.withField("LINKER", CD_Linker, ACC_PRIVATE | ACC_FINAL | ACC_STATIC);
+
+            // method handles
+            methodList.forEach(method -> {
+                final String methodName = method.getName();
+                final String handleName = STR."mh_\{methodName}";
+                final var parameters = List.of(method.getParameters());
+
+                final DowncallMethodData methodData = new DowncallMethodData(
+                    getMethodEntrypoint(method),
+                    handleName,
+                    STR."load$\{method.getName()}",
+                    STR."""
+                        \{method.getReturnType().getCanonicalName()} \
+                        \{method.getDeclaringClass().getCanonicalName()}.\{method.getName()}\
+                        \{Arrays.stream(method.getParameterTypes()).map(Class::getCanonicalName)
+                        .collect(Collectors.joining(", ", "(", ")"))}""",
+                    parameters,
+                    method.getDeclaredAnnotation(ByValue.class) == null &&
+                    !parameters.isEmpty() &&
+                    SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType())
+                );
+                methodDataMap.put(method, methodData);
+
+                if (!addedHandleList.contains(handleName)) {
+                    handleDataMap.put(method, methodData);
+                    addedHandleList.add(handleName);
+                    classBuilder.withField(handleName, CD_MethodHandle,
+                        ACC_PRIVATE | ACC_FINAL);
+                }
+            });
+
+            // constructor
+            classBuilder.withMethod(INIT_NAME, MethodTypeDesc.of(CD_void, CD_SymbolLookup), ACC_PUBLIC,
+                methodBuilder ->
+                    methodBuilder.withCode(codeBuilder -> {
+                        // super
+                        codeBuilder.aload(codeBuilder.receiverSlot())
+                            .invokespecial(CD_Object, INIT_NAME, MTD_void);
+
+                        // method handles
+                        handleDataMap.values().forEach(methodData -> {
+                            // initialize field
+                            codeBuilder.aload(codeBuilder.receiverSlot())
+                                .aload(codeBuilder.parameterSlot(0))
+                                .invokestatic(cd_thisClass, methodData.loaderName(), MethodTypeDesc.of(CD_MethodHandle, CD_SymbolLookup))
+                                .putfield(cd_thisClass, methodData.handleName(), CD_MethodHandle);
+                        });
+
+                        codeBuilder.return_();
+                    }));
+
+            // methods
+            methodDataMap.forEach((method, methodData) -> {
+                final String methodName = method.getName();
+                final int modifiers = method.getModifiers();
+                final var returnType = method.getReturnType();
+                final ClassDesc cd_returnType = ClassDesc.ofDescriptor(returnType.descriptorString());
+                final TypeKind returnTypeKind = TypeKind.from(cd_returnType).asLoadable();
+                final List parameters = methodData.parameters();
+                final MethodTypeDesc mtd_method = MethodTypeDesc.of(cd_returnType,
+                    parameters.stream()
+                        .map(parameter -> ClassDesc.ofDescriptor(parameter.getType().descriptorString()))
+                        .toList());
+
+                final String handleName = methodData.handleName();
+                final Consumer wrapping = codeBuilder -> {
+                    final boolean hasAllocator =
+                        !parameters.isEmpty() &&
+                        SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType());
+                    final boolean shouldAddStack =
+                        !parameters.isEmpty() &&
+                        !SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) &&
+                        parameters.stream().anyMatch(parameter -> requireAllocator(parameter.getType()));
+                    final int stackSlot;
+                    final int stackPointerSlot;
+                    final int allocatorSlot;
+
+                    if (shouldAddStack) {
+                        stackSlot = codeBuilder.allocateLocal(TypeKind.ReferenceType);
+                        stackPointerSlot = codeBuilder.allocateLocal(TypeKind.LongType);
+                        allocatorSlot = stackSlot;
+                    } else {
+                        stackSlot = -1;
+                        stackPointerSlot = -1;
+                        allocatorSlot = hasAllocator ? codeBuilder.parameterSlot(0) : -1;
+                    }
+
+                    final Map parameterRefSlot = HashMap.newHashMap(Math.toIntExact(parameters.stream()
+                        .filter(parameter -> parameter.getDeclaredAnnotation(Ref.class) != null)
+                        .count()));
+
+                    // check size
+                    for (int i = 0, size = parameters.size(); i < size; i++) {
+                        final Parameter parameter = parameters.get(i);
+                        final Sized sized = parameter.getDeclaredAnnotation(Sized.class);
+                        final Class type = parameter.getType();
+                        if (sized == null || !type.isArray()) {
+                            continue;
+                        }
+                        codeBuilder.ldc(sized.value())
+                            .aload(codeBuilder.parameterSlot(i))
+                            .arraylength()
+                            .invokestatic(CD_Checks,
+                                "checkArraySize",
+                                MethodTypeDesc.of(CD_void, CD_int, CD_int));
+                    }
+
+                    // initialize stack
+                    if (shouldAddStack) {
+                        codeBuilder.invokestatic(CD_MemoryStack, "stackGet", MethodTypeDesc.of(CD_MemoryStack))
+                            .astore(stackSlot)
+                            .aload(stackSlot)
+                            .invokevirtual(CD_MemoryStack, "pointer", MethodTypeDesc.of(CD_long))
+                            .lstore(stackPointerSlot);
+                    }
+
+                    codeBuilder.trying(
+                        blockCodeBuilder -> {
+                            final boolean skipFirstParam = methodData.skipFirstParam();
+                            final int parameterSize = parameters.size();
+                            final List parameterCDList = new ArrayList<>(skipFirstParam ? parameterSize - 1 : parameterSize);
+
+                            final ClassDesc cd_returnTypeDowncall = convertToDowncallCD(returnType);
+                            final boolean returnVoid = returnType == void.class;
+                            final boolean shouldStoreResult =
+                                !returnVoid &&
+                                shouldStoreResult(returnType, parameters);
+                            final int resultSlot;
+
+                            // ref
+                            for (int i = 0, size = parameters.size(); i < size; i++) {
+                                final Parameter parameter = parameters.get(i);
+                                if (parameter.getDeclaredAnnotation(Ref.class) == null) {
+                                    continue;
+                                }
+                                final Class type = parameter.getType();
+                                if (type.isArray()) {
+                                    final Class componentType = type.getComponentType();
+                                    final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType);
+                                    blockCodeBuilder.aload(allocatorSlot)
+                                        .aload(blockCodeBuilder.parameterSlot(i));
+
+                                    if (componentType == String.class && getCharset(blockCodeBuilder, parameter)) {
+                                        blockCodeBuilder.invokestatic(CD_Marshal,
+                                            "marshal",
+                                            MTD_marshalStringCharsetArray
+                                        ).astore(slot);
+                                    } else {
+                                        blockCodeBuilder.invokestatic(CD_Marshal,
+                                            "marshal",
+                                            MethodTypeDesc.of(CD_MemorySegment,
+                                                CD_SegmentAllocator,
+                                                convertToMarshalCD(componentType).arrayType())
+                                        ).astore(slot);
+                                    }
+                                    parameterRefSlot.put(parameter, slot);
+                                }
+                            }
+
+                            // invocation
+                            if (!shouldStoreResult) {
+                                tryAddLayout(blockCodeBuilder, returnType);
+                            }
+                            blockCodeBuilder.aload(blockCodeBuilder.receiverSlot())
+                                .getfield(cd_thisClass, handleName, CD_MethodHandle);
+                            for (int i = skipFirstParam ? 1 : 0; i < parameterSize; i++) {
+                                final Parameter parameter = parameters.get(i);
+                                final Class type = parameter.getType();
+
+                                final Integer refSlot = parameterRefSlot.get(parameter);
+                                if (refSlot != null) {
+                                    blockCodeBuilder.aload(refSlot);
+                                } else {
+                                    final int slot = blockCodeBuilder.parameterSlot(i);
+                                    if (type.isPrimitive() ||
+                                        type == MemorySegment.class) {
+                                        blockCodeBuilder.loadInstruction(
+                                            TypeKind.fromDescriptor(type.descriptorString()).asLoadable(),
+                                            slot
+                                        );
+                                    } else if (type == String.class) {
+                                        blockCodeBuilder.aload(allocatorSlot)
+                                            .aload(slot);
+                                        if (getCharset(blockCodeBuilder, parameter)) {
+                                            blockCodeBuilder.invokestatic(CD_Marshal,
+                                                "marshal",
+                                                MTD_marshalStringCharset);
+                                        } else {
+                                            blockCodeBuilder.invokestatic(CD_Marshal,
+                                                "marshal",
+                                                MTD_marshalString);
+                                        }
+                                    } else if (Addressable.class.isAssignableFrom(type) ||
+                                               CEnum.class.isAssignableFrom(type)) {
+                                        blockCodeBuilder.aload(slot)
+                                            .invokestatic(CD_Marshal,
+                                                "marshal",
+                                                MethodTypeDesc.of(convertToDowncallCD(type), convertToMarshalCD(type)));
+                                    } else if (Upcall.class.isAssignableFrom(type)) {
+                                        blockCodeBuilder.aload(allocatorSlot)
+                                            .checkcast(CD_Arena)
+                                            .aload(slot)
+                                            .invokestatic(CD_Marshal,
+                                                "marshal",
+                                                MethodTypeDesc.of(CD_MemorySegment, CD_Arena, CD_Upcall));
+                                    } else if (type.isArray()) {
+                                        final Class componentType = type.getComponentType();
+                                        final boolean isStringArray = componentType == String.class;
+                                        final boolean isUpcallArray = Upcall.class.isAssignableFrom(componentType);
+                                        blockCodeBuilder.aload(allocatorSlot);
+                                        if (isUpcallArray) {
+                                            blockCodeBuilder.checkcast(CD_Arena);
+                                        }
+                                        blockCodeBuilder.aload(slot);
+                                        if (isStringArray && getCharset(blockCodeBuilder, parameter)) {
+                                            blockCodeBuilder.invokestatic(CD_Marshal,
+                                                "marshal",
+                                                MTD_marshalStringCharsetArray);
+                                        } else {
+                                            blockCodeBuilder.invokestatic(CD_Marshal,
+                                                "marshal",
+                                                MethodTypeDesc.of(CD_MemorySegment,
+                                                    isUpcallArray ? CD_Arena : CD_SegmentAllocator,
+                                                    convertToMarshalCD(componentType).arrayType()));
+                                        }
+                                    }
+                                }
+
+                                parameterCDList.add(convertToDowncallCD(type));
+                            }
+                            blockCodeBuilder.invokevirtual(CD_MethodHandle,
+                                "invokeExact",
+                                MethodTypeDesc.of(cd_returnTypeDowncall, parameterCDList));
+
+                            if (shouldStoreResult) {
+                                final TypeKind typeKind = TypeKind.from(cd_returnTypeDowncall);
+                                resultSlot = blockCodeBuilder.allocateLocal(typeKind);
+                                blockCodeBuilder.storeInstruction(typeKind, resultSlot);
+
+                                tryAddLayout(blockCodeBuilder, returnType);
+                                blockCodeBuilder.loadInstruction(typeKind, resultSlot);
+                            } else {
+                                resultSlot = -1;
+                            }
+
+                            // wrap return value
+                            if (CEnum.class.isAssignableFrom(returnType)) {
+                                final Method wrapper = findCEnumWrapper(returnType);
+                                blockCodeBuilder.invokestatic(cd_returnType,
+                                    wrapper.getName(),
+                                    MethodTypeDesc.of(cd_returnType, CD_int));
+                            } else if (Upcall.class.isAssignableFrom(returnType)) {
+                                final Method wrapper = findUpcallWrapper(returnType);
+                                blockCodeBuilder.aload(resultSlot)
+                                    .ifThenElse(Opcode.IFNONNULL,
+                                        blockCodeBuilder1 -> blockCodeBuilder1.aload(resultSlot)
+                                            .invokestatic(cd_returnType,
+                                                wrapper.getName(),
+                                                MethodTypeDesc.of(cd_returnType, CD_MemorySegment)),
+                                        CodeBuilder::aconst_null);
+                            } else if (returnType == String.class) {
+                                final boolean hasCharset = getCharset(blockCodeBuilder, method);
+                                blockCodeBuilder.invokestatic(CD_Unmarshal,
+                                    "unmarshalAsString",
+                                    MethodTypeDesc.of(CD_String,
+                                        hasCharset ?
+                                            List.of(CD_MemorySegment, CD_Charset) :
+                                            List.of(CD_MemorySegment)));
+                            } else if (returnType.isArray()) {
+                                final Class componentType = returnType.getComponentType();
+                                if (componentType == String.class) {
+                                    final boolean hasCharset = getCharset(blockCodeBuilder, method);
+                                    blockCodeBuilder.invokestatic(CD_Unmarshal,
+                                        "unmarshalAsString",
+                                        MethodTypeDesc.of(CD_String.arrayType(),
+                                            hasCharset ?
+                                                List.of(CD_AddressLayout, CD_MemorySegment, CD_Charset) :
+                                                List.of(CD_AddressLayout, CD_MemorySegment)));
+                                } else if (componentType.isPrimitive()) {
+                                    blockCodeBuilder.invokestatic(CD_Unmarshal,
+                                        "unmarshal",
+                                        MethodTypeDesc.of(cd_returnType,
+                                            convertToValueLayoutCD(returnType),
+                                            CD_MemorySegment));
+                                }
+                            }
+
+                            // copy ref result
+                            for (int i = 0, size = parameters.size(); i < size; i++) {
+                                final Parameter parameter = parameters.get(i);
+                                final Class type = parameter.getType();
+                                final Class componentType = type.getComponentType();
+                                if (parameter.getDeclaredAnnotation(Ref.class) != null &&
+                                    type.isArray()) {
+                                    final boolean isPrimitiveArray = componentType.isPrimitive();
+                                    final boolean isStringArray = componentType == String.class;
+                                    if (isPrimitiveArray || isStringArray) {
+                                        final int refSlot = parameterRefSlot.get(parameter);
+                                        final int parameterSlot = blockCodeBuilder.parameterSlot(i);
+                                        blockCodeBuilder.aload(refSlot)
+                                            .aload(parameterSlot);
+                                        if (isPrimitiveArray) {
+                                            blockCodeBuilder.invokestatic(CD_Unmarshal,
+                                                "copy",
+                                                MethodTypeDesc.of(CD_void, CD_MemorySegment, ClassDesc.ofDescriptor(type.descriptorString())));
+                                        } else {
+                                            if (getCharset(blockCodeBuilder, parameter)) {
+                                                blockCodeBuilder.invokestatic(CD_Unmarshal,
+                                                    "copy",
+                                                    MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_String.arrayType(), CD_Charset));
+                                            } else {
+                                                blockCodeBuilder.invokestatic(CD_Unmarshal,
+                                                    "copy",
+                                                    MethodTypeDesc.of(CD_void, CD_MemorySegment, CD_String.arrayType()));
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+
+                            // reset stack
+                            if (shouldAddStack) {
+                                blockCodeBuilder.aload(stackSlot)
+                                    .lload(stackPointerSlot)
+                                    .invokevirtual(CD_MemoryStack, "setPointer", MethodTypeDesc.of(CD_void, CD_long));
+                            }
+
+                            // return
+                            blockCodeBuilder.returnInstruction(returnTypeKind);
+                        },
+                        catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> {
+                            final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType);
+                            // create exception
+                            blockCodeBuilder.astore(slot)
+                                .new_(CD_IllegalStateException)
+                                .dup()
+                                .ldc(methodData.exceptionString())
+                                .aload(slot)
+                                .invokespecial(CD_IllegalStateException, INIT_NAME, MethodTypeDesc.of(CD_void, CD_String, CD_Throwable));
+                            // reset stack
+                            if (shouldAddStack) {
+                                blockCodeBuilder.aload(stackSlot)
+                                    .lload(stackPointerSlot)
+                                    .invokevirtual(CD_MemoryStack, "setPointer", MethodTypeDesc.of(CD_void, CD_long));
+                            }
+                            // throw
+                            blockCodeBuilder.athrow();
+                        })
+                    );
+                };
+                classBuilder.withMethod(methodName,
+                    mtd_method,
+                    Modifier.isPublic(modifiers) ? ACC_PUBLIC : ACC_PROTECTED,
+                    methodBuilder -> methodBuilder.withCode(codeBuilder -> {
+                        if (method.isDefault()) {
+                            codeBuilder.aload(codeBuilder.receiverSlot())
+                                .getfield(cd_thisClass, handleName, CD_MethodHandle);
+                            codeBuilder.ifThenElse(Opcode.IFNONNULL,
+                                wrapping::accept,
+                                blockCodeBuilder -> {
+                                    // invoke super interface
+                                    blockCodeBuilder.aload(blockCodeBuilder.receiverSlot());
+                                    for (int i = 0, size = parameters.size(); i < size; i++) {
+                                        final var parameter = parameters.get(i);
+                                        blockCodeBuilder.loadInstruction(
+                                            TypeKind.fromDescriptor(parameter.getType().descriptorString())
+                                                .asLoadable(),
+                                            blockCodeBuilder.parameterSlot(i));
+                                    }
+                                    final Class declaringClass = method.getDeclaringClass();
+                                    blockCodeBuilder.invokespecial(declaringClass.describeConstable().orElseThrow(),
+                                        method.getName(),
+                                        mtd_method,
+                                        declaringClass.isInterface()
+                                    ).returnInstruction(returnTypeKind);
+                                }
+                            ).nop();
+                        } else {
+                            wrapping.accept(codeBuilder);
+                        }
+                    }));
+            });
+
+            // handle loader
+            handleDataMap.forEach((method, methodData) -> classBuilder.withMethod(
+                methodData.loaderName(),
+                MethodTypeDesc.of(CD_MethodHandle, CD_SymbolLookup),
+                ACC_PRIVATE | ACC_STATIC,
+                methodBuilder -> methodBuilder.withCode(codeBuilder -> {
+                    final int optionalSlot = codeBuilder.allocateLocal(TypeKind.ReferenceType);
+                    codeBuilder.aload(codeBuilder.parameterSlot(0))
+                        .ldc(methodData.entrypoint())
+                        .invokeinterface(CD_SymbolLookup, "find", MethodTypeDesc.of(CD_Optional, CD_String))
+                        .astore(optionalSlot)
+                        .aload(optionalSlot)
+                        .invokevirtual(CD_Optional, "isPresent", MethodTypeDesc.of(CD_boolean));
+                    codeBuilder.ifThenElse(
+                        blockCodeBuilder -> {
+                            blockCodeBuilder.getstatic(cd_thisClass, "LINKER", CD_Linker)
+                                .aload(optionalSlot)
+                                .invokevirtual(CD_Optional, "get", MethodTypeDesc.of(CD_Object))
+                                .checkcast(CD_MemorySegment);
+
+                            // layout
+                            final var returnType = method.getReturnType();
+                            final boolean returnVoid = returnType == void.class;
+                            if (!returnVoid) {
+                                convertToValueLayout(blockCodeBuilder, returnType);
+                                if (!returnType.isPrimitive()) {
+                                    final SizedSeg sizedSeg = method.getDeclaredAnnotation(SizedSeg.class);
+                                    final Sized sized = method.getDeclaredAnnotation(Sized.class);
+                                    final boolean isSizedSeg = sizedSeg != null;
+                                    final boolean isSized = sized != null && returnType.isArray();
+                                    if (isSizedSeg || isSized) {
+                                        if (isSizedSeg) {
+                                            blockCodeBuilder.constantInstruction(sizedSeg.value());
+                                            convertToValueLayout(blockCodeBuilder, byte.class);
+                                        } else {
+                                            blockCodeBuilder.constantInstruction((long) sized.value());
+                                            convertToValueLayout(blockCodeBuilder, returnType.getComponentType());
+                                        }
+                                        blockCodeBuilder.invokestatic(CD_MemoryLayout,
+                                                "sequenceLayout",
+                                                MethodTypeDesc.of(CD_SequenceLayout, CD_long, CD_MemoryLayout),
+                                                true)
+                                            .invokeinterface(CD_AddressLayout,
+                                                "withTargetLayout",
+                                                MethodTypeDesc.of(CD_AddressLayout, CD_MemoryLayout));
+                                    }
+                                }
+                            }
+                            final var parameters = methodData.parameters();
+                            final boolean skipFirstParam = methodData.skipFirstParam();
+                            final int size = skipFirstParam ? parameters.size() - 1 : parameters.size();
+                            blockCodeBuilder.constantInstruction(size)
+                                .anewarray(CD_MemoryLayout);
+                            for (int i = 0; i < size; i++) {
+                                blockCodeBuilder.dup()
+                                    .constantInstruction(i);
+                                convertToValueLayout(blockCodeBuilder, parameters.get(skipFirstParam ? i + 1 : i).getType());
+                                blockCodeBuilder.aastore();
+                            }
+                            blockCodeBuilder.invokestatic(CD_FunctionDescriptor,
+                                returnVoid ? "ofVoid" : "of",
+                                returnVoid ?
+                                    MethodTypeDesc.of(CD_FunctionDescriptor, CD_MemoryLayout.arrayType()) :
+                                    MethodTypeDesc.of(CD_FunctionDescriptor, CD_MemoryLayout, CD_MemoryLayout.arrayType()),
+                                true);
+
+                            // linker option
+                            final Critical critical = method.getDeclaredAnnotation(Critical.class);
+                            if (critical != null) {
+                                blockCodeBuilder.iconst_1()
+                                    .anewarray(CD_Linker_Option)
+                                    .dup()
+                                    .iconst_0()
+                                    .constantInstruction(critical.allowHeapAccess() ? 1 : 0)
+                                    .invokestatic(CD_Linker_Option,
+                                        "critical",
+                                        MethodTypeDesc.of(CD_Linker_Option, CD_boolean),
+                                        true)
+                                    .aastore();
+                            } else {
+                                blockCodeBuilder.iconst_0()
+                                    .anewarray(CD_Linker_Option);
+                            }
+
+                            blockCodeBuilder.invokeinterface(CD_Linker,
+                                "downcallHandle",
+                                MethodTypeDesc.of(CD_MethodHandle,
+                                    CD_MemorySegment,
+                                    CD_FunctionDescriptor,
+                                    CD_Linker_Option.arrayType())
+                            ).areturn();
+                        },
+                        blockCodeBuilder -> {
+                            if (method.isDefault()) {
+                                blockCodeBuilder.aconst_null()
+                                    .areturn();
+                            } else {
+                                blockCodeBuilder.new_(CD_IllegalStateException)
+                                    .dup()
+                                    .ldc(STR."""
+                                        Failed to load function with name \
+                                        \{methodData.entrypoint()}: \{methodData.exceptionString()}""")
+                                    .invokespecial(CD_IllegalStateException, INIT_NAME, MethodTypeDesc.of(CD_void, CD_String))
+                                    .athrow();
+                            }
+                        });
+                })
+            ));
+
+            // class initializer
+            classBuilder.withMethod(CLASS_INIT_NAME, MTD_void, ACC_STATIC,
+                methodBuilder -> methodBuilder.withCode(codeBuilder -> {
+                    // linker
+                    codeBuilder.invokestatic(CD_Linker, "nativeLinker", MethodTypeDesc.of(CD_Linker), true)
+                        .putstatic(cd_thisClass, "LINKER", CD_Linker)
+                        .return_();
+                }));
+        });
+
+        try {
+            final MethodHandles.Lookup hiddenClass = MethodHandles.privateLookupIn(callerClass, MethodHandles.lookup())
+                .defineHiddenClass(bytes, true, MethodHandles.Lookup.ClassOption.STRONG);
+            return (T) hiddenClass.findConstructor(hiddenClass.lookupClass(), MethodType.methodType(void.class, SymbolLookup.class))
+                .invoke(lookup);
+        } catch (Throwable e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static  T load(Class targetClass, SymbolLookup lookup) {
+        return loadBytecode(targetClass, lookup);
+    }
+
+    public static  T load(String libname) {
+        return load(STACK_WALKER.getCallerClass(), SymbolLookup.libraryLookup(libname, Arena.ofAuto()));
+    }
+
+    public static  T load(SymbolLookup lookup) {
+        return load(STACK_WALKER.getCallerClass(), lookup);
+    }
+}
diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java
index dc55fce..55a481d 100644
--- a/src/main/java/overrun/marshal/DowncallProcessor.java
+++ b/src/main/java/overrun/marshal/DowncallProcessor.java
@@ -17,13 +17,11 @@
 package overrun.marshal;
 
 import overrun.marshal.gen.Downcall;
-import overrun.marshal.gen2.DowncallData;
 import overrun.marshal.internal.Processor;
 
 import javax.annotation.processing.RoundEnvironment;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.util.ElementFilter;
-import java.io.IOException;
 import java.util.Set;
 
 /**
@@ -51,11 +49,11 @@ public boolean process(Set annotations, RoundEnvironment
 
     private void processClasses(RoundEnvironment roundEnv) {
         ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Downcall.class)).forEach(e -> {
-            try {
-                new DowncallData(processingEnv).generate(e);
-            } catch (IOException ex) {
-                printStackTrace(ex);
-            }
+//            try {
+//                new DowncallData(processingEnv).generate(e);
+//            } catch (IOException ex) {
+//                printStackTrace(ex);
+//            }
         });
     }
 
diff --git a/src/main/java/overrun/marshal/MemoryStack.java b/src/main/java/overrun/marshal/MemoryStack.java
index 963a033..afeac7b 100644
--- a/src/main/java/overrun/marshal/MemoryStack.java
+++ b/src/main/java/overrun/marshal/MemoryStack.java
@@ -46,8 +46,8 @@ public sealed class MemoryStack implements Arena {
     private static final ThreadLocal TLS = ThreadLocal.withInitial(MemoryStack::create);
 
     static {
-        if (DEFAULT_STACK_SIZE > 0) throw new IllegalStateException("Invalid stack size.");
-        if (DEFAULT_STACK_FRAMES > 0) throw new IllegalStateException("Invalid stack frames.");
+        if (DEFAULT_STACK_SIZE <= 0) throw new IllegalStateException("Invalid stack size.");
+        if (DEFAULT_STACK_FRAMES <= 0) throw new IllegalStateException("Invalid stack frames.");
     }
 
     private final MemorySegment segment;
diff --git a/src/main/java/overrun/marshal/Unmarshal.java b/src/main/java/overrun/marshal/Unmarshal.java
index b2bf7f4..0351bf8 100644
--- a/src/main/java/overrun/marshal/Unmarshal.java
+++ b/src/main/java/overrun/marshal/Unmarshal.java
@@ -16,8 +16,6 @@
 
 package overrun.marshal;
 
-import overrun.marshal.gen.CEnum;
-
 import java.lang.foreign.AddressLayout;
 import java.lang.foreign.MemoryLayout;
 import java.lang.foreign.MemorySegment;
@@ -103,10 +101,6 @@ public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegm
         return unmarshal(elementLayout, segment, String[]::new, s -> s.get(STRING_LAYOUT, 0L).getString(0L, charset));
     }
 
-    public static  T[] unmarshalAsCEnum(OfInt elementLayout, MemorySegment segment, IntFunction generator, IntFunction function) {
-        return segment.elements(elementLayout).mapToInt(s -> s.get(elementLayout, 0L)).mapToObj(function).toArray(generator);
-    }
-
     private static int checkArraySize(String typeName, long byteSize, int elemSize) {
         if (!((byteSize & (elemSize - 1)) == 0)) {
             throw new IllegalStateException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, byteSize));
diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java
index b9d3f83..50c8336 100644
--- a/src/main/java/overrun/marshal/Upcall.java
+++ b/src/main/java/overrun/marshal/Upcall.java
@@ -147,7 +147,7 @@ static  Type type(Class tClass) {
      * @since 0.1.0
      */
     @Target(ElementType.METHOD)
-    @Retention(RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.RUNTIME)
     @interface Wrapper {
     }
 
diff --git a/src/main/java/overrun/marshal/gen/Access.java b/src/main/java/overrun/marshal/gen/Access.java
index e13560b..92a0c6b 100644
--- a/src/main/java/overrun/marshal/gen/Access.java
+++ b/src/main/java/overrun/marshal/gen/Access.java
@@ -33,6 +33,7 @@
 @Documented
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.SOURCE)
+@Deprecated(since = "0.1.0", forRemoval = true)
 public @interface Access {
     /**
      * {@return the access modifier of the method}
diff --git a/src/main/java/overrun/marshal/gen/CEnum.java b/src/main/java/overrun/marshal/gen/CEnum.java
index 8ebf454..38eaf46 100644
--- a/src/main/java/overrun/marshal/gen/CEnum.java
+++ b/src/main/java/overrun/marshal/gen/CEnum.java
@@ -63,7 +63,7 @@ public interface CEnum {
      * @since 0.1.0
      */
     @Target(ElementType.METHOD)
-    @Retention(RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.RUNTIME)
     @interface Wrapper {
     }
 }
diff --git a/src/main/java/overrun/marshal/gen/Critical.java b/src/main/java/overrun/marshal/gen/Critical.java
index d5ff1e4..1f284d7 100644
--- a/src/main/java/overrun/marshal/gen/Critical.java
+++ b/src/main/java/overrun/marshal/gen/Critical.java
@@ -32,7 +32,7 @@
  */
 @Documented
 @Target(ElementType.METHOD)
-@Retention(RetentionPolicy.SOURCE)
+@Retention(RetentionPolicy.RUNTIME)
 public @interface Critical {
     /**
      * {@return whether the linked function should allow access to the Java heap}
diff --git a/src/main/java/overrun/marshal/gen/Custom.java b/src/main/java/overrun/marshal/gen/Custom.java
index 9238367..1638b2b 100644
--- a/src/main/java/overrun/marshal/gen/Custom.java
+++ b/src/main/java/overrun/marshal/gen/Custom.java
@@ -33,6 +33,7 @@
 @Documented
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.SOURCE)
+@Deprecated(since = "0.1.0", forRemoval = true)
 public @interface Custom {
     /**
      * {@return the custom code}
diff --git a/src/main/java/overrun/marshal/gen/Default.java b/src/main/java/overrun/marshal/gen/Default.java
index f81298b..c29d47f 100644
--- a/src/main/java/overrun/marshal/gen/Default.java
+++ b/src/main/java/overrun/marshal/gen/Default.java
@@ -38,6 +38,7 @@
 @Documented
 @Target(ElementType.METHOD)
 @Retention(RetentionPolicy.SOURCE)
+@Deprecated(since = "0.1.0", forRemoval = true)
 public @interface Default {
     /**
      * {@return the default value of the method}
diff --git a/src/main/java/overrun/marshal/gen/Downcall.java b/src/main/java/overrun/marshal/gen/Downcall.java
index c58a776..f8b921c 100644
--- a/src/main/java/overrun/marshal/gen/Downcall.java
+++ b/src/main/java/overrun/marshal/gen/Downcall.java
@@ -66,6 +66,7 @@
 @Documented
 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.SOURCE)
+@Deprecated(since = "0.1.0", forRemoval = true)
 public @interface Downcall {
     /**
      * {@return the name of the native library}
diff --git a/src/main/java/overrun/marshal/gen/Entrypoint.java b/src/main/java/overrun/marshal/gen/Entrypoint.java
index f6127b9..0b71d7e 100644
--- a/src/main/java/overrun/marshal/gen/Entrypoint.java
+++ b/src/main/java/overrun/marshal/gen/Entrypoint.java
@@ -31,7 +31,7 @@
  */
 @Documented
 @Target(ElementType.METHOD)
-@Retention(RetentionPolicy.SOURCE)
+@Retention(RetentionPolicy.RUNTIME)
 public @interface Entrypoint {
     /**
      * Uses the specified entrypoint (the native name of the function), instead of the method name.
diff --git a/src/main/java/overrun/marshal/gen/Loader.java b/src/main/java/overrun/marshal/gen/Loader.java
index 8cc9c48..ef3e7a7 100644
--- a/src/main/java/overrun/marshal/gen/Loader.java
+++ b/src/main/java/overrun/marshal/gen/Loader.java
@@ -22,20 +22,21 @@
 /**
  * Marks a static method as the library loader.
  * 

- * The target method must only contain a parameter of type {@link String} and returns {@link SymbolLookup}. + * The target method must only contain a parameter of type {@link Object} and returns {@link SymbolLookup}. *

Example

*
{@code
  * @Loader
- * static SymbolLookup load(String name) {
+ * public static SymbolLookup load(Object data) {
  *     //...
  * }
  * }
* * @author squid233 - * @see Downcall + * @see overrun.marshal.Downcall Downcall * @since 0.1.0 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) +@Deprecated(since = "0.1.0", forRemoval = true) public @interface Loader { } diff --git a/src/main/java/overrun/marshal/gen/Ref.java b/src/main/java/overrun/marshal/gen/Ref.java index 3539bf3..fc38da7 100644 --- a/src/main/java/overrun/marshal/gen/Ref.java +++ b/src/main/java/overrun/marshal/gen/Ref.java @@ -30,6 +30,6 @@ */ @Documented @Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.RUNTIME) public @interface Ref { } diff --git a/src/main/java/overrun/marshal/gen/Sized.java b/src/main/java/overrun/marshal/gen/Sized.java index d2d6871..653f162 100644 --- a/src/main/java/overrun/marshal/gen/Sized.java +++ b/src/main/java/overrun/marshal/gen/Sized.java @@ -35,7 +35,7 @@ */ @Documented @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.RUNTIME) public @interface Sized { /** * {@return the size of the array} diff --git a/src/main/java/overrun/marshal/gen/Skip.java b/src/main/java/overrun/marshal/gen/Skip.java index 9fe8bb8..a721a9e 100644 --- a/src/main/java/overrun/marshal/gen/Skip.java +++ b/src/main/java/overrun/marshal/gen/Skip.java @@ -31,6 +31,6 @@ */ @Documented @Target({ElementType.FIELD, ElementType.METHOD}) -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.RUNTIME) public @interface Skip { } diff --git a/src/main/java/overrun/marshal/gen/StrCharset.java b/src/main/java/overrun/marshal/gen/StrCharset.java index 12c69b6..c17102f 100644 --- a/src/main/java/overrun/marshal/gen/StrCharset.java +++ b/src/main/java/overrun/marshal/gen/StrCharset.java @@ -32,7 +32,7 @@ */ @Documented @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.RUNTIME) public @interface StrCharset { /** * {@return the charset} diff --git a/src/main/java/overrun/marshal/gen/struct/ByValue.java b/src/main/java/overrun/marshal/gen/struct/ByValue.java index ac00e62..627aada 100644 --- a/src/main/java/overrun/marshal/gen/struct/ByValue.java +++ b/src/main/java/overrun/marshal/gen/struct/ByValue.java @@ -34,6 +34,6 @@ */ @Documented @Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) +@Retention(RetentionPolicy.RUNTIME) public @interface ByValue { } diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java index f1c6031..10ed7d0 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallData.java @@ -18,6 +18,7 @@ import overrun.marshal.*; import overrun.marshal.gen.*; +import overrun.marshal.gen.Downcall; import overrun.marshal.gen.struct.ByValue; import overrun.marshal.gen.struct.StructRef; import overrun.marshal.gen1.*; diff --git a/src/main/java/overrun/marshal/gen2/DowncallMethodData.java b/src/main/java/overrun/marshal/gen2/DowncallMethodData.java new file mode 100644 index 0000000..f4ce759 --- /dev/null +++ b/src/main/java/overrun/marshal/gen2/DowncallMethodData.java @@ -0,0 +1,36 @@ +/* + * 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.gen2; + +import java.lang.reflect.Parameter; +import java.util.List; + +/** + * Holds downcall method name + * + * @author squid233 + * @since 0.1.0 + */ +public record DowncallMethodData( + String entrypoint, + String handleName, + String loaderName, + String exceptionString, + List parameters, + boolean skipFirstParam +) { +} 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 20d181f..53b9bc6 100644 --- a/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ b/src/main/resources/META-INF/services/javax.annotation.processing.Processor @@ -1,2 +1 @@ -overrun.marshal.DowncallProcessor overrun.marshal.StructProcessor diff --git a/src/test/java/overrun/marshal/test/DowncallProvider.java b/src/test/java/overrun/marshal/test/DowncallProvider.java new file mode 100644 index 0000000..fd49ee9 --- /dev/null +++ b/src/test/java/overrun/marshal/test/DowncallProvider.java @@ -0,0 +1,92 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_INT; + +/** + * Provides downcall handle + * + * @author squid233 + * @since 0.1.0 + */ +public final class DowncallProvider { + private static final Linker LINKER = Linker.nativeLinker(); + private static final Arena ARENA = Arena.ofAuto(); + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final Map table = HashMap.newHashMap(1); + + static { + try { + seg("test", LOOKUP.findStatic(DowncallProvider.class, "test", MethodType.methodType(void.class)), FunctionDescriptor.ofVoid()); + seg("testDefault", LOOKUP.findStatic(DowncallProvider.class, "testDefault", MethodType.methodType(void.class)), FunctionDescriptor.ofVoid()); + seg("test_int", LOOKUP.findStatic(DowncallProvider.class, "test_int", MethodType.methodType(void.class, int.class)), FunctionDescriptor.ofVoid(JAVA_INT)); + seg("test_String", LOOKUP.findStatic(DowncallProvider.class, "test_String", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("test_UTF16String", LOOKUP.findStatic(DowncallProvider.class, "test_UTF16String", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("test_CEnum", LOOKUP.findStatic(DowncallProvider.class, "test_CEnum", MethodType.methodType(void.class, int.class)), FunctionDescriptor.ofVoid(JAVA_INT)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + public static SymbolLookup lookup(boolean testDefaultNull) { + return name -> { + if (testDefaultNull && "testDefault".equals(name)) { + return Optional.empty(); + } + return Optional.ofNullable(table.get(name)); + }; + } + + private static void test() { + System.out.print("test"); + } + + private static void testDefault() { + System.out.print("testDefault"); + } + + private static void test_int(int i) { + System.out.print(i); + } + + private static void test_String(MemorySegment s) { + System.out.print(s.reinterpret(12).getString(0)); + } + + private static void test_UTF16String(MemorySegment s) { + System.out.print(s.reinterpret(40).getString(0, StandardCharsets.UTF_16)); + } + + private static void test_CEnum(int i) { + System.out.print(i); + } + + private static void seg(String name, MethodHandle mh, FunctionDescriptor fd) { + table.put(name, LINKER.upcallStub(mh, fd, ARENA)); + } +} diff --git a/src/test/java/overrun/marshal/test/DowncallTest.java b/src/test/java/overrun/marshal/test/DowncallTest.java new file mode 100644 index 0000000..554dbe3 --- /dev/null +++ b/src/test/java/overrun/marshal/test/DowncallTest.java @@ -0,0 +1,118 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import org.junit.jupiter.api.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; + +/** + * Test downcall + * + * @author squid233 + * @since 0.1.0 + */ +public final class DowncallTest { + private static IDowncall d; + private static PrintStream stdout = null; + private static ByteArrayOutputStream outputStream = null; + + @BeforeAll + static void beforeAll() { + d = IDowncall.getInstance(false); + } + + @BeforeEach + void beforeEach() { + stdout = System.out; + outputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(outputStream)); + } + + @AfterEach + void afterEach() { + System.setOut(stdout); + outputStream.reset(); + } + + @AfterAll + static void afterAll() throws IOException { + if (outputStream != null) { + outputStream.close(); + } + } + + @Test + void test() { + d.test(); + Assertions.assertEquals("test", outputStream.toString()); + } + + @Test + void testWithEntrypoint() { + d.testWithEntrypoint(); + Assertions.assertEquals("test", outputStream.toString()); + } + + @Test + void testSkip() { + d.testSkip(); + Assertions.assertEquals("testSkip", outputStream.toString()); + } + + @ParameterizedTest + @ValueSource(booleans = {false, true}) + void testDefault(boolean testDefaultNull) { + IDowncall.getInstance(testDefaultNull).testDefault(); + if (testDefaultNull) { + Assertions.assertEquals("testDefault in interface", outputStream.toString()); + } else { + Assertions.assertEquals("testDefault", outputStream.toString()); + } + } + + @Test + void test_int() { + d.test_int(42); + Assertions.assertEquals("42", outputStream.toString()); + } + + @Test + void test_String() { + d.test_String("Hello world"); + Assertions.assertEquals("Hello world", outputStream.toString()); + } + + @Test + void test_UTF16String() { + d.test_UTF16String(new String("Hello UTF-16 world".getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16)); + Assertions.assertEquals("Hello UTF-16 world", outputStream.toString()); + } + + @Test + void test_CEnum() { + d.test_CEnum(MyEnum.A); + d.test_CEnum(MyEnum.B); + d.test_CEnum(MyEnum.C); + Assertions.assertEquals("024", outputStream.toString()); + } +} diff --git a/src/test/java/overrun/marshal/test/IDowncall.java b/src/test/java/overrun/marshal/test/IDowncall.java new file mode 100644 index 0000000..52db442 --- /dev/null +++ b/src/test/java/overrun/marshal/test/IDowncall.java @@ -0,0 +1,54 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import overrun.marshal.Downcall; +import overrun.marshal.gen.*; + +/** + * Downcall interface + * + * @author squid233 + * @since 0.1.0 + */ +public interface IDowncall { + static IDowncall getInstance(boolean testDefaultNull) { + return Downcall.load(DowncallProvider.lookup(testDefaultNull)); + } + + void test(); + + @Entrypoint("test") + void testWithEntrypoint(); + + @Skip + default void testSkip() { + System.out.print("testSkip"); + } + + default void testDefault() { + System.out.print("testDefault in interface"); + } + + void test_int(int i); + + void test_String(String s); + + void test_UTF16String(@StrCharset("UTF-16") String s); + + void test_CEnum(MyEnum myEnum); +} diff --git a/src/test/java/overrun/marshal/test/MyEnum.java b/src/test/java/overrun/marshal/test/MyEnum.java new file mode 100644 index 0000000..585cc7f --- /dev/null +++ b/src/test/java/overrun/marshal/test/MyEnum.java @@ -0,0 +1,41 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import overrun.marshal.gen.CEnum; + +/** + * Enum + * + * @author squid233 + * @since 0.1.0 + */ +public enum MyEnum implements CEnum { + A(0), + B(2), + C(4); + private final int value; + + MyEnum(int value) { + this.value = value; + } + + @Override + public int value() { + return value; + } +} From 9e2810fb7384c88740817880ed7f680698675388 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 22 Jan 2024 19:31:11 +0800 Subject: [PATCH 18/28] Remove DowncallData --- .../marshal/test/CDowncallTestWithLoader.java | 32 - .../overrun/marshal/DowncallProcessor.java | 64 -- .../java/overrun/marshal/gen2/BaseData.java | 2 +- .../overrun/marshal/gen2/DowncallData.java | 792 ------------------ 4 files changed, 1 insertion(+), 889 deletions(-) delete mode 100644 demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java delete mode 100644 src/main/java/overrun/marshal/DowncallProcessor.java delete mode 100644 src/main/java/overrun/marshal/gen2/DowncallData.java diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java b/demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java deleted file mode 100644 index 3cd8f3e..0000000 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTestWithLoader.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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.gen.Downcall; - -import java.lang.foreign.MemorySegment; - -/** - * Test with loader - * - * @author squid233 - * @since 0.1.0 - */ -@Downcall(libname = "NativeLib", name = "DowncallTestWithLoader", loader = NativeLibLoader.class) -interface CDowncallTestWithLoader { - MemorySegment testWithArgAndReturnValue(MemorySegment segment); -} diff --git a/src/main/java/overrun/marshal/DowncallProcessor.java b/src/main/java/overrun/marshal/DowncallProcessor.java deleted file mode 100644 index 55a481d..0000000 --- a/src/main/java/overrun/marshal/DowncallProcessor.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal; - -import overrun.marshal.gen.Downcall; -import overrun.marshal.internal.Processor; - -import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.element.TypeElement; -import javax.lang.model.util.ElementFilter; -import java.util.Set; - -/** - * Downcall annotation processor - * - * @author squid233 - * @since 0.1.0 - */ -public final class DowncallProcessor extends Processor { - /** - * constructor - */ - public DowncallProcessor() { - } - - @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(Downcall.class)).forEach(e -> { -// try { -// new DowncallData(processingEnv).generate(e); -// } catch (IOException ex) { -// printStackTrace(ex); -// } - }); - } - - @Override - public Set getSupportedAnnotationTypes() { - return Set.of(Downcall.class.getCanonicalName()); - } -} diff --git a/src/main/java/overrun/marshal/gen2/BaseData.java b/src/main/java/overrun/marshal/gen2/BaseData.java index 5c361a5..abd4d39 100644 --- a/src/main/java/overrun/marshal/gen2/BaseData.java +++ b/src/main/java/overrun/marshal/gen2/BaseData.java @@ -41,7 +41,7 @@ * @author squid233 * @since 0.1.0 */ -public abstract sealed class BaseData permits DowncallData, StructData { +public abstract sealed class BaseData permits StructData { /** * the processing environment */ diff --git a/src/main/java/overrun/marshal/gen2/DowncallData.java b/src/main/java/overrun/marshal/gen2/DowncallData.java deleted file mode 100644 index 10ed7d0..0000000 --- a/src/main/java/overrun/marshal/gen2/DowncallData.java +++ /dev/null @@ -1,792 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import overrun.marshal.*; -import overrun.marshal.gen.*; -import overrun.marshal.gen.Downcall; -import overrun.marshal.gen.struct.ByValue; -import overrun.marshal.gen.struct.StructRef; -import overrun.marshal.gen1.*; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; -import java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.foreign.*; -import java.lang.invoke.MethodHandle; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.function.Function; -import java.util.function.Predicate; - -import static overrun.marshal.internal.Util.*; - -/** - * Holds downcall fields and functions - * - * @author squid233 - * @since 0.1.0 - */ -public final class DowncallData extends BaseData { - private static final List> METHOD_ANNOTATIONS = List.of( - ByValue.class, - Critical.class, - Default.class, - SizedSeg.class, - Sized.class, - StrCharset.class - ); - private static final List> PARAMETER_ANNOTATIONS = List.of( - NullableRef.class, - Ref.class, - SizedSeg.class, - Sized.class, - StrCharset.class - ); - - /** - * Construct - * - * @param processingEnv the processing environment - */ - public DowncallData(ProcessingEnvironment processingEnv) { - super(processingEnv); - } - - private void processDowncallType(TypeElement typeElement, String simpleClassName) { - final var enclosedElements = typeElement.getEnclosedElements(); - - final Downcall downcall = typeElement.getAnnotation(Downcall.class); - - this.document = getDocComment(typeElement); - this.nonFinal = downcall.nonFinal(); - - // process fieldDataList - skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).forEach(element -> { - final Object constantValue = element.getConstantValue(); - if (constantValue == null) { - return; - } - fieldDataList.add(new FieldData( - getDocComment(element), - List.of(), - AccessModifier.PUBLIC, - true, - true, - TypeData.detectType(processingEnv, element.asType()), - element.getSimpleName().toString(), - TypeUse.literal(getConstExp(constantValue)) - )); - }); - - final var methods = ElementFilter.methodsIn(enclosedElements); - if (methods.isEmpty()) { - return; - } - // collect method handles - final Map methodHandleDataMap = LinkedHashMap.newLinkedHashMap(methods.size()); - skipAnnotated(methods).forEach(executableElement -> { - final Access access = executableElement.getAnnotation(Access.class); - final String entrypoint = getMethodEntrypoint(executableElement); - final TypeMirror returnType = executableElement.getReturnType(); - methodHandleDataMap.put(entrypoint, - new MethodHandleData( - executableElement, - access != null ? access.value() : AccessModifier.PUBLIC, - entrypoint, - TypeUse.valueLayout(processingEnv, returnType) - .map(typeUse -> importData -> { - final ByValue byValue = executableElement.getAnnotation(ByValue.class); - final StructRef structRef = executableElement.getAnnotation(StructRef.class); - final SizedSeg sizedSeg = executableElement.getAnnotation(SizedSeg.class); - final Sized sized = executableElement.getAnnotation(Sized.class); - final boolean structRefNotNull = structRef != null; - final boolean sizedSegNotNull = sizedSeg != null; - final boolean sizedNotNull = sized != null; - final Spec spec = typeUse.apply(importData); - if (structRefNotNull || sizedSegNotNull || sizedNotNull) { - final InvokeSpec invokeSpec = new InvokeSpec(spec, "withTargetLayout"); - if (structRefNotNull) { - final Spec layout = Spec.accessSpec(structRef.value(), "LAYOUT"); - return byValue != null ? layout : invokeSpec.addArgument(layout); - } - if (sizedSegNotNull) { - return invokeSpec.addArgument(new InvokeSpec( - importData.simplifyOrImport(MemoryLayout.class), - "sequenceLayout" - ).addArgument(getConstExp(sizedSeg.value())) - .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData))); - } - return invokeSpec.addArgument(new InvokeSpec( - importData.simplifyOrImport(MemoryLayout.class), - "sequenceLayout") - .addArgument(getConstExp(sized.value())) - .addArgument(switch (0) { - default -> { - final Optional seqLayout; - if (returnType.getKind() == TypeKind.ARRAY && - returnType instanceof ArrayType arrayType) { - seqLayout = TypeUse.valueLayout(processingEnv, arrayType.getComponentType()); - } else { - seqLayout = TypeUse.valueLayout(byte.class); - } - yield seqLayout.orElseThrow().apply(importData); - } - })); - } - return spec; - }), - skipAnnotated(executableElement.getParameters()) - .map(element -> TypeUse.valueLayout(processingEnv, element)) - .filter(Optional::isPresent) - .map(Optional::get) - .toList(), - executableElement.getAnnotation(Default.class) != null - )); - }); - - // add linker and lookup - final Predicate containsKey = methodHandleDataMap::containsKey; - final String lookupName = addLookup(typeElement, containsKey); - final String linkerName = tryInsertUnderline("LINKER", containsKey); - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - true, - true, - TypeData.fromClass(Linker.class), - linkerName, - importData -> new InvokeSpec(importData.simplifyOrImport(Linker.class), "nativeLinker") - )); - - // adds methods - // method handles - methodHandleDataMap.forEach((name, methodHandleData) -> fieldDataList.add(new FieldData( - " The method handle of {@code " + name + "}.", - List.of(), - methodHandleData.accessModifier(), - true, - true, - TypeData.fromClass(MethodHandle.class), - methodHandleData.name(), - importData -> { - final InvokeSpec invokeSpec = new InvokeSpec( - new InvokeSpec(lookupName, "find") - .addArgument(getConstExp(name)), "map" - ).addArgument(new LambdaSpec("s") - .addStatementThis(new InvokeSpec(linkerName, "downcallHandle") - .addArgument("s") - .addArgument(switch (0) { - default -> { - final var returnType = methodHandleData.returnType(); - final String methodName = returnType.isPresent() ? "of" : "ofVoid"; - final InvokeSpec fdOf = new InvokeSpec(importData.simplifyOrImport(FunctionDescriptor.class), methodName); - returnType.ifPresent(typeUse -> { - final Spec spec = typeUse.apply(importData); - fdOf.addArgument(spec); - }); - methodHandleData.parameterTypes().forEach(typeUse -> - fdOf.addArgument(typeUse.apply(importData))); - yield fdOf; - } - }) - .addArgument(switch (0) { - default -> { - final Critical critical = methodHandleData.executableElement().getAnnotation(Critical.class); - yield critical != null ? - new InvokeSpec(importData.simplifyOrImport(Linker.Option.class), "critical") - .addArgument(getConstExp(critical.allowHeapAccess())) : - null; - } - }))); - if (methodHandleData.optional()) { - return new InvokeSpec(invokeSpec, "orElse").addArgument("null"); - } - return new InvokeSpec( - invokeSpec, "orElseThrow" - ); - } - ))); - - // methods - final var fieldNames = fieldDataList.stream().map(FieldData::name).toList(); - skipAnnotated(methods).forEach(executableElement -> { - final MethodHandleData methodHandleData = methodHandleDataMap.get(getMethodEntrypoint(executableElement)); - if (methodHandleData == null) { - return; - } - - final Custom custom = executableElement.getAnnotation(Custom.class); - if (custom != null) { - addCustomMethod(executableElement, methodHandleData, custom); - } else { - addDowncallMethod(simpleClassName, - executableElement, - fieldNames, - methodHandleData); - } - }); - } - - /** - * Generates the file - * - * @param typeElement the type element - * @throws IOException if an I/O error occurred - */ - @Override - public void generate(TypeElement typeElement) throws IOException { - final Downcall downcall = typeElement.getAnnotation(Downcall.class); - final DeclaredTypeData generatedClassName = generateClassName(downcall.name(), typeElement); - - processDowncallType(typeElement, generatedClassName.name()); - - generate(generatedClassName); - } - - private void addDowncallMethod( - String simpleClassName, - ExecutableElement executableElement, - List fieldNames, - MethodHandleData methodHandleData) { - final var returnType = TypeUse.toProcessedType(processingEnv, executableElement, ExecutableElement::getReturnType); - - final var parameters = executableElement.getParameters(); - final int skipFirst = shouldSkipFirstParameter(executableElement, parameters); - if (skipFirst < 0) { - return; - } - final var parameterDataList = parameters.stream() - .map(element -> new ParameterData( - getElementAnnotations(PARAMETER_ANNOTATIONS, element), - TypeUse.toProcessedType(processingEnv, element).orElseThrow(), - element.getSimpleName().toString() - )) - .toList(); - final var parameterNames = parameterDataList.stream() - .skip(skipFirst > 0 ? 1 : 0) - .map(ParameterData::name) - .toList(); - - final List variablesInScope = new ArrayList<>(parameterNames); - - final String memoryStackName = tryInsertUnderline("stack", s -> variablesInScope.stream().anyMatch(s::equals)); - final boolean hasAllocatorParameter = - !parameterDataList.isEmpty() && - isAExtendsB(processingEnv, parameters.getFirst().asType(), SegmentAllocator.class); - final String allocatorParameter = hasAllocatorParameter ? - parameterDataList.getFirst().name() : - memoryStackName; - final boolean shouldWrapWithMemoryStack = - parameters.stream().anyMatch(element -> useAllocator(element.asType())) && - !hasAllocatorParameter; - if (shouldWrapWithMemoryStack) { - variablesInScope.add(memoryStackName); - } - - final boolean shouldAddInvoker = parameterNames.stream().anyMatch(fieldNames::contains); - - final List statements = new ArrayList<>(); - final List tryStatements = new ArrayList<>(); - final List invocationStatements = new ArrayList<>(); - - // check array size of @Sized array - parameters.stream() - .filter(element -> element.getAnnotation(Sized.class) != null) - .forEach(element -> { - final Sized sized = element.getAnnotation(Sized.class); - statements.add(importData -> - Spec.statement(new InvokeSpec(importData.simplifyOrImport(Checks.class), "checkArraySize") - .addArgument(getConstExp(sized.value())) - .addArgument(Spec.accessSpec(element.getSimpleName().toString(), "length")))); - }); - - // ref segments - final Map refNameMap = HashMap.newHashMap((int) parameters.stream() - .filter(element -> element.getAnnotation(Ref.class) != null) - .count()); - parameters.stream() - .filter(element -> element.getAnnotation(Ref.class) != null) - .forEach(element -> invocationStatements.add(importData -> { - final String elementName = element.getSimpleName().toString(); - final String refSegName = tryInsertUnderline(elementName, - s -> variablesInScope.stream().anyMatch(s::equals)); - variablesInScope.add(refSegName); - refNameMap.put(elementName, refSegName); - - final Spec allocateSpec = wrapParameter(importData, allocatorParameter, element, false, null); - - return new VariableStatement("var", refSegName, allocateSpec) - .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) - .setFinal(true); - })); - - // invocation - final String methodHandleName = methodHandleData.name(); - final TypeUse invokeTypeUse = importData -> { - final InvokeSpec invokeSpec = new InvokeSpec(shouldAddInvoker ? - Spec.accessSpec(simpleClassName, methodHandleName) : - Spec.literal(methodHandleName), "invokeExact"); - // wrap parameters - var stream = parameters.stream(); - if (skipFirst > 0) { - stream = stream.skip(1); - } else if (executableElement.getAnnotation(ByValue.class) != null && - !isSameClass(parameters.getFirst().asType(), SegmentAllocator.class)) { - invokeSpec.addArgument(Spec.cast(importData.simplifyOrImport(SegmentAllocator.class), - Spec.literal(allocatorParameter))); - stream = stream.skip(1); - } - stream.map(element -> wrapParameter(importData, allocatorParameter, element, true, refNameMap::get)) - .forEach(invokeSpec::addArgument); - return invokeSpec; - }; - - // store result - final var downcallReturnType = TypeUse.toDowncallType(processingEnv, executableElement, ExecutableElement::getReturnType); - final boolean shouldStoreResult = - downcallReturnType.isPresent() && - (canConvertToAddress(executableElement, ExecutableElement::getReturnType) || - parameters.stream().anyMatch(element -> element.getAnnotation(Ref.class) != null)); - final String resultVarName; - if (shouldStoreResult) { - resultVarName = tryInsertUnderline("result", s -> variablesInScope.stream().anyMatch(s::equals)); - variablesInScope.add(resultVarName); - invocationStatements.add(importData -> new VariableStatement("var", - resultVarName, - Spec.cast(downcallReturnType.get().apply(importData), invokeTypeUse.apply(importData))) - .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) - .setFinal(true)); - } else { - resultVarName = null; - if (downcallReturnType.isPresent()) { - invocationStatements.add(importData -> Spec.returnStatement(wrapReturnValue(importData, - Spec.cast(downcallReturnType.get().apply(importData), invokeTypeUse.apply(importData)), - executableElement))); - } else { - invocationStatements.add(importData -> Spec.statement(invokeTypeUse.apply(importData))); - } - } - - // passing to ref - parameters.stream() - .filter(element -> element.getAnnotation(Ref.class) != null) - .forEach(element -> invocationStatements.add(importData -> - copyRefResult(importData, element, refNameMap::get))); - - // return result - if (shouldStoreResult) { - invocationStatements.add(importData -> Spec.returnStatement(wrapReturnValue(importData, - Spec.literal(resultVarName), - executableElement))); - } - - statements.add(importData -> { - final TryStatement tryStatement = new TryStatement(); - - if (shouldWrapWithMemoryStack) { - tryStatement.addResource(new VariableStatement( - "var", - memoryStackName, - new InvokeSpec(importData.simplifyOrImport(MemoryStack.class), "stackPush") - ).setAccessModifier(AccessModifier.PACKAGE_PRIVATE) - .setAddSemicolon(false)); - } - - final Default defaultAnnotation = executableElement.getAnnotation(Default.class); - if (defaultAnnotation != null) { - final TypeUse ifTypeUse = importData1 -> { - final IfStatement ifStatement = new IfStatement(Spec.notNullSpec(methodHandleName)); - invocationStatements.forEach(typeUse -> ifStatement.addStatement(typeUse.apply(importData1))); - final String defaultValue = defaultAnnotation.value(); - if (!defaultValue.isBlank()) { - ifStatement.addElseClause(ElseClause.of(), elseClause -> - elseClause.addStatement(Spec.returnStatement(Spec.indentCodeBlock(defaultValue))) - ); - } - return ifStatement; - }; - tryStatements.add(ifTypeUse); - } else { - tryStatements.addAll(invocationStatements); - } - - tryStatements.forEach(typeUse -> tryStatement.addStatement(typeUse.apply(importData))); - - tryStatement.addCatchClause(switch (0) { - default -> { - final CatchClause catchClause = new CatchClause( - importData.simplifyOrImport(Throwable.class), - tryInsertUnderline("e", s -> fieldNames.contains(s) || parameterNames.contains(s)) - ); - catchClause.addStatement(Spec.throwStatement( - new ConstructSpec(importData.simplifyOrImport(RuntimeException.class)) - .addArgument(Spec.literal(catchClause.name())) - )); - yield catchClause; - } - }); - return tryStatement; - }); - - functionDataList.add(new FunctionData( - getDocComment(executableElement), - getElementAnnotations(METHOD_ANNOTATIONS, executableElement), - methodHandleData.accessModifier(), - true, - returnType.orElseGet(() -> TypeUse.literal(void.class)), - executableElement.getSimpleName().toString(), - parameterDataList, - statements - )); - } - - private int shouldSkipFirstParameter(ExecutableElement executableElement, - List rawParameterList) { - final boolean returnByValue = executableElement.getAnnotation(ByValue.class) != null; - final boolean rawParamListNotEmpty = !rawParameterList.isEmpty(); - final boolean firstParamArena = - rawParamListNotEmpty && - isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), Arena.class); - final boolean firstParamSegmentAllocator = - firstParamArena || - (rawParamListNotEmpty && - isAExtendsB(processingEnv, rawParameterList.getFirst().asType(), SegmentAllocator.class)); - if (rawParameterList.stream() - .anyMatch(element -> isAExtendsB(processingEnv, element.asType(), Upcall.class))) { - if (firstParamArena) { - return 1; - } - processingEnv.getMessager().printError("The first parameter of method with upcall must be Arena", executableElement); - return -1; - } - if (returnByValue) { - if (firstParamSegmentAllocator) { - return 0; - } - processingEnv.getMessager().printError("The first parameter of method marked as @ByValue must be SegmentAllocator", executableElement); - return -1; - } - return firstParamSegmentAllocator ? 1 : 0; - } - - private void addCustomMethod(ExecutableElement executableElement, - MethodHandleData methodHandleData, - Custom custom) { - functionDataList.add(new FunctionData( - getDocComment(executableElement), - getElementAnnotations(METHOD_ANNOTATIONS, executableElement), - methodHandleData.accessModifier(), - true, - TypeUse.of(processingEnv, executableElement.getReturnType()), - executableElement.getSimpleName().toString(), - parametersToParameterDataList(skipAnnotated(executableElement.getParameters()).toList()), - List.of(_ -> Spec.indented(custom.value())) - )); - } - - private String addLookup(TypeElement typeElement, Predicate insertUnderlineTest) { - final Downcall downcall = typeElement.getAnnotation(Downcall.class); - final String loader = typeElement.getAnnotationMirrors().stream() - .filter(m -> isSameClass(m.getAnnotationType(), Downcall.class)) - .findFirst() - .orElseThrow() - .getElementValues().entrySet().stream() - .filter(e -> "loader".contentEquals(e.getKey().getSimpleName())) - .findFirst() - .map(e -> e.getValue().getValue().toString()) - .orElse(null); - final String libname = downcall.libname(); - final String libnameConstExp = getConstExp(libname); - final TypeElement loaderTypeElement; - final Optional annotatedMethod; - if (loader == null) { - loaderTypeElement = null; - annotatedMethod = Optional.empty(); - } else { - loaderTypeElement = getTypeElementFromClass(processingEnv, loader); - annotatedMethod = findAnnotatedMethod(loaderTypeElement, - Loader.class, - executableElement -> { - if (isSameClass(executableElement.getReturnType(), SymbolLookup.class)) { - final var parameters = executableElement.getParameters(); - return parameters.size() == 1 && isSameClass(parameters.getFirst().asType(), String.class); - } - return false; - }); - if (annotatedMethod.isEmpty()) { - processingEnv.getMessager().printError("Couldn't find loader method in %s. Please mark it with @Loader" - .formatted(loader), - typeElement); - return null; - } - } - final String s = tryInsertUnderline("LOOKUP", insertUnderlineTest); - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - true, - true, - TypeData.fromClass(SymbolLookup.class), - s, - loader == null ? - importData -> new InvokeSpec(importData.simplifyOrImport(SymbolLookup.class), "libraryLookup") - .addArgument(libnameConstExp) - .addArgument(new InvokeSpec(importData.simplifyOrImport(Arena.class), "global")) : - importData -> new InvokeSpec(importData.simplifyOrImport( - processingEnv, loaderTypeElement.asType() - ), annotatedMethod.get().getSimpleName().toString()) - .addArgument(libnameConstExp) - )); - return s; - } - - private Spec copyRefResult( - ImportData importData, - Element element, - Function refFunction - ) { - final TypeMirror typeMirror = element.asType(); - final String name = element.getSimpleName().toString(); - final Spec spec; - if (isBooleanArray(typeMirror) || isPrimitiveArray(typeMirror)) { - spec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "copy") - .addArgument(refFunction.apply(name)) - .addArgument(name); - } else if (isStringArray(typeMirror)) { - final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "copy") - .addArgument(refFunction.apply(name)) - .addArgument(name); - if (element.getAnnotation(StrCharset.class) != null) { - invokeSpec.addArgument(createCharset(getCharset(element)).apply(importData)); - } - spec = invokeSpec; - } else { - processingEnv.getMessager().printWarning("Using @Ref on unknown type %s".formatted(typeMirror), element); - spec = null; - } - - return spec != null ? Spec.statement(spec) : Spec.literal(""); - } - - private Spec wrapReturnValue( - ImportData importData, - Spec resultSpec, - ExecutableElement executableElement - ) { - final Spec spec; - final StructRef structRef = executableElement.getAnnotation(StructRef.class); - if (structRef != null) { - spec = new ConstructSpec(structRef.value()).addArgument(resultSpec); - } else { - final TypeMirror typeMirror = executableElement.getReturnType(); - if (isBooleanArray(typeMirror) || - isPrimitiveArray(typeMirror)) { - spec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "unmarshal") - .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(typeMirror)) - .orElseThrow() - .apply(importData)) - .addArgument(resultSpec); - } else if (isStringArray(typeMirror)) { - final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "unmarshalAsString") - .addArgument(TypeUse.valueLayout(MemorySegment.class).orElseThrow().apply(importData)) - .addArgument(resultSpec); - spec = invokeSpec; - if (executableElement.getAnnotation(StrCharset.class) != null) { - invokeSpec.addArgument(createCharset(getCharset(executableElement)).apply(importData)); - } - } else if (isSameClass(typeMirror, String.class)) { - final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(Unmarshal.class), "unmarshalAsString") - .addArgument(resultSpec); - if (executableElement.getAnnotation(StrCharset.class) != null) { - invokeSpec.addArgument(createCharset(getCharset(executableElement)).apply(importData)); - } - spec = invokeSpec; - } else if (isAExtendsB(processingEnv, typeMirror, Upcall.class)) { - final var annotatedMethod = findAnnotatedMethod( - (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror), - Upcall.Wrapper.class, - executableElement1 -> { - if (isAExtendsB(processingEnv, executableElement1.getReturnType(), typeMirror)) { - final var parameters = executableElement1.getParameters(); - return parameters.size() == 1 && isSameClass(parameters.getFirst().asType(), MemorySegment.class); - } - return false; - }); - spec = annotatedMethod - .map(executableElement1 -> new InvokeSpec(importData.simplifyOrImport(processingEnv, typeMirror), - executableElement1.getSimpleName().toString()) - .addArgument(resultSpec)) - .orElseGet(() -> { - processingEnv.getMessager().printError( - "Couldn't find wrapper in %s. Please mark it with @Wrapper".formatted(typeMirror), - executableElement - ); - return resultSpec; - }); - } else if (isAExtendsB(processingEnv, typeMirror, CEnum.class)) { - final var annotatedMethod = findAnnotatedMethod( - (TypeElement) processingEnv.getTypeUtils().asElement(typeMirror), - CEnum.Wrapper.class, - executableElement1 -> { - if (isAExtendsB(processingEnv, executableElement1.getReturnType(), typeMirror)) { - final var parameters = executableElement1.getParameters(); - return parameters.size() == 1 && isSameClass(parameters.getFirst().asType(), int.class); - } - return false; - }); - spec = annotatedMethod - .map(executableElement1 -> new InvokeSpec(importData.simplifyOrImport(processingEnv, typeMirror), - executableElement1.getSimpleName().toString()) - .addArgument(resultSpec)) - .orElseGet(() -> { - processingEnv.getMessager().printError( - "Couldn't find wrapper in %s. Please mark it with @Wrapper".formatted(typeMirror), - executableElement - ); - return resultSpec; - }); - } else { - spec = resultSpec; - } - } - return canConvertToAddress(executableElement, ExecutableElement::getReturnType) ? - Spec.ternaryOp(Spec.neqSpec(new InvokeSpec(resultSpec, "address"), Spec.literal("0L")), - spec, - Spec.literal("null")) : - spec; - } - - private Spec wrapParameter( - ImportData importData, - String allocatorParameter, - Element element, - boolean usingRef, - Function refFunction - ) { - final String name = element.getSimpleName().toString(); - final Spec spec; - if (usingRef && element.getAnnotation(Ref.class) != null) { - spec = Spec.literal(refFunction.apply(name)); - } else { - final TypeMirror typeMirror = element.asType(); - if (shouldMarshal(processingEnv, element)) { - final InvokeSpec invokeMarshal = new InvokeSpec(importData.simplifyOrImport(Marshal.class), "marshal"); - if (requireAllocator(processingEnv, typeMirror)) { - invokeMarshal.addArgument(allocatorParameter); - } - invokeMarshal.addArgument(name); - if (element.getAnnotation(StrCharset.class) != null) { - invokeMarshal.addArgument(createCharset(getCharset(element)).apply(importData)); - } - spec = invokeMarshal; - } else { - spec = Spec.literal(name); - } - } - return spec; - } - - private boolean useAllocator(TypeMirror typeMirror) { - return isBooleanArray(typeMirror) || - isPrimitiveArray(typeMirror) || - isStringArray(typeMirror) || - isSameClass(typeMirror, String.class) || - isAExtendsB(processingEnv, typeMirror, Upcall.class); - } - - private List parametersToParameterDataList(List parameters) { - return parameters.stream() - .map(element -> new ParameterData( - getElementAnnotations(PARAMETER_ANNOTATIONS, element), - importData -> { - final StructRef structRef = element.getAnnotation(StructRef.class); - if (structRef != null) { - return Spec.literal(structRef.value()); - } - return Spec.literal(importData.simplifyOrImport(processingEnv, element.asType())); - }, - element.getSimpleName().toString() - )) - .toList(); - } - - private TypeUse createCharset(String name) { - return importData -> { - 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" -> - Spec.accessSpec(importData.simplifyOrImport(StandardCharsets.class), upperCase.replace('-', '_')); - case "UTF_8", "ISO_8859_1", "US_ASCII", - "UTF_16", "UTF_16BE", "UTF_16LE", - "UTF_32", "UTF_32BE", "UTF_32LE" -> - Spec.accessSpec(importData.simplifyOrImport(StandardCharsets.class), upperCase); - default -> new InvokeSpec(importData.simplifyOrImport(Charset.class), "forName") - .addArgument(getConstExp(name)); - }; - }; - } - - private static String getCharset(Element element) { - final StrCharset strCharset = element.getAnnotation(StrCharset.class); - if (strCharset != null) { - final String value = strCharset.value(); - if (!value.isBlank()) { - return value; - } - } - return "UTF-8"; - } - - private static String getMethodEntrypoint(ExecutableElement executableElement) { - final Entrypoint entrypoint = executableElement.getAnnotation(Entrypoint.class); - if (entrypoint != null) { - final String value = entrypoint.value(); - if (!value.isBlank()) { - return value; - } - } - return executableElement.getSimpleName().toString(); - } - - private boolean canConvertToAddress(T element, Function function) { - if (element.getAnnotation(StructRef.class) != null) { - return true; - } - final TypeMirror type = function.apply(element); - final TypeKind typeKind = type.getKind(); - final boolean nonAddressType = - (typeKind.isPrimitive()) || - (typeKind == TypeKind.VOID) || - (typeKind == TypeKind.DECLARED && - (isSameClass(type, MemorySegment.class) || - isAExtendsB(processingEnv, type, SegmentAllocator.class) || - isAExtendsB(processingEnv, type, CEnum.class)) - ); - return !nonAddressType; - } -} From bc7ab4f71708b324024eb8d7b2bd7bcbd2628110 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 22 Jan 2024 20:41:36 +0800 Subject: [PATCH 19/28] Update javadoc --- .../overrun/marshal/test/CDowncallTest.java | 14 -- src/main/java/overrun/marshal/Downcall.java | 28 +++ src/main/java/overrun/marshal/Marshal.java | 141 +++++++++++++++ .../java/overrun/marshal/MemoryStack.java | 147 +++++++++++++++ src/main/java/overrun/marshal/Unmarshal.java | 168 +++++++++++++++++- .../java/overrun/marshal/gen/NullableRef.java | 1 + .../marshal/gen2/DowncallMethodData.java | 6 + .../marshal/gen2/struct/MemberData.java | 2 + 8 files changed, 492 insertions(+), 15 deletions(-) diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index aeacf0d..91d650d 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -42,11 +42,6 @@ interface CDowncallTest { @Skip int SKIPPED = -1; - void test(); - - @Entrypoint("test") - void testWithEntrypoint(); - /** * This is a test method with javadoc. * @@ -56,15 +51,6 @@ interface CDowncallTest { int testWithReturnValue(); - @Default - void testWithOptional(); - - @Default(""" - Math.sqrt( - 2 - )""") - double testDefaultWithMultiline(); - void testWithArgument(int i, MemorySegment holder); MemorySegment testWithArgAndReturnValue(MemorySegment segment); diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 3aa8073..31199e0 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -47,7 +47,35 @@ /** * Downcall library loader. + *

Loading native library

+ * You can load native libraries with {@link #load(Class, SymbolLookup)}. + * This method generates a hidden class that loads method handle with the given symbol lookup. + *

Methods

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

+ * {@link Entrypoint @Entrypoint} specifies the entrypoint of the annotated method. + *

+ * THe loader will not throw an exception + * if the method is modified with {@code default} and the native function is not found; + * instead, it will return {@code null} and automatically invokes the original method declared in the target class. + *

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

Example

+ *
{@code
+ * public interface GL {
+ *     GL gl = Downcall.load("libGL.so");
+ *     int COLOR_BUFFER_BIT = 0x00004000;
+ *     void glClear(int mask);
+ * }
+ * }
* + * @see Critical + * @see Entrypoint + * @see Ref + * @see Sized + * @see SizedSeg + * @see Skip + * @see StrCharset * @author squid233 * @since 0.1.0 */ diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java index 0c74f3e..d7da620 100644 --- a/src/main/java/overrun/marshal/Marshal.java +++ b/src/main/java/overrun/marshal/Marshal.java @@ -47,30 +47,71 @@ static VarHandle arrayVarHandle(ValueLayout valueLayout) { return MethodHandles.insertCoordinates(valueLayout.arrayElementVarHandle(), 1, 0L); } + /** + * Converts the given string to a segment. + * + * @param allocator the allocator + * @param string the string + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, String string) { if (string == null) return MemorySegment.NULL; return allocator.allocateFrom(string); } + /** + * Converts the given string to a segment. + * + * @param allocator the allocator + * @param string the string + * @param charset the charset + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, String string, Charset charset) { if (string == null) return MemorySegment.NULL; return allocator.allocateFrom(string, charset); } + /** + * Converts the given CEnum to an integer. + * + * @param cEnum the CEnum + * @return the integer + */ public static int marshal(CEnum cEnum) { return cEnum.value(); } + /** + * Converts the given addressable to a segment. + * + * @param addressable the addressable + * @return the segment + */ public static MemorySegment marshal(Addressable addressable) { if (addressable == null) return MemorySegment.NULL; return addressable.segment(); } + /** + * Converts the given upcall to a segment. + * + * @param arena the arena + * @param upcall the upcall + * @return the segment + */ public static MemorySegment marshal(Arena arena, Upcall upcall) { if (upcall == null) return MemorySegment.NULL; return upcall.stub(arena); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, boolean[] arr) { if (arr == null) return MemorySegment.NULL; final MemorySegment segment = allocator.allocate(JAVA_BOOLEAN, arr.length); @@ -80,41 +121,98 @@ public static MemorySegment marshal(SegmentAllocator allocator, boolean[] arr) { return segment; } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, char[] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_CHAR, arr); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, byte[] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_BYTE, arr); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, short[] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_SHORT, arr); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, int[] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_INT, arr); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, long[] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_LONG, arr); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, float[] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_FLOAT, arr); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, double[] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_DOUBLE, arr); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @param function a function to apply to each element + * @return the segment + */ public static MemorySegment marshal(A allocator, T[] arr, BiFunction function) { if (arr == null) return MemorySegment.NULL; final MemorySegment segment = allocator.allocate(ADDRESS, arr.length); @@ -124,21 +222,50 @@ public static MemorySegment marshal(A allocator, return segment; } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, MemorySegment[] arr) { if (arr == null) return MemorySegment.NULL; return marshal(allocator, arr, (_, segment) -> segment); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, String[] arr) { if (arr == null) return MemorySegment.NULL; return marshal(allocator, arr, SegmentAllocator::allocateFrom); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @param charset the charset + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, String[] arr, Charset charset) { if (arr == null) return MemorySegment.NULL; return marshal(allocator, arr, (segmentAllocator, str) -> segmentAllocator.allocateFrom(str, charset)); } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, CEnum[] arr) { if (arr == null) return MemorySegment.NULL; final MemorySegment segment = allocator.allocate(JAVA_INT, arr.length); @@ -148,11 +275,25 @@ public static MemorySegment marshal(SegmentAllocator allocator, CEnum[] arr) { return segment; } + /** + * Converts the given array to a segment. + * + * @param allocator the allocator + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(SegmentAllocator allocator, Addressable[] arr) { if (arr == null) return MemorySegment.NULL; return marshal(allocator, arr, (_, addressable) -> addressable.segment()); } + /** + * Converts the given array to a segment. + * + * @param arena the arena + * @param arr the array + * @return the segment + */ public static MemorySegment marshal(Arena arena, Upcall[] arr) { if (arr == null) return MemorySegment.NULL; return marshal(arena, arr, (arena1, upcall) -> upcall.stub(arena1)); diff --git a/src/main/java/overrun/marshal/MemoryStack.java b/src/main/java/overrun/marshal/MemoryStack.java index afeac7b..b66baa8 100644 --- a/src/main/java/overrun/marshal/MemoryStack.java +++ b/src/main/java/overrun/marshal/MemoryStack.java @@ -355,6 +355,9 @@ public void close() { /** * Single value version of {@link #malloc}. + * + * @param x the first value + * @return the segment */ public MemorySegment bytes(byte x) { return allocateFrom(JAVA_BYTE, x); @@ -362,6 +365,10 @@ public MemorySegment bytes(byte x) { /** * Two value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @return the segment */ public MemorySegment bytes(byte x, byte y) { final var segment = malloc(2, JAVA_BYTE); @@ -372,6 +379,11 @@ public MemorySegment bytes(byte x, byte y) { /** * Three value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @return the segment */ public MemorySegment bytes(byte x, byte y, byte z) { final var segment = malloc(3, JAVA_BYTE); @@ -383,6 +395,12 @@ public MemorySegment bytes(byte x, byte y, byte z) { /** * Four value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @param w the fourth value + * @return the segment */ public MemorySegment bytes(byte x, byte y, byte z, byte w) { final var segment = malloc(4, JAVA_BYTE); @@ -395,6 +413,9 @@ public MemorySegment bytes(byte x, byte y, byte z, byte w) { /** * Vararg version of {@link #malloc}. + * + * @param values the values + * @return the segment */ public MemorySegment bytes(byte... values) { return allocateFrom(JAVA_BYTE, values); @@ -404,6 +425,9 @@ public MemorySegment bytes(byte... values) { /** * Single value version of {@link #malloc}. + * + * @param x the first value + * @return the segment */ public MemorySegment shorts(short x) { return allocateFrom(JAVA_SHORT, x); @@ -411,6 +435,10 @@ public MemorySegment shorts(short x) { /** * Two value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @return the segment */ public MemorySegment shorts(short x, short y) { final var segment = malloc(4, JAVA_SHORT); @@ -421,6 +449,11 @@ public MemorySegment shorts(short x, short y) { /** * Three value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @return the segment */ public MemorySegment shorts(short x, short y, short z) { final var segment = malloc(6, JAVA_SHORT); @@ -432,6 +465,12 @@ public MemorySegment shorts(short x, short y, short z) { /** * Four value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @param w the fourth value + * @return the segment */ public MemorySegment shorts(short x, short y, short z, short w) { final var segment = malloc(8, JAVA_SHORT); @@ -444,6 +483,9 @@ public MemorySegment shorts(short x, short y, short z, short w) { /** * Vararg version of {@link #malloc}. + * + * @param values the values + * @return the segment */ public MemorySegment shorts(short... values) { return allocateFrom(JAVA_SHORT, values); @@ -453,6 +495,9 @@ public MemorySegment shorts(short... values) { /** * Single value version of {@link #malloc}. + * + * @param x the first value + * @return the segment */ public MemorySegment ints(int x) { return allocateFrom(JAVA_INT, x); @@ -460,6 +505,10 @@ public MemorySegment ints(int x) { /** * Two value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @return the segment */ public MemorySegment ints(int x, int y) { final var segment = malloc(8, JAVA_INT); @@ -470,6 +519,11 @@ public MemorySegment ints(int x, int y) { /** * Three value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @return the segment */ public MemorySegment ints(int x, int y, int z) { final var segment = malloc(12, JAVA_INT); @@ -481,6 +535,12 @@ public MemorySegment ints(int x, int y, int z) { /** * Four value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @param w the fourth value + * @return the segment */ public MemorySegment ints(int x, int y, int z, int w) { final var segment = malloc(16, JAVA_INT); @@ -493,6 +553,9 @@ public MemorySegment ints(int x, int y, int z, int w) { /** * Vararg version of {@link #malloc}. + * + * @param values the values + * @return the segment */ public MemorySegment ints(int... values) { return allocateFrom(JAVA_INT, values); @@ -502,6 +565,9 @@ public MemorySegment ints(int... values) { /** * Single value version of {@link #malloc}. + * + * @param x the first value + * @return the segment */ public MemorySegment longs(long x) { return allocateFrom(JAVA_LONG, x); @@ -509,6 +575,10 @@ public MemorySegment longs(long x) { /** * Two value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @return the segment */ public MemorySegment longs(long x, long y) { final var segment = malloc(16, JAVA_LONG); @@ -519,6 +589,11 @@ public MemorySegment longs(long x, long y) { /** * Three value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @return the segment */ public MemorySegment longs(long x, long y, long z) { final var segment = malloc(24, JAVA_LONG); @@ -530,6 +605,12 @@ public MemorySegment longs(long x, long y, long z) { /** * Four value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @param w the fourth value + * @return the segment */ public MemorySegment longs(long x, long y, long z, long w) { final var segment = malloc(32, JAVA_LONG); @@ -542,6 +623,9 @@ public MemorySegment longs(long x, long y, long z, long w) { /** * Vararg version of {@link #malloc}. + * + * @param values the values + * @return the segment */ public MemorySegment longs(long... values) { return allocateFrom(JAVA_LONG, values); @@ -551,6 +635,9 @@ public MemorySegment longs(long... values) { /** * Single value version of {@link #malloc}. + * + * @param x the first value + * @return the segment */ public MemorySegment floats(float x) { return allocateFrom(JAVA_FLOAT, x); @@ -558,6 +645,10 @@ public MemorySegment floats(float x) { /** * Two value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @return the segment */ public MemorySegment floats(float x, float y) { final var segment = malloc(8, JAVA_FLOAT); @@ -568,6 +659,11 @@ public MemorySegment floats(float x, float y) { /** * Three value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @return the segment */ public MemorySegment floats(float x, float y, float z) { final var segment = malloc(12, JAVA_FLOAT); @@ -579,6 +675,12 @@ public MemorySegment floats(float x, float y, float z) { /** * Four value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @param w the fourth value + * @return the segment */ public MemorySegment floats(float x, float y, float z, float w) { final var segment = malloc(16, JAVA_FLOAT); @@ -591,6 +693,9 @@ public MemorySegment floats(float x, float y, float z, float w) { /** * Vararg version of {@link #malloc}. + * + * @param values the values + * @return the segment */ public MemorySegment floats(float... values) { return allocateFrom(JAVA_FLOAT, values); @@ -600,6 +705,9 @@ public MemorySegment floats(float... values) { /** * Single value version of {@link #malloc}. + * + * @param x the first value + * @return the segment */ public MemorySegment doubles(double x) { return allocateFrom(JAVA_DOUBLE, x); @@ -607,6 +715,10 @@ public MemorySegment doubles(double x) { /** * Two value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @return the segment */ public MemorySegment doubles(double x, double y) { final var segment = malloc(16, JAVA_DOUBLE); @@ -617,6 +729,11 @@ public MemorySegment doubles(double x, double y) { /** * Three value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @return the segment */ public MemorySegment doubles(double x, double y, double z) { final var segment = malloc(24, JAVA_DOUBLE); @@ -628,6 +745,12 @@ public MemorySegment doubles(double x, double y, double z) { /** * Four value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @param w the fourth value + * @return the segment */ public MemorySegment doubles(double x, double y, double z, double w) { final var segment = malloc(32, JAVA_DOUBLE); @@ -640,6 +763,9 @@ public MemorySegment doubles(double x, double y, double z, double w) { /** * Vararg version of {@link #malloc}. + * + * @param values the values + * @return the segment */ public MemorySegment doubles(double... values) { return allocateFrom(JAVA_DOUBLE, values); @@ -649,6 +775,9 @@ public MemorySegment doubles(double... values) { /** * Single value version of {@link #malloc}. + * + * @param x the first value + * @return the segment */ public MemorySegment segments(MemorySegment x) { return allocateFrom(ADDRESS, x); @@ -656,6 +785,10 @@ public MemorySegment segments(MemorySegment x) { /** * Two value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @return the segment */ public MemorySegment segments(MemorySegment x, MemorySegment y) { final var segment = malloc(ADDRESS.byteSize() * 2, ADDRESS); @@ -666,6 +799,11 @@ public MemorySegment segments(MemorySegment x, MemorySegment y) { /** * Three value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @return the segment */ public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z) { final var segment = malloc(ADDRESS.byteSize() * 3, ADDRESS); @@ -677,6 +815,12 @@ public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z) /** * Four value version of {@link #malloc}. + * + * @param x the first value + * @param y the second value + * @param z the third value + * @param w the fourth value + * @return the segment */ public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z, MemorySegment w) { final var segment = malloc(ADDRESS.byteSize() * 4, ADDRESS); @@ -689,6 +833,9 @@ public MemorySegment segments(MemorySegment x, MemorySegment y, MemorySegment z, /** * Vararg version of {@link #malloc}. + * + * @param values the values + * @return the segment */ public MemorySegment segments(MemorySegment... values) { final var segment = malloc(ADDRESS.byteSize() * values.length, ADDRESS); diff --git a/src/main/java/overrun/marshal/Unmarshal.java b/src/main/java/overrun/marshal/Unmarshal.java index 0351bf8..6c63db7 100644 --- a/src/main/java/overrun/marshal/Unmarshal.java +++ b/src/main/java/overrun/marshal/Unmarshal.java @@ -33,22 +33,46 @@ * @since 0.1.0 */ public final class Unmarshal { + /** + * The max string size. + */ + public static final long STR_SIZE = Integer.MAX_VALUE - 8; private static final AddressLayout STRING_LAYOUT = ADDRESS.withTargetLayout( - MemoryLayout.sequenceLayout(Integer.MAX_VALUE - 8, JAVA_BYTE) + MemoryLayout.sequenceLayout(STR_SIZE, JAVA_BYTE) ); private static final VarHandle vh_stringArray = Marshal.arrayVarHandle(STRING_LAYOUT); private Unmarshal() { } + /** + * Unmarshal the given segment as a string. + * + * @param segment the segment + * @return the string + */ public static String unmarshalAsString(MemorySegment segment) { return segment.getString(0L); } + /** + * Unmarshal the given segment as a string. + * + * @param segment the segment + * @param charset the charset + * @return the string + */ public static String unmarshalAsString(MemorySegment segment, Charset charset) { return segment.getString(0L, charset); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static boolean[] unmarshal(OfBoolean elementLayout, MemorySegment segment) { final boolean[] arr = new boolean[checkArraySize(boolean[].class.getSimpleName(), segment.byteSize(), (int) elementLayout.byteSize())]; for (int i = 0, l = arr.length; i < l; i++) { @@ -57,46 +81,127 @@ public static boolean[] unmarshal(OfBoolean elementLayout, MemorySegment segment return arr; } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static char[] unmarshal(OfChar elementLayout, MemorySegment segment) { return segment.toArray(elementLayout); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static byte[] unmarshal(OfByte elementLayout, MemorySegment segment) { return segment.toArray(elementLayout); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static short[] unmarshal(OfShort elementLayout, MemorySegment segment) { return segment.toArray(elementLayout); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static int[] unmarshal(OfInt elementLayout, MemorySegment segment) { return segment.toArray(elementLayout); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static long[] unmarshal(OfLong elementLayout, MemorySegment segment) { return segment.toArray(elementLayout); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static float[] unmarshal(OfFloat elementLayout, MemorySegment segment) { return segment.toArray(elementLayout); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static double[] unmarshal(OfDouble elementLayout, MemorySegment segment) { return segment.toArray(elementLayout); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @param generator a function which produces a new array of the desired type and the provided length + * @param function a function to apply to each element + * @param the type of the element + * @return the array + */ public static T[] unmarshal(MemoryLayout elementLayout, MemorySegment segment, IntFunction generator, Function function) { return segment.elements(elementLayout).map(function).toArray(generator); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static MemorySegment[] unmarshal(AddressLayout elementLayout, MemorySegment segment) { return unmarshal(elementLayout, segment, MemorySegment[]::new, Function.identity()); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @return the array + */ public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment) { return unmarshal(elementLayout, segment, String[]::new, s -> s.get(STRING_LAYOUT, 0L).getString(0L)); } + /** + * Unmarshal the given segment as an array. + * + * @param elementLayout the source element layout + * @param segment the segment + * @param charset the charset + * @return the array + */ public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment, Charset charset) { return unmarshal(elementLayout, segment, String[]::new, s -> s.get(STRING_LAYOUT, 0L).getString(0L, charset)); } @@ -112,6 +217,12 @@ private static int checkArraySize(String typeName, long byteSize, int elemSize) return (int) arraySize; } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, boolean[] dst) { if (dst == null) return; for (int i = 0; i < dst.length; i++) { @@ -119,41 +230,89 @@ public static void copy(MemorySegment src, boolean[] dst) { } } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, char[] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_CHAR, 0L, dst, 0, dst.length); } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, byte[] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_BYTE, 0L, dst, 0, dst.length); } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, short[] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_SHORT, 0L, dst, 0, dst.length); } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, int[] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_INT, 0L, dst, 0, dst.length); } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, long[] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_LONG, 0L, dst, 0, dst.length); } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, float[] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_FLOAT, 0L, dst, 0, dst.length); } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, double[] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_DOUBLE, 0L, dst, 0, dst.length); } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + */ public static void copy(MemorySegment src, String[] dst) { if (dst == null) return; for (int i = 0; i < dst.length; i++) { @@ -161,6 +320,13 @@ public static void copy(MemorySegment src, String[] dst) { } } + /** + * Copies from the given segment to the destination. + * + * @param src the source segment + * @param dst the destination + * @param charset the charset + */ public static void copy(MemorySegment src, String[] dst, Charset charset) { if (dst == null) return; for (int i = 0; i < dst.length; i++) { diff --git a/src/main/java/overrun/marshal/gen/NullableRef.java b/src/main/java/overrun/marshal/gen/NullableRef.java index 1954503..07b06c9 100644 --- a/src/main/java/overrun/marshal/gen/NullableRef.java +++ b/src/main/java/overrun/marshal/gen/NullableRef.java @@ -31,5 +31,6 @@ @Documented @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.SOURCE) +@Deprecated(since = "0.1.0", forRemoval = true) public @interface NullableRef { } diff --git a/src/main/java/overrun/marshal/gen2/DowncallMethodData.java b/src/main/java/overrun/marshal/gen2/DowncallMethodData.java index f4ce759..a1fa3a9 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallMethodData.java +++ b/src/main/java/overrun/marshal/gen2/DowncallMethodData.java @@ -22,6 +22,12 @@ /** * Holds downcall method name * + * @param entrypoint the entrypoint + * @param handleName the handleName + * @param loaderName the loaderName + * @param exceptionString the exceptionString + * @param parameters the parameters + * @param skipFirstParam the skipFirstParam * @author squid233 * @since 0.1.0 */ diff --git a/src/main/java/overrun/marshal/gen2/struct/MemberData.java b/src/main/java/overrun/marshal/gen2/struct/MemberData.java index 38c1d0c..4c9265a 100644 --- a/src/main/java/overrun/marshal/gen2/struct/MemberData.java +++ b/src/main/java/overrun/marshal/gen2/struct/MemberData.java @@ -19,6 +19,8 @@ /** * Holds member * + * @param pathElementName the pathElementName + * @param varHandleName the varHandleName * @author squid233 * @since 0.1.0 */ From 760f8fe9e91f9a7b25153a45f13e123100d893e3 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:54:02 +0800 Subject: [PATCH 20/28] Update Upcall and test --- .../overrun/marshal/test/CDowncallTest.java | 20 ---- .../marshal/test/GLFWErrorCallback.java | 7 +- .../overrun/marshal/test/NativeLibLoader.java | 33 ------ .../java/overrun/marshal/test/UpcallTest.java | 50 --------- src/main/java/overrun/marshal/Downcall.java | 50 ++++++--- src/main/java/overrun/marshal/Upcall.java | 50 +++++++-- src/main/java/overrun/marshal/gen/Custom.java | 42 ------- .../java/overrun/marshal/gen/Default.java | 47 -------- .../java/overrun/marshal/gen/Downcall.java | 9 -- src/main/java/overrun/marshal/gen/Loader.java | 42 ------- .../overrun/marshal/test/ComplexUpcall.java | 105 ++++++++++++++++++ .../overrun/marshal/test/DowncallTest.java | 4 +- .../overrun/marshal/test/SimpleUpcall.java | 31 +++--- .../java/overrun/marshal/test/UpcallTest.java | 57 ++++++++++ 14 files changed, 256 insertions(+), 291 deletions(-) delete mode 100644 demo/src/main/java/overrun/marshal/test/NativeLibLoader.java delete mode 100644 demo/src/main/java/overrun/marshal/test/UpcallTest.java delete mode 100644 src/main/java/overrun/marshal/gen/Custom.java delete mode 100644 src/main/java/overrun/marshal/gen/Default.java delete mode 100644 src/main/java/overrun/marshal/gen/Loader.java create mode 100644 src/test/java/overrun/marshal/test/ComplexUpcall.java rename demo/src/main/java/overrun/marshal/test/Upcall2.java => src/test/java/overrun/marshal/test/SimpleUpcall.java (61%) create mode 100644 src/test/java/overrun/marshal/test/UpcallTest.java diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java index 91d650d..986911f 100644 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java @@ -55,15 +55,6 @@ interface CDowncallTest { MemorySegment testWithArgAndReturnValue(MemorySegment segment); - @Custom(""" - return testWithArgAndReturnValue(MemorySegment.NULL);""") - MemorySegment testWithCustomBody(); - - @Custom(""" - System.out.println("Hello world"); - return testWithArgAndReturnValue(MemorySegment.NULL);""") - MemorySegment testWithCustomBodyMultiline(); - @Access(AccessModifier.PRIVATE) int testWithPrivate(int i); @@ -179,15 +170,4 @@ interface CDowncallTest { void testAnotherEntrypoint(int[] segment); void testDuplicateName(int[] testDuplicateName); - - /** - * This is a test that tests all features. - * - * @param segment A memory segment - * @return Another memory segment - */ - @Access(AccessModifier.PROTECTED) - @Default("MemorySegment.NULL") - @Entrypoint("testAllFeatures") - MemorySegment testAll(MemorySegment segment); } diff --git a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java index 30d981b..91bbac5 100644 --- a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java +++ b/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java @@ -16,6 +16,7 @@ package overrun.marshal.test; +import overrun.marshal.MemoryStack; import overrun.marshal.gen.SizedSeg; import overrun.marshal.Upcall; @@ -46,9 +47,9 @@ default MemorySegment stub(Arena arena) { @Wrapper static GLFWErrorCallback wrap(MemorySegment stub) { - return TYPE.wrap(stub, (arena, methodHandle) -> (error, description) -> { - try { - methodHandle.invokeExact(error, arena.get().allocateFrom(description)); + return TYPE.wrap(stub, methodHandle -> (error, description) -> { + try (MemoryStack stack = MemoryStack.stackPush()) { + methodHandle.invokeExact(error, stack.allocateFrom(description)); } catch (Throwable e) { throw new RuntimeException(e); } diff --git a/demo/src/main/java/overrun/marshal/test/NativeLibLoader.java b/demo/src/main/java/overrun/marshal/test/NativeLibLoader.java deleted file mode 100644 index fc0e1ea..0000000 --- a/demo/src/main/java/overrun/marshal/test/NativeLibLoader.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import overrun.marshal.gen.Loader; - -import java.lang.foreign.Arena; -import java.lang.foreign.SymbolLookup; - -/** - * @author squid233 - * @since 0.1.0 - */ -public final class NativeLibLoader { - @Loader - public static SymbolLookup load(String basename) { - return SymbolLookup.libraryLookup(basename, Arena.ofAuto()); - } -} diff --git a/demo/src/main/java/overrun/marshal/test/UpcallTest.java b/demo/src/main/java/overrun/marshal/test/UpcallTest.java deleted file mode 100644 index 35b6147..0000000 --- a/demo/src/main/java/overrun/marshal/test/UpcallTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; -import java.util.Arrays; - -/** - * @author squid233 - * @since 0.1.0 - */ -public final class UpcallTest { - private static void glfwSetErrorCallback(Arena arena, GLFWErrorCallback callback) { - // simulates native code - final var mhTest1 = GLFWErrorCallback.wrap(callback.stub(arena)); - mhTest1.invoke(0x0, arena.allocateFrom("No error")); - mhTest1.invoke(0xffff, arena.allocateFrom("Some error")); - } - - public static void main(String[] args) { - final Arena arena = Arena.ofAuto(); - - glfwSetErrorCallback(arena, (error, description) -> - System.out.println("0x" + Integer.toHexString(error) + ": " + description)); - - Upcall2 upcall2 = (segment, arr) -> { - System.out.println("segment = " + segment); - System.out.println("arr = " + Arrays.toString(arr)); - return MemorySegment.NULL; - }; - System.out.println("descriptor = " + Upcall2.TYPE.descriptor()); - System.out.println("return = " + Upcall2.wrap(upcall2.stub(arena)).invoke(MemorySegment.ofAddress(0x20L), arena.allocateFrom(ValueLayout.JAVA_INT, 1, 2, 3, 4))); - } -} diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 31199e0..f06bab6 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -60,6 +60,8 @@ * instead, it will return {@code null} and automatically invokes the original method declared in the target class. *

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

Parameter Annotations

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

Example

*
{@code
  * public interface GL {
@@ -69,6 +71,7 @@
  * }
  * }
* + * @author squid233 * @see Critical * @see Entrypoint * @see Ref @@ -76,7 +79,6 @@ * @see SizedSeg * @see Skip * @see StrCharset - * @author squid233 * @since 0.1.0 */ public final class Downcall { @@ -242,23 +244,23 @@ private static Method findWrapper( "Couldn't find wrapper method in \{aClass}; mark it with @\{annotation.getSimpleName()}")); } - private static Method findSingleParamWrapper( - Class aClass, - Class annotation, - Class paramType, - Class returnType) { - return findWrapper(aClass, annotation, method -> { + private static Method findCEnumWrapper(Class aClass) { + return findWrapper(aClass, CEnum.Wrapper.class, method -> { final var types = method.getParameterTypes(); - return types.length == 1 && types[0] == paramType && returnType.isAssignableFrom(method.getReturnType()); + return types.length == 1 && + types[0] == int.class && + CEnum.class.isAssignableFrom(method.getReturnType()); }); } - private static Method findCEnumWrapper(Class aClass) { - return findSingleParamWrapper(aClass, CEnum.Wrapper.class, int.class, CEnum.class); - } - private static Method findUpcallWrapper(Class aClass) { - return findSingleParamWrapper(aClass, Upcall.Wrapper.class, MemorySegment.class, Upcall.class); + return findWrapper(aClass, Upcall.Wrapper.class, method -> { + final var types = method.getParameterTypes(); + return types.length == 2 && + types[0] == Arena.class && + types[1] == MemorySegment.class && + Upcall.class.isAssignableFrom(method.getReturnType()); + }); } @SuppressWarnings("unchecked") @@ -271,6 +273,21 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { method.getDeclaredAnnotation(Skip.class) == null && !Modifier.isStatic(method.getModifiers())) .toList(); + + // check method parameter + methodList.forEach(method -> { + final Class[] types = method.getParameterTypes(); + final boolean isFirstArena = types.length > 0 && Arena.class.isAssignableFrom(types[0]); + if (Upcall.class.isAssignableFrom(method.getReturnType()) && !isFirstArena) { + throw new IllegalStateException(STR."The first parameter of method \{method} is not an arena; however, the return type is an upcall"); + } + for (Parameter parameter : method.getParameters()) { + if (Upcall.class.isAssignableFrom(parameter.getType()) && !isFirstArena) { + throw new IllegalStateException(STR."The first parameter of method \{method} is not an arena; however, the parameter \{parameter.toString()} is an upcall"); + } + } + }); + final Map methodDataMap = LinkedHashMap.newLinkedHashMap(methodList.size()); final Map handleDataMap = LinkedHashMap.newLinkedHashMap(methodList.size()); final List addedHandleList = new ArrayList<>(methodList.size()); @@ -537,10 +554,13 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { final Method wrapper = findUpcallWrapper(returnType); blockCodeBuilder.aload(resultSlot) .ifThenElse(Opcode.IFNONNULL, - blockCodeBuilder1 -> blockCodeBuilder1.aload(resultSlot) + blockCodeBuilder1 -> blockCodeBuilder1.aload(allocatorSlot) + .aload(resultSlot) .invokestatic(cd_returnType, wrapper.getName(), - MethodTypeDesc.of(cd_returnType, CD_MemorySegment)), + MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), + CD_Arena, + CD_MemorySegment)), CodeBuilder::aconst_null); } else if (returnType == String.class) { final boolean hasCharset = getCharset(blockCodeBuilder, method); diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index 50c8336..6d45ff0 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -27,7 +27,7 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.Method; import java.util.Arrays; -import java.util.function.BiFunction; +import java.util.function.Function; import java.util.function.Supplier; /** @@ -60,12 +60,12 @@ * return TYPE.of(arena, this); * } * - * // Create a wrap method + * // Create an optional wrap method * @Wrapper * static MyCallback wrap(MemorySegment stub) { - * return TYPE.wrap(stub, (arena, mh) -> (i, p) -> { - * try { - * return mh.invokeExact(i, arena.get().allocateFrom(p)); + * return TYPE.wrap(stub, mh -> (i, p) -> { + * try (var arena = Arena.ofConfined()) { + * return mh.invokeExact(i, arena.allocateFrom(p)); * } catch (Throwable e) { * throw new RuntimeException(e); * } @@ -126,7 +126,7 @@ static Type type(Class tClass) { } /** - * Marks a method as an upcall stub provider. + * Marks a method as an upcall stub provider. The marked method must not throw any exception. * * @author squid233 * @see Upcall @@ -140,7 +140,7 @@ static Type type(Class tClass) { /** * Marks a static method as an upcall wrapper. *

- * The marked method must only contain one {@link java.lang.foreign.MemorySegment MemorySegment} parameter. + * The parameters of marked method must be only one {@link Arena} and one {@link MemorySegment}. * * @author squid233 * @see Upcall @@ -152,7 +152,8 @@ static Type type(Class tClass) { } /** - * The type wrapper of an upcall interface. You should always cache it as a static field. + * The type wrapper of an upcall interface. + * The constructor uses heavy reflective, and you should always cache it as a static field. * * @param The type of the upcall interface. * @author squid233 @@ -222,8 +223,8 @@ public MethodHandle downcall(MemorySegment stub) { * 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)); + public T wrap(MemorySegment stub, Function function) { + return function.apply(downcall(stub)); } /** @@ -257,4 +258,33 @@ private static MemoryLayout toMemoryLayout(Class carrier) { } } } + + /** + * The base container of {@link Arena} and {@link Upcall}. + * + * @param the type of the upcall + * @author squid233 + * @since 0.1.0 + */ + abstract class BaseContainer implements Upcall { + /** + * The arena. + */ + protected final Arena arena; + /** + * The upcall delegate. + */ + protected final T delegate; + + /** + * Creates a base container. + * + * @param arena the arena + * @param delegate the upcall delegate + */ + public BaseContainer(Arena arena, T delegate) { + this.arena = arena; + this.delegate = delegate; + } + } } diff --git a/src/main/java/overrun/marshal/gen/Custom.java b/src/main/java/overrun/marshal/gen/Custom.java deleted file mode 100644 index 1638b2b..0000000 --- a/src/main/java/overrun/marshal/gen/Custom.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import java.lang.annotation.*; - -/** - * Marks a method that uses custom code instead of generated code. - *

Example

- *
{@code
- * @Custom("""
- *     System.out.println("Hello world");""")
- * void myCode();
- * }
- * - * @author squid233 - * @since 0.1.0 - */ -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -@Deprecated(since = "0.1.0", forRemoval = true) -public @interface Custom { - /** - * {@return the custom code} - */ - String value(); -} diff --git a/src/main/java/overrun/marshal/gen/Default.java b/src/main/java/overrun/marshal/gen/Default.java deleted file mode 100644 index c29d47f..0000000 --- a/src/main/java/overrun/marshal/gen/Default.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import java.lang.annotation.*; - -/** - * Marks a method that has a default return value. - *

- * A method marked as {@code @Default} will not throw an exception or be invoked - * if it couldn't be found in the specified library. - *

Example

- *
{@code
- * @Default
- * void functionThatMightBeAbsent();
- *
- * @Default("42")
- * int anotherFunction();
- * }
- * - * @author squid233 - * @since 0.1.0 - */ -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -@Deprecated(since = "0.1.0", forRemoval = true) -public @interface Default { - /** - * {@return the default value of the method} - */ - String value() default ""; -} diff --git a/src/main/java/overrun/marshal/gen/Downcall.java b/src/main/java/overrun/marshal/gen/Downcall.java index f8b921c..133e1eb 100644 --- a/src/main/java/overrun/marshal/gen/Downcall.java +++ b/src/main/java/overrun/marshal/gen/Downcall.java @@ -30,11 +30,6 @@ *

* The access modifier of the method can be changed by marking methods with {@link Access @Access}. *

- * The {@link Custom @Custom} annotation overwrite all generated method body with the custom code. - *

- * The {@link Default @Default} annotation makes a method - * not to throw an exception but return {@code null} if it was not found from the native library. - *

* The {@link Critical @Critical} annotation indicates * that the annotated method is {@linkplain java.lang.foreign.Linker.Option#critical(boolean) critical}. *

@@ -54,8 +49,6 @@ * @author squid233 * @see Access * @see Critical - * @see Custom - * @see Default * @see Entrypoint * @see NullableRef * @see Ref @@ -83,8 +76,6 @@ /** * {@return the library loader} - * - * @see Loader */ Class loader() default Object.class; diff --git a/src/main/java/overrun/marshal/gen/Loader.java b/src/main/java/overrun/marshal/gen/Loader.java deleted file mode 100644 index ef3e7a7..0000000 --- a/src/main/java/overrun/marshal/gen/Loader.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import java.lang.annotation.*; -import java.lang.foreign.SymbolLookup; - -/** - * Marks a static method as the library loader. - *

- * The target method must only contain a parameter of type {@link Object} and returns {@link SymbolLookup}. - *

Example

- *
{@code
- * @Loader
- * public static SymbolLookup load(Object data) {
- *     //...
- * }
- * }
- * - * @author squid233 - * @see overrun.marshal.Downcall Downcall - * @since 0.1.0 - */ -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -@Deprecated(since = "0.1.0", forRemoval = true) -public @interface Loader { -} diff --git a/src/test/java/overrun/marshal/test/ComplexUpcall.java b/src/test/java/overrun/marshal/test/ComplexUpcall.java new file mode 100644 index 0000000..4d79b98 --- /dev/null +++ b/src/test/java/overrun/marshal/test/ComplexUpcall.java @@ -0,0 +1,105 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import overrun.marshal.Upcall; +import overrun.marshal.gen.SizedSeg; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.lang.invoke.MethodHandle; + +/** + * A complex upcall + * + * @author squid233 + * @since 0.1.0 + */ +@FunctionalInterface +public interface ComplexUpcall extends Upcall { + int[] invoke(int[] arr); + + /** + * the container + * + * @author squid233 + * @since 0.1.0 + */ + sealed class Container extends BaseContainer implements ComplexUpcall { + public static final Type TYPE = Upcall.type(); + + public Container(Arena arena, ComplexUpcall delegate) { + super(arena, delegate); + } + + @Stub + @SizedSeg(2 * Integer.BYTES) + public MemorySegment invoke(@SizedSeg(2 * Integer.BYTES) MemorySegment arr) { + return arena.allocateFrom(ValueLayout.JAVA_INT, invoke(arr.toArray(ValueLayout.JAVA_INT))); + } + + @Override + public int[] invoke(int[] arr) { + return delegate.invoke(arr); + } + + @Override + public MemorySegment stub(Arena arena) { + return TYPE.of(arena, this); + } + } + + /** + * the wrapper container + * + * @author squid233 + * @since 0.1.0 + */ + final class WrapperContainer extends Container { + private final MethodHandle handle; + + public WrapperContainer(Arena arena, MemorySegment stub) { + super(arena, null); + this.handle = TYPE.downcall(stub); + } + + @Override + public MemorySegment invoke(MemorySegment arr) { + try { + return (MemorySegment) handle.invokeExact(arr); + } catch (Throwable e) { + throw new RuntimeException(e); + } + } + + @Override + public int[] invoke(int[] arr) { + return invoke(arena.allocateFrom(ValueLayout.JAVA_INT, arr)).reinterpret(2 * Integer.BYTES).toArray(ValueLayout.JAVA_INT); + } + } + + @Wrapper + static Container wrap(Arena arena, MemorySegment stub) { + return new WrapperContainer(arena, stub); + } + + @Override + default MemorySegment stub(Arena arena) { + return new Container(arena, this).stub(arena); + } +} diff --git a/src/test/java/overrun/marshal/test/DowncallTest.java b/src/test/java/overrun/marshal/test/DowncallTest.java index 554dbe3..bf4aaec 100644 --- a/src/test/java/overrun/marshal/test/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/DowncallTest.java @@ -42,14 +42,14 @@ static void beforeAll() { } @BeforeEach - void beforeEach() { + void setUp() { stdout = System.out; outputStream = new ByteArrayOutputStream(); System.setOut(new PrintStream(outputStream)); } @AfterEach - void afterEach() { + void tearDown() { System.setOut(stdout); outputStream.reset(); } diff --git a/demo/src/main/java/overrun/marshal/test/Upcall2.java b/src/test/java/overrun/marshal/test/SimpleUpcall.java similarity index 61% rename from demo/src/main/java/overrun/marshal/test/Upcall2.java rename to src/test/java/overrun/marshal/test/SimpleUpcall.java index 9873d3f..ab97b40 100644 --- a/demo/src/main/java/overrun/marshal/test/Upcall2.java +++ b/src/test/java/overrun/marshal/test/SimpleUpcall.java @@ -16,42 +16,37 @@ package overrun.marshal.test; -import overrun.marshal.gen.SizedSeg; import overrun.marshal.Upcall; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; -import java.lang.foreign.ValueLayout; /** + * A simple upcall + * * @author squid233 * @since 0.1.0 */ @FunctionalInterface -public interface Upcall2 extends Upcall { - Type TYPE = Upcall.type(); - - MemorySegment invoke(MemorySegment segment, int[] arr); +public interface SimpleUpcall extends Upcall { + Type TYPE = Upcall.type(); - @SizedSeg(16L) @Stub - default MemorySegment invoke(@SizedSeg(8L) MemorySegment segment, @SizedSeg(4L * Integer.BYTES) MemorySegment arr) { - return invoke(segment, arr.toArray(ValueLayout.JAVA_INT)); - } - - @Override - default MemorySegment stub(Arena arena) { - return TYPE.of(arena, this); - } + int invoke(int i); @Wrapper - static Upcall2 wrap(MemorySegment stub) { - return TYPE.wrap(stub, (arenaSupplier, methodHandle) -> (segment, arr) -> { + static SimpleUpcall wrap(Arena arena, MemorySegment stub) { + return TYPE.wrap(stub, methodHandle -> i -> { try { - return (MemorySegment) methodHandle.invokeExact(segment, arenaSupplier.get().allocateFrom(ValueLayout.JAVA_INT, arr)); + return (int) methodHandle.invokeExact(i); } catch (Throwable e) { throw new RuntimeException(e); } }); } + + @Override + default MemorySegment stub(Arena arena) { + return TYPE.of(arena, this); + } } diff --git a/src/test/java/overrun/marshal/test/UpcallTest.java b/src/test/java/overrun/marshal/test/UpcallTest.java new file mode 100644 index 0000000..db6b441 --- /dev/null +++ b/src/test/java/overrun/marshal/test/UpcallTest.java @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import org.junit.jupiter.api.Test; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Test upcall + * + * @author squid233 + * @since 0.1.0 + */ +public final class UpcallTest { + private int invokeSimpleUpcall(Arena arena, MemorySegment upcall) { + return SimpleUpcall.wrap(arena, upcall).invoke(42); + } + + private int[] invokeComplexUpcall(Arena arena, MemorySegment upcall) { + return ComplexUpcall.wrap(arena, upcall).invoke(new int[]{4, 2}); + } + + @Test + void testSimpleUpcall() { + final Arena arena = Arena.ofAuto(); + final SimpleUpcall upcall = i -> i * 2; + final MemorySegment stub = upcall.stub(arena); + assertEquals(84, invokeSimpleUpcall(arena, stub)); + } + + @Test + void testComplexUpcall() { + final Arena arena = Arena.ofAuto(); + final ComplexUpcall upcall = arr -> new int[]{arr[0] * 4, arr[1] * 2}; + final MemorySegment stub = upcall.stub(arena); + assertArrayEquals(new int[]{16, 4}, invokeComplexUpcall(arena, stub)); + } +} From 5c0482be421772efa290eaaedf5d48ac849a44e8 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Tue, 23 Jan 2024 09:47:05 +0800 Subject: [PATCH 21/28] Update javadoc and test --- .../java/overrun/marshal/test/MyEnum.java | 2 +- .../java/overrun/marshal/{gen => }/CEnum.java | 2 +- src/main/java/overrun/marshal/Downcall.java | 34 +++++++-- src/main/java/overrun/marshal/Marshal.java | 2 - src/main/java/overrun/marshal/Upcall.java | 37 +++++----- .../java/overrun/marshal/gen2/TypeUse.java | 2 +- .../java/overrun/marshal/package-info.java | 4 +- .../overrun/marshal/test/ComplexUpcall.java | 2 +- .../marshal/test/DowncallInheritTest.java | 39 +++++++++++ .../overrun/marshal/test/DowncallTest.java | 2 +- src/test/java/overrun/marshal/test/ID1.java | 27 ++++++++ src/test/java/overrun/marshal/test/ID2.java | 29 ++++++++ src/test/java/overrun/marshal/test/ID3.java | 69 +++++++++++++++++++ .../java/overrun/marshal/test/MyEnum.java | 2 +- 14 files changed, 219 insertions(+), 34 deletions(-) rename src/main/java/overrun/marshal/{gen => }/CEnum.java (98%) create mode 100644 src/test/java/overrun/marshal/test/DowncallInheritTest.java create mode 100644 src/test/java/overrun/marshal/test/ID1.java create mode 100644 src/test/java/overrun/marshal/test/ID2.java create mode 100644 src/test/java/overrun/marshal/test/ID3.java diff --git a/demo/src/main/java/overrun/marshal/test/MyEnum.java b/demo/src/main/java/overrun/marshal/test/MyEnum.java index f1291d0..31895fb 100644 --- a/demo/src/main/java/overrun/marshal/test/MyEnum.java +++ b/demo/src/main/java/overrun/marshal/test/MyEnum.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.gen.CEnum; +import overrun.marshal.CEnum; /** * @author squid233 diff --git a/src/main/java/overrun/marshal/gen/CEnum.java b/src/main/java/overrun/marshal/CEnum.java similarity index 98% rename from src/main/java/overrun/marshal/gen/CEnum.java rename to src/main/java/overrun/marshal/CEnum.java index 38eaf46..eb12d99 100644 --- a/src/main/java/overrun/marshal/gen/CEnum.java +++ b/src/main/java/overrun/marshal/CEnum.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen; +package overrun.marshal; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index f06bab6..2861121 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -51,6 +51,8 @@ * You can load native libraries with {@link #load(Class, SymbolLookup)}. * This method generates a hidden class that loads method handle with the given symbol lookup. *

Methods

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

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

* {@link Entrypoint @Entrypoint} specifies the entrypoint of the annotated method. @@ -86,7 +88,7 @@ public final class Downcall { private static final ClassDesc CD_Addressable = ClassDesc.of("overrun.marshal.Addressable"); private static final ClassDesc CD_AddressLayout = ClassDesc.of("java.lang.foreign.AddressLayout"); private static final ClassDesc CD_Arena = ClassDesc.of("java.lang.foreign.Arena"); - private static final ClassDesc CD_CEnum = ClassDesc.of("overrun.marshal.gen.CEnum"); + private static final ClassDesc CD_CEnum = ClassDesc.of("overrun.marshal.CEnum"); private static final ClassDesc CD_Charset = ClassDesc.of("java.nio.charset.Charset"); private static final ClassDesc CD_Checks = ClassDesc.of("overrun.marshal.Checks"); private static final ClassDesc CD_FunctionDescriptor = ClassDesc.of("java.lang.foreign.FunctionDescriptor"); @@ -814,15 +816,39 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { } } - public static T load(Class targetClass, SymbolLookup lookup) { + /** + * Loads the given class with the given symbol lookup. + * + * @param targetClass the target class + * @param lookup the symbol lookup + * @param the type of the target class + * @return the loaded implementation instance of the target class + */ + public static T load(Class targetClass, SymbolLookup lookup) { return loadBytecode(targetClass, lookup); } + /** + * Loads the caller class with the given library name. + * + * @param libname the library name + * @param the type of the caller class + * @return the loaded implementation instance of the caller class + */ + @SuppressWarnings("unchecked") public static T load(String libname) { - return load(STACK_WALKER.getCallerClass(), SymbolLookup.libraryLookup(libname, Arena.ofAuto())); + return load((Class) STACK_WALKER.getCallerClass(), SymbolLookup.libraryLookup(libname, Arena.ofAuto())); } + /** + * Loads the caller class with the given symbol lookup. + * + * @param lookup the symbol lookup + * @param the type of the caller class + * @return the loaded implementation instance of the caller class + */ + @SuppressWarnings("unchecked") public static T load(SymbolLookup lookup) { - return load(STACK_WALKER.getCallerClass(), lookup); + return load((Class) STACK_WALKER.getCallerClass(), lookup); } } diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java index d7da620..2a87c13 100644 --- a/src/main/java/overrun/marshal/Marshal.java +++ b/src/main/java/overrun/marshal/Marshal.java @@ -16,8 +16,6 @@ package overrun.marshal; -import overrun.marshal.gen.CEnum; - import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentAllocator; diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index 6d45ff0..99fa582 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -46,13 +46,9 @@ * Type TYPE = Upcall.type(); * * // The function to be invoked in C - * int invoke(int i, String p); - * - * // The stub provider + * // Also the stub provider * @Stub - * default int invoke(int i, MemorySegment p) { - * return invoke(error, description.reinterpret(Long.MAX_VALUE).getString(0)); - * } + * int invoke(int i); * * // Create an upcall stub segment with Type * @Override @@ -62,10 +58,10 @@ * * // Create an optional wrap method * @Wrapper - * static MyCallback wrap(MemorySegment stub) { - * return TYPE.wrap(stub, mh -> (i, p) -> { - * try (var arena = Arena.ofConfined()) { - * return mh.invokeExact(i, arena.allocateFrom(p)); + * static MyCallback wrap(Arena arena, MemorySegment stub) { + * return TYPE.wrap(stub, mh -> i -> { + * try { + * return (int) mh.invokeExact(i); * } catch (Throwable e) { * throw new RuntimeException(e); * } @@ -74,7 +70,7 @@ * } * * // C downcall - * setMyCallback((i, p) -> System.out.println(i + ": " + p)); + * setMyCallback(i -> i * 2); * }

* * @author squid233 @@ -177,20 +173,21 @@ private Type(Class tClass) { } final var returnType = method.getReturnType(); final MemoryLayout[] memoryLayouts = Arrays.stream(method.getParameters()) - .map(p -> { - final MemoryLayout layout = toMemoryLayout(p.getType()); - final SizedSeg sizedSeg = p.getDeclaredAnnotation(SizedSeg.class); - if (sizedSeg != null && layout instanceof AddressLayout addressLayout) { - return addressLayout.withTargetLayout(MemoryLayout.sequenceLayout(sizedSeg.value(), ValueLayout.JAVA_BYTE)); - } - return layout; - }) + .map(p -> withSizedSeg(toMemoryLayout(p.getType()), p.getDeclaredAnnotation(SizedSeg.class))) .toArray(MemoryLayout[]::new); if (returnType == void.class) { descriptor = FunctionDescriptor.ofVoid(memoryLayouts); } else { - descriptor = FunctionDescriptor.of(toMemoryLayout(returnType), memoryLayouts); + descriptor = FunctionDescriptor.of(withSizedSeg(toMemoryLayout(returnType), method.getDeclaredAnnotation(SizedSeg.class)), + memoryLayouts); + } + } + + private static MemoryLayout withSizedSeg(MemoryLayout layout, SizedSeg sizedSeg) { + if (sizedSeg != null && layout instanceof AddressLayout addressLayout) { + return addressLayout.withTargetLayout(MemoryLayout.sequenceLayout(sizedSeg.value(), ValueLayout.JAVA_BYTE)); } + return layout; } /** diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java index 24d10cc..19787ee 100644 --- a/src/main/java/overrun/marshal/gen2/TypeUse.java +++ b/src/main/java/overrun/marshal/gen2/TypeUse.java @@ -16,7 +16,7 @@ package overrun.marshal.gen2; -import overrun.marshal.gen.CEnum; +import overrun.marshal.CEnum; import overrun.marshal.gen.struct.StructRef; import overrun.marshal.gen1.Spec; import overrun.marshal.internal.Util; diff --git a/src/main/java/overrun/marshal/package-info.java b/src/main/java/overrun/marshal/package-info.java index c7deb6b..784b10d 100644 --- a/src/main/java/overrun/marshal/package-info.java +++ b/src/main/java/overrun/marshal/package-info.java @@ -18,9 +18,9 @@ * The main package of marshal. * * @author squid233 - * @see overrun.marshal.gen.Downcall + * @see overrun.marshal.Downcall * @see overrun.marshal.Upcall - * @see overrun.marshal.struct + * @see overrun.marshal.gen.struct * @since 0.1.0 */ package overrun.marshal; diff --git a/src/test/java/overrun/marshal/test/ComplexUpcall.java b/src/test/java/overrun/marshal/test/ComplexUpcall.java index 4d79b98..9629be1 100644 --- a/src/test/java/overrun/marshal/test/ComplexUpcall.java +++ b/src/test/java/overrun/marshal/test/ComplexUpcall.java @@ -89,7 +89,7 @@ public MemorySegment invoke(MemorySegment arr) { @Override public int[] invoke(int[] arr) { - return invoke(arena.allocateFrom(ValueLayout.JAVA_INT, arr)).reinterpret(2 * Integer.BYTES).toArray(ValueLayout.JAVA_INT); + return invoke(arena.allocateFrom(ValueLayout.JAVA_INT, arr)).toArray(ValueLayout.JAVA_INT); } } diff --git a/src/test/java/overrun/marshal/test/DowncallInheritTest.java b/src/test/java/overrun/marshal/test/DowncallInheritTest.java new file mode 100644 index 0000000..13acb9b --- /dev/null +++ b/src/test/java/overrun/marshal/test/DowncallInheritTest.java @@ -0,0 +1,39 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * test inherit downcall + * + * @author squid233 + * @since 0.1.0 + */ +public final class DowncallInheritTest { + @Test + void testInheritDowncall() { + final ID3 d = ID3.INSTANCE; + assertEquals(84, d.mul2(42)); + int[] arr = {0}; + d.get(arr); + assertArrayEquals(new int[]{42}, arr); + } +} diff --git a/src/test/java/overrun/marshal/test/DowncallTest.java b/src/test/java/overrun/marshal/test/DowncallTest.java index bf4aaec..940e4e6 100644 --- a/src/test/java/overrun/marshal/test/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/DowncallTest.java @@ -79,7 +79,7 @@ void testSkip() { Assertions.assertEquals("testSkip", outputStream.toString()); } - @ParameterizedTest + @ParameterizedTest(name = "testDefault(testDefaultNull = [" + ParameterizedTest.INDEX_PLACEHOLDER + "] " + ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER + ")") @ValueSource(booleans = {false, true}) void testDefault(boolean testDefaultNull) { IDowncall.getInstance(testDefaultNull).testDefault(); diff --git a/src/test/java/overrun/marshal/test/ID1.java b/src/test/java/overrun/marshal/test/ID1.java new file mode 100644 index 0000000..626c989 --- /dev/null +++ b/src/test/java/overrun/marshal/test/ID1.java @@ -0,0 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +/** + * downcall interface 1 + * + * @author squid233 + * @since 0.1.0 + */ +public interface ID1 { + int mul2(int i); +} diff --git a/src/test/java/overrun/marshal/test/ID2.java b/src/test/java/overrun/marshal/test/ID2.java new file mode 100644 index 0000000..12fdb7a --- /dev/null +++ b/src/test/java/overrun/marshal/test/ID2.java @@ -0,0 +1,29 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import overrun.marshal.gen.Ref; + +/** + * downcall interface 2 + * + * @author squid233 + * @since 0.1.0 + */ +public interface ID2 { + void get(@Ref int[] arr); +} diff --git a/src/test/java/overrun/marshal/test/ID3.java b/src/test/java/overrun/marshal/test/ID3.java new file mode 100644 index 0000000..ccb1f40 --- /dev/null +++ b/src/test/java/overrun/marshal/test/ID3.java @@ -0,0 +1,69 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import overrun.marshal.Downcall; + +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.Optional; + +/** + * @author squid233 + * @since 0.1.0 + */ +public interface ID3 extends ID1, ID2 { + final class Provider { + private static final Linker LINKER = Linker.nativeLinker(); + private static final Arena ARENA = Arena.ofAuto(); + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + private static final MemorySegment s_mul2, s_get; + + static { + try { + s_mul2 = segment(LOOKUP.findStatic(Provider.class, "mul2_", MethodType.methodType(int.class, int.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)); + s_get = segment(LOOKUP.findStatic(Provider.class, "get", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + private static int mul2_(int i) { + return i * 2; + } + + private static void get(MemorySegment arr) { + arr.reinterpret(ValueLayout.JAVA_INT.byteSize()).setAtIndex(ValueLayout.JAVA_INT, 0, 42); + } + + private static MemorySegment segment(MethodHandle handle, FunctionDescriptor fd) { + return LINKER.upcallStub(handle, fd, ARENA); + } + + static SymbolLookup load() { + return name -> switch (name) { + case "mul2" -> Optional.of(s_mul2); + case "get" -> Optional.of(s_get); + default -> Optional.empty(); + }; + } + } + + ID3 INSTANCE = Downcall.load(Provider.load()); +} diff --git a/src/test/java/overrun/marshal/test/MyEnum.java b/src/test/java/overrun/marshal/test/MyEnum.java index 585cc7f..cc949f3 100644 --- a/src/test/java/overrun/marshal/test/MyEnum.java +++ b/src/test/java/overrun/marshal/test/MyEnum.java @@ -16,7 +16,7 @@ package overrun.marshal.test; -import overrun.marshal.gen.CEnum; +import overrun.marshal.CEnum; /** * Enum From f5e8590d7132f06f4d7485304aff3e7946244bf2 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:05:58 +0800 Subject: [PATCH 22/28] Fix bug; update javadoc and test --- .../overrun/marshal/test/CDowncallTest.java | 173 ------------------ src/main/java/overrun/marshal/Downcall.java | 8 +- src/main/java/overrun/marshal/Marshal.java | 4 +- .../java/overrun/marshal/MemoryStack.java | 7 + src/main/java/overrun/marshal/Upcall.java | 1 + src/main/java/overrun/marshal/gen/Access.java | 42 ----- .../overrun/marshal/gen/AccessModifier.java | 1 - .../java/overrun/marshal/gen/Downcall.java | 86 --------- .../java/overrun/marshal/gen/NullableRef.java | 36 ---- .../marshal/gen1/VariableStatement.java | 1 + .../marshal/test/DowncallProvider.java | 89 ++++++++- .../overrun/marshal/test/DowncallTest.java | 118 ++++++++++-- .../java/overrun/marshal/test/IDowncall.java | 44 ++++- .../java/overrun/marshal/test/MyEnum.java | 10 + 14 files changed, 247 insertions(+), 373 deletions(-) delete mode 100644 demo/src/main/java/overrun/marshal/test/CDowncallTest.java delete mode 100644 src/main/java/overrun/marshal/gen/Access.java delete mode 100644 src/main/java/overrun/marshal/gen/Downcall.java delete mode 100644 src/main/java/overrun/marshal/gen/NullableRef.java diff --git a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java b/demo/src/main/java/overrun/marshal/test/CDowncallTest.java deleted file mode 100644 index 986911f..0000000 --- a/demo/src/main/java/overrun/marshal/test/CDowncallTest.java +++ /dev/null @@ -1,173 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import overrun.marshal.MemoryStack; -import overrun.marshal.gen.*; -import overrun.marshal.gen.struct.ByValue; -import overrun.marshal.gen.struct.StructRef; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; - -/** - * Test basic features - * - * @author squid233 - * @since 0.1.0 - */ -@Downcall(libname = "NativeLib.dll", nonFinal = true) -interface CDowncallTest { - int CONST_VALUE = 42; - boolean BOOL_VALUE = true; - /** - * A string value - */ - String STR_VALUE = "Hello world"; - @Skip - int SKIPPED = -1; - - /** - * This is a test method with javadoc. - * - * @return an integer - */ - int testWithDocAndReturnValue(); - - int testWithReturnValue(); - - void testWithArgument(int i, MemorySegment holder); - - MemorySegment testWithArgAndReturnValue(MemorySegment segment); - - @Access(AccessModifier.PRIVATE) - int testWithPrivate(int i); - - void testWithArray(int i, MemorySegment arr, MemorySegment nullableArr); - - void testWithArray(int i, int[] arr, @NullableRef int[] nullableArr); - - void testWithArray(SegmentAllocator allocator, int i, int[] arr, @NullableRef int[] nullableArr); - - void testWithArray(Arena arena, int i, int[] arr, @NullableRef int[] nullableArr); - - void testWithArray(MemoryStack stack, int i, int[] arr, @NullableRef int[] nullableArr); - - void testWithOneRef(MemorySegment arr); - - /** - * Test with ref - * - * @param arr arr - */ - void testWithOneRef(@Ref int[] arr); - - void testWithRefArray(MemorySegment arr0, MemorySegment arr1, MemorySegment arr2, MemorySegment arr3, MemorySegment arr4, MemorySegment arr5); - - void testWithRefArray(int[] arr0, @Ref int[] arr1, @NullableRef @Ref int[] arr2, boolean[] arr3, @Ref boolean[] arr4, @Sized(3) int[] arr5); - - void testWithString(MemorySegment str1, MemorySegment str2, MemorySegment nullableStr); - - void testWithString(String str1, @StrCharset("UTF-16") String str2, @NullableRef String nullableStr); - - @Entrypoint("testReturnString") - MemorySegment ntestReturnString(); - - String testReturnString(); - - @Entrypoint("testReturnStringUTF16") - MemorySegment ntestReturnStringUTF16(); - - @StrCharset("UTF-16") - String testReturnStringUTF16(); - - MemorySegment testStringArray(MemorySegment arr, MemorySegment refArr); - - String[] testStringArray(String[] arr, @Ref String[] refArr); - - @StrCharset("UTF-16") - String[] testStringArrayUTF16(@StrCharset("UTF-16") String[] arr, @Ref @StrCharset("UTF-16") String[] refArr); - - @Entrypoint("testWithReturnArray") - MemorySegment ntestWithReturnArray(); - - boolean[] testWithReturnArray(); - - void testMixArrSeg(MemorySegment segment, MemorySegment arr); - - void testMixArrSeg(MemorySegment segment, @Ref int[] arr); - - @Critical(allowHeapAccess = true) - void testCriticalTrue(); - - @Critical(allowHeapAccess = false) - void testCriticalFalse(); - - @Entrypoint("testReturnSizedArr") - @SizedSeg(4 * Integer.BYTES) - MemorySegment ntestReturnSizedArr(); - - @Sized(4) - int[] testReturnSizedArr(); - - @SizedSeg(4L) - MemorySegment testReturnSizedSeg(); - - void testUpcall(MemorySegment cb, MemorySegment nullableCb); - - void testUpcall(Arena arena, GLFWErrorCallback cb, @NullableRef GLFWErrorCallback nullableCb); - - @Entrypoint("testReturnUpcall") - MemorySegment ntestReturnUpcall(); - - GLFWErrorCallback testReturnUpcall(Arena arena); - - void testStruct(MemorySegment struct, MemorySegment nullableStruct); - - void testStruct(@StructRef("overrun.marshal.test.Vector3") Object struct, @NullableRef @StructRef("overrun.marshal.test.Vector3") Object nullableStruct); - - @StructRef("overrun.marshal.test.StructTest") - Object testReturnStruct(); - - @ByValue - @StructRef("overrun.marshal.test.StructTest") - Object returnByValueStruct(SegmentAllocator allocator); - - @ByValue - @StructRef("overrun.marshal.test.StructTest") - Object returnByValueStructWithArena(Arena arena); - - int testEnumValue(int value); - - MyEnum testEnumValue(MyEnum value); - - int testEnumValueWithRef(int value, MemorySegment ref); - - MyEnum testEnumValueWithRef(MyEnum value, @Ref int[] ref); - - void testNameMemoryStack(MemorySegment stack, MemorySegment arr); - - void testNameMemoryStack(MemorySegment stack, int[] arr); - - @Entrypoint("_testAnotherEntrypoint") - void testAnotherEntrypoint(MemorySegment segment); - - void testAnotherEntrypoint(int[] segment); - - void testDuplicateName(int[] testDuplicateName); -} diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 2861121..0a923cc 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -551,7 +551,8 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { final Method wrapper = findCEnumWrapper(returnType); blockCodeBuilder.invokestatic(cd_returnType, wrapper.getName(), - MethodTypeDesc.of(cd_returnType, CD_int)); + MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), CD_int), + wrapper.getDeclaringClass().isInterface()); } else if (Upcall.class.isAssignableFrom(returnType)) { final Method wrapper = findUpcallWrapper(returnType); blockCodeBuilder.aload(resultSlot) @@ -562,7 +563,8 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { wrapper.getName(), MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), CD_Arena, - CD_MemorySegment)), + CD_MemorySegment), + wrapper.getDeclaringClass().isInterface()), CodeBuilder::aconst_null); } else if (returnType == String.class) { final boolean hasCharset = getCharset(blockCodeBuilder, method); @@ -586,7 +588,7 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { blockCodeBuilder.invokestatic(CD_Unmarshal, "unmarshal", MethodTypeDesc.of(cd_returnType, - convertToValueLayoutCD(returnType), + convertToValueLayoutCD(componentType), CD_MemorySegment)); } } diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java index 2a87c13..a278847 100644 --- a/src/main/java/overrun/marshal/Marshal.java +++ b/src/main/java/overrun/marshal/Marshal.java @@ -209,9 +209,11 @@ public static MemorySegment marshal(SegmentAllocator allocator, double[] arr) { * @param allocator the allocator * @param arr the array * @param function a function to apply to each element + * @param the type of the allocator + * @param the type of the element * @return the segment */ - public static MemorySegment marshal(A allocator, T[] arr, BiFunction function) { + public static MemorySegment marshal(A allocator, T[] arr, BiFunction function) { if (arr == null) return MemorySegment.NULL; final MemorySegment segment = allocator.allocate(ADDRESS, arr.length); for (int i = 0, l = arr.length; i < l; i++) { diff --git a/src/main/java/overrun/marshal/MemoryStack.java b/src/main/java/overrun/marshal/MemoryStack.java index b66baa8..3a80290 100644 --- a/src/main/java/overrun/marshal/MemoryStack.java +++ b/src/main/java/overrun/marshal/MemoryStack.java @@ -72,6 +72,8 @@ protected MemoryStack(MemorySegment segment) { * Creates a new {@code MemoryStack} with the default size. * *

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

+ * + * @return the memory stack */ public static MemoryStack create() { return create(DEFAULT_STACK_SIZE); @@ -83,6 +85,7 @@ public static MemoryStack create() { *

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

* * @param byteSize the maximum number of bytes that may be allocated on the stack + * @return the memory stack */ public static MemoryStack create(long byteSize) { return create(Arena.ofAuto(), byteSize); @@ -95,6 +98,7 @@ public static MemoryStack create(long byteSize) { * * @param arena the arena for allocating buffer * @param byteSize the maximum number of bytes that may be allocated on the stack + * @return the memory stack */ public static MemoryStack create(Arena arena, long byteSize) { return create(arena.allocate(byteSize)); @@ -106,6 +110,7 @@ public static MemoryStack create(Arena arena, long byteSize) { *

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

* * @param segment the backing memory segment + * @return the memory stack */ public static MemoryStack create(MemorySegment segment) { return DEBUG_STACK ? @@ -193,6 +198,8 @@ public long pointer() { * *

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

+ * + * @param pointer the pointer */ public void setPointer(long pointer) { if (CHECKS) { diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index 99fa582..fc13fb5 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -221,6 +221,7 @@ public MethodHandle downcall(MemorySegment stub) { * @return the downcall type */ public T wrap(MemorySegment stub, Function function) { + if (stub.address() == 0L) return null; return function.apply(downcall(stub)); } diff --git a/src/main/java/overrun/marshal/gen/Access.java b/src/main/java/overrun/marshal/gen/Access.java deleted file mode 100644 index 92a0c6b..0000000 --- a/src/main/java/overrun/marshal/gen/Access.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import java.lang.annotation.*; - -/** - * Changes the access modifier of a method. - *

Example

- *
{@code
- * @Access(AccessModifier.PROTECTED)
- * int ngetStatus(MemorySegment dst);
- * }
- * - * @author squid233 - * @see AccessModifier - * @since 0.1.0 - */ -@Documented -@Target(ElementType.METHOD) -@Retention(RetentionPolicy.SOURCE) -@Deprecated(since = "0.1.0", forRemoval = true) -public @interface Access { - /** - * {@return the access modifier of the method} - */ - AccessModifier value() default AccessModifier.PUBLIC; -} diff --git a/src/main/java/overrun/marshal/gen/AccessModifier.java b/src/main/java/overrun/marshal/gen/AccessModifier.java index dfd4ef2..43fe74d 100644 --- a/src/main/java/overrun/marshal/gen/AccessModifier.java +++ b/src/main/java/overrun/marshal/gen/AccessModifier.java @@ -20,7 +20,6 @@ * The access modifier. * * @author squid233 - * @see Access * @since 0.1.0 */ public enum AccessModifier { diff --git a/src/main/java/overrun/marshal/gen/Downcall.java b/src/main/java/overrun/marshal/gen/Downcall.java deleted file mode 100644 index 133e1eb..0000000 --- a/src/main/java/overrun/marshal/gen/Downcall.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import java.lang.annotation.*; - -/** - * Marks a class or interface as a downcall handles provider. - *

Constants

- * The generated file will include constants of primitive - * (boolean, byte, short, int, long, float, double) and String types in the marked class or interface. - *

Methods

- * Methods annotated with {@link Skip @Skip} will be skipped while generating. - *

- * The javadoc of the original method will be copied. - *

- * The access modifier of the method can be changed by marking methods with {@link Access @Access}. - *

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

- * The generator tries to generate static methods that invoke native functions, - * which has the same name or is specified by {@link Entrypoint @Entrypoint}. - *

Parameter Annotations

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

Example

- *
{@code
- * @Downcall(libname = "libGL.so", name = "GL")
- * interface CGL {
- *     int COLOR_BUFFER_BIT = 0x00004000;
- *     void glClear(int mask);
- * }
- * }
- * - * @author squid233 - * @see Access - * @see Critical - * @see Entrypoint - * @see NullableRef - * @see Ref - * @see Sized - * @see SizedSeg - * @since 0.1.0 - */ -@Documented -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.SOURCE) -@Deprecated(since = "0.1.0", forRemoval = true) -public @interface Downcall { - /** - * {@return the name of the native library} - *

- * Might be a filename with an extension - * or the basename of {@linkplain #loader() the library loader}. - */ - String libname(); - - /** - * {@return the name of the generated class} - */ - String name() default ""; - - /** - * {@return the library loader} - */ - Class loader() default Object.class; - - /** - * {@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/gen/NullableRef.java b/src/main/java/overrun/marshal/gen/NullableRef.java deleted file mode 100644 index 07b06c9..0000000 --- a/src/main/java/overrun/marshal/gen/NullableRef.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -import java.lang.annotation.*; - -/** - * Marks a parameter as nullable. Not to confuse with {@link Ref}. - *

Example

- *
{@code
- * void test(@NullableRef int[] arr);
- * }
- * - * @author squid233 - * @since 0.1.0 - */ -@Documented -@Target(ElementType.PARAMETER) -@Retention(RetentionPolicy.SOURCE) -@Deprecated(since = "0.1.0", forRemoval = true) -public @interface NullableRef { -} diff --git a/src/main/java/overrun/marshal/gen1/VariableStatement.java b/src/main/java/overrun/marshal/gen1/VariableStatement.java index 132474c..d21951b 100644 --- a/src/main/java/overrun/marshal/gen1/VariableStatement.java +++ b/src/main/java/overrun/marshal/gen1/VariableStatement.java @@ -111,6 +111,7 @@ public VariableStatement setFinal(boolean isFinal) { * setAddSemicolon * * @param addSemicolon addSemicolon + * @return this */ public VariableStatement setAddSemicolon(boolean addSemicolon) { this.addSemicolon = addSemicolon; diff --git a/src/test/java/overrun/marshal/test/DowncallProvider.java b/src/test/java/overrun/marshal/test/DowncallProvider.java index fd49ee9..c079a51 100644 --- a/src/test/java/overrun/marshal/test/DowncallProvider.java +++ b/src/test/java/overrun/marshal/test/DowncallProvider.java @@ -16,11 +16,14 @@ package overrun.marshal.test; +import overrun.marshal.MemoryStack; + import java.lang.foreign.*; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -35,6 +38,8 @@ * @since 0.1.0 */ public final class DowncallProvider { + public static final String TEST_STRING = "Hello world"; + public static final String TEST_UTF16_STRING = "Hello UTF-16 world"; private static final Linker LINKER = Linker.nativeLinker(); private static final Arena ARENA = Arena.ofAuto(); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); @@ -44,10 +49,24 @@ public final class DowncallProvider { try { seg("test", LOOKUP.findStatic(DowncallProvider.class, "test", MethodType.methodType(void.class)), FunctionDescriptor.ofVoid()); seg("testDefault", LOOKUP.findStatic(DowncallProvider.class, "testDefault", MethodType.methodType(void.class)), FunctionDescriptor.ofVoid()); - seg("test_int", LOOKUP.findStatic(DowncallProvider.class, "test_int", MethodType.methodType(void.class, int.class)), FunctionDescriptor.ofVoid(JAVA_INT)); - seg("test_String", LOOKUP.findStatic(DowncallProvider.class, "test_String", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); - seg("test_UTF16String", LOOKUP.findStatic(DowncallProvider.class, "test_UTF16String", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); - seg("test_CEnum", LOOKUP.findStatic(DowncallProvider.class, "test_CEnum", MethodType.methodType(void.class, int.class)), FunctionDescriptor.ofVoid(JAVA_INT)); + seg("testInt", LOOKUP.findStatic(DowncallProvider.class, "testInt", MethodType.methodType(void.class, int.class)), FunctionDescriptor.ofVoid(JAVA_INT)); + seg("testString", LOOKUP.findStatic(DowncallProvider.class, "testString", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("testUTF16String", LOOKUP.findStatic(DowncallProvider.class, "testUTF16String", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("testCEnum", LOOKUP.findStatic(DowncallProvider.class, "testCEnum", MethodType.methodType(void.class, int.class)), FunctionDescriptor.ofVoid(JAVA_INT)); + seg("testUpcall", LOOKUP.findStatic(DowncallProvider.class, "testUpcall", MethodType.methodType(int.class, MemorySegment.class)), FunctionDescriptor.of(JAVA_INT, ADDRESS)); + seg("testIntArray", LOOKUP.findStatic(DowncallProvider.class, "testIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("testVarArgsJava", LOOKUP.findStatic(DowncallProvider.class, "testVarArgsJava", MethodType.methodType(void.class, int.class, MemorySegment.class)), FunctionDescriptor.ofVoid(JAVA_INT, ADDRESS)); + seg("testReturnInt", LOOKUP.findStatic(DowncallProvider.class, "testReturnInt", MethodType.methodType(int.class)), FunctionDescriptor.of(JAVA_INT)); + seg("testReturnString", LOOKUP.findStatic(DowncallProvider.class, "testReturnString", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testReturnUTF16String", LOOKUP.findStatic(DowncallProvider.class, "testReturnUTF16String", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testReturnCEnum", LOOKUP.findStatic(DowncallProvider.class, "testReturnCEnum", MethodType.methodType(int.class)), FunctionDescriptor.of(JAVA_INT)); + seg("testReturnUpcall", LOOKUP.findStatic(DowncallProvider.class, "testReturnUpcall", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testReturnIntArray", LOOKUP.findStatic(DowncallProvider.class, "testReturnIntArray", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testSizedIntArray", LOOKUP.findStatic(DowncallProvider.class, "testSizedIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("testReturnSizedSeg", LOOKUP.findStatic(DowncallProvider.class, "testReturnSizedSeg", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testRefIntArray", LOOKUP.findStatic(DowncallProvider.class, "testRefIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); + seg("testCriticalFalse", MethodHandles.empty(MethodType.methodType(void.class)), FunctionDescriptor.ofVoid()); + seg("testCriticalTrue", LOOKUP.findStatic(DowncallProvider.class, "testCriticalTrue", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } @@ -70,22 +89,76 @@ private static void testDefault() { System.out.print("testDefault"); } - private static void test_int(int i) { + private static void testInt(int i) { System.out.print(i); } - private static void test_String(MemorySegment s) { + private static void testString(MemorySegment s) { System.out.print(s.reinterpret(12).getString(0)); } - private static void test_UTF16String(MemorySegment s) { + private static void testUTF16String(MemorySegment s) { System.out.print(s.reinterpret(40).getString(0, StandardCharsets.UTF_16)); } - private static void test_CEnum(int i) { + private static void testCEnum(int i) { System.out.print(i); } + private static int testUpcall(MemorySegment upcall) { + try (MemoryStack stack = MemoryStack.stackPush()) { + return SimpleUpcall.wrap(stack, upcall).invoke(42); + } + } + + private static void testIntArray(MemorySegment arr) { + testVarArgsJava(2, arr); + } + + private static void testVarArgsJava(int c, MemorySegment arr) { + System.out.print(Arrays.toString(arr.reinterpret(JAVA_INT.scale(0, c)).toArray(JAVA_INT))); + } + + private static int testReturnInt() { + return 42; + } + + private static MemorySegment testReturnString() { + return ARENA.allocateFrom(TEST_STRING); + } + + private static MemorySegment testReturnUTF16String() { + return ARENA.allocateFrom(new String(TEST_UTF16_STRING.getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16), StandardCharsets.UTF_16); + } + + private static int testReturnCEnum() { + return 2; + } + + private static MemorySegment testReturnUpcall() { + return ((SimpleUpcall) (i -> i * 2)).stub(ARENA); + } + + private static MemorySegment testReturnIntArray() { + return ARENA.allocateFrom(JAVA_INT, 4, 2); + } + + private static void testSizedIntArray(MemorySegment arr) { + testIntArray(arr); + } + + private static MemorySegment testReturnSizedSeg() { + return ARENA.allocateFrom(JAVA_INT, 8); + } + + private static void testRefIntArray(MemorySegment arr) { + arr.reinterpret(JAVA_INT.byteSize()).set(JAVA_INT, 0, 8); + } + + private static void testCriticalTrue(MemorySegment arr) { + testRefIntArray(arr); + } + private static void seg(String name, MethodHandle mh, FunctionDescriptor fd) { table.put(name, LINKER.upcallStub(mh, fd, ARENA)); } diff --git a/src/test/java/overrun/marshal/test/DowncallTest.java b/src/test/java/overrun/marshal/test/DowncallTest.java index 940e4e6..8483ec6 100644 --- a/src/test/java/overrun/marshal/test/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/DowncallTest.java @@ -23,8 +23,15 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; +import static org.junit.jupiter.api.Assertions.*; +import static overrun.marshal.test.DowncallProvider.TEST_STRING; +import static overrun.marshal.test.DowncallProvider.TEST_UTF16_STRING; + /** * Test downcall * @@ -64,19 +71,19 @@ static void afterAll() throws IOException { @Test void test() { d.test(); - Assertions.assertEquals("test", outputStream.toString()); + assertEquals("test", outputStream.toString()); } @Test void testWithEntrypoint() { d.testWithEntrypoint(); - Assertions.assertEquals("test", outputStream.toString()); + assertEquals("test", outputStream.toString()); } @Test void testSkip() { d.testSkip(); - Assertions.assertEquals("testSkip", outputStream.toString()); + assertEquals("testSkip", outputStream.toString()); } @ParameterizedTest(name = "testDefault(testDefaultNull = [" + ParameterizedTest.INDEX_PLACEHOLDER + "] " + ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER + ")") @@ -84,35 +91,108 @@ void testSkip() { void testDefault(boolean testDefaultNull) { IDowncall.getInstance(testDefaultNull).testDefault(); if (testDefaultNull) { - Assertions.assertEquals("testDefault in interface", outputStream.toString()); + assertEquals("testDefault in interface", outputStream.toString()); } else { - Assertions.assertEquals("testDefault", outputStream.toString()); + assertEquals("testDefault", outputStream.toString()); + } + } + + @Test + void testInt() { + d.testInt(42); + assertEquals("42", outputStream.toString()); + } + + @Test + void testString() { + d.testString(TEST_STRING); + assertEquals(TEST_STRING, outputStream.toString()); + } + + @Test + void testUTF16String() { + d.testUTF16String(new String(TEST_UTF16_STRING.getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16)); + assertEquals(TEST_UTF16_STRING, outputStream.toString()); + } + + @Test + void testCEnum() { + d.testCEnum(MyEnum.A); + d.testCEnum(MyEnum.B); + d.testCEnum(MyEnum.C); + assertEquals("024", outputStream.toString()); + } + + @Test + void testUpcall() { + try (Arena arena = Arena.ofConfined()) { + assertEquals(84, d.testUpcall(arena, i -> i * 2)); + } + } + + @Test + void testIntArray() { + d.testIntArray(new int[]{4, 2}); + d.testVarArgsJava(2, 4, 2); + d.testVarArgsJava(0); + assertEquals("[4, 2][4, 2][]", outputStream.toString()); + } + + @Test + void testReturnInt() { + assertEquals(42, d.testReturnInt()); + } + + @Test + void testReturnString() { + assertEquals(TEST_STRING, d.testReturnString()); + assertEquals(TEST_UTF16_STRING, d.testReturnUTF16String()); + } + + @Test + void testReturnCEnum() { + assertEquals(MyEnum.B, d.testReturnCEnum()); + } + + @Test + void testReturnUpcall() { + try (Arena arena = Arena.ofConfined()) { + final SimpleUpcall upcall = d.testReturnUpcall(arena); + assertEquals(84, upcall.invoke(42)); } } @Test - void test_int() { - d.test_int(42); - Assertions.assertEquals("42", outputStream.toString()); + void testReturnIntArray() { + assertArrayEquals(new int[]{4, 2}, d.testReturnIntArray()); + } + + @Test + void testSizedIntArray() { + assertThrowsExactly(IllegalArgumentException.class, () -> d.testSizedIntArray(new int[0])); + assertDoesNotThrow(() -> d.testSizedIntArray(new int[]{4, 2})); + assertEquals("[4, 2]", outputStream.toString()); } @Test - void test_String() { - d.test_String("Hello world"); - Assertions.assertEquals("Hello world", outputStream.toString()); + void testReturnSizedSeg() { + final MemorySegment segment = d.testReturnSizedSeg(); + assertEquals(4L, segment.byteSize()); + assertEquals(8, segment.get(ValueLayout.JAVA_INT, 0)); } @Test - void test_UTF16String() { - d.test_UTF16String(new String("Hello UTF-16 world".getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16)); - Assertions.assertEquals("Hello UTF-16 world", outputStream.toString()); + void testRefIntArray() { + int[] arr = {0}; + d.testRefIntArray(arr); + assertArrayEquals(new int[]{8}, arr); } @Test - void test_CEnum() { - d.test_CEnum(MyEnum.A); - d.test_CEnum(MyEnum.B); - d.test_CEnum(MyEnum.C); - Assertions.assertEquals("024", outputStream.toString()); + void testCritical() { + d.testCriticalFalse(); + int[] arr = {0}; + d.testCriticalTrue(arr); + assertArrayEquals(new int[]{8}, arr); } } diff --git a/src/test/java/overrun/marshal/test/IDowncall.java b/src/test/java/overrun/marshal/test/IDowncall.java index 52db442..5d11ae3 100644 --- a/src/test/java/overrun/marshal/test/IDowncall.java +++ b/src/test/java/overrun/marshal/test/IDowncall.java @@ -19,6 +19,9 @@ import overrun.marshal.Downcall; import overrun.marshal.gen.*; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; + /** * Downcall interface * @@ -44,11 +47,44 @@ default void testDefault() { System.out.print("testDefault in interface"); } - void test_int(int i); + void testInt(int i); + + void testString(String s); + + void testUTF16String(@StrCharset("UTF-16") String s); + + void testCEnum(MyEnum myEnum); + + int testUpcall(Arena arena, SimpleUpcall upcall); + + void testIntArray(int[] arr); + + void testVarArgsJava(int c, int... arr); + + int testReturnInt(); + + @SizedSeg(12) + String testReturnString(); + + @SizedSeg(40) + @StrCharset("UTF-16") + String testReturnUTF16String(); + + MyEnum testReturnCEnum(); + + SimpleUpcall testReturnUpcall(Arena arena); + + @Sized(2) + int[] testReturnIntArray(); + + void testSizedIntArray(@Sized(2) int[] arr); + + @SizedSeg(4L) + MemorySegment testReturnSizedSeg(); - void test_String(String s); + void testRefIntArray(@Ref int[] arr); - void test_UTF16String(@StrCharset("UTF-16") String s); + void testCriticalFalse(); - void test_CEnum(MyEnum myEnum); + void testCriticalTrue(@Ref int[] arr); } diff --git a/src/test/java/overrun/marshal/test/MyEnum.java b/src/test/java/overrun/marshal/test/MyEnum.java index cc949f3..b5b9f50 100644 --- a/src/test/java/overrun/marshal/test/MyEnum.java +++ b/src/test/java/overrun/marshal/test/MyEnum.java @@ -38,4 +38,14 @@ public enum MyEnum implements CEnum { public int value() { return value; } + + @Wrapper + public static MyEnum wrap(int value) { + return switch (value) { + case 0 -> A; + case 2 -> B; + case 4 -> C; + default -> throw new IllegalStateException("Unexpected value: " + value); + }; + } } From 135b53f7ec402440a6570973f314f0084799f857 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Tue, 23 Jan 2024 11:16:25 +0800 Subject: [PATCH 23/28] Update README and test --- README.md | 21 ++++++++++--------- .../overrun/marshal/test/DowncallTest.java | 11 +++++++++- .../java/overrun/marshal/test/IDowncall.java | 8 +++++++ 3 files changed, 29 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9a8b4f3..a3646c6 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ This library requires JDK 22 or newer. ## Overview ```java +import overrun.marshal.*; import overrun.marshal.gen.*; import java.lang.foreign.Arena; @@ -20,14 +21,13 @@ import java.lang.foreign.ValueLayout; /** * GLFW constants and functions - *

- * The documentation will be automatically copied - * into the generated file - *

- * You don't have to specify the name if the name of the class or interface starts with 'C' */ -@Downcall(libname = "libglfw.so") -interface CGLFW { +interface GLFW { + /** + * The instance of the loaded library + */ + GLFW INSTANCE = Downcall.load("libglfw3.so"); + /** * A field */ @@ -68,12 +68,13 @@ interface CGLFW { class Main { public static void main(String[] args) { + GLFW glfw = GLFW.INSTANCE; int key = GLFW.GLFW_KEY_A; - GLFW.glfwSwapInterval(1); + glfw.glfwSwapInterval(1); // Arena try (var arena = Arena.ofConfined()) { - MemorySegment windowHandle = glfwCreateWindow(arena, + MemorySegment windowHandle = glfw.glfwCreateWindow(arena, 800, 600, "The title", @@ -82,7 +83,7 @@ class Main { // ref by array int[] ax = {0}, ay = {0}; - glfwGetWindowPos(windowHandle, ax, ay); + glfw.glfwGetWindowPos(windowHandle, ax, ay); } } } diff --git a/src/test/java/overrun/marshal/test/DowncallTest.java b/src/test/java/overrun/marshal/test/DowncallTest.java index 8483ec6..715fd53 100644 --- a/src/test/java/overrun/marshal/test/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/DowncallTest.java @@ -19,12 +19,14 @@ import org.junit.jupiter.api.*; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import overrun.marshal.MemoryStack; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; import java.lang.foreign.ValueLayout; import java.nio.charset.StandardCharsets; @@ -133,9 +135,16 @@ void testUpcall() { @Test void testIntArray() { d.testIntArray(new int[]{4, 2}); + try (Arena arena = Arena.ofConfined()) { + d.testIntArray((SegmentAllocator) arena, new int[]{4, 2}); + d.testIntArray(arena, new int[]{4, 2}); + } + try (MemoryStack stack = MemoryStack.stackPush()) { + d.testIntArray(stack, new int[]{4, 2}); + } d.testVarArgsJava(2, 4, 2); d.testVarArgsJava(0); - assertEquals("[4, 2][4, 2][]", outputStream.toString()); + assertEquals("[4, 2][4, 2][4, 2][4, 2][4, 2][]", outputStream.toString()); } @Test diff --git a/src/test/java/overrun/marshal/test/IDowncall.java b/src/test/java/overrun/marshal/test/IDowncall.java index 5d11ae3..a644df0 100644 --- a/src/test/java/overrun/marshal/test/IDowncall.java +++ b/src/test/java/overrun/marshal/test/IDowncall.java @@ -17,10 +17,12 @@ package overrun.marshal.test; import overrun.marshal.Downcall; +import overrun.marshal.MemoryStack; import overrun.marshal.gen.*; import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; /** * Downcall interface @@ -59,6 +61,12 @@ default void testDefault() { void testIntArray(int[] arr); + void testIntArray(SegmentAllocator allocator, int[] arr); + + void testIntArray(Arena arena, int[] arr); + + void testIntArray(MemoryStack stack, int[] arr); + void testVarArgsJava(int c, int... arr); int testReturnInt(); From c8ca8419bffcf3781b4202e5006e19a3c01a4f35 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:03:35 +0800 Subject: [PATCH 24/28] Rewrite struct --- build.gradle.kts | 1 + demo/build.gradle.kts | 8 - .../overrun/marshal/test/CStructTest.java | 71 -- .../java/overrun/marshal/test/CVector3.java | 29 - .../marshal/test/GLFWErrorCallback.java | 58 -- .../java/overrun/marshal/test/MyEnum.java | 48 -- settings.gradle.kts | 2 - src/main/java/module-info.java | 3 +- src/main/java/overrun/marshal/BoolHelper.java | 83 -- .../marshal/{gen/struct => }/ByValue.java | 5 +- src/main/java/overrun/marshal/Downcall.java | 4 +- .../{gen2 => }/DowncallMethodData.java | 4 +- src/main/java/overrun/marshal/Marshal.java | 53 +- src/main/java/overrun/marshal/StrHelper.java | 92 -- .../java/overrun/marshal/StructProcessor.java | 664 --------------- src/main/java/overrun/marshal/Unmarshal.java | 194 ++++- src/main/java/overrun/marshal/Upcall.java | 3 +- .../overrun/marshal/gen/AccessModifier.java | 53 -- .../overrun/marshal/gen/struct/Const.java | 40 - .../overrun/marshal/gen/struct/Padding.java | 40 - .../overrun/marshal/gen/struct/Struct.java | 55 -- .../overrun/marshal/gen/struct/StructRef.java | 40 - .../marshal/gen/struct/package-info.java | 24 - .../overrun/marshal/gen1/Annotatable.java | 32 - .../overrun/marshal/gen1/AnnotationSpec.java | 96 --- .../overrun/marshal/gen1/CatchClause.java | 80 -- .../java/overrun/marshal/gen1/ClassSpec.java | 196 ----- .../overrun/marshal/gen1/ConstructSpec.java | 75 -- .../java/overrun/marshal/gen1/ElseClause.java | 73 -- .../overrun/marshal/gen1/IfStatement.java | 78 -- .../java/overrun/marshal/gen1/InvokeSpec.java | 136 --- .../java/overrun/marshal/gen1/LambdaSpec.java | 78 -- .../java/overrun/marshal/gen1/MethodSpec.java | 209 ----- .../overrun/marshal/gen1/ParameterSpec.java | 80 -- .../java/overrun/marshal/gen1/SourceFile.java | 114 --- src/main/java/overrun/marshal/gen1/Spec.java | 323 ------- .../overrun/marshal/gen1/StatementBlock.java | 32 - .../overrun/marshal/gen1/TryStatement.java | 86 -- .../marshal/gen1/VariableStatement.java | 156 ---- .../overrun/marshal/gen2/AnnotationData.java | 33 - .../overrun/marshal/gen2/ArrayTypeData.java | 27 - .../java/overrun/marshal/gen2/BaseData.java | 316 ------- .../marshal/gen2/DeclaredTypeData.java | 32 - .../java/overrun/marshal/gen2/FieldData.java | 47 -- .../overrun/marshal/gen2/FunctionData.java | 47 -- .../java/overrun/marshal/gen2/ImportData.java | 81 -- .../marshal/gen2/MethodHandleData.java | 45 - .../overrun/marshal/gen2/ParameterData.java | 35 - .../marshal/gen2/PrimitiveTypeData.java | 31 - .../java/overrun/marshal/gen2/TypeData.java | 84 -- .../java/overrun/marshal/gen2/TypeUse.java | 259 ------ .../overrun/marshal/gen2/VoidTypeData.java | 32 - .../marshal/gen2/struct/MemberData.java | 31 - .../marshal/gen2/struct/StructData.java | 486 ----------- .../overrun/marshal/internal/Processor.java | 323 ------- .../java/overrun/marshal/internal/Util.java | 354 -------- .../java/overrun/marshal/package-info.java | 2 +- .../java/overrun/marshal/struct/IStruct.java | 57 -- .../java/overrun/marshal/struct/Struct.java | 117 +++ .../overrun/marshal/struct/StructHandle.java | 785 ++++++++++++++++++ .../marshal/struct/StructHandleView.java | 310 +++++++ .../javax.annotation.processing.Processor | 1 - .../overrun/marshal/test/ComplexStruct.java | 98 +++ .../marshal/test/DowncallInheritTest.java | 4 + .../marshal/test/DowncallProvider.java | 5 +- .../overrun/marshal/test/DowncallTest.java | 27 +- src/test/java/overrun/marshal/test/ID1.java | 9 + src/test/java/overrun/marshal/test/ID3.java | 22 +- .../java/overrun/marshal/test/StructTest.java | 132 +++ .../overrun/marshal/test/UnmarshalTest.java | 66 ++ .../java/overrun/marshal/test/Vector3.java | 79 ++ 71 files changed, 1828 insertions(+), 5567 deletions(-) delete mode 100644 demo/build.gradle.kts delete mode 100644 demo/src/main/java/overrun/marshal/test/CStructTest.java delete mode 100644 demo/src/main/java/overrun/marshal/test/CVector3.java delete mode 100644 demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java delete mode 100644 demo/src/main/java/overrun/marshal/test/MyEnum.java delete mode 100644 src/main/java/overrun/marshal/BoolHelper.java rename src/main/java/overrun/marshal/{gen/struct => }/ByValue.java (89%) rename src/main/java/overrun/marshal/{gen2 => }/DowncallMethodData.java (94%) delete mode 100644 src/main/java/overrun/marshal/StrHelper.java delete mode 100644 src/main/java/overrun/marshal/StructProcessor.java delete mode 100644 src/main/java/overrun/marshal/gen/AccessModifier.java delete mode 100644 src/main/java/overrun/marshal/gen/struct/Const.java delete mode 100644 src/main/java/overrun/marshal/gen/struct/Padding.java delete mode 100644 src/main/java/overrun/marshal/gen/struct/Struct.java delete mode 100644 src/main/java/overrun/marshal/gen/struct/StructRef.java delete mode 100644 src/main/java/overrun/marshal/gen/struct/package-info.java delete mode 100644 src/main/java/overrun/marshal/gen1/Annotatable.java delete mode 100644 src/main/java/overrun/marshal/gen1/AnnotationSpec.java delete mode 100644 src/main/java/overrun/marshal/gen1/CatchClause.java delete mode 100644 src/main/java/overrun/marshal/gen1/ClassSpec.java delete mode 100644 src/main/java/overrun/marshal/gen1/ConstructSpec.java delete mode 100644 src/main/java/overrun/marshal/gen1/ElseClause.java delete mode 100644 src/main/java/overrun/marshal/gen1/IfStatement.java delete mode 100644 src/main/java/overrun/marshal/gen1/InvokeSpec.java delete mode 100644 src/main/java/overrun/marshal/gen1/LambdaSpec.java delete mode 100644 src/main/java/overrun/marshal/gen1/MethodSpec.java delete mode 100644 src/main/java/overrun/marshal/gen1/ParameterSpec.java delete mode 100644 src/main/java/overrun/marshal/gen1/SourceFile.java delete mode 100644 src/main/java/overrun/marshal/gen1/Spec.java delete mode 100644 src/main/java/overrun/marshal/gen1/StatementBlock.java delete mode 100644 src/main/java/overrun/marshal/gen1/TryStatement.java delete mode 100644 src/main/java/overrun/marshal/gen1/VariableStatement.java delete mode 100644 src/main/java/overrun/marshal/gen2/AnnotationData.java delete mode 100644 src/main/java/overrun/marshal/gen2/ArrayTypeData.java delete mode 100644 src/main/java/overrun/marshal/gen2/BaseData.java delete mode 100644 src/main/java/overrun/marshal/gen2/DeclaredTypeData.java delete mode 100644 src/main/java/overrun/marshal/gen2/FieldData.java delete mode 100644 src/main/java/overrun/marshal/gen2/FunctionData.java delete mode 100644 src/main/java/overrun/marshal/gen2/ImportData.java delete mode 100644 src/main/java/overrun/marshal/gen2/MethodHandleData.java delete mode 100644 src/main/java/overrun/marshal/gen2/ParameterData.java delete mode 100644 src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java delete mode 100644 src/main/java/overrun/marshal/gen2/TypeData.java delete mode 100644 src/main/java/overrun/marshal/gen2/TypeUse.java delete mode 100644 src/main/java/overrun/marshal/gen2/VoidTypeData.java delete mode 100644 src/main/java/overrun/marshal/gen2/struct/MemberData.java delete mode 100644 src/main/java/overrun/marshal/gen2/struct/StructData.java delete mode 100644 src/main/java/overrun/marshal/internal/Processor.java delete mode 100644 src/main/java/overrun/marshal/internal/Util.java delete mode 100644 src/main/java/overrun/marshal/struct/IStruct.java create mode 100644 src/main/java/overrun/marshal/struct/Struct.java create mode 100644 src/main/java/overrun/marshal/struct/StructHandle.java create mode 100644 src/main/java/overrun/marshal/struct/StructHandleView.java delete mode 100644 src/main/resources/META-INF/services/javax.annotation.processing.Processor create mode 100644 src/test/java/overrun/marshal/test/ComplexStruct.java create mode 100644 src/test/java/overrun/marshal/test/StructTest.java create mode 100644 src/test/java/overrun/marshal/test/UnmarshalTest.java create mode 100644 src/test/java/overrun/marshal/test/Vector3.java diff --git a/build.gradle.kts b/build.gradle.kts index 0679614..74a537a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -87,6 +87,7 @@ allprojects { dependencies { // add your dependencies + compileOnly("org.jetbrains:annotations:24.1.0") } tasks.withType { diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts deleted file mode 100644 index 373efa1..0000000 --- a/demo/build.gradle.kts +++ /dev/null @@ -1,8 +0,0 @@ -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 deleted file mode 100644 index c782711..0000000 --- a/demo/src/main/java/overrun/marshal/test/CStructTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import overrun.marshal.gen.SizedSeg; -import overrun.marshal.gen.Sized; -import overrun.marshal.gen.Skip; -import overrun.marshal.gen.StrCharset; -import overrun.marshal.gen.struct.Const; -import overrun.marshal.gen.struct.Padding; -import overrun.marshal.gen.struct.Struct; -import overrun.marshal.gen.struct.StructRef; - -import java.lang.foreign.MemorySegment; - -/** - * {@linkplain #LAYOUT Layout} - * - * @author squid233 - * @since 0.1.0 - */ -@Struct -final class CStructTest { - @Skip - int LAYOUT; - int x; - @Const - int y; - int index; - @Padding(4) - int padding0; - int[] segmentAllocator; - GLFWErrorCallback arena; - /** - * the timestamp - */ - long stamp; - byte b; - @Padding(7) - int padding; - MemorySegment segment; - @SizedSeg(16L) - MemorySegment sizedSegment; - String name; - @StrCharset("UTF-16") - String utf16Name; - int[] arr; - @Sized(4) - int[] sizedArr; - boolean[] boolArr; - String[] strArr; - GLFWErrorCallback upcall; - @StructRef("overrun.marshal.test.StructTest") - MemorySegment myStruct; - @StructRef("overrun.marshal.test.Vector3") - int anotherStruct; -} diff --git a/demo/src/main/java/overrun/marshal/test/CVector3.java b/demo/src/main/java/overrun/marshal/test/CVector3.java deleted file mode 100644 index e2065bf..0000000 --- a/demo/src/main/java/overrun/marshal/test/CVector3.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import overrun.marshal.gen.struct.Const; -import overrun.marshal.gen.struct.Struct; - -/** - * @author squid233 - * @since 0.1.0 - */ -@Const -@Struct -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 deleted file mode 100644 index 91bbac5..0000000 --- a/demo/src/main/java/overrun/marshal/test/GLFWErrorCallback.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import overrun.marshal.MemoryStack; -import overrun.marshal.gen.SizedSeg; -import overrun.marshal.Upcall; - -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; - -/** - * Test upcall stub - * - * @author squid233 - * @since 0.1.0 - */ -@FunctionalInterface -public interface GLFWErrorCallback extends Upcall { - Type TYPE = Upcall.type(); - - void invoke(int error, String description); - - @Stub - default void invoke(int error, @SizedSeg(Long.MAX_VALUE) MemorySegment description) { - invoke(error, description.getString(0)); - } - - @Override - default MemorySegment stub(Arena arena) { - return TYPE.of(arena, this); - } - - @Wrapper - static GLFWErrorCallback wrap(MemorySegment stub) { - return TYPE.wrap(stub, methodHandle -> (error, description) -> { - try (MemoryStack stack = MemoryStack.stackPush()) { - methodHandle.invokeExact(error, stack.allocateFrom(description)); - } catch (Throwable e) { - throw new RuntimeException(e); - } - }); - } -} diff --git a/demo/src/main/java/overrun/marshal/test/MyEnum.java b/demo/src/main/java/overrun/marshal/test/MyEnum.java deleted file mode 100644 index 31895fb..0000000 --- a/demo/src/main/java/overrun/marshal/test/MyEnum.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.test; - -import overrun.marshal.CEnum; - -/** - * @author squid233 - * @since 0.1.0 - */ -public enum MyEnum implements CEnum { - NO_ERROR(0x0), - SOME_ERROR(0x10); - - private final int value; - - MyEnum(int value) { - this.value = value; - } - - @Wrapper - public static MyEnum of(int value) { - return switch (value) { - case 0x0 -> NO_ERROR; - case 0x10 -> SOME_ERROR; - default -> throw new IllegalArgumentException("Unexpected value: " + value); - }; - } - - @Override - public int value() { - return value; - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 2db3568..e3e4378 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,5 +9,3 @@ pluginManagement { val projName: String by settings rootProject.name = projName - -include("demo") diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 3ff7ff0..aa86c4c 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -23,8 +23,7 @@ module io.github.overrun.marshal { exports overrun.marshal; exports overrun.marshal.gen; - exports overrun.marshal.gen.struct; exports overrun.marshal.struct; - requires java.compiler; + requires static org.jetbrains.annotations; } diff --git a/src/main/java/overrun/marshal/BoolHelper.java b/src/main/java/overrun/marshal/BoolHelper.java deleted file mode 100644 index d69fbcc..0000000 --- a/src/main/java/overrun/marshal/BoolHelper.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.foreign.ValueLayout; - -/** - * Helper of boolean. - * - * @author squid233 - * @since 0.1.0 - */ -@Deprecated(since = "0.1.0", forRemoval = true) -public final class BoolHelper { - private BoolHelper() { - //no instance - } - - /** - * Allocates a memory segment with the given boolean array. - * - * @param allocator the segment allocator - * @param arr the boolean array - * @return the memory segment - */ - public static MemorySegment of(SegmentAllocator allocator, boolean[] arr) { - final MemorySegment segment = allocator.allocate(ValueLayout.JAVA_BOOLEAN, arr.length); - for (int i = 0; i < arr.length; i++) { - segment.setAtIndex(ValueLayout.JAVA_BOOLEAN, i, arr[i]); - } - return segment; - } - - /** - * Copies data from the given source memory segment into a boolean array. - * - * @param src the source - * @param dst the destination - */ - public static void copy(MemorySegment src, boolean[] dst) { - final int length = checkArraySize(Math.min(src.byteSize(), dst.length)); - for (int i = 0; i < length; i++) { - dst[i] = src.getAtIndex(ValueLayout.JAVA_BOOLEAN, i); - } - } - - /** - * Converts a memory segment into a boolean array. - * - * @param segment the memory segment - * @return the boolean array - */ - public static boolean[] toArray(MemorySegment segment) { - boolean[] b = new boolean[checkArraySize(segment.byteSize())]; - for (int i = 0; i < b.length; i++) { - b[i] = segment.getAtIndex(ValueLayout.JAVA_BOOLEAN, i); - } - return b; - } - - private static int checkArraySize(long byteSize) { - if (byteSize > (Integer.MAX_VALUE - 8)) { - throw new IllegalStateException(String.format("Segment is too large to wrap as %s. Size: %d", ValueLayout.JAVA_BOOLEAN, byteSize)); - } - return (int) byteSize; - } -} diff --git a/src/main/java/overrun/marshal/gen/struct/ByValue.java b/src/main/java/overrun/marshal/ByValue.java similarity index 89% rename from src/main/java/overrun/marshal/gen/struct/ByValue.java rename to src/main/java/overrun/marshal/ByValue.java index 627aada..b4aa688 100644 --- a/src/main/java/overrun/marshal/gen/struct/ByValue.java +++ b/src/main/java/overrun/marshal/ByValue.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen.struct; +package overrun.marshal; import java.lang.annotation.*; @@ -25,8 +25,7 @@ *

Example

*
{@code
  * @ByValue
- * @StructRef("org.example.MyStruct")
- * Object returnStruct(SegmentAllocator allocator);
+ * MyStruct returnStruct(SegmentAllocator allocator);
  * }
* * @author squid233 diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 0a923cc..2384965 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -17,8 +17,6 @@ package overrun.marshal; import overrun.marshal.gen.*; -import overrun.marshal.gen.struct.ByValue; -import overrun.marshal.gen2.DowncallMethodData; import java.lang.annotation.Annotation; import java.lang.classfile.ClassFile; @@ -579,7 +577,7 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { if (componentType == String.class) { final boolean hasCharset = getCharset(blockCodeBuilder, method); blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsString", + "unmarshalAsStringArray", MethodTypeDesc.of(CD_String.arrayType(), hasCharset ? List.of(CD_AddressLayout, CD_MemorySegment, CD_Charset) : diff --git a/src/main/java/overrun/marshal/gen2/DowncallMethodData.java b/src/main/java/overrun/marshal/DowncallMethodData.java similarity index 94% rename from src/main/java/overrun/marshal/gen2/DowncallMethodData.java rename to src/main/java/overrun/marshal/DowncallMethodData.java index a1fa3a9..88f8912 100644 --- a/src/main/java/overrun/marshal/gen2/DowncallMethodData.java +++ b/src/main/java/overrun/marshal/DowncallMethodData.java @@ -14,7 +14,7 @@ * copies or substantial portions of the Software. */ -package overrun.marshal.gen2; +package overrun.marshal; import java.lang.reflect.Parameter; import java.util.List; @@ -31,7 +31,7 @@ * @author squid233 * @since 0.1.0 */ -public record DowncallMethodData( +record DowncallMethodData( String entrypoint, String handleName, String loaderName, diff --git a/src/main/java/overrun/marshal/Marshal.java b/src/main/java/overrun/marshal/Marshal.java index a278847..3598317 100644 --- a/src/main/java/overrun/marshal/Marshal.java +++ b/src/main/java/overrun/marshal/Marshal.java @@ -16,6 +16,8 @@ package overrun.marshal; +import org.jetbrains.annotations.Nullable; + import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentAllocator; @@ -52,7 +54,7 @@ static VarHandle arrayVarHandle(ValueLayout valueLayout) { * @param string the string * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, String string) { + public static MemorySegment marshal(SegmentAllocator allocator, @Nullable String string) { if (string == null) return MemorySegment.NULL; return allocator.allocateFrom(string); } @@ -65,7 +67,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, String string) { * @param charset the charset * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, String string, Charset charset) { + public static MemorySegment marshal(SegmentAllocator allocator, @Nullable String string, Charset charset) { if (string == null) return MemorySegment.NULL; return allocator.allocateFrom(string, charset); } @@ -76,8 +78,8 @@ public static MemorySegment marshal(SegmentAllocator allocator, String string, C * @param cEnum the CEnum * @return the integer */ - public static int marshal(CEnum cEnum) { - return cEnum.value(); + public static int marshal(@Nullable CEnum cEnum) { + return cEnum != null ? cEnum.value() : 0; } /** @@ -86,7 +88,7 @@ public static int marshal(CEnum cEnum) { * @param addressable the addressable * @return the segment */ - public static MemorySegment marshal(Addressable addressable) { + public static MemorySegment marshal(@Nullable Addressable addressable) { if (addressable == null) return MemorySegment.NULL; return addressable.segment(); } @@ -98,7 +100,7 @@ public static MemorySegment marshal(Addressable addressable) { * @param upcall the upcall * @return the segment */ - public static MemorySegment marshal(Arena arena, Upcall upcall) { + public static MemorySegment marshal(Arena arena, @Nullable Upcall upcall) { if (upcall == null) return MemorySegment.NULL; return upcall.stub(arena); } @@ -110,11 +112,11 @@ public static MemorySegment marshal(Arena arena, Upcall upcall) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, boolean[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, boolean @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; final MemorySegment segment = allocator.allocate(JAVA_BOOLEAN, arr.length); for (int i = 0, l = arr.length; i < l; i++) { - vh_booleanArray.set(segment, (long) i, segment, arr[i]); + vh_booleanArray.set(segment, (long) i, arr[i]); } return segment; } @@ -126,7 +128,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, boolean[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, char[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, char @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_CHAR, arr); } @@ -138,7 +140,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, char[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, byte[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, byte @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_BYTE, arr); } @@ -150,7 +152,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, byte[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, short[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, short @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_SHORT, arr); } @@ -162,7 +164,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, short[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, int[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, int @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_INT, arr); } @@ -174,7 +176,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, int[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, long[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, long @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_LONG, arr); } @@ -186,7 +188,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, long[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, float[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, float @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_FLOAT, arr); } @@ -198,7 +200,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, float[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, double[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, double @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; return allocator.allocateFrom(JAVA_DOUBLE, arr); } @@ -213,7 +215,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, double[] arr) { * @param the type of the element * @return the segment */ - public static
MemorySegment marshal(A allocator, T[] arr, BiFunction function) { + public static MemorySegment marshal(A allocator, T @Nullable [] arr, BiFunction function) { if (arr == null) return MemorySegment.NULL; final MemorySegment segment = allocator.allocate(ADDRESS, arr.length); for (int i = 0, l = arr.length; i < l; i++) { @@ -229,8 +231,7 @@ public static MemorySegment marshal(A allocator, * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, MemorySegment[] arr) { - if (arr == null) return MemorySegment.NULL; + public static MemorySegment marshal(SegmentAllocator allocator, MemorySegment @Nullable [] arr) { return marshal(allocator, arr, (_, segment) -> segment); } @@ -241,8 +242,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, MemorySegment[] * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, String[] arr) { - if (arr == null) return MemorySegment.NULL; + public static MemorySegment marshal(SegmentAllocator allocator, String @Nullable [] arr) { return marshal(allocator, arr, SegmentAllocator::allocateFrom); } @@ -254,8 +254,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, String[] arr) { * @param charset the charset * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, String[] arr, Charset charset) { - if (arr == null) return MemorySegment.NULL; + public static MemorySegment marshal(SegmentAllocator allocator, String @Nullable [] arr, Charset charset) { return marshal(allocator, arr, (segmentAllocator, str) -> segmentAllocator.allocateFrom(str, charset)); } @@ -266,7 +265,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, String[] arr, Ch * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, CEnum[] arr) { + public static MemorySegment marshal(SegmentAllocator allocator, CEnum @Nullable [] arr) { if (arr == null) return MemorySegment.NULL; final MemorySegment segment = allocator.allocate(JAVA_INT, arr.length); for (int i = 0; i < arr.length; i++) { @@ -282,9 +281,8 @@ public static MemorySegment marshal(SegmentAllocator allocator, CEnum[] arr) { * @param arr the array * @return the segment */ - public static MemorySegment marshal(SegmentAllocator allocator, Addressable[] arr) { - if (arr == null) return MemorySegment.NULL; - return marshal(allocator, arr, (_, addressable) -> addressable.segment()); + public static MemorySegment marshal(SegmentAllocator allocator, @Nullable Addressable @Nullable [] arr) { + return marshal(allocator, arr, (_, addressable) -> addressable != null ? addressable.segment() : MemorySegment.NULL); } /** @@ -294,8 +292,7 @@ public static MemorySegment marshal(SegmentAllocator allocator, Addressable[] ar * @param arr the array * @return the segment */ - public static MemorySegment marshal(Arena arena, Upcall[] arr) { - if (arr == null) return MemorySegment.NULL; + public static MemorySegment marshal(Arena arena, Upcall @Nullable [] arr) { return marshal(arena, arr, (arena1, upcall) -> upcall.stub(arena1)); } } diff --git a/src/main/java/overrun/marshal/StrHelper.java b/src/main/java/overrun/marshal/StrHelper.java deleted file mode 100644 index fb4aca0..0000000 --- a/src/main/java/overrun/marshal/StrHelper.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.foreign.*; -import java.nio.charset.Charset; - -/** - * Helper of string arrays. - * - * @author squid233 - * @since 0.1.0 - */ -@Deprecated(since = "0.1.0", forRemoval = true) -public final class StrHelper { - private static final SequenceLayout STR_LAYOUT = MemoryLayout.sequenceLayout(Integer.MAX_VALUE - 8, ValueLayout.JAVA_BYTE); - - private StrHelper() { - //no instance - } - - /** - * Allocates a memory segment with the given string array. - * - * @param allocator the segment allocator - * @param strings the string array - * @param charset the string charset - * @return the memory segment - */ - public static MemorySegment of(SegmentAllocator allocator, String[] strings, Charset charset) { - final MemorySegment segment = allocator.allocate(ValueLayout.ADDRESS, strings.length); - for (int i = 0; i < strings.length; i++) { - segment.setAtIndex(ValueLayout.ADDRESS, i, allocator.allocateFrom(strings[i], charset)); - } - return segment; - } - - /** - * Copies data from the given source memory segment into a string array. - * - * @param src the source - * @param dst the destination - * @param charset the string charset - */ - public static void copy(MemorySegment src, String[] dst, Charset charset) { - final int length = checkArraySize(Math.min(src.byteSize(), dst.length)); - for (int i = 0; i < length; i++) { - dst[i] = src.getAtIndex(ValueLayout.ADDRESS.withTargetLayout(STR_LAYOUT), i).getString(0L, charset); - } - } - - /** - * Converts a memory segment into a string array. - * - * @param segment the memory segment - * @param charset the string charset - * @return the string array - */ - public static String[] toArray(MemorySegment segment, Charset charset) { - String[] arr = new String[checkArraySize(segment.byteSize())]; - for (int i = 0; i < arr.length; i++) { - arr[i] = segment.getAtIndex(ValueLayout.ADDRESS.withTargetLayout(STR_LAYOUT), i).getString(0L, charset); - } - return arr; - } - - private static int checkArraySize(long byteSize) { - final long elemSize = ValueLayout.ADDRESS.byteSize(); - if (!((byteSize & (elemSize - 1)) == 0)) { - throw new IllegalStateException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, byteSize)); - } - long arraySize = byteSize / elemSize; - if (arraySize > (Integer.MAX_VALUE - 8)) { - throw new IllegalStateException(String.format("Segment is too large to wrap as %s. Size: %d", ValueLayout.ADDRESS, byteSize)); - } - return (int) arraySize; - } -} diff --git a/src/main/java/overrun/marshal/StructProcessor.java b/src/main/java/overrun/marshal/StructProcessor.java deleted file mode 100644 index c0b4317..0000000 --- a/src/main/java/overrun/marshal/StructProcessor.java +++ /dev/null @@ -1,664 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal; - -import overrun.marshal.gen.*; -import overrun.marshal.gen.struct.Const; -import overrun.marshal.gen.struct.Padding; -import overrun.marshal.gen.struct.Struct; -import overrun.marshal.gen.struct.StructRef; -import overrun.marshal.gen1.*; -import overrun.marshal.gen2.struct.StructData; -import overrun.marshal.internal.Processor; -import overrun.marshal.struct.IStruct; - -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 java.util.function.Consumer; - -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 -> { - try { - new StructData(processingEnv).generate(e); -// writeFile(e, ElementFilter.fieldsIn(e.getEnclosedElements())); - } 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.addImports("overrun.marshal.*", - "overrun.marshal.gen.*", - "overrun.marshal.gen.struct.*", - "java.lang.foreign.*"); - file.addImports( - MemoryLayout.PathElement.class, - VarHandle.class - ); - file.addClass(simpleClassName, classSpec -> { - 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 -> 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 (isValueType(eType)) { - 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); - if (sized != null) { - member = new InvokeSpec(member, "withTargetLayout") - .addArgument(new InvokeSpec(MemoryLayout.class, "sequenceLayout") - .addArgument(getConstExp(sized.value())) - .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); - } else { - printError("Unsupported field: " + eType + ' ' + e.getSimpleName()); - } - }))) - .setStatic(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 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) {
-                        sb.append('(').append(getCustomCharset(e)).append(") ");
-                    }
-                    if (typeConst || e.getAnnotation(Const.class) != null) {
-                        sb.append("final ");
-                    }
-                    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) {
-                            sb.append('[').append(getConstExp(sizedSeg.value())).append(']');
-                        } else {
-                            sb.append('*');
-                        }
-                    } else {
-                        final Sized sized = e.getAnnotation(Sized.class);
-                        if (sized != null && simplifiedString.endsWith("[]")) {
-                            sb.append(simplifiedString, 0, simplifiedString.length() - 1).append(getConstExp(sized.value())).append(']');
-                        } else {
-                            sb.append(simplifiedString);
-                        }
-                    }
-                    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")) - .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) - .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 - forEach(fields, false, e -> classSpec.addField(new VariableStatement(VarHandle.class, e.getSimpleName().toString(), null) - .setAccessModifier(AccessModifier.PRIVATE) - .setFinal(true))); - - // constructor - classSpec.addMethod(new MethodSpec((Spec) 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"))); - forEach(fields, false, e -> { - 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))); - }); - }); - classSpec.addMethod(new MethodSpec((Spec) 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 -> { - 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(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("count")) - .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(); - 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 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 isUpcall = isUpcall(eType); - final boolean generateN = canConvertToAddress && (isStruct || !isMemorySegment); - - // getter - final Spec castSpec = Spec.cast(returnType, - new InvokeSpec(Spec.accessSpec("this", nameString), "get") - .addArgument("this._memorySegment") - .addArgument("0L") - .addArgument("index")); - addGetterAt(classSpec, e, generateN ? "nget" : "get", returnType, null, - (isMemorySegment && sizedSeg != null || isArray && sized != null) ? - new InvokeSpec(Spec.parentheses(castSpec), "reinterpret") - .addArgument(sizedSeg != null ? - Spec.literal(getConstExp(sizedSeg.value())) : - Spec.operatorSpec("*", - Spec.literal(getConstExp(sized.value())), - new InvokeSpec(toValueLayoutStr(getArrayComponentType(eType)), "byteSize"))) : - (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 || isUpcall || isStruct) { - storeResult = new VariableStatement(MemorySegment.class, "$_marshalResult", returnValue) - .setAccessModifier(AccessModifier.PACKAGE_PRIVATE) - .setFinal(true); - finalReturnValue = Spec.literal("$_marshalResult"); - } else { - finalReturnValue = returnValue; - } - 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"); - if (strCharset != null) { - invokeSpec.addArgument(createCharset(file, getCustomCharset(e))); - } - finalReturnValue = invokeSpec; - } else if (isArray) { - final TypeMirror arrayComponentType = getArrayComponentType(eType); - if (sized == null) { - finalReturnValue = new InvokeSpec(finalReturnValue, "reinterpret") - .addArgument(Spec.operatorSpec("*", Spec.literal("count"), - new InvokeSpec(toValueLayoutStr(arrayComponentType), "byteSize"))); - } - if (isBooleanArray(eType)) { - finalReturnValue = new InvokeSpec(BoolHelper.class.getCanonicalName(), "toArray") - .addArgument(finalReturnValue); - } else if (isStringArray(eType)) { - finalReturnValue = new InvokeSpec(StrHelper.class.getCanonicalName(), "toArray") - .addArgument(finalReturnValue) - .addArgument(createCharset(file, getCustomCharset(e))); - } else { - finalReturnValue = new InvokeSpec(finalReturnValue, "toArray") - .addArgument(toValueLayoutStr(arrayComponentType)); - } - } else if (isUpcall) { - // find wrap method - final var wrapMethod = findUpcallWrapperMethod(eType); - if (wrapMethod.isPresent()) { - finalReturnValue = new InvokeSpec(eTypeString, wrapMethod.get().getSimpleName().toString()) - .addArgument(finalReturnValue); - } else { - printError(wrapperNotFound(eType, type, ".", nameString, "Upcall.Wrapper")); - return; - } - } - if (storeResult != null) { - finalReturnValue = Spec.ternaryOp(Spec.neqSpec(new InvokeSpec("$_marshalResult", "address"), Spec.literal("0L")), - finalReturnValue, - Spec.literal("null")); - } - addGetterAt(classSpec, e, "get", overloadReturnType, 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") - .also(invokeSpec -> { - invokeSpec.addArgument(paramIndex); - invokeSpec.addArgument(nameString); - }))); - if (generateN) { - 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(paramSegmentAllocator, "allocateFrom") - .addArgument(nameString); - if (strCharset != null) { - alloc.addArgument(createCharset(file, getCustomCharset(e))); - } - invokeSpec.addArgument(alloc); - } else if (isArray) { - if (isBooleanArray(eType)) { - invokeSpec.addArgument(new InvokeSpec(BoolHelper.class.getCanonicalName(), "of") - .addArgument(paramSegmentAllocator) - .addArgument(nameString)); - } else if (isStringArray(eType)) { - invokeSpec.addArgument(new InvokeSpec(StrHelper.class.getCanonicalName(), "of") - .addArgument(paramSegmentAllocator) - .addArgument(nameString) - .addArgument(createCharset(file, getCustomCharset(e)))); - } else { - invokeSpec.addArgument(new InvokeSpec(paramSegmentAllocator, "allocateFrom") - .addArgument(toValueLayoutStr(getArrayComponentType(eType))) - .addArgument(nameString)); - } - } 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", overloadReturnType, Spec.statement(invokeSpec)); - } - } - } - }); - - // override - addIStructImpl(classSpec, MemorySegment.class, "segment", Spec.literal("this._memorySegment")); - addIStructImpl(classSpec, StructLayout.class, "layout", Spec.literal("LAYOUT")); - addIStructImpl(classSpec, long.class, "elementCount", new InvokeSpec("this._sequenceLayout", "elementCount")); - }); - - final JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + '.' + simpleClassName); - try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) { - file.write(out); - } - } - - private void addSetterAt(ClassSpec classSpec, VariableElement e, String setType, String valueType, Spec setter) { - final String nameString = e.getSimpleName().toString(); - final TypeMirror eType = e.asType(); - 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 %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, paramIndex); - addSetterValue(e, valueType, methodSpec); - methodSpec.addStatement(setter); - }); - addSetter(classSpec, e, setType, valueType, insertArena, insertAllocator); - } - - 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 %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 (insertArena) { - invokeSpec.addArgument(paramArena); - } else if (insertAllocator) { - invokeSpec.addArgument(paramSegmentAllocator); - } - }) - .addArgument("0L") - .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(nameString) + "At"), methodSpec -> { - final SizedSeg sizedSeg = e.getAnnotation(SizedSeg.class); - 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 " + nameString + '}', insertCount ? "@param count the length of the array\n " : "")); - 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); - } - - private void addGetter(ClassSpec classSpec, VariableElement e, String getType, String returnType, boolean insertCount) { - 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); - final String eDoc = getDocument(e); - methodSpec.setDocument(""" - Gets the first %s. - - %s@return %1$s\ - """.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"); - } - methodSpec.addStatement(Spec.returnStatement(new InvokeSpec("this", name + "At") - .also(invokeSpec -> { - if (insertCount) { - invokeSpec.addArgument("count"); - } - }) - .addArgument("0L"))); - }); - } - - private void addSetterValue(VariableElement e, String valueType, MethodSpec methodSpec) { - methodSpec.addParameter(new ParameterSpec(valueType, e.getSimpleName().toString()).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 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 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)); - }); - } - - 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); - } - - 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/Unmarshal.java b/src/main/java/overrun/marshal/Unmarshal.java index 6c63db7..6bb9e8d 100644 --- a/src/main/java/overrun/marshal/Unmarshal.java +++ b/src/main/java/overrun/marshal/Unmarshal.java @@ -16,6 +16,8 @@ package overrun.marshal; +import org.jetbrains.annotations.Nullable; + import java.lang.foreign.AddressLayout; import java.lang.foreign.MemoryLayout; import java.lang.foreign.MemorySegment; @@ -45,14 +47,23 @@ public final class Unmarshal { private Unmarshal() { } + /** + * {@return {@code true} if the given segment is a null pointer; {@code false} otherwise} + * + * @param segment the native segment + */ + public static boolean isNullPointer(@Nullable MemorySegment segment) { + return segment == null || segment.address() == 0L; + } + /** * Unmarshal the given segment as a string. * * @param segment the segment * @return the string */ - public static String unmarshalAsString(MemorySegment segment) { - return segment.getString(0L); + public static @Nullable String unmarshalAsString(MemorySegment segment) { + return isNullPointer(segment) ? null : segment.getString(0L); } /** @@ -62,8 +73,8 @@ public static String unmarshalAsString(MemorySegment segment) { * @param charset the charset * @return the string */ - public static String unmarshalAsString(MemorySegment segment, Charset charset) { - return segment.getString(0L, charset); + public static @Nullable String unmarshalAsString(MemorySegment segment, Charset charset) { + return isNullPointer(segment) ? null : segment.getString(0L, charset); } /** @@ -73,7 +84,8 @@ public static String unmarshalAsString(MemorySegment segment, Charset charset) { * @param segment the segment * @return the array */ - public static boolean[] unmarshal(OfBoolean elementLayout, MemorySegment segment) { + public static boolean @Nullable [] unmarshal(OfBoolean elementLayout, MemorySegment segment) { + if (isNullPointer(segment)) return null; final boolean[] arr = new boolean[checkArraySize(boolean[].class.getSimpleName(), segment.byteSize(), (int) elementLayout.byteSize())]; for (int i = 0, l = arr.length; i < l; i++) { arr[i] = (boolean) Marshal.vh_booleanArray.get(segment, (long) i); @@ -88,8 +100,8 @@ public static boolean[] unmarshal(OfBoolean elementLayout, MemorySegment segment * @param segment the segment * @return the array */ - public static char[] unmarshal(OfChar elementLayout, MemorySegment segment) { - return segment.toArray(elementLayout); + public static char @Nullable [] unmarshal(OfChar elementLayout, MemorySegment segment) { + return isNullPointer(segment) ? null : segment.toArray(elementLayout); } /** @@ -99,8 +111,8 @@ public static char[] unmarshal(OfChar elementLayout, MemorySegment segment) { * @param segment the segment * @return the array */ - public static byte[] unmarshal(OfByte elementLayout, MemorySegment segment) { - return segment.toArray(elementLayout); + public static byte @Nullable [] unmarshal(OfByte elementLayout, MemorySegment segment) { + return isNullPointer(segment) ? null : segment.toArray(elementLayout); } /** @@ -110,8 +122,8 @@ public static byte[] unmarshal(OfByte elementLayout, MemorySegment segment) { * @param segment the segment * @return the array */ - public static short[] unmarshal(OfShort elementLayout, MemorySegment segment) { - return segment.toArray(elementLayout); + public static short @Nullable [] unmarshal(OfShort elementLayout, MemorySegment segment) { + return isNullPointer(segment) ? null : segment.toArray(elementLayout); } /** @@ -121,8 +133,8 @@ public static short[] unmarshal(OfShort elementLayout, MemorySegment segment) { * @param segment the segment * @return the array */ - public static int[] unmarshal(OfInt elementLayout, MemorySegment segment) { - return segment.toArray(elementLayout); + public static int @Nullable [] unmarshal(OfInt elementLayout, MemorySegment segment) { + return isNullPointer(segment) ? null : segment.toArray(elementLayout); } /** @@ -132,8 +144,8 @@ public static int[] unmarshal(OfInt elementLayout, MemorySegment segment) { * @param segment the segment * @return the array */ - public static long[] unmarshal(OfLong elementLayout, MemorySegment segment) { - return segment.toArray(elementLayout); + public static long @Nullable [] unmarshal(OfLong elementLayout, MemorySegment segment) { + return isNullPointer(segment) ? null : segment.toArray(elementLayout); } /** @@ -143,8 +155,8 @@ public static long[] unmarshal(OfLong elementLayout, MemorySegment segment) { * @param segment the segment * @return the array */ - public static float[] unmarshal(OfFloat elementLayout, MemorySegment segment) { - return segment.toArray(elementLayout); + public static float @Nullable [] unmarshal(OfFloat elementLayout, MemorySegment segment) { + return isNullPointer(segment) ? null : segment.toArray(elementLayout); } /** @@ -154,8 +166,89 @@ public static float[] unmarshal(OfFloat elementLayout, MemorySegment segment) { * @param segment the segment * @return the array */ - public static double[] unmarshal(OfDouble elementLayout, MemorySegment segment) { - return segment.toArray(elementLayout); + public static double @Nullable [] unmarshal(OfDouble elementLayout, MemorySegment segment) { + return isNullPointer(segment) ? null : segment.toArray(elementLayout); + } + + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static boolean @Nullable [] unmarshalAsBooleanArray(MemorySegment segment) { + return unmarshal(JAVA_BOOLEAN, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static char @Nullable [] unmarshalAsCharArray(MemorySegment segment) { + return unmarshal(JAVA_CHAR, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static byte @Nullable [] unmarshalAsByteArray(MemorySegment segment) { + return unmarshal(JAVA_BYTE, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static short @Nullable [] unmarshalAsShortArray(MemorySegment segment) { + return unmarshal(JAVA_SHORT, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static int @Nullable [] unmarshalAsIntArray(MemorySegment segment) { + return unmarshal(JAVA_INT, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static long @Nullable [] unmarshalAsLongArray(MemorySegment segment) { + return unmarshal(JAVA_LONG, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static float @Nullable [] unmarshalAsFloatArray(MemorySegment segment) { + return unmarshal(JAVA_FLOAT, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static double @Nullable [] unmarshalAsDoubleArray(MemorySegment segment) { + return unmarshal(JAVA_DOUBLE, segment); } /** @@ -168,8 +261,8 @@ public static double[] unmarshal(OfDouble elementLayout, MemorySegment segment) * @param the type of the element * @return the array */ - public static T[] unmarshal(MemoryLayout elementLayout, MemorySegment segment, IntFunction generator, Function function) { - return segment.elements(elementLayout).map(function).toArray(generator); + public static T @Nullable [] unmarshal(MemoryLayout elementLayout, MemorySegment segment, IntFunction generator, Function function) { + return isNullPointer(segment) ? null : segment.elements(elementLayout).map(function).toArray(generator); } /** @@ -179,8 +272,18 @@ public static T[] unmarshal(MemoryLayout elementLayout, MemorySegment segmen * @param segment the segment * @return the array */ - public static MemorySegment[] unmarshal(AddressLayout elementLayout, MemorySegment segment) { - return unmarshal(elementLayout, segment, MemorySegment[]::new, Function.identity()); + public static MemorySegment @Nullable [] unmarshal(AddressLayout elementLayout, MemorySegment segment) { + return unmarshal(elementLayout, segment, MemorySegment[]::new, s -> s.get(ADDRESS, 0L)); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static MemorySegment @Nullable [] unmarshalAsAddressArray(MemorySegment segment) { + return unmarshal(ADDRESS, segment); } /** @@ -190,7 +293,7 @@ public static MemorySegment[] unmarshal(AddressLayout elementLayout, MemorySegme * @param segment the segment * @return the array */ - public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment) { + public static String @Nullable [] unmarshalAsStringArray(AddressLayout elementLayout, MemorySegment segment) { return unmarshal(elementLayout, segment, String[]::new, s -> s.get(STRING_LAYOUT, 0L).getString(0L)); } @@ -202,10 +305,31 @@ public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegm * @param charset the charset * @return the array */ - public static String[] unmarshalAsString(AddressLayout elementLayout, MemorySegment segment, Charset charset) { + public static String @Nullable [] unmarshalAsStringArray(AddressLayout elementLayout, MemorySegment segment, Charset charset) { return unmarshal(elementLayout, segment, String[]::new, s -> s.get(STRING_LAYOUT, 0L).getString(0L, charset)); } + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @return the array + */ + public static String @Nullable [] unmarshalAsStringArray(MemorySegment segment) { + return unmarshalAsStringArray(ADDRESS, segment); + } + + /** + * Unmarshal the given segment as an array. + * + * @param segment the segment + * @param charset the charset + * @return the array + */ + public static String @Nullable [] unmarshalAsStringArray(MemorySegment segment, Charset charset) { + return unmarshalAsStringArray(ADDRESS, segment, charset); + } + private static int checkArraySize(String typeName, long byteSize, int elemSize) { if (!((byteSize & (elemSize - 1)) == 0)) { throw new IllegalStateException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, byteSize)); @@ -223,7 +347,7 @@ private static int checkArraySize(String typeName, long byteSize, int elemSize) * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, boolean[] dst) { + public static void copy(MemorySegment src, boolean @Nullable [] dst) { if (dst == null) return; for (int i = 0; i < dst.length; i++) { dst[i] = (boolean) Marshal.vh_booleanArray.get(src, (long) i); @@ -236,7 +360,7 @@ public static void copy(MemorySegment src, boolean[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, char[] dst) { + public static void copy(MemorySegment src, char @Nullable [] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_CHAR, 0L, dst, 0, dst.length); } @@ -247,7 +371,7 @@ public static void copy(MemorySegment src, char[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, byte[] dst) { + public static void copy(MemorySegment src, byte @Nullable [] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_BYTE, 0L, dst, 0, dst.length); } @@ -258,7 +382,7 @@ public static void copy(MemorySegment src, byte[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, short[] dst) { + public static void copy(MemorySegment src, short @Nullable [] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_SHORT, 0L, dst, 0, dst.length); } @@ -269,7 +393,7 @@ public static void copy(MemorySegment src, short[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, int[] dst) { + public static void copy(MemorySegment src, int @Nullable [] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_INT, 0L, dst, 0, dst.length); } @@ -280,7 +404,7 @@ public static void copy(MemorySegment src, int[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, long[] dst) { + public static void copy(MemorySegment src, long @Nullable [] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_LONG, 0L, dst, 0, dst.length); } @@ -291,7 +415,7 @@ public static void copy(MemorySegment src, long[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, float[] dst) { + public static void copy(MemorySegment src, float @Nullable [] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_FLOAT, 0L, dst, 0, dst.length); } @@ -302,7 +426,7 @@ public static void copy(MemorySegment src, float[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, double[] dst) { + public static void copy(MemorySegment src, double @Nullable [] dst) { if (dst == null) return; MemorySegment.copy(src, JAVA_DOUBLE, 0L, dst, 0, dst.length); } @@ -313,7 +437,7 @@ public static void copy(MemorySegment src, double[] dst) { * @param src the source segment * @param dst the destination */ - public static void copy(MemorySegment src, String[] dst) { + public static void copy(MemorySegment src, String @Nullable [] dst) { if (dst == null) return; for (int i = 0; i < dst.length; i++) { dst[i] = ((MemorySegment) vh_stringArray.get(src, (long) i)).getString(0L); @@ -327,7 +451,7 @@ public static void copy(MemorySegment src, String[] dst) { * @param dst the destination * @param charset the charset */ - public static void copy(MemorySegment src, String[] dst, Charset charset) { + public static void copy(MemorySegment src, String @Nullable [] dst, Charset charset) { if (dst == null) return; for (int i = 0; i < dst.length; i++) { dst[i] = ((MemorySegment) vh_stringArray.get(src, (long) i)).getString(0L, charset); diff --git a/src/main/java/overrun/marshal/Upcall.java b/src/main/java/overrun/marshal/Upcall.java index fc13fb5..052fd2f 100644 --- a/src/main/java/overrun/marshal/Upcall.java +++ b/src/main/java/overrun/marshal/Upcall.java @@ -221,8 +221,7 @@ public MethodHandle downcall(MemorySegment stub) { * @return the downcall type */ public T wrap(MemorySegment stub, Function function) { - if (stub.address() == 0L) return null; - return function.apply(downcall(stub)); + return Unmarshal.isNullPointer(stub) ? null : function.apply(downcall(stub)); } /** diff --git a/src/main/java/overrun/marshal/gen/AccessModifier.java b/src/main/java/overrun/marshal/gen/AccessModifier.java deleted file mode 100644 index 43fe74d..0000000 --- a/src/main/java/overrun/marshal/gen/AccessModifier.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen; - -/** - * The access modifier. - * - * @author squid233 - * @since 0.1.0 - */ -public enum AccessModifier { - /** - * {@code public} access - */ - PUBLIC("public"), - /** - * {@code protected} access - */ - PROTECTED("protected"), - /** - * package-private access - */ - PACKAGE_PRIVATE(""), - /** - * {@code private} access - */ - PRIVATE("private"); - - private final String toStringValue; - - AccessModifier(String toStringValue) { - this.toStringValue = toStringValue; - } - - @Override - public String toString() { - return toStringValue; - } -} diff --git a/src/main/java/overrun/marshal/gen/struct/Const.java b/src/main/java/overrun/marshal/gen/struct/Const.java deleted file mode 100644 index 1866ef8..0000000 --- a/src/main/java/overrun/marshal/gen/struct/Const.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen.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
- * record Vector2(int x, int y) {
- * }
- * }
- * - * @author squid233 - * @since 0.1.0 - */ -@Documented -@Target({ElementType.FIELD, ElementType.TYPE}) -@Retention(RetentionPolicy.SOURCE) -public @interface Const { -} diff --git a/src/main/java/overrun/marshal/gen/struct/Padding.java b/src/main/java/overrun/marshal/gen/struct/Padding.java deleted file mode 100644 index 27a783b..0000000 --- a/src/main/java/overrun/marshal/gen/struct/Padding.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen.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/gen/struct/Struct.java b/src/main/java/overrun/marshal/gen/struct/Struct.java deleted file mode 100644 index fbf9ac2..0000000 --- a/src/main/java/overrun/marshal/gen/struct/Struct.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen.struct; - -import java.lang.annotation.*; - -/** - * 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

- *

- * /**
- *  * {@linkplain LAYOUT Layout}
- *  */
- * @Struct
- * class Point {
- *     @Skip
- *     int LAYOUT;
- *     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/gen/struct/StructRef.java b/src/main/java/overrun/marshal/gen/struct/StructRef.java deleted file mode 100644 index e22ea36..0000000 --- a/src/main/java/overrun/marshal/gen/struct/StructRef.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen.struct; - -import java.lang.annotation.*; - -/** - * Marks a field, a parameter, or the return type of method 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(); -} diff --git a/src/main/java/overrun/marshal/gen/struct/package-info.java b/src/main/java/overrun/marshal/gen/struct/package-info.java deleted file mode 100644 index 8e9317d..0000000 --- a/src/main/java/overrun/marshal/gen/struct/package-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -/** - * The struct package of marshal. - * - * @author squid233 - * @see overrun.marshal.gen.struct.Struct - * @since 0.1.0 - */ -package overrun.marshal.gen.struct; diff --git a/src/main/java/overrun/marshal/gen1/Annotatable.java b/src/main/java/overrun/marshal/gen1/Annotatable.java deleted file mode 100644 index 267230b..0000000 --- a/src/main/java/overrun/marshal/gen1/Annotatable.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -/** - * 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/gen1/AnnotationSpec.java b/src/main/java/overrun/marshal/gen1/AnnotationSpec.java deleted file mode 100644 index 2a8781f..0000000 --- a/src/main/java/overrun/marshal/gen1/AnnotationSpec.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.stream.Collectors; - -/** - * Annotation - * - * @author squid233 - * @since 0.1.0 - */ -public final class AnnotationSpec implements Spec { - private final String type; - private final Map arguments = new LinkedHashMap<>(); - private final boolean escapeAtCharacter; - - /** - * Constructor - * - * @param type type - */ - @Deprecated(since = "0.1.0") - public AnnotationSpec(Class type) { - this(type.getSimpleName()); - } - - /** - * Constructor - * - * @param type type - * @param escapeAtCharacter escapeAtCharacter - */ - public AnnotationSpec(String type, boolean escapeAtCharacter) { - this.type = type; - this.escapeAtCharacter = escapeAtCharacter; - } - - /** - * Constructor - * - * @param type type - */ - public AnnotationSpec(String type) { - this(type, false); - } - - /** - * Add an argument - * - * @param name name - * @param value value - * @return this - */ - public AnnotationSpec addArgument(String name, String value) { - arguments.put(name, value); - return this; - } - - @Override - public void append(StringBuilder builder, int indent) { - if (escapeAtCharacter) { - builder.append("@"); - } else { - builder.append('@'); - } - builder.append(type); - if (!arguments.isEmpty()) { - builder.append('('); - if (arguments.size() == 1 && "value".equals(arguments.keySet().stream().findFirst().orElse(null))) { - builder.append(arguments.get("value")); - } else { - builder.append(arguments.entrySet().stream() - .map(e -> e.getKey() + " = " + e.getValue()) - .collect(Collectors.joining(", "))); - } - builder.append(')'); - } - } -} diff --git a/src/main/java/overrun/marshal/gen1/CatchClause.java b/src/main/java/overrun/marshal/gen1/CatchClause.java deleted file mode 100644 index dc00821..0000000 --- a/src/main/java/overrun/marshal/gen1/CatchClause.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; - -/** - * catch clause - * - * @author squid233 - * @since 0.1.0 - */ -public final class CatchClause implements Spec, StatementBlock { - private final Spec type; - private final String name; - private final List statements = new ArrayList<>(); - - /** - * Constructor - * - * @param type exception type - * @param name variable name - */ - public CatchClause(Spec type, String name) { - this.type = type; - this.name = name; - } - - /** - * Constructor - * - * @param type exception type - * @param name variable name - */ - public CatchClause(String type, String name) { - this(Spec.literal(type), name); - } - - /** - * Add a statement - * - * @param spec statement - */ - @Override - public void addStatement(Spec spec) { - statements.add(spec); - } - - /** - * {@return name} - */ - public String name() { - return name; - } - - @Override - public void append(StringBuilder builder, int indent) { - final String indentString = Spec.indentString(indent); - builder.append("catch ("); - type.append(builder, indent); - builder.append(' ').append(name).append(") {\n"); - statements.forEach(spec -> spec.append(builder, indent + 4)); - builder.append(indentString).append('}'); - } -} diff --git a/src/main/java/overrun/marshal/gen1/ClassSpec.java b/src/main/java/overrun/marshal/gen1/ClassSpec.java deleted file mode 100644 index 18dd18d..0000000 --- a/src/main/java/overrun/marshal/gen1/ClassSpec.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import overrun.marshal.gen.AccessModifier; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -/** - * Class spec - * - * @author squid233 - * @since 0.1.0 - */ -public final class ClassSpec implements Annotatable, Spec { - /** - * Class - */ - public static final int CLASS = 1; - /** - * Interface - */ - public static final int INTERFACE = 2; - private final String className; - private String document = null; - private AccessModifier accessModifier = AccessModifier.PUBLIC; - private boolean isFinal = false; - private int classType = CLASS; - private final List fieldSpecs = new ArrayList<>(); - 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 - * - * @param className class name - */ - public ClassSpec(String className) { - this.className = className; - } - - /** - * Set document - * - * @param document document - */ - public void setDocument(String document) { - this.document = document; - } - - /** - * Set access modifier - * - * @param accessModifier access modifier - */ - public void setAccessModifier(AccessModifier accessModifier) { - this.accessModifier = accessModifier; - } - - /** - * Set final - * - * @param beFinal final - */ - public void setFinal(boolean beFinal) { - this.isFinal = beFinal; - } - - /** - * Set class type - * - * @param classType class type - */ - public void setClassType(int classType) { - this.classType = classType; - } - - /** - * Add a field - * - * @param fieldSpec field - */ - public void addField(VariableStatement fieldSpec) { - fieldSpecs.add(fieldSpec); - } - - /** - * Add a field and perform the action - * - * @param fieldSpec field - * @param consumer action - */ - public void addField(VariableStatement fieldSpec, Consumer consumer) { - consumer.accept(fieldSpec); - addField(fieldSpec); - } - - /** - * Add a method and perform the action - * - * @param methodSpec method - * @param consumer action - */ - public void addMethod(MethodSpec methodSpec, Consumer consumer) { - consumer.accept(methodSpec); - methodSpecs.add(methodSpec); - } - - @Override - public void addAnnotation(AnnotationSpec annotationSpec) { - annotationSpecs.add(annotationSpec); - } - - /** - * Add superclass - * - * @param className class name - */ - 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); - // document - Spec.appendDocument(builder, document, indentString); - // annotation - annotationSpecs.forEach(annotationSpec -> { - annotationSpec.append(builder, indent); - builder.append('\n'); - }); - // declaration - builder.append(indentString).append(accessModifier); - if (isFinal && classType == CLASS) { - builder.append(" final"); - } - builder.append(' ') - .append(switch (classType) { - case CLASS -> "class"; - case INTERFACE -> "interface"; - default -> - throw new IllegalStateException("Unsupported class type for " + className + ": " + classType); - }) - .append(' ').append(className); - addAfterClass(builder, "extends", superclasses); - addAfterClass(builder, "implements", superinterfaces); - builder.append(" {\n"); - // body - fieldSpecs.forEach(variableStatement -> variableStatement.append(builder, indent + 4)); - builder.append('\n'); - methodSpecs.forEach(methodSpec -> methodSpec.append(builder, indent + 4)); - // 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/gen1/ConstructSpec.java b/src/main/java/overrun/marshal/gen1/ConstructSpec.java deleted file mode 100644 index 1f64fb9..0000000 --- a/src/main/java/overrun/marshal/gen1/ConstructSpec.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; - -/** - * Constructor - * - * @author squid233 - * @since 0.1.0 - */ -public final class ConstructSpec implements Spec { - private final Spec object; - private final List arguments = new ArrayList<>(); - - /** - * Constructor - * - * @param object the caller object - */ - public ConstructSpec(Spec object) { - this.object = object; - } - - /** - * Constructor - * - * @param object the caller object - */ - public ConstructSpec(String object) { - this(Spec.literal(object)); - } - - /** - * Add argument - * - * @param spec the argument - * @return this - */ - public ConstructSpec addArgument(Spec spec) { - arguments.add(spec); - return this; - } - - @Override - public void append(StringBuilder builder, int indent) { - builder.append("new "); - object.append(builder, indent); - builder.append('('); - for (int i = 0; i < arguments.size(); i++) { - Spec spec = arguments.get(i); - if (i != 0) { - builder.append(", "); - } - spec.append(builder, indent); - } - builder.append(')'); - } -} diff --git a/src/main/java/overrun/marshal/gen1/ElseClause.java b/src/main/java/overrun/marshal/gen1/ElseClause.java deleted file mode 100644 index 6d49edb..0000000 --- a/src/main/java/overrun/marshal/gen1/ElseClause.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; - -/** - * Else - * - * @author squid233 - * @since 0.1.0 - */ -public final class ElseClause implements Spec, StatementBlock { - private final Spec condition; - private final List statements = new ArrayList<>(); - - private ElseClause(Spec condition) { - this.condition = condition; - } - - /** - * Create else clause - * - * @return else clause - */ - public static ElseClause of() { - return new ElseClause(null); - } - - /** - * Create else clause - * - * @param condition condition - * @return else clause - */ - public static ElseClause ofIf(Spec condition) { - return new ElseClause(condition); - } - - @Override - public void addStatement(Spec spec) { - statements.add(spec); - } - - @Override - public void append(StringBuilder builder, int indent) { - final String indentString = Spec.indentString(indent); - builder.append("else"); - if (condition != null) { - builder.append(" if ("); - condition.append(builder, indent); - builder.append(')'); - } - builder.append(" {\n"); - statements.forEach(spec -> spec.append(builder, indent + 4)); - builder.append(indentString).append('}'); - } -} diff --git a/src/main/java/overrun/marshal/gen1/IfStatement.java b/src/main/java/overrun/marshal/gen1/IfStatement.java deleted file mode 100644 index 741abdb..0000000 --- a/src/main/java/overrun/marshal/gen1/IfStatement.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -/** - * If - * - * @author squid233 - * @since 0.1.0 - */ -public final class IfStatement implements Spec, StatementBlock { - private final Spec condition; - private final List statements = new ArrayList<>(); - private final List elseClauses = new ArrayList<>(); - - /** - * Constructor - * - * @param condition condition - */ - public IfStatement(Spec condition) { - this.condition = condition; - } - - /** - * Add a statement - * - * @param spec statement - */ - @Override - public void addStatement(Spec spec) { - statements.add(spec); - } - - /** - * Add an else clause and perform the action - * - * @param elseClause else clause - * @param consumer action - */ - public void addElseClause(ElseClause elseClause, Consumer consumer) { - consumer.accept(elseClause); - elseClauses.add(elseClause); - } - - @Override - public void append(StringBuilder builder, int indent) { - final String indentString = Spec.indentString(indent); - builder.append(indentString).append("if ("); - condition.append(builder, indent); - builder.append(") {\n"); - statements.forEach(spec -> spec.append(builder, indent + 4)); - builder.append(indentString).append('}'); - if (!elseClauses.isEmpty()) { - builder.append(' '); - elseClauses.forEach(elseClause -> elseClause.append(builder, indent)); - } - builder.append('\n'); - } -} diff --git a/src/main/java/overrun/marshal/gen1/InvokeSpec.java b/src/main/java/overrun/marshal/gen1/InvokeSpec.java deleted file mode 100644 index 09d4be5..0000000 --- a/src/main/java/overrun/marshal/gen1/InvokeSpec.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -/** - * Invoke - * - * @author squid233 - * @since 0.1.0 - */ -public final class InvokeSpec implements Spec { - private final Spec object; - private final String method; - private final List arguments = new ArrayList<>(); - - /** - * Constructor - * - * @param object the caller object - * @param method the method - */ - @Deprecated(since = "0.1.0") - public InvokeSpec(Class object, String method) { - this(Spec.simpleClassName(object), method); - } - - /** - * Constructor - * - * @param object the caller object - * @param method the method - */ - public InvokeSpec(Spec object, String method) { - this.object = object; - this.method = method; - } - - /** - * Constructor - * - * @param object the caller object - * @param method the method - */ - 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 - * - * @param spec the argument - * @return this - */ - public InvokeSpec addArgument(Spec spec) { - if (spec == null) { - return this; - } - arguments.add(spec); - return this; - } - - /** - * Add argument - * - * @param spec the argument - * @return this - */ - public InvokeSpec addArgument(String spec) { - if (spec == null) { - return this; - } - return addArgument(Spec.literal(spec)); - } - - /** - * Runs the action - * - * @param consumer the action - * @return this - */ - public InvokeSpec also(Consumer consumer) { - consumer.accept(this); - return this; - } - - @Override - public void append(StringBuilder builder, int indent) { - final String indentString = Spec.indentString(indent + 4); - if (object != null) { - object.append(builder, indent); - if (object instanceof InvokeSpec) { - builder.append('\n').append(indentString); - } - builder.append('.'); - } - builder.append(method).append('('); - for (int i = 0, size = arguments.size(); i < size; i++) { - Spec spec = arguments.get(i); - if (i != 0) { - builder.append(","); - if (size >= 5) { - builder.append('\n').append(indentString); - } else { - builder.append(' '); - } - } - spec.append(builder, indent + 4); - } - builder.append(')'); - } -} diff --git a/src/main/java/overrun/marshal/gen1/LambdaSpec.java b/src/main/java/overrun/marshal/gen1/LambdaSpec.java deleted file mode 100644 index 4e42426..0000000 --- a/src/main/java/overrun/marshal/gen1/LambdaSpec.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; - -/** - * Lambda - * - * @author squid233 - * @since 0.1.0 - */ -public final class LambdaSpec implements Spec, StatementBlock { - private final String[] parameters; - private final List statements = new ArrayList<>(); - - /** - * Constructor - * - * @param parameters parameters - */ - public LambdaSpec(String... parameters) { - this.parameters = parameters; - } - - /** - * Add a statement - * - * @param spec statement - */ - @Override - public void addStatement(Spec spec) { - statements.add(spec); - } - - /** - * Add a statement - * - * @param spec statement - * @return this - */ - public LambdaSpec addStatementThis(Spec spec) { - addStatement(spec); - return this; - } - - @Override - public void append(StringBuilder builder, int indent) { - final boolean multi = statements.size() > 1; - builder.append(switch (parameters.length) { - case 0 -> "()"; - case 1 -> parameters[0]; - default -> '(' + String.join(", ", parameters) + ')'; - }).append(" -> "); - if (multi) { - builder.append("{\n"); - } - statements.forEach(spec -> spec.append(builder, multi ? indent + 4 : indent)); - if (multi) { - builder.append(Spec.indentString(indent)).append('}'); - } - } -} diff --git a/src/main/java/overrun/marshal/gen1/MethodSpec.java b/src/main/java/overrun/marshal/gen1/MethodSpec.java deleted file mode 100644 index 61a4d54..0000000 --- a/src/main/java/overrun/marshal/gen1/MethodSpec.java +++ /dev/null @@ -1,209 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import overrun.marshal.gen.AccessModifier; - -import java.util.ArrayList; -import java.util.List; - -/** - * Method spec - * - * @author squid233 - * @since 0.1.0 - */ -public final class MethodSpec implements Annotatable, Spec, StatementBlock { - private final Spec returnType; - private final String name; - private String document = null; - private final List annotations = new ArrayList<>(); - private AccessModifier accessModifier = AccessModifier.PUBLIC; - private final List parameters = new ArrayList<>(); - private final List statements = new ArrayList<>(); - private boolean isStatic = false; - private boolean hasMethodBody = true; - private boolean isDefault = false; - - /** - * Constructor - * - * @param returnType return type - * @param name name - */ - public MethodSpec(Spec returnType, String name) { - this.returnType = returnType; - this.name = name; - } - - /** - * Constructor - * - * @param returnType return type - * @param name name - */ - public MethodSpec(String returnType, String name) { - this(Spec.literal(returnType), name); - } - - /** - * Set document - * - * @param document document - */ - public void setDocument(String document) { - this.document = document; - } - - @Override - public void addAnnotation(AnnotationSpec annotationSpec) { - annotations.add(annotationSpec); - } - - /** - * Set access modifier - * - * @param accessModifier access modifier - */ - public void setAccessModifier(AccessModifier accessModifier) { - this.accessModifier = accessModifier; - } - - /** - * Add a parameter - * - * @param parameterSpec parameter - */ - 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 - * - * @param type type - * @param name name - */ - public void addParameter(String type, String name) { - addParameter(new ParameterSpec(type, name)); - } - - /** - * Set static - * - * @param isStatic static - */ - public void setStatic(boolean isStatic) { - this.isStatic = isStatic; - } - - /** - * Set has method body - * - * @param hasMethodBody has method body - */ - public void setHasMethodBody(boolean hasMethodBody) { - this.hasMethodBody = hasMethodBody; - } - - /** - * Set default - * - * @param isDefault default - */ - public void setDefault(boolean isDefault) { - this.isDefault = isDefault; - } - - /** - * Add a statement - * - * @param spec statement - */ - @Override - public void addStatement(Spec spec) { - statements.add(spec); - } - - @Override - public void append(StringBuilder builder, int indent) { - final String indentString = Spec.indentString(indent); - final String indentString4 = Spec.indentString(indent + 4); - final int size = parameters.size(); - final boolean separateLine = size >= 5; - Spec.appendDocument(builder, document, indentString); - annotations.forEach(annotationSpec -> { - builder.append(indentString); - annotationSpec.append(builder, indent); - builder.append('\n'); - }); - builder.append(indentString).append(accessModifier); - if (accessModifier != AccessModifier.PACKAGE_PRIVATE) { - builder.append(' '); - } - if (isStatic) { - builder.append("static "); - } - if (isDefault) { - builder.append("default "); - } - if (returnType != null) { - returnType.append(builder, indent); - builder.append(' '); - } - builder.append(name).append('('); - if (separateLine) { - builder.append('\n').append(indentString4); - } - for (int i = 0; i < size; i++) { - ParameterSpec parameterSpec = parameters.get(i); - if (i != 0) { - builder.append(","); - if (separateLine) { - builder.append("\n").append(indentString4); - } else { - builder.append(' '); - } - } - parameterSpec.append(builder, indent); - } - if (separateLine) { - builder.append('\n').append(indentString); - } - builder.append(')'); - if (hasMethodBody) { - builder.append(" {"); - builder.append('\n'); - statements.forEach(spec -> spec.append(builder, indent + 4)); - builder.append(indentString); - builder.append('}'); - } else { - builder.append(';'); - } - builder.append("\n\n"); - } -} diff --git a/src/main/java/overrun/marshal/gen1/ParameterSpec.java b/src/main/java/overrun/marshal/gen1/ParameterSpec.java deleted file mode 100644 index 439096e..0000000 --- a/src/main/java/overrun/marshal/gen1/ParameterSpec.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - -/** - * Method parameter - * - * @author squid233 - * @since 0.1.0 - */ -public final class ParameterSpec implements Annotatable, Spec { - private final Spec type; - private final String name; - private final List annotations = new ArrayList<>(); - - /** - * Constructor - * - * @param type type - * @param name name - */ - public ParameterSpec(Spec type, String name) { - this.type = type; - this.name = name; - } - - /** - * Constructor - * - * @param type type - * @param name name - */ - public ParameterSpec(String type, String name) { - this(Spec.literal(type), name); - } - - @Override - public void addAnnotation(AnnotationSpec annotationSpec) { - annotations.add(annotationSpec); - } - - /** - * Also runs the action - * - * @param consumer the action - * @return this - */ - public ParameterSpec also(Consumer consumer) { - consumer.accept(this); - return this; - } - - @Override - public void append(StringBuilder builder, int indent) { - annotations.forEach(annotationSpec -> { - annotationSpec.append(builder, indent); - builder.append(' '); - }); - type.append(builder, indent); - builder.append(' ').append(name); - } -} diff --git a/src/main/java/overrun/marshal/gen1/SourceFile.java b/src/main/java/overrun/marshal/gen1/SourceFile.java deleted file mode 100644 index 40d8b9f..0000000 --- a/src/main/java/overrun/marshal/gen1/SourceFile.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.io.IOException; -import java.io.Writer; -import java.util.*; -import java.util.function.Consumer; - -/** - * Source file - * - * @author squid233 - * @since 0.1.0 - */ -public final class SourceFile { - private final List classSpecs = new ArrayList<>(1); - private final String packageName; - private final Set imports = new LinkedHashSet<>(); - - /** - * Constructor - * - * @param packageName package name - */ - public SourceFile(String packageName) { - this.packageName = packageName; - } - - /** - * Add imports - * - * @param names class names - */ - public void addImports(String... names) { - imports.addAll(Arrays.asList(names)); - } - - /** - * Add import - * - * @param name class name - */ - public void addImport(String name) { - imports.add(name); - } - - /** - * Add imports - * - * @param names class names - */ - public void addImports(Class... names) { - Arrays.stream(names).map(Class::getCanonicalName).forEach(this::addImport); - } - - /** - * Add import - * - * @param name class name - */ - public void addImport(Class name) { - addImport(name.getCanonicalName()); - } - - /** - * Add a class and perform the action - * - * @param className class name - * @param consumer action - */ - public void addClass(String className, Consumer consumer) { - final ClassSpec spec = new ClassSpec(className); - consumer.accept(spec); - classSpecs.add(spec); - } - - /** - * Write to the writer - * - * @param writer the writer - * @throws IOException If an I/O error occurs - */ - public void write(Writer writer) throws IOException { - final StringBuilder sb = new StringBuilder(32768); - // header - sb.append("// This file is auto-generated. DO NOT EDIT!\n"); - // package - if (packageName != null) { - sb.append("package ").append(packageName).append(";\n\n"); - } - // import - if (!imports.isEmpty()) { - imports.forEach(s -> sb.append("import ").append(s).append(";\n")); - sb.append('\n'); - } - classSpecs.forEach(classSpec -> classSpec.append(sb, 0)); - writer.write(sb.toString()); - } -} diff --git a/src/main/java/overrun/marshal/gen1/Spec.java b/src/main/java/overrun/marshal/gen1/Spec.java deleted file mode 100644 index 4e13b4b..0000000 --- a/src/main/java/overrun/marshal/gen1/Spec.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.stream.Collectors; - -/** - * Spec - * - * @author squid233 - * @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, _) -> 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); - }; - } - - /** - * Create simple class name spec - * - * @param clazz class - * @return spec - */ - static Spec simpleClassName(Class clazz) { - return literal(clazz.getSimpleName()); - } - - /** - * 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 - * - * @param object object - * @param member member - * @return spec - */ - @Deprecated(since = "0.1.0") - static Spec accessSpec(Class object, String member) { - return literal(object.getSimpleName() + '.' + member); - } - - /** - * Create access spec - * - * @param object object - * @param member member - * @return spec - */ - @Deprecated(since = "0.1.0") - static Spec accessSpec(Class object, Class member) { - return literal(object.getSimpleName() + '.' + member.getSimpleName()); - } - - /** - * Create access spec - * - * @param object object - * @param member member - * @return spec - */ - static Spec accessSpec(String object, String member) { - return literal(object + '.' + member); - } - - /** - * Create ternary operator spec - * - * @param condition condition - * @param trueValue true value - * @param falseValue false value - * @return spec - */ - static Spec ternaryOp(Spec condition, Spec trueValue, Spec falseValue) { - return (builder, indent) -> { - builder.append('('); - condition.append(builder, indent); - builder.append(") ? ("); - trueValue.append(builder, indent); - builder.append(") : ("); - falseValue.append(builder, indent); - builder.append(')'); - }; - } - - /** - * Create not null spec - * - * @param value value - * @return spec - */ - 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 - * - * @param type type - * @param exp expression - * @return cast - */ - static Spec cast(Spec type, Spec exp) { - return (builder, indent) -> { - builder.append('('); - type.append(builder, indent); - builder.append(") "); - exp.append(builder, indent); - }; - } - - /** - * Create an expression casting - * - * @param type type - * @param exp expression - * @return cast - */ - static Spec cast(String type, Spec exp) { - return (builder, indent) -> { - builder.append('(').append(type).append(") "); - exp.append(builder, indent); - }; - } - - /** - * Create a throw statement - * - * @param exception exception - * @return statement - */ - static Spec throwStatement(Spec exception) { - return (builder, indent) -> { - builder.append(indentString(indent)).append("throw "); - exception.append(builder, indent); - builder.append(";\n"); - }; - } - - /** - * Create a return statement - * - * @param value return value - * @return statement - */ - static Spec returnStatement(Spec value) { - return (builder, indent) -> { - builder.append(indentString(indent)).append("return "); - value.append(builder, indent); - builder.append(";\n"); - }; - } - - /** - * Create a statement - * - * @param spec the code - * @return the statement - */ - static Spec statement(Spec spec) { - return (builder, indent) -> { - builder.append(indentString(indent)); - spec.append(builder, indent); - builder.append(";\n"); - }; - } - - /** - * Create an indented literal string - * - * @param s the string - * @return the spec - */ - static Spec indented(String s) { - return (builder, indent) -> builder.append(s.indent(indent)); - } - - /** - * Indent code block - * - * @param s the code - * @return the spec - */ - static Spec indentCodeBlock(String s) { - return (builder, indent) -> s.lines().findFirst().ifPresent(first -> { - final String indentString = indentString(indent); - builder.append(first); - if (s.lines().count() > 1) { - builder.append('\n').append(s.lines() - .skip(1) - .map(s1 -> indentString + s1) - .collect(Collectors.joining("\n"))); - } - }); - } - - /** - * Get the indent string - * - * @param indent indent count - * @return the string - */ - static String indentString(int indent) { - return " ".repeat(indent); - } - - /** - * Append document to the string builder - * - * @param builder the string builder - * @param document the document - * @param indentStr the indent string - */ - static void appendDocument(StringBuilder builder, String document, String indentStr) { - if (document != null) { - builder.append(indentStr).append("/**\n"); - document.lines().forEach(s -> builder.append(indentStr).append(" *").append(s).append('\n')); - builder.append(indentStr).append(" */\n"); - } - } - - /** - * Append to the string builder - * - * @param builder the string builder - * @param indent the indent - */ - void append(StringBuilder builder, int indent); -} diff --git a/src/main/java/overrun/marshal/gen1/StatementBlock.java b/src/main/java/overrun/marshal/gen1/StatementBlock.java deleted file mode 100644 index 519c420..0000000 --- a/src/main/java/overrun/marshal/gen1/StatementBlock.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -/** - * Statement block - * - * @author squid233 - * @since 0.1.0 - */ -public interface StatementBlock { - /** - * Add a statement - * - * @param spec statement - */ - void addStatement(Spec spec); -} diff --git a/src/main/java/overrun/marshal/gen1/TryStatement.java b/src/main/java/overrun/marshal/gen1/TryStatement.java deleted file mode 100644 index 5325fe5..0000000 --- a/src/main/java/overrun/marshal/gen1/TryStatement.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import java.util.ArrayList; -import java.util.List; - -/** - * Try - * - * @author squid233 - * @since 0.1.0 - */ -public final class TryStatement implements Spec, StatementBlock { - private final List resources = new ArrayList<>(); - private final List statements = new ArrayList<>(); - private final List catchClauses = new ArrayList<>(); - - /** - * Constructor - */ - public TryStatement() { - } - - @Override - public void addStatement(Spec spec) { - statements.add(spec); - } - - /** - * Add a resource - * - * @param resource resource - */ - public void addResource(Spec resource) { - resources.add(resource); - } - - /** - * Add a catch clause - * - * @param catchClause catch clause - */ - public void addCatchClause(CatchClause catchClause) { - catchClauses.add(catchClause); - } - - @Override - public void append(StringBuilder builder, int indent) { - final String indentString = Spec.indentString(indent); - builder.append(indentString).append("try "); - if (!resources.isEmpty()) { - builder.append('('); - for (int i = 0, s = resources.size(); i < s; i++) { - Spec resource = resources.get(i); - if (i > 0) { - builder.append(";\n"); - } - resource.append(builder, indent + 4); - } - builder.append(") "); - } - builder.append("{\n"); - statements.forEach(spec -> spec.append(builder, indent + 4)); - builder.append(indentString).append('}'); - if (!catchClauses.isEmpty()) { - builder.append(' '); - catchClauses.forEach(catchClause -> catchClause.append(builder, indent)); - } - builder.append('\n'); - } -} diff --git a/src/main/java/overrun/marshal/gen1/VariableStatement.java b/src/main/java/overrun/marshal/gen1/VariableStatement.java deleted file mode 100644 index d21951b..0000000 --- a/src/main/java/overrun/marshal/gen1/VariableStatement.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen1; - -import overrun.marshal.gen.AccessModifier; - -import java.util.ArrayList; -import java.util.List; - -/** - * Variable statement - * - * @author squid233 - * @since 0.1.0 - */ -public final class VariableStatement implements Annotatable, Spec { - private final String type; - private final String name; - private final Spec value; - private String document = null; - private AccessModifier accessModifier = AccessModifier.PUBLIC; - private boolean isStatic = false; - private boolean isFinal = false; - private boolean addSemicolon = true; - private final List annotations = new ArrayList<>(); - - /** - * Constructor - * - * @param type type - * @param name name - * @param value value - */ - @Deprecated - public VariableStatement(Class type, String name, Spec value) { - this(type.getSimpleName(), name, value); - } - - /** - * Constructor - * - * @param type type - * @param name name - * @param value value - */ - public VariableStatement(String type, String name, Spec value) { - this.type = type; - this.name = name; - this.value = value; - } - - /** - * Set document - * - * @param document document - * @return this - */ - public VariableStatement setDocument(String document) { - this.document = document; - return this; - } - - /** - * Set access modifier - * - * @param accessModifier access modifier - * @return this - */ - public VariableStatement setAccessModifier(AccessModifier accessModifier) { - this.accessModifier = accessModifier; - return this; - } - - /** - * Set static - * - * @param isStatic static - * @return this - */ - public VariableStatement setStatic(boolean isStatic) { - this.isStatic = isStatic; - return this; - } - - /** - * Set final - * - * @param isFinal final - * @return this - */ - public VariableStatement setFinal(boolean isFinal) { - this.isFinal = isFinal; - return this; - } - - /** - * setAddSemicolon - * - * @param addSemicolon addSemicolon - * @return this - */ - public VariableStatement setAddSemicolon(boolean addSemicolon) { - this.addSemicolon = addSemicolon; - return this; - } - - @Override - public void addAnnotation(AnnotationSpec annotationSpec) { - annotations.add(annotationSpec); - } - - @Override - public void append(StringBuilder builder, int indent) { - final String indentString = Spec.indentString(indent); - Spec.appendDocument(builder, document, indentString); - if (addSemicolon) { - builder.append(indentString); - } - annotations.forEach(annotationSpec -> { - annotationSpec.append(builder, indent); - builder.append(' '); - }); - builder.append(accessModifier); - if (accessModifier != AccessModifier.PACKAGE_PRIVATE) { - builder.append(' '); - } - if (isStatic) { - builder.append("static "); - } - if (isFinal) { - builder.append("final "); - } - builder.append(type).append(' ').append(name); - if (value != null) { - builder.append(" = "); - value.append(builder, indent); - } - if (addSemicolon) { - builder.append(";\n"); - } - } -} diff --git a/src/main/java/overrun/marshal/gen2/AnnotationData.java b/src/main/java/overrun/marshal/gen2/AnnotationData.java deleted file mode 100644 index cc03624..0000000 --- a/src/main/java/overrun/marshal/gen2/AnnotationData.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import java.util.Map; - -/** - * Holds annotation - * - * @param type the type - * @param map the map - * @author squid233 - * @since 0.1.0 - */ -public record AnnotationData( - String type, - Map map -) { -} diff --git a/src/main/java/overrun/marshal/gen2/ArrayTypeData.java b/src/main/java/overrun/marshal/gen2/ArrayTypeData.java deleted file mode 100644 index c816852..0000000 --- a/src/main/java/overrun/marshal/gen2/ArrayTypeData.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -/** - * Holds array type name - * - * @param componentType the component type - * @author squid233 - * @since 0.1.0 - */ -public record ArrayTypeData(TypeData componentType) implements TypeData { -} diff --git a/src/main/java/overrun/marshal/gen2/BaseData.java b/src/main/java/overrun/marshal/gen2/BaseData.java deleted file mode 100644 index abd4d39..0000000 --- a/src/main/java/overrun/marshal/gen2/BaseData.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import overrun.marshal.gen.Skip; -import overrun.marshal.gen1.*; -import overrun.marshal.gen2.struct.StructData; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; -import javax.tools.JavaFileObject; -import java.io.IOException; -import java.io.PrintWriter; -import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import static overrun.marshal.internal.Util.isAExtendsB; - -/** - * Base data - * - * @author squid233 - * @since 0.1.0 - */ -public abstract sealed class BaseData permits StructData { - /** - * the processing environment - */ - protected final ProcessingEnvironment processingEnv; - /** - * imports - */ - protected final ImportData imports = new ImportData(new ArrayList<>()); - /** - * fieldDataList - */ - protected final List fieldDataList = new ArrayList<>(); - /** - * functionDataList - */ - protected final List functionDataList = new ArrayList<>(); - /** - * document - */ - protected String document = null; - /** - * nonFinal - */ - protected boolean nonFinal = false; - /** - * superclasses - */ - protected final List superclasses = new ArrayList<>(); - /** - * superinterfaces - */ - protected final List superinterfaces = new ArrayList<>(); - - /** - * Construct - * - * @param processingEnv the processing environment - */ - protected BaseData(ProcessingEnvironment processingEnv) { - this.processingEnv = processingEnv; - } - - /** - * {@return generateClassName} - * - * @param name name - * @param typeElement typeElement - */ - protected DeclaredTypeData generateClassName(String name, TypeElement typeElement) { - final DeclaredTypeData declaredTypeDataOfTypeElement = (DeclaredTypeData) TypeData.detectType(processingEnv, typeElement.asType()); - final String simpleClassName; - if (name.isBlank()) { - final String string = declaredTypeDataOfTypeElement.name(); - if (string.startsWith("C")) { - simpleClassName = string.substring(1); - } else { - processingEnv.getMessager().printError(""" - Class name must start with C if the name is not specified. Current name: %s - Possible solutions: - 1) Add C as a prefix e.g. C%1$s - 2) Specify the name in @Struct e.g. @Struct(name = "%1$s")""" - .formatted(string), typeElement); - return null; - } - } else { - simpleClassName = name; - } - return new DeclaredTypeData(declaredTypeDataOfTypeElement.packageName(), simpleClassName); - } - - /** - * Generates the file - * - * @param declaredTypeData the class name - * @throws IOException if an I/O error occurred - */ - protected void generate(DeclaredTypeData declaredTypeData) throws IOException { - final String packageName = declaredTypeData.packageName(); - final String simpleClassName = declaredTypeData.name(); - final SourceFile file = createSourceFile(packageName, simpleClassName); - - imports.imports() - .stream() - .filter(typeData -> !"java.lang".equals(typeData.packageName())) - .map(DeclaredTypeData::toString) - .sorted((s1, s2) -> { - final boolean s1Java = s1.startsWith("java."); - final boolean s2Java = s2.startsWith("java."); - if (s1Java && !s2Java) { - return 1; - } - if (!s1Java && s2Java) { - return -1; - } - return s1.compareTo(s2); - }) - .forEach(file::addImport); - - final JavaFileObject sourceFile = processingEnv.getFiler() - .createSourceFile(packageName + "." + simpleClassName); - try (PrintWriter out = new PrintWriter(sourceFile.openWriter())) { - file.write(out); - } - } - - private SourceFile createSourceFile(String packageName, String simpleClassName) { - final SourceFile file = new SourceFile(packageName); - file.addClass(simpleClassName, classSpec -> { - if (document != null) { - classSpec.setDocument(document); - } - classSpec.setFinal(!nonFinal); - superclasses.forEach(typeData -> classSpec.addSuperclass(imports.simplifyOrImport(typeData))); - superinterfaces.forEach(typeData -> classSpec.addSuperinterface(imports.simplifyOrImport(typeData))); - - addFields(classSpec); - addMethods(classSpec); - }); - return file; - } - - /** - * Generates the file - * - * @param typeElement the type element - * @throws IOException if an I/O error occurred - */ - public abstract void generate(TypeElement typeElement) throws IOException; - - /** - * addFields - * - * @param spec spec - */ - protected void addFields(ClassSpec spec) { - fieldDataList.forEach(fieldData -> { - final TypeData type = fieldData.type(); - final TypeUse value = fieldData.value(); - spec.addField(new VariableStatement( - imports.simplifyOrImport(type), - fieldData.name(), - value != null ? value.apply(imports) : null - ).setDocument(fieldData.document()) - .setAccessModifier(fieldData.accessModifier()) - .setStatic(fieldData.staticField()) - .setFinal(fieldData.finalField())); - }); - } - - /** - * addMethods - * - * @param spec spec - */ - protected void addMethods(ClassSpec spec) { - functionDataList.forEach(functionData -> - spec.addMethod(new MethodSpec(functionData.returnType().apply(imports), functionData.name()), methodSpec -> { - methodSpec.setDocument(functionData.document()); - addAnnotationSpec(functionData.annotations(), methodSpec); - methodSpec.setAccessModifier(functionData.accessModifier()); - methodSpec.setStatic(functionData.staticMethod()); - functionData.parameters().forEach(parameterData -> { - final ParameterSpec parameterSpec = new ParameterSpec(parameterData.type().apply(imports), parameterData.name()); - addAnnotationSpec(parameterData.annotations(), parameterSpec); - methodSpec.addParameter(parameterSpec); - }); - functionData.statements().forEach(typeUse -> - methodSpec.addStatement(typeUse.apply(imports))); - })); - } - - /** - * addAnnotationSpec - * - * @param list list - * @param annotatable annotatable - * @param escapeAtCharacter escapeAtCharacter - */ - protected static void addAnnotationSpec(List list, Annotatable annotatable, boolean escapeAtCharacter) { - list.forEach(annotationData -> { - final AnnotationSpec annotationSpec = new AnnotationSpec( - annotationData.type(), - escapeAtCharacter - ); - annotationData.map().forEach(annotationSpec::addArgument); - annotatable.addAnnotation(annotationSpec); - }); - } - - /** - * addAnnotationSpec - * - * @param list list - * @param annotatable annotatable - */ - protected static void addAnnotationSpec(List list, Annotatable annotatable) { - addAnnotationSpec(list, annotatable, false); - } - - /** - * {@return getDocComment} - * - * @param e e - */ - protected String getDocComment(Element e) { - return processingEnv.getElementUtils().getDocComment(e); - } - - /** - * {@return getConstExp} - * - * @param v v - */ - protected String getConstExp(Object v) { - return processingEnv.getElementUtils().getConstantExpression(v); - } - - /** - * {@return skipAnnotated} - * - * @param collection collection - * @param aClass aClass - * @param type - */ - protected static Stream skipAnnotated(Collection collection, Class aClass) { - return collection.stream().filter(t -> t.getAnnotation(aClass) == null); - } - - /** - * {@return skipAnnotated} - * - * @param collection collection - * @param type - */ - protected static Stream skipAnnotated(Collection collection) { - return skipAnnotated(collection, Skip.class); - } - - private Stream findElementAnnotations( - List annotationMirrors, - Class aClass - ) { - return annotationMirrors.stream() - .filter(mirror -> isAExtendsB(processingEnv, - mirror.getAnnotationType(), - aClass)); - } - - /** - * {@return getElementAnnotations} - * - * @param list list - * @param e e - */ - protected List getElementAnnotations(List> list, Element e) { - final var mirrors = e.getAnnotationMirrors(); - return list.stream() - .filter(aClass -> e.getAnnotation(aClass) != null) - .flatMap(aClass -> findElementAnnotations(mirrors, aClass) - .map(mirror -> new AnnotationData(imports.simplifyOrImport(processingEnv, mirror.getAnnotationType()), - mapAnnotationMirrorValues(mirror.getElementValues())))) - .toList(); - } - - private static Map mapAnnotationMirrorValues(Map map) { - return map.entrySet().stream() - .collect(Collectors.toUnmodifiableMap( - entry -> entry.getKey().getSimpleName().toString(), - entry -> entry.getValue().toString() - )); - } -} diff --git a/src/main/java/overrun/marshal/gen2/DeclaredTypeData.java b/src/main/java/overrun/marshal/gen2/DeclaredTypeData.java deleted file mode 100644 index 953df14..0000000 --- a/src/main/java/overrun/marshal/gen2/DeclaredTypeData.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -/** - * Holds declared type package and name - * - * @param packageName the package name - * @param name the name - * @author squid233 - * @since 0.1.0 - */ -public record DeclaredTypeData(String packageName, String name) implements TypeData { - @Override - public String toString() { - return packageName() + "." + name(); - } -} diff --git a/src/main/java/overrun/marshal/gen2/FieldData.java b/src/main/java/overrun/marshal/gen2/FieldData.java deleted file mode 100644 index cdae6d9..0000000 --- a/src/main/java/overrun/marshal/gen2/FieldData.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import overrun.marshal.gen.AccessModifier; - -import java.util.List; - -/** - * Holds field - * - * @param document the document - * @param annotations the annotations - * @param accessModifier the access modifier - * @param staticField the static field - * @param finalField the final field - * @param type the type - * @param name the name - * @param value the value - * @author squid233 - * @since 0.1.0 - */ -public record FieldData( - String document, - List annotations, - AccessModifier accessModifier, - boolean staticField, - boolean finalField, - TypeData type, - String name, - TypeUse value -) { -} diff --git a/src/main/java/overrun/marshal/gen2/FunctionData.java b/src/main/java/overrun/marshal/gen2/FunctionData.java deleted file mode 100644 index 74e23ca..0000000 --- a/src/main/java/overrun/marshal/gen2/FunctionData.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import overrun.marshal.gen.AccessModifier; - -import java.util.List; - -/** - * Holds downcall function - * - * @param document the document - * @param annotations the annotations - * @param accessModifier the access modifier - * @param staticMethod static method - * @param returnType the return type - * @param name the name - * @param parameters the parameters - * @param statements the statement - * @author squid233 - * @since 0.1.0 - */ -public record FunctionData( - String document, - List annotations, - AccessModifier accessModifier, - boolean staticMethod, - TypeUse returnType, - String name, - List parameters, - List statements -) { -} diff --git a/src/main/java/overrun/marshal/gen2/ImportData.java b/src/main/java/overrun/marshal/gen2/ImportData.java deleted file mode 100644 index 774a4b9..0000000 --- a/src/main/java/overrun/marshal/gen2/ImportData.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.type.TypeMirror; -import java.util.List; - -/** - * Holds imports - * - * @param imports the imports - * @author squid233 - * @since 0.1.0 - */ -public record ImportData(List imports) { - private boolean isImported(TypeData typeData) { - return typeData instanceof DeclaredTypeData declaredTypeData && - imports.stream() - .map(DeclaredTypeData::name) - .anyMatch(name -> declaredTypeData.name().equals(name)); - } - - private boolean addImport(TypeData typeData) { - return switch (typeData) { - case ArrayTypeData arrayTypeData -> addImport(arrayTypeData.componentType()); - case DeclaredTypeData declaredTypeData when !isImported(typeData) -> imports().add(declaredTypeData); - default -> false; - }; - } - - /** - * Simplifies type name or import - * - * @param typeData the type data - * @return the name - */ - public String simplifyOrImport(TypeData typeData) { - return switch (typeData) { - case ArrayTypeData arrayTypeData -> simplifyOrImport(arrayTypeData.componentType()) + "[]"; - case DeclaredTypeData declaredTypeData when (isImported(typeData) || addImport(typeData)) -> - declaredTypeData.name(); - default -> typeData.toString(); - }; - } - - /** - * Simplifies type name or import - * - * @param aClass the class - * @return the name - */ - public String simplifyOrImport(Class aClass) { - return simplifyOrImport(TypeData.fromClass(aClass)); - } - - /** - * Simplifies type name or import - * - * @param env the processing environment - * @param typeMirror the type mirror - * @return the name - */ - public String simplifyOrImport(ProcessingEnvironment env, TypeMirror typeMirror) { - return simplifyOrImport(TypeData.detectType(env, typeMirror)); - } -} diff --git a/src/main/java/overrun/marshal/gen2/MethodHandleData.java b/src/main/java/overrun/marshal/gen2/MethodHandleData.java deleted file mode 100644 index 5b366fc..0000000 --- a/src/main/java/overrun/marshal/gen2/MethodHandleData.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import overrun.marshal.gen.AccessModifier; - -import javax.lang.model.element.ExecutableElement; -import java.util.List; -import java.util.Optional; - -/** - * Holds method handle - * - * @param executableElement the executable element - * @param accessModifier the access modifier - * @param name the name - * @param returnType the return type - * @param parameterTypes the parameter types - * @param optional optional - * @author squid233 - * @since 0.1.0 - */ -public record MethodHandleData( - ExecutableElement executableElement, - AccessModifier accessModifier, - String name, - Optional returnType, - List parameterTypes, - boolean optional -) { -} diff --git a/src/main/java/overrun/marshal/gen2/ParameterData.java b/src/main/java/overrun/marshal/gen2/ParameterData.java deleted file mode 100644 index 7c1944d..0000000 --- a/src/main/java/overrun/marshal/gen2/ParameterData.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import java.util.List; - -/** - * Holds parameter - * - * @param annotations the annotations - * @param type the type - * @param name the name - * @author squid233 - * @since 0.1.0 - */ -public record ParameterData( - List annotations, - TypeUse type, - String name -) { -} diff --git a/src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java b/src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java deleted file mode 100644 index bba70f9..0000000 --- a/src/main/java/overrun/marshal/gen2/PrimitiveTypeData.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -/** - * Holds primitive type name - * - * @param name the name - * @author squid233 - * @since 0.1.0 - */ -public record PrimitiveTypeData(String name) implements TypeData { - @Override - public String toString() { - return name(); - } -} diff --git a/src/main/java/overrun/marshal/gen2/TypeData.java b/src/main/java/overrun/marshal/gen2/TypeData.java deleted file mode 100644 index 7bff73d..0000000 --- a/src/main/java/overrun/marshal/gen2/TypeData.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -/** - * Holds type name - * - * @author squid233 - * @since 0.1.0 - */ -public sealed interface TypeData permits ArrayTypeData, DeclaredTypeData, PrimitiveTypeData, VoidTypeData { - /** - * Detects type - * - * @param env the processing environment - * @param type the type - * @return the type data - */ - static TypeData detectType(ProcessingEnvironment env, TypeMirror type) { - final TypeKind typeKind = type.getKind(); - if (typeKind.isPrimitive()) { - return new PrimitiveTypeData(type.toString()); - } - if (typeKind == TypeKind.ARRAY && - type instanceof ArrayType arrayType) { - return new ArrayTypeData(detectType(env, arrayType.getComponentType())); - } - if (typeKind == TypeKind.DECLARED && - env.getTypeUtils().asElement(type) instanceof TypeElement typeElement) { - final String qualifiedName = typeElement.getQualifiedName().toString(); - final String simpleName = typeElement.getSimpleName().toString(); - return new DeclaredTypeData(qualifiedName.substring(0, qualifiedName.lastIndexOf(simpleName) - 1), simpleName); - } - if (typeKind == TypeKind.VOID) { - return VoidTypeData.INSTANCE; - } - throw new IllegalArgumentException("Unknown type: " + type); - } - - /** - * Create type data from class - * - * @param aClass the class - * @return the type data - */ - static TypeData fromClass(Class aClass) { - if (aClass.isPrimitive()) { - return new PrimitiveTypeData(aClass.getCanonicalName()); - } - if (aClass.isArray()) { - return new ArrayTypeData(fromClass(aClass.arrayType())); - } - if (aClass.isMemberClass()) { - final TypeData typeData = fromClass(aClass.getEnclosingClass()); - if (typeData instanceof DeclaredTypeData(String packageName, String name)) { - return new DeclaredTypeData(packageName, name + "." + aClass.getSimpleName()); - } - } - return new DeclaredTypeData(aClass.getPackageName(), aClass.getSimpleName()); - } - - @Override - String toString(); -} diff --git a/src/main/java/overrun/marshal/gen2/TypeUse.java b/src/main/java/overrun/marshal/gen2/TypeUse.java deleted file mode 100644 index 19787ee..0000000 --- a/src/main/java/overrun/marshal/gen2/TypeUse.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -import overrun.marshal.CEnum; -import overrun.marshal.gen.struct.StructRef; -import overrun.marshal.gen1.Spec; -import overrun.marshal.internal.Util; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.foreign.ValueLayout; -import java.util.Optional; -import java.util.function.Function; - -/** - * Holds spec with type data - * - * @author squid233 - * @since 0.1.0 - */ -@FunctionalInterface -public interface TypeUse { - private static Optional valueLayout(TypeKind typeKind) { - return switch (typeKind) { - case BOOLEAN -> valueLayout(boolean.class); - case CHAR -> valueLayout(char.class); - case BYTE -> valueLayout(byte.class); - case SHORT -> valueLayout(short.class); - case INT -> valueLayout(int.class); - case LONG -> valueLayout(long.class); - case FLOAT -> valueLayout(float.class); - case DOUBLE -> valueLayout(double.class); - case ARRAY, DECLARED -> valueLayout(MemorySegment.class); - default -> Optional.empty(); - }; - } - - private static Optional valueLayout(String layout) { - if (layout == null) { - return Optional.empty(); - } - return Optional.of(importData -> - Spec.accessSpec(importData.simplifyOrImport(ValueLayout.class), layout)); - } - - /** - * Gets the value layout - * - * @param env the processing environment - * @param typeMirror the type mirror - * @return the value layout - */ - static Optional valueLayout(ProcessingEnvironment env, TypeMirror typeMirror) { - if (Util.isAExtendsB(env, typeMirror, SegmentAllocator.class)) { - return Optional.empty(); - } - if (Util.isAExtendsB(env, typeMirror, CEnum.class)) { - return valueLayout(int.class); - } - return valueLayout(typeMirror.getKind()); - } - - /** - * Gets the value layout - * - * @param env the processing environment - * @param element the element - * @return the value layout - */ - static Optional valueLayout(ProcessingEnvironment env, Element element) { - if (element.getAnnotation(StructRef.class) != null) { - return valueLayout(MemorySegment.class); - } - return valueLayout(env, element.asType()); - } - - /** - * Gets the value layout - * - * @param carrier the carrier - * @return the value layout - */ - static Optional valueLayout(Class carrier) { - if (carrier == boolean.class) return valueLayout("JAVA_BOOLEAN"); - if (carrier == char.class) return valueLayout("JAVA_CHAR"); - if (carrier == byte.class) return valueLayout("JAVA_BYTE"); - if (carrier == short.class) return valueLayout("JAVA_SHORT"); - if (carrier == int.class) return valueLayout("JAVA_INT"); - if (carrier == long.class) return valueLayout("JAVA_LONG"); - if (carrier == float.class) return valueLayout("JAVA_FLOAT"); - if (carrier == double.class) return valueLayout("JAVA_DOUBLE"); - if (carrier == MemorySegment.class) return valueLayout("ADDRESS"); - return Optional.empty(); - } - - /** - * Converts to the downcall type - * - * @param env the processing environment - * @param typeMirror the type mirror - * @return the downcall type - */ - static Optional toDowncallType(ProcessingEnvironment env, TypeMirror typeMirror) { - final TypeKind typeKind = typeMirror.getKind(); - if (typeKind.isPrimitive()) { - return Optional.of(literal(typeMirror.toString())); - } - return switch (typeKind) { - case ARRAY, DECLARED -> { - if (typeKind == TypeKind.DECLARED) { - if (Util.isAExtendsB(env, typeMirror, SegmentAllocator.class)) { - yield Optional.of(of(env, typeMirror)); - } - if (Util.isAExtendsB(env, typeMirror, CEnum.class)) { - yield Optional.of(literal(int.class)); - } - } - yield Optional.of(of(MemorySegment.class)); - } - default -> Optional.empty(); - }; - } - - /** - * Converts to the downcall type - * - * @param env the processing environment - * @param element the element - * @param function the function - * @param the element type - * @return the downcall type - */ - static Optional toDowncallType(ProcessingEnvironment env, T element, Function function) { - final StructRef structRef = element.getAnnotation(StructRef.class); - if (structRef != null) { - return Optional.of(of(MemorySegment.class)); - } - return toDowncallType(env, function.apply(element)); - } - - /** - * Converts to the processed type - * - * @param env the processing environment - * @param typeMirror the type mirror - * @return the processed type - */ - static Optional toProcessedType(ProcessingEnvironment env, TypeMirror typeMirror) { - final TypeKind typeKind = typeMirror.getKind(); - if (typeKind.isPrimitive()) { - return Optional.of(literal(typeMirror.toString())); - } - return switch (typeKind) { - case ARRAY, DECLARED -> Optional.of(of(env, typeMirror)); - default -> Optional.empty(); - }; - } - - /** - * Converts to the processed type - * - * @param env the processing environment - * @param element the element - * @param function the function - * @param the element type - * @return the processed type - */ - static Optional toProcessedType(ProcessingEnvironment env, T element, Function function) { - final StructRef structRef = element.getAnnotation(StructRef.class); - if (structRef != null) { - return Optional.of(literal(structRef.value())); - } - return toProcessedType(env, function.apply(element)); - } - - /** - * Converts to the processed type - * - * @param env the processing environment - * @param element the element - * @return the processed type - */ - static Optional toProcessedType(ProcessingEnvironment env, Element element) { - return toProcessedType(env, element, Element::asType); - } - - /** - * {@return import type use} - * - * @param aClass class - */ - static TypeUse of(Class aClass) { - return literal(importData -> importData.simplifyOrImport(aClass)); - } - - /** - * {@return import type use} - * - * @param env env - * @param typeMirror typeMirror - */ - static TypeUse of(ProcessingEnvironment env, TypeMirror typeMirror) { - return literal(importData -> importData.simplifyOrImport(env, typeMirror)); - } - - /** - * {@return literal type use} - * - * @param s string - */ - static TypeUse literal(String s) { - return _ -> Spec.literal(s); - } - - /** - * {@return literal type use} - * - * @param aClass class - */ - static TypeUse literal(Class aClass) { - return literal(aClass.getCanonicalName()); - } - - /** - * {@return literal type use} - * - * @param function function - */ - static TypeUse literal(Function function) { - return importData -> Spec.literal(function.apply(importData)); - } - - /** - * Applies the imports - * - * @param importData the import data - * @return the spec - */ - Spec apply(ImportData importData); -} diff --git a/src/main/java/overrun/marshal/gen2/VoidTypeData.java b/src/main/java/overrun/marshal/gen2/VoidTypeData.java deleted file mode 100644 index 8c7bdde..0000000 --- a/src/main/java/overrun/marshal/gen2/VoidTypeData.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2; - -/** - * Holds void - * - * @author squid233 - * @since 0.1.0 - */ -public record VoidTypeData() implements TypeData { - static final VoidTypeData INSTANCE = new VoidTypeData(); - - @Override - public String toString() { - return "void"; - } -} diff --git a/src/main/java/overrun/marshal/gen2/struct/MemberData.java b/src/main/java/overrun/marshal/gen2/struct/MemberData.java deleted file mode 100644 index 4c9265a..0000000 --- a/src/main/java/overrun/marshal/gen2/struct/MemberData.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2.struct; - -/** - * Holds member - * - * @param pathElementName the pathElementName - * @param varHandleName the varHandleName - * @author squid233 - * @since 0.1.0 - */ -public record MemberData( - String pathElementName, - String varHandleName -) { -} diff --git a/src/main/java/overrun/marshal/gen2/struct/StructData.java b/src/main/java/overrun/marshal/gen2/struct/StructData.java deleted file mode 100644 index f01f6e1..0000000 --- a/src/main/java/overrun/marshal/gen2/struct/StructData.java +++ /dev/null @@ -1,486 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.gen2.struct; - -import overrun.marshal.gen.AccessModifier; -import overrun.marshal.gen.Sized; -import overrun.marshal.gen.SizedSeg; -import overrun.marshal.gen.StrCharset; -import overrun.marshal.gen.struct.Const; -import overrun.marshal.gen.struct.Padding; -import overrun.marshal.gen.struct.Struct; -import overrun.marshal.gen.struct.StructRef; -import overrun.marshal.gen1.ConstructSpec; -import overrun.marshal.gen1.InvokeSpec; -import overrun.marshal.gen1.Spec; -import overrun.marshal.gen1.VariableStatement; -import overrun.marshal.gen2.*; -import overrun.marshal.struct.IStruct; - -import javax.annotation.processing.ProcessingEnvironment; -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 java.io.IOException; -import java.lang.annotation.Annotation; -import java.lang.foreign.*; -import java.lang.invoke.VarHandle; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; - -import static overrun.marshal.internal.Util.getArrayComponentType; -import static overrun.marshal.internal.Util.tryInsertUnderline; - -/** - * Holds struct fields and methods - * - * @author squid233 - * @since 0.1.0 - */ -public final class StructData extends BaseData { - private static final String LAYOUT_NAME = "LAYOUT"; - private static final String PARAMETER_ALLOCATOR_NAME = "allocator"; - private static final String PARAMETER_COUNT_NAME = "count"; - private static final String PARAMETER_INDEX_NAME = "index"; - private static final String PARAMETER_SEGMENT_NAME = "segment"; - private static final List> MEMBER_ANNOTATION = List.of( - SizedSeg.class, - Sized.class, - StrCharset.class - ); - private final Predicate insertPredicate = s -> fieldDataList.stream().anyMatch(fieldData -> s.equals(fieldData.name())); - private String peSequenceElementName; - private String segmentName; - private String layoutName; - - /** - * Construct - * - * @param processingEnv the processing environment - */ - public StructData(ProcessingEnvironment processingEnv) { - super(processingEnv); - } - - private void processStructType(TypeElement typeElement, String simpleClassName) { - final var enclosedElements = typeElement.getEnclosedElements(); - - final var fields = skipAnnotated(ElementFilter.fieldsIn(enclosedElements)).toList(); - final var ignorePadding = skipAnnotated(fields, Padding.class).toList(); - final Map memberPathElementNameMap = LinkedHashMap.newLinkedHashMap(ignorePadding.size()); - final Map memberVarHandleNameMap = LinkedHashMap.newLinkedHashMap(ignorePadding.size()); - final Map memberDataMap = LinkedHashMap.newLinkedHashMap(ignorePadding.size()); - - final Struct struct = typeElement.getAnnotation(Struct.class); - final Const structConst = typeElement.getAnnotation(Const.class); - - document = getDocComment(typeElement); - nonFinal = struct.nonFinal(); - superinterfaces.add(TypeData.fromClass(IStruct.class)); - - addLayout(simpleClassName, fields, ignorePadding, structConst); - addPathElements(ignorePadding, memberPathElementNameMap); - addVarHandles(ignorePadding, memberVarHandleNameMap); - addStructInfo(); - - memberPathElementNameMap.forEach((name, pathElementName) -> memberDataMap.put(name, - new MemberData(pathElementName, memberVarHandleNameMap.get(name)))); - - addConstructors(simpleClassName, memberDataMap); - addAllocators(simpleClassName); - addSlices(simpleClassName); - addGetters(ignorePadding, memberDataMap); - if (structConst == null) { - addSetters(); - } - addStructImpl(); - } - - private void addLayout(String simpleClassName, List fields, List ignorePadding, Const structConst) { - // layout document - final StringBuilder sb = new StringBuilder(1024); - sb.append(""" - The layout of this struct. -

-                 struct \
-                """)
-            .append(simpleClassName)
-            .append(" {\n");
-        ignorePadding.forEach(element -> {
-            final String typeNameString;
-            final StructRef structRef = element.getAnnotation(StructRef.class);
-            if (structRef != null) {
-                typeNameString = "{@link " + structRef.value() + "}";
-            } else {
-                final StringBuilder typeName = new StringBuilder(8);
-                appendMemberType(typeName, TypeData.detectType(processingEnv, element.asType()));
-                typeNameString = typeName.toString();
-            }
-            final VariableStatement variableStatement = new VariableStatement(
-                typeNameString,
-                element.getSimpleName().toString(),
-                null
-            ).setAccessModifier(AccessModifier.PACKAGE_PRIVATE)
-                .setFinal(structConst != null || element.getAnnotation(Const.class) != null);
-            addAnnotationSpec(getElementAnnotations(MEMBER_ANNOTATION, element), variableStatement, true);
-            variableStatement.append(sb, 5);
-        });
-        sb.append(" }
\n"); - // layout field - fieldDataList.add(new FieldData( - sb.toString(), - List.of(), - AccessModifier.PUBLIC, - true, - true, - TypeData.fromClass(StructLayout.class), - LAYOUT_NAME, - importData -> switch (0) { - default -> { - final InvokeSpec invokeSpec = new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "structLayout"); - fields.forEach(element -> { - final Padding padding = element.getAnnotation(Padding.class); - if (padding != null) { - invokeSpec.addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "paddingLayout") - .addArgument(getConstExp(padding.value()))); - } else { - final SizedSeg sizedSeg = element.getAnnotation(SizedSeg.class); - final Sized sized = element.getAnnotation(Sized.class); - final TypeMirror type = element.asType(); - - final InvokeSpec valueLayout; - if (element.getAnnotation(StructRef.class) != null) { - valueLayout = new InvokeSpec(TypeUse.valueLayout(MemorySegment.class) - .orElseThrow() - .apply(importData), "withName"); - } else { - valueLayout = new InvokeSpec(TypeUse.valueLayout(processingEnv, type) - .orElseThrow() - .apply(importData), "withName" - ); - } - valueLayout.addArgument(getConstExp(element.getSimpleName().toString())); - final Spec finalSpec; - if (sizedSeg != null) { - finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") - .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") - .addArgument(getConstExp(sizedSeg.value())) - .addArgument(TypeUse.valueLayout(byte.class).orElseThrow().apply(importData))); - } else if (sized != null) { - finalSpec = new InvokeSpec(valueLayout, "withTargetLayout") - .addArgument(new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") - .addArgument(getConstExp(sized.value())) - .addArgument(TypeUse.valueLayout(processingEnv, getArrayComponentType(type)).orElseThrow().apply(importData))); - } else { - finalSpec = valueLayout; - } - invokeSpec.addArgument(finalSpec); - } - }); - yield invokeSpec; - } - } - )); - } - - private void addPathElements(List ignorePadding, Map memberPathElementNameMap) { - ignorePadding.stream() - .map(element -> element.getSimpleName().toString()) - .forEach(s -> { - final String name = "PE_" + s; - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - true, - true, - TypeData.fromClass(MemoryLayout.PathElement.class), - name, - importData -> new InvokeSpec(importData.simplifyOrImport(MemoryLayout.PathElement.class), "groupElement") - .addArgument(getConstExp(s)) - )); - memberPathElementNameMap.put(s, name); - }); - peSequenceElementName = tryInsertUnderline("PE_SEQUENCE_ELEMENT", insertPredicate); - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - true, - true, - TypeData.fromClass(MemoryLayout.PathElement.class), - peSequenceElementName, - importData -> new InvokeSpec(importData.simplifyOrImport(MemoryLayout.PathElement.class), "sequenceElement") - )); - } - - private void addVarHandles(List ignorePadding, Map memberVarHandleNameMap) { - ignorePadding.forEach(element -> { - final String s = element.getSimpleName().toString(); - final StringBuilder vhDocument = new StringBuilder(" The var handle of {@code " + s + "}."); - final String docComment = getDocComment(element); - if (docComment != null) { - vhDocument.append("\n
\n") - .append(docComment) - .append("
"); - } - final String name = "vh_" + s; - fieldDataList.add(new FieldData( - vhDocument.toString(), - List.of(), - AccessModifier.PUBLIC, - false, - true, - TypeData.fromClass(VarHandle.class), - name, - null - )); - memberVarHandleNameMap.put(s, name); - }); - } - - private void addStructInfo() { - segmentName = tryInsertUnderline("segment", insertPredicate); - layoutName = tryInsertUnderline("layout", insertPredicate); - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - false, - true, - TypeData.fromClass(MemorySegment.class), - segmentName, - null - )); - fieldDataList.add(new FieldData( - null, - List.of(), - AccessModifier.PRIVATE, - false, - true, - TypeData.fromClass(SequenceLayout.class), - layoutName, - null - )); - } - - private void addConstructors(String simpleClassName, Map memberDataMap) { - final List statements = new ArrayList<>(memberDataMap.size() + 2); - - statements.add(_ -> Spec.assignStatement(Spec.accessSpec("this", segmentName), Spec.literal(PARAMETER_SEGMENT_NAME))); - statements.add(importData -> Spec.assignStatement(Spec.accessSpec("this", layoutName), - new InvokeSpec(importData.simplifyOrImport(MemoryLayout.class), "sequenceLayout") - .addArgument(PARAMETER_COUNT_NAME) - .addArgument(LAYOUT_NAME))); - memberDataMap.forEach((_, memberData) -> statements.add(_ -> - Spec.assignStatement(Spec.accessSpec("this", memberData.varHandleName()), - new InvokeSpec(Spec.accessSpec("this", layoutName), "varHandle") - .addArgument(peSequenceElementName) - .addArgument(memberData.pathElementName())))); - - functionDataList.add(new FunctionData( - """ - Creates {@code %s} with the given segment and element count. - - @param %s the segment - @param %s the element count\ - """.formatted(simpleClassName, PARAMETER_SEGMENT_NAME, PARAMETER_COUNT_NAME), - List.of(), - AccessModifier.PUBLIC, - false, - _ -> null, - simpleClassName, - List.of( - new ParameterData(List.of(), TypeUse.of(MemorySegment.class), PARAMETER_SEGMENT_NAME), - new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_COUNT_NAME) - ), - statements - )); - - functionDataList.add(new FunctionData( - """ - Creates {@code %s} with the given segment. The count is auto-inferred. - - @param %s the segment\ - """.formatted(simpleClassName, PARAMETER_SEGMENT_NAME), - List.of(), - AccessModifier.PUBLIC, - false, - _ -> null, - simpleClassName, - List.of(new ParameterData(List.of(), TypeUse.of(MemorySegment.class), PARAMETER_SEGMENT_NAME)), - List.of(importData -> Spec.statement(InvokeSpec.invokeThis() - .addArgument(PARAMETER_SEGMENT_NAME) - .addArgument(new InvokeSpec(importData.simplifyOrImport(IStruct.class), "inferCount") - .addArgument(PARAMETER_SEGMENT_NAME) - .addArgument(LAYOUT_NAME)))) - )); - } - - private void addAllocators(String simpleClassName) { - functionDataList.add(new FunctionData( - """ - Allocates {@code %s}. - - @param %s the allocator - @return the allocated {@code %1$s}\ - """.formatted(simpleClassName, PARAMETER_ALLOCATOR_NAME), - List.of(), - AccessModifier.PUBLIC, - true, - TypeUse.literal(simpleClassName), - "create", - List.of(new ParameterData(List.of(), TypeUse.of(SegmentAllocator.class), PARAMETER_ALLOCATOR_NAME)), - List.of(_ -> Spec.returnStatement(new ConstructSpec(simpleClassName) - .addArgument(new InvokeSpec(PARAMETER_ALLOCATOR_NAME, "allocate") - .addArgument(LAYOUT_NAME)) - .addArgument(Spec.literal(getConstExp(1L))))) - )); - functionDataList.add(new FunctionData( - """ - Allocates {@code %s} with the given count.. - - @param %s the allocator - @param %s the element count - @return the allocated {@code %1$s}\ - """.formatted(simpleClassName, PARAMETER_ALLOCATOR_NAME, PARAMETER_COUNT_NAME), - List.of(), - AccessModifier.PUBLIC, - true, - TypeUse.literal(simpleClassName), - "create", - List.of( - new ParameterData(List.of(), TypeUse.of(SegmentAllocator.class), PARAMETER_ALLOCATOR_NAME), - new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_COUNT_NAME) - ), - List.of(_ -> Spec.returnStatement(new ConstructSpec(simpleClassName) - .addArgument(new InvokeSpec(PARAMETER_ALLOCATOR_NAME, "allocate") - .addArgument(LAYOUT_NAME) - .addArgument(PARAMETER_COUNT_NAME)) - .addArgument(Spec.literal(PARAMETER_COUNT_NAME)))) - )); - } - - private void addSlices(String simpleClassName) { - functionDataList.add(new FunctionData( - """ - Returns a slice of this struct, at the given index. - - @param %s The new struct base offset - @param %s The new struct size - @return a slice of this struct\ - """ - .formatted(PARAMETER_INDEX_NAME, PARAMETER_COUNT_NAME), - List.of(), - AccessModifier.PUBLIC, - false, - TypeUse.literal(simpleClassName), - "get", - List.of( - new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_INDEX_NAME), - new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_COUNT_NAME) - ), - List.of(_ -> Spec.returnStatement(new ConstructSpec(simpleClassName) - .addArgument(new InvokeSpec(segmentName, "asSlice") - .addArgument(new InvokeSpec(LAYOUT_NAME, "scale") - .addArgument(getConstExp(0L)) - .addArgument(PARAMETER_INDEX_NAME)) - .addArgument(new InvokeSpec(LAYOUT_NAME, "scale") - .addArgument(getConstExp(0L)) - .addArgument(PARAMETER_COUNT_NAME)) - .addArgument(new InvokeSpec(LAYOUT_NAME, "byteAlignment"))) - .addArgument(Spec.literal(PARAMETER_COUNT_NAME)))) - )); - functionDataList.add(new FunctionData( - """ - Returns a slice of this struct. - - @param %s The new struct base offset - @return a slice of this struct\ - """ - .formatted(PARAMETER_INDEX_NAME), - List.of(), - AccessModifier.PUBLIC, - false, - TypeUse.literal(simpleClassName), - "get", - List.of( - new ParameterData(List.of(), TypeUse.literal(long.class), PARAMETER_INDEX_NAME) - ), - List.of(_ -> Spec.returnStatement(new InvokeSpec((Spec) null, "get") - .addArgument(PARAMETER_INDEX_NAME) - .addArgument(getConstExp(1L)))) - )); - } - - private void addGetters(List ignorePadding, Map memberDataMap) { - // indexed - } - - private void addSetters() { - } - - private void addStructImpl() { - addStructImpl(TypeUse.of(MemorySegment.class), "segment", Spec.literal(segmentName)); - addStructImpl(TypeUse.of(StructLayout.class), "layout", Spec.literal(LAYOUT_NAME)); - addStructImpl(TypeUse.literal(long.class), "elementCount", new InvokeSpec(layoutName, "elementCount")); - } - - private void addStructImpl(TypeUse returnType, String name, Spec returnValue) { - functionDataList.add(new FunctionData( - null, - List.of(new AnnotationData(imports.simplifyOrImport(Override.class), Map.of())), - AccessModifier.PUBLIC, - false, - returnType, - name, - List.of(), - List.of(_ -> Spec.returnStatement(returnValue)) - )); - } - - /** - * Generates the file - * - * @param typeElement the type element - * @throws IOException if an I/O error occurred - */ - @Override - public void generate(TypeElement typeElement) throws IOException { - final Struct struct = typeElement.getAnnotation(Struct.class); - final DeclaredTypeData generatedClassName = generateClassName(struct.name(), typeElement); - - processStructType(typeElement, generatedClassName.name()); - - generate(generatedClassName); - } - - private void appendMemberType(StringBuilder sb, TypeData typeData) { - switch (typeData) { - case ArrayTypeData arrayTypeData -> { - appendMemberType(sb, arrayTypeData.componentType()); - sb.append("[]"); - } - case DeclaredTypeData _ -> sb.append("{@link ").append(imports.simplifyOrImport(typeData)).append("}"); - default -> sb.append(imports.simplifyOrImport(typeData)); - } - } -} diff --git a/src/main/java/overrun/marshal/internal/Processor.java b/src/main/java/overrun/marshal/internal/Processor.java deleted file mode 100644 index d23cc4b..0000000 --- a/src/main/java/overrun/marshal/internal/Processor.java +++ /dev/null @@ -1,323 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.internal; - -import overrun.marshal.Upcall; -import overrun.marshal.gen.StrCharset; -import overrun.marshal.gen1.*; - -import javax.annotation.processing.AbstractProcessor; -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 - * - * @author squid233 - * @since 0.1.0 - */ -public abstract class Processor extends AbstractProcessor { - /** - * upcallTypeMirror - */ - protected TypeMirror upcallTypeMirror; - - /** - * constructor - */ - protected Processor() { - } - - @Override - public synchronized void init(ProcessingEnvironment processingEnv) { - super.init(processingEnv); - upcallTypeMirror = getTypeMirror(Upcall.class); - } - - /** - * Print error - * - * @param msg message - */ - protected void printError(Object msg) { - processingEnv.getMessager().printError(String.valueOf(msg)); - } - - /** - * Print stack trace - * - * @param t throwable - */ - protected void printStackTrace(Throwable t) { - try (PrintWriter writer = new PrintWriter(Writer.nullWriter()) { - private final StringBuffer sb = new StringBuffer(2048); - - @Override - public void println(String x) { - sb.append(x); - sb.append('\n'); - } - - @Override - public void println(Object x) { - sb.append(x); - sb.append('\n'); - } - - @Override - public void close() { - printError(sb); - } - }) { - t.printStackTrace(writer); - } - } - - /** - * Get document - * - * @param element element - * @return document - */ - protected String getDocument(Element element) { - return processingEnv.getElementUtils().getDocComment(element); - } - - /** - * Get const expression - * - * @param value value - * @return const expression - */ - protected String getConstExp(Object value) { - return processingEnv.getElementUtils().getConstantExpression(value); - } - - private TypeMirror getTypeMirror(Class aClass) { - return processingEnv.getElementUtils().getTypeElement(aClass.getCanonicalName()).asType(); - } - - private boolean isAOfB(TypeMirror a, TypeMirror b) { - return Util.isDeclared(a) && - processingEnv.getTypeUtils().isAssignable(a, b); - } - - /** - * isUpcall - * - * @param typeMirror typeMirror - * @return isUpcall - */ - protected boolean isUpcall(TypeMirror typeMirror) { - return isAOfB(typeMirror, upcallTypeMirror); - } - - /** - * Add an annotation - * - * @param annotatable annotatable - * @param annotation annotation - * @param tClass tClass - * @param function function - * @param annotation type - * @param value - */ - 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().isBlank() ? 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)); - } - }; - } - - /** - * 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 upcall wrapper method - * - * @param typeMirror the type - * @return the upcall wrapper method - */ - protected Optional findUpcallWrapperMethod(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(); - } - - /** - * specifiedMethodNotFound - * - * @param wrapType wrapType - * @param type type - * @param enclosingType enclosingType - * @param delimiter delimiter - * @param name name - * @param annotationName annotationName - * @return string - */ - protected static String specifiedMethodNotFound(String wrapType, Object type, Object enclosingType, Object delimiter, Object name, Object annotationName) { - return """ - Couldn't find any %s method in %s while %s%s%s required - Possible solution: Mark the %1$s method with @%s""" - .formatted(wrapType, type, enclosingType, delimiter, name, annotationName); - } - - /** - * wrapperNotFound - * - * @param type type - * @param enclosingType enclosingType - * @param delimiter delimiter - * @param name name - * @param annotationName annotationName - * @return string - */ - protected static String wrapperNotFound(Object type, Object enclosingType, Object delimiter, Object name, Object annotationName) { - return specifiedMethodNotFound("wrap", type, enclosingType, delimiter, name, annotationName); - } - - @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 deleted file mode 100644 index ca84105..0000000 --- a/src/main/java/overrun/marshal/internal/Util.java +++ /dev/null @@ -1,354 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.internal; - -import overrun.marshal.Upcall; -import overrun.marshal.gen.struct.StructRef; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.Element; -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.ArrayType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; -import javax.lang.model.util.ElementFilter; -import java.lang.annotation.Annotation; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.util.Optional; -import java.util.function.Predicate; - -/** - * Util - * - * @author squid233 - * @since 0.1.0 - */ -public final class Util { - 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); - } - - /** - * try insert prefix - * - * @param prefix the prefix - * @param name the name - * @param predicate the predicate - * @return the string - */ - public static String tryInsertPrefix(String prefix, String name, Predicate predicate) { - if (predicate.test(name)) { - return tryInsertPrefix(prefix, prefix + name, predicate); - } - return name; - } - - /** - * try insert underline - * - * @param name the name - * @param predicate the predicate - * @return the string - */ - public static String tryInsertUnderline(String name, Predicate predicate) { - return tryInsertPrefix("_", name, predicate); - } - - /** - * Invalid type - * - * @param typeMirror typeMirror - * @return invalidType - */ - public static IllegalStateException invalidType(TypeMirror typeMirror) { - return new IllegalStateException("Invalid type: " + typeMirror); - } - - /** - * Simplify class name - * - * @param rawClassName rawClassName - * @return name - */ - @Deprecated(since = "0.1.0") - public static String simplify(String rawClassName) { - if (isString(rawClassName)) { - return String.class.getSimpleName(); - } - if (isMemorySegment(rawClassName)) { - return MemorySegment.class.getSimpleName(); - } - return rawClassName; - } - - /** - * isDeclared - * - * @param typeMirror typeMirror - * @return isDeclared - */ - public static boolean isDeclared(TypeMirror typeMirror) { - return typeMirror.getKind() == TypeKind.DECLARED; - } - - /** - * isMemorySegment - * - * @param clazzName clazzName - * @return isMemorySegment - */ - @Deprecated(since = "0.1.0") - public static boolean isMemorySegment(String clazzName) { - return MemorySegment.class.getCanonicalName().equals(clazzName); - } - - /** - * isMemorySegment - * - * @param typeMirror typeMirror - * @return isMemorySegment - */ - @Deprecated(since = "0.1.0") - public static boolean isMemorySegment(TypeMirror typeMirror) { - return isSameClass(typeMirror, MemorySegment.class); - } - - /** - * isString - * - * @param clazzName clazzName - * @return isString - */ - @Deprecated(since = "0.1.0") - public static boolean isString(String clazzName) { - return String.class.getCanonicalName().equals(clazzName); - } - - /** - * isString - * - * @param typeMirror typeMirror - * @return isString - */ - @Deprecated(since = "0.1.0") - public static boolean isString(TypeMirror typeMirror) { - return isSameClass(typeMirror, String.class); - } - - /** - * isArray - * - * @param typeMirror typeMirror - * @return isArray - */ - @Deprecated(since = "0.1.0") - public static boolean isArray(TypeMirror typeMirror) { - return typeMirror.getKind() == TypeKind.ARRAY; - } - - /** - * isBooleanArray - * - * @param typeMirror typeMirror - * @return isBooleanArray - */ - public static boolean isBooleanArray(TypeMirror typeMirror) { - return isArray(typeMirror) && - getArrayComponentType(typeMirror).getKind() == TypeKind.BOOLEAN; - } - - /** - * isPrimitiveArray - * - * @param typeMirror typeMirror - * @return isPrimitiveArray - */ - public static boolean isPrimitiveArray(TypeMirror typeMirror) { - 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)); - } - - /** - * getArrayComponentType - * - * @param typeMirror typeMirror - * @return getArrayComponentType - */ - public static TypeMirror getArrayComponentType(TypeMirror typeMirror) { - return ((ArrayType) typeMirror).getComponentType(); - } - - /** - * insertUnderline - * - * @param builtinName builtinName - * @param nameString nameString - * @return insertUnderline - */ - @Deprecated(since = "0.1.0") - public static String insertUnderline(String builtinName, String nameString) { - return builtinName.equals(nameString) ? "_" + builtinName : builtinName; - } - - /** - * Finds annotated method - * - * @param typeElement the type element - * @param aClass the class - * @param predicate the filter - * @return the method - */ - public static Optional findAnnotatedMethod( - TypeElement typeElement, - Class aClass, - Predicate predicate - ) { - return ElementFilter.methodsIn(typeElement.getEnclosedElements()) - .stream() - .filter(executableElement -> executableElement.getAnnotation(aClass) != null) - .filter(predicate) - .findFirst(); - } - - /** - * Gets the type element from class name - * - * @param env the processing environment - * @param aClass the class - * @return the type element - */ - public static TypeElement getTypeElementFromClass(ProcessingEnvironment env, String aClass) { - return env.getElementUtils().getTypeElement(aClass); - } - - /** - * Gets the type element from class - * - * @param env the processing environment - * @param aClass the class - * @return the type element - */ - public static TypeElement getTypeElementFromClass(ProcessingEnvironment env, Class aClass) { - return getTypeElementFromClass(env, aClass.getCanonicalName()); - } - - /** - * {@return is A extends B} - * - * @param env the processing environment - * @param t1 A - * @param t2 B - */ - public static boolean isAExtendsB(ProcessingEnvironment env, TypeMirror t1, TypeMirror t2) { - return isDeclared(t1) && - isDeclared(t2) && - env.getTypeUtils().isAssignable(t1, t2); - } - - /** - * {@return is A extends B} - * - * @param env the processing environment - * @param t1 A - * @param t2 B - */ - public static boolean isAExtendsB(ProcessingEnvironment env, TypeMirror t1, Class t2) { - return isAExtendsB(env, t1, getTypeElementFromClass(env, t2).asType()); - } - - /** - * {@return is same class} - * - * @param typeMirror the type mirror - * @param aClass the class - */ - public static boolean isSameClass(TypeMirror typeMirror, Class aClass) { - if (((typeMirror.getKind().isPrimitive() && aClass.isPrimitive()) || - isDeclared(typeMirror)) && - aClass.getCanonicalName().equals(typeMirror.toString())) { - return true; - } - if (typeMirror.getKind() == TypeKind.ARRAY && typeMirror instanceof ArrayType arrayType && - aClass.isArray()) { - return isSameClass(arrayType.getComponentType(), aClass.getComponentType()); - } - return false; - } - - /** - * {@return shouldMarshal} - * - * @param env env - * @param element element - */ - public static boolean shouldMarshal(ProcessingEnvironment env, Element element) { - if (element.getAnnotation(StructRef.class) != null) { - return true; - } - final TypeMirror typeMirror = element.asType(); - final TypeKind typeKind = typeMirror.getKind(); - final boolean shouldNotMarshal = - typeKind.isPrimitive() || - (typeKind == TypeKind.DECLARED && - (isSameClass(typeMirror, MemorySegment.class) || - isAExtendsB(env, typeMirror, SegmentAllocator.class))); - return !shouldNotMarshal; - } - - /** - * {@return requireAllocator} - * - * @param env env - * @param typeMirror typeMirror - */ - public static boolean requireAllocator(ProcessingEnvironment env, TypeMirror typeMirror) { - return switch (typeMirror.getKind()) { - case ARRAY -> true; - case DECLARED -> isSameClass(typeMirror, String.class) || - isAExtendsB(env, typeMirror, Upcall.class); - default -> false; - }; - } -} diff --git a/src/main/java/overrun/marshal/package-info.java b/src/main/java/overrun/marshal/package-info.java index 784b10d..76933ea 100644 --- a/src/main/java/overrun/marshal/package-info.java +++ b/src/main/java/overrun/marshal/package-info.java @@ -20,7 +20,7 @@ * @author squid233 * @see overrun.marshal.Downcall * @see overrun.marshal.Upcall - * @see overrun.marshal.gen.struct + * @see overrun.marshal.struct.Struct * @since 0.1.0 */ package overrun.marshal; diff --git a/src/main/java/overrun/marshal/struct/IStruct.java b/src/main/java/overrun/marshal/struct/IStruct.java deleted file mode 100644 index 0ba9d41..0000000 --- a/src/main/java/overrun/marshal/struct/IStruct.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2023-2024 Overrun Organization - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - */ - -package overrun.marshal.struct; - -import overrun.marshal.Addressable; - -import java.lang.foreign.MemorySegment; -import java.lang.foreign.StructLayout; - -/** - * A struct provider. - * - * @author squid233 - * @since 0.1.0 - */ -public interface IStruct extends Addressable { - /** - * {@return the memory segment of this struct} - */ - @Override - MemorySegment segment(); - - /** - * {@return the layout of this struct} - */ - StructLayout layout(); - - /** - * {@return the element count of this struct} - */ - long elementCount(); - - /** - * Infers how many this struct is there in the given segment. - * - * @param segment the segment - * @param layout the struct layout - * @return the count - */ - static long inferCount(MemorySegment segment, StructLayout layout) { - return segment.byteSize() / layout.byteSize(); - } -} 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..d8072d9 --- /dev/null +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -0,0 +1,117 @@ +/* + * 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 overrun.marshal.Addressable; + +import java.lang.foreign.*; + +/** + * The presentation of C struct. + * + * @author squid233 + * @since 0.1.0 + */ +public class Struct implements Addressable { + private final MemorySegment segment; + private final StructLayout layout; + private final SequenceLayout sequenceLayout; + + /** + * Creates a struct with the given layout. + * + * @param segment the segment + * @param elementCount the element count + * @param layout the struct layout + */ + public Struct(MemorySegment segment, long elementCount, StructLayout layout) { + this.segment = segment; + this.layout = layout; + this.sequenceLayout = MemoryLayout.sequenceLayout(elementCount, layout); + } + + /** + * Allocates a struct with the given layout. + * + * @param allocator the allocator + * @param elementCount the element count + * @param layout the struct layout + */ + public Struct(SegmentAllocator allocator, long elementCount, StructLayout layout) { + this(allocator.allocate(layout, elementCount), elementCount, layout); + } + + /** + * Creates a struct with the given layout. + * + * @param segment the segment + * @param layout the struct layout + */ + public Struct(MemorySegment segment, StructLayout layout) { + this(segment, estimateCount(segment, layout), layout); + } + + /** + * Allocates a struct with the given layout. + * + * @param allocator the allocator + * @param layout the struct layout + */ + public Struct(SegmentAllocator allocator, StructLayout layout) { + this(allocator, 1L, layout); + } + + /** + * Estimates the struct count of the given segment. + * + * @param segment the segment + * @param layout the struct layout + * @return the count + */ + public static long estimateCount(MemorySegment segment, StructLayout layout) { + return segment.spliterator(layout).estimateSize(); + } + + /** + * {@return the segment of this struct} + */ + @Override + public MemorySegment segment() { + return segment; + } + + /** + * {@return the layout of this struct} + */ + public StructLayout layout() { + return layout; + } + + /** + * {@return the sequence layout of this struct buffer} + */ + public SequenceLayout sequenceLayout() { + return sequenceLayout; + } + + /** + * {@return the element count of this struct buffer} + */ + public long elementCount() { + return sequenceLayout().elementCount(); + } +} diff --git a/src/main/java/overrun/marshal/struct/StructHandle.java b/src/main/java/overrun/marshal/struct/StructHandle.java new file mode 100644 index 0000000..a555537 --- /dev/null +++ b/src/main/java/overrun/marshal/struct/StructHandle.java @@ -0,0 +1,785 @@ +/* + * 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 org.jetbrains.annotations.Nullable; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemoryLayout; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.function.BiFunction; +import java.util.function.Function; + +/** + * The struct handle that provides getter and setter of an element in a struct. + * + * @author squid233 + * @since 0.1.0 + */ +public class StructHandle implements StructHandleView { + /** + * The var handle where to access the struct. + */ + protected final VarHandle varHandle; + + /** + * Creates a struct handle with the given var handle. + * + * @param varHandle the var handle + */ + protected StructHandle(VarHandle varHandle) { + this.varHandle = varHandle; + } + + private static VarHandle ofValue(Struct struct, String name) { + return MethodHandles.insertCoordinates(struct.sequenceLayout().varHandle(MemoryLayout.PathElement.sequenceElement(), + MemoryLayout.PathElement.groupElement(name)), + 0, + struct.segment()); + } + + /** + * Creates a boolean struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Bool ofBoolean(Struct struct, String name) { + return new Bool(ofValue(struct, name)); + } + + /** + * Creates a char struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Char ofChar(Struct struct, String name) { + return new Char(ofValue(struct, name)); + } + + /** + * Creates a byte struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Byte ofByte(Struct struct, String name) { + return new Byte(ofValue(struct, name)); + } + + /** + * Creates a short struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Short ofShort(Struct struct, String name) { + return new Short(ofValue(struct, name)); + } + + /** + * Creates an int struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Int ofInt(Struct struct, String name) { + return new Int(ofValue(struct, name)); + } + + /** + * Creates a long struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Long ofLong(Struct struct, String name) { + return new Long(ofValue(struct, name)); + } + + /** + * Creates a float struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Float ofFloat(Struct struct, String name) { + return new Float(ofValue(struct, name)); + } + + /** + * Creates a double struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Double ofDouble(Struct struct, String name) { + return new Double(ofValue(struct, name)); + } + + /** + * Creates an address struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Address ofAddress(Struct struct, String name) { + return new Address(ofValue(struct, name)); + } + + /** + * Creates a string struct handle. + * + * @param struct the struct + * @param name the name + * @param charset the charset + * @return the struct handle + */ + public static Str ofString(Struct struct, String name, Charset charset) { + return new Str(ofValue(struct, name), charset); + } + + /** + * Creates a string struct handle. + * + * @param struct the struct + * @param name the name + * @return the struct handle + */ + public static Str ofString(Struct struct, String name) { + return ofString(struct, name, StandardCharsets.UTF_8); + } + + /** + * Creates an addressable struct handle. + * + * @param struct the struct + * @param name the name + * @param factory the factory + * @param the type of the addressable + * @return the struct handle + */ + public static Addressable ofAddressable(Struct struct, String name, Function factory) { + return new Addressable<>(ofValue(struct, name), factory); + } + + /** + * Creates an upcall struct handle. + * + * @param struct the struct + * @param name the name + * @param factory the factory + * @param the type of the upcall + * @return the struct handle + */ + public static Upcall ofUpcall(Struct struct, String name, BiFunction factory) { + return new Upcall<>(ofValue(struct, name), factory); + } + + /** + * boolean generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Bool extends StructHandle implements StructHandleView.Bool { + private Bool(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, boolean value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(boolean value) { + set(0L, value); + } + + @Override + public boolean get(long index) { + return (boolean) varHandle.get(0L, index); + } + + @Override + public boolean get() { + return get(0L); + } + } + + /** + * char generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Char extends StructHandle implements StructHandleView.Char { + private Char(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, char value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(char value) { + set(0L, value); + } + + @Override + public char get(long index) { + return (char) varHandle.get(0L, index); + } + + @Override + public char get() { + return get(0L); + } + } + + /** + * byte generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Byte extends StructHandle implements StructHandleView.Byte { + private Byte(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, byte value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(byte value) { + set(0L, value); + } + + @Override + public byte get(long index) { + return (byte) varHandle.get(0L, index); + } + + @Override + public byte get() { + return get(0L); + } + } + + /** + * short generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Short extends StructHandle implements StructHandleView.Short { + private Short(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, short value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(short value) { + set(0L, value); + } + + @Override + public short get(long index) { + return (short) varHandle.get(0L, index); + } + + @Override + public short get() { + return get(0L); + } + } + + /** + * int generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Int extends StructHandle implements StructHandleView.Int { + private Int(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, int value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(int value) { + set(0L, value); + } + + @Override + public int get(long index) { + return (int) varHandle.get(0L, index); + } + + @Override + public int get() { + return get(0L); + } + } + + /** + * long generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Long extends StructHandle implements StructHandleView.Long { + private Long(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, long value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(long value) { + set(0L, value); + } + + @Override + public long get(long index) { + return (long) varHandle.get(0L, index); + } + + @Override + public long get() { + return get(0L); + } + } + + /** + * float generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Float extends StructHandle implements StructHandleView.Float { + private Float(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, float value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(float value) { + set(0L, value); + } + + @Override + public float get(long index) { + return (float) varHandle.get(0L, index); + } + + @Override + public float get() { + return get(0L); + } + } + + /** + * double generic type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Double extends StructHandle implements StructHandleView.Double { + private Double(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, double value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(double value) { + set(0L, value); + } + + @Override + public double get(long index) { + return (double) varHandle.get(0L, index); + } + + @Override + public double get() { + return get(0L); + } + } + + /** + * memory segment type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Address extends StructHandle implements StructHandleView.Address { + private Address(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public void set(long index, MemorySegment value) { + varHandle.set(0L, index, value); + } + + /** + * Sets the value. + * + * @param value the value + */ + public void set(MemorySegment value) { + set(0L, value); + } + + @Override + public MemorySegment get(long index) { + return (MemorySegment) varHandle.get(0L, index); + } + + @Override + public MemorySegment get() { + return get(0L); + } + } + + /** + * string type + * + * @author squid233 + * @since 0.1.0 + */ + public static final class Str extends StructHandle implements StructHandleView.Str { + private final Charset charset; + + private Str(VarHandle varHandle, Charset charset) { + super(varHandle); + this.charset = charset; + } + + /** + * Sets the value at the given index. + * + * @param allocator the allocator + * @param index the index + * @param value the value + */ + public void set(SegmentAllocator allocator, long index, String value) { + varHandle.set(0L, index, allocator.allocateFrom(value, charset)); + } + + /** + * Sets the value. + * + * @param allocator the allocator + * @param value the value + */ + public void set(SegmentAllocator allocator, String value) { + set(allocator, 0L, value); + } + + @Override + public String get(long size, long index) { + return ((MemorySegment) varHandle.get(0L, index)).reinterpret(size).getString(0L, charset); + } + + @Override + public String get(long size) { + return get(size, 0L); + } + } + + /** + * generic type + * + * @param the type of the element + * @author squid233 + * @since 0.1.0 + */ + public static abstract class Type extends StructHandle implements StructHandleView.Type { + /** + * Creates a struct handle with the given var handle. + * + * @param varHandle the var handle + */ + protected Type(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param index the index + * @param value the value + */ + public abstract void set(long index, T value); + + /** + * Sets the value. + * + * @param value the value + */ + public abstract void set(T value); + } + + /** + * generic type with userdata + * + * @param the type of the element + * @param the type of the getter userdata + * @param the type of the setter userdata + * @author squid233 + * @since 0.1.0 + */ + public static abstract class TypeExtGS extends StructHandle implements StructHandleView.TypeExt { + /** + * Creates a struct handle with the given var handle. + * + * @param varHandle the var handle + */ + protected TypeExtGS(VarHandle varHandle) { + super(varHandle); + } + + /** + * Sets the value at the given index. + * + * @param userdata the userdata + * @param index the index + * @param value the value + */ + public abstract void set(S userdata, long index, T value); + + /** + * Sets the value. + * + * @param userdata the userdata + * @param value the value + */ + public abstract void set(S userdata, T value); + } + + /** + * generic type with userdata + * + * @param the type of the element + * @param the type of the userdata + * @author squid233 + * @since 0.1.0 + */ + public static abstract class TypeExt extends TypeExtGS { + /** + * Creates a struct handle with the given var handle. + * + * @param varHandle the var handle + */ + protected TypeExt(VarHandle varHandle) { + super(varHandle); + } + } + + /** + * addressable type + * + * @param the addressable type + * @author squid233 + * @since 0.1.0 + */ + public static final class Addressable extends Type { + private final Function factory; + + private Addressable(VarHandle varHandle, Function factory) { + super(varHandle); + this.factory = factory; + } + + @Override + public void set(long index, @Nullable T value) { + varHandle.set(0L, index, value != null ? value.segment() : MemorySegment.NULL); + } + + @Override + public void set(T value) { + set(0L, value); + } + + @Override + public T get(long index) { + if (factory == null) throw new UnsupportedOperationException(); + return factory.apply((MemorySegment) varHandle.get(0L, index)); + } + + @Override + public T get() { + return get(0L); + } + } + + /** + * upcall type + * + * @param the upcall type + * @author squid233 + * @since 0.1.0 + */ + public static final class Upcall extends TypeExt { + private final BiFunction factory; + + private Upcall(VarHandle varHandle, BiFunction factory) { + super(varHandle); + this.factory = factory; + } + + @Override + public void set(Arena userdata, long index, T value) { + varHandle.set(0L, index, value.stub(userdata)); + } + + @Override + public void set(Arena userdata, T value) { + set(userdata, 0L, value); + } + + @Override + public T get(Arena userdata, long index) { + if (factory == null) throw new UnsupportedOperationException(); + return factory.apply(userdata, (MemorySegment) varHandle.get(0L, index)); + } + + @Override + public T get(Arena userdata) { + return get(userdata, 0L); + } + } +} diff --git a/src/main/java/overrun/marshal/struct/StructHandleView.java b/src/main/java/overrun/marshal/struct/StructHandleView.java new file mode 100644 index 0000000..dd3cafd --- /dev/null +++ b/src/main/java/overrun/marshal/struct/StructHandleView.java @@ -0,0 +1,310 @@ +/* + * 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.foreign.MemorySegment; + +/** + * A view of a {@linkplain StructHandle struct handle}, which only provides getters. + * + * @author squid233 + * @since 0.1.0 + */ +public interface StructHandleView { + /** + * boolean generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Bool extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + boolean get(long index); + + /** + * Gets the value. + * + * @return the value + */ + boolean get(); + } + + /** + * char generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Char extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + char get(long index); + + /** + * Gets the value. + * + * @return the value + */ + char get(); + } + + /** + * byte generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Byte extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + byte get(long index); + + /** + * Gets the value. + * + * @return the value + */ + byte get(); + } + + /** + * short generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Short extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + short get(long index); + + /** + * Gets the value. + * + * @return the value + */ + short get(); + } + + /** + * int generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Int extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + int get(long index); + + /** + * Gets the value. + * + * @return the value + */ + int get(); + } + + /** + * long generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Long extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + long get(long index); + + /** + * Gets the value. + * + * @return the value + */ + long get(); + } + + /** + * float generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Float extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + float get(long index); + + /** + * Gets the value. + * + * @return the value + */ + float get(); + } + + /** + * double generic type + * + * @author squid233 + * @since 0.1.0 + */ + interface Double extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + double get(long index); + + /** + * Gets the value. + * + * @return the value + */ + double get(); + } + + /** + * memory segment type + * + * @author squid233 + * @since 0.1.0 + */ + interface Address extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + MemorySegment get(long index); + + /** + * Gets the value. + * + * @return the value + */ + MemorySegment get(); + } + + /** + * string type + * + * @author squid233 + * @since 0.1.0 + */ + interface Str extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param size the size of the string + * @param index the index + * @return the value + */ + String get(long size, long index); + + /** + * Gets the value. + * + * @param size the size of the string + * @return the value + */ + String get(long size); + } + + /** + * generic type + * + * @param the type of the element + * @author squid233 + * @since 0.1.0 + */ + interface Type extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param index the index + * @return the value + */ + T get(long index); + + /** + * Gets the value. + * + * @return the value + */ + T get(); + } + + /** + * generic type with userdata + * + * @param the type of the element + * @param the type of the userdata + * @author squid233 + * @since 0.1.0 + */ + interface TypeExt extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param userdata the userdata + * @param index the index + * @return the value + */ + T get(U userdata, long index); + + /** + * Gets the value. + * + * @param userdata the userdata + * @return the value + */ + T get(U userdata); + } +} diff --git a/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 53b9bc6..0000000 --- a/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -overrun.marshal.StructProcessor diff --git a/src/test/java/overrun/marshal/test/ComplexStruct.java b/src/test/java/overrun/marshal/test/ComplexStruct.java new file mode 100644 index 0000000..1d741a1 --- /dev/null +++ b/src/test/java/overrun/marshal/test/ComplexStruct.java @@ -0,0 +1,98 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import overrun.marshal.struct.Struct; +import overrun.marshal.struct.StructHandle; + +import java.lang.foreign.*; +import java.nio.charset.StandardCharsets; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class ComplexStruct extends Struct { + public static final StructLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.ADDRESS.withName("IntArray"), + ValueLayout.ADDRESS.withName("Upcall"), + ValueLayout.ADDRESS.withName("Addressable").withTargetLayout(Vector3.LAYOUT), + ValueLayout.ADDRESS.withName("UTF16Str"), + ValueLayout.ADDRESS.withName("Str"), + ValueLayout.ADDRESS.withName("Address"), + ValueLayout.JAVA_LONG.withName("Long"), + ValueLayout.JAVA_DOUBLE.withName("Double"), + ValueLayout.JAVA_INT.withName("Int"), + ValueLayout.JAVA_FLOAT.withName("Float"), + ValueLayout.JAVA_SHORT.withName("Short"), + ValueLayout.JAVA_CHAR.withName("Char"), + ValueLayout.JAVA_BYTE.withName("Byte"), + ValueLayout.JAVA_BOOLEAN.withName("Bool"), + MemoryLayout.paddingLayout(2L) + ); + public final StructHandle.Bool Bool = StructHandle.ofBoolean(this, "Bool"); + public final StructHandle.Char Char = StructHandle.ofChar(this, "Char"); + public final StructHandle.Byte Byte = StructHandle.ofByte(this, "Byte"); + public final StructHandle.Short Short = StructHandle.ofShort(this, "Short"); + public final StructHandle.Int Int = StructHandle.ofInt(this, "Int"); + public final StructHandle.Float Float = StructHandle.ofFloat(this, "Float"); + public final StructHandle.Long Long = StructHandle.ofLong(this, "Long"); + public final StructHandle.Double Double = StructHandle.ofDouble(this, "Double"); + public final StructHandle.Address Address = StructHandle.ofAddress(this, "Address"); + public final StructHandle.Str Str = StructHandle.ofString(this, "Str"); + public final StructHandle.Str UTF16Str = StructHandle.ofString(this, "UTF16Str", StandardCharsets.UTF_16); + public final StructHandle.Addressable Addressable = StructHandle.ofAddressable(this, "Addressable", Vector3::new); + public final StructHandle.Upcall Upcall = StructHandle.ofUpcall(this, "Upcall", SimpleUpcall::wrap); + + /** + * Creates a struct with the given layout. + * + * @param segment the segment + * @param elementCount the element count + */ + public ComplexStruct(MemorySegment segment, long elementCount) { + super(segment, elementCount, LAYOUT); + } + + /** + * Allocates a struct with the given layout. + * + * @param allocator the allocator + * @param elementCount the element count + */ + public ComplexStruct(SegmentAllocator allocator, long elementCount) { + super(allocator, elementCount, LAYOUT); + } + + /** + * Creates a struct with the given layout. + * + * @param segment the segment + */ + public ComplexStruct(MemorySegment segment) { + super(segment, LAYOUT); + } + + /** + * Allocates a struct with the given layout. + * + * @param allocator the allocator + */ + public ComplexStruct(SegmentAllocator allocator) { + super(allocator, LAYOUT); + } +} diff --git a/src/test/java/overrun/marshal/test/DowncallInheritTest.java b/src/test/java/overrun/marshal/test/DowncallInheritTest.java index 13acb9b..b4908fa 100644 --- a/src/test/java/overrun/marshal/test/DowncallInheritTest.java +++ b/src/test/java/overrun/marshal/test/DowncallInheritTest.java @@ -35,5 +35,9 @@ void testInheritDowncall() { int[] arr = {0}; d.get(arr); assertArrayEquals(new int[]{42}, arr); + + assertEquals(1, d.get1()); + assertEquals(1, d.get2()); + assertEquals(3, d.get3()); } } diff --git a/src/test/java/overrun/marshal/test/DowncallProvider.java b/src/test/java/overrun/marshal/test/DowncallProvider.java index c079a51..6196438 100644 --- a/src/test/java/overrun/marshal/test/DowncallProvider.java +++ b/src/test/java/overrun/marshal/test/DowncallProvider.java @@ -30,6 +30,7 @@ import static java.lang.foreign.ValueLayout.ADDRESS; import static java.lang.foreign.ValueLayout.JAVA_INT; +import static overrun.marshal.test.TestUtil.*; /** * Provides downcall handle @@ -38,8 +39,6 @@ * @since 0.1.0 */ public final class DowncallProvider { - public static final String TEST_STRING = "Hello world"; - public static final String TEST_UTF16_STRING = "Hello UTF-16 world"; private static final Linker LINKER = Linker.nativeLinker(); private static final Arena ARENA = Arena.ofAuto(); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); @@ -128,7 +127,7 @@ private static MemorySegment testReturnString() { } private static MemorySegment testReturnUTF16String() { - return ARENA.allocateFrom(new String(TEST_UTF16_STRING.getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16), StandardCharsets.UTF_16); + return ARENA.allocateFrom(utf16Str(TEST_UTF16_STRING), StandardCharsets.UTF_16); } private static int testReturnCEnum() { diff --git a/src/test/java/overrun/marshal/test/DowncallTest.java b/src/test/java/overrun/marshal/test/DowncallTest.java index 715fd53..a7e9d94 100644 --- a/src/test/java/overrun/marshal/test/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/DowncallTest.java @@ -17,8 +17,6 @@ package overrun.marshal.test; import org.junit.jupiter.api.*; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; import overrun.marshal.MemoryStack; import java.io.ByteArrayOutputStream; @@ -28,11 +26,9 @@ import java.lang.foreign.MemorySegment; import java.lang.foreign.SegmentAllocator; import java.lang.foreign.ValueLayout; -import java.nio.charset.StandardCharsets; import static org.junit.jupiter.api.Assertions.*; -import static overrun.marshal.test.DowncallProvider.TEST_STRING; -import static overrun.marshal.test.DowncallProvider.TEST_UTF16_STRING; +import static overrun.marshal.test.TestUtil.*; /** * Test downcall @@ -88,15 +84,16 @@ void testSkip() { assertEquals("testSkip", outputStream.toString()); } - @ParameterizedTest(name = "testDefault(testDefaultNull = [" + ParameterizedTest.INDEX_PLACEHOLDER + "] " + ParameterizedTest.ARGUMENTS_WITH_NAMES_PLACEHOLDER + ")") - @ValueSource(booleans = {false, true}) - void testDefault(boolean testDefaultNull) { - IDowncall.getInstance(testDefaultNull).testDefault(); - if (testDefaultNull) { - assertEquals("testDefault in interface", outputStream.toString()); - } else { - assertEquals("testDefault", outputStream.toString()); - } + @Test + void testDefaultInInterface() { + IDowncall.getInstance(true).testDefault(); + assertEquals("testDefault in interface", outputStream.toString()); + } + + @Test + void testDefault() { + IDowncall.getInstance(false).testDefault(); + assertEquals("testDefault", outputStream.toString()); } @Test @@ -113,7 +110,7 @@ void testString() { @Test void testUTF16String() { - d.testUTF16String(new String(TEST_UTF16_STRING.getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16)); + d.testUTF16String(utf16Str(TEST_UTF16_STRING)); assertEquals(TEST_UTF16_STRING, outputStream.toString()); } diff --git a/src/test/java/overrun/marshal/test/ID1.java b/src/test/java/overrun/marshal/test/ID1.java index 626c989..788c31b 100644 --- a/src/test/java/overrun/marshal/test/ID1.java +++ b/src/test/java/overrun/marshal/test/ID1.java @@ -16,6 +16,8 @@ package overrun.marshal.test; +import overrun.marshal.gen.Entrypoint; + /** * downcall interface 1 * @@ -24,4 +26,11 @@ */ public interface ID1 { int mul2(int i); + + int get1(); + + int get2(); + + @Entrypoint("get1") + int get3(); } diff --git a/src/test/java/overrun/marshal/test/ID3.java b/src/test/java/overrun/marshal/test/ID3.java index ccb1f40..b3f5c92 100644 --- a/src/test/java/overrun/marshal/test/ID3.java +++ b/src/test/java/overrun/marshal/test/ID3.java @@ -17,6 +17,7 @@ package overrun.marshal.test; import overrun.marshal.Downcall; +import overrun.marshal.gen.Entrypoint; import java.lang.foreign.*; import java.lang.invoke.MethodHandle; @@ -33,12 +34,14 @@ final class Provider { private static final Linker LINKER = Linker.nativeLinker(); private static final Arena ARENA = Arena.ofAuto(); private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - private static final MemorySegment s_mul2, s_get; + private static final MemorySegment s_mul2, s_get, s_get1, s_get3; static { try { s_mul2 = segment(LOOKUP.findStatic(Provider.class, "mul2_", MethodType.methodType(int.class, int.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_INT)); s_get = segment(LOOKUP.findStatic(Provider.class, "get", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)); + s_get1 = segment(LOOKUP.findStatic(Provider.class, "get1", MethodType.methodType(int.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT)); + s_get3 = segment(LOOKUP.findStatic(Provider.class, "get3", MethodType.methodType(int.class)), FunctionDescriptor.of(ValueLayout.JAVA_INT)); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } @@ -52,6 +55,14 @@ private static void get(MemorySegment arr) { arr.reinterpret(ValueLayout.JAVA_INT.byteSize()).setAtIndex(ValueLayout.JAVA_INT, 0, 42); } + private static int get1() { + return 1; + } + + private static int get3() { + return 3; + } + private static MemorySegment segment(MethodHandle handle, FunctionDescriptor fd) { return LINKER.upcallStub(handle, fd, ARENA); } @@ -60,10 +71,19 @@ static SymbolLookup load() { return name -> switch (name) { case "mul2" -> Optional.of(s_mul2); case "get" -> Optional.of(s_get); + case "get1" -> Optional.of(s_get1); + case "get3" -> Optional.of(s_get3); default -> Optional.empty(); }; } } ID3 INSTANCE = Downcall.load(Provider.load()); + + @Override + @Entrypoint("get1") + int get2(); + + @Override + int get3(); } diff --git a/src/test/java/overrun/marshal/test/StructTest.java b/src/test/java/overrun/marshal/test/StructTest.java new file mode 100644 index 0000000..6002a9c --- /dev/null +++ b/src/test/java/overrun/marshal/test/StructTest.java @@ -0,0 +1,132 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import org.junit.jupiter.api.Test; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.*; +import static overrun.marshal.test.TestUtil.*; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class StructTest { + @Test + void testSingleStruct() { + try (Arena arena = Arena.ofConfined()) { + final Vector3 vector3 = new Vector3(arena); + assertEquals(0, vector3.x.get(0L)); + assertEquals(0, vector3.y.get(0L)); + assertEquals(0, vector3.z.get(0L)); + assertEquals(0, vector3.x.get()); + assertEquals(0, vector3.y.get()); + assertEquals(0, vector3.z.get()); + assertDoesNotThrow(() -> { + vector3.x.set(1); + vector3.y.set(2); + }); + assertEquals(1, vector3.x.get(0L)); + assertEquals(2, vector3.y.get(0L)); + assertEquals(1, vector3.x.get()); + assertEquals(2, vector3.y.get()); + assertDoesNotThrow(() -> { + vector3.x.set(0L, 3); + vector3.y.set(0L, 4); + }); + assertEquals(3, vector3.x.get(0L)); + assertEquals(4, vector3.y.get(0L)); + assertEquals(3, vector3.x.get()); + assertEquals(4, vector3.y.get()); + } + } + + @Test + void testInitializedStruct() { + try (Arena arena = Arena.ofConfined()) { + final MemorySegment segment = arena.allocate(Vector3.LAYOUT); + segment.set(ValueLayout.JAVA_INT, 0L, 1); + segment.set(ValueLayout.JAVA_INT, 4L, 2); + segment.set(ValueLayout.JAVA_INT, 8L, 3); + final Vector3 vector3 = new Vector3(segment, 1L); + assertEquals(1, vector3.x.get()); + assertEquals(2, vector3.y.get()); + assertEquals(3, vector3.z.get()); + } + } + + @Test + void testMultipleStruct() { + try (Arena arena = Arena.ofConfined()) { + final Vector3 vector3 = new Vector3(arena, 2L); + vector3.x.set(0L, 1); + vector3.y.set(0L, 2); + vector3.x.set(1L, 3); + vector3.y.set(1L, 4); + assertEquals(1, vector3.x.get()); + assertEquals(2, vector3.y.get()); + assertEquals(3, vector3.x.get(1L)); + assertEquals(4, vector3.y.get(1L)); + } + } + + @Test + void testComplexStruct() { + try (Arena arena = Arena.ofConfined()) { + final Vector3 vector3 = new Vector3(arena); + vector3.x.set(11); + vector3.y.set(12); + + final ComplexStruct struct = new ComplexStruct(arena); + struct.Bool.set(true); + struct.Char.set('1'); + struct.Byte.set((byte) 1); + struct.Short.set((short) 2); + struct.Int.set(3); + struct.Long.set(4L); + struct.Float.set(5F); + struct.Double.set(6D); + struct.Address.set(MemorySegment.ofAddress(7L)); + struct.Str.set(arena, TEST_STRING); + struct.UTF16Str.set(arena, utf16Str(TEST_UTF16_STRING)); + struct.Addressable.set(vector3); + struct.Upcall.set(arena, i -> i * 2); + + assertTrue(struct.Bool.get()); + assertEquals('1', struct.Char.get()); + assertEquals((byte) 1, struct.Byte.get()); + assertEquals((short) 2, struct.Short.get()); + assertEquals(3, struct.Int.get()); + assertEquals(4L, struct.Long.get()); + assertEquals(5F, struct.Float.get()); + assertEquals(6D, struct.Double.get()); + assertEquals(MemorySegment.ofAddress(7L), struct.Address.get()); + assertEquals(TEST_STRING, struct.Str.get(TEST_STRING.getBytes(StandardCharsets.UTF_8).length + 1)); + assertEquals(TEST_UTF16_STRING, struct.UTF16Str.get(utf16Str(TEST_UTF16_STRING).getBytes(StandardCharsets.UTF_16).length + 2)); + final Vector3 getVector = struct.Addressable.get(); + assertEquals(11, getVector.x.get()); + assertEquals(12, getVector.y.get()); + assertEquals(0, getVector.z.get()); + assertEquals(84, struct.Upcall.get(arena).invoke(42)); + } + } +} diff --git a/src/test/java/overrun/marshal/test/UnmarshalTest.java b/src/test/java/overrun/marshal/test/UnmarshalTest.java new file mode 100644 index 0000000..6adbe2f --- /dev/null +++ b/src/test/java/overrun/marshal/test/UnmarshalTest.java @@ -0,0 +1,66 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import org.junit.jupiter.api.Test; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static overrun.marshal.Marshal.marshal; +import static overrun.marshal.Unmarshal.*; +import static overrun.marshal.test.TestUtil.*; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class UnmarshalTest { + @Test + void testString() { + try (Arena arena = Arena.ofConfined()) { + assertEquals(TEST_STRING, unmarshalAsString(arena.allocateFrom(TEST_STRING))); + assertEquals(TEST_UTF16_STRING, unmarshalAsString(arena.allocateFrom(utf16Str(TEST_UTF16_STRING), StandardCharsets.UTF_16), StandardCharsets.UTF_16)); + } + } + + @Test + void testPrimitiveArray() { + try (Arena arena = Arena.ofConfined()) { + assertArrayEquals(new boolean[]{false, true}, unmarshalAsBooleanArray(marshal(arena, new boolean[]{false, true}))); + assertArrayEquals(new char[]{'1', '2'}, unmarshalAsCharArray(marshal(arena, new char[]{'1', '2'}))); + assertArrayEquals(new byte[]{1, 2}, unmarshalAsByteArray(marshal(arena, new byte[]{1, 2}))); + assertArrayEquals(new short[]{3, 4}, unmarshalAsShortArray(marshal(arena, new short[]{3, 4}))); + assertArrayEquals(new int[]{5, 6}, unmarshalAsIntArray(marshal(arena, new int[]{5, 6}))); + assertArrayEquals(new long[]{7L, 8L}, unmarshalAsLongArray(marshal(arena, new long[]{7L, 8L}))); + assertArrayEquals(new float[]{9F, 10F}, unmarshalAsFloatArray(marshal(arena, new float[]{9F, 10F}))); + assertArrayEquals(new double[]{11D, 12D}, unmarshalAsDoubleArray(marshal(arena, new double[]{11D, 12D}))); + } + } + + @Test + void testObjectArray() { + try (Arena arena = Arena.ofConfined()) { + assertArrayEquals(new MemorySegment[]{MemorySegment.ofAddress(1L), MemorySegment.ofAddress(2L)}, unmarshalAsAddressArray(marshal(arena, new MemorySegment[]{MemorySegment.ofAddress(1L), MemorySegment.ofAddress(2L)}))); + assertArrayEquals(new String[]{"Hello", "world"}, unmarshalAsStringArray(marshal(arena, new String[]{"Hello", "world"}))); + assertArrayEquals(new String[]{"Hello", "UTF-16", "world"}, unmarshalAsStringArray(marshal(arena, new String[]{utf16Str("Hello"), utf16Str("UTF-16"), utf16Str("world")}, StandardCharsets.UTF_16), StandardCharsets.UTF_16)); + } + } +} diff --git a/src/test/java/overrun/marshal/test/Vector3.java b/src/test/java/overrun/marshal/test/Vector3.java new file mode 100644 index 0000000..7ec25d5 --- /dev/null +++ b/src/test/java/overrun/marshal/test/Vector3.java @@ -0,0 +1,79 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import overrun.marshal.struct.Struct; +import overrun.marshal.struct.StructHandle; +import overrun.marshal.struct.StructHandleView; + +import java.lang.foreign.*; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class Vector3 extends Struct { + /** + * The struct layout. + */ + public static final StructLayout LAYOUT = MemoryLayout.structLayout( + ValueLayout.JAVA_INT.withName("x"), + ValueLayout.JAVA_INT.withName("y"), + ValueLayout.JAVA_INT.withName("z") + ); + public final StructHandle.Int x = StructHandle.ofInt(this, "x"); + public final StructHandle.Int y = StructHandle.ofInt(this, "y"); + public final StructHandleView.Int z = StructHandle.ofInt(this, "z"); + + /** + * Creates a struct with the given layout. + * + * @param segment the segment + * @param elementCount the element count + */ + public Vector3(MemorySegment segment, long elementCount) { + super(segment, elementCount, LAYOUT); + } + + /** + * Allocates a struct with the given layout. + * + * @param allocator the allocator + * @param elementCount the element count + */ + public Vector3(SegmentAllocator allocator, long elementCount) { + super(allocator, elementCount, LAYOUT); + } + + /** + * Creates a struct with the given layout. + * + * @param segment the segment + */ + public Vector3(MemorySegment segment) { + super(segment, LAYOUT); + } + + /** + * Allocates a struct with the given layout. + * + * @param allocator the allocator + */ + public Vector3(SegmentAllocator allocator) { + super(allocator, LAYOUT); + } +} From 49a3d30a8130a15c6ba28cab30ccd54b6ee64bac Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Fri, 26 Jan 2024 16:04:09 +0800 Subject: [PATCH 25/28] Add missing TestUtil.java --- .../java/overrun/marshal/test/TestUtil.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/test/java/overrun/marshal/test/TestUtil.java diff --git a/src/test/java/overrun/marshal/test/TestUtil.java b/src/test/java/overrun/marshal/test/TestUtil.java new file mode 100644 index 0000000..393abdb --- /dev/null +++ b/src/test/java/overrun/marshal/test/TestUtil.java @@ -0,0 +1,32 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.test; + +import java.nio.charset.StandardCharsets; + +/** + * @author squid233 + * @since 0.1.0 + */ +public final class TestUtil { + public static final String TEST_STRING = "Hello world"; + public static final String TEST_UTF16_STRING = "Hello UTF-16 world"; + + public static String utf16Str(String utf8Str) { + return new String(utf8Str.getBytes(StandardCharsets.UTF_16), StandardCharsets.UTF_16); + } +} From 1f10813c050f31cbd19966695d2fbe3fff39a3d3 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Fri, 26 Jan 2024 17:42:23 +0800 Subject: [PATCH 26/28] Struct support array --- .../overrun/marshal/struct/StructHandle.java | 71 +++++++++++++++++-- .../marshal/struct/StructHandleView.java | 34 +++++++-- .../overrun/marshal/test/ComplexStruct.java | 3 + .../java/overrun/marshal/test/StructTest.java | 2 + 4 files changed, 102 insertions(+), 8 deletions(-) diff --git a/src/main/java/overrun/marshal/struct/StructHandle.java b/src/main/java/overrun/marshal/struct/StructHandle.java index a555537..620600a 100644 --- a/src/main/java/overrun/marshal/struct/StructHandle.java +++ b/src/main/java/overrun/marshal/struct/StructHandle.java @@ -179,6 +179,18 @@ public static Str ofString(Struct struct, String name) { return ofString(struct, name, StandardCharsets.UTF_8); } + /** + * Creates an array struct handle. + * + * @param struct the struct + * @param name the name + * @param the type of the array + * @return the struct handle + */ + public static Array ofArray(Struct struct, String name, BiFunction setterFactory, Function getterFactory) { + return new Array<>(ofValue(struct, name), setterFactory, getterFactory); + } + /** * Creates an addressable struct handle. * @@ -610,13 +622,64 @@ public void set(SegmentAllocator allocator, String value) { } @Override - public String get(long size, long index) { - return ((MemorySegment) varHandle.get(0L, index)).reinterpret(size).getString(0L, charset); + public String get(long byteSize, long index) { + return ((MemorySegment) varHandle.get(0L, index)).reinterpret(byteSize).getString(0L, charset); + } + + @Override + public String get(long byteSize) { + return get(byteSize, 0L); + } + } + + /** + * array type + * + * @param the type of the array + * @author squid233 + * @since 0.1.0 + */ + public static final class Array extends StructHandle implements StructHandleView.Array { + private final BiFunction setterFactory; + private final Function getterFactory; + + private Array(VarHandle varHandle, BiFunction setterFactory, Function getterFactory) { + super(varHandle); + this.setterFactory = setterFactory; + this.getterFactory = getterFactory; + } + + /** + * Sets the value at the given index. + * + * @param allocator the allocator + * @param index the index + * @param value the value + */ + public void set(SegmentAllocator allocator, long index, T value) { + if (setterFactory == null) throw new UnsupportedOperationException(); + varHandle.set(0L, index, setterFactory.apply(allocator, value)); + } + + /** + * Sets the value. + * + * @param allocator the allocator + * @param value the value + */ + public void set(SegmentAllocator allocator, T value) { + set(allocator, 0L, value); + } + + @Override + public T get(long byteSize, long index) { + if (getterFactory == null) throw new UnsupportedOperationException(); + return getterFactory.apply(((MemorySegment) varHandle.get(0L, index)).reinterpret(byteSize)); } @Override - public String get(long size) { - return get(size, 0L); + public T get(long byteSize) { + return get(byteSize, 0L); } } diff --git a/src/main/java/overrun/marshal/struct/StructHandleView.java b/src/main/java/overrun/marshal/struct/StructHandleView.java index dd3cafd..fa0dbba 100644 --- a/src/main/java/overrun/marshal/struct/StructHandleView.java +++ b/src/main/java/overrun/marshal/struct/StructHandleView.java @@ -242,19 +242,45 @@ interface Str extends StructHandleView { /** * Gets the value at the given index. * - * @param size the size of the string + * @param byteSize the byte size of the string * @param index the index * @return the value */ - String get(long size, long index); + String get(long byteSize, long index); /** * Gets the value. * - * @param size the size of the string + * @param byteSize the byte size of the string * @return the value */ - String get(long size); + String get(long byteSize); + } + + /** + * array type + * + * @param the type of the array + * @author squid233 + * @since 0.1.0 + */ + interface Array extends StructHandleView { + /** + * Gets the value at the given index. + * + * @param byteSize the byte size of the array + * @param index the index + * @return the value + */ + T get(long byteSize, long index); + + /** + * Gets the value. + * + * @param byteSize the byte size of the array + * @return the value + */ + T get(long byteSize); } /** diff --git a/src/test/java/overrun/marshal/test/ComplexStruct.java b/src/test/java/overrun/marshal/test/ComplexStruct.java index 1d741a1..ea536e9 100644 --- a/src/test/java/overrun/marshal/test/ComplexStruct.java +++ b/src/test/java/overrun/marshal/test/ComplexStruct.java @@ -16,6 +16,8 @@ package overrun.marshal.test; +import overrun.marshal.Marshal; +import overrun.marshal.Unmarshal; import overrun.marshal.struct.Struct; import overrun.marshal.struct.StructHandle; @@ -57,6 +59,7 @@ public final class ComplexStruct extends Struct { public final StructHandle.Str UTF16Str = StructHandle.ofString(this, "UTF16Str", StandardCharsets.UTF_16); public final StructHandle.Addressable Addressable = StructHandle.ofAddressable(this, "Addressable", Vector3::new); public final StructHandle.Upcall Upcall = StructHandle.ofUpcall(this, "Upcall", SimpleUpcall::wrap); + public final StructHandle.Array IntArray = StructHandle.ofArray(this, "IntArray", Marshal::marshal, Unmarshal::unmarshalAsIntArray); /** * Creates a struct with the given layout. diff --git a/src/test/java/overrun/marshal/test/StructTest.java b/src/test/java/overrun/marshal/test/StructTest.java index 6002a9c..dd2ace6 100644 --- a/src/test/java/overrun/marshal/test/StructTest.java +++ b/src/test/java/overrun/marshal/test/StructTest.java @@ -110,6 +110,7 @@ void testComplexStruct() { struct.UTF16Str.set(arena, utf16Str(TEST_UTF16_STRING)); struct.Addressable.set(vector3); struct.Upcall.set(arena, i -> i * 2); + struct.IntArray.set(arena, new int[]{8, 9}); assertTrue(struct.Bool.get()); assertEquals('1', struct.Char.get()); @@ -127,6 +128,7 @@ void testComplexStruct() { assertEquals(12, getVector.y.get()); assertEquals(0, getVector.z.get()); assertEquals(84, struct.Upcall.get(arena).invoke(42)); + assertArrayEquals(new int[]{8, 9}, struct.IntArray.get(ValueLayout.JAVA_INT.scale(0L, 2L))); } } } From b8f398006e57d9e53c1c0844db18405189b26274 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:38:59 +0800 Subject: [PATCH 27/28] Downcall support return struct by-value --- src/main/java/overrun/marshal/Downcall.java | 263 ++++++++++-------- src/main/java/overrun/marshal/gen/Sized.java | 2 +- .../java/overrun/marshal/gen/SizedSeg.java | 2 +- src/main/java/overrun/marshal/gen/Skip.java | 2 +- .../java/overrun/marshal/gen/StrCharset.java | 2 +- .../java/overrun/marshal/struct/Struct.java | 33 ++- .../marshal/test/DowncallProvider.java | 38 +++ .../overrun/marshal/test/DowncallTest.java | 40 +++ .../java/overrun/marshal/test/IDowncall.java | 14 + 9 files changed, 281 insertions(+), 115 deletions(-) diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index 2384965..4f64a4e 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -17,6 +17,7 @@ package overrun.marshal; import overrun.marshal.gen.*; +import overrun.marshal.struct.Struct; import java.lang.annotation.Annotation; import java.lang.classfile.ClassFile; @@ -25,16 +26,10 @@ import java.lang.classfile.TypeKind; import java.lang.constant.ClassDesc; import java.lang.constant.MethodTypeDesc; -import java.lang.foreign.Arena; -import java.lang.foreign.MemorySegment; -import java.lang.foreign.SegmentAllocator; -import java.lang.foreign.SymbolLookup; +import java.lang.foreign.*; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.Parameter; +import java.lang.reflect.*; import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -101,6 +96,7 @@ public final class Downcall { private static final ClassDesc CD_SegmentAllocator = ClassDesc.of("java.lang.foreign.SegmentAllocator"); private static final ClassDesc CD_SequenceLayout = ClassDesc.of("java.lang.foreign.SequenceLayout"); private static final ClassDesc CD_StandardCharsets = ClassDesc.of("java.nio.charset.StandardCharsets"); + private static final ClassDesc CD_StructLayout = ClassDesc.of("java.lang.foreign.StructLayout"); private static final ClassDesc CD_SymbolLookup = ClassDesc.of("java.lang.foreign.SymbolLookup"); private static final ClassDesc CD_Unmarshal = ClassDesc.of("overrun.marshal.Unmarshal"); private static final ClassDesc CD_Upcall = ClassDesc.of("overrun.marshal.Upcall"); @@ -174,15 +170,21 @@ private static boolean getCharset(CodeBuilder codeBuilder, AnnotatedElement elem return false; } - private static void tryAddLayout(CodeBuilder codeBuilder, Class aClass) { + private static String unmarshalMethod(Class aClass) { if (aClass.isArray()) { final Class componentType = aClass.getComponentType(); - if (componentType == String.class) { - convertToValueLayout(codeBuilder, MemorySegment.class); - } else if (componentType.isPrimitive()) { - convertToValueLayout(codeBuilder, componentType); - } + if (componentType == boolean.class) return "unmarshalAsBooleanArray"; + if (componentType == char.class) return "unmarshalAsCharArray"; + if (componentType == byte.class) return "unmarshalAsByteArray"; + if (componentType == short.class) return "unmarshalAsShortArray"; + if (componentType == int.class) return "unmarshalAsIntArray"; + if (componentType == long.class) return "unmarshalAsLongArray"; + if (componentType == float.class) return "unmarshalAsFloatArray"; + if (componentType == double.class) return "unmarshalAsDoubleArray"; + if (componentType == MemorySegment.class) return "unmarshalAsAddressArray"; + if (componentType == String.class) return "unmarshalAsStringArray"; } + return "unmarshal"; } private static String getMethodEntrypoint(Method method) { @@ -192,18 +194,6 @@ private static String getMethodEntrypoint(Method method) { entrypoint.value(); } - private static ClassDesc convertToValueLayoutCD(Class aClass) { - if (aClass == boolean.class) return CD_ValueLayout_OfBoolean; - if (aClass == char.class) return CD_ValueLayout_OfChar; - if (aClass == byte.class) return CD_ValueLayout_OfByte; - if (aClass == short.class) return CD_ValueLayout_OfShort; - if (aClass == int.class || CEnum.class.isAssignableFrom(aClass)) return CD_ValueLayout_OfInt; - if (aClass == long.class) return CD_ValueLayout_OfLong; - if (aClass == float.class) return CD_ValueLayout_OfFloat; - if (aClass == double.class) return CD_ValueLayout_OfDouble; - return CD_AddressLayout; - } - private static ClassDesc convertToDowncallCD(Class aClass) { if (aClass.isPrimitive()) return aClass.describeConstable().orElseThrow(); if (CEnum.class.isAssignableFrom(aClass)) return CD_int; @@ -220,11 +210,6 @@ private static ClassDesc convertToMarshalCD(Class aClass) { return CD_MemorySegment; } - private static boolean shouldStoreResult(Class aClass, List parameters) { - return Upcall.class.isAssignableFrom(aClass) || - parameters.stream().anyMatch(parameter -> parameter.getDeclaredAnnotation(Ref.class) != null); - } - private static boolean requireAllocator(Class aClass) { return !aClass.isPrimitive() && (aClass == String.class || @@ -352,9 +337,19 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { // methods methodDataMap.forEach((method, methodData) -> { + final var returnType = method.getReturnType(); + + // check method return type + if (Struct.class.isAssignableFrom(returnType) && Arrays.stream(returnType.getDeclaredConstructors()) + .noneMatch(constructor -> { + final Class[] types = constructor.getParameterTypes(); + return types.length == 1 && types[0] == MemorySegment.class; + })) { + throw new IllegalStateException(STR."The struct \{returnType} must contain a constructor that only accept one memory segment: \{methodData.exceptionString()}"); + } + final String methodName = method.getName(); final int modifiers = method.getModifiers(); - final var returnType = method.getReturnType(); final ClassDesc cd_returnType = ClassDesc.ofDescriptor(returnType.descriptorString()); final TypeKind returnTypeKind = TypeKind.from(cd_returnType).asLoadable(); final List parameters = methodData.parameters(); @@ -423,10 +418,6 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { final ClassDesc cd_returnTypeDowncall = convertToDowncallCD(returnType); final boolean returnVoid = returnType == void.class; - final boolean shouldStoreResult = - !returnVoid && - shouldStoreResult(returnType, parameters); - final int resultSlot; // ref for (int i = 0, size = parameters.size(); i < size; i++) { @@ -459,9 +450,6 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { } // invocation - if (!shouldStoreResult) { - tryAddLayout(blockCodeBuilder, returnType); - } blockCodeBuilder.aload(blockCodeBuilder.receiverSlot()) .getfield(cd_thisClass, handleName, CD_MethodHandle); for (int i = skipFirstParam ? 1 : 0; i < parameterSize; i++) { @@ -474,7 +462,8 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { } else { final int slot = blockCodeBuilder.parameterSlot(i); if (type.isPrimitive() || - type == MemorySegment.class) { + type == MemorySegment.class || + SegmentAllocator.class.isAssignableFrom(type)) { blockCodeBuilder.loadInstruction( TypeKind.fromDescriptor(type.descriptorString()).asLoadable(), slot @@ -533,62 +522,13 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { "invokeExact", MethodTypeDesc.of(cd_returnTypeDowncall, parameterCDList)); - if (shouldStoreResult) { + final int resultSlot; + if (returnVoid) { + resultSlot = -1; + } else { final TypeKind typeKind = TypeKind.from(cd_returnTypeDowncall); resultSlot = blockCodeBuilder.allocateLocal(typeKind); blockCodeBuilder.storeInstruction(typeKind, resultSlot); - - tryAddLayout(blockCodeBuilder, returnType); - blockCodeBuilder.loadInstruction(typeKind, resultSlot); - } else { - resultSlot = -1; - } - - // wrap return value - if (CEnum.class.isAssignableFrom(returnType)) { - final Method wrapper = findCEnumWrapper(returnType); - blockCodeBuilder.invokestatic(cd_returnType, - wrapper.getName(), - MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), CD_int), - wrapper.getDeclaringClass().isInterface()); - } else if (Upcall.class.isAssignableFrom(returnType)) { - final Method wrapper = findUpcallWrapper(returnType); - blockCodeBuilder.aload(resultSlot) - .ifThenElse(Opcode.IFNONNULL, - blockCodeBuilder1 -> blockCodeBuilder1.aload(allocatorSlot) - .aload(resultSlot) - .invokestatic(cd_returnType, - wrapper.getName(), - MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), - CD_Arena, - CD_MemorySegment), - wrapper.getDeclaringClass().isInterface()), - CodeBuilder::aconst_null); - } else if (returnType == String.class) { - final boolean hasCharset = getCharset(blockCodeBuilder, method); - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsString", - MethodTypeDesc.of(CD_String, - hasCharset ? - List.of(CD_MemorySegment, CD_Charset) : - List.of(CD_MemorySegment))); - } else if (returnType.isArray()) { - final Class componentType = returnType.getComponentType(); - if (componentType == String.class) { - final boolean hasCharset = getCharset(blockCodeBuilder, method); - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsStringArray", - MethodTypeDesc.of(CD_String.arrayType(), - hasCharset ? - List.of(CD_AddressLayout, CD_MemorySegment, CD_Charset) : - List.of(CD_AddressLayout, CD_MemorySegment))); - } else if (componentType.isPrimitive()) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshal", - MethodTypeDesc.of(cd_returnType, - convertToValueLayoutCD(componentType), - CD_MemorySegment)); - } } // copy ref result @@ -624,6 +564,69 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { } } + // wrap return value + final int unmarshalSlot; + if (returnVoid) { + unmarshalSlot = -1; + } else { + unmarshalSlot = blockCodeBuilder.allocateLocal(returnTypeKind); + blockCodeBuilder.loadInstruction(TypeKind.from(cd_returnTypeDowncall), resultSlot); + } + if (returnType == String.class) { + final boolean hasCharset = getCharset(blockCodeBuilder, method); + blockCodeBuilder.invokestatic(CD_Unmarshal, + "unmarshalAsString", + MethodTypeDesc.of(CD_String, + hasCharset ? + List.of(CD_MemorySegment, CD_Charset) : + List.of(CD_MemorySegment))); + } else if (Struct.class.isAssignableFrom(returnType)) { + blockCodeBuilder.ifThenElse(Opcode.IFNONNULL, + blockCodeBuilder1 -> blockCodeBuilder1.new_(cd_returnType) + .dup() + .aload(resultSlot) + .invokespecial(cd_returnType, + INIT_NAME, + MethodTypeDesc.of(CD_void, CD_MemorySegment)), + CodeBuilder::aconst_null); + } else if (CEnum.class.isAssignableFrom(returnType)) { + final Method wrapper = findCEnumWrapper(returnType); + blockCodeBuilder.invokestatic(cd_returnType, + wrapper.getName(), + MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), CD_int), + wrapper.getDeclaringClass().isInterface()); + } else if (Upcall.class.isAssignableFrom(returnType)) { + final Method wrapper = findUpcallWrapper(returnType); + blockCodeBuilder.ifThenElse(Opcode.IFNONNULL, + blockCodeBuilder1 -> blockCodeBuilder1.aload(allocatorSlot) + .aload(resultSlot) + .invokestatic(cd_returnType, + wrapper.getName(), + MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), + CD_Arena, + CD_MemorySegment), + wrapper.getDeclaringClass().isInterface()), + CodeBuilder::aconst_null); + } else if (returnType.isArray()) { + final Class componentType = returnType.getComponentType(); + if (componentType == String.class) { + final boolean hasCharset = getCharset(blockCodeBuilder, method); + blockCodeBuilder.invokestatic(CD_Unmarshal, + "unmarshalAsStringArray", + MethodTypeDesc.of(CD_String.arrayType(), + hasCharset ? + List.of(CD_MemorySegment, CD_Charset) : + List.of(CD_MemorySegment))); + } else if (componentType.isPrimitive() || componentType == MemorySegment.class) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + unmarshalMethod(returnType), + MethodTypeDesc.of(cd_returnType, CD_MemorySegment)); + } + } + if (!returnVoid) { + blockCodeBuilder.storeInstruction(returnTypeKind, unmarshalSlot); + } + // reset stack if (shouldAddStack) { blockCodeBuilder.aload(stackSlot) @@ -632,6 +635,9 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { } // return + if (!returnVoid) { + blockCodeBuilder.loadInstruction(returnTypeKind, unmarshalSlot); + } blockCodeBuilder.returnInstruction(returnTypeKind); }, catchBuilder -> catchBuilder.catching(CD_Throwable, blockCodeBuilder -> { @@ -707,37 +713,74 @@ private static T loadBytecode(Class callerClass, SymbolLookup lookup) { .invokevirtual(CD_Optional, "get", MethodTypeDesc.of(CD_Object)) .checkcast(CD_MemorySegment); - // layout final var returnType = method.getReturnType(); final boolean returnVoid = returnType == void.class; + final boolean methodByValue = method.getDeclaredAnnotation(ByValue.class) != null; + + // layout if (!returnVoid) { - convertToValueLayout(blockCodeBuilder, returnType); - if (!returnType.isPrimitive()) { + if (returnType.isPrimitive()) { + convertToValueLayout(blockCodeBuilder, returnType); + } else { final SizedSeg sizedSeg = method.getDeclaredAnnotation(SizedSeg.class); final Sized sized = method.getDeclaredAnnotation(Sized.class); final boolean isSizedSeg = sizedSeg != null; - final boolean isSized = sized != null && returnType.isArray(); - if (isSizedSeg || isSized) { - if (isSizedSeg) { - blockCodeBuilder.constantInstruction(sizedSeg.value()); - convertToValueLayout(blockCodeBuilder, byte.class); - } else { - blockCodeBuilder.constantInstruction((long) sized.value()); - convertToValueLayout(blockCodeBuilder, returnType.getComponentType()); + final boolean isSized = sized != null; + if (Struct.class.isAssignableFrom(returnType)) { + final String name = Arrays.stream(returnType.getDeclaredFields()) + .filter(field -> Modifier.isStatic(field.getModifiers()) && field.getType() == StructLayout.class) + .findFirst() + .orElseThrow(() -> + new IllegalStateException(STR."The struct \{returnType} must contain one public static field that is StructLayout")) + .getName(); + if (!methodByValue) { + convertToValueLayout(blockCodeBuilder, returnType); + if (isSizedSeg) { + blockCodeBuilder.constantInstruction(sizedSeg.value()); + } else if (isSized) { + blockCodeBuilder.constantInstruction((long) sized.value()); + } } - blockCodeBuilder.invokestatic(CD_MemoryLayout, - "sequenceLayout", - MethodTypeDesc.of(CD_SequenceLayout, CD_long, CD_MemoryLayout), - true) - .invokeinterface(CD_AddressLayout, + blockCodeBuilder.getstatic(ClassDesc.ofDescriptor(returnType.descriptorString()), + name, + CD_StructLayout); + if (!methodByValue) { + if (isSizedSeg || isSized) { + blockCodeBuilder.invokestatic(CD_MemoryLayout, + "sequenceLayout", + MethodTypeDesc.of(CD_SequenceLayout, CD_long, CD_MemoryLayout), + true); + } + blockCodeBuilder.invokeinterface(CD_AddressLayout, "withTargetLayout", MethodTypeDesc.of(CD_AddressLayout, CD_MemoryLayout)); + } + } else { + convertToValueLayout(blockCodeBuilder, returnType); + if (isSizedSeg || isSized) { + if (isSizedSeg) { + blockCodeBuilder.constantInstruction(sizedSeg.value()); + convertToValueLayout(blockCodeBuilder, byte.class); + } else { + blockCodeBuilder.constantInstruction((long) sized.value()); + convertToValueLayout(blockCodeBuilder, returnType.isArray() ? returnType.getComponentType() : byte.class); + } + blockCodeBuilder.invokestatic(CD_MemoryLayout, + "sequenceLayout", + MethodTypeDesc.of(CD_SequenceLayout, CD_long, CD_MemoryLayout), + true) + .invokeinterface(CD_AddressLayout, + "withTargetLayout", + MethodTypeDesc.of(CD_AddressLayout, CD_MemoryLayout)); + } } } } final var parameters = methodData.parameters(); final boolean skipFirstParam = methodData.skipFirstParam(); - final int size = skipFirstParam ? parameters.size() - 1 : parameters.size(); + final int size = skipFirstParam || methodByValue ? + parameters.size() - 1 : + parameters.size(); blockCodeBuilder.constantInstruction(size) .anewarray(CD_MemoryLayout); for (int i = 0; i < size; i++) { diff --git a/src/main/java/overrun/marshal/gen/Sized.java b/src/main/java/overrun/marshal/gen/Sized.java index 653f162..d704bb8 100644 --- a/src/main/java/overrun/marshal/gen/Sized.java +++ b/src/main/java/overrun/marshal/gen/Sized.java @@ -34,7 +34,7 @@ * @since 0.1.0 */ @Documented -@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Target({ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Sized { /** diff --git a/src/main/java/overrun/marshal/gen/SizedSeg.java b/src/main/java/overrun/marshal/gen/SizedSeg.java index 925b9af..012790f 100644 --- a/src/main/java/overrun/marshal/gen/SizedSeg.java +++ b/src/main/java/overrun/marshal/gen/SizedSeg.java @@ -33,7 +33,7 @@ * @since 0.1.0 */ @Documented -@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER}) +@Target({ElementType.METHOD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface SizedSeg { /** diff --git a/src/main/java/overrun/marshal/gen/Skip.java b/src/main/java/overrun/marshal/gen/Skip.java index a721a9e..b145393 100644 --- a/src/main/java/overrun/marshal/gen/Skip.java +++ b/src/main/java/overrun/marshal/gen/Skip.java @@ -30,7 +30,7 @@ * @since 0.1.0 */ @Documented -@Target({ElementType.FIELD, ElementType.METHOD}) +@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Skip { } diff --git a/src/main/java/overrun/marshal/gen/StrCharset.java b/src/main/java/overrun/marshal/gen/StrCharset.java index c17102f..3ee566c 100644 --- a/src/main/java/overrun/marshal/gen/StrCharset.java +++ b/src/main/java/overrun/marshal/gen/StrCharset.java @@ -31,7 +31,7 @@ * @since 0.1.0 */ @Documented -@Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD}) +@Target({ElementType.PARAMETER, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface StrCharset { /** diff --git a/src/main/java/overrun/marshal/struct/Struct.java b/src/main/java/overrun/marshal/struct/Struct.java index d8072d9..1363fad 100644 --- a/src/main/java/overrun/marshal/struct/Struct.java +++ b/src/main/java/overrun/marshal/struct/Struct.java @@ -21,7 +21,38 @@ import java.lang.foreign.*; /** - * The presentation of C struct. + * The presentation of a C structure. + *

Struct handles

+ * You can access an element of a structure via {@linkplain StructHandle}. + * The struct handle provides accessor where to set and get the element of a structure. + * You can get a read-only struct handle by declaring it with the {@linkplain StructHandleView view} variants. + *

Example

+ *
{@code
+ * class Point extends Struct {
+ *     public static final StructLayout LAYOUT = MemoryLayout.structLayout(
+ *         ValueLayout.JAVA_INT.withName("x"),
+ *         ValueLayout.JAVA_INT.withName("y")
+ *     );
+ *     public final StructHandle.Int x = StructHandle.ofInt(this, "x");
+ *     // read-only
+ *     public final StructHandleView.Int y = StructHandle.ofInt(this, "y");
+ *     // constructors ...
+ * }
+ * }
+ *

Incoming change

+ *
{@code
+ * value class Point extends Struct {
+ *     public static final StructLayout LAYOUT = MemoryLayout.structLayout(
+ *         // a dummy method that creates value layout
+ *         ValueLayout.of().withName("x"),
+ *         ValueLayout.of().withName("y")
+ *     );
+ *     public final StructHandle x = StructHandle.of(this, "x");
+ *     // read-only
+ *     public final StructHandleView y = StructHandle.of(this, "y");
+ *     // constructors ...
+ * }
+ * }
* * @author squid233 * @since 0.1.0 diff --git a/src/test/java/overrun/marshal/test/DowncallProvider.java b/src/test/java/overrun/marshal/test/DowncallProvider.java index 6196438..d4d12e2 100644 --- a/src/test/java/overrun/marshal/test/DowncallProvider.java +++ b/src/test/java/overrun/marshal/test/DowncallProvider.java @@ -55,11 +55,16 @@ public final class DowncallProvider { seg("testUpcall", LOOKUP.findStatic(DowncallProvider.class, "testUpcall", MethodType.methodType(int.class, MemorySegment.class)), FunctionDescriptor.of(JAVA_INT, ADDRESS)); seg("testIntArray", LOOKUP.findStatic(DowncallProvider.class, "testIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); seg("testVarArgsJava", LOOKUP.findStatic(DowncallProvider.class, "testVarArgsJava", MethodType.methodType(void.class, int.class, MemorySegment.class)), FunctionDescriptor.ofVoid(JAVA_INT, ADDRESS)); + seg("testStruct", LOOKUP.findStatic(DowncallProvider.class, "testStruct", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); seg("testReturnInt", LOOKUP.findStatic(DowncallProvider.class, "testReturnInt", MethodType.methodType(int.class)), FunctionDescriptor.of(JAVA_INT)); seg("testReturnString", LOOKUP.findStatic(DowncallProvider.class, "testReturnString", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnUTF16String", LOOKUP.findStatic(DowncallProvider.class, "testReturnUTF16String", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnCEnum", LOOKUP.findStatic(DowncallProvider.class, "testReturnCEnum", MethodType.methodType(int.class)), FunctionDescriptor.of(JAVA_INT)); seg("testReturnUpcall", LOOKUP.findStatic(DowncallProvider.class, "testReturnUpcall", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testReturnStruct", LOOKUP.findStatic(DowncallProvider.class, "testReturnStruct", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testReturnStructByValue", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructByValue", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(Vector3.LAYOUT)); + seg("testReturnStructSizedSeg", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructSizedSeg", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); + seg("testReturnStructSized", LOOKUP.findStatic(DowncallProvider.class, "testReturnStructSized", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testReturnIntArray", LOOKUP.findStatic(DowncallProvider.class, "testReturnIntArray", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); seg("testSizedIntArray", LOOKUP.findStatic(DowncallProvider.class, "testSizedIntArray", MethodType.methodType(void.class, MemorySegment.class)), FunctionDescriptor.ofVoid(ADDRESS)); seg("testReturnSizedSeg", LOOKUP.findStatic(DowncallProvider.class, "testReturnSizedSeg", MethodType.methodType(MemorySegment.class)), FunctionDescriptor.of(ADDRESS)); @@ -118,6 +123,16 @@ private static void testVarArgsJava(int c, MemorySegment arr) { System.out.print(Arrays.toString(arr.reinterpret(JAVA_INT.scale(0, c)).toArray(JAVA_INT))); } + private static void writeVector3(MemorySegment segment, int x, int y, int z) { + segment.set(JAVA_INT, 0, x); + segment.set(JAVA_INT, 4, y); + segment.set(JAVA_INT, 8, z); + } + + private static void testStruct(MemorySegment vector3) { + writeVector3(vector3.reinterpret(Vector3.LAYOUT.byteSize()), 1, 2, 3); + } + private static int testReturnInt() { return 42; } @@ -138,6 +153,29 @@ private static MemorySegment testReturnUpcall() { return ((SimpleUpcall) (i -> i * 2)).stub(ARENA); } + private static MemorySegment testReturnStruct() { + final MemorySegment segment = ARENA.allocate(Vector3.LAYOUT); + writeVector3(segment, 4, 5, 6); + return segment; + } + + private static MemorySegment testReturnStructByValue() { + final MemorySegment segment = ARENA.allocate(Vector3.LAYOUT); + writeVector3(segment, 7, 8, 9); + return segment; + } + + private static MemorySegment testReturnStructSizedSeg() { + final MemorySegment segment = ARENA.allocate(Vector3.LAYOUT, 2L); + writeVector3(segment, 1, 2, 3); + writeVector3(segment.asSlice(Vector3.LAYOUT.scale(0L, 1L)), 4, 5, 6); + return segment; + } + + private static MemorySegment testReturnStructSized() { + return testReturnStructSizedSeg(); + } + private static MemorySegment testReturnIntArray() { return ARENA.allocateFrom(JAVA_INT, 4, 2); } diff --git a/src/test/java/overrun/marshal/test/DowncallTest.java b/src/test/java/overrun/marshal/test/DowncallTest.java index a7e9d94..8caec9a 100644 --- a/src/test/java/overrun/marshal/test/DowncallTest.java +++ b/src/test/java/overrun/marshal/test/DowncallTest.java @@ -144,6 +144,17 @@ void testIntArray() { assertEquals("[4, 2][4, 2][4, 2][4, 2][4, 2][]", outputStream.toString()); } + @Test + void testStruct() { + try (Arena arena = Arena.ofConfined()) { + final Vector3 vector3 = new Vector3(arena); + d.testStruct(vector3); + assertEquals(1, vector3.x.get()); + assertEquals(2, vector3.y.get()); + assertEquals(3, vector3.z.get()); + } + } + @Test void testReturnInt() { assertEquals(42, d.testReturnInt()); @@ -168,6 +179,35 @@ void testReturnUpcall() { } } + @Test + void testReturnStruct() { + try (Arena arena = Arena.ofConfined()) { + final Vector3 returnStruct = d.testReturnStruct(); + assertEquals(4, returnStruct.x.get()); + assertEquals(5, returnStruct.y.get()); + assertEquals(6, returnStruct.z.get()); + final Vector3 returnStructByValue = d.testReturnStructByValue(arena); + assertEquals(7, returnStructByValue.x.get()); + assertEquals(8, returnStructByValue.y.get()); + assertEquals(9, returnStructByValue.z.get()); + } + } + + @Test + void testReturnStructSized() { + assertStructSized(d.testReturnStructSizedSeg()); + assertStructSized(d.testReturnStructSized()); + } + + private void assertStructSized(Vector3 vector3) { + assertEquals(1, vector3.x.get()); + assertEquals(2, vector3.y.get()); + assertEquals(3, vector3.z.get()); + assertEquals(4, vector3.x.get(1L)); + assertEquals(5, vector3.y.get(1L)); + assertEquals(6, vector3.z.get(1L)); + } + @Test void testReturnIntArray() { assertArrayEquals(new int[]{4, 2}, d.testReturnIntArray()); diff --git a/src/test/java/overrun/marshal/test/IDowncall.java b/src/test/java/overrun/marshal/test/IDowncall.java index a644df0..517535a 100644 --- a/src/test/java/overrun/marshal/test/IDowncall.java +++ b/src/test/java/overrun/marshal/test/IDowncall.java @@ -16,6 +16,7 @@ package overrun.marshal.test; +import overrun.marshal.ByValue; import overrun.marshal.Downcall; import overrun.marshal.MemoryStack; import overrun.marshal.gen.*; @@ -69,6 +70,8 @@ default void testDefault() { void testVarArgsJava(int c, int... arr); + void testStruct(Vector3 vector3); + int testReturnInt(); @SizedSeg(12) @@ -82,6 +85,17 @@ default void testDefault() { SimpleUpcall testReturnUpcall(Arena arena); + Vector3 testReturnStruct(); + + @ByValue + Vector3 testReturnStructByValue(SegmentAllocator allocator); + + @SizedSeg(2L) + Vector3 testReturnStructSizedSeg(); + + @Sized(2) + Vector3 testReturnStructSized(); + @Sized(2) int[] testReturnIntArray(); From bf4bec0328c0fc1e3d967b98ee6a7219f61f5393 Mon Sep 17 00:00:00 2001 From: squid233 <60126026+squid233@users.noreply.github.com> Date: Fri, 26 Jan 2024 21:41:30 +0800 Subject: [PATCH 28/28] Update javadoc --- src/main/java/overrun/marshal/struct/StructHandle.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/overrun/marshal/struct/StructHandle.java b/src/main/java/overrun/marshal/struct/StructHandle.java index 620600a..ab98f58 100644 --- a/src/main/java/overrun/marshal/struct/StructHandle.java +++ b/src/main/java/overrun/marshal/struct/StructHandle.java @@ -182,9 +182,11 @@ public static Str ofString(Struct struct, String name) { /** * Creates an array struct handle. * - * @param struct the struct - * @param name the name - * @param the type of the array + * @param struct the struct + * @param name the name + * @param setterFactory the setter factory + * @param getterFactory the getter factory + * @param the type of the array * @return the struct handle */ public static Array ofArray(Struct struct, String name, BiFunction setterFactory, Function getterFactory) {