How does RuntimeGeneratedFunction ensure correct dependencies are loaded?

How does RuntimeGeneratedFunction ensure that the correct dependency libraries are available?

This example compiles an expression which includes the constant twoπ which is defined in IrrationalConstants.jl and used in DiffRules.jl. DiffRules is a direct dependency of my project, FastDifferentiation.jl, but IrrationalConstants is not.

My code does not import or export twoπ. It is not defined in the REPL environment:

julia> twoπ
ERROR: UndefVarError: `twoπ` not defined

nor is it defined in FastDifferentiation:

julia> FastDifferentiation.twoπ
ERROR: UndefVarError: `twoπ` not defined

But somehow RuntimeGeneratedFunctions figures out that twoπ in the quoted expression is the value defined in IrrationalConstants instead of throwing a not defined exception:

julia> using FastDifferentiation

julia> @variables x

julia> f = mod2pi(x)
mod2pi(x)

julia> derivative([f],x)
1-element Vector{FastDifferentiation.Node}:
 (if_else  isinteger((x / twoπ)) NaN 1) #here twoπ is an element of the FastDifferentiation expression tree

julia> g = ans
1-element Vector{FastDifferentiation.Node}:
 (if_else  isinteger((x / twoπ)) NaN 1)

julia> h = make_function(g,[x,y])
RuntimeGeneratedFunction(#=in FastDifferentiation=#, #=using FastDifferentiation=#, :((input_variables,)->begin
          #= c:\Users\seatt\source\FastDifferentiation.jl\src\CodeGeneration.jl:229 =#
          #= c:\Users\seatt\source\FastDifferentiation.jl\src\CodeGeneration.jl:229 =# @inbounds begin
                  #= c:\Users\seatt\source\FastDifferentiation.jl\src\CodeGeneration.jl:230 =#
                  begin
                      result_element_type = promote_type(Float64, eltype(input_variables))
                      result = Array{result_element_type}(undef, (1,))
                      var"##227" = input_variables[1] / twoπ  #here twoπ is in the quoted expression that I generate. But it is not prefixed with DiffRules or IrrationalConstants
                      var"##226" = isinteger(var"##227")
                      var"##225" = if var"##226"
                              #= c:\Users\seatt\source\FastDifferentiation.jl\src\CodeGeneration.jl:58 =#
                              begin
                                  var"##s#228" = NaN
                              end
                          else
                              #= c:\Users\seatt\source\FastDifferentiation.jl\src\CodeGeneration.jl:60 =#
                              begin
                                  var"##s#229" = 1
                              end
                          end
                      result[1] = var"##225"
                      return result
                  end
              end
      end))


julia> h([2*π])
1-element Vector{Float64}:
 NaN

I want to ensure that when users call make_function it returns a function that will compile correctly regardless of what packages the user has loaded into their environment. It seems RuntimeGeneratedFunction is doing the right thing with twoπ but I don’t understand how.

You have to choose the symbol lookup location. See:

1 Like

Since the constant twoπ is defined in IrrationalConstants would FastDifferentiation have to take a dependency on IrrationalConstants? Otherwise I wouldn’t have a module reference to pass as an argument to @RuntimeGeneratedFunction.

Is this how it should be done?

julia> using RuntimeGeneratedFunctions, IrrationalConstants

julia> RuntimeGeneratedFunctions.init(IrrationalConstants)

julia> RuntimeGeneratedFunctions.init(@__MODULE__)

julia> a = @RuntimeGeneratedFunction(IrrationalConstants,:(x->twoπ*x))
RuntimeGeneratedFunction(#=in Main=#, #=using IrrationalConstants=#, :((x,)->begin
          #= REPL[6]:1 =#
          twoπ * x
      end))

julia> a(1.0)
6.283185307179586

Yes. Or you’d have to have some other package/module that is referenced as the symbol holder.