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

WIP: Add ability to automatically box scalars in wrapped API with special case argument type #760

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gen/api_defs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@
@bind h5t_commit(loc_id::hid_t, name::Ptr{UInt8}, dtype_id::hid_t, lcpl_id::hid_t, tcpl_id::hid_t, tapl_id::hid_t)::herr_t "Error committing type"
@bind h5t_copy(dtype_id::hid_t)::hid_t "Error copying datatype"
@bind h5t_create(class_id::Cint, sz::Csize_t)::hid_t error("Error creating datatype of id ", class_id)
@bind h5t_enum_insert(dtype_id::hid_t, name::Cstring, value::Ptr{Cvoid})::herr_t error("Error adding ", name, " to enum datatype")
@bind h5t_enum_insert(dtype_id::hid_t, name::Cstring, value::Ref{Cvoid})::herr_t error("Error adding ", name, " to enum datatype")
@bind h5t_equal(dtype_id1::hid_t, dtype_id2::hid_t)::htri_t "Error checking datatype equality"
@bind h5t_get_array_dims(dtype_id::hid_t, dims::Ptr{hsize_t})::Cint "Error getting dimensions of array"
@bind h5t_get_array_ndims(dtype_id::hid_t)::Cint "Error getting ndims of array"
Expand Down
62 changes: 52 additions & 10 deletions gen/bind_generator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,31 @@ expression. If not provided, no error check is done. If it is a `String`, the st
as the message in an `error()` call, otherwise the expression is used as-is. Note that the
expression may refer to any arguments by name.

## Special behaviors

**Library names:**

It is assumed that the HDF library names are given in global constants named `libhdf5`
and `libhdf5_hl`. The former is used for all `ccall`s, except if the C library name begins
with one of "H5DO", "H5DS", "H5L5", or "H5TB" then the latter library is used.

**Argument type:**

All arguments are automatically converted to the declared type following Julia's standard
[Calling C and Fortran Code](https://docs.julialang.org/en/v1/manual/calling-c-and-fortran-code/)
rules, _except_ in the special case where the argument type is declared as `::Ref{Cvoid}`.
In this exceptional case, the argument `arg::Ref{Cvoid}` is transformed following the rule
```julia
c_arg = arg isa Ref{<:Any} ? arg : Base.cconvert(Ref{eltype(arg)}, arg)
```
and the `ccall` is expanded as if the original argument had been declared as
`c_arg::Ptr{Cvoid}`.
This conversion rule allows for automatically boxing scalars while passing through
explicit `Ref` values.
(`Array` arguments are wrapped with a benign `RefArray`.)

**Return types:**

The declared return type in the function-like signature must be the return type of the C
function, and the Julia return type is inferred as one of the following possibilities:

Expand All @@ -58,7 +83,9 @@ function, and the Julia return type is inferred as one of the following possibil

3. Otherwise, the C function return value is returned from the Julia function.

Furthermore, the C return value is interpreted to automatically generate error checks
**Error checking:**

The C return value is also interpreted to automatically generate error checks
(only when `ErrorStringOrExpression` is provided):

1. If `ReturnType === :herr_t` or `ReturnType === :htri_t`, an error is raised when the return
Expand All @@ -71,10 +98,6 @@ Furthermore, the C return value is interpreted to automatically generate error c
equal to `C_NULL`.

3. For all other return types, it is assumed a negative value indicates error.

It is assumed that the HDF library names are given in global constants named `libhdf5`
and `libhdf5_hl`. The former is used for all `ccall`s, except if the C library name begins
with "H5DO" or "H5TB" then the latter library is used.
"""
macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
sig.head === :(::) || error("return type required on function signature")
Expand All @@ -90,15 +113,30 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
funcargs = funcsig.args[2:end]

# Pull apart argument names and types
args = Vector{Symbol}()
argt = Vector{Union{Expr,Symbol}}()
args = Vector{Symbol}() # arguments in function signature
argv = Vector{Symbol}() # arguments passed in ccall
argt = Vector{Union{Expr,Symbol}}() # types of ccall arguments
argc = Vector{Expr}() # optional conversions taking args => argv
for ii in 1:length(funcargs)
argex = funcargs[ii]
if !isexpr(argex, :(::)) || !(argex.args[1] isa Symbol)
error("expected `name::type` expression in argument ", ii, ", got ", funcargs[ii])
end
push!(args, argex.args[1])
push!(argt, argex.args[2])
if isexpr(argex.args[2], :curly) && argex.args[2] == :(Ref{Cvoid})
# Special case: maybe box the argument in Ref
arg = argex.args[1]::Symbol
sym = Symbol("#", arg, "#")
push!(argc, :($sym = $arg isa Ref{<:Any} ? $arg : $(GlobalRef(Base, :cconvert))(Ref{eltype($arg)}, $arg)))
push!(argv, sym)
push!(argt, :(Ptr{Cvoid}))
# in-place modify funcargs so that the doc generation (`string(funcsig)` below)
# shows the C argument type.
funcargs[ii].args[2] = :(Ptr{Cvoid})
else
push!(argv, argex.args[1])
push!(argt, argex.args[2])
end
end

prefix, rest = split(string(jlfuncname), "_", limit = 2)
Expand All @@ -122,7 +160,7 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)

# The ccall(...) itself
cfunclib = Expr(:tuple, quot(cfuncname), lib)
ccallexpr = :(ccall($cfunclib, $rettype, ($(argt...),), $(args...)))
ccallexpr = :(ccall($cfunclib, $rettype, ($(argt...),), $(argv...)))

# The error condition expression
errexpr = err isa String ? :(error($err)) : err
Expand Down Expand Up @@ -157,7 +195,11 @@ macro bind(sig::Expr, err::Union{String,Expr,Nothing} = nothing)
# avoids inserting the line number nodes for the macro --- the call site
# is instead explicitly injected into the function body via __source__.
jlfuncsig = Expr(:call, jlfuncname, args...)
jlfuncbody = Expr(:block, __source__, :($statsym = $ccallexpr))
jlfuncbody = Expr(:block, __source__)
if !isempty(argc)
append!(jlfuncbody.args, argc)
end
push!(jlfuncbody.args, :($statsym = $ccallexpr))
if errexpr !== nothing
push!(jlfuncbody.args, errexpr)
end
Expand Down
7 changes: 6 additions & 1 deletion src/api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -885,7 +885,12 @@ function h5t_create(class_id, sz)
end

function h5t_enum_insert(dtype_id, name, value)
var"#status#" = ccall((:H5Tenum_insert, libhdf5), herr_t, (hid_t, Cstring, Ptr{Cvoid}), dtype_id, name, value)
var"#value#" = if value isa Ref{<:Any}
value
else
Base.cconvert(Ref{eltype(value)}, value)
end
var"#status#" = ccall((:H5Tenum_insert, libhdf5), herr_t, (hid_t, Cstring, Ptr{Cvoid}), dtype_id, name, var"#value#")
var"#status#" < 0 && error("Error adding ", name, " to enum datatype")
return nothing
end
Expand Down