Consider relaxing Note [Core letrec invariant] for string literals, and perhaps for unlifted HNFs, and perhaps for general unlifted ok-for-spec-eval bindings
Summary
We currently have special code in CorePrep's cpeBind _ _ Rec{}
that floats literal strings out of letrec
s.
I'd rather want to get rid of this code.
Similarly, if we float around unlifted value bindings, we may not park in a letrec, requiring similar special code. Hence I'd also want to allow unlifted normal forms in letrecs.
This path is well-trodden by OCaml, which allows cyclic lists such as letrec a = 1 :: b; b = 2 :: a in ...
: https://v2.ocaml.org/manual/letrecvalues.html
Stretch goal: letrec for unlifted ok-for-spec-eval bindings
One could even ponder allowing arbitrary (unlifted) ok-for-spec-eval bindings to exist in a letrec.
Unlifted bindings in a letrec are pretty much like regular lifted letrec
s where we force the unlifted bindings before we enter the letrec. If the recursive unlifted binding recurses before producing a value, we simply run into a black hole, just as with lazy recursive thunks.
This is the exact same semantics as in (source-level) scheme: https://docs.racket-lang.org/reference/let.html#%28form._%28%28lib._racket%2Fprivate%2Fletstx-scheme..rkt%29._letrec%29%29 Strict Letrec initialisation has been treated semantically in Denotational semantics for lazy initialization of letrec. As you can see in Fig. 4, it's just the same as Launchbury's semantics, only that letrec immediately seqs the bindings. Let's just do the same, should be quite simple to implement.
I'm not suggesting any change to source Haskell (yet), just tackling Core and STG for now.