Skip to content

Conversation

@varada1110
Copy link

@varada1110 varada1110 commented Nov 20, 2025

Total of 10 test failures observed on AIX:
jtreg/generator/nestedTypes/TestNestedTypesUnsupported.java
jtreg/generator/test8246400/LibTest8246400Test.java
jtreg/generator/test8258605/LibTest8258605Test.java
jtreg/generator/test8261511/Test8261511.java
jtreg/generator/testStruct/LibStructTest.java
testng/org/openjdk/jextract/test/toolprovider/ConstantsTest.java
testng/org/openjdk/jextract/test/toolprovider/IncompleteArrayTest.java
testng/org/openjdk/jextract/test/toolprovider/Test8240811.java
testng/org/openjdk/jextract/test/toolprovider/TestClassGeneration.java
testng/org/openjdk/jextract/test/toolprovider/nestedAnonOffset/TestNestedAnonOffset.java

This PR fixes AIX specific layout generation issues related to incorrect alignment double and pointer types.

  1. Structs containing double fields fail with:
    i. Unsupported layout: 4%D8
    ii. Invalid alignment constraint for member layout
    double in AIX structs has size 8 but alignment 4 (except as first field). AIX specific handling for C_DOUBLE computes the correct alignment.

  2. Clang was detected as 32-bit due to missing -m64 during macro extraction, causing inconsistent macros. This caused jextract to interpret pointer constants incorrectly, leading to failures like:
    expected [-1] but found [4294967295]

  3. TestNestedAnonOffset.java test failed on AIX because it also expects more padding similar to platforms like windows and linux

After the patch jtreg tests passes successfully.

JBS: CODETOOLS-7904115


Progress

  • Change must not contain extraneous whitespace
  • Change must be properly reviewed (no review required)

Issue

  • CODETOOLS-7904115: Fix for AIX test case failures due to incorrect alignment for double and pointer (Bug - P4)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jextract.git pull/296/head:pull/296
$ git checkout pull/296

Update a local copy of the PR:
$ git checkout pull/296
$ git pull https://git.openjdk.org/jextract.git pull/296/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 296

View PR using the GUI difftool:
$ git pr show -t 296

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jextract/pull/296.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 20, 2025

👋 Welcome back varadam! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Nov 20, 2025

@varada1110 This change now passes all automated pre-integration checks.

After integration, the commit message for the final commit will be:

7904115: Fix for AIX test case failures due to incorrect alignment for double and pointer

You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed.

At the time when this comment was updated there had been 5 new commits pushed to the master branch:

As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details.

As you do not have Committer status in this project an existing Committer must agree to sponsor your change.

➡️ To flag this PR as ready for integration with the above commit message, type /integrate in a new comment. (Afterwards, your sponsor types /sponsor in a new comment to perform the integration).

@varada1110 varada1110 marked this pull request as ready for review November 20, 2025 13:57
@openjdk openjdk bot added ready Pull request is ready to be integrated rfr Pull request is ready for review labels Nov 20, 2025
@mlbridge
Copy link

mlbridge bot commented Nov 20, 2025

Webrevs

Comment on lines 90 to 92
if (TypeImpl.IS_AIX) {
clangArgs.add("-m64");
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like the right place to add this, as the -m64 flag would be added for each clang argument.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an extra call to addClangArg should be added to JextractTool.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed it. Thank you

Comment on lines +264 to +269
if (align == 8) {
align = 4;
yield alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", 8, align);
} else {
yield alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", 4, align);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are there 2 cases here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't this be:

case Double -> alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", (TypeImpl.IS_AIX ? 4 : 8), align);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes right.
These are my findings!

case Double -> alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", (TypeImpl.IS_AIX ? 4 : 8), align);
The above fix solves most of the failures except two of them. This solves the failure like "Unsupported layout: 4%D8"

The remaining two test failures are due to 'IllegalArgumentException: Invalid alignment constraint for member layout: D8(d)'
I observed that the expected alignment is 8 for them and the ABI expects the alignment constraint for d to be 4, not 8
For one of the test failure : jtreg/generator/testStruct/LibStructTest.java
For the struct AllTypes, the double d field is not the first field, so according to AIX power mode rules, subsequent doubles are aligned on 4-byte boundaries, not 8

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's expected, but I think the test should be modified to handle that instead. The issue with the code in this patch is that it adjusts the alignment down to 4 in some cases when the requested alignment is 8. The requested alignment (i.e. the align argument) is always correct here, since that is what we get straight from clang based on alignment specifiers or pack pragmas.

For instance, for a struct like this:

struct foo {
    alignas(8) double d;
};

align would be 8 here for the field d, but you overwrite it to 4, which doesn't seem right.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I had another look at the test, and it looks like it can not really be adjusted, so there might be an issue elsewhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a little debugging, and it looks like clang doesn't attach the alignment specified by alignas to the field declaration any ways. So, we can not handle hyper-aligned fields even if we wanted to. In other words, you can disregard the example above.

On the other hand, I'm not sure if clang attaches the right alignment to double fields that are not the first field on AIX. Look at the code in StructBuilder that generates the layouts for fields:

if (member instanceof Variable var) {
  memberLayout = layoutString(var.type(), align);
  memberLayout = String.format("%1$s%2$s.withName(\"%3$s\")", indentString(indent + 1), memberLayout, member.name());
} 

You should be able to get the alignment of the field deceleration using long fieldAlign = ClangAlignOf.getOrThrow(var) / 8;. If this is 4 as expected for a non-first double field, then we could just pass that information down to the code that generates the layout string, I think.

@JornVernee
Copy link
Member

@varada1110

You could also try the following patch:

diff --git a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java
index 7c1ae3a..a317dc5 100644
--- a/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java
+++ b/src/main/java/org/openjdk/jextract/impl/ClassSourceBuilder.java
@@ -30,7 +30,6 @@ import org.openjdk.jextract.Type.Declared;
 import org.openjdk.jextract.Type.Delegated;
 import org.openjdk.jextract.Type.Function;
 import org.openjdk.jextract.Type.Primitive;
-import org.openjdk.jextract.impl.DeclarationImpl.ClangAlignOf;
 import org.openjdk.jextract.impl.DeclarationImpl.ClangEnumType;
 import org.openjdk.jextract.impl.DeclarationImpl.DeclarationString;
 import org.openjdk.jextract.impl.DeclarationImpl.JavaName;
@@ -201,19 +200,25 @@ abstract class ClassSourceBuilder {
         throw new IllegalArgumentException("Not handled: " + type);
     }
 
+    private static final int NO_ALIGN_REQUIRED_MARKER = -1;
+
     String layoutString(Type type) {
-        return layoutString(type, Long.MAX_VALUE);
+        return fieldLayoutString(type, -1, NO_ALIGN_REQUIRED_MARKER);
     }
 
-    String layoutString(Type type, long align) {
+    String fieldLayoutString(Type type, long typeAlign, long expectedAlign) {
         return switch (type) {
-            case Primitive p -> primitiveLayoutString(p, 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 -> 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));
+            case Primitive p -> primitiveLayoutString(p, typeAlign, expectedAlign);
+            case Declared d when Utils.isEnum(d) ->
+                    fieldLayoutString(ClangEnumType.get(d.tree()).get(), typeAlign, expectedAlign);
+            case Declared d when Utils.isStructOrUnion(d) ->
+                    alignIfNeeded(JavaName.getFullNameOrThrow(d.tree()) + ".layout()", typeAlign, expectedAlign);
+            case Delegated d when d.kind() == Delegated.Kind.POINTER ->
+                    alignIfNeeded(runtimeHelperName() + ".C_POINTER", typeAlign, expectedAlign);
+            case Delegated d -> fieldLayoutString(d.type(), typeAlign, expectedAlign);
+            case Function _ -> alignIfNeeded(runtimeHelperName() + ".C_POINTER", typeAlign, expectedAlign);
+            case Array a -> String.format("MemoryLayout.sequenceLayout(%1$d, %2$s)", a.elementCount().orElse(0L),
+                    fieldLayoutString(a.elementType(), typeAlign, expectedAlign));
             default -> throw new UnsupportedOperationException();
         };
     }
@@ -250,18 +255,18 @@ abstract class ClassSourceBuilder {
         return " ".repeat(size * 4);
     }
 
-    private String primitiveLayoutString(Primitive primitiveType, long align) {
+    private String primitiveLayoutString(Primitive primitiveType, long defaultAlign, long expectedAlign) {
         return switch (primitiveType.kind()) {
-            case Bool -> runtimeHelperName() + ".C_BOOL";
-            case Char -> runtimeHelperName() + ".C_CHAR";
-            case Short -> alignIfNeeded(runtimeHelperName() + ".C_SHORT", 2, align);
-            case Int -> alignIfNeeded(runtimeHelperName() + ".C_INT", 4, align);
-            case Long -> alignIfNeeded(runtimeHelperName() + ".C_LONG", TypeImpl.IS_WINDOWS ? 4 : 8, align);
-            case LongLong -> alignIfNeeded(runtimeHelperName() + ".C_LONG_LONG", 8, align);
-            case Float -> alignIfNeeded(runtimeHelperName() + ".C_FLOAT", 4, align);
-            case Double -> alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", 8, align);
+            case Bool -> alignIfNeeded(runtimeHelperName() + ".C_BOOL", defaultAlign, expectedAlign);
+            case Char -> alignIfNeeded(runtimeHelperName() + ".C_CHAR", defaultAlign, expectedAlign);
+            case Short -> alignIfNeeded(runtimeHelperName() + ".C_SHORT", defaultAlign, expectedAlign);
+            case Int -> alignIfNeeded(runtimeHelperName() + ".C_INT", defaultAlign, expectedAlign);
+            case Long -> alignIfNeeded(runtimeHelperName() + ".C_LONG", defaultAlign, expectedAlign);
+            case LongLong -> alignIfNeeded(runtimeHelperName() + ".C_LONG_LONG", defaultAlign, expectedAlign);
+            case Float -> alignIfNeeded(runtimeHelperName() + ".C_FLOAT", defaultAlign, expectedAlign);
+            case Double -> alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", defaultAlign, expectedAlign);
             case LongDouble -> TypeImpl.IS_WINDOWS ?
-                    alignIfNeeded(runtimeHelperName() + ".C_LONG_DOUBLE", 8, align) :
+                    alignIfNeeded(runtimeHelperName() + ".C_LONG_DOUBLE", defaultAlign, expectedAlign) :
                     paddingLayoutString(8, 0);
             case HalfFloat, Char16, WChar -> paddingLayoutString(2, 0); // unsupported
             case Float128, Int128 -> paddingLayoutString(16, 0); // unsupported
@@ -269,8 +274,8 @@ abstract class ClassSourceBuilder {
         };
     }
 
-    private String alignIfNeeded(String layoutPrefix, long align, long expectedAlign) {
-        return align > expectedAlign ?
+    private String alignIfNeeded(String layoutPrefix, long defaultAlign, long expectedAlign) {
+        return expectedAlign != NO_ALIGN_REQUIRED_MARKER && defaultAlign != expectedAlign ?
                 String.format("%1$s.align(%2$s, %3$d)", runtimeHelperName(), layoutPrefix, expectedAlign) :
                 layoutPrefix;
     }
diff --git a/src/main/java/org/openjdk/jextract/impl/StructBuilder.java b/src/main/java/org/openjdk/jextract/impl/StructBuilder.java
index dedad14..3415996 100644
--- a/src/main/java/org/openjdk/jextract/impl/StructBuilder.java
+++ b/src/main/java/org/openjdk/jextract/impl/StructBuilder.java
@@ -460,7 +460,7 @@ final class StructBuilder extends ClassSourceBuilder implements OutputFactory.Bu
 
         boolean isStruct = scoped.kind() == Scoped.Kind.STRUCT;
 
-        long align = ClangAlignOf.getOrThrow(scoped) / 8;
+        long scopedTypeAlign = ClangAlignOf.getOrThrow(scoped) / 8;
         long offset = base;
 
         long size = 0L; // bits
@@ -477,7 +477,16 @@ final class StructBuilder extends ClassSourceBuilder implements OutputFactory.Bu
                 }
                 String memberLayout;
                 if (member instanceof Variable var) {
-                    memberLayout = layoutString(var.type(), align);
+                    // FIXME we can not handle hyper-aligned fields here since clang doesn't attach the
+                    // alignment specified by a field alignment specifier to the field declaration cursor.
+                    //
+                    // struct foo { // ClangAlignOf == 8
+                    //     _Alignas(8) int x; // ClangAlignOf == 4
+                    // };
+                    long fieldTypeAlign = ClangAlignOf.getOrThrow(var) / 8;
+                    // if struct is packed, adjust expected alignment down
+                    long expectedAlign = Math.min(scopedTypeAlign, fieldTypeAlign);
+                    memberLayout = fieldLayoutString(var.type(), fieldTypeAlign, expectedAlign);
                     memberLayout = String.format("%1$s%2$s.withName(\"%3$s\")", indentString(indent + 1), memberLayout, member.name());
                 } else {
                     // anon struct

And then I think you should be able to just adjust expectedAlign in StructBuilder for what AIX needs.

@mcimadamore
Copy link
Contributor

It seems like the issue described in this PR has more to do with libclang not having a way to report the alignment of a specific layout fields (instead, it has only ways to report intrinsic alignment of a type). @varada1110 I wonder if you should perhaps also try to fix this in libclang as well? It seems like a similar problem can also occur with other libclang-dependent tools, such as Rust bindgen ?

@mcimadamore
Copy link
Contributor

My 0.02$ on this issue.

Jextract code (perhaps incorrectly) hardwrires the expected alignment for common C types. Let's ignore the fact that it's hardwired...

The main reason we do this is to avoid redundant call to align. Not because it would be inefficient, but simply because it will make layouts unreadable.

So, we do:

case Double -> alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", 8, align);

And then:

   private String alignIfNeeded(String layoutPrefix, long align, long expectedAlign) {
        return align > expectedAlign ?
                String.format("%1$s.align(%2$s, %3$d)", runtimeHelperName(), layoutPrefix, expectedAlign) :
                layoutPrefix;
    }

What this means is: if the expected alignment requirement is bigger than the alignment of the primitive type, we can safely skip the call to align (most of the times). Which means we only need to call align when we're trying to, say, put an int field in a 2-byte aligned offset -- this will lead to an exception when building the layout, so we need to properly align the layout (to 2 bytes).

Now, given the fuzzy nature of double alignment on AIX, perhaps it would be best to skip this "optimization" at least for doubles. That is, we could simply do:

case Double -> alignIfNeeded(runtimeHelperName() + ".C_DOUBLE", IS_AIX ? Long.MAX_VALUE : 8, align);

What this will do is it will pass a very big intrinsic layout alignment for double. This alignment will always be bigger than whatever jextract wants, which means a call to align will always be emitted. This seems the path of least resistance?

@mcimadamore
Copy link
Contributor

My 0.02$ on this issue.

I had an offline discussion with @JornVernee --- it seems part of the issue here is caused by the fact that the required alignment we pass is the alignment of the struct as a whole (which takes into account any pragma pack directives). In reality it would probably be more robust to figure out what the alignment is based on what is the offset at which clang wants to put the struct field. This should work and only make very minimal assumptions.

I also wonder if we should do that as part of this PR, or as a separate PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready Pull request is ready to be integrated rfr Pull request is ready for review

Development

Successfully merging this pull request may close these issues.

3 participants