Bitfield packing depends on the order of member type sizes and does not properly and consistently pack bitfields.
Affects:
All: FBC32, FBC64, GEN GAS, GEN GCC
Description:
A bitfield with different access type sizes where members are ordered in descending order based on the bitsize is properly padded and behaves as expected.
A bitfield with different access type sizes where members are ordered in ascending order based on the bitsize is not packed at all, instead acting as a regular type non bitfield. Which is inconsistent and unexpected behaviour.
In addition, the documentation states that bitfields are packed:
(Via: https://www.freebasic.net/wiki/KeyPgType)
It makes no mention of bitfield members having to all be the same access type in order to actually be packed and work -as expected/-consistently. Nor having to order them in descending size if you want different access types.Bitfield members in a type are packed together, unless the next member is a non-bitfield (nested union is considered a non-bitfield).
Consequence:
Makes porting several headers and libraries all but impossible as they rely on packed bitfields of different access types being passed back and forth.
Replicate:
Code: Select all
#include once "crt/mem.bi"
scope
print "With different members"
type DescendingBits field = 1
Reserved:31 as ulong
AutoDealloc:1 as ubyte
end type
type AscendingBits field = 1
AutoDealloc:1 as ubyte
Reserved:31 as ulong
end type
dim as DescendingBits descbits
dim as AscendingBits ascbits
dim as any ptr descptr = @descbits
dim as any ptr ascptr = @ascbits
descbits.Reserved = &h7fffffff
descbits.AutoDealloc = 1
ascbits.Reserved = &h7fffffff
ascbits.AutoDealloc = 1
print " Bitfields:"
print " Desc: "& bin(*cptr(ulong ptr, descptr), 32)
print " Asc:32: "& bin(*cptr(ulong ptr, ascptr), 32)
print " Asc:40: "& bin(*cptr(ulongint ptr, ascptr), 40) '// Not out of bounds since we only access the first 5 bytes, which is the size of AscendingBits
'// Save all the flags we've set, we assume they fit in a 32bit because the flags are 32bits
dim as ulong descflags = *cptr(ulong ptr, descptr)
dim as ulong ascflags = *cptr(ulong ptr, ascptr)
'// Actually save the ascflags using the actual size and unwanted padding
dim as any ptr ascflagsfull = callocate(5) '// Actual size is 40bits, 5 bytes
memcpy(ascflagsfull, ascptr, 5)
'// Clear all
descbits.Reserved = 0
descbits.AutoDealloc = 0
ascbits.Reserved = 0
ascbits.AutoDealloc = 0
'// Load them back in from the 32bit saved value which should fit them all
*cptr(ulong ptr, descptr) = descflags
*cptr(ulong ptr, ascptr) = ascflags
print " Desc"
print " SizeOf: "& sizeof(descbits)
print " Bits:32: "& bin(*cptr(ulong ptr, descptr), 32)
print " Flags: "& descflags
print " :31: "& hex(descbits.Reserved)
print " :1: "& descbits.AutoDealloc
print " Asc (Note incorrect size and incorrect value of :31)"
print " SizeOf: "& sizeof(ascbits)
print " Bits:32: "& bin(*cptr(ulong ptr, ascptr), 32)
print " Bits:40: "& bin(*cptr(ulongint ptr, ascptr), 40)
print " Flags: "& ascflags
print " :31: "& hex(ascbits.Reserved)
print " :1: "& ascbits.AutoDealloc
'// Clear ascbits
ascbits.Reserved = 0
ascbits.AutoDealloc = 0
'// Load in "full"
memcpy(ascptr, ascflagsfull, 5)
deallocate ascflagsfull
print " Asc (40bit memcpy)"
print " SizeOf: "& sizeof(ascbits)
print " Bits:32: "& bin(*cptr(ulong ptr, ascptr), 32)
print " Bits:40: "& bin(*cptr(ulongint ptr, ascptr), 40)
print " Flags: "& ascflags
print " :31: "& hex(ascbits.Reserved)
print " :1: "& ascbits.AutoDealloc
end scope
scope
print "With both members as ulong"
'// However, if both members are set to ulong it works as expected for both cases:
type DescendingBits field = 1
Reserved:31 as ulong
AutoDealloc:1 as ulong
end type
type AscendingBits field = 1
AutoDealloc:1 as ulong
Reserved:31 as ulong
end type
dim as DescendingBits descbits
dim as AscendingBits ascbits
dim as any ptr descptr = @descbits
dim as any ptr ascptr = @ascbits
descbits.Reserved = &h7fffffff
descbits.AutoDealloc = 1
ascbits.Reserved = &h7fffffff
ascbits.AutoDealloc = 1
print " Bitfields:"
print " Desc: "& bin(*cptr(ulong ptr, descptr), 32)
print " Asc: "& bin(*cptr(ulong ptr, ascptr), 32)
'// Save all the flags we've set, and now they both actually fit in 32bits
dim as ulong descflags = *cptr(ulong ptr, descptr)
dim as ulong ascflags = *cptr(ulong ptr, ascptr)
'// Clear all
descbits.Reserved = 0
descbits.AutoDealloc = 0
ascbits.Reserved = 0
ascbits.AutoDealloc = 0
'// Load them back in from the 32bit saved value which should fit them all
*cptr(ulong ptr, descptr) = descflags
*cptr(ulong ptr, ascptr) = ascflags
print " Desc"
print " SizeOf: "& sizeof(descbits)
print " Bits: "& bin(*cptr(ulong ptr, descptr), 32)
print " Flags: "& descflags
print " :31: "& hex(descbits.Reserved)
print " :1: "& descbits.AutoDealloc
print " Asc"
print " SizeOf: "& sizeof(ascbits)
print " Bits: "& bin(*cptr(ulong ptr, ascptr), 32)
print " Flags: "& ascflags
print " :31: "& hex(ascbits.Reserved)
print " :1: "& ascbits.AutoDealloc
end scope
Code: Select all
With different members
Bitfields:
Desc: 11111111111111111111111111111111
Asc:32: 11111111111111111111111100000001
Asc:40: 0111111111111111111111111111111100000001
Desc
SizeOf: 4
Bits:32: 11111111111111111111111111111111
Flags: 4294967295
:31: 7FFFFFFF
:1: 1
Asc (Note incorrect size and incorrect value of :31)
SizeOf: 5
Bits:32: 11111111111111111111111100000001
Bits:40: 0000000011111111111111111111111100000001
Flags: 4294967041
:31: FFFFFF
:1: 1
Asc (40bit memcpy)
SizeOf: 5
Bits:32: 11111111111111111111111100000001
Bits:40: 0111111111111111111111111111111100000001
Flags: 4294967041
:31: 7FFFFFFF
:1: 1
With both members as ulong
Bitfields:
Desc: 11111111111111111111111111111111
Asc: 11111111111111111111111111111111
Desc
SizeOf: 4
Bits: 11111111111111111111111111111111
Flags: 4294967295
:31: 7FFFFFFF
:1: 1
Asc
SizeOf: 4
Bits: 11111111111111111111111111111111
Flags: 4294967295
:31: 7FFFFFFF
:1: 1