Making GHC upgrades easy

Simon Peyton Jones June 12, 2026 [GHC] #hackage #cabal

What this post is about

When a shiny new version of GHC comes out, it should be easy to upgrade. After all, the new compiler should be more capable than the previous version!

But in practice that isn't even nearly true. The upgrade path is so hard that many companies are using versions of GHC from many years back; it's just too much work for them to upgrade. This is bad in many ways:

The GHC team has been working hard on this issue, and has made lots of progress. This post summarises what we have done, what remains to be done, and invites your help.

1. Goals

We have two big goals. The most important is this one::

(STABILITY goal) The Big Stability Goal.

Suppose a package P compiles successfully with GHC 10.0. When GHC 10.2 is released, it should be possible to use GHC 10.2 to compile package P, and all its dependencies, without modification.

It is impossible to provide a 100.0% promise of (STABILITY goal): see Section 4.1 below. But we can get very close.

There is a complementary goal, which relates to the base package. The base package provides core functionality to every Haskell program, including all the modules specified in the Haskell Report, especially the Prelude module. Almost every Haskell package in existence depends, directly or indirectly, on base. (Hence its name.)

(BASE goal) The Base Package Goal.

The base package should be a package like any other:

We are now getting very close to achieving these goals. This post explains why it is harder than it looks, what we have done recently, and what we mean by "very close".

The two goals look independent, but in fact overcoming one set of obstacles will unlock both goals, which is why I am treating them together here. Section 2 describes the problem.

2. Background: the problem

In the past, each version of GHC came with a new version of the base package. For example:

Moreover, each version of GHC was indissolubly tied to one, and only one, version of base. For example, every program compiled with (say) GHC 9.10, say, must be compiled against base-4.20.2. No other version of base will do.

2.1 Why tight coupling is a problem

This tight coupling was convenient for the implementers of early versions of GHC, well before we were thinking about stability, even before Cabal and Hackage even existed. But with the benefit of hindsight, the tight coupling is highly undesirable:

This is very, very bad. It may take months for a wave of version bumps and to sweep through Hackage. It directly contradicts (STABILITY goal).

2.2 The problem of known entities

But why does GHC 9.10, say, insist on base-4.20.2 and nothing else?

The main reason is that GHC needs to "know about" hundreds of functions, types, and classes defined in base. Example: when generating the code for deriving(Show) for a new data type, the generated code needs to refer to auxiliary functions defined in base. By "know about" I mean that GHC needs to know the precise module in which the Show class (and many other auxiliary functions) is defined.

There are many, many other examples: desugaring list comprehensions, or arrow notation, or record accesses. Collectively these functions, types, and classes are called "known entities".

This tight coupling between base and GHC directly contradicts (BASE goal).

3. The Glorious Plan, and progress so far

We have made a lot of progress towards meeting (STABILITY goal) and (BASE goal). This section lays out the steps we either have taken or propose to take.

This is a multi-year project involving contributions from many people; see Section 6 for a timeline and credits.

3.1 Splitting base and ghc-internal

The first step was to split the old base library into two libraries, ghc-internal and base:

This architecture has a major advantages, in principle anyway:

This separation was achieved in GHC 9.14. Doing it was trickier than it seemed; first ghc-internal and base had to be separated, and then base had to be made reinstallable. (Timeline in Section 6 below.)

Even after this all "known entities" (see Section 2) had to be defined in ghc-internal. In practice that pins a lot of library code in ghc-internal and means that base is largely just a shim. (Still useful! But without much functionality of its own.). See Section 3.3 for the next step.

3.2 Template Haskell

Template Haskell allows you to create a source Haskell AST (Abstract Syntax Tree), and then to pattern match on it. Since every release of GHC has changes to its Haskell AST, any package that does pattern-matching on a Template Haskell AST cannot possibly compile with a new version of GHC.

This directly threatens (STABILITY goal). More concretely:

This results in a lot of breakage when a new version of GHC comes out. And most of that breakage is unnecessary! Most use only quotations and splices (which are perfectly portable), rather than using the AST directly (which is not). For more detail on different classes of Template Haskell usage, with different stability properties, see Teo's blog post, or their talk at the 2025 Haskell Ecosystem Workshop.

Thus motivated, Teo has been busy splitting up the previously-monolithic template-haskell package, whose API necessarily changed with each version of GHC, into several packages:

These new packages have very stable APIs. There is still a package template-haskell that exposes the TH AST data type, and that is necessarily unstable. But very few clients need to depend on it. (Side note: you might think it should be called template-haskell-internal and you'd be right; but that's a disruptive change and we won't make it yet.)

These new packages already exist in GHC 9.14, and all the boot libraries now depend on them rather than on template-haskell. (Side note: while these changes have landed in the relevant boot-library repos, they have not all been released at the time of writing.) By GHC 10.2, the ghc package itself will no longer transitively depend on template-haskell.

To get the benefits, however, library authors who use Template Haskell must update their packages to depend on stable APIs, namely:

and remove dependencies on template-haskell.

More background in :

3.3 Known entities

Up to and including GHC 10.0, for every "known entity" E (function, type, or class; see Section 2.2), GHC insists that

Our recent work (which will be in 10.2) means that, for the first time, these "known entities" no longer need to be defined in a known module. Instead, E can be defined in any module of base or of ghc-internal.

Moreover, changes in base can move E from one module to another, without changing GHC. For example, using GHC 10.2 you can compile a module M against two versions of base that define E in different modules, without changing GHC itself. So how does GHC find E? It looks in the export list of a base module called GHC.Essentials. The author of base must simply ensure that GHC.Essentials exports all the known entities; but they do not need to be defined in GHC.Essentials.

This architecture properly supports (BASE goal) because it allows the maintainer of base to refactor code freely, including moving known entities from one module to another.

Better still, it also allows us to move code from ghc-internal to base. That is good because bug fixes to code in ghc-internal can only come coupled to a new GHC release, whereas bug fixes to code in base can be made and released independently of the compiler. The more code we can remove from ghc-internal and put in base, the better!

3.4 Cleaning up the base API.

For historical reasons, base exports quite a few functions that should properly be considered internal to GHC -- they have been "grandfathered" into base. For example base:GHC.Base exports mapFB, a function that is used only inside GHC's implementation of fusion for lists. Another more foundational example: base:GHC.IO exposes the representation of IO, not just the API of IO. Here is a list of all base modules with an indication of their stability and status.

Even though those exports may be historical and somewhat accidental, packages may nevertheless depend on them. That is bad all round:

The obvious question is: why not just remove these accidental exports from the API of base? Two reasons:

So there is a task here, on which we have not made much progress. We need to

A topical example is the discussion in CLC ticket #405. One comment explains that GHC.IO.Encoding and GHC.IO.Exception both say "The API of this module is unstable and not meant to be consumed by the general public", and yet these modules are imported by 132 and 287 packages respectively. So it's not easy just to remove them!

3.5 Decoupling base from GHC

Ultimately we can move to the situation where base is a separate package like any other, with its own maintainer, repository, and release cycle. In particular, the release cycle of base no longer needs to be coupled to that of GHC.

Moreover, because known entities can now be defined in base (not just in ghc-internal) lots of code can move from ghc-internal into base, so that base is no longer just a shim. This process has started but there is plenty more to do.

4. What is now possible

Because base is now reinstallable, it becomes possible to do the following.

Now any package P that compiles with GHC 9.14 against base-4.22.0.0 can also be compiled with the shiny new GHC 10.0, against base-4.22.0.1. That is: we can meet (STABILITY goal). Victory! (NB: provided the user bounds are base-4.22.0.*, P can also be compiled by ghc 9.14 against the self-same base-4.22.0.1.)

Moreover, this same pattern can be repeated when GHC 10.2 is released. Then, again in principle, one could release base-4.22.0.2 which can be compiled with GHC 9.14, or 10.0, or 10.2. Then package P can be compiled with GHC 10.2.

4.1 Caveats

There are caveats, of course

But the biggest caveat is that all this takes work.

Some of this work will be simple and routine. But it will take some care. And the more versions of GHC are supposed to compile the same base version, the more care this will take.

The good news is that

5. How you can help

GHC is an open source project. It relies utterly on the contributions of volunteers. There are a few people whose day job involves working on GHC, but most of them are working on specific projects for specific customers. Cycles are scarce.

We want to deliver on our goals: (STABILITY goal) and (BASE goal). The groundwork, which can only really be done by people deeply familiar with GHC, has now been completed. We are now in a phase where you can help; and indeed further progress relies on your help. Specifically

None of this is rocket science. It does not require intimate knowledge of GHC. But it does require care, judgement, discussion, and negotiation.

You would not be on your own. There is a small community of people to consult and discuss with; the Haskell Foundation Stability Working Group is very supportive. You would be very welcome at the weekly GHC Team video call.

If you are willing to help, please write to Rodrigo: rodrigo@well-typed.com.

6. Credits

I hope it has become clear that although the goals are very simple and clear, the path to achieving them has been far from simple. It has taken several years, involved interactions across the ecosystem (not just GHC internals), needed lots of discussion and communication, and is still on-going.

I am hugely grateful to those who have made it all possible. Specifically:

You can find some more background here and here.

Although many people have contributed, often supported by their employers, the above list makes it clear that the direct support of Well-Typed has been particularly critical to success. Thank you Well-Typed!

I'm very grateful to many people who reviewed drafts of this post and helped me to improve it, including Moritz Angermann, Manuel Bärenz, Teo Camarasu, Tobias Dammers, Trevis Elser, Adam Gundry, Wolfgang Jeltsch, Andreas Klebinger, Andrew Lelechenko, and Rodrigo Mesquita.