Sorry to triple-post, but I realize we’ve already implemented logic that does this (or something similar anyway).
The program
fn main() {
let x: u32 = witness::B;
assert!(jet::eq_32(x, x));
}
compiles to
(witness & iden); (((((OH & OH); jet_eq_32 ); jet_verify ) & unit); IH)
Because our compiler, for each let binding, implements this as a composition of the let-binding (witness & iden, where witness is the binding and iden carries forward any previous bindings) with the rest of the block, which can then refer to the binding with “selectors” such as OH which you see, which are trees of drops and takes that index into the input value to obtain specific values.
So one way to allow witnesses to appear in multiple functions, we could implement logic which “hoists” any witness accesses above main into a top-level pre-main block which creates let-bindings for each witnes. Then witness references in the functions themselves would compile to selectors into the let-bound values.
If we did this for every witness access, whether it appeared in one function or many, in main or elsewhere, we’d effectively have turned witnesses into implicit function parameters, and then again we might wonder whether we want to add syntax for this (and even whether this syntax should be mandatory, since witness access is a side-effect in SimplicityHL) (even though it’s not in Simplicity). This would have a nontrivial on-chain cost: in cases where witness data was accessed only once, rather than compiling to a single witness node, this access would become (witness & iden); followed by some selector (and every single other selector would need to be enlarged to select around the witness).
But if we didn’t do this, say, we only did this hoisting for witness values that appeared in more than one function, then we have “spooky action at a distance” where functions compile into different programs – and even have different input type signatures – depending on whether they appear alongside a different function that makes the same access. This would have surprising efficiency properties, complicate definition of an ABI and a linker, and undermine covenant logic which often needs to know the exact CMR of particular pieces of code.