Skip to content
Closed
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
9 changes: 6 additions & 3 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ os:
- linux
- osx
julia:
- 0.5
- 0.6
- 0.7
- 1.0
- nightly
matrix:
allow_failures:
- julia: nightly
after_success:
- julia -e 'cd(Pkg.dir("FunctionWrappers")); Pkg.add("Coverage"); using Coverage; Codecov.submit(process_folder())'
- julia --project -e 'import Pkg; Pkg.add("Coverage"); using Coverage; Codecov.submit(Codecov.process_folder())'
2 changes: 1 addition & 1 deletion REQUIRE
Original file line number Diff line number Diff line change
@@ -1 +1 @@
julia 0.5
julia 0.7-alpha
44 changes: 23 additions & 21 deletions appveyor.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
environment:
matrix:
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.5/julia-0.5-latest-win32.exe"
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.5/julia-0.5-latest-win64.exe"
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x86/0.6/julia-0.6-latest-win32.exe"
- JULIA_URL: "https://julialang-s3.julialang.org/bin/winnt/x64/0.6/julia-0.6-latest-win64.exe"
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x86/julia-latest-win32.exe"
- JULIA_URL: "https://julialangnightlies-s3.julialang.org/bin/winnt/x64/julia-latest-win64.exe"
- julia_version: 0.7
- julia_version: 1
- julia_version: nightly

platform:
- x86 # 32-bit
- x64 # 64-bit

matrix:
allow_failures:
- julia_version: latest

branches:
only:
- master
- /release-.*/

notifications:
- provider: Email
Expand All @@ -18,22 +24,18 @@ notifications:
on_build_status_changed: false

install:
- ps: "[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12"
# if there's a newer build queued for the same PR, cancel this one
- ps: if ($env:APPVEYOR_PULL_REQUEST_NUMBER -and $env:APPVEYOR_BUILD_NUMBER -ne ((Invoke-RestMethod `
https://ci.appveyor.com/api/projects/$env:APPVEYOR_ACCOUNT_NAME/$env:APPVEYOR_PROJECT_SLUG/history?recordsNumber=50).builds | `
Where-Object pullRequestId -eq $env:APPVEYOR_PULL_REQUEST_NUMBER)[0].buildNumber) { `
throw "There are newer queued builds for this pull request, failing early." }
# Download most recent Julia Windows binary
- ps: (new-object net.webclient).DownloadFile(
$env:JULIA_URL,
"C:\projects\julia-binary.exe")
# Run installer silently, output to C:\projects\julia
- C:\projects\julia-binary.exe /S /D=C:\projects\julia
- ps: iex ((new-object net.webclient).DownloadString("https://raw.githubusercontent.com/JuliaCI/Appveyor.jl/version-1/bin/install.ps1"))

build_script:
- C:\projects\julia\bin\julia -e "versioninfo();
Pkg.clone(pwd(), \"FunctionWrappers\"); Pkg.build(\"FunctionWrappers\")"
- echo "%JL_BUILD_SCRIPT%"
- C:\julia\bin\julia -e "%JL_BUILD_SCRIPT%"

test_script:
- C:\projects\julia\bin\julia --check-bounds=yes -e "Pkg.test(\"FunctionWrappers\")"
- echo "%JL_TEST_SCRIPT%"
- C:\julia\bin\julia -e "%JL_TEST_SCRIPT%"

# # Uncomment to support code coverage upload. Should only be enabled for packages
# # which would have coverage gaps without running on Windows
# on_success:
# - echo "%JL_CODECOV_SCRIPT%"
# - C:\julia\bin\julia -e "%JL_CODECOV_SCRIPT%"
104 changes: 51 additions & 53 deletions src/FunctionWrappers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,89 +11,87 @@ module FunctionWrappers
%v = trunc i8 %0 to i1
call void @llvm.assume(i1 %v)
ret void
"""), Void, Tuple{Bool}, v)
"""), Cvoid, Tuple{Bool}, v)
end

@static if isdefined(Base, Symbol("@nospecialize"))
is_singleton(@nospecialize(T)) = isdefined(T, :instance)
else
is_singleton(T::ANY) = isdefined(T, :instance)
end
is_singleton(@nospecialize(T)) = isdefined(T, :instance)

# Convert return type and generates cfunction signatures
Base.@pure map_rettype(T) =
(isbits(T) || T === Any || is_singleton(T)) ? T : Ref{T}
(isbitstype(T) || T === Any || is_singleton(T)) ? T : Ref{T}
Base.@pure function map_cfunc_argtype(T)
if is_singleton(T)
return Ref{T}
end
return (isbits(T) || T === Any) ? T : Ref{T}
return (isbitstype(T) || T === Any) ? T : Ref{T}
end
Base.@pure function map_argtype(T)
if is_singleton(T)
return Any
end
return (isbits(T) || T === Any) ? T : Any
return (isbitstype(T) || T === Any) ? T : Any
end
Base.@pure get_cfunc_argtype(Obj, Args) =
Tuple{Ref{Obj}, (map_cfunc_argtype(Arg) for Arg in Args.parameters)...}

# Call wrapper since `cfunction` does not support non-function
# or closures
if VERSION >= v"0.6.0"
# Can in princeple be lower but 0.6 doesn't warn on this so it doesn't matter
eval(parse("struct CallWrapper{Ret} <: Function end"))
else
include_string("immutable CallWrapper{Ret} <: Function end")

# callable that converts output of f to type Ret
struct CallWrapper{Ret, F}
f::F
end
(::CallWrapper{Ret}){Ret}(f, args...)::Ret = f(args...)

# Specialized wrapper for
CallWrapper{Ret}(f::F) where {Ret, F} = CallWrapper{Ret, F}(f)

(wrapper::CallWrapper{Ret})(args...) where {Ret} = convert(Ret, wrapper.f(args...))

for nargs in 0:128
@eval (::CallWrapper{Ret}){Ret}(f, $((Symbol("arg", i) for i in 1:nargs)...))::Ret =
f($((Symbol("arg", i) for i in 1:nargs)...))
@eval function (wrapper::CallWrapper{Ret})($((Symbol("arg", i) for i in 1:nargs)...)) where Ret
convert(Ret, wrapper.f($((Symbol("arg", i) for i in 1:nargs)...)))
end
end

let ex = if VERSION >= v"0.6.0"
# Can in princeple be lower but 0.6 doesn't warn on this so it doesn't matter
parse("mutable struct FunctionWrapper{Ret,Args<:Tuple} end")
else
parse("type FunctionWrapper{Ret,Args<:Tuple} end")
@generated function make_cfunction(obj::objT, ::Type{Ret}, ::Type{Args}) where {objT,Ret,Args}
quote
wrapped = CallWrapper{Ret}(obj)
@cfunction(
$(Expr(:$, :wrapped)), # use $ to create runtime closure over obj
map_rettype(Ret),
($([:(map_cfunc_argtype($Arg)) for Arg in Args.parameters]...), ))
end
end
ex.args[3] = quote
ptr::Ptr{Void}
objptr::Ptr{Void}
obj
objT
function (::Type{FunctionWrapper{Ret,Args}}){Ret,Args,objT}(obj::objT)
objref = Base.cconvert(Ref{objT}, obj)
new{Ret,Args}(cfunction(CallWrapper{Ret}(), map_rettype(Ret),
get_cfunc_argtype(objT, Args)),
Base.unsafe_convert(Ref{objT}, objref), objref, objT)
end
(::Type{FunctionWrapper{Ret,Args}}){Ret,Args}(obj::FunctionWrapper{Ret,Args}) = obj

mutable struct FunctionWrapper{Ret,Args<:Tuple}
ptr::Ptr{Cvoid}
objptr::Ptr{Cvoid}
cfun
obj
objT
function FunctionWrapper{Ret,Args}(obj::objT) where {Ret,Args,objT}
cfun = make_cfunction(obj, Ret, Args)
ptr = Base.unsafe_convert(Ptr{Cvoid}, Base.cconvert(Ptr{Cvoid}, cfun))
objptr = Base.unsafe_convert(Ref{objT}, Base.cconvert(Ref{objT}, obj))
new{Ret,Args}(ptr, objptr, cfun, obj, objT)
end
eval(ex)

FunctionWrapper{Ret,Args}(obj::FunctionWrapper{Ret,Args}) where {Ret,Args} = obj
end

Base.convert{T<:FunctionWrapper}(::Type{T}, obj) = T(obj)
Base.convert{T<:FunctionWrapper}(::Type{T}, obj::T) = obj
Base.convert(::Type{T}, obj) where {T<:FunctionWrapper} = T(obj)
Base.convert(::Type{T}, obj::T) where {T<:FunctionWrapper} = obj

@noinline function reinit_wrapper{Ret,Args}(f::FunctionWrapper{Ret,Args})
objref = f.obj
@noinline function reinit_wrapper(f::FunctionWrapper{Ret,Args}) where {Ret,Args}
obj = f.obj
objT = f.objT
ptr = cfunction(CallWrapper{Ret}(), map_rettype(Ret),
get_cfunc_argtype(objT, Args))
cfun = make_cfunction(obj, Ret, Args)
ptr = Base.unsafe_convert(Ptr{Cvoid}, Base.cconvert(Ptr{Cvoid}, cfun))
f.ptr = ptr
f.objptr = Base.unsafe_convert(Ref{objT}, objref)
return ptr
f.objptr = Base.unsafe_convert(Ref{objT}, Base.cconvert(Ref{objT}, obj))
f.cfun = cfun
return ptr::Ptr{Cvoid}
end

@generated function do_ccall{Ret,Args}(f::FunctionWrapper{Ret,Args}, args::Args)
@generated function do_ccall(f::FunctionWrapper{Ret,Args}, args::Args) where {Ret,Args}
# Has to be generated since the arguments type of `ccall` does not allow
# anything other than tuple (i.e. `@pure` function doesn't work).
quote
$(Expr(:meta, :inline))
Base.@_inline_meta
ptr = f.ptr
if ptr == C_NULL
# For precompile support
Expand All @@ -102,8 +100,8 @@ end
assume(ptr != C_NULL)
objptr = f.objptr
ccall(ptr, $(map_rettype(Ret)),
(Ptr{Void}, $((map_argtype(Arg) for Arg in Args.parameters)...)),
objptr, $((:(args[$i]) for i in 1:length(Args.parameters))...))
($((map_argtype(Arg) for Arg in Args.parameters)...),),
$((:(args[$i]) for i in 1:length(Args.parameters))...))
end
end

Expand Down
20 changes: 9 additions & 11 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

import FunctionWrappers
import FunctionWrappers: FunctionWrapper
using Base.Test
using Test

if VERSION >= v"0.6.0"
# Can in princeple be lower but 0.6 doesn't warn on this so it doesn't matter
eval(parse("struct CallbackF64 f::FunctionWrapper{Float64,Tuple{Int}} end"))
else
eval(parse("immutable CallbackF64 f::FunctionWrapper{Float64,Tuple{Int}} end"))
struct CallbackF64
f::FunctionWrapper{Float64,Tuple{Int}}
end
(cb::CallbackF64)(v) = cb.f(v)

gen_closure(x) = y->x + y

@testset "As field" begin
Expand Down Expand Up @@ -56,10 +54,10 @@ end
@test FunctionWrappers.identityAnyAny(1) === 1
end

@testset "Void" begin
identityVoidVoid = FunctionWrapper{Void,Tuple{Void}}(identity)
@test identityVoidVoid(nothing) === nothing
@testset "Nothing" begin
identityNothingNothing = FunctionWrapper{Nothing,Tuple{Nothing}}(identity)
@test identityNothingNothing(nothing) === nothing
f1 = (a, b)->b
fIntVoidInt = FunctionWrapper{Int,Tuple{Void,Int}}(f1)
@test fIntVoidInt(nothing, 1) === 1
fIntNothingInt = FunctionWrapper{Int,Tuple{Nothing,Int}}(f1)
@test fIntNothingInt(nothing, 1) === 1
end