Admin message

Due to a large amount of spam we do not allow new users to create repositories, they are "external" users. If you are a new user and want to create a repository, for example for forking GHC, open a new issue on ghc/ghc using the "get-verified" issue template

Removing the unboxed tuple size limit from GHCi bytecode
GHCi bytecode supports unboxed tuples, but only up to a certain size. Usually, on 64 platforms, tuples with up to around 64 elements can be expected to work. This used to be adequate for most practical use cases, but now that we're relying more on bytecode for optimized code for faster Template Haskell and for haskell-debugger we run into the limit, for example https://gitlab.haskell.org/ghc/ghc/-/issues/26486 # The Problem The actual maximum tuple size in elements depends on their type. Some element types are passed in registers, some only on the stack. The limit is the number of stack words used by the calling convention to return the tuple. The problem happens when we return a tuple from native code to bytecode. Some elements of the tuple will be in registers and the stack (growing down) looks as follows: ``` saved_data bytecode_object stg_ctoi_t tuple_element1 tuple_element2 tuple_element3 <- Sp ``` At this time the next continuation on the stack is called, `stg_ctoi_t`. Its task is to save the elements of the tuple in registers to the stack (the interpreter cannot see the registers) and invoke the interpreter to run `bytecode_object` The only information available to `stg_ctoi_t` is `Sp`, pointing to the topmost element (on the stack) of the tuple. It doesn't know where it is on the stack itself, and doesn't know where `bytecode_object` is either! So far we've been solving this by using specialized `stg_ctoi_tN` for each tuple size. So if we know we expect a tuple with 3 words on the stack, we make our stack look like this: ``` saved_data tuple_descriptor_bytecode_object tuple_info continuation_bytecode_object stg_ctoi_t3 tuple_element1 tuple_element2 tuple_element3 <- Sp ``` `stg_ctoi_t3` knows that `Sp` is 3 words away from itself. This means it also knows where `bytecode_object` is (next slot). Additionally we have at a known distance from `stg_ctoi_tN`: A `tuple_info` word on the stack, that describes which registers need to be saved, and a `tuple_descriptor_bytecode_object` that describes the returned tuple to the storage manager. This means we have enough info to prepare the stack for `bytecode_object` to take over. However, we need a `stg_ctoi_tN` for each size tuple we support. The `stg_ctoi_tN` are compiled code written in Cmm. Unfortunately space constraints prevent us from including an infinite number of `stg_ctoi_tN` in the RTS. # The fix ## Move `Sp` to a better location? What if we returned the tuple as follows when calling `stg_ctoi_t`? ``` saved_data tuple_info bytecode_object stg_ctoi_t <- Sp tuple_element1 tuple_element2 tuple_element3 ``` Then `stg_ctoi_t` can find itself on the stand by extension `tuple_info` and everything else it needs to prepare the stack frame for the interpreter. This works for most frames that know their arguments. We just need to track the top of the stack a bit differently, for stack check purposes. However stack underflow frames actually do need to know the top of the stack. I don't know if we can implement them. ## Compile more `stg_ctoi_tN` as needed We could dynamically compile more `stg_ctoi_tN` when we handle a `bci_PUSH_ALTS_T` instruction. This requires us to create new executable code, so it may restrict us on platforms that require code signing and block allocating writable+executable memory. Note: all of the problematic returns are compiled->interpreted code. If we keep track of used tuple sizes in our compiled code we may be able to precompile all the needed `stg_ctoi_tN`. But tracking this for code not compiled by GHC would be difficult. ## Make large tuples self-descriptive Change calling convention to return a tuple to include the `tuple_size` on the stack for large tuples. Like this: ``` saved_data tuple_info bytecode_object stg_ctoi_t tuple_element1 tuple_element2 ... tuple_element50 tuple_element51 tuple_size (51) <- Sp ``` The downside is that all native code needs to be compiled with this convention: We get a bit of overhead and an awkard small/large split. ## Store tuple return info in TSO Besides `Sp`, `stg_ctoi_t` has access to the Thread State Object. We can store the size of the next expected tuple in `tso->ctoi_spill_words`. This means that the calling convention can stay the same. However we do have to ensure that `ctoi_spill_words` stays valid for nested calls with tuple returns and through stack unwinding/replaying for exceptions and continuations. It's a bit similar to maintaining masking state. (I've implemented this one) ## Something else? Perhaps I've overlooked the best way to return tuples. Let me know in a comment.
issue