Skip to content
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

The cfunction 1.0 deserves #26486

Merged
merged 2 commits into from
Apr 5, 2018
Merged

The cfunction 1.0 deserves #26486

merged 2 commits into from
Apr 5, 2018

Conversation

vtjnash
Copy link
Member

@vtjnash vtjnash commented Mar 16, 2018

This PR turns cfunction into a macro, and has it work more similarly to ccall (no runtime compiler required, defined by the containing method parameters, supports any type object including closures). In most cases, v0.6 code should just need to add an @. But it also has new advanced capabilities to efficiently create gc-managed runtime closures pointers! This will fix #12256, fix #26070 and close #1096 (I feel slightly cheated to not get to close a 3-digit issue on this one 😜).

Implements https://github.com/vtjnash/julep/wiki/0004:-Static-compilation-for-cfunction

The best place to see the net effect of this change is perhaps the stdlib, but also the docs (where I have an example of qsort), or the Julep (which has a more complete exploration of qsort usages), or lastly perhaps the tests (which try to have examples of weird uses of almost everything).

Syntax questions:

  1. Creating a closure over the value of a local variable requires annotating with $: i.e. ptr = @cfunction $cl RT (A, B). However, I've also considered instead using local for this purpose instead: ptr = @cfunction local cl RT (A, B). Thoughts on which one reads better? As can be seen from there being zero usages in this repo (outside of tests), this feature isn't a particularly common need / the other form is more preferred and easier to use (when possible), so I don't really care either way.

  2. Should I continue to use basically the same syntax as before (i.e. just add @), or use the following alternate syntax rearrangement, or just try to provide both: ptr = @cfunction cl(A, B)::RT (which, for the variant that creates a closure over the value of the local variable cl, would be ptr = @cfunction $cl(A, B)::RT or ptr = @cfunction local cl(A, B)::RT)

# NOTE: don't use @check/GitError directly, because the code can be arbitrary
payload = Any[ tree, f ]
cbf = @cfunction(function treewalk_callback(root_cstr::Cstring, entry_ptr::Ptr{Cvoid}, payload::Vector{Any})::Cint
Copy link
Member

Choose a reason for hiding this comment

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

Isn't it possible to capture tree and f instead of using the payload mechanism?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, it's just slower that way than to just use the C API correctly

Copy link
Member

Choose a reason for hiding this comment

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

I presume it's slower because closure support requires a stub that sets the locals and jumps to the "real" function? Whereas without any closed-over variables, it can just call directly.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, and in addition to the indirect function call, it also requires allocation to be able to track the lifetime, and runtime work to manage and handle that, etc.

@thofma thofma mentioned this pull request Mar 16, 2018
2 tasks
base/c.jl Outdated
@cfunction(callable, ReturnType, (ArgumentTypes...,)) -> Ptr{Cvoid}
@cfunction(\$callable, ReturnType, (ArgumentTypes...,)) -> CFunction

Generate a C-callable function pointer from the Julia function `f`
Copy link
Member

Choose a reason for hiding this comment

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

There is no f :)

base/exports.jl Outdated
@@ -925,6 +925,7 @@ export

# C interface
cfunction,
Copy link
Member

Choose a reason for hiding this comment

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

Remove this?

## Closure cfunctions

The first argument to [`@cfunction`](@ref) can be marked with a `$`, in which case
the return value will instead be a `struct CFunction` which closes over the argument.
Copy link
Member

Choose a reason for hiding this comment

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

Out of curiosity: what happens if I don't use a $, but I give a global name that's bound to a non-singleton function object?

Copy link
Member Author

Choose a reason for hiding this comment

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

Like ccall, it's resolved by the macro to close over the value (not the binding), since that's almost always what you want anyways, and makes it easier to avoid accidentally shooting your self in the (gc) foot.

@JeffBezanson
Copy link
Member

I think the $ syntax is good. It looks like the first argument can also be a function expression, which also gets $ treatment?

@vtjnash
Copy link
Member Author

vtjnash commented Mar 16, 2018

which also gets $ treatment?

Not really. Because I felt that it read nicely to keep the function definition and use together this way, I'm (ab)using the knowledge that it got evaluated in global scope early, and then treated as a constant. In either case, it can be an arbitrary expression, the $ keyword just indicates when to evaluate it.

@stevengj
Copy link
Member

+1 for ptr = @cfunction $cl(A, B)::RT

@@ -182,26 +185,26 @@ an integer less/greater than zero if `a` should appear before/after `b` (or zero
is permitted). Now, suppose that we have a 1d array `A` of values in Julia that we want to sort
using the `qsort` function (rather than Julia's built-in `sort` function). Before we worry about
calling `qsort` and passing arguments, we need to write a comparison function that works for some
arbitrary type T:
arbitrary type T (which defines `<`):
Copy link
Member

Choose a reason for hiding this comment

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

T isn't used in the example anymore.

end
mycompare (generic function with 1 method)
```

Notice that we have to be careful about the return type: `qsort` expects a function returning
a C `int`, so we must be sure to return `Cint` via a call to `convert` and a `typeassert`.
a C `int`, so we annotate the return type of the function be sure it returns a `Cint`.
Copy link
Member

Choose a reason for hiding this comment

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

Missing "to"?

@tknopp
Copy link
Contributor

tknopp commented Mar 17, 2018

@vtjnash: Any chance that this will fix https://discourse.julialang.org/t/performance-issue-with-gtk-jl/6171. That inference on callbacks requires up to minutes is a major issue when using Gtk currently.

@vtjnash vtjnash force-pushed the jn/cfunction-closure branch 2 times, most recently from 4380acd to dffb8cc Compare March 28, 2018 18:20
macro head uses this to resolve symbols,
which we want to allow even in pure context (generated functions)
Provide static support for handling dynamic calls and closures
@vtjnash vtjnash merged commit 28e7956 into master Apr 5, 2018
@vtjnash vtjnash deleted the jn/cfunction-closure branch April 5, 2018 16:20
@giordano
Copy link
Contributor

giordano commented Jun 1, 2018

Did I miss it or this change isn't mentioned in NEWS.md?

@fredrikekre fredrikekre added the needs news A NEWS entry is required for this change label Jun 1, 2018
f::Any
_1::Ptr{Cvoid}
_2::Ptr{Cvoid}
let construtor = false end
Copy link
Member

Choose a reason for hiding this comment

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

s/construtor/constructor/

(not that it matters, since this variable is not visible) … a comment explaining the purpose of this line would be helpful

Copy link
Member Author

Choose a reason for hiding this comment

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

It disables creation of the default constructors (ensuring that this type is not accidentally constructible from user code).

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.

Error when calling cfunction on a constructor precompiling cfunction calls julia functions as C callbacks
9 participants