Skip to content

purego: new mechanism for calling C functions - #49

Merged
TotallyGamerJet merged 31 commits into
ebitengine:mainfrom
TotallyGamerJet:func
Oct 19, 2022
Merged

TotallyGamerJet merged 31 commits into
ebitengine:mainfrom
TotallyGamerJet:func

Conversation

@TotallyGamerJet

@TotallyGamerJet TotallyGamerJet commented Oct 18, 2022 •

Copy link
Copy Markdown
Collaborator

This PR adds a new function purego.Func which takes a shared object handle, the name of a symbol in the object and a pointer to a function matching the calling convention. This is the start of a better way of calling C functions. First, it is type safe from the callers perspective. Secondly, it removes repeated code such as strings.CString and runtime.KeepAlive into one place (purego.Func). I ported the objective-c runtime to use this new mechanism to test it out and see how it looks in code. I think it's a big improvement.

The downside is that this is a slower method than calling purego.SyscallN directly but will eventually allow for more complex arguments and returns such as floats, complex, and structs.

We need to decide on if purego.Func should return an error?

I'm kind of leaning towards no since the only error you could potentially recover from is one where the symbol isn't found in the shared object but then what would you do? Stub the function? The other errors (fptr not being a function pointer, too many return arguments) just aren't recoverable in any meaningful way which is why I think it should panic.

Should the objc package use this method or call purego.SyscallN directly?

I think the later since this package gets called a LOT in code ported from Objective-C and we want it to be as fast as possible. The overhead of determining the types seems like a waste for these core functions.

TODO:
[X] Document the conversion from Go types to C types as part of purego.Func.
[X] Keep pointer types alive
[ ] Implement those other types.
[ ] Port oto/ebitengine to use this new method?

@hajimehoshi hajimehoshi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seems good.

Comment thread func_darwin.go Resolved
Comment thread func_darwin.go Resolved
Comment thread objc/objc_runtime_darwin.go Resolved
Comment thread func_darwin.go Resolved
Comment thread func_darwin.go Resolved
Comment thread objc/objc_runtime_darwin.go Resolved

@hajimehoshi hajimehoshi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Pretty nice. Are you working on the TODOs now?

@TotallyGamerJet

Copy link
Copy Markdown
Collaborator Author

Yes, I am!

Comment thread func_darwin.go Resolved
@TotallyGamerJet

TotallyGamerJet commented Oct 18, 2022 •

Copy link
Copy Markdown
Collaborator Author

I've ported Oto to use purego.FuncHandle. Do you want to wait until floats and struct support is added before merging this? I'm not sure when I'll be able to do that since I'm still working on writing the generator code for the glfw.dylib. It is also a decent amount of additional code since I have to write an architecture specific portion to fill the registers and stack properly. This is fully functional as it for all the types currently supported by SyscallN

Comment thread dlfcn.go Resolved
@hajimehoshi

hajimehoshi commented Oct 18, 2022 •

Copy link
Copy Markdown
Member

Do you want to wait until floats and struct support is added before merging this?
[...]
This is fully functional as it for all the types currently supported by SyscallN

I'm confused. Do you mean the current FuncHandle is strong enough to replace SyscallN even without floats and structs?

@TotallyGamerJet

Copy link
Copy Markdown
Collaborator Author

Yes it can replace all occurrences of SyscallN in Oto and Ebitengine in its current state

@hajimehoshi

Copy link
Copy Markdown
Member

So

Do you want to wait until floats and struct support is added before merging this?

I'm fine to merge this with leaving a comment about the TODOs!

Comment thread func_darwin.go Resolved
Comment thread func_darwin.go Resolved
Comment thread func_darwin.go Resolved
Comment thread func_darwin.go Resolved
Comment thread func_darwin.go Resolved
Comment thread func_darwin.go
// func => C function
// []T, unsafe.Pointer, *T => void*
//
// There is a special case when the last argument of fptr is a variadic interface (or []interface}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just out of curiosity, would it be hard to support other types than interface{}?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Do you have an example?

Also note that this does not support calling variadic functions like int printf(const char* format, ...); It's basically a cast to the desired C function. (*(char*, int)())(printf)("%d", 3) is not the same as printf("%d, 3) since the second uses va_args under the hood. The first is what is currently handled

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

For example: https://pkg.go.dev/reflect#Append (I don't think this can be a C function but this is an example with a non-interface{} type)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I now understand that the purpose of RegisterFunc is to have a Go function that is a counterpart of a C function. So, having another type than interface{} would not make sense.

By the way, in the comment, the order of types is (Go type) => (C type), but is there an order here?

@TotallyGamerJet TotallyGamerJet Oct 18, 2022 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes the Go function should have the same signature as the C function being called.

There is no order except for how I thought of the type

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK so I'm fine with =>. Still LGTM

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I was about to do the <=> for the types that can be returned. Do you think that's a good idea?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes that's still a good idea, but I thought you meant this was one direction. Am I misunderstanding?

I like that since some types like slice cannot be returned from C to Go since there is no way to get its length

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Oh my bad. I did not include a period after "I like that." I was stating why I thought it was a good idea to have bidirectional arrows. Unless you think it's better to have another column for C => Go conversions?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO I prefer <=> from the whole discussion. Let's go with <=> then :-)

Comment thread func_darwin.go Resolved

@hajimehoshi hajimehoshi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM, thanks!

@hajimehoshi

Copy link
Copy Markdown
Member

I'm gonna sleep so I might reply tomorrow. Thank you for your hard work!

@hajimehoshi hajimehoshi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Still LGTM. You can merge if you are ready.

@TotallyGamerJet
TotallyGamerJet merged commit 7128aea into ebitengine:main Oct 19, 2022
@TotallyGamerJet
TotallyGamerJet deleted the func branch October 19, 2022 15:39
hajimehoshi pushed a commit that referenced this pull request Aug 24, 2026
The RegisterFunc type table has func <=> C function in it, but that
only holds in the argument direction. Declare a C function as returning
a func, and calling it panics:

panic: reflect.MakeFunc: value of type *func(int32, int32) int32 is not
assignable to type func(int32, int32) int32

Registering is fine, it's the call that goes, and it goes for every
signature. Every other case in that return switch leaves the result as
the declared type. The reflect.Func case swapped in a *func, registered
into that, and handed the pointer back to MakeFunc, which wont take it.

If the C function returns NULL the inner RegisterFunc still panics
purego: cfn is nil. Left that alone, since whether it should hand you a
nil Go func instead looked like your call rather than a bug.

Test is a block in TestABI plus a return_func_ptr helper in abi_test.c,
and it fails on main with teh panic above. Been broken since #49 back
in 2022, which is probably why nobody's hit it: you'd normally take the
pointer back as a uintptr and register it youself, and nothing in the
repo declares a func return.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants