Let's fix static pointers (#26556)
Dear Laurent, Duncan, Mathieu, Facundo, Edsko I have spent a little while digging into *static pointers* recently. See my post below. I wonder if you have any comments on my proposal? Do you know anyone else I should consult? Thanks! Simon On Fri, 7 Nov 2025 at 18:13, Simon Peyton Jones (@simonpj) < gitlab@gitlab.haskell.org> wrote:
Simon Peyton Jones https://gitlab.haskell.org/simonpj created an issue: #26556 https://gitlab.haskell.org/ghc/ghc/-/issues/26556
Static pointers are not properly implemented. For example:
- #26545 https://gitlab.haskell.org/ghc/ghc/-/issues/26545 - #24464 https://gitlab.haskell.org/ghc/ghc/-/issues/24464 - #24773 https://gitlab.haskell.org/ghc/ghc/-/issues/24773
among others. Moreover, the implementation is very messy, scattered about in FloatOut and elsewhere.
Let's fix it. <#m_5415477730196602759_discussion>Discussion
I embarked on what I thought would be a simple refactor to
- Identify static bindings in the type checker - Promote them to top level desugarer
thereby avoiding all the terribly painful static-form-floating stuff that been an ongoing source of breakage and irritation.
Sadly it was not as simple as I had thought. Merge request !14994 https://gitlab.haskell.org/ghc/ghc/-/merge_requests/14994 is my work in progress
-
At first it seems simple: given static e - When typechecking e ensure that all its free variables are top-level defined - When desugaring, move e to top level
Apparently simple! -
*Complication 1*. e might generate constraints. We don't want to solve those from locally-bound Givens, because they'll be out of scope when we promote to top level.
Solution: wrap the constraints in an implication with SkolInfo of StaticFormSkol; and in the constraint solver zap all Givens when walking inside such an implication. That was done in
commit 39d4a24beaa7874a69ffdc1528ca160818829169Author: Simon Peyton Jones
Date: Tue Sep 30 23:11:19 2025 +0100 Build implication for constraints from (static e) This commit addresses #26466, by buiding an implication for the constraints arising from a (static e) form. The implication has a special ic_info field of StaticFormSkol, which tells the constraint solver to use an empty set of Givens. So that complication wasn't at all bad. -
*Complication 2*. What if we have
f x = let y = reverse "hello" in ...(static (y++y))...
The free vars of the static are just {y}, and y is morally-top-level. It in turn has no free variables.
Sadly (as it turns out) GHC tries to accept this case. When looking at the defn of y (with no static in sight yet) the typechecker marks it at a "static binding", meaning that it too can (and indeed must) be floated to top level.
So if the desugarer moves the static to the top level, it must move y too. And that means it must mark the typechecked binding in some way, so the desugarer can identify it. Not so hard, but there is quite a bit of new plumbing. -
*Complication 3*. But what if y's RHS generates constraints, which use Givens (or solved dictionaries, which are very similar) from its context. E.g.
f x = let p = x+1::Int; y = 2+3::Int in ...
Now there may be a d :: Num Int lying around from dealing with p, and y may use it. Oh no! Now that'll be out of scope if we move y to top level.
Plausible solution: use them same mechanism for static bindings as we did for static e expressions. That is, build an implication constraint whose SkolInfo says "zap Givens". This turned out to be considerably harder to implement than it was for Complication 1. -
*Complication 4*. What if y is not generalised, perhaps because of the Monomorphism Restriction? e.g.
f :: Num a => a -> blahf x = let y = 3+3 in (x+y, static( ..y.. ))
Now y is monomorphic and really does use the dictionary passed to f. So it really cannot appear in the static. Somehow y really isn't static after all. We must reject this program. Not only is it an implementation mess (Complications 1,2,3 are already imposing quite a signficant implemenation burden) but it becomes pretty hard to explain to the programmer just which uses of static are OK and which are not.
What a swamp. At this point I threw up my hands and wrote this summary
<#m_5415477730196602759_proposal>Proposal
To me the solution is clear: the rule should be
- *in static e, all the free vars of e should be bound at top level*
That is a nice simple rule; it is easy to explain and easy to implement. It is also what the user manual says!
In retrospect, by addressing Complication 2 I was trying too hard! (And this extra feature is entirely undocumented.) I thought that I could deal with Complication 2 using the same mechanism as the one that deals with MonoLocalBinds. But I was wrong.
Making this change could perhaps break some programs. They would all be easy to fix, by moving bindings for any free variables to top level. But note that the status quo is not stable: it has bugs e.g #24464 https://gitlab.haskell.org/ghc/ghc/-/issues/24464, #26545 https://gitlab.haskell.org/ghc/ghc/-/issues/26545. What we have is at attempt to be clever that is simply wrong.
— View it on GitLab https://gitlab.haskell.org/ghc/ghc/-/issues/26556. You're receiving this email because of your activity on gitlab.haskell.org. Unsubscribe https://gitlab.haskell.org/-/sent_notifications/4b29fc65ccdc21e95267b66fdfb6... from this thread · Manage all notifications https://gitlab.haskell.org/-/profile/notifications · Help https://gitlab.haskell.org/help
Thanks Laurent and Mathieu
*Edsko, Duncan, Facundo*: do you have any views?
I have now written a GHC proposal
https://github.com/ghc-proposals/ghc-proposals/blob/wip/spj-static/proposals....
Could you add your thoughts to it? I think it's a no-brainer myself, but
we should go through the proper process.
Do any of you know people I should consult? Eg authors of libraries that
use StaticPtrs?
Thanks
Simon
On Fri, 7 Nov 2025 at 21:18, Simon Peyton Jones
Dear Laurent, Duncan, Mathieu, Facundo, Edsko
I have spent a little while digging into *static pointers* recently. See my post below. I wonder if you have any comments on my proposal?
Do you know anyone else I should consult?
Thanks!
Simon
On Fri, 7 Nov 2025 at 18:13, Simon Peyton Jones (@simonpj) < gitlab@gitlab.haskell.org> wrote:
Simon Peyton Jones https://gitlab.haskell.org/simonpj created an issue: #26556 https://gitlab.haskell.org/ghc/ghc/-/issues/26556
Static pointers are not properly implemented. For example:
- #26545 https://gitlab.haskell.org/ghc/ghc/-/issues/26545 - #24464 https://gitlab.haskell.org/ghc/ghc/-/issues/24464 - #24773 https://gitlab.haskell.org/ghc/ghc/-/issues/24773
among others. Moreover, the implementation is very messy, scattered about in FloatOut and elsewhere.
Let's fix it. <#m_-1657473426741401767_m_5415477730196602759_discussion>Discussion
I embarked on what I thought would be a simple refactor to
- Identify static bindings in the type checker - Promote them to top level desugarer
thereby avoiding all the terribly painful static-form-floating stuff that been an ongoing source of breakage and irritation.
Sadly it was not as simple as I had thought. Merge request !14994 https://gitlab.haskell.org/ghc/ghc/-/merge_requests/14994 is my work in progress
-
At first it seems simple: given static e - When typechecking e ensure that all its free variables are top-level defined - When desugaring, move e to top level
Apparently simple! -
*Complication 1*. e might generate constraints. We don't want to solve those from locally-bound Givens, because they'll be out of scope when we promote to top level.
Solution: wrap the constraints in an implication with SkolInfo of StaticFormSkol; and in the constraint solver zap all Givens when walking inside such an implication. That was done in
commit 39d4a24beaa7874a69ffdc1528ca160818829169Author: Simon Peyton Jones
Date: Tue Sep 30 23:11:19 2025 +0100 Build implication for constraints from (static e) This commit addresses #26466, by buiding an implication for the constraints arising from a (static e) form. The implication has a special ic_info field of StaticFormSkol, which tells the constraint solver to use an empty set of Givens. So that complication wasn't at all bad. -
*Complication 2*. What if we have
f x = let y = reverse "hello" in ...(static (y++y))...
The free vars of the static are just {y}, and y is morally-top-level. It in turn has no free variables.
Sadly (as it turns out) GHC tries to accept this case. When looking at the defn of y (with no static in sight yet) the typechecker marks it at a "static binding", meaning that it too can (and indeed must) be floated to top level.
So if the desugarer moves the static to the top level, it must move y too. And that means it must mark the typechecked binding in some way, so the desugarer can identify it. Not so hard, but there is quite a bit of new plumbing. -
*Complication 3*. But what if y's RHS generates constraints, which use Givens (or solved dictionaries, which are very similar) from its context. E.g.
f x = let p = x+1::Int; y = 2+3::Int in ...
Now there may be a d :: Num Int lying around from dealing with p, and y may use it. Oh no! Now that'll be out of scope if we move y to top level.
Plausible solution: use them same mechanism for static bindings as we did for static e expressions. That is, build an implication constraint whose SkolInfo says "zap Givens". This turned out to be considerably harder to implement than it was for Complication 1. -
*Complication 4*. What if y is not generalised, perhaps because of the Monomorphism Restriction? e.g.
f :: Num a => a -> blahf x = let y = 3+3 in (x+y, static( ..y.. ))
Now y is monomorphic and really does use the dictionary passed to f. So it really cannot appear in the static. Somehow y really isn't static after all. We must reject this program. Not only is it an implementation mess (Complications 1,2,3 are already imposing quite a signficant implemenation burden) but it becomes pretty hard to explain to the programmer just which uses of static are OK and which are not.
What a swamp. At this point I threw up my hands and wrote this summary
<#m_-1657473426741401767_m_5415477730196602759_proposal>Proposal
To me the solution is clear: the rule should be
- *in static e, all the free vars of e should be bound at top level*
That is a nice simple rule; it is easy to explain and easy to implement. It is also what the user manual says!
In retrospect, by addressing Complication 2 I was trying too hard! (And this extra feature is entirely undocumented.) I thought that I could deal with Complication 2 using the same mechanism as the one that deals with MonoLocalBinds. But I was wrong.
Making this change could perhaps break some programs. They would all be easy to fix, by moving bindings for any free variables to top level. But note that the status quo is not stable: it has bugs e.g #24464 https://gitlab.haskell.org/ghc/ghc/-/issues/24464, #26545 https://gitlab.haskell.org/ghc/ghc/-/issues/26545. What we have is at attempt to be clever that is simply wrong.
— View it on GitLab https://gitlab.haskell.org/ghc/ghc/-/issues/26556. You're receiving this email because of your activity on gitlab.haskell.org. Unsubscribe https://gitlab.haskell.org/-/sent_notifications/4b29fc65ccdc21e95267b66fdfb6... from this thread · Manage all notifications https://gitlab.haskell.org/-/profile/notifications · Help https://gitlab.haskell.org/help
participants (1)
-
Simon Peyton Jones