From de10548f28564816fa4a7d316580a339e6a4e645 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Jan 2026 22:53:02 +0100 Subject: [PATCH 1/4] proof-of-concept: preserve fixed width integer types This POC is incomplete, but it demonstrates that (most likely) enough information is available to jextract to preserve the original types, and keep the library portable. --- README.md | 5 +++-- .../java/org/openjdk/jextract/clang/Type.java | 12 ++++++++++++ .../jextract/impl/ClassSourceBuilder.java | 17 +++++++++++++++++ .../openjdk/jextract/impl/DeclarationImpl.java | 6 +++++- .../jextract/impl/HeaderFileBuilder.java | 6 ++++-- .../org/openjdk/jextract/impl/TypeMaker.java | 4 +++- 6 files changed, 44 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 89d58f19..55e9e0f6 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ Alternatively, to build jextract from the latest sources (which include all the > Over time, new branches will be added, each targeting a specific JDK version. > -`jextract` can be built using `gradle`, as follows (on Windows, `gradlew.bat` should be used instead). +`jextract` can be built using `gradlew`, as follows (on Windows, `gradlew.bat` should be used instead). -We currently use gradle version 8.11.1 which is fetched automatically by the gradle wrapper. Please refer to the [compatibility matrix](https://docs.gradle.org/current/userguide/compatibility.html) to see which version of java is needed in `PATH`/`JAVA_HOME` to run gradle. Note that the JDK we use to build (the toolchain JDK) is passed in separately as a property. +We currently use Gradle version 8.11.1 which is fetched automatically by the Gradle wrapper. Please refer to the [compatibility matrix](https://docs.gradle.org/current/userguide/compatibility.html) to see which version of java is needed in `PATH`/`JAVA_HOME` to run Gradle. Note that the JDK we use to build (the toolchain JDK) is passed in separately as a property. @@ -36,6 +36,7 @@ We currently use gradle version 8.11.1 which is fetched automatically by the gra $ sh ./gradlew -Pjdk_home= -Pllvm_home= clean verify ``` +Alternatively these properties can be specified in the [`gradle.properties` file](https://docs.gradle.org/current/userguide/build_environment.html#the_gradle_properties_file). >
Using a local installation of LLVM > diff --git a/src/main/java/org/openjdk/jextract/clang/Type.java b/src/main/java/org/openjdk/jextract/clang/Type.java index c5a61637..2662c0d4 100644 --- a/src/main/java/org/openjdk/jextract/clang/Type.java +++ b/src/main/java/org/openjdk/jextract/clang/Type.java @@ -137,6 +137,18 @@ public Type canonicalType() { return new Type(canonicalType, owner); } + /** + * For a typedef returns the underlying type. + */ + public Type typeDefUnderlyingType() { + var cursor = getDeclarationCursor(); + if (cursor.isInvalid()) { + throw new AssertionError("TODO"); + } + var underlyingType = Index_h.clang_getTypedefDeclUnderlyingType(cursor.owner, cursor.segment); + return new Type(underlyingType, owner); + } + /** * Determine whether a Type has the "const" qualifier set, * without looking through typedefs that may have added "const" at a diff --git a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java index 7c1ae3a7..5b605f25 100644 --- a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java +++ b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java @@ -211,6 +211,7 @@ String layoutString(Type type, long align) { case Declared d when Utils.isEnum(d) -> layoutString(ClangEnumType.get(d.tree()).get(), align); case Declared d when Utils.isStructOrUnion(d) -> alignIfNeeded(JavaName.getFullNameOrThrow(d.tree()) + ".layout()", ClangAlignOf.getOrThrow(d.tree()) / 8, align); case Delegated d when d.kind() == Delegated.Kind.POINTER -> alignIfNeeded(runtimeHelperName() + ".C_POINTER", 8, align); + case Delegated d when d.kind() == Delegated.Kind.TYPEDEF -> typeDefLayoutString(d, align); case Delegated d -> layoutString(d.type(), align); case Function _ -> alignIfNeeded(runtimeHelperName() + ".C_POINTER", 8, align); case Array a -> String.format("MemoryLayout.sequenceLayout(%1$d, %2$s)", a.elementCount().orElse(0L), layoutString(a.elementType(), align)); @@ -218,6 +219,22 @@ String layoutString(Type type, long align) { }; } + private String typeDefLayoutString(Type.Delegated type, long align) { + String name = type.name().orElse(""); + return switch (name) { + // https://en.cppreference.com/w/cpp/types/integer.html + case "int8_t", "int16_t", "int32_t", "int64_t", + "uint8_t", "uint16_t", "uint32_t", "uint64_t", + // https://en.cppreference.com/w/cpp/types/floating-point.html + "float16_t", "float32_t", "float64_t", "float128_t", "bfloat16_t", + // Other types which are common enough and have an entry in FFM `Linker#canonicalLayouts()` + "size_t" + // TODO: Use proper names and proper alignment + -> alignIfNeeded(runtimeHelperName() + ".DEBUG_" + name, 1, align); + default -> layoutString(type.type(), align); + }; + } + String functionDescriptorString(int textBoxIndent, Type.Function functionType) { final MethodType type = Utils.methodTypeFor(functionType); boolean noArgs = type.parameterCount() == 0; diff --git a/src/main/java/org/openjdk/jextract/impl/DeclarationImpl.java b/src/main/java/org/openjdk/jextract/impl/DeclarationImpl.java index e8bee3f2..2ff47a1a 100644 --- a/src/main/java/org/openjdk/jextract/impl/DeclarationImpl.java +++ b/src/main/java/org/openjdk/jextract/impl/DeclarationImpl.java @@ -223,7 +223,11 @@ public List parameters() { @Override public Type.Function type() { - return type; + // Use actual types from parameters, since they preserve typedef information + // This is needed for example for `malloc` to preserve the parameter type `size_t` and avoid it becoming `long` + // TODO: Does that also affect return type? maybe not, for C function `mbstowcs` it properly emits `size_t` as return type + // TODO: Maybe this is not safe / correct + return Type.function(type.varargs(), type.returnType(), params.stream().map(Variable::type).toArray(Type[]::new)); } @Override diff --git a/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java b/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java index 16159091..3a7724ba 100644 --- a/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java +++ b/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java @@ -408,12 +408,14 @@ void emitBasicPrimitiveTypes(){ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double"); public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*")) .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR)); + + // TODO: constants for fixed width integer types and other common `..._t` types """); if (TypeImpl.IS_WINDOWS) { - appendIndentedLines("public static final ValueLayout.OfInt C_LONG = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get(\"long\");"); + appendIndentedLines("public static final ValueLayout C_LONG = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get(\"long\");"); appendIndentedLines("public static final ValueLayout.OfDouble C_LONG_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get(\"double\");"); } else { - appendIndentedLines("public static final ValueLayout.OfLong C_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get(\"long\");"); + appendIndentedLines("public static final ValueLayout C_LONG = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get(\"long\");"); } } private void emitGlobalGetter(String holderClass, String javaName, diff --git a/src/main/java/org/openjdk/jextract/impl/TypeMaker.java b/src/main/java/org/openjdk/jextract/impl/TypeMaker.java index c8788f7c..b12ca99d 100644 --- a/src/main/java/org/openjdk/jextract/impl/TypeMaker.java +++ b/src/main/java/org/openjdk/jextract/impl/TypeMaker.java @@ -152,7 +152,9 @@ static Type makeType(org.openjdk.jextract.clang.Type t, TreeMaker treeMaker) { } } case Typedef: { - Type __type = makeType(t.canonicalType(), treeMaker); + // Don't use `canonicalType()` here since that would lose information about intermediate typedefs, + // for example `typedef uint16_t MyType` would otherwise be resolved as `short` and not `uint16_t` + Type __type = makeType(t.typeDefUnderlyingType(), treeMaker); return Type.typedef(t.spelling(), __type); } case Complex: { From b1ebdcf9e65da3ec4bb20bfd37fef95871bf8d58 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Sun, 25 Jan 2026 23:54:03 +0100 Subject: [PATCH 2/4] remove fixed-width floating point types again (since they only exist for C++) --- .../java/org/openjdk/jextract/impl/ClassSourceBuilder.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java index 5b605f25..b0512f90 100644 --- a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java +++ b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java @@ -222,11 +222,9 @@ String layoutString(Type type, long align) { private String typeDefLayoutString(Type.Delegated type, long align) { String name = type.name().orElse(""); return switch (name) { - // https://en.cppreference.com/w/cpp/types/integer.html + // https://en.cppreference.com/w/c/types/integer.html case "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", - // https://en.cppreference.com/w/cpp/types/floating-point.html - "float16_t", "float32_t", "float64_t", "float128_t", "bfloat16_t", // Other types which are common enough and have an entry in FFM `Linker#canonicalLayouts()` "size_t" // TODO: Use proper names and proper alignment From d1e09a19ad114ee79d4ecf448cf2c94528604848 Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 26 Jan 2026 11:34:35 +0100 Subject: [PATCH 3/4] generate compilable code --- .../jextract/impl/ClassSourceBuilder.java | 19 +++++++++++-------- .../jextract/impl/HeaderFileBuilder.java | 14 ++++++++++++-- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java index b0512f90..d84b2762 100644 --- a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java +++ b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java @@ -41,6 +41,7 @@ import java.lang.invoke.MethodType; import java.lang.invoke.VarHandle; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -211,7 +212,7 @@ String layoutString(Type type, long align) { case Declared d when Utils.isEnum(d) -> layoutString(ClangEnumType.get(d.tree()).get(), align); case Declared d when Utils.isStructOrUnion(d) -> alignIfNeeded(JavaName.getFullNameOrThrow(d.tree()) + ".layout()", ClangAlignOf.getOrThrow(d.tree()) / 8, align); case Delegated d when d.kind() == Delegated.Kind.POINTER -> alignIfNeeded(runtimeHelperName() + ".C_POINTER", 8, align); - case Delegated d when d.kind() == Delegated.Kind.TYPEDEF -> typeDefLayoutString(d, align); + case Delegated d when d.kind() == Delegated.Kind.TYPEDEF -> typedefLayoutString(d, align); case Delegated d -> layoutString(d.type(), align); case Function _ -> alignIfNeeded(runtimeHelperName() + ".C_POINTER", 8, align); case Array a -> String.format("MemoryLayout.sequenceLayout(%1$d, %2$s)", a.elementCount().orElse(0L), layoutString(a.elementType(), align)); @@ -219,16 +220,18 @@ String layoutString(Type type, long align) { }; } - private String typeDefLayoutString(Type.Delegated type, long align) { + private String typedefLayoutString(Type.Delegated type, long align) { String name = type.name().orElse(""); + String layoutName = runtimeHelperName() + ".C_" + name.toUpperCase(Locale.ROOT); return switch (name) { // https://en.cppreference.com/w/c/types/integer.html - case "int8_t", "int16_t", "int32_t", "int64_t", - "uint8_t", "uint16_t", "uint32_t", "uint64_t", - // Other types which are common enough and have an entry in FFM `Linker#canonicalLayouts()` - "size_t" - // TODO: Use proper names and proper alignment - -> alignIfNeeded(runtimeHelperName() + ".DEBUG_" + name, 1, align); + case "int8_t", "uint8_t" -> layoutName; + case "int16_t", "uint16_t" -> alignIfNeeded(layoutName, 2, align); + case "int32_t", "uint32_t" -> alignIfNeeded(layoutName, 4, align); + case "int64_t", "uint64_t" -> alignIfNeeded(layoutName, 8, align); + // Other types which are common enough and have an entry in FFM `Linker#canonicalLayouts()` + case "size_t" -> alignIfNeeded(layoutName, 8, align); + // Fall back to the layout for the underlying type default -> layoutString(type.type(), align); }; } diff --git a/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java b/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java index 3a7724ba..be700397 100644 --- a/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java +++ b/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java @@ -408,9 +408,19 @@ void emitBasicPrimitiveTypes(){ public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double"); public static final AddressLayout C_POINTER = ((AddressLayout) Linker.nativeLinker().canonicalLayouts().get("void*")) .withTargetLayout(MemoryLayout.sequenceLayout(java.lang.Long.MAX_VALUE, C_CHAR)); - - // TODO: constants for fixed width integer types and other common `..._t` types + + public static final ValueLayout.OfByte C_INT8_T = (ValueLayout.OfByte) Linker.nativeLinker().canonicalLayouts().get("int8_t"); + public static final ValueLayout.OfByte C_UINT8_T = C_INT8_T; + public static final ValueLayout.OfShort C_INT16_T = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("int16_t"); + public static final ValueLayout.OfShort C_UINT16_T = C_INT16_T; + public static final ValueLayout.OfInt C_INT32_T = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int32_t"); + public static final ValueLayout.OfInt C_UINT32_T = C_INT32_T; + public static final ValueLayout.OfLong C_INT64_T = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("int64_t"); + public static final ValueLayout.OfLong C_UINT64_T = C_INT64_T; + + public static final ValueLayout C_SIZE_T = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get("size_t"); """); + // For C_LONG use unspecific ValueLayout to make generated code portable; because on Linux it is OfLong but on Windows it is OfInt if (TypeImpl.IS_WINDOWS) { appendIndentedLines("public static final ValueLayout C_LONG = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get(\"long\");"); appendIndentedLines("public static final ValueLayout.OfDouble C_LONG_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get(\"double\");"); From 09216562934f568a88986616f2f17b931dedd70b Mon Sep 17 00:00:00 2001 From: Marcono1234 Date: Mon, 26 Jan 2026 11:48:13 +0100 Subject: [PATCH 4/4] simplify generated type constants --- .../java/org/openjdk/jextract/impl/HeaderFileBuilder.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java b/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java index be700397..3cb647c0 100644 --- a/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java +++ b/src/main/java/org/openjdk/jextract/impl/HeaderFileBuilder.java @@ -403,6 +403,8 @@ void emitBasicPrimitiveTypes(){ public static final ValueLayout.OfByte C_CHAR =(ValueLayout.OfByte)Linker.nativeLinker().canonicalLayouts().get("char"); public static final ValueLayout.OfShort C_SHORT = (ValueLayout.OfShort) Linker.nativeLinker().canonicalLayouts().get("short"); public static final ValueLayout.OfInt C_INT = (ValueLayout.OfInt) Linker.nativeLinker().canonicalLayouts().get("int"); + // For C_LONG use unspecific ValueLayout to make generated code portable; because on Linux it is OfLong but on Windows it is OfInt + public static final ValueLayout C_LONG = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get("long"); public static final ValueLayout.OfLong C_LONG_LONG = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("long long"); public static final ValueLayout.OfFloat C_FLOAT = (ValueLayout.OfFloat) Linker.nativeLinker().canonicalLayouts().get("float"); public static final ValueLayout.OfDouble C_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get("double"); @@ -418,14 +420,11 @@ void emitBasicPrimitiveTypes(){ public static final ValueLayout.OfLong C_INT64_T = (ValueLayout.OfLong) Linker.nativeLinker().canonicalLayouts().get("int64_t"); public static final ValueLayout.OfLong C_UINT64_T = C_INT64_T; + // For C_SIZE_T use unspecific ValueLayout to make generated code portable; because its layout is platform-specific public static final ValueLayout C_SIZE_T = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get("size_t"); """); - // For C_LONG use unspecific ValueLayout to make generated code portable; because on Linux it is OfLong but on Windows it is OfInt if (TypeImpl.IS_WINDOWS) { - appendIndentedLines("public static final ValueLayout C_LONG = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get(\"long\");"); appendIndentedLines("public static final ValueLayout.OfDouble C_LONG_DOUBLE = (ValueLayout.OfDouble) Linker.nativeLinker().canonicalLayouts().get(\"double\");"); - } else { - appendIndentedLines("public static final ValueLayout C_LONG = (ValueLayout) Linker.nativeLinker().canonicalLayouts().get(\"long\");"); } } private void emitGlobalGetter(String holderClass, String javaName,