-
-
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
Specialized codegen for opaque closure calls #49337
Conversation
Nice improvements! julia> cls = x::Float64 -> x + 1.0
#3 (generic function with 1 method)
julia> opc = Base.Experimental.@opaque x::Float64 -> x + 1.0
(::Float64)::Any->◌
julia> callcls(cls, x) = cls(x)
callcls (generic function with 1 method)
julia> callcls(cls, 1.0)
2.0
julia> callcls(opc, 1.0)
[15898] signal (11.2): Segmentation fault: 11
in expression starting at REPL[5]:1
unknown function (ip: 0x0)
Allocations: 636559 (Pool: 635390; Big: 1169); GC: 1
[1] 15898 segmentation fault ./usr/bin/julia |
I can reproduce aviatesk's report above: julia> using Base.Experimental: @opaque
julia> fo = @opaque (x::Float64)->(x+1.0)
(::Float64)::Any->◌
julia> f = (x::Float64)->(x+1.0)
#4 (generic function with 1 method)
julia> g(f,x) = f(x)
g (generic function with 1 method)
julia> g(f, 5.0)
6.0
julia> g(fo, 5.0)
[13851] signal (11.1): Segmentation fault
in expression starting at REPL[6]:1
unknown function (ip: (nil))
Allocations: 2000414 (Pool: 1998635; Big: 1779); GC: 4
segmentation fault (core dumped) OpaqueClosures have long been touted as a potential FunctionWrappers replacement.
EDIT: julia> f()
(::Float64)::Float64->◌ so, when |
I did say
Currently, the macro infers the return type, but the underlying mechanism has the capability to assert it. We could add a function that restricts the return, but for the time being, if you need to assert it, I would use a return type declaration (which converts rather than asserts, but should be good enough). |
Updated. I think this is complete now. Could maybe use another test or two for the tricky cases, but I think the implementation is pretty much there. |
|
@@ -235,6 +235,8 @@ typedef jl_value_t *(*jl_fptr_sparam_t)(jl_value_t*, jl_value_t**, uint32_t, jl_ | |||
extern jl_call_t jl_fptr_interpret_call; | |||
JL_DLLEXPORT extern jl_callptr_t jl_fptr_interpret_call_addr; | |||
|
|||
JL_DLLEXPORT extern jl_callptr_t jl_f_opaque_closure_call_addr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need support in all of the other places we declare "custom" calling conventions? (e.g. jl_invoke_api, staticdata.c) It is probably quite awkward currently to add new ones unfortunately.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure. The only place this goes is the codeinstance invoke in the builtin mt, but that already has a codeinstance in it with this exact same ->invoke, so how does that work now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that does seem unclear. The magic strings in decls.functionObject
do have weird handling in various places though, so I don't know if this needs it too.
oc->invoke = invoke; | ||
oc->specptr = specptr; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to assume that specptr
is always specsig
, but sometimes codegen may put a different token into invoke
, in which case the object it stored into specptr
might not be the requisite object. It seems like we might need 2 fields: one for the closure data given by invoke
, and one for the guaranteed specsig (assuming specsig is valid), which might have been allocated by emit_cfunc_invalidate
to be a reverse-trampoline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code above is supposed to check for those special cases and rewrite them into something that works for OC. This does not have the exact same semantics as the corresponding codeinstance fields.
Benchmark: ``` using Base.Experimental: @opaque f() = @opaque (x::Float64)->x+1.0 vec = [f() for i = 1:10_000]; g((x,f),) = f(Float64(x)) ``` Before: ``` julia> @time mapreduce(g, +, enumerate(vec)) 0.001928 seconds (30.00 k allocations: 781.297 KiB) 5.0015e7 ``` After: ``` julia> @time mapreduce(g, +, enumerate(vec)) 0.000085 seconds (3 allocations: 48 bytes) 5.0015e7 ```
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
Co-authored-by: Jameson Nash <vtjnash@gmail.com>
This commit message seems to be completely wrong after the squash merge. Please try to be accurate, as otherwise bisecting and backporting and blaming become confusing to discuss why we are working with a commit described as "WIP codegen for opaque closure calls", since WIP should not be on master. |
What's the final performance here vs FunctionWrappers? |
I'm seeing ~15% faster (assuming this is a valid benchmark)
|
Benchmark:
Before:
After: