diff --git a/.travis.yml b/.travis.yml index 66f2d04..746a026 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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())' diff --git a/REQUIRE b/REQUIRE index 94237c0..cdbb2dc 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1 +1 @@ -julia 0.5 +julia 0.7-alpha diff --git a/appveyor.yml b/appveyor.yml index 48d7358..964272f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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 @@ -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%" diff --git a/src/FunctionWrappers.jl b/src/FunctionWrappers.jl index 3f3483e..9e4d004 100644 --- a/src/FunctionWrappers.jl +++ b/src/FunctionWrappers.jl @@ -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 @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index 1658bf7..242b4e9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 @@ -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