Skip to content

Conversation

@nizarbenalla
Copy link
Member

@nizarbenalla nizarbenalla commented Jul 23, 2025

Please review this patch to add a new option where you can generate an direct invoker methods. This feature is intended to be used on structs that are just a collection of functions we had like to call.

If this feature is used, we remove the getter to avoid name collisions as we assume this is similar to an interface.

Changes to GUIDE.md have been intentionally left out from this initial patch to make reviews easier.

struct Foo {
    struct Bar (*a)(void);
    struct Bar (*b)(int);
};

Before

var foo = alloc_callback_h.foo();

var barA = Foo.a.invoke(Foo.a(foo), arena);
var barB = Foo.b.invoke(Foo.b(foo), arena, 100);

After

var foo = alloc_callback_h.foo();

var barA = Foo.a(foo, arena);
var barB = Foo.b(foo, arena, 100);

Progress

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

Issue

  • CODETOOLS-7903947: Access to function pointers in structs could be streamlined (Enhancement - P4)

Reviewing

Using git

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

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

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 287

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

Using diff file

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

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Jul 23, 2025

👋 Welcome back nbenalla! 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 Jul 23, 2025

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

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

7903947: Access to function pointers  in structs could be streamlined

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 no new commits pushed to the master branch. If another commit should be pushed before you perform the /integrate command, your PR will be automatically rebased. If you prefer to avoid any potential 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).

@openjdk openjdk bot changed the title 7903947: Access to function pointers in structs could be streamlined 7903947: Access to function pointers in structs could be streamlined Jul 23, 2025
@openjdk openjdk bot added ready Pull request is ready to be integrated rfr Pull request is ready for review labels Jul 23, 2025
@mlbridge
Copy link

mlbridge bot commented Jul 23, 2025

Webrevs

@mcimadamore
Copy link
Contributor

Just to recap solution we have explored previously.

It would be nice if, in principle, just doing this:

var bar = Foo.a(foo, <args>);

worked, and it invoked the correct function pointer on the foo struct. But this solution can lead to clashes. Specifically, if the function pointer takes no arguments, then Foo.a(foo) would look exactly like a struct getter.

Another solution would be to tweak a::invoke method to work on a foo instance directly, and not on the address that function takes. But this breaks composition -- if you first obtain the function pointer (a MemorySegment) stored at Foo::a, and then want to invoke it at a later point, how would you do it? If invoke takes a segment for Foo you can't really -- you always need to first store the address in the Foo struct, and then call the desired function pointer using a::invoke. Which seems awkward.

(note also that we cannot "just" add an overload of a::invoke that takes a Foo, because that is a memory segment too, so we get another clash).

In other words, there's no "easy" solution here. One possibility might be to add an invoker accessor to Foo -- something like Foo.a$invoke(<args>). Since this uses a different name there would be no clash with the getter. This leaves the problem that, in structs that contain only function pointers, the getters are probably not very useful, but maybe we can add logic to filter those out.

The other solution is the one explored here: the developer knows this isn't just any struct, but a "functional" struct. It tells jextract, and jextract reacts by generating slightly different code. Perhaps one issue with this approach (although we arrived at it honestly) is that if one stares at code like:

var barA = Foo.a(<args>);

It is pretty difficult to tell whether that's a getter/setter or an "invoker".

/**
* Generates exactly one invoker, inlining the struct.get(...) to avoid name collision.
*/
private void emitFunctionalConvenience(String invokerName,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd call this emitInvoker

} else if (Utils.isPointer(type) || Utils.isPrimitive(type)) {
boolean isFuncPtr = functionalDispatch && Utils.isFunctionPointer(type);
if (isFuncPtr) {
emitFieldSetter(javaName, varTree, layoutField, offsetField);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we even want the setter -- the fields in these structs are typically populated by a library

}
}

if (kind == IncludeKind.STRUCT || kind == IncludeKind.UNION) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I get this logic -- it seems like we're always setting a property P for a typedef with name N whenever we see it set for a struct N? That seems odd.

But in example as the one you added in the test are a bit ambiguous, as the same declaration induces both an anonymous struct decl and a typedef, so it is less clear which is which. If I write:

typedef struct Foo {
int a;
};

It seems to be that jextract only retains a "struct" tree, and throws away the typedef. In fact I was surprised that your test worked at all, as in the above case dump-includes would include something like this:

--include-struct Foo   # header: ....

And not:

--include-typedef Foo   # header: ....

So adding a property on the typedef should have no effect?

@mlbridge
Copy link

mlbridge bot commented Jul 24, 2025

Mailing list message from Duncan Gittins on jextract-dev:

This proposal makes it nicer to handle invokers. I generated my code with
this change but as you'vr noted it does indeed lead to clashes using the
proposed naming convention.
My Windows COM apps run fine once I'd commented out a few of the clashing
setters that I don't use, such as:

// public static void Release(MemorySegment struct, MemorySegment
fieldValue) {
// struct.set(Release$LAYOUT, Release$OFFSET, fieldValue);
// }
public static int Release(MemorySegment struct, MemorySegment _x0) {
MemorySegment fp = struct.get(Release$LAYOUT, Release$OFFSET);
return Release.invoke(fp, _x0);
}

Will the use of ",functional" be the intended way to trigger this
enhancement? I wonder whether last error capture could be built in with
"--include-function funcname,erro" or "--include-function
funcname,GetLastError"

Kind regards

Duncan

On Thu, 24 Jul 2025 at 10:29, Maurizio Cimadamore <mcimadamore at openjdk.org>
wrote:

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/jextract-dev/attachments/20250724/72274e5f/attachment.htm>

@mcimadamore
Copy link
Contributor

Will the use of ",functional" be the intended way to trigger this
enhancement? I wonder whether last error capture could be built in with
"--include-function funcname,erro" or "--include-function
funcname,GetLastError"

Nice connection -- we do in fact have plans (at least in theory) to use this same mechanism for errno -- and maybe even to declare "disjoint enums" (e.g. enums that can be modelled as plain Java enums because you swear not to rely on low-level bitmask operation -- which is often the case if the enum you are modelling is just a "tag")

@mcimadamore
Copy link
Contributor

This proposal makes it nicer to handle invokers. I generated my code with
this change but as you'vr noted it does indeed lead to clashes using the
proposed naming convention.

I think the issue is that the changes in this PR generate both setter and invoker -- and we should only emit the invoker.

@mlbridge
Copy link

mlbridge bot commented Jul 25, 2025

Mailing list message from Duncan Gittins on jextract-dev:

Now I think about it, renaming the invoker may be necessary after all.
The setter would be useful for applications to fill struct with Java
upcalls to implement callbacks. eg implementing COM api, rather than using
one.

Duncan

On Thu, 24 Jul 2025 at 16:05, Maurizio Cimadamore <mcimadamore at openjdk.org>
wrote:

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/jextract-dev/attachments/20250725/6cb6c395/attachment-0001.htm>

@mcimadamore
Copy link
Contributor

Now I think about it, renaming the invoker may be necessary after all.
The setter would be useful for applications to fill struct with Java
upcalls to implement callbacks. eg implementing COM api, rather than using
one.

Thanks, this is an useful data point. If it turns out that our assumption that "these structs only need invokers" is not 100% true, that doesn't leave us in a good place. While you could refrain from using the new flag on your struct, it feels like a "glass half empty" situation, because the code jextract would be able to generate is almost there.

Which brings us back to another option: maybe the more honest option is to indeed add an invoker-like accessor for each function pointer field (e.g. Foo.m$invoke). The dollar in the same sucks a bit but, oh well, at least it's clear.

Then, let's approach this from the other angle, and give developer more options to NOT generate stuff they don't want. E.g. maybe this struct is read-only and doesn't need setters? Or maybe it's invoke-only and doesn't need neither getters nor setters? Whatever the needs, and whatever the reason, we'd have a way to generate, more or less, only the stuff one would be interested in.

So, maybe, instead of "functional struct", maybe some way to say what you want in a struct -- similar to Linux filesystem rwx (read/write/execute) :-)

@nizarbenalla
Copy link
Member Author

I kept trying to avoid renaming the invoker as I didn't like having the dollar sign or a prefix in the invoker, but I like the idea of having readonly/write/execute mode in the config.

@mcimadamore
Copy link
Contributor

I kept trying to avoid renaming the invoker as I didn't like having the dollar sign or a prefix in the invoker, but I like the idea of having readonly/write/execute mode in the config.

I feel your pain. We defo went thorugh a lot of pain to remove dollars from generated code:

https://cr.openjdk.org/~mcimadamore/panama/jextract_changes.html

So, it will be a bit sad to see them coming back. Also, it will make some code more verbose compared to their C counterpart, as the C syntax can distinguish between application (foo.x()) and access (foo.x = ...).

@mcimadamore
Copy link
Contributor

I kept trying to avoid renaming the invoker as I didn't like having the dollar sign or a prefix in the invoker, but I like the idea of having readonly/write/execute mode in the config.

I feel your pain. We defo went thorugh a lot of pain to remove dollars from generated code:

https://cr.openjdk.org/~mcimadamore/panama/jextract_changes.html

So, it will be a bit sad to see them coming back. Also, it will make some code more verbose compared to their C counterpart, as the C syntax can distinguish between application (foo.x()) and access (foo.x = ...).

Maybe worth thinking some more about what our options are...

@bridgekeeper
Copy link

bridgekeeper bot commented Aug 25, 2025

@nizarbenalla This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@nizarbenalla
Copy link
Member Author

Keep Alive

@bridgekeeper
Copy link

bridgekeeper bot commented Oct 7, 2025

@nizarbenalla This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply issue a /touch or /keepalive command to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

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.

2 participants