Regarding OP_EXEC vs OP_EVAL… it all depends on how they end up being used.
OP_EVAL - less “safe” but less overhead; seems like it would be most useful as an internal low-level tool for cashscript or other compilers to use to re-use code… i.e. to call internal functions they 100% control/have generated/have guaranteed known pre and post-conditions.
OP_EXEC - more safe, more overhead; seems like a more generic solution to the problem where both trusted code and “untrusted” code can be exec’d and the caller has guarantees about pre- and post-conditions, since they are enforced. I.e. you know the callee cannot mess with your stack, so you can jump into “untrusted” code, e.g. from a cashtoken commitment or the scriptsig stack or other random places.
So in short – if you envision OP_EVAL only being used for trusted code, ie code that you can verify is what it says it is either because you yourself generated it (compiler), or because you check its hash beforehand (p2sh style), then it’s fine. Less overhead. Lean and mean. But if you ever think it may be used to execute arbitrary contract code – then it’s a landmine waiting to go off.
OP_EXEC enforces a call signature and pre- and post-conditions. Called code cannot touch your stack. All it can do is push results onto your stack but cannot otherwise mess with it. Much safer.
My personal preference is for OP_EXEC. But, I get the efficienty-at-the-expense-of-genericity arguments of OP_EVAL.
Anyway in my ideal world we would have both OP_EVAL and OP_EXEC. This way nobody can make fun of us for being primitive apes.
However if you don’t envision anybody ever needed something more well-defined and secure like OP_EXEC… ever… then OP_EVAL only is fine.