
Hi all, I'm wondering what the supposed storage layout of integral types is. In particular for integral types with size less than the size of a word. For example, on a 64bit machine is a 32bit integer supposed to be written as a whole word and therefore as 64 bits or just as 32bits in the payload of a closure? I'm asking because since commit be5d74ca I see differently aligned integers in the payload of a closure on a 64bit big-endian machine. For example, in the following code an Int32 object is created which contains the actual integer in the high part of the payload (the snippet comes from the add operator GHC.Int.$fNumInt32_$c+_entry): Hp = Hp + 16; ... I64[Hp - 8] = GHC.Int.I32#_con_info; I32[Hp] = _scz7::I32; whereas e.g. in function rts_getInt32 the opposite is assumed and the actual integer is expected in the low part of the payload: HsInt32 rts_getInt32 (HaskellObj p) { // See comment above: // ASSERT(p->header.info == I32zh_con_info || // p->header.info == I32zh_static_info); return (HsInt32)(HsInt)(UNTAG_CLOSURE(p)->payload[0]); } The same seems to be the case for the interpreter and foreign calls (case bci_CCALL) where integral arguments are passed in the low part of a whole word. Currently, my intuition is that the payload of a closure for an integral type with size smaller than WordSize is written as a whole word where the subword is aligned according to the machines endianness. Can someone confirm this? If that is indeed true, then rts_getInt32 seems to be correct but not the former. Otherwise the converse seems to be the case. Cheers, Stefan

On Tue, Jan 19, 2021 at 05:34:04PM +0100, Stefan Schulze Frielinghaus wrote:
Hi all,
I'm wondering what the supposed storage layout of integral types is. In particular for integral types with size less than the size of a word. For example, on a 64bit machine is a 32bit integer supposed to be written as a whole word and therefore as 64 bits or just as 32bits in the payload of a closure?
I'm asking because since commit be5d74ca I see differently aligned integers in the payload of a closure on a 64bit big-endian machine. For example, in the following code an Int32 object is created which contains the actual integer in the high part of the payload (the snippet comes from the add operator GHC.Int.$fNumInt32_$c+_entry):
Hp = Hp + 16; ... I64[Hp - 8] = GHC.Int.I32#_con_info; I32[Hp] = _scz7::I32;
whereas e.g. in function rts_getInt32 the opposite is assumed and the actual integer is expected in the low part of the payload:
HsInt32 rts_getInt32 (HaskellObj p) { // See comment above: // ASSERT(p->header.info == I32zh_con_info || // p->header.info == I32zh_static_info); return (HsInt32)(HsInt)(UNTAG_CLOSURE(p)->payload[0]); }
The same seems to be the case for the interpreter and foreign calls (case bci_CCALL) where integral arguments are passed in the low part of a whole word.
Currently, my intuition is that the payload of a closure for an integral type with size smaller than WordSize is written as a whole word where the subword is aligned according to the machines endianness. Can someone confirm this? If that is indeed true, then rts_getInt32 seems to be correct but not the former. Otherwise the converse seems to be the case.
Interestingly it looks like as if 32bit floats are also only accessed as 32bit values, whereas for characters we have that a 32bit subword is supposed to be accessed as a 64bit word: section ""data" . stg_CHARLIKE_closure" { stg_CHARLIKE_closure: const ghczmprim_GHCziTypes_Czh_con_info; const 0; ... Thus in total what I see is that the payload of a closure for I{8,16,32}# and F# are read/written according to their natural size, respectively (ignoring the inconsistency in rts_getInt* for a moment). In contrast to the payload of a C# closure which is read/written as a 64bit value although the natural size is 32bit only. Can someone confirm these observations? What is the general direction: are subwords supposed to be read/written according to their natural size only? Cheers, Stefan
participants (1)
-
Stefan Schulze Frielinghaus