refactor sizealignoffset (#12077)

* small refactoring
* refactor computeObjectOffsetFoldFunction with AccumObject
* refactor packed object offstes fold function
* refactor compute union object offsets fold function
* merge normal/packed object offset fold function
* compiletime offsetof in c++ inheritance objects
* enable c++ inheritance offsetof tests
* correct alignment for big sets/enums on weird 32bit platforms
* uncomputedSize -> unknownSize
* workaround for travis
* fixes win32 alignment problems
This commit is contained in:
Andreas Rumpf 2019-08-28 19:36:29 +02:00 • committed by GitHub
commit 21fc8b4d4d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 198 additions and 230 deletions

View file

@ -7,6 +7,7 @@
#
## code owner: Arne Döring
## e-mail: arne.doering@gmx.net
## included from types.nim
proc align(address, alignment: BiggestInt): BiggestInt =
result = (address + (alignment - 1)) and not (alignment - 1)
@ -14,7 +15,6 @@ proc align(address, alignment: BiggestInt): BiggestInt =
proc align(address, alignment: int): int =
result = (address + (alignment - 1)) and not (alignment - 1)
const
## a size is concidered "unknown" when it is an imported type from C
## or C++.
@ -39,6 +39,13 @@ proc inc(arg: var OffsetAccum; value: int) =
else:
arg.offset += value
proc alignmentMax(a,b: int): int =
if unlikely(a == szIllegalRecursion or b == szIllegalRecursion): raiseIllegalTypeRecursion()
if a == szUnknownSize or b == szUnknownSize:
szUnknownSize
else:
max(a,b)
proc align(arg: var OffsetAccum; value: int) =
if unlikely(value == szIllegalRecursion): raiseIllegalTypeRecursion()
if value == szUnknownSize or arg.maxAlign == szUnknownSize or arg.offset == szUnknownSize:
@ -48,11 +55,22 @@ proc align(arg: var OffsetAccum; value: int) =
arg.maxAlign = max(value, arg.maxAlign)
arg.offset = align(arg.offset, value)
proc finish(arg: var OffsetAccum) =
if arg.maxAlign == szUnknownSize or arg.offset == szUnknownSize:
proc mergeBranch(arg: var OffsetAccum; value: OffsetAccum) =
if value.maxAlign == szUnknownSize or arg.maxAlign == szUnknownSize or
value.offset == szUnknownSize or arg.offset == szUnknownSize:
arg.maxAlign = szUnknownSize
arg.offset = szUnknownSize
else:
arg.offset = align(arg.offset, arg.maxAlign)
arg.offset = max(arg.offset, value.offset)
arg.maxAlign = max(arg.maxAlign, value.maxAlign)
proc finish(arg: var OffsetAccum): int =
if arg.maxAlign == szUnknownSize or arg.offset == szUnknownSize:
result = szUnknownSize
arg.offset = szUnknownSize
else:
result = align(arg.offset, arg.maxAlign) - arg.offset
arg.offset += result
proc computeSizeAlign(conf: ConfigRef; typ: PType)
@ -93,154 +111,69 @@ proc setOffsetsToUnknown(n: PNode) =
for i in 0 ..< safeLen(n):
setOffsetsToUnknown(n[i])
proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode,
initialOffset: BiggestInt): tuple[offset, align: BiggestInt] =
proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, packed: bool, accum: var OffsetAccum): void =
## ``offset`` is the offset within the object, after the node has been written, no padding bytes added
## ``align`` maximum alignment from all sub nodes
assert n != nil
if n.typ != nil and n.typ.size == szIllegalRecursion:
result.offset = szIllegalRecursion
result.align = szIllegalRecursion
return
result.align = 1
raiseIllegalTypeRecursion()
case n.kind
of nkRecCase:
assert(n.sons[0].kind == nkSym)
let (kindOffset, kindAlign) = computeObjectOffsetsFoldFunction(conf, n.sons[0], initialOffset)
var maxChildAlign: BiggestInt = if initialOffset == szUnknownSize: szUnknownSize else: 0
for i in 1 ..< sonsLen(n):
let child = n.sons[i]
case child.kind
of nkOfBranch, nkElse:
# offset parameter cannot be known yet, it needs to know the alignment first
let align = computeSubObjectAlign(conf, n.sons[i].lastSon)
if align == szIllegalRecursion:
result.offset = szIllegalRecursion
result.align = szIllegalRecursion
return
if align == szUnknownSize or maxChildAlign == szUnknownSize:
maxChildAlign = szUnknownSize
computeObjectOffsetsFoldFunction(conf, n.sons[0], packed, accum)
var maxChildAlign: int = if accum.offset == szUnknownSize: szUnknownSize else: 1
if not packed:
for i in 1 ..< sonsLen(n):
let child = n.sons[i]
case child.kind
of nkOfBranch, nkElse:
# offset parameter cannot be known yet, it needs to know the alignment first
let align = int(computeSubObjectAlign(conf, n.sons[i].lastSon))
maxChildAlign = alignmentMax(maxChildAlign, align)
else:
maxChildAlign = max(maxChildAlign, align)
else:
internalError(conf, "computeObjectOffsetsFoldFunction(record case branch)")
internalError(conf, "computeObjectOffsetsFoldFunction(record case branch)")
if maxChildAlign == szUnknownSize:
setOffsetsToUnknown(n)
result.align = szUnknownSize
result.offset = szUnknownSize
accum.offset = szUnknownSize
accum.maxAlign = szUnknownSize
else:
# the union neds to be aligned first, before the offsets can be assigned
let kindUnionOffset = align(kindOffset, maxChildAlign)
var maxChildOffset: BiggestInt = 0
accum.align(maxChildAlign)
let accumRoot = accum # copy, because each branch should start af the same offset
for i in 1 ..< sonsLen(n):
let (offset, align) = computeObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset)
maxChildOffset = max(maxChildOffset, offset)
result.align = max(kindAlign, maxChildAlign)
result.offset = maxChildOffset
var branchAccum = accumRoot
computeObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, packed, branchAccum)
accum.mergeBranch(branchAccum)
of nkRecList:
result.align = 1 # maximum of all member alignments
var offset = initialOffset
for i, child in n.sons:
let (newOffset, align) = computeObjectOffsetsFoldFunction(conf, child, offset)
if newOffset == szIllegalRecursion:
result.offset = szIllegalRecursion
result.align = szIllegalRecursion
return
elif newOffset == szUnknownSize or offset == szUnknownSize:
# if anything is unknown, the rest becomes unknown as well
offset = szUnknownSize
result.align = szUnknownSize
else:
offset = newOffset
result.align = max(result.align, align)
# final alignment
if offset == szUnknownSize:
result.offset = szUnknownSize
else:
result.offset = align(offset, result.align)
computeObjectOffsetsFoldFunction(conf, child, packed, accum)
of nkSym:
var size = szUnknownSize
var align = szUnknownSize
if n.sym.bitsize == 0: # 0 represents bitsize not set
computeSizeAlign(conf, n.sym.typ)
size = n.sym.typ.size.int
align = n.sym.typ.align.int
result.align = align
if initialOffset == szUnknownSize or size == szUnknownSize or align == szUnknownSize:
n.sym.offset = szUnknownSize
result.offset = szUnknownSize
else:
n.sym.offset = align(initialOffset, align).int
result.offset = n.sym.offset + n.sym.typ.size
align = if packed: 1 else: n.sym.typ.align.int
accum.align(align)
n.sym.offset = accum.offset
accum.inc(size)
else:
result.align = szUnknownSize
result.offset = szUnknownSize
accum.maxAlign = szUnknownSize
accum.offset = szUnknownSize
proc computePackedObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, initialOffset: BiggestInt, debug: bool): BiggestInt =
## ``result`` is the offset within the object, after the node has been written, no padding bytes added
proc computeUnionObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode; accum: var OffsetAccum) =
## ``accum.offset`` will the offset from the larget member of the union.
case n.kind
of nkRecCase:
assert(n.sons[0].kind == nkSym)
let kindOffset = computePackedObjectOffsetsFoldFunction(conf, n.sons[0], initialOffset, debug)
# the union neds to be aligned first, before the offsets can be assigned
let kindUnionOffset = kindOffset
var maxChildOffset: BiggestInt = kindUnionOffset
for i in 1 ..< sonsLen(n):
let offset = computePackedObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, kindUnionOffset, debug)
if offset == szIllegalRecursion:
return szIllegalRecursion
if offset == szUnknownSize or maxChildOffset == szUnknownSize:
maxChildOffset = szUnknownSize
else:
maxChildOffset = max(maxChildOffset, offset)
result = maxChildOffset
of nkRecList:
result = initialOffset
for i, child in n.sons:
result = computePackedObjectOffsetsFoldFunction(conf, child, result, debug)
if result == szIllegalRecursion:
break
of nkSym:
var size = szUnknownSize
if n.sym.bitsize == 0:
computeSizeAlign(conf, n.sym.typ)
size = n.sym.typ.size.int
if initialOffset == szUnknownSize or size == szUnknownSize:
n.sym.offset = szUnknownSize
result = szUnknownSize
else:
n.sym.offset = int(initialOffset)
result = initialOffset + n.sym.typ.size
else:
result = szUnknownSize
proc computeUnionObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, debug: bool): tuple[offset, align: BiggestInt] =
## ``result`` is the offset from the larget member of the union.
case n.kind
of nkRecCase:
result.offset = szUnknownSize
result.align = szUnknownSize
accum.offset = szUnknownSize
accum.maxAlign = szUnknownSize
localError(conf, n.info, "Illegal use of ``case`` in union type.")
#internalError(conf, "Illegal use of ``case`` in union type.")
of nkRecList:
var maxChildOffset: BiggestInt = 0
let accumRoot = accum # copy, because each branch should start af the same offset
for i, child in n.sons:
let (offset, align) = computeUnionObjectOffsetsFoldFunction(conf, child, debug)
if offset == szIllegalRecursion or align == szIllegalRecursion:
result.offset = szIllegalRecursion
result.align = szIllegalRecursion
elif offset == szUnknownSize or align == szUnknownSize:
result.offset = szUnknownSize
result.align = szUnknownSize
else:
assert offset != szUncomputedSize
assert align != szUncomputedSize
result.offset = max(result.offset, offset)
result.align = max(result.align, align)
var branchAccum = accumRoot
computeUnionObjectOffsetsFoldFunction(conf, child, branchAccum)
accum.mergeBranch(branchAccum)
of nkSym:
var size = szUnknownSize
var align = szUnknownSize
@ -248,17 +181,12 @@ proc computeUnionObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode, debug: boo
computeSizeAlign(conf, n.sym.typ)
size = n.sym.typ.size.int
align = n.sym.typ.align.int
result.align = align
if size == szUnknownSize:
n.sym.offset = szUnknownSize
result.offset = szUnknownSize
else:
n.sym.offset = 0
result.offset = n.sym.typ.size
accum.align(align)
n.sym.offset = accum.offset
accum.inc(size)
else:
result.offset = szUnknownSize
result.align = szUnknownSize
accum.maxAlign = szUnknownSize
accum.offset = szUnknownSize
proc computeSizeAlign(conf: ConfigRef; typ: PType) =
## computes and sets ``size`` and ``align`` members of ``typ``
@ -288,8 +216,7 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
# mark computation in progress
typ.size = szIllegalRecursion
typ.align = szIllegalRecursion
var maxAlign, sizeAccum, length: BiggestInt
typ.paddingAtEnd = 0
var tk = typ.kind
case tk
@ -299,26 +226,23 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
else:
typ.size = conf.target.ptrSize
typ.align = int16(conf.target.ptrSize)
of tyNil:
typ.size = conf.target.ptrSize
typ.align = int16(conf.target.ptrSize)
of tyString:
if conf.selectedGC == gcDestructors:
typ.size = conf.target.ptrSize * 2
else:
typ.size = conf.target.ptrSize
typ.align = int16(conf.target.ptrSize)
of tyCString, tySequence, tyPtr, tyRef, tyVar, tyLent, tyOpenArray:
let base = typ.lastSon
if base == typ:
# this is not the correct location to detect ``type A = ptr A``
typ.size = szIllegalRecursion
typ.align = szIllegalRecursion
typ.paddingAtEnd = szIllegalRecursion
return
typ.align = int16(conf.target.ptrSize)
if typ.kind == tySequence and conf.selectedGC == gcDestructors:
typ.size = conf.target.ptrSize * 2
@ -340,12 +264,13 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
computeSizeAlign(conf, base)
typ.size = 0
typ.align = base.align
of tyEnum:
if firstOrd(conf, typ) < Zero:
typ.size = 4 # use signed int32
typ.align = 4
else:
length = toInt64(lastOrd(conf, typ)) # BUGFIX: use lastOrd!
let length = toInt64(lastOrd(conf, typ)) # BUGFIX: use lastOrd!
if length + 1 < `shl`(1, 8):
typ.size = 1
typ.align = 1
@ -357,30 +282,37 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
typ.align = 4
else:
typ.size = 8
typ.align = 8
typ.align = int16(conf.floatInt64Align)
of tySet:
if typ.sons[0].kind == tyGenericParam:
typ.size = szUncomputedSize
typ.align = szUncomputedSize # in original version this was 1
typ.align = szUncomputedSize
else:
length = toInt64(lengthOrd(conf, typ.sons[0]))
let length = toInt64(lengthOrd(conf, typ.sons[0]))
if length <= 8:
typ.size = 1
typ.align = 1
elif length <= 16:
typ.size = 2
typ.align = 2
elif length <= 32:
typ.size = 4
typ.align = 4
elif length <= 64:
typ.size = 8
typ.align = int16(conf.floatInt64Align)
elif align(length, 8) mod 8 == 0:
typ.size = align(length, 8) div 8
typ.align = int16(conf.floatInt64Align)
else:
typ.size = align(length, 8) div 8 + 1
typ.align = int16(typ.size)
typ.align = int16(conf.floatInt64Align)
of tyRange:
computeSizeAlign(conf, typ.sons[0])
typ.size = typ.sons[0].size
typ.align = typ.sons[0].align
typ.paddingAtEnd = typ.sons[0].paddingAtEnd
of tyTuple:
try:
var accum = OffsetAccum(maxAlign: 1)
@ -392,112 +324,121 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
let sym = typ.n[i].sym
sym.offset = accum.offset
accum.inc(int(child.size))
accum.finish
typ.paddingAtEnd = int16(accum.finish())
typ.size = accum.offset
typ.align = int16(accum.maxAlign)
except IllegalTypeRecursionError:
typ.paddingAtEnd = szIllegalRecursion
typ.size = szIllegalRecursion
typ.align = szIllegalRecursion
of tyObject:
var headerSize: BiggestInt
var headerAlign: int16
if typ.sons[0] != nil:
# compute header size
if conf.cmd == cmdCompileToCpp:
# if the target is C++ the members of this type are written
# into the padding byets at the end of the parent type. At the
# moment it is not supported to calculate that.
headerSize = szUnknownSize
headerAlign = szUncomputedSize
else:
var st = typ.sons[0]
while st.kind in skipPtrs:
st = st.sons[^1]
computeSizeAlign(conf, st)
if st.size == szIllegalRecursion:
typ.size = st.size
typ.align = st.align
return
headerSize = st.size
headerAlign = st.align
elif isObjectWithTypeFieldPredicate(typ):
# this branch is taken for RootObj
headerSize = conf.target.intSize
headerAlign = conf.target.intSize.int16
else:
headerSize = 0
headerAlign = 1
let (offset, align) =
try:
var accum =
if typ.sons[0] != nil:
# compute header size
var st = typ.sons[0]
while st.kind in skipPtrs:
st = st.sons[^1]
computeSizeAlign(conf, st)
if conf.cmd == cmdCompileToCpp:
OffsetAccum(
offset: int(st.size) - int(st.paddingAtEnd),
maxAlign: st.align
)
else:
OffsetAccum(
offset: int(st.size),
maxAlign: st.align
)
elif isObjectWithTypeFieldPredicate(typ):
# this branch is taken for RootObj
OffsetAccum(
offset: conf.target.intSize,
maxAlign: conf.target.intSize
)
else:
OffsetAccum(maxAlign: 1)
if tfUnion in typ.flags:
if tfPacked in typ.flags:
let info = if typ.sym != nil: typ.sym.info else: unknownLineInfo()
localError(conf, info, "type may not be packed and union at the same time.")
(BiggestInt(szUnknownSize), BiggestInt(szUnknownSize))
localError(conf, info, "union type may not be packed.")
accum = OffsetAccum(offset: szUnknownSize, maxAlign: szUnknownSize)
elif accum.offset != 0:
let info = if typ.sym != nil: typ.sym.info else: unknownLineInfo()
localError(conf, info, "union type may not have an object header")
accum = OffsetAccum(offset: szUnknownSize, maxAlign: szUnknownSize)
else:
computeUnionObjectOffsetsFoldFunction(conf, typ.n, false)
computeUnionObjectOffsetsFoldFunction(conf, typ.n, accum)
elif tfPacked in typ.flags:
(computePackedObjectOffsetsFoldFunction(conf, typ.n, headerSize, false), BiggestInt(1))
accum.maxAlign = 1
computeObjectOffsetsFoldFunction(conf, typ.n, true, accum)
else:
computeObjectOffsetsFoldFunction(conf, typ.n, headerSize)
if offset == szIllegalRecursion:
computeObjectOffsetsFoldFunction(conf, typ.n, false, accum)
let paddingAtEnd = int16(accum.finish())
if typ.sym != nil and
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc}:
typ.size = szUnknownSize
typ.align = szUnknownSize
typ.paddingAtEnd = szUnknownSize
else:
typ.size = accum.offset
typ.align = int16(accum.maxAlign)
typ.paddingAtEnd = paddingAtEnd
except IllegalTypeRecursionError:
typ.size = szIllegalRecursion
typ.align = szIllegalRecursion
return
if offset == szUnknownSize or (
typ.sym != nil and
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc}):
typ.size = szUnknownSize
typ.align = szUnknownSize
return
# header size is already in size from computeObjectOffsetsFoldFunction
# maxAlign is probably not changed at all from headerAlign
if tfPacked in typ.flags:
typ.size = offset
typ.align = 1
else:
typ.align = int16(max(align, headerAlign))
typ.size = align(offset, typ.align)
typ.paddingAtEnd = szIllegalRecursion
of tyInferred:
if typ.len > 1:
computeSizeAlign(conf, typ.lastSon)
typ.size = typ.lastSon.size
typ.align = typ.lastSon.align
typ.paddingAtEnd = typ.lastSon.paddingAtEnd
of tyGenericInst, tyDistinct, tyGenericBody, tyAlias, tySink, tyOwned:
computeSizeAlign(conf, typ.lastSon)
typ.size = typ.lastSon.size
typ.align = typ.lastSon.align
typ.paddingAtEnd = typ.lastSon.paddingAtEnd
of tyTypeClasses:
if typ.isResolvedUserTypeClass:
computeSizeAlign(conf, typ.lastSon)
typ.size = typ.lastSon.size
typ.align = typ.lastSon.align
typ.paddingAtEnd = typ.lastSon.paddingAtEnd
else:
typ.size = szUncomputedSize
typ.align = szUncomputedSize
typ.size = szUnknownSize
typ.align = szUnknownSize
typ.paddingAtEnd = szUnknownSize
of tyTypeDesc:
computeSizeAlign(conf, typ.base)
typ.size = typ.base.size
typ.align = typ.base.align
typ.paddingAtEnd = typ.base.paddingAtEnd
of tyForward:
# is this really illegal recursion, or maybe just unknown?
typ.size = szIllegalRecursion
typ.align = szIllegalRecursion
typ.paddingAtEnd = szIllegalRecursion
of tyStatic:
if typ.n != nil:
computeSizeAlign(conf, typ.lastSon)
typ.size = typ.lastSon.size
typ.align = typ.lastSon.align
typ.paddingAtEnd = typ.lastSon.paddingAtEnd
else:
typ.size = szUncomputedSize
typ.align = szUncomputedSize
typ.size = szUnknownSize
typ.align = szUnknownSize
typ.paddingAtEnd = szUnknownSize
else:
typ.size = szUncomputedSize
typ.align = szUncomputedSize
typ.size = szUnknownSize
typ.align = szUnknownSize
typ.paddingAtEnd = szUnknownSize
template foldSizeOf*(conf: ConfigRef; n: PNode; fallback: PNode): PNode =
let config = conf