Skip to content

Commit

Permalink
cfunction macro: extend cfunction capabilities
Browse files Browse the repository at this point in the history
Provide static support for handling dynamic calls and closures
  • Loading branch information
vtjnash committed Mar 16, 2018
1 parent b2119da commit e0d3951
Show file tree
Hide file tree
Showing 34 changed files with 1,131 additions and 428 deletions.
44 changes: 38 additions & 6 deletions base/c.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,56 @@ respectively.
"""
cglobal

struct CFunction
ptr::Ptr{Cvoid}
f::Any
_1::Ptr{Cvoid}
_2::Ptr{Cvoid}
let construtor = false end
end
unsafe_convert(::Type{Ptr{Cvoid}}, cf::CFunction) = cf.ptr

"""
cfunction(f::Function, returntype::Type, argtypes::Type) -> Ptr{Cvoid}
@cfunction(callable, ReturnType, (ArgumentTypes...,)) -> Ptr{Cvoid}
@cfunction(\$callable, ReturnType, (ArgumentTypes...,)) -> CFunction
Generate a C-callable function pointer from the Julia function `f`
for the given type signature.
Note that the argument type tuple must be a literal tuple, and not a tuple-valued variable or expression
(although it can include a splat expression). And that these arguments will be evaluated in global scope
during compile-time (not deferred until runtime).
Adding a `\$` in front of the function argument changes this to instead create a runtime closure
over the local variable `callable`.
Generate C-callable function pointer from the Julia function `f`. Type annotation of the return
value in the callback function is a must for situations where Julia cannot infer the return
type automatically.
See [manual section on ccall and cfunction usage](@ref Calling-C-and-Fortran-Code).
# Examples
```julia-repl
julia> function foo(x::Int, y::Int)
return x + y
end
julia> cfunction(foo, Int, Tuple{Int,Int})
julia> @cfunction(foo, Int, (Int, Int))
Ptr{Cvoid} @0x000000001b82fcd0
```
"""
cfunction(f, r, a) = ccall(:jl_function_ptr, Ptr{Cvoid}, (Any, Any, Any), f, r, a)
macro cfunction(f, at, rt)
if !(isa(rt, Expr) && rt.head === :tuple)
throw(ArgumentError("@cfunction argument types must be a literal tuple"))
end
rt.head = :call
pushfirst!(rt.args, GlobalRef(Core, :svec))
if isa(f, Expr) && f.head === :$
fptr = f.args[1]
typ = CFunction
else
fptr = QuoteNode(f)
typ = Ptr{Cvoid}
end
cfun = Expr(:cfunction, typ, fptr, at, rt, QuoteNode(:ccall))
return esc(cfun)
end

if ccall(:jl_is_char_signed, Ref{Bool}, ())
const Cchar = Int8
Expand Down
90 changes: 56 additions & 34 deletions base/compiler/abstractinterpretation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -663,8 +663,8 @@ function abstract_call(@nospecialize(f), fargs::Union{Tuple{},Vector{Any}}, argt
return abstract_call_gf_by_type(f, argtypes, atype, sv)
end

function abstract_eval_call(e::Expr, vtypes::VarTable, sv::InferenceState)
argtypes = Any[abstract_eval(a, vtypes, sv) for a in e.args]
# wrapper around `abstract_call` for first computing if `f` is available
function abstract_eval_call(fargs::Union{Tuple{},Vector{Any}}, argtypes::Vector{Any}, vtypes::VarTable, sv::InferenceState)
#print("call ", e.args[1], argtypes, "\n\n")
for x in argtypes
x === Bottom && return Bottom
Expand All @@ -689,7 +689,53 @@ function abstract_eval_call(e::Expr, vtypes::VarTable, sv::InferenceState)
end
return abstract_call_gf_by_type(nothing, argtypes, argtypes_to_type(argtypes), sv)
end
return abstract_call(f, e.args, argtypes, vtypes, sv)
return abstract_call(f, fargs, argtypes, vtypes, sv)
end

function sp_type_rewrap(@nospecialize(T), linfo::MethodInstance, isreturn::Bool)
isref = false
if T === Bottom
return Bottom
elseif isa(T, Type)
if isa(T, DataType) && (T::DataType).name === _REF_NAME
isref = true
T = T.parameters[1]
if isreturn && T === Any
return Bottom # a return type of Ref{Any} is invalid
end
end
else
return Any
end
if isa(linfo.def, Method)
spsig = linfo.def.sig
if isa(spsig, UnionAll)
if !isempty(linfo.sparam_vals)
env = pointer_from_objref(linfo.sparam_vals) + sizeof(Ptr{Cvoid})
T = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), T, spsig, env)
isref && isreturn && T === Any && return Bottom # catch invalid return Ref{T} where T = Any
for v in linfo.sparam_vals
if isa(v, TypeVar)
T = UnionAll(v, T)
end
end
else
T = rewrap_unionall(T, spsig)
end
end
end
return T
end

function abstract_eval_cfunction(e::Expr, vtypes::VarTable, sv::InferenceState)
f = abstract_eval(e.args[2], vtypes, sv)
# rt = sp_type_rewrap(e.args[3], sv.linfo, true)
at = Any[ sp_type_rewrap(argt, sv.linfo, false) for argt in e.args[4]::SimpleVector ]
pushfirst!(at, f)
# this may be the wrong world for the call,
# but some of the result is likely to be valid anyways
# and that may help generate better codegen
abstract_eval_call((), at, vtypes, sv)
end

function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState)
Expand All @@ -710,7 +756,8 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState)
end
e = e::Expr
if e.head === :call
t = abstract_eval_call(e, vtypes, sv)
argtypes = Any[ abstract_eval(a, vtypes, sv) for a in e.args ]
t = abstract_eval_call(e.args, argtypes, vtypes, sv)
elseif e.head === :new
t = instanceof_tfunc(abstract_eval(e.args[1], vtypes, sv))[1]
for i = 2:length(e.args)
Expand All @@ -722,42 +769,17 @@ function abstract_eval(@nospecialize(e), vtypes::VarTable, sv::InferenceState)
abstract_eval(e.args[1], vtypes, sv)
t = Any
elseif e.head === :foreigncall
rt = e.args[2]
if isa(sv.linfo.def, Method)
spsig = sv.linfo.def.sig
if isa(spsig, UnionAll)
if !isempty(sv.linfo.sparam_vals)
env = pointer_from_objref(sv.linfo.sparam_vals) + sizeof(Ptr{Cvoid})
rt = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), e.args[2], spsig, env)
else
rt = rewrap_unionall(e.args[2], spsig)
end
end
end
abstract_eval(e.args[1], vtypes, sv)
t = sp_type_rewrap(e.args[2], sv.linfo, true)
for i = 3:length(e.args)
if abstract_eval(e.args[i], vtypes, sv) === Bottom
t = Bottom
end
end
if rt === Bottom
t = Bottom
elseif isa(rt, Type)
t = rt
if isa(t, DataType) && (t::DataType).name === _REF_NAME
t = t.parameters[1]
if t === Any
t = Bottom # a return type of Box{Any} is invalid
end
end
for v in sv.linfo.sparam_vals
if isa(v,TypeVar)
t = UnionAll(v, t)
end
end
else
t = Any
end
elseif e.head === :cfunction
t = e.args[1]
isa(t, Type) || (t = Any)
abstract_eval_cfunction(e, vtypes, sv)
elseif e.head === :static_parameter
n = e.args[1]
t = Any
Expand Down
15 changes: 12 additions & 3 deletions base/compiler/optimize.jl
Original file line number Diff line number Diff line change
Expand Up @@ -769,17 +769,26 @@ function substitute!(
head = e.head
if head === :static_parameter
return quoted(spvals[e.args[1]])
elseif head === :cfunction
@assert !isa(spsig, UnionAll) || !isempty(spvals)
if !(e.args[2] isa QuoteNode) # very common no-op
e.args[2] = substitute!(e.args[2], na, argexprs, spsig, spvals, offset, boundscheck)
end
e.args[3] = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), e.args[3], spsig, spvals)
e.args[4] = svec(Any[
ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), argt, spsig, spvals)
for argt
in e.args[4] ]...)
elseif head === :foreigncall
@assert !isa(spsig, UnionAll) || !isempty(spvals)
for i = 1:length(e.args)
if i == 2
e.args[2] = ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), e.args[2], spsig, spvals)
elseif i == 3
argtuple = Any[
e.args[3] = svec(Any[
ccall(:jl_instantiate_type_in_env, Any, (Any, Any, Ptr{Any}), argt, spsig, spvals)
for argt
in e.args[3] ]
e.args[3] = svec(argtuple...)
in e.args[3] ]...)
elseif i == 4
@assert isa((e.args[4]::QuoteNode).value, Symbol)
elseif i == 5
Expand Down
8 changes: 5 additions & 3 deletions base/compiler/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}(
:meta => 0:typemax(Int),
:global => 1:1,
:foreigncall => 3:typemax(Int),
:cfunction => 6:6,
:isdefined => 1:1,
:simdloop => 0:0,
:gc_preserve_begin => 0:typemax(Int),
Expand Down Expand Up @@ -139,8 +140,9 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_
end
validate_val!(x.args[1])
elseif head === :call || head === :invoke || head == :gc_preserve_end || head === :meta ||
head === :inbounds || head === :foreigncall || head === :const || head === :enter ||
head === :leave || head === :method || head === :global || head === :static_parameter ||
head === :inbounds || head === :foreigncall || head === :cfunction ||
head === :const || head === :enter || head === :leave ||
head === :method || head === :global || head === :static_parameter ||
head === :new || head === :thunk || head === :simdloop || head === :throw_undef_if_not
validate_val!(x)
else
Expand Down Expand Up @@ -221,7 +223,7 @@ end

function is_valid_rvalue(lhs, x)
is_valid_argument(x) && return true
if isa(x, Expr) && x.head in (:new, :the_exception, :isdefined, :call, :invoke, :foreigncall, :gc_preserve_begin)
if isa(x, Expr) && x.head in (:new, :the_exception, :isdefined, :call, :invoke, :foreigncall, :cfunction, :gc_preserve_begin)
return true
# TODO: disallow `globalref = call` when .typ field is removed
#return isa(lhs, SSAValue) || isa(lhs, Slot)
Expand Down
6 changes: 6 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,12 @@ end

# PR #23066
@deprecate cfunction(f, r, a::Tuple) cfunction(f, r, Tuple{a...})
@noinline function cfunction(f, r, a)
@nospecialize(f, r, a)
depwarn("The function `cfunction` is now written as a macro `@cfunction`.", :cfunction)
return ccall(:jl_function_ptr, Ptr{Cvoid}, (Any, Any, Any), f, r, a)
end
export cfunction

# PR 23341
@eval GMP @deprecate gmp_version() version() false
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ export

# C interface
cfunction,
@cfunction,
cglobal,
disable_sigint,
pointer,
Expand Down
19 changes: 13 additions & 6 deletions base/libuv.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,20 @@ function process_events(block::Bool)
end
end

function uv_alloc_buf end
function uv_readcb end
function uv_writecb_task end
function uv_return_spawn end
function uv_asynccb end
function uv_timercb end

function reinit_stdio()
global uv_jl_alloc_buf = cfunction(uv_alloc_buf, Cvoid, Tuple{Ptr{Cvoid}, Csize_t, Ptr{Cvoid}})
global uv_jl_readcb = cfunction(uv_readcb, Cvoid, Tuple{Ptr{Cvoid}, Cssize_t, Ptr{Cvoid}})
global uv_jl_writecb_task = cfunction(uv_writecb_task, Cvoid, Tuple{Ptr{Cvoid}, Cint})
global uv_jl_return_spawn = cfunction(uv_return_spawn, Cvoid, Tuple{Ptr{Cvoid}, Int64, Int32})
global uv_jl_asynccb = cfunction(uv_asynccb, Cvoid, Tuple{Ptr{Cvoid}})
global uv_jl_timercb = cfunction(uv_timercb, Cvoid, Tuple{Ptr{Cvoid}})
global uv_jl_alloc_buf = @cfunction(uv_alloc_buf, Cvoid, (Ptr{Cvoid}, Csize_t, Ptr{Cvoid}))
global uv_jl_readcb = @cfunction(uv_readcb, Cvoid, (Ptr{Cvoid}, Cssize_t, Ptr{Cvoid}))
global uv_jl_writecb_task = @cfunction(uv_writecb_task, Cvoid, (Ptr{Cvoid}, Cint))
global uv_jl_return_spawn = @cfunction(uv_return_spawn, Cvoid, (Ptr{Cvoid}, Int64, Int32))
global uv_jl_asynccb = @cfunction(uv_asynccb, Cvoid, (Ptr{Cvoid},))
global uv_jl_timercb = @cfunction(uv_timercb, Cvoid, (Ptr{Cvoid},))

global uv_eventloop = ccall(:jl_global_event_loop, Ptr{Cvoid}, ())
global stdin = init_stdio(ccall(:jl_stdin_stream, Ptr{Cvoid}, ()))
Expand Down
Loading

0 comments on commit e0d3951

Please sign in to comment.