From 8c69803753695d8529221ddb79828ebf55350a5d Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 4 Oct 2024 12:50:23 -0700 Subject: [PATCH 1/4] Model Swift's type metadata and value witness table for memory layout information Extend the Java SwiftKit with the ability to query the memory layout of an arbitrary Swift type given its Swift type metadata pointer (e.g., `Any.Type` in the Swift world). Use this to show the size, stride, and alignment of `Int?` from the Java side, which produces: Memory layout for Swift.Int?: size = 9 stride = 16 alignment = 8 --- .../java/org/example/HelloJava2Swift.java | 6 + .../main/java/org/swift/javakit/SwiftKit.java | 120 +++++++++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) diff --git a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java index 00264946..6d9c2de0 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java @@ -68,5 +68,11 @@ static void tests() { obj.voidMethod(); obj.takeIntMethod(42); + + MemorySegment swiftType = SwiftKit.getTypeByMangledNameInEnvironment("SiSg"); + System.out.println("Memory layout for Swift.Int?:"); + System.out.println(" size = " + SwiftKit.sizeOfSwiftType(swiftType)); + System.out.println(" stride = " + SwiftKit.strideOfSwiftType(swiftType)); + System.out.println(" alignment = " + SwiftKit.alignmentOfSwiftType(swiftType)); } } diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java index 07765a08..9e818690 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java @@ -17,6 +17,7 @@ import org.swift.swiftkit.SwiftHeapObject; import java.lang.foreign.*; +import java.lang.foreign.MemoryLayout.PathElement; import java.lang.invoke.MethodHandle; import java.util.Arrays; import java.util.stream.Collectors; @@ -200,7 +201,7 @@ public static MemorySegment getTypeByName(String string) { */ private static class swift_getTypeByMangledNameInEnvironment { public static final FunctionDescriptor DESC = FunctionDescriptor.of( - /*returns=*/ValueLayout.ADDRESS, + /*returns=*/SWIFT_POINTER, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, @@ -230,4 +231,121 @@ public static MemorySegment getTypeByMangledNameInEnvironment(String string) { throw new AssertionError("should not reach here", ex$); } } + + /** + * The value layout for Swift's Int type, which is a signed type that follows + * the size of a pointer (aka C's ptrdiff_t). + */ + public static ValueLayout SWIFT_INT = (ValueLayout.ADDRESS.byteSize() == 4) ? + ValueLayout.JAVA_INT : ValueLayout.JAVA_LONG; + + /** + * The value layout for Swift's UInt type, which is an unsigned type that follows + * the size of a pointer (aka C's size_t). Java does not have unsigned integer + * types in general, so we use the layout for Swift's Int. + */ + public static ValueLayout SWIFT_UINT = SWIFT_INT; + + /** + * Read a Swift.Int value from memory at the given offset and translate it into a Java long. + * + * This function copes with the fact that a Swift.Int might be 32 or 64 bits. + */ + public static final long getSwiftInt(MemorySegment memorySegment, long offset) { + if (SWIFT_INT == ValueLayout.JAVA_LONG) { + return memorySegment.get(ValueLayout.JAVA_LONG, offset); + } else { + return memorySegment.get(ValueLayout.JAVA_INT, offset); + } + } + + /** + * Value witness table layout. + */ + public static final MemoryLayout valueWitnessTableLayout = MemoryLayout.structLayout( + ValueLayout.ADDRESS.withName("initializeBufferWithCopyOfBuffer"), + ValueLayout.ADDRESS.withName("destroy"), + ValueLayout.ADDRESS.withName("initializeWithCopy"), + ValueLayout.ADDRESS.withName("assignWithCopy"), + ValueLayout.ADDRESS.withName("initializeWithTake"), + ValueLayout.ADDRESS.withName("assignWithTake"), + ValueLayout.ADDRESS.withName("getEnumTagSinglePayload"), + ValueLayout.ADDRESS.withName("storeEnumTagSinglePayload"), + SwiftKit.SWIFT_INT.withName("size"), + SwiftKit.SWIFT_INT.withName("stride"), + SwiftKit.SWIFT_UINT.withName("flags"), + SwiftKit.SWIFT_UINT.withName("extraInhabitantCount") + ).withName("SwiftValueWitnessTable"); + + /** + * Offset for the "size" field within the value witness table. + */ + static final long valueWitnessTable$size$offset = + valueWitnessTableLayout.byteOffset(PathElement.groupElement("size")); + + /** + * Offset for the "stride" field within the value witness table. + */ + static final long valueWitnessTable$stride$offset = + valueWitnessTableLayout.byteOffset(PathElement.groupElement("stride")); + + /** + * Offset for the "flags" field within the value witness table. + */ + static final long valueWitnessTable$flags$offset = + valueWitnessTableLayout.byteOffset(PathElement.groupElement("flags")); + + /** + * Type metadata pointer. + */ + public static final StructLayout fullTypeMetadataLayout = MemoryLayout.structLayout( + SWIFT_POINTER.withName("vwt") + ).withName("SwiftFullTypeMetadata"); + + /** + * Offset for the "vwt" field within the full type metadata. + */ + static final long fullTypeMetadata$vwt$offset = + fullTypeMetadataLayout.byteOffset(PathElement.groupElement("vwt")); + + /** + * Given the address of Swift type metadata for a type, return the addres + * of the "full" type metadata that can be accessed via fullTypeMetadataLayout. + */ + public static MemorySegment fullTypeMetadata(MemorySegment typeMetadata) { + return MemorySegment.ofAddress(typeMetadata.address() - SWIFT_POINTER.byteSize()) + .reinterpret(fullTypeMetadataLayout.byteSize()); + } + + /** + * Given the address of Swift type's metadata, return the address that + * references the value witness table for the type. + */ + public static MemorySegment valueWitnessTable(MemorySegment typeMetadata) { + return fullTypeMetadata(typeMetadata).get(SWIFT_POINTER, fullTypeMetadata$vwt$offset); + } + + /** + * Determine the size of a Swift type given its type metadata. + */ + public static long sizeOfSwiftType(MemorySegment typeMetadata) { + return getSwiftInt(valueWitnessTable(typeMetadata), valueWitnessTable$size$offset); + } + + /** + * Determine the stride of a Swift type given its type metadata, which is + * how many bytes are between successive elements of this type within an + * array. It is >= the size. + */ + public static long strideOfSwiftType(MemorySegment typeMetadata) { + return getSwiftInt(valueWitnessTable(typeMetadata), valueWitnessTable$stride$offset); + } + + /** + * Determine the alignment of the given Swift type. + */ + public static long alignmentOfSwiftType(MemorySegment typeMetadata) { + long flags = getSwiftInt(valueWitnessTable(typeMetadata), valueWitnessTable$flags$offset); + return (flags & 0xFF) + 1; + } } From 968b3bcad732b8e9005e6ead9eac85d742f2eb25 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 4 Oct 2024 21:48:08 -0700 Subject: [PATCH 2/4] [jextract-swift] Add an API to produce a MemoryLayout for a given Swift type Given Swift type metadata, inspect the metadata to produce a memory layout for the Swift type that covers its size/alignment and can be used to refer to memory containing a Swift value of that type. --- .../java/org/example/HelloJava2Swift.java | 1 + .../main/java/org/swift/javakit/SwiftKit.java | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java index 6d9c2de0..95870989 100644 --- a/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java +++ b/JavaSwiftKitDemo/src/main/java/org/example/HelloJava2Swift.java @@ -74,5 +74,6 @@ static void tests() { System.out.println(" size = " + SwiftKit.sizeOfSwiftType(swiftType)); System.out.println(" stride = " + SwiftKit.strideOfSwiftType(swiftType)); System.out.println(" alignment = " + SwiftKit.alignmentOfSwiftType(swiftType)); + System.out.println(" Java layout = " + SwiftKit.layoutOfSwiftType(swiftType)); } } diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java index 9e818690..cc859d00 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java @@ -348,4 +348,23 @@ public static long alignmentOfSwiftType(MemorySegment typeMetadata) { long flags = getSwiftInt(valueWitnessTable(typeMetadata), valueWitnessTable$flags$offset); return (flags & 0xFF) + 1; } + + /** + * Produce a layout that describes a Swift type based on its + * type metadata. The resulting layout is completely opaque to Java, but + * has appropriate size/alignment to model the memory associated with a + * Swift type. + * + * In the future, this layout could be extended to provide more detail, + * such as the fields of a Swift struct. + */ + public static MemoryLayout layoutOfSwiftType(MemorySegment typeMetadata) { + long size = sizeOfSwiftType(typeMetadata); + long stride = strideOfSwiftType(typeMetadata); + return MemoryLayout.structLayout( + MemoryLayout.sequenceLayout(size, JAVA_BYTE) + .withByteAlignment(alignmentOfSwiftType(typeMetadata)), + MemoryLayout.paddingLayout(stride - size) + ); + } } From 7dd04a5e1fffac94441b1f9d05abc8382e86521b Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 4 Oct 2024 22:32:11 -0700 Subject: [PATCH 3/4] Add SwiftKit.nameOfSwiftType() to get the name from Swift type metadata Use this new API to give a name to the memory layout we create for a Swift type. --- .../main/java/org/swift/javakit/SwiftKit.java | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java index cc859d00..edb95cff 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java @@ -22,6 +22,7 @@ import java.util.Arrays; import java.util.stream.Collectors; +import static java.lang.foreign.ValueLayout.ADDRESS; import static java.lang.foreign.ValueLayout.JAVA_BYTE; public class SwiftKit { @@ -349,6 +350,46 @@ public static long alignmentOfSwiftType(MemorySegment typeMetadata) { return (flags & 0xFF) + 1; } + /** + * Descriptor for the swift_getTypeName runtime function. + */ + public static final FunctionDescriptor swift_getTypeName$descriptor = FunctionDescriptor.of( + /*returns=*/MemoryLayout.structLayout( + SWIFT_POINTER.withName("utf8Chars"), + SWIFT_INT.withName("length") + ), + ValueLayout.ADDRESS, + ValueLayout.JAVA_BOOLEAN + ); + + /** + * Address of the swift_getTypeName runtime function. + */ + public static final MemorySegment swift_getTypeName$addr = findOrThrow("swift_getTypeName"); + + /** + * Handle for the swift_getTypeName runtime function. + */ + public static final MethodHandle swift_getTypeName$handle = Linker.nativeLinker().downcallHandle(swift_getTypeName$addr, swift_getTypeName$descriptor); + + /** + * Produce the name of the Swift type given its Swift type metadata. + * + * If 'qualified' is true, leave all of the qualification in place to + * disambiguate the type, producing a more complete (but longer) type name. + */ + public static String nameOfSwiftType(MemorySegment typeMetadata, boolean qualified) { + try { + try (Arena arena = Arena.ofConfined()) { + MemorySegment charsAndLength = (MemorySegment)swift_getTypeName$handle.invokeExact((SegmentAllocator)arena, typeMetadata, qualified); + MemorySegment utf8Chars = charsAndLength.get(SWIFT_POINTER, 0); + return utf8Chars.getString(0); + } + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + /** * Produce a layout that describes a Swift type based on its * type metadata. The resulting layout is completely opaque to Java, but @@ -365,6 +406,6 @@ public static MemoryLayout layoutOfSwiftType(MemorySegment typeMetadata) { MemoryLayout.sequenceLayout(size, JAVA_BYTE) .withByteAlignment(alignmentOfSwiftType(typeMetadata)), MemoryLayout.paddingLayout(stride - size) - ); + ).withName(nameOfSwiftType(typeMetadata, true)); } } From 8cb407e25b634714dd724ea3ac1b07c27124a84e Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Fri, 4 Oct 2024 22:40:25 -0700 Subject: [PATCH 4/4] Free the memory the Swift runtime allocated in swift_getTypeName(). --- .../main/java/org/swift/javakit/SwiftKit.java | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java index edb95cff..ba76232b 100644 --- a/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java +++ b/JavaSwiftKitDemo/src/main/java/org/swift/javakit/SwiftKit.java @@ -63,6 +63,36 @@ static MemorySegment findOrThrow(String symbol) { .orElseThrow(() -> new UnsatisfiedLinkError("unresolved symbol: %s".formatted(symbol))); } + // ==== ------------------------------------------------------------------------------------------------------------ + // free + /** + * Descriptor for the free C runtime function. + */ + public static final FunctionDescriptor free$descriptor = FunctionDescriptor.ofVoid( + ValueLayout.ADDRESS + ); + + /** + * Address of the free C runtime function. + */ + public static final MemorySegment free$addr = findOrThrow("free"); + + /** + * Handle for the free C runtime function. + */ + public static final MethodHandle free$handle = Linker.nativeLinker().downcallHandle(free$addr, free$descriptor); + + /** + * free the given pointer + */ + public static void cFree(MemorySegment pointer) { + try { + free$handle.invokeExact(pointer); + } catch (Throwable ex$) { + throw new AssertionError("should not reach here", ex$); + } + } + // ==== ------------------------------------------------------------------------------------------------------------ // swift_retainCount @@ -383,7 +413,9 @@ public static String nameOfSwiftType(MemorySegment typeMetadata, boolean qualifi try (Arena arena = Arena.ofConfined()) { MemorySegment charsAndLength = (MemorySegment)swift_getTypeName$handle.invokeExact((SegmentAllocator)arena, typeMetadata, qualified); MemorySegment utf8Chars = charsAndLength.get(SWIFT_POINTER, 0); - return utf8Chars.getString(0); + String typeName = utf8Chars.getString(0); + cFree(utf8Chars); + return typeName; } } catch (Throwable ex$) { throw new AssertionError("should not reach here", ex$);