diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..453925c --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1 @@ +style = "sciml" \ No newline at end of file diff --git a/.github/workflows/FormatCheck.yml b/.github/workflows/FormatCheck.yml new file mode 100644 index 0000000..2a3517a --- /dev/null +++ b/.github/workflows/FormatCheck.yml @@ -0,0 +1,42 @@ +name: format-check + +on: + push: + branches: + - 'master' + - 'release-' + tags: '*' + pull_request: + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + julia-version: [1] + julia-arch: [x86] + os: [ubuntu-latest] + steps: + - uses: julia-actions/setup-julia@latest + with: + version: ${{ matrix.julia-version }} + + - uses: actions/checkout@v1 + - name: Install JuliaFormatter and format + # This will use the latest version by default but you can set the version like so: + # + # julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter", version="0.13.0"))' + run: | + julia -e 'using Pkg; Pkg.add(PackageSpec(name="JuliaFormatter"))' + julia -e 'using JuliaFormatter; format(".", verbose=true)' + - name: Format check + run: | + julia -e ' + out = Cmd(`git diff --name-only`) |> read |> String + if out == "" + exit(0) + else + @error "Some files have not been formatted !!!" + write(stdout, out) + exit(1) + end' diff --git a/src/RuntimeGeneratedFunctions.jl b/src/RuntimeGeneratedFunctions.jl index 7a84cf0..47bbe31 100644 --- a/src/RuntimeGeneratedFunctions.jl +++ b/src/RuntimeGeneratedFunctions.jl @@ -4,7 +4,6 @@ using ExprTools, Serialization, SHA export RuntimeGeneratedFunction, @RuntimeGeneratedFunction - """ @RuntimeGeneratedFunction(function_expression) @RuntimeGeneratedFunction(context_module, function_expression, opaque_closures=true) @@ -51,11 +50,11 @@ end """ struct RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id} <: Function body::Expr - function RuntimeGeneratedFunction(cache_tag, context_tag, ex; opaque_closures=true) + function RuntimeGeneratedFunction(cache_tag, context_tag, ex; opaque_closures = true) def = splitdef(ex) args, body = normalize_args(def[:args]), def[:body] if opaque_closures && isdefined(Base, :Experimental) && - isdefined(Base.Experimental, Symbol("@opaque")) + isdefined(Base.Experimental, Symbol("@opaque")) body = closures_to_opaque(body) end id = expr_to_id(body) @@ -64,7 +63,12 @@ struct RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id} <: Functio end # For internal use in deserialize() - doesen't check whether the body is in the cache! - function RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id}(body::Expr) where {argnames,cache_tag,context_tag,id} + function RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id}(body::Expr) where { + argnames, + cache_tag, + context_tag, + id + } new{argnames, cache_tag, context_tag, id}(body) end end @@ -78,16 +82,13 @@ function _check_rgf_initialized(mods...) end end -function RuntimeGeneratedFunction( - cache_module::Module, context_module::Module, code; opaque_closures=true, - ) +function RuntimeGeneratedFunction(cache_module::Module, context_module::Module, code; + opaque_closures = true) _check_rgf_initialized(cache_module, context_module) - RuntimeGeneratedFunction( - getfield(cache_module, _tagname), - getfield(context_module, _tagname), - code; - opaque_closures = opaque_closures - ) + RuntimeGeneratedFunction(getfield(cache_module, _tagname), + getfield(context_module, _tagname), + code; + opaque_closures = opaque_closures) end macro RuntimeGeneratedFunction(code) @@ -95,26 +96,33 @@ macro RuntimeGeneratedFunction(code) RuntimeGeneratedFunction(@__MODULE__, @__MODULE__, $(esc(code))) end end -macro RuntimeGeneratedFunction(context_module, code, opaque_closures=true) +macro RuntimeGeneratedFunction(context_module, code, opaque_closures = true) quote - RuntimeGeneratedFunction( - @__MODULE__, $(esc(context_module)), $(esc(code)); - opaque_closures = $(esc(opaque_closures)), - ) + RuntimeGeneratedFunction(@__MODULE__, $(esc(context_module)), $(esc(code)); + opaque_closures = $(esc(opaque_closures))) end end # Duplicate RuntimeGeneratedFunction docs onto @RuntimeGeneratedFunction @eval @doc $(@doc RuntimeGeneratedFunction) var"@RuntimeGeneratedFunction" -function Base.show(io::IO, ::MIME"text/plain", f::RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id}) where {argnames,cache_tag,context_tag,id} +function Base.show(io::IO, ::MIME"text/plain", + f::RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id}) where { + argnames, + cache_tag, + context_tag, + id + } cache_mod = parentmodule(cache_tag) context_mod = parentmodule(context_tag) func_expr = Expr(:->, Expr(:tuple, argnames...), f.body) - print(io, "RuntimeGeneratedFunction(#=in $cache_mod=#, #=using $context_mod=#, ", repr(func_expr), ")") + print(io, "RuntimeGeneratedFunction(#=in $cache_mod=#, #=using $context_mod=#, ", + repr(func_expr), ")") end -(f::RuntimeGeneratedFunction)(args::Vararg{Any,N}) where N = generated_callfunc(f, args...) +function (f::RuntimeGeneratedFunction)(args::Vararg{Any, N}) where {N} + generated_callfunc(f, args...) +end # We'll generate a method of this function in every module which wants to use # @RuntimeGeneratedFunction @@ -193,26 +201,36 @@ function init(mod) lock(_cache_lock) do if !isdefined(mod, _cachename) mod.eval(quote - const $_cachename = Dict() - struct $_tagname - end - - # We create method of `generated_callfunc` in the user's module - # so that any global symbols within the body will be looked up - # in the user's module scope. - # - # This is straightforward but clunky. A neater solution should - # be to explicitly expand in the user's module and return a - # CodeInfo from `generated_callfunc`, but it seems we'd need - # `jl_expand_and_resolve` which doesn't exist until Julia 1.3 - # or so. See: - # https://github.com/JuliaLang/julia/pull/32902 - # https://github.com/NHDaly/StagedFunctions.jl/blob/master/src/StagedFunctions.jl#L30 - @inline @generated function $RuntimeGeneratedFunctions.generated_callfunc( - f::$RuntimeGeneratedFunctions.RuntimeGeneratedFunction{argnames, cache_tag, $_tagname, id}, __args...) where {argnames, cache_tag, id} - $RuntimeGeneratedFunctions.generated_callfunc_body(argnames, cache_tag, id, __args) - end - end) + const $_cachename = Dict() + struct $_tagname end + + # We create method of `generated_callfunc` in the user's module + # so that any global symbols within the body will be looked up + # in the user's module scope. + # + # This is straightforward but clunky. A neater solution should + # be to explicitly expand in the user's module and return a + # CodeInfo from `generated_callfunc`, but it seems we'd need + # `jl_expand_and_resolve` which doesn't exist until Julia 1.3 + # or so. See: + # https://github.com/JuliaLang/julia/pull/32902 + # https://github.com/NHDaly/StagedFunctions.jl/blob/master/src/StagedFunctions.jl#L30 + @inline @generated function $RuntimeGeneratedFunctions.generated_callfunc(f::$RuntimeGeneratedFunctions.RuntimeGeneratedFunction{ + argnames, + cache_tag, + $_tagname, + id + }, + __args...) where { + argnames, + cache_tag, + id + } + $RuntimeGeneratedFunctions.generated_callfunc_body(argnames, + cache_tag, + id, __args) + end + end) end end end @@ -235,11 +253,11 @@ end @nospecialize -closures_to_opaque(x, _=nothing) = x +closures_to_opaque(x, _ = nothing) = x _tconvert(T, x) = Expr(:(::), Expr(:call, GlobalRef(Base, :convert), T, x), T) -function closures_to_opaque(ex::Expr, return_type=nothing) +function closures_to_opaque(ex::Expr, return_type = nothing) head, args = ex.head, ex.args - fdef = splitdef(ex; throw=false) + fdef = splitdef(ex; throw = false) if fdef !== nothing body = get(fdef, :body, nothing) if haskey(fdef, :rtype) @@ -264,16 +282,15 @@ function closures_to_opaque(ex::Expr, return_type=nothing) elseif head === :generator f_args = Expr(:tuple, Any[x.args[1] for x in args[2:end]]...) iters = Any[x.args[2] for x in args[2:end]] - return Expr( - :call, - GlobalRef(Base, :Generator), - closures_to_opaque(Expr(:(->), f_args, args[1])), - iters..., - ) + return Expr(:call, + GlobalRef(Base, :Generator), + closures_to_opaque(Expr(:(->), f_args, args[1])), + iters...) elseif head === :opaque_closure return closures_to_opaque(args[1]) elseif head === :return && return_type !== nothing - return Expr(:return, _tconvert(return_type, closures_to_opaque(args[1], return_type))) + return Expr(:return, + _tconvert(return_type, closures_to_opaque(args[1], return_type))) end return Expr(head, Any[closures_to_opaque(x, return_type) for x in args]...) end @@ -281,7 +298,14 @@ end # We write an explicit deserialize() here to trigger caching of the body on a # remote node when using Serialialization.jl (in Distributed.jl and elsewhere) function Serialization.deserialize(s::AbstractSerializer, - ::Type{RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id}}) where {argnames,cache_tag,context_tag,id} + ::Type{ + RuntimeGeneratedFunction{argnames, cache_tag, + context_tag, id}}) where { + argnames, + cache_tag, + context_tag, + id + } body = deserialize(s) cached_body = _cache_body(cache_tag, id, body) RuntimeGeneratedFunction{argnames, cache_tag, context_tag, id}(cached_body) diff --git a/test/precomp/RGFPrecompTest.jl b/test/precomp/RGFPrecompTest.jl index 016ac71..b3c0c00 100644 --- a/test/precomp/RGFPrecompTest.jl +++ b/test/precomp/RGFPrecompTest.jl @@ -1,9 +1,9 @@ module RGFPrecompTest - using RuntimeGeneratedFunctions - using RGFPrecompTest2 - RuntimeGeneratedFunctions.init(@__MODULE__) +using RuntimeGeneratedFunctions +using RGFPrecompTest2 +RuntimeGeneratedFunctions.init(@__MODULE__) - f = @RuntimeGeneratedFunction(:((x,y)->x+y)) +f = @RuntimeGeneratedFunction(:((x, y) -> x + y)) - g = RGFPrecompTest2.generate_rgf(@__MODULE__) +g = RGFPrecompTest2.generate_rgf(@__MODULE__) end diff --git a/test/precomp/RGFPrecompTest2.jl b/test/precomp/RGFPrecompTest2.jl index 2f31fbf..7e46923 100644 --- a/test/precomp/RGFPrecompTest2.jl +++ b/test/precomp/RGFPrecompTest2.jl @@ -1,13 +1,13 @@ module RGFPrecompTest2 - using RuntimeGeneratedFunctions - RuntimeGeneratedFunctions.init(@__MODULE__) +using RuntimeGeneratedFunctions +RuntimeGeneratedFunctions.init(@__MODULE__) - y_in_RGFPrecompTest2 = 2 +y_in_RGFPrecompTest2 = 2 - # Simulates a helper function which generates an RGF, but caches it in a - # different module. - function generate_rgf(cache_module) - context_module = @__MODULE__ - RuntimeGeneratedFunction(cache_module, @__MODULE__, :((x)->y_in_RGFPrecompTest2+x)) - end +# Simulates a helper function which generates an RGF, but caches it in a +# different module. +function generate_rgf(cache_module) + context_module = @__MODULE__ + RuntimeGeneratedFunction(cache_module, @__MODULE__, :((x) -> y_in_RGFPrecompTest2 + x)) +end end diff --git a/test/runtests.jl b/test/runtests.jl index cf27d1a..6f18643 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,29 +4,29 @@ using Test RuntimeGeneratedFunctions.init(@__MODULE__) -function f(_du,_u,_p,_t) +function f(_du, _u, _p, _t) @inbounds _du[1] = _u[1] @inbounds _du[2] = _u[2] nothing end -ex1 = :((_du,_u,_p,_t) -> begin - @inbounds _du[1] = _u[1] - @inbounds _du[2] = _u[2] - nothing -end) +ex1 = :((_du, _u, _p, _t) -> begin + @inbounds _du[1] = _u[1] + @inbounds _du[2] = _u[2] + nothing + end) -ex2 = :(function f(_du,_u,_p,_t) - @inbounds _du[1] = _u[1] - @inbounds _du[2] = _u[2] - nothing -end) +ex2 = :(function f(_du, _u, _p, _t) + @inbounds _du[1] = _u[1] + @inbounds _du[2] = _u[2] + nothing + end) -ex3 = :(function (_du::T,_u::Vector{E},_p::P,_t::Any) where {T<:Vector,E,P} - @inbounds _du[1] = _u[1] - @inbounds _du[2] = _u[2] - nothing -end) +ex3 = :(function (_du::T, _u::Vector{E}, _p::P, _t::Any) where {T <: Vector, E, P} + @inbounds _du[1] = _u[1] + @inbounds _du[2] = _u[2] + nothing + end) f1 = @RuntimeGeneratedFunction(ex1) f2 = @RuntimeGeneratedFunction(ex2) @@ -39,72 +39,75 @@ u = rand(2) p = nothing t = nothing -@test f1(du,u,p,t) === nothing +@test f1(du, u, p, t) === nothing du == u du = rand(2) -f2(du,u,p,t) +f2(du, u, p, t) @test du == u du = rand(2) -@test f3(du,u,p,t) === nothing +@test f3(du, u, p, t) === nothing du == u -t1 = @belapsed $f($du,$u,$p,$t) -t2 = @belapsed $f1($du,$u,$p,$t) -t3 = @belapsed $f2($du,$u,$p,$t) -t4 = @belapsed $f3($du,$u,$p,$t) +t1 = @belapsed $f($du, $u, $p, $t) +t2 = @belapsed $f1($du, $u, $p, $t) +t3 = @belapsed $f2($du, $u, $p, $t) +t4 = @belapsed $f3($du, $u, $p, $t) -@test t1 ≈ t2 atol = 3e-9 -@test t1 ≈ t3 atol = 3e-9 -@test t1 ≈ t4 atol = 3e-9 +@test t1≈t2 atol=3e-9 +@test t1≈t3 atol=3e-9 +@test t1≈t4 atol=3e-9 function no_worldage() - ex = :(function f(_du,_u,_p,_t) - @inbounds _du[1] = _u[1] - @inbounds _du[2] = _u[2] - nothing - end) + ex = :(function f(_du, _u, _p, _t) + @inbounds _du[1] = _u[1] + @inbounds _du[2] = _u[2] + nothing + end) f1 = @RuntimeGeneratedFunction(ex) du = rand(2) u = rand(2) p = nothing t = nothing - f1(du,u,p,t) + f1(du, u, p, t) end @test no_worldage() === nothing # Test show() -@test sprint(show, MIME"text/plain"(), @RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+1)))) == - """ - RuntimeGeneratedFunction(#=in $(@__MODULE__)=#, #=using $(@__MODULE__)=#, :((x, y)->begin - x + y + 1 - end))""" +@test sprint(show, MIME"text/plain"(), + @RuntimeGeneratedFunction(Base.remove_linenums!(:((x, y) -> x + y + 1)))) == + """ + RuntimeGeneratedFunction(#=in $(@__MODULE__)=#, #=using $(@__MODULE__)=#, :((x, y)->begin + x + y + 1 + end))""" # Test with precompilation push!(LOAD_PATH, joinpath(@__DIR__, "precomp")) using RGFPrecompTest -@test RGFPrecompTest.f(1,2) == 3 +@test RGFPrecompTest.f(1, 2) == 3 @test RGFPrecompTest.g(40) == 42 # Test that RuntimeGeneratedFunction with identical body expressions (but # allocated separately) don't clobber each other when one is GC'd. -f_gc = @RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+100001))) +f_gc = @RuntimeGeneratedFunction(Base.remove_linenums!(:((x, y) -> x + y + 100001))) let - @RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+100001))) + @RuntimeGeneratedFunction(Base.remove_linenums!(:((x, y) -> x + y + 100001))) end GC.gc() -@test f_gc(1,-1) == 100001 +@test f_gc(1, -1) == 100001 # Test that threaded use works tasks = [] -for k=1:4 - let k=k +for k in 1:4 + let k = k t = Threads.@spawn begin r = Bool[] - for i=1:100 - f = @RuntimeGeneratedFunction(Base.remove_linenums!(:((x,y)->x+y+$i*$k))) - x = 1; y = 2; - push!(r, f(x,y) == x + y + i*k) + for i in 1:100 + f = @RuntimeGeneratedFunction(Base.remove_linenums!(:((x, y) -> x + y + + $i * $k))) + x = 1 + y = 2 + push!(r, f(x, y) == x + y + i * k) end r end @@ -113,27 +116,26 @@ for k=1:4 end @test all(all.(fetch.(tasks))) - # Test that globals are resolved within the correct scope module GlobalsTest - using RuntimeGeneratedFunctions - RuntimeGeneratedFunctions.init(@__MODULE__) +using RuntimeGeneratedFunctions +RuntimeGeneratedFunctions.init(@__MODULE__) - y_in_GlobalsTest = 40 - f = @RuntimeGeneratedFunction(:(x->x + y_in_GlobalsTest)) +y_in_GlobalsTest = 40 +f = @RuntimeGeneratedFunction(:(x -> x + y_in_GlobalsTest)) end @test GlobalsTest.f(2) == 42 -f_outside = @RuntimeGeneratedFunction(GlobalsTest, :(x->x + y_in_GlobalsTest)) +f_outside = @RuntimeGeneratedFunction(GlobalsTest, :(x -> x + y_in_GlobalsTest)) @test f_outside(2) == 42 @test_throws ErrorException @eval(module NotInitTest - using RuntimeGeneratedFunctions - # RuntimeGeneratedFunctions.init(@__MODULE__) # <-- missing - f = @RuntimeGeneratedFunction(:(x->x+y)) -end) + using RuntimeGeneratedFunctions + # RuntimeGeneratedFunctions.init(@__MODULE__) # <-- missing + f = @RuntimeGeneratedFunction(:(x -> x + y)) + end) # closures if VERSION >= v"1.7.0-DEV.351" @@ -144,13 +146,13 @@ if VERSION >= v"1.7.0-DEV.351" @test @RuntimeGeneratedFunction(ex)(2)(3) === 5.0 ex = :(x -> function (y::Int) - return x + y - end) + return x + y + end) @test @RuntimeGeneratedFunction(ex)(2)(3) === 5 ex = :(x -> function f(y::Int)::UInt8 - return x + y - end) + return x + y + end) @test @RuntimeGeneratedFunction(ex)(2)(3) === 0x05 ex = :(x -> sum(i^2 for i in 1:x)) diff --git a/test/serialize_rgf.jl b/test/serialize_rgf.jl index a391487..fa9f871 100644 --- a/test/serialize_rgf.jl +++ b/test/serialize_rgf.jl @@ -5,6 +5,6 @@ using Serialization RuntimeGeneratedFunctions.init(@__MODULE__) -f = @RuntimeGeneratedFunction(:(x->"Hi from a separate process. x=$x")) +f = @RuntimeGeneratedFunction(:(x -> "Hi from a separate process. x=$x")) serialize(stdout, f)