diff --git a/README.md b/README.md index d24acee..4ec624d 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Import as a Gradle dependency: ```groovy dependencies { - implementation("io.github.over-run:marshal:0.1.0-alpha.25-jdk22") + implementation("io.github.over-run:marshal:0.1.0-alpha.26-jdk22") } ``` diff --git a/gradle.properties b/gradle.properties index b57879a..34c5ad7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ projGroupId=io.github.over-run projArtifactId=marshal # The project name should only contain lowercase letters, numbers and hyphen. projName=marshal -projVersion=0.1.0-alpha.25-jdk22 +projVersion=0.1.0-alpha.26-jdk22 projDesc=Marshaler of native libraries # Uncomment them if you want to publish to maven repository. projUrl=https://github.com/Over-Run/marshal diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 4d35c6f..01c157d 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -23,9 +23,11 @@ module io.github.overrun.marshal { exports overrun.marshal; exports overrun.marshal.gen; + exports overrun.marshal.gen.processor; exports overrun.marshal.struct; opens overrun.marshal.internal.data; requires static org.jetbrains.annotations; + requires jdk.jshell; } diff --git a/src/main/java/overrun/marshal/Downcall.java b/src/main/java/overrun/marshal/Downcall.java index f8375ad..8db663b 100644 --- a/src/main/java/overrun/marshal/Downcall.java +++ b/src/main/java/overrun/marshal/Downcall.java @@ -16,10 +16,12 @@ package overrun.marshal; -import overrun.marshal.gen.Type; import overrun.marshal.gen.*; -import overrun.marshal.internal.data.DowncallData; +import overrun.marshal.gen.processor.ArgumentProcessor; +import overrun.marshal.gen.processor.ArgumentProcessorContext; +import overrun.marshal.gen.processor.ProcessorTypes; import overrun.marshal.internal.DowncallOptions; +import overrun.marshal.internal.data.DowncallData; import overrun.marshal.struct.ByValue; import overrun.marshal.struct.Struct; import overrun.marshal.struct.StructAllocator; @@ -38,7 +40,6 @@ import java.lang.reflect.*; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -46,6 +47,7 @@ import static java.lang.classfile.ClassFile.*; import static java.lang.constant.ConstantDescs.*; import static overrun.marshal.internal.Constants.*; +import static overrun.marshal.internal.StringCharset.getCharset; /** * Downcall library loader. @@ -191,18 +193,6 @@ private static String unmarshalMethod(Class aClass) { return "unmarshal"; } - private static String marshalFromBooleanMethod(Type type) { - return switch (type) { - case CHAR -> "marshalAsChar"; - case BYTE -> "marshalAsByte"; - case SHORT -> "marshalAsShort"; - case INT -> "marshalAsInt"; - case LONG -> "marshalAsLong"; - case FLOAT -> "marshalAsFloat"; - case DOUBLE -> "marshalAsDouble"; - }; - } - // type private static ValueLayout getValueLayout(Class carrier) { @@ -224,41 +214,6 @@ private static boolean requireAllocator(Class aClass) { Upcall.class.isAssignableFrom(aClass)); } - // string charset - - 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", MTD_Charset_String); - } - } - - private static boolean getCharset(CodeBuilder codeBuilder, AnnotatedElement element) { - final String charset = getCharset(element); - if (charset != null) { - getCharset(codeBuilder, charset); - return true; - } - return false; - } - // class desc private static ClassDesc convertToDowncallCD(AnnotatedElement element, Class aClass) { @@ -331,6 +286,28 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look final ClassDesc cd_thisClass = ClassDesc.of(lookupClass.getPackageName(), DEFAULT_NAME); final Map methodDataMap = LinkedHashMap.newLinkedHashMap(methodList.size()); + //region method handles + final AtomicInteger handleCount = new AtomicInteger(); + methodList.forEach(method -> { + final String entrypoint = getMethodEntrypoint(method); + final String handleName = STR."$mh\{handleCount.getAndIncrement()}"; + final var parameters = List.of(method.getParameters()); + + final DowncallMethodData methodData = new DowncallMethodData( + entrypoint, + handleName, + exceptionStringMap.get(method), + parameters, + method.getDeclaredAnnotation(ByValue.class) == null && + !parameters.isEmpty() && + SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) + ); + methodDataMap.put(method, methodData); + }); + //endregion + + final DowncallData downcallData = generateData(methodDataMap, lookup, descriptorMap); + final byte[] bytes = cf.build(cd_thisClass, classBuilder -> { classBuilder.withFlags(ACC_FINAL | ACC_SUPER); @@ -343,25 +320,12 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look } //region method handles - final AtomicInteger handleCount = new AtomicInteger(); - methodList.forEach(method -> { - final String entrypoint = getMethodEntrypoint(method); - final String handleName = STR."$mh\{handleCount.getAndIncrement()}"; - final var parameters = List.of(method.getParameters()); - - final DowncallMethodData methodData = new DowncallMethodData( - entrypoint, - handleName, - exceptionStringMap.get(method), - parameters, - method.getDeclaredAnnotation(ByValue.class) == null && - !parameters.isEmpty() && - SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) - ); - methodDataMap.put(method, methodData); - - classBuilder.withField(handleName, CD_MethodHandle, - ACC_PRIVATE | ACC_FINAL | ACC_STATIC); + methodDataMap.values().forEach(data -> { + if (downcallData.handleMap().get(data.entrypoint()) != null) { + classBuilder.withField(data.handleName(), + CD_MethodHandle, + ACC_PRIVATE | ACC_FINAL | ACC_STATIC); + } }); //endregion @@ -385,7 +349,6 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look final String methodName = method.getName(); final int modifiers = method.getModifiers(); 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() @@ -393,351 +356,289 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look .toList()); final String handleName = methodData.handleName(); - final Consumer wrapping = returnType == MethodHandle.class ? null : 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; - } + classBuilder.withMethod(methodName, + mtd_method, + Modifier.isPublic(modifiers) ? ACC_PUBLIC : ACC_PROTECTED, + methodBuilder -> methodBuilder.withCode(codeBuilder -> { + final TypeKind returnTypeKind = TypeKind.from(cd_returnType).asLoadable(); - 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; + if (returnType == MethodHandle.class) { + if (method.isDefault() && + downcallData.handleMap().get(methodData.entrypoint()) == null) { + // invoke super interface + invokeSuperMethod(codeBuilder, parameters); + codeBuilder.invokespecial(cd_targetClass, + methodName, + mtd_method, + targetClass.isInterface()) + .areturn(); + } else { + codeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle) + .areturn(); + } + return; } - codeBuilder.ldc(sized.value()) - .aload(codeBuilder.parameterSlot(i)) - .arraylength() - .invokestatic(CD_Checks, - "checkArraySize", - MTD_void_int_int); - } - // initialize stack - if (shouldAddStack) { - codeBuilder.invokestatic(CD_MemoryStack, "stackGet", MTD_MemoryStack) - .astore(stackSlot) - .aload(stackSlot) - .invokevirtual(CD_MemoryStack, "pointer", MTD_long) - .lstore(stackPointerSlot); - } + if (method.isDefault() && + downcallData.handleMap().get(methodData.entrypoint()) == null) { + // invoke super interface + invokeSuperMethod(codeBuilder, parameters); + codeBuilder.invokespecial(cd_targetClass, + methodName, + mtd_method, + targetClass.isInterface() + ).returnInstruction(returnTypeKind); + } else { + //region body + final boolean hasAllocator = + !parameters.isEmpty() && + SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()); + final boolean shouldAddStack = + !parameters.isEmpty() && + !SegmentAllocator.class.isAssignableFrom(parameters.getFirst().getType()) && + parameters.stream().anyMatch(parameter -> requireAllocator(parameter.getType())); + final int stackSlot; + final int stackPointerSlot; + final int allocatorSlot; - codeBuilder.trying( - blockCodeBuilder -> { - final boolean skipFirstParam = methodData.skipFirstParam(); - final int parameterSize = parameters.size(); + 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 ClassDesc cd_returnTypeDowncall = convertToDowncallCD(method, returnType); - final boolean returnVoid = returnType == void.class; + final Map parameterRefSlot = HashMap.newHashMap(Math.toIntExact(parameters.stream() + .filter(parameter -> parameter.getDeclaredAnnotation(Ref.class) != null) + .count())); - // ref + // check size for (int i = 0, size = parameters.size(); i < size; i++) { final Parameter parameter = parameters.get(i); - if (parameter.getDeclaredAnnotation(Ref.class) == null) { - continue; - } + final Sized sized = parameter.getDeclaredAnnotation(Sized.class); final Class type = parameter.getType(); - if (type.isArray()) { - final Class componentType = type.getComponentType(); - final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); - blockCodeBuilder.aload(allocatorSlot) - .aload(blockCodeBuilder.parameterSlot(i)); - - if (componentType == String.class && getCharset(blockCodeBuilder, parameter)) { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_StringArray_Charset - ).astore(slot); - } else { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MethodTypeDesc.of(CD_MemorySegment, - CD_SegmentAllocator, - convertToMarshalCD(componentType).arrayType()) - ).astore(slot); - } - parameterRefSlot.put(parameter, slot); + if (sized == null || !type.isArray()) { + continue; } + codeBuilder.ldc(sized.value()) + .aload(codeBuilder.parameterSlot(i)) + .arraylength() + .invokestatic(CD_Checks, + "checkArraySize", + MTD_void_int_int); } - // invocation - final List parameterCDList = new ArrayList<>(skipFirstParam ? parameterSize - 1 : parameterSize); - blockCodeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle); - for (int i = skipFirstParam ? 1 : 0; i < parameterSize; i++) { - final Parameter parameter = parameters.get(i); - final Class type = parameter.getType(); - final ClassDesc cd_parameterDowncall = convertToDowncallCD(parameter, type); + // initialize stack + if (shouldAddStack) { + codeBuilder.invokestatic(CD_MemoryStack, "stackGet", MTD_MemoryStack) + .astore(stackSlot) + .aload(stackSlot) + .invokevirtual(CD_MemoryStack, "pointer", MTD_long) + .lstore(stackPointerSlot); + } - final Integer refSlot = parameterRefSlot.get(parameter); - if (refSlot != null) { - blockCodeBuilder.aload(refSlot); - } else { - final int slot = blockCodeBuilder.parameterSlot(i); - final Convert convert = parameter.getDeclaredAnnotation(Convert.class); - if (convert != null && type == boolean.class) { - final Type convertType = convert.value(); - blockCodeBuilder.loadInstruction( - TypeKind.fromDescriptor(type.descriptorString()).asLoadable(), - slot - ).invokestatic(CD_Marshal, - marshalFromBooleanMethod(convertType), - MethodTypeDesc.of(convertType.classDesc(), CD_boolean)); - } else if (type.isPrimitive() || - type == MemorySegment.class || - SegmentAllocator.class.isAssignableFrom(type)) { - 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_MemorySegment_SegmentAllocator_String_Charset); - } else { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_String); + codeBuilder.trying( + blockCodeBuilder -> { + final boolean skipFirstParam = methodData.skipFirstParam(); + final int parameterSize = parameters.size(); + + final ClassDesc cd_returnTypeDowncall = convertToDowncallCD(method, returnType); + final boolean returnVoid = returnType == void.class; + + // ref + for (int i = 0, size = parameters.size(); i < size; i++) { + final Parameter parameter = parameters.get(i); + if (parameter.getDeclaredAnnotation(Ref.class) == null) { + continue; } - } else if (Addressable.class.isAssignableFrom(type) || - CEnum.class.isAssignableFrom(type)) { - blockCodeBuilder.aload(slot) - .invokestatic(CD_Marshal, - "marshal", - MethodTypeDesc.of(cd_parameterDowncall, convertToMarshalCD(type))); - } else if (Upcall.class.isAssignableFrom(type)) { - blockCodeBuilder.aload(allocatorSlot) - .checkcast(CD_Arena) - .aload(slot) - .invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_Arena_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); + final Class type = parameter.getType(); + if (type.isArray()) { + final Class componentType = type.getComponentType(); + final int slot = blockCodeBuilder.allocateLocal(TypeKind.ReferenceType); + blockCodeBuilder.aload(allocatorSlot) + .aload(blockCodeBuilder.parameterSlot(i)); + + if (componentType == String.class && getCharset(blockCodeBuilder, parameter)) { + blockCodeBuilder.invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_SegmentAllocator_StringArray_Charset + ).astore(slot); + } else { + blockCodeBuilder.invokestatic(CD_Marshal, + "marshal", + MethodTypeDesc.of(CD_MemorySegment, + CD_SegmentAllocator, + convertToMarshalCD(componentType).arrayType()) + ).astore(slot); + } + parameterRefSlot.put(parameter, slot); } - blockCodeBuilder.aload(slot); - if (isStringArray && getCharset(blockCodeBuilder, parameter)) { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MTD_MemorySegment_SegmentAllocator_StringArray_Charset); + } + + // invocation + final List parameterCDList = new ArrayList<>(skipFirstParam ? parameterSize - 1 : parameterSize); + blockCodeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle); + for (int i = skipFirstParam ? 1 : 0; i < parameterSize; i++) { + final Parameter parameter = parameters.get(i); + final Class type = parameter.getType(); + final ClassDesc cd_parameterDowncall = convertToDowncallCD(parameter, type); + + final Integer refSlot = parameterRefSlot.get(parameter); + if (refSlot != null) { + blockCodeBuilder.aload(refSlot); } else { - blockCodeBuilder.invokestatic(CD_Marshal, - "marshal", - MethodTypeDesc.of(CD_MemorySegment, - isUpcallArray ? CD_Arena : CD_SegmentAllocator, - convertToMarshalCD(componentType).arrayType())); + ArgumentProcessor.getInstance().process( + blockCodeBuilder, + ProcessorTypes.fromClass(type), + new ArgumentProcessorContext( + parameter, + blockCodeBuilder.parameterSlot(i), + allocatorSlot, + parameter.getDeclaredAnnotation(Convert.class) + ) + ); } - } - } - parameterCDList.add(cd_parameterDowncall); - } - blockCodeBuilder.invokevirtual(CD_MethodHandle, - "invokeExact", - MethodTypeDesc.of(cd_returnTypeDowncall, parameterCDList)); + parameterCDList.add(cd_parameterDowncall); + } + blockCodeBuilder.invokevirtual(CD_MethodHandle, + "invokeExact", + MethodTypeDesc.of(cd_returnTypeDowncall, parameterCDList)); - final int resultSlot; - if (returnVoid) { - resultSlot = -1; - } else { - final TypeKind typeKind = TypeKind.from(cd_returnTypeDowncall); - resultSlot = blockCodeBuilder.allocateLocal(typeKind); - blockCodeBuilder.storeInstruction(typeKind, resultSlot); - } + final int resultSlot; + if (returnVoid) { + resultSlot = -1; + } else { + final TypeKind typeKind = TypeKind.from(cd_returnTypeDowncall); + resultSlot = blockCodeBuilder.allocateLocal(typeKind); + blockCodeBuilder.storeInstruction(typeKind, resultSlot); + } - // copy ref result - for (int i = 0, size = parameters.size(); i < size; i++) { - final Parameter parameter = parameters.get(i); - final Class type = parameter.getType(); - final Class componentType = type.getComponentType(); - if (parameter.getDeclaredAnnotation(Ref.class) != null && - type.isArray()) { - final boolean isPrimitiveArray = componentType.isPrimitive(); - final boolean isStringArray = componentType == String.class; - if (isPrimitiveArray || isStringArray) { - final int refSlot = parameterRefSlot.get(parameter); - final int parameterSlot = blockCodeBuilder.parameterSlot(i); - blockCodeBuilder.aload(refSlot) - .aload(parameterSlot); - if (isPrimitiveArray) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MethodTypeDesc.of(CD_void, CD_MemorySegment, ClassDesc.ofDescriptor(type.descriptorString()))); - } else { - if (getCharset(blockCodeBuilder, parameter)) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MTD_void_MemorySegment_StringArray_Charset); - } else { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "copy", - MTD_void_MemorySegment_StringArray); + // copy ref result + for (int i = 0, size = parameters.size(); i < size; i++) { + final Parameter parameter = parameters.get(i); + final Class type = parameter.getType(); + final Class componentType = type.getComponentType(); + if (parameter.getDeclaredAnnotation(Ref.class) != null && + type.isArray()) { + final boolean isPrimitiveArray = componentType.isPrimitive(); + final boolean isStringArray = componentType == String.class; + if (isPrimitiveArray || isStringArray) { + final int refSlot = parameterRefSlot.get(parameter); + final int parameterSlot = blockCodeBuilder.parameterSlot(i); + blockCodeBuilder.aload(refSlot) + .aload(parameterSlot); + if (isPrimitiveArray) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + "copy", + MethodTypeDesc.of(CD_void, CD_MemorySegment, ClassDesc.ofDescriptor(type.descriptorString()))); + } else { + if (getCharset(blockCodeBuilder, parameter)) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + "copy", + MTD_void_MemorySegment_StringArray_Charset); + } else { + blockCodeBuilder.invokestatic(CD_Unmarshal, + "copy", + MTD_void_MemorySegment_StringArray); + } + } } } } - } - } - - // wrap return value - final int unmarshalSlot; - if (returnVoid) { - unmarshalSlot = -1; - } else { - if (shouldAddStack) { - unmarshalSlot = blockCodeBuilder.allocateLocal(returnTypeKind); - } else { - unmarshalSlot = -1; - } - blockCodeBuilder.loadInstruction(TypeKind.from(cd_returnTypeDowncall), resultSlot); - } - final Convert convert = method.getDeclaredAnnotation(Convert.class); - if (convert != null && returnType == boolean.class) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsBoolean", - MethodTypeDesc.of(CD_boolean, convert.value().classDesc())); - } else if (returnType == String.class) { - final boolean hasCharset = getCharset(blockCodeBuilder, method); - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unboundString", - hasCharset ? MTD_String_MemorySegment_Charset : MTD_String_MemorySegment); - } else if (Struct.class.isAssignableFrom(returnType)) { - blockCodeBuilder.ifThenElse(Opcode.IFNONNULL, - blockCodeBuilder1 -> { - final var structAllocatorField = getStructAllocatorField(returnType); - Objects.requireNonNull(structAllocatorField); - blockCodeBuilder1.getstatic(cd_returnType, structAllocatorField.getName(), CD_StructAllocator) - .aload(resultSlot) - .invokevirtual(CD_StructAllocator, "of", MTD_Object_MemorySegment) - .checkcast(cd_returnType); - }, - CodeBuilder::aconst_null); - } else if (CEnum.class.isAssignableFrom(returnType)) { - final Method wrapper = findCEnumWrapper(returnType); - blockCodeBuilder.invokestatic(cd_returnType, - wrapper.getName(), - MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), CD_int), - wrapper.getDeclaringClass().isInterface()); - } else if (returnType.isArray()) { - final Class componentType = returnType.getComponentType(); - if (componentType == String.class) { - final boolean hasCharset = getCharset(blockCodeBuilder, method); - blockCodeBuilder.invokestatic(CD_Unmarshal, - "unmarshalAsStringArray", - hasCharset ? MTD_StringArray_MemorySegment_Charset : MTD_StringArray_MemorySegment); - } else if (componentType.isPrimitive() || componentType == MemorySegment.class) { - blockCodeBuilder.invokestatic(CD_Unmarshal, - unmarshalMethod(returnType), - MethodTypeDesc.of(cd_returnType, CD_MemorySegment)); - } - } - // reset stack - if (shouldAddStack) { - if (!returnVoid) { - blockCodeBuilder.storeInstruction(returnTypeKind, unmarshalSlot); - } - blockCodeBuilder.aload(stackSlot) - .lload(stackPointerSlot) - .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); - if (!returnVoid) { - blockCodeBuilder.loadInstruction(returnTypeKind, unmarshalSlot); - } - } + // wrap return value + final int unmarshalSlot; + if (returnVoid) { + unmarshalSlot = -1; + } else { + if (shouldAddStack) { + unmarshalSlot = blockCodeBuilder.allocateLocal(returnTypeKind); + } else { + unmarshalSlot = -1; + } + blockCodeBuilder.loadInstruction(TypeKind.from(cd_returnTypeDowncall), resultSlot); + } + final Convert convert = method.getDeclaredAnnotation(Convert.class); + if (convert != null && returnType == boolean.class) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + "unmarshalAsBoolean", + MethodTypeDesc.of(CD_boolean, convert.value().classDesc())); + } else if (returnType == String.class) { + final boolean hasCharset = getCharset(blockCodeBuilder, method); + blockCodeBuilder.invokestatic(CD_Unmarshal, + "unboundString", + hasCharset ? MTD_String_MemorySegment_Charset : MTD_String_MemorySegment); + } else if (Struct.class.isAssignableFrom(returnType)) { + blockCodeBuilder.ifThenElse(Opcode.IFNONNULL, + blockCodeBuilder1 -> { + final var structAllocatorField = getStructAllocatorField(returnType); + Objects.requireNonNull(structAllocatorField); + blockCodeBuilder1.getstatic(cd_returnType, structAllocatorField.getName(), CD_StructAllocator) + .aload(resultSlot) + .invokevirtual(CD_StructAllocator, "of", MTD_Object_MemorySegment) + .checkcast(cd_returnType); + }, + CodeBuilder::aconst_null); + } else if (CEnum.class.isAssignableFrom(returnType)) { + final Method wrapper = findCEnumWrapper(returnType); + blockCodeBuilder.invokestatic(cd_returnType, + wrapper.getName(), + MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.getReturnType().descriptorString()), CD_int), + wrapper.getDeclaringClass().isInterface()); + } else if (returnType.isArray()) { + final Class componentType = returnType.getComponentType(); + if (componentType == String.class) { + final boolean hasCharset = getCharset(blockCodeBuilder, method); + blockCodeBuilder.invokestatic(CD_Unmarshal, + "unmarshalAsStringArray", + hasCharset ? MTD_StringArray_MemorySegment_Charset : MTD_StringArray_MemorySegment); + } else if (componentType.isPrimitive() || componentType == MemorySegment.class) { + blockCodeBuilder.invokestatic(CD_Unmarshal, + unmarshalMethod(returnType), + MethodTypeDesc.of(cd_returnType, CD_MemorySegment)); + } + } - // 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, MTD_void_String_Throwable); - // reset stack - if (shouldAddStack) { - blockCodeBuilder.aload(stackSlot) - .lload(stackPointerSlot) - .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); - } - // throw - blockCodeBuilder.athrow(); - }) - ); - }; - classBuilder.withMethod(methodName, - mtd_method, - Modifier.isPublic(modifiers) ? ACC_PUBLIC : ACC_PROTECTED, - methodBuilder -> methodBuilder.withCode(codeBuilder -> { - if (returnType == MethodHandle.class) { - codeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle); - if (method.isDefault()) { - codeBuilder.ifThenElse(Opcode.IFNONNULL, - blockCodeBuilder -> blockCodeBuilder - .getstatic(cd_thisClass, handleName, CD_MethodHandle) - .areturn(), - blockCodeBuilder -> { - // invoke super interface - invokeSuperMethod(blockCodeBuilder, parameters); - blockCodeBuilder.invokespecial(cd_targetClass, - methodName, - mtd_method, - targetClass.isInterface()) - .areturn(); - }); - } else { - codeBuilder.areturn(); - } - return; - } + // reset stack + if (shouldAddStack) { + if (!returnVoid) { + blockCodeBuilder.storeInstruction(returnTypeKind, unmarshalSlot); + } + blockCodeBuilder.aload(stackSlot) + .lload(stackPointerSlot) + .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); + if (!returnVoid) { + blockCodeBuilder.loadInstruction(returnTypeKind, unmarshalSlot); + } + } - if (method.isDefault()) { - codeBuilder.getstatic(cd_thisClass, handleName, CD_MethodHandle) - .ifThenElse(Opcode.IFNONNULL, - wrapping::accept, - blockCodeBuilder -> { - // invoke super interface - invokeSuperMethod(blockCodeBuilder, parameters); - blockCodeBuilder.invokespecial(cd_targetClass, - methodName, - mtd_method, - targetClass.isInterface() - ).returnInstruction(returnTypeKind); + // 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, MTD_void_String_Throwable); + // reset stack + if (shouldAddStack) { + blockCodeBuilder.aload(stackSlot) + .lload(stackPointerSlot) + .invokevirtual(CD_MemoryStack, "setPointer", MTD_void_long); } - ).nop(); - } else { - wrapping.accept(codeBuilder); + // throw + blockCodeBuilder.athrow(); + }) + ); + //endregion } })); }); @@ -779,12 +680,17 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look .astore(handleMapSlot); // method handles - methodDataMap.values().forEach(methodData -> codeBuilder - .aload(handleMapSlot) - .ldc(methodData.entrypoint()) - .invokeinterface(CD_Map, "get", MTD_Object_Object) - .checkcast(CD_MethodHandle) - .putstatic(cd_thisClass, methodData.handleName(), CD_MethodHandle)); + methodDataMap.values().forEach(methodData -> { + final String entrypoint = methodData.entrypoint(); + if (downcallData.handleMap().get(entrypoint) != null) { + codeBuilder + .aload(handleMapSlot) + .ldc(entrypoint) + .invokeinterface(CD_Map, "get", MTD_Object_Object) + .checkcast(CD_MethodHandle) + .putstatic(cd_thisClass, methodData.handleName(), CD_MethodHandle); + } + }); codeBuilder.return_(); })); @@ -794,7 +700,7 @@ private static T loadBytecode(MethodHandles.Lookup caller, SymbolLookup look try { final MethodHandles.Lookup hiddenClass = caller.defineHiddenClassWithClassData( bytes, - generateData(methodDataMap, lookup, descriptorMap), + downcallData, true, MethodHandles.Lookup.ClassOption.STRONG ); diff --git a/src/main/java/overrun/marshal/gen/Convert.java b/src/main/java/overrun/marshal/gen/Convert.java index fd9420f..fdf3ace 100644 --- a/src/main/java/overrun/marshal/gen/Convert.java +++ b/src/main/java/overrun/marshal/gen/Convert.java @@ -19,7 +19,8 @@ import java.lang.annotation.*; /** - * Marks an element that needs to convert from {@code boolean} to the given type. + * Marks a parameter that needs to convert from {@code boolean} to the given type, + * or a return type that needs to convert from the given type to {@code boolean}. *

* The type of the marked element must be {@code boolean}; otherwise this annotation will be ignored. *

Example

diff --git a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java b/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java new file mode 100644 index 0000000..8278a39 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/ArgumentProcessor.java @@ -0,0 +1,148 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.gen.Type; +import overrun.marshal.internal.StringCharset; + +import java.lang.classfile.CodeBuilder; +import java.lang.constant.MethodTypeDesc; +import java.util.ArrayList; +import java.util.List; + +import static java.lang.constant.ConstantDescs.CD_boolean; +import static overrun.marshal.internal.Constants.*; + +/** + * Method argument processor + * + * @author squid233 + * @since 0.1.0 + */ +public final class ArgumentProcessor implements Processor { + private static final ArgumentProcessor INSTANCE = new ArgumentProcessor(); + private final List> list = new ArrayList<>(0); + + private ArgumentProcessor() { + } + + @SuppressWarnings("preview") + public boolean process(CodeBuilder builder, ProcessorType type, ArgumentProcessorContext context) { + switch (type) { + case ProcessorType.Value value -> { + if (value == ProcessorType.Value.BOOLEAN && + context.convert() != null) { + final Type convertType = context.convert().value(); + builder.loadInstruction( + value.typeKind(), + context.parameterSlot() + ).invokestatic(CD_Marshal, + marshalFromBooleanMethod(convertType), + MethodTypeDesc.of(convertType.classDesc(), CD_boolean)); + } else { + builder.loadInstruction( + value.typeKind().asLoadable(), + context.parameterSlot() + ); + } + } + case ProcessorType.Allocator _ -> builder.aload(context.parameterSlot()); + case ProcessorType.Str _ -> { + builder.aload(context.allocatorSlot()) + .aload(context.parameterSlot()); + if (StringCharset.getCharset(builder, context.parameter())) { + builder.invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_SegmentAllocator_String_Charset); + } else { + builder.invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_SegmentAllocator_String); + } + } + case ProcessorType.Addr _, ProcessorType.CEnum _ -> builder + .aload(context.parameterSlot()) + .invokestatic(CD_Marshal, + "marshal", + MethodTypeDesc.of(type.downcallClassDesc(), type.marshalClassDesc())); + case ProcessorType.Upcall _ -> builder + .aload(context.allocatorSlot()) + .checkcast(CD_Arena) + .aload(context.parameterSlot()) + .invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_Arena_Upcall); + case ProcessorType.Array array -> { + final ProcessorType componentType = array.componentType(); + final boolean isStringArray = componentType instanceof ProcessorType.Str; + final boolean isUpcallArray = componentType instanceof ProcessorType.Upcall; + builder.aload(context.allocatorSlot()); + if (isUpcallArray) { + builder.checkcast(CD_Arena); + } + builder.aload(context.parameterSlot()); + if (isStringArray && StringCharset.getCharset(builder, context.parameter())) { + builder.invokestatic(CD_Marshal, + "marshal", + MTD_MemorySegment_SegmentAllocator_StringArray_Charset); + } else { + builder.invokestatic(CD_Marshal, + "marshal", + MethodTypeDesc.of(CD_MemorySegment, + isUpcallArray ? CD_Arena : CD_SegmentAllocator, + array.marshalClassDesc())); + } + } + default -> { + for (var processor : list) { + if (!processor.process(builder, type, context)) { + break; + } + } + } + } + return false; + } + + /** + * Registers a processor + * + * @param processor the processor + */ + public void registerProcessor(Processor processor) { + list.add(processor); + } + + /** + * {@return this} + */ + public static ArgumentProcessor getInstance() { + return INSTANCE; + } + + private static String marshalFromBooleanMethod(Type convertType) { + return switch (convertType) { + case CHAR -> "marshalAsChar"; + case BYTE -> "marshalAsByte"; + case SHORT -> "marshalAsShort"; + case INT -> "marshalAsInt"; + case LONG -> "marshalAsLong"; + case FLOAT -> "marshalAsFloat"; + case DOUBLE -> "marshalAsDouble"; + }; + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/ArgumentProcessorContext.java b/src/main/java/overrun/marshal/gen/processor/ArgumentProcessorContext.java new file mode 100644 index 0000000..6fc4f25 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/ArgumentProcessorContext.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.gen.processor; + +import overrun.marshal.gen.Convert; + +import java.lang.reflect.Parameter; + +/** + * The argument processor context + * + * @param parameter parameter + * @param parameterSlot parameter slot + * @param allocatorSlot allocator slot + * @param convert boolean convert + * @author squid233 + * @since 0.1.0 + */ +public record ArgumentProcessorContext( + Parameter parameter, + int parameterSlot, + int allocatorSlot, + Convert convert +) { +} diff --git a/src/main/java/overrun/marshal/gen/processor/Processor.java b/src/main/java/overrun/marshal/gen/processor/Processor.java new file mode 100644 index 0000000..567a7cd --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/Processor.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.gen.processor; + +import java.lang.classfile.CodeBuilder; + +/** + * Processor + * + * @param target type + * @param context type + * @author squid233 + * @since 0.1.0 + */ +public interface Processor { + /** + * Processes with the context + * + * @param builder the code builder + * @param type the type + * @param context the context + * @return {@code true} if should continue; {@code false} otherwise + */ + boolean process(CodeBuilder builder, T type, C context); +} diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorType.java b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java new file mode 100644 index 0000000..8decc8b --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorType.java @@ -0,0 +1,267 @@ +/* + * MIT License + * + * Copyright (c) 2024 Overrun Organization + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + */ + +package overrun.marshal.gen.processor; + +import overrun.marshal.Addressable; + +import java.lang.classfile.TypeKind; +import java.lang.constant.ClassDesc; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.lang.foreign.ValueLayout; + +import static java.lang.constant.ConstantDescs.*; +import static overrun.marshal.internal.Constants.*; + +/** + * Types to be processed + * + * @author squid233 + * @since 0.1.0 + */ +public sealed interface ProcessorType { + /** + * {@return the class desc for method handles} + */ + ClassDesc downcallClassDesc(); + + /** + * {@return the class desc for methods in {@link overrun.marshal.Marshal}} + */ + ClassDesc marshalClassDesc(); + + /** + * Primitive types, including {@link MemorySegment} + */ + enum Value implements ProcessorType { + /** + * {@code boolean} type + */ + BOOLEAN(CD_boolean, ValueLayout.JAVA_BOOLEAN), + /** + * {@code char} type + */ + CHAR(CD_char, ValueLayout.JAVA_CHAR), + /** + * {@code byte} type + */ + BYTE(CD_byte, ValueLayout.JAVA_BYTE), + /** + * {@code short} type + */ + SHORT(CD_short, ValueLayout.JAVA_SHORT), + /** + * {@code int} type + */ + INT(CD_int, ValueLayout.JAVA_INT), + /** + * {@code long} type + */ + LONG(CD_long, ValueLayout.JAVA_LONG), + /** + * {@code float} type + */ + FLOAT(CD_float, ValueLayout.JAVA_FLOAT), + /** + * {@code double} type + */ + DOUBLE(CD_double, ValueLayout.JAVA_DOUBLE), + /** + * {@link MemorySegment} type + */ + ADDRESS(CD_MemorySegment, ValueLayout.ADDRESS); + + private final ClassDesc classDesc; + private final TypeKind typeKind; + private final ValueLayout layout; + + Value(ClassDesc classDesc, ValueLayout layout) { + this.classDesc = classDesc; + this.typeKind = TypeKind.from(classDesc); + this.layout = layout; + } + + /** + * {@return the class desc of this type} + */ + public ClassDesc classDesc() { + return classDesc; + } + + /** + * {@return the type kind of this type} + */ + public TypeKind typeKind() { + return typeKind; + } + + /** + * {@return the layout of this type} + */ + public ValueLayout layout() { + return layout; + } + + @Override + public ClassDesc downcallClassDesc() { + return classDesc(); + } + + @Override + public ClassDesc marshalClassDesc() { + return classDesc(); + } + } + + /** + * {@link SegmentAllocator} + */ + final class Allocator implements ProcessorType { + /** + * The instance + */ + public static final Allocator INSTANCE = new Allocator(); + + private Allocator() { + } + + @Override + public ClassDesc downcallClassDesc() { + return CD_SegmentAllocator; + } + + @Override + public ClassDesc marshalClassDesc() { + return CD_SegmentAllocator; + } + } + + /** + * {@link String} + */ + final class Str implements ProcessorType { + /** + * The instance + */ + public static final Str INSTANCE = new Str(); + + private Str() { + } + + @Override + public ClassDesc downcallClassDesc() { + return CD_MemorySegment; + } + + @Override + public ClassDesc marshalClassDesc() { + return CD_String; + } + } + + /** + * {@link Addressable} + */ + final class Addr implements ProcessorType { + /** + * The instance + */ + public static final Addr INSTANCE = new Addr(); + + private Addr() { + } + + @Override + public ClassDesc downcallClassDesc() { + return CD_MemorySegment; + } + + @Override + public ClassDesc marshalClassDesc() { + return CD_Addressable; + } + } + + /** + * {@link overrun.marshal.CEnum} + */ + final class CEnum implements ProcessorType { + /** + * The instance + */ + public static final CEnum INSTANCE = new CEnum(); + + private CEnum() { + } + + @Override + public ClassDesc downcallClassDesc() { + return CD_int; + } + + @Override + public ClassDesc marshalClassDesc() { + return CD_CEnum; + } + } + + /** + * {@link overrun.marshal.Upcall} + */ + final class Upcall implements ProcessorType { + /** + * The instance + */ + public static final Upcall INSTANCE = new Upcall(); + + private Upcall() { + } + + @Override + public ClassDesc downcallClassDesc() { + return CD_MemorySegment; + } + + @Override + public ClassDesc marshalClassDesc() { + return CD_Upcall; + } + } + + /** + * Array type + * + * @param componentType the component type + */ + record Array(ProcessorType componentType) implements ProcessorType { + @Override + public ClassDesc downcallClassDesc() { + return CD_MemorySegment; + } + + @Override + public ClassDesc marshalClassDesc() { + return componentType().marshalClassDesc().arrayType(); + } + } + + /** + * Custom type + */ + non-sealed interface Custom extends ProcessorType { + } +} diff --git a/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.java new file mode 100644 index 0000000..09a1d57 --- /dev/null +++ b/src/main/java/overrun/marshal/gen/processor/ProcessorTypes.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.gen.processor; + +import overrun.marshal.Addressable; +import overrun.marshal.CEnum; +import overrun.marshal.Upcall; + +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SegmentAllocator; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * Processor types + * + * @author squid233 + * @since 0.1.0 + */ +public final class ProcessorTypes { + private static final Map, ProcessorType> map = new HashMap<>(0); + + private ProcessorTypes() { + } + + /** + * Get the processor type from the given class. + * + * @param aClass the class + * @return the processor type + */ + public static ProcessorType fromClass(Class aClass) { + if (aClass == boolean.class) return ProcessorType.Value.BOOLEAN; + if (aClass == char.class) return ProcessorType.Value.CHAR; + if (aClass == byte.class) return ProcessorType.Value.BYTE; + if (aClass == short.class) return ProcessorType.Value.SHORT; + if (aClass == int.class) return ProcessorType.Value.INT; + if (aClass == long.class) return ProcessorType.Value.LONG; + if (aClass == float.class) return ProcessorType.Value.FLOAT; + if (aClass == double.class) return ProcessorType.Value.DOUBLE; + if (aClass == MemorySegment.class) return ProcessorType.Value.ADDRESS; + if (aClass == String.class) return ProcessorType.Str.INSTANCE; + if (SegmentAllocator.class.isAssignableFrom(aClass)) return ProcessorType.Allocator.INSTANCE; + if (Addressable.class.isAssignableFrom(aClass)) return ProcessorType.Addr.INSTANCE; + if (CEnum.class.isAssignableFrom(aClass)) return ProcessorType.CEnum.INSTANCE; + if (Upcall.class.isAssignableFrom(aClass)) return ProcessorType.Upcall.INSTANCE; + if (aClass.isArray()) return new ProcessorType.Array(fromClass(aClass.componentType())); + return Objects.requireNonNull(map.get(aClass), STR."Cannot find processor type of \{aClass}"); + } + + /** + * Registers a processor type for the given class. + * + * @param aClass the class + * @param type the processor type + */ + public static void registerClass(Class aClass, ProcessorType type) { + if (type != null) { + map.put(aClass, type); + } else { + map.remove(aClass); + } + } +} diff --git a/src/main/java/overrun/marshal/internal/StringCharset.java b/src/main/java/overrun/marshal/internal/StringCharset.java new file mode 100644 index 0000000..b8b09a5 --- /dev/null +++ b/src/main/java/overrun/marshal/internal/StringCharset.java @@ -0,0 +1,91 @@ +/* + * 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.internal; + +import overrun.marshal.gen.StrCharset; + +import java.lang.classfile.CodeBuilder; +import java.lang.reflect.AnnotatedElement; +import java.util.Locale; + +import static overrun.marshal.internal.Constants.*; + +/** + * String charsets + * + * @author squid233 + * @since 0.1.0 + */ +public final class StringCharset { + private StringCharset() { + } + + /** + * {@return hasCharset} + * + * @param strCharset strCharset + */ + public static boolean hasCharset(StrCharset strCharset) { + return strCharset != null && !strCharset.value().isBlank(); + } + + /** + * {@return getCharset} + * + * @param element element + */ + public static String getCharset(AnnotatedElement element) { + final StrCharset strCharset = element.getDeclaredAnnotation(StrCharset.class); + return hasCharset(strCharset) ? strCharset.value() : null; + } + + /** + * getCharset + * + * @param codeBuilder codeBuilder + * @param charset charset + */ + public 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", MTD_Charset_String); + } + } + + /** + * {@return getCharset} + * + * @param codeBuilder codeBuilder + * @param element element + */ + public static boolean getCharset(CodeBuilder codeBuilder, AnnotatedElement element) { + final String charset = getCharset(element); + if (charset != null) { + getCharset(codeBuilder, charset); + return true; + } + return false; + } +}