GHC issueshttps://gitlab.haskell.org/ghc/ghc/-/issues2024-03-25T03:08:10Zhttps://gitlab.haskell.org/ghc/ghc/-/issues/22255Awkward design around pinned ByteArray# and compaction2024-03-25T03:08:10ZMatthew Cravenclyring@gmail.comAwkward design around pinned ByteArray# and compactionAccording to the documentation for `GHC.Compact`:
> Pinned `ByteArray#` objects cannot be compacted. This is for a good reason: the memory is pinned so that it can be referenced by address (the address might be stored in a C data struct...According to the documentation for `GHC.Compact`:
> Pinned `ByteArray#` objects cannot be compacted. This is for a good reason: the memory is pinned so that it can be referenced by address (the address might be stored in a C data structure, for example), so we can't make a copy of it to store in the `Compact`.
We also provide a primop `isByteArrayPinned#` to allow users to opportunistically exploit existing pinned-ness of `ByteArray#`s to avoid copying when using the FFI or `Storable`.
However, these features disagree about when a `ByteArray#` is pinned:
- Compaction considers a `ByteArray#` to be pinned only if it was explicitly pinned at creation-time with `newPinnedByteArray#` or `newAlignedPinnedByteArray#`.
- In addition to explicitly pinned `ByteArray#`s, `isByteArrayPinned#` considers `ByteArray#`s in large-object heap blocks or in compact regions to be pinned.
This mismatch can easily lead to memory-unsafety. Here's a small example program:
<details>
```haskell
import qualified Data.ByteString.Short as SBS
import qualified Data.ByteString.Char8 as BS
import GHC.Compact (compact, getCompact)
import Control.Monad (forM_)
import System.Mem (performMajorGC)
main :: IO ()
main = do
x <- compact (SBS.fromShort (SBS.replicate 8000 48))
let printCompact v = BS.putStrLn (BS.take 20 (getCompact v))
printCompact x
performMajorGC
forM_ [8000..9000] $ \i -> compact (SBS.fromShort (SBS.replicate i 49))
printCompact x
```
</details>
This program _seems_ innocent. But under the hood, `SBS.replicate 8000 48` creates a large pinned-but-not-_explicitly_-pinned `ByteArray#`, and `SBS.fromShort` (since `bytestring-0.11.1.0`) sees that it is pinned and opportunistically creates a non-GC reference into it instead of making a copy. Then the underlying buffer is copied into the compact region `x` without issue since it was not explicitly pinned, but the non-GC reference in the `ByteString` cannot be updated and becomes a dangling pointer. (The subsequent code tries to demonstrate this by making two calls to `printCompact x` produce different output, though of course this isn't truly guaranteed to work.)
Of course, `GHC.Compact` is not a "Safe Haskell" module and `ByteString`s are typically not compactible. But it's still a little unsatisfactory that this performs unsafe memory accesses instead of raising a `CompactionFailed` exception. Here are a few options:
2. Prevent compaction of these _implicitly_ pinned `ByteArray#`s.
1. Do nothing, and just document this infelicity.
3. Weaken `isByteArrayPinned#` to only consider _explicitly_ pinned `ByteArray#`s.
4. Provide a primop that makes an implictly pinned `ByteArray#` explicitly pinned, without copying its contents.
5. Distinguish between `ByteArray#` references (which may happen to refer to pinned objects) and `PinnedByteArray#` references, and only refuse to compact the latter. (This means the distinction must exist at runtime. Perhaps both can be references to `ARR_WORDS` heap objects, but with different pointer tags?)https://gitlab.haskell.org/ghc/ghc/-/issues/19454evacuate: strange closure type 20043649 (something to do with compact regions?)2021-03-01T22:22:57ZMatthew Pickeringevacuate: strange closure type 20043649 (something to do with compact regions?)I am getting the following error sometimes when using my branch of GHC to build Cabal which compacts modifaces. Seems to be something to do with `compactAddWithSharing`.
Branch is here, https://gitlab.haskell.org/ghc/ghc/-/commits/wip/...I am getting the following error sometimes when using my branch of GHC to build Cabal which compacts modifaces. Seems to be something to do with `compactAddWithSharing`.
Branch is here, https://gitlab.haskell.org/ghc/ghc/-/commits/wip/compact-modiface-new
The problem seems to happen quite late in the compilation so perhaps there is something overflowing. I would start looking in `evacuate_hash_entry` I think. It also doesn't always happen..
```
ghc: internal error: evacuate: strange closure type 20043649
Stack trace:
0x7f84eb1801b2 set_initial_registers (rts/Libdw.c:294.5)
0x7f84eacc1198 dwfl_thread_getframes (/nix/store/sv6f05ngaarba50ybr6fdfc7cciv6nbv-elfutils-0.176/lib/libdw-0.176.so)
0x7f84eacc0c5b get_one_thread_cb (/nix/store/sv6f05ngaarba50ybr6fdfc7cciv6nbv-elfutils-0.176/lib/libdw-0.176.so)
0x7f84eacc0f97 dwfl_getthreads (/nix/store/sv6f05ngaarba50ybr6fdfc7cciv6nbv-elfutils-0.176/lib/libdw-0.176.so)
0x7f84eacc1567 dwfl_getthread_frames (/nix/store/sv6f05ngaarba50ybr6fdfc7cciv6nbv-elfutils-0.176/lib/libdw-0.176.so)
0x7f84eb180879 libdwGetBacktrace (rts/Libdw.c:265.8)
0x7f84eb1888fd rtsFatalInternalErrorFn (rts/RtsMessages.c:170.6)
0x7f84eb188add barf (rts/RtsMessages.c:49.3)
0x7f84eb172681 evacuate1 (rts/sm/Evac.c:1061.5)
0x7f84eb1ad730 evacuate_hash_entry (rts/sm/Scav.c:176.5)
0x7f84eb17c2dc mapHashTable (rts/Hash.c:466.76)
0x7f84eb1ad900 scavenge_compact1 (rts/sm/Scav.c:196.9)
0x7f84eb1ae1c5 scavenge_one (rts/sm/Scav.c:1573.9)
0x7f84eb1aefcb scavenge_loop1 (rts/sm/Scav.c:2039.12)
0x7f84eb1a54ad scavenge_until_all_done (rts/sm/GC.c:1274.13)
0x7f84eb1a6c3b GarbageCollect (rts/sm/GC.c:1498.22)
0x7f84eb18bb09 scheduleDoGC (rts/Schedule.c:1892.9)
0x7f84eb18c7ca schedule (rts/Schedule.c:579.7)
0x7f84eb18df31 scheduleWaitThread (rts/Schedule.c:2657.11)
0x7f84eb18875d hs_main (rts/RtsMain.c:73.18)
0x436902 (null) (/home/matt/ghc-compact-iface/_build/stage1/bin/ghc)
0x7f84eae5bd8b __libc_start_main (/nix/store/xg6ilb9g9zhi2zg1dpi4zcp288rhnvns-glibc-2.30/lib/libc-2.30.so)
0x43694a _start (../sysdeps/x86_64/start.S:122.0)
(GHC version 9.1.20210226 for x86_64_unknown_linux)
Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug
```https://gitlab.haskell.org/ghc/ghc/-/issues/18709Teach GHC about the evaluatedness of compacted things2020-09-17T21:02:40ZDavid FeuerTeach GHC about the evaluatedness of compacted things## Motivation
The result values of `compactAdd#` and `compactAddWithSharing#` are always in normal form. Unfortunately, the compiler doesn't know that.
## Proposal
Use the same trickery that `seq#` employs to teach GHC what's what. Be...## Motivation
The result values of `compactAdd#` and `compactAddWithSharing#` are always in normal form. Unfortunately, the compiler doesn't know that.
## Proposal
Use the same trickery that `seq#` employs to teach GHC what's what. Be sure to make the pointer field in `Compact` strict to take advantage of this.https://gitlab.haskell.org/ghc/ghc/-/issues/18706inCompact and isCompact don't seem to work2020-10-23T01:27:31ZDavid FeuerinCompact and isCompact don't seem to work## inCompact and isCompact always seem to return False
`inCompact` is supposed to indicate whether a certain value is in a
particular region. `isCompact` is supposed to indicate whether a
certain value is in any compact region. I have n...## inCompact and isCompact always seem to return False
`inCompact` is supposed to indicate whether a certain value is in a
particular region. `isCompact` is supposed to indicate whether a
certain value is in any compact region. I have not been able to
convince either of these to return `True` under any circumstances.
## Steps to reproduce
```haskell
import Data.Compact
import Control.Exception (evaluate)
import Control.DeepSeq
import System.Mem
main = do
p <- evaluate $ force [1..10000 :: Integer]
foo <- compact p
performMajorGC -- Even this doesn't do the trick.
isCompact p >>= print
inCompact foo p >>= print
print . sum $ getCompact foo
```
This prints `False` twice before successfully summing and printing the contents of `foo`.
Optimization level makes no difference.
## Expected behavior
I expect this to print `True` twice. Even if `isCompact` is too hard to implement, I would at least expect `inCompact` to work.
## Environment
* GHC version used: 8.10.1
Optional:
* Operating System:
* System Architecture:https://gitlab.haskell.org/ghc/ghc/-/issues/18283T16992 fails with debug runtime2021-01-07T05:09:26ZÖmer Sinan AğacanT16992 fails with debug runtime```
$ ghc-stage2 T16992.hs -O -fforce-recomp -debug
[1 of 1] Compiling Main ( T16992.hs, T16992.o )
Linking T16992 ...
$ ./T16992
T16992: internal error: ASSERTION FAILED: file rts/sm/Sanity.c, line 1018
(GHC version 8....```
$ ghc-stage2 T16992.hs -O -fforce-recomp -debug
[1 of 1] Compiling Main ( T16992.hs, T16992.o )
Linking T16992 ...
$ ./T16992
T16992: internal error: ASSERTION FAILED: file rts/sm/Sanity.c, line 1018
(GHC version 8.11.0.20200530 for x86_64_unknown_linux)
Please report this as a GHC bug: https://www.haskell.org/ghc/reportabug
zsh: abort (core dumped) ./T16992
```https://gitlab.haskell.org/ghc/ghc/-/issues/18046When importCompactByteStrings returns Nothing is unclear2020-04-13T15:00:44ZÖmer Sinan AğacanWhen importCompactByteStrings returns Nothing is unclearDocumentation of `GHC.Compact.Serialized.importCompactByteStrings`:
> Convenience function for importing a compact region that is represented by a list of strict 'ByteString's.
This says nothing about when it returns nothing. Type of t...Documentation of `GHC.Compact.Serialized.importCompactByteStrings`:
> Convenience function for importing a compact region that is represented by a list of strict 'ByteString's.
This says nothing about when it returns nothing. Type of the function:
```haskell
importCompactByteStrings :: SerializedCompact a -> [ByteString.ByteString] -> IO (Maybe (Compact a))
```
Looking at the code, this calls the function `sanityCheckByteStrings` and when it returns `False`, `importCompactByteStrings` returns `Nothing`. However it's also unclear what does `sanityCheckByteStrings` do.
We should document what does it mean for `importCompactByteStrings` to return `Nothing`.https://gitlab.haskell.org/ghc/ghc/-/issues/17960LitString, ByteString, FastString and ShortByteString with relevance to compa...2020-04-03T10:26:59ZMatthew PickeringLitString, ByteString, FastString and ShortByteString with relevance to compactingIn the `Literal` data type, `LitString` is used to represented string literals. In the old days this was just string literals which a user would write in the program, so usually quite short strings. These strings are currently
represent...In the `Literal` data type, `LitString` is used to represented string literals. In the old days this was just string literals which a user would write in the program, so usually quite short strings. These strings are currently
represented by a `ByteString`.
In recent times @hsyl20 has modified Template Haskell (!141) in order to directly construct a `LitString` from a `ByteString`. This means that you can now construct very big `LitString`s by embedding whole files into a `LitString`.
Now that we are trying to compact the `ModIface` (#17097), we have to change the `LitString` representation to something compactable, for now I changed it to `FastString`, but this isn't ideal as now @hsyl20 s big files will be loaded into memory rather than just existing as a pointer for the whole compilation pipeline. It is unlikely that any of his big strings would make it into
an interface file anyway because they are too big to include in an unfolding.
So the proposal is something like the following:
* Back a `LitString` by a `FastString`, `ShortByteString` or `Text` so it can be compacted. There is quite a lot of manipulation of `LitString` that happens so I went with `FastString` for now. Perhaps `Text` would be a better choice.
* Introduce a new form of literal, `LitBytes` which is not part of the `Literal` data type, and must exist only at the top-level. This way, the unfolding can never be exposed for something of `LitBytes`. This can be backed by a `ByteString`. This will also mean the `LitBytes` is not copied into an interface file.
* Use `LitBytes` as a target for the TH file embedding stuff rather than `LitString`.
This will mean that we can compact a `Literal`, but without having to copy the big `LitString`s created from embedding files into memory.
cc @hsyl20https://gitlab.haskell.org/ghc/ghc/-/issues/17734Functions in compact regions2020-03-31T07:47:02ZVladislav ZavialovFunctions in compact regions```
ghci> compact even
*** Exception: compaction failed: cannot compact functions
```
Why is that? Functions can be stored in compact regions as pointers to the code together with data captured in the closure.```
ghci> compact even
*** Exception: compaction failed: cannot compact functions
```
Why is that? Functions can be stored in compact regions as pointers to the code together with data captured in the closure.https://gitlab.haskell.org/ghc/ghc/-/issues/17097Explore putting persistent data structures into compact region2021-04-09T11:01:03ZMatthew PickeringExplore putting persistent data structures into compact region@AndreasK and @bgamari and I (and Hanue) pondered whether it would be worthwhile putting the `ModDetails` into a compact region. The note explains that the information doesn't change and is essentially a cache. Putting it into a compact ...@AndreasK and @bgamari and I (and Hanue) pondered whether it would be worthwhile putting the `ModDetails` into a compact region. The note explains that the information doesn't change and is essentially a cache. Putting it into a compact region would reduce GC pressure.
It would also be potentially worthwhile to use fixed size arrays rather than lists for its fields.
There are probably other data structures which would benefit from the treatment, perhaps `ModIface`?
----------------------
Update: 2020-03-24
A proof of concept that compacting is possible is present on [this branch](https://gitlab.haskell.org/ghc/ghc/-/tree/wip/compact-iface)
# Roadmap to Compacting a `ModIface`.
* [x] Use a type for `FastString` backed by an unpinned ByteArray to implement FastString (!1675) (Owner @DanielG)
* [x] (Optional) Reduce allocations as a result of !1675 by using `ShortByteString` in more places rather than converting back to a pinned `ByteString`.
* [ ] (Optional) Remove use of `FastString` from code gen, in order to be able to remove the cached `length` field and cached Z-encoded string from `FastString`. (!2949, owner @sgraf812)
* [ ] Remove `TyThing` from `WiredIn` names, see (!2598, owner @mpickering )
* [ ] Introduce `IfaceLiteral` type to remove the use of `Type` in `Literal`. (https://gitlab.haskell.org/ghc/ghc/-/commit/aa293b8959cd27efe2e5146cb8d8f77d0dbb706a#bd4216a09ad2640db4a06e7b5a593a1a94e884fa_15_16, owner @mpickering)
* [ ] Separate cache fields from the `ModIface`, they are functions so can't be compacted (https://gitlab.haskell.org/ghc/ghc/-/commit/dbe6222995e583981a9c660c0935bf6141882a36, owner @mpickering )https://gitlab.haskell.org/ghc/ghc/-/issues/15652SerializedCompact has a [(Ptr a, Word)] instead of a custom datatype2021-01-24T06:20:32ZchessaiSerializedCompact has a [(Ptr a, Word)] instead of a custom datatype```hs
data SerializedCompact a = SerializedCompact
{ serializedCompactBlockList :: [(Ptr a, Word)]
, serializedCompactRoot :: Ptr a
}
```
I'm not sure why the first member of `SerializedCompact` isn't something like
```hs
data Co...```hs
data SerializedCompact a = SerializedCompact
{ serializedCompactBlockList :: [(Ptr a, Word)]
, serializedCompactRoot :: Ptr a
}
```
I'm not sure why the first member of `SerializedCompact` isn't something like
```hs
data CompactBlock a = CompactBlock {-# UNPACK #-} (Ptr a) {-# UNPACK #-} Word
```
so the `Ptr` can unpack into the constructor, which isn't possible with `(,)`. `SerializedCompact` would then look like
```hs
data SerializedCompact a = SerializedCompact
{ serializedCompactBlockList :: [CompactBlock a]
, serializedCompactRoot :: Ptr a
}
```
<details><summary>Trac metadata</summary>
| Trac field | Value |
| ---------------------- | ------------------ |
| Version | 8.4.3 |
| Type | Bug |
| TypeOfFailure | OtherFailure |
| Priority | normal |
| Resolution | Unresolved |
| Component | libraries/compact |
| Test case | |
| Differential revisions | |
| BlockedBy | |
| Related | |
| Blocking | |
| CC | andrewthad, ezyang |
| Operating system | |
| Architecture | |
</details>
<!-- {"blocked_by":[],"summary":"SerializedCompact has a [(Ptr a, Word)] instead of a custom datatype","status":"New","operating_system":"","component":"libraries/compact","related":[],"milestone":"8.8.1","resolution":"Unresolved","owner":{"tag":"OwnedBy","contents":"ezyang"},"version":"8.4.3","keywords":["compact","ghc-compact,","regions"],"differentials":[],"test_case":"","architecture":"","cc":["andrewthad","ezyang"],"type":"Bug","description":"{{{#!hs\r\ndata SerializedCompact a = SerializedCompact\r\n { serializedCompactBlockList :: [(Ptr a, Word)]\r\n , serializedCompactRoot :: Ptr a\r\n }\r\n}}}\r\n\r\nI'm not sure why the first member of {{{SerializedCompact}}} isn't something like\r\n\r\n{{{#!hs\r\ndata CompactBlock a = CompactBlock {-# UNPACK #-} (Ptr a) {-# UNPACK #-} Word\r\n}}}\r\n\r\nso the {{{Ptr}}} can unpack into the constructor, which isn't possible with {{{(,)}}}. {{{SerializedCompact}}} would then look like\r\n\r\n{{{#!hs\r\ndata SerializedCompact a = SerializedCompact\r\n { serializedCompactBlockList :: [CompactBlock a]\r\n , serializedCompactRoot :: Ptr a\r\n }\r\n}}}","type_of_failure":"OtherFailure","blocking":[]} -->Edward Z. YangEdward Z. Yanghttps://gitlab.haskell.org/ghc/ghc/-/issues/13165Speed up the RTS hash table2020-12-21T04:09:30ZdobenourSpeed up the RTS hash tableThe RTS hash table is rather slow. Every lookup involves at least one indirect call (to get a hash code). It also uses separate chaining, which is itself slow.
Until recently, this didn't really matter, since the RTS hash table wasn't u...The RTS hash table is rather slow. Every lookup involves at least one indirect call (to get a hash code). It also uses separate chaining, which is itself slow.
Until recently, this didn't really matter, since the RTS hash table wasn't used for anything particularly performance critical other than `StableName` operations. However, it has since become the bottleneck when compacting with sharing, to the point that compacting without sharing is around 10x faster and is thus the default.
Fortunately, there are easy ways to make the hash table faster. These include:
- Use linear probing instead of separate chaining.
- Specialize for the case of pointer keys
- Don't use indirect calls for hashing
- Use a fast, universal hash function for pointers.
- Use SSE instructions where available to do fast searches.
- Minimize the number of indirections in the use of the table.
In both the case of the StablePtr table and that of compact regions, the keys of the table are not GC pointers, but the values are, so there needs to be a way to ensure that the GC handles the table correctly8.6.1https://gitlab.haskell.org/ghc/ghc/-/issues/19095compact regions: Add a hash-consing variant2021-03-05T09:36:27ZMatthew Pickeringcompact regions: Add a hash-consing variantThere are currently two ways to compact an object.
1. Normal way, don't share anything.
2. Sharing preserving, preserve existing sharing
It has been discussed a few times about adding another variant which performs hash-consing when a...There are currently two ways to compact an object.
1. Normal way, don't share anything.
2. Sharing preserving, preserve existing sharing
It has been discussed a few times about adding another variant which performs hash-consing when adding closures into the compact. Typically there are can be a lot of identical allocated closures which are not shared so putting them into a compact and hash-consing them could be a good way to reduce the memory footprint of an application.
One particular example of this happening in GHC is there are many identical `TyConApp` nodes allocated in memory as no special effort is made to introduce sharing except for a some special cases.https://gitlab.haskell.org/ghc/ghc/-/issues/17640compactAdd run time grows linearly with compact region size for certain inputs2020-09-28T12:21:35ZFabian ThorandcompactAdd run time grows linearly with compact region size for certain inputs## Summary
For objects of certain sizes `compactAdd` is linear in the number of already added objects.
When adding multiple such objects, the total time for adding them all grows quadratically.
## Steps to reproduce
The following repr...## Summary
For objects of certain sizes `compactAdd` is linear in the number of already added objects.
When adding multiple such objects, the total time for adding them all grows quadratically.
## Steps to reproduce
The following reproducer adds the value of type `X` into a compact region `n` times, and then again `2n` times into a different compact region. The total time quadruples, even though the number of `compactAdd`s was only doubled.
```haskell
{-# OPTIONS_GHC -O2 #-}
import Control.Monad (replicateM_)
import GHC.Compact (compact, compactAdd, compactSize)
import System.CPUTime (getCPUTime)
import Data.Word (Word64)
import Text.Printf (printf)
import System.Environment (getArgs)
data X = X !Word64 !Word64 !Word64 !Word64 !Word64 !Word64 !Word64 !Word64
main :: IO ()
main = do
-- Use this in the object we put in the compact region,
-- in order to prevent GHC from making it a CAF
-- (which are not added to compact regions).
blackboxInput <- length <$> getArgs
let
-- Build a value that is larger than the allowed remaining free space
-- before a block is considered full.
obj = X (fromIntegral blackboxInput) 0 0 0 0 0 0 0
numAdd = 200000
c1 <- compact ()
c2 <- compact ()
-- Copy the above object multiple times in each compact region
-- and measure how long it takes.
printf "Calling compactAdd %d times\n" numAdd
start1 <- getCPUTime
replicateM_ numAdd (compactAdd c1 obj)
end1 <- getCPUTime
printf "Calling compactAdd %d times\n" (2 * numAdd)
start2 <- getCPUTime
replicateM_ (2 * numAdd) (compactAdd c2 obj)
end2 <- getCPUTime
-- Print the sizes of the compact regions as a sanity check
-- The 2nd should be twice as large as the first
putStrLn "Compact region sizes (sanity check)"
compactSize c1 >>= print
compactSize c2 >>= print
let
ps1 = end1 - start1
ps2 = end2 - start2
printf "1st: %d ps total, %f ps/obj\n" ps1 (realToFrac ps1 / realToFrac numAdd :: Double)
printf "2nd: %d ps total, %f ps/obj\n" ps2 (realToFrac ps2 / realToFrac (2 * numAdd) :: Double)
-- Note that the time per compactAdd for the second run is roughly
-- twice as large as for the first run.
-- The total time has quadrupled, even though the number of
-- compactAdd calls only doubled.
```
To me it looks like the cause is the linear search for a free block in `allocateForCompact` that is triggered when the object does not fit into the nursery block, but the nursery block isn't full either:
https://gitlab.haskell.org/ghc/ghc/blob/ghc-8.6.5-release/rts/sm/CNF.c#L516-524
Note that `-O2` is required so that the value of type `X` is actually a single heap
object. Otherwise, GHC doesn't seem to unpack the fields, and the small
boxed `Word64` fill up the nursery block eventually which prevents this
bug from triggering.
## Expected behavior
The time it takes for adding the objects to the compact region should grow linearly with the number of objects, not quadratically.
## Environment
* GHC version used: 8.8.1, 8.6.5
Optional:
* Operating System: Ubuntu 18.04.3 LTS
* System Architecture: x86_64