Closure compilation

I’m trying to understand closure compilation … from the ocaml source.

in [Closure.close_functions] there is this:

    if !useless_env && occurs_var env_param ubody then raise NotClosed;

prior to this the cenv is populated with accesses to the env_param for each free variable identified from all fundefs.
I assume having been identified as such each is going to appear at least once somewhere in the fundef bodies.
therefore the above occurs check is going to hit for one fundef or another.
my question is why is fv<>[] not a condition for intially_closed/usless_env to be false from the outset and avoid the second round of compilation
when the exception is raised.
I believe I’m misunderstanding something though and help appreciated!

You might be interested in looking at the alternative closure compilation scheme from the ocaml sources: closure_conversion.ml in the middle_end/flambda directory.

As a potential answer to your question: the Closure compilation pass does some simplifications in addition to closure conversion, so some free variables from the initial function body might have been simplified to expressions that do not need to access the environment.
A simple example:

let one = 1 in
let f x = x + one in
...

In this example f has a free variable (one), but when compiled with the knowledge that one is bound to the constant 1 the resulting body will be x + 1, which doesn’t contain occurrences of the environment param. If we relied on the free variables instead, we would have compiled it as a non-closed function instead.

thanks, will take a closer look.
I see now that the occurs check is on the clambda and
it is only after approximations are taken account of that a search for
env_param is meaningful.

which leads to another question!:
as I’m struggling to create a recursive function at the top level
that registers as not closed, is it the case that only partial applications and things that depend on them cause non-closure?

You can try something like:

let f =
  let x = Sys.opaque_identity 0 in
  fun y -> x + y

This is not recursive, but since x is not at toplevel it will not be statically allocated and so the anonymous function that will become f will need to put it in its closure. The Sys.opaque_identity is here to prevent simplification of constants.