What is the RuntimeRep requirements for novel Backends?
This issue was requested in the conversation of !7577 and serves as a summary of that thread. It should be the single place to discuss these issues. Please make no further comment on !7577 and feel free to edit/rename if I've missed something important.
Progenitor issue: #21078 (closed)
** ON HOLD **
Please see !7577 status
The essential problem is Does the current design of
RuntimeRep satisfy the needs of novel backends?. The discussion becomes tricky because answering that question implies more questions:
- How is equality on
RuntimeRepdefined? The definition of a given
RuntimeRepis defined by the platform, but this means that for new backends we are trying to reason about platforms we do not yet support or know about.
- Given (1) How are we to make
RuntimeRepplatform dependent and extensible for future backends? See the suggestions section below.
- We (the IOG team) believed we needed a new prim type, I (Jeff) called
JSVal. Thus with this prim type we should be able to basically copy marshalling code from the old
- So I (Jeff) implemented this new prim type. In the process of doing so it occurred to me that we needed a new
RuntimeRepto support the type. But none of the current
RuntimeRepcases support our use case:
data RuntimeRep = VecRep VecCount VecElem -- ^ a SIMD vector type | TupleRep [RuntimeRep] -- ^ An unboxed tuple of the given reps | SumRep [RuntimeRep] -- ^ An unboxed sum of the given reps | BoxedRep Levity -- ^ boxed; represented by a pointer | IntRep -- ^ signed, word-sized value | Int8Rep -- ^ signed, 8-bit value | Int16Rep -- ^ signed, 16-bit value | Int32Rep -- ^ signed, 32-bit value | Int64Rep -- ^ signed, 64-bit value | WordRep -- ^ unsigned, word-sized value | Word8Rep -- ^ unsigned, 8-bit value | Word16Rep -- ^ unsigned, 16-bit value | Word32Rep -- ^ unsigned, 32-bit value | Word64Rep -- ^ unsigned, 64-bit value | AddrRep -- ^ A pointer, but /not/ to a Haskell value | FloatRep -- ^ a 32-bit floating point number | DoubleRep -- ^ a 64-bit floating point number
Ideally we would use
AddrRep, but because
RuntimeRepthat works for
Add a new runtime rep for each backend
Use Pattern synonyms
The ability to do
does make me think @monoidal is right that erring on the side of more separate things is fine. We can always unify them later, but splitting apart is not so easy!
Points in the previous discourse
In this section I'll try to run down the major points in the thread on !7577 to consolidate the conversation.
What is wrong with old implementation?
- It is a hack using
- It is unclear to me (Jeff) why exactly this is problematic. What issues does it cause exactly? Is there something we want to do but can't due to this implementation? Is it a slow implementation? Or just conceptually wrong?
JSValin GHCJS is currently represented as
But this is a bit of a hack. At the moment we have these primitive types with their JS representation:
- Word#/Word32#: JS number
- Int#/Int32#: JS number
- ByteArray#: JS object that wraps a typed array
- Addr#: A pair of a JS number (offset) and a typed array object
Why can't we have
type Opaque# :: TYPE JSRep
type ByteArray# :: TYPE JSRepor
newtype ByteArray# a = ByteArray# Opaque#
Would that work? ... Thinking about it, I naively claim
To the untyped JS backend, every
RuntimeRepexcept the special
BoxedRepcould be treated the same.
AddrRepneeds pointer arithmetic,
BoxedRepneeds support from the RTS, I suppose. Hence they are excluded.)
That is, we could define the axioms
type Word :: TYPE DoubleRepor
- What is wrong with the
- @sgraf812 questions from above; generally, is a new prim type actually required? More specifically:
- Could we get away with changing the representation of a type rather than adding a prim type
- Could we use
type Word :: TYPE DoubleRepand still compile valid js since js is essentially untyped anyway?
Needs on the wasm side
- wasm does not need
Opaque#as I have defined it.
JSVal#in the wasm backend live on the Haskell heap and thus
BoxedRep Unliftedcan be used
- But this means the wasm backend needs special logic in GHC's GC.
JSVal#prim type is expected to be UnliftedRep, with a word-sized payload to represent a table index. The
JSVal#closures are managed by the C garbage collector. We do need additional hooks in GC, so the live
JSVal#s on the Haskell heap can be collected and reported to JS periodically though. So it seems the Opaque type design here cannot be used per-se for wasm.
Specifically because in wasm,
JSVal#s exist on the Haskell heap and thus
BoxedRep Unlifted works.
AddrRephas no special meaning in wasm. It's expected to be C memory address.
BoxedRep Unlifted, which is the representation of an unlifted, boxed pointer to the Haskell heap, managed by GHC's GC.
BoxedRep Unliftedpoints to the Haskell heap and
AddrReppoints to something in C land (or WebAsm/JS land) that the Haskell GC shouldn't need to follow.
Yes, and that's exactly what I want. All
JSVal#s exist on the Haskell heap; they do need special handling to cooperate with JS though, and whatever special handling we add is supposed to be no-op on other native platforms.
- What logic is required in the GC to collect and communicate collection of wasm
- What exactly is the definition of
newtype JSVal# = JSVal# Addr#with rep
Other major points
@bgamari notes that
AddrRepis always ignored by GHC's GC.
- That fact about
AddrRepleads us back full circle to the platform dependency of
RuntimeRep, as noted by @TerrorJack:
I agree it's a very important property.
CLRRepor whatever foreign runtime rep may all have differences re how they interact with GC.