No more evaluation in closed module in Julia 1.5+ == no more code-gen in modules?

In some “helper package” (module) I have some functions similar to that one:

function make_polynomial(c::Array{T,1} where T<:Number; return_code=false)
    if length(c) == 1
        f_body = :($(c[1]))
    else
        f_body = :($(c[end]))
        for ci in reverse(c[1:end-1])
            f_body = :($ci + x*$f_body)
        end
    end
    f_code = :(x -> $f_body)

    return return_code ? f_code : eval(f_code)
end

This one generates the code for a function efficiently evaluating a 1D-polynomial by Horner’s rule, for a given value of the independent variable. It then either returns the code defining this function, or directly this function (via an eval).

Now, since Julia 1.5, another module, utilising this helper module, does not precompile anymore. (Due to the fact that the eval happens in the closed helper module, “breaking incremental precompilation”, “don’t do this”.)

Any suggestion on what I should do here? Is there any other way of getting a function calculating something from a runtime-valued variable (x), by using hard-coded (and “efficiently unrolled”) coefficient values from a vector of such coefficients?

And no, I cannot change all the callers to get the code and eval it there; would take me days…

Can you build the anonymous function directly with code instead using expressions?

you should check out evalpoly. It does what you want I think.

Thanks, but the evaluation of a polynomial is just one example of several similar functions.
(BTW, this also takes around 38 ns on my PC, compared to 26 ns of the above “pre-generated” solution.)

That’s what I tried in several ways, but could not come up with a solution. I mean, of course it is possible, but not with the same performance. The goal is to fully utilise the fact that, at construction of the function, the number of coefficients and their values are known, so no loop is needed. With the above implementation, using c = randn(5), @code_typed gives:

CodeInfo(
1 ─ %1 = Base.mul_float(x, -0.12695133782437318)::Float64
│   %2 = Base.add_float(0.21835680782713537, %1)::Float64
│   %3 = Base.mul_float(x, %2)::Float64
│   %4 = Base.add_float(1.0391119724087707, %3)::Float64
│   %5 = Base.mul_float(x, %4)::Float64
│   %6 = Base.add_float(-0.7659185523381208, %5)::Float64
│   %7 = Base.mul_float(x, %6)::Float64
│   %8 = Base.add_float(0.8006651451155793, %7)::Float64
└──      return %8
) => Float64

Anything involving a vector (or tuple), and some sort of loop, results in quite some overhead and around 42 instead of 26 ns execution time (evaluated using @benchmark).

Is there a way of accessing the value of a Symbol (or Expression), in the caller scope, inside a macro?

The problem is that the above isn’t allowing fused multiply add to be used. You probably want (muladd(x, $f_body, $ci)).

Use a generated function, pass in the length as a Val{n} value? Alternatively, disable pre-compilation: __precompile__(false).

Thanks for the explanation.

You can also pass in the calling scope’s eval function and use that instead of the module eval.

2 Likes

Hehe, thanks. Brings down the operations to:

CodeInfo(
1 ─ %1 = Base.muladd_float(x, -0.12695133782437318, 0.21835680782713537)::Float64
│   %2 = Base.muladd_float(x, %1, 1.0391119724087707)::Float64
│   %3 = Base.muladd_float(x, %2, -0.7659185523381208)::Float64
│   %4 = Base.muladd_float(x, %3, 0.8006651451155793)::Float64
└──      return %4
) => Float64

However, in the exec. time, I can hardly see any difference…