Wow, but this seems incredibly fragile to me. touch# is a primop that is very magical and, in the face of simplification, prone to being eliminated by the compiler. I remember some bug in which the compiler eliminated the touch# after deducing that the code is dead. Chaos ensues.
If it were me, I'd just keep a random object inside the compact region, and then refer to that object in the regular way (i.e. not via the custom hash table of raw addresses) in order to keep the compact region alive.
In fact, it seems that the main purpose of that complicated ST-like RankNTypes dance is to ensure liveness of the compact region, when objects in the compact region are being referred to using raw addresses. Then why not just keep some regular Haskell value inside the compact region and use that to keep track of regions instead of phantom types? That is, every `Entry` now contains some token that is stored inside the compact region, as well as the address of the data it actually points to. Then, whenever any Entry is reachable in GC, the entire compact region is reachable in GC.
Am I missing something?
The ST-like region typing can be still useful though, because we get a static bound on the lifetime of a compact region. We'd prefer to avoid leaking space via large compact regions which survive beyond their intended life.
Of course the obvious down side is you have to remember to manually deallocate it :-)