-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ccallmacro #32748
Ccallmacro #32748
Conversation
This is a beautiful first PR! Bravo. A couple of suggestions. It would be good to get some tests for the error cases too, to verify that they error and that they do so in the expected fashion. There are some error cases that should probably be explicitly handled rather than failing in the internals of the julia> @ccall printf("%d\n"::Cstring, 1)::Cvoid;
ERROR: LoadError: type Int64 has no field head |
Is there room to generalize this for calls into other statically typed languages? |
Let's discuss that in a separate issue rather than muddying the waters in this PR. This does a fairly straightforward, well-defined thing: it adds a more intuitive syntax for the existing |
I'm not the expert here, but The difficulty is that most static languages require some extra effort create these dynamic libraries; i.e. converting their own internal types to C-types and providing a flat interface that doesn't depend on the language's own notions of objects, generics, high-order functions, etc. (much like you have to wrap Julia functions with An interesting thing to think about would be how to construct a flat API to do this--but that's exactly what it would have to be: an API. It would be the responsibility of each language backend to implement that API. Very likely, such an API would rely heavily on Julia's C interface internally, but as Stefan says, it's pretty well outside the scope of this PR... ... But it's an interesting idea. |
The semicolon is perhaps the simplest option, but I think the closest semantically would be something like:
|
I think that @ccall printf("%f %d"::Cstring; 1.5::Cdouble, 123::Cint)::Cint So everything after the semicolon is passed using varargs calling convention. Does that seem feasible, @vtjnash? I have to say, I'm a bit confused about how this works and don't really understand the varargs calling convention and its limitations. |
Could you have a dict to translate |
@simonbyrne It wouldn't be my preference because, while it does resemble C and Julia syntax around varargs, it's doing something subtly different. I'd prefer something that's less susceptible to misinterpretation by someone learning the API (varargs are strictly not splat-able). Still, I like how this looks and wouldn't be too bothered if the consensus went that way. It would require rebasing the macro on @StefanKarpinski the syntax you're suggesting is exactly what @vtjnash suggested to me at JuliaCon, and it's definitely possible with |
I don't understand what you mean. What purpose would that serve? |
So that you could write |
It would be very confusing to have |
It's definitely possible, and it might even be desirable to have a 3rd-party macro that could do something like |
I don't quite get why |
The documented convention is this: ccall(:printf, Cint, (Cstring, Cint...), "%d, %d, %d\n", 1, 2, 3) In fact, the documentation explicitly says it is not possible to have different types. https://docs.julialang.org/en/v1.3-dev/manual/calling-c-and-fortran-code/#man-bits-types-1 (scroll down a bit to the bottom of the first table there.) There's no reason why it couldn't be done, since foreigncall does it, it's just not supported for ccall. |
It seems to me like a good approach then would be to use the more general syntax and require type annotations on all the varargs arguments but throw an error if they're not all the same, saying "varargs |
c.f. julia> f() = ccall(:printf, Cint, (Cstring, Cint...), "%d, %d, %d\n", 1, 2, 3)
f (generic function with 1 method)
julia> @code_lowered f()
CodeInfo(
1 ─ %1 = (Base.cconvert)(Main.Cstring, "%d, %d, %d\n")
│ %2 = (Base.cconvert)(Main.Cint, 1)
│ %3 = (Base.cconvert)(Main.Cint, 2)
│ %4 = (Base.cconvert)(Main.Cint, 3)
│ %5 = (Base.unsafe_convert)(Main.Cstring, %1)
│ %6 = (Base.unsafe_convert)(Main.Cint, %2)
│ %7 = (Base.unsafe_convert)(Main.Cint, %3)
│ %8 = (Base.unsafe_convert)(Main.Cint, %4)
│ %9 = $(Expr(:foreigncall, :(:printf), Int32, svec(Cstring, Vararg{Int32,N} where N), :(:ccall), 4, :(%5), :(%6), :(%7), :(%8), :(%4), :(%3), :(%2), :(%1)))
└── return %9
) |
I guess the issue may be that we just lack a good |
In January, @vtjnash said foreigncall didn't support it, so this may be a relatively recent change.
Something like that. It depends on your C compiler or platform or something. I'll implement what you suggest; i.e. using the semicolon to delimit varargs and throwing an error if they aren't all of the same type. |
Actually, the more I look at this output from the lowering of It's possible that I misunderstood Jameson and he was just telling me to re-implement |
What's the status of this PR? Is there an alternative way to call C variadic functions in Julia even without this PR? #32800 was merged 5 months ago, but we still don't have an easy-to-use syntax to use this feature. |
I'm in favor of brushing this off and merging it. A conclusive decision is needed. |
I was thinking about this the other day. |
Agreed, I was thinking just last week that this had been forgotten. Aaron put a lot of work into the implementation; I've reviewed in detail and I'm happy with it, as well as the API which I think has had a fair amount of discussion. The only thing remaining is to make a decision that this is the right future API for
|
Test failures seem due to an unrelated change. Perhaps you could squash all these commits @ninjaaron? I think this is self contained enough that it makes sense to land as a single commit. |
8a8eb9c
to
d185966
Compare
Er... That didn't go right. I'm not an experienced squasher. This may take a minute. |
d185966
to
a075410
Compare
a075410
to
a0b7b60
Compare
I think I finally got it right, but the process seemed a bit weird. I did this: $ git pull https://github.com/JuliaLang/julia master
$ git reset --soft 9d4e8ae013f05269750035e9483d17fb02a34791 # commit before the first ccall macro commit
$ git commit -am "some message"
$ git pull https://github.com/JuliaLang/julia master
# ... fix some merge conflicts ...
# ... run the tests ...
$ git commit -am "some other message"
$ git push origin +ccallmacro Is that kind of right? |
There's lots of ways to squash. My two favorites:
|
From triage: |
I can implement the second thing you mention there for varargs of the same type fairly easily, but I guess we'd have to start the review process over again. |
Imo let's get this in first |
Yes, I was just recording the conversation, I definitely don't think we need to add more features now. |
Thanks for recording the triage discussion, It's very appreciated for those of us not on the call. I believed triage approved of this, so I've done a final in-depth review and I still like what I see (keeping a couple of tiny nits to myself ;-) ). |
Here we implement a syntax for ccall with Julia-like type annotations on the arguments. Compared to ccall: * The new syntax is more familiar to Julia programmers * The new syntax gives a notation for calling C varargs functions * Support for specifying calling convention is not yet implemented as that will require another syntax discussion * The semantics for interpolating "function like things" is much simplified (only function pointers are allowed)
Here we implement a syntax for ccall with Julia-like type annotations on the arguments. Compared to ccall: * The new syntax is more familiar to Julia programmers * The new syntax gives a notation for calling C varargs functions * Support for specifying calling convention is not yet implemented as that will require another syntax discussion * The semantics for interpolating "function like things" is much simplified (only function pointers are allowed)
I wrote a macro quite a while back for wrapping
ccall
in more "julian" syntax and posted it in this thread on discourse.People liked it and there was some suggestion it should be in Base, and @StefanKarpinski chimed in that he'd proposed exactly this macro in the past, but never got around to implementing it. He suggested I should create a package first and do some testing before making a PR. I did that, but didn't do more with it for a while. Anyway, I talked to Stefan and some others about it more at JuliaCon last week and got it ready (or, so I believe) to submit to the project.
I put implementation details inside of a module to avoid namespace pollution on some very generically named functions. I don't see this pattern used much elsewhere for small things like this, but it seemed like the cleanest way to do it, so I did it.
At JuliaCon, @vtjnash suggested a much superior way to implement varargs than what I have done using
foreigncall
. However,foreigncall
isn't well documented (that I've found) and I wasn't really able to get a handle on it from the source code (it's implemented in Scheme), and I also wasn't sure off all the additional lowering that would need to be done between the@ccall
macro andforiegncall
, so I've simply provided the same semanticsccall
provides for varargs, which unfortunately means all varargs must be of the same type. For the record (and perhaps for future development), this was Jameson's suggested interface:(mind the semicolon)
The interface I currently provide is this:
This is obviously not as good, but it's all I can do with the
ccall
interface, as far as I know. Of course, the two syntactic forms could both be supported by the same macro, but I'm not the person to be implementing that at my current level of Julia ability (well below 9000). I either need some guidance from someone who knowsforeigncall
(preferably in the form of public documentation) or this functionality needs to be added toccall
and documented--or someone who knows more than I do could just do it, but I guess everyone who knows howforeigncall
works has other things to occupy their time.