# # # The Nim Compiler # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## 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) 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++. szUnknownSize* = -3 szIllegalRecursion* = -2 szUncomputedSize* = -1 type IllegalTypeRecursionError = object of Exception proc raiseIllegalTypeRecursion() = raise newException(IllegalTypeRecursionError, "illegal type recursion") type OffsetAccum = object maxAlign: int offset: int proc inc(arg: var OffsetAccum; value: int) = if unlikely(value == szIllegalRecursion): raiseIllegalTypeRecursion() if value == szUnknownSize or arg.offset == szUnknownSize: arg.offset = szUnknownSize 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: arg.maxAlign = szUnknownSize arg.offset = szUnknownSize else: arg.maxAlign = max(value, arg.maxAlign) arg.offset = align(arg.offset, value) 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 = 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) proc computeSubObjectAlign(conf: ConfigRef; n: PNode): BiggestInt = ## returns object alignment case n.kind of nkRecCase: assert(n.sons[0].kind == nkSym) result = computeSubObjectAlign(conf, n.sons[0]) for i in 1 ..< len(n): let child = n.sons[i] case child.kind of nkOfBranch, nkElse: let align = computeSubObjectAlign(conf, child.lastSon) if align < 0: return align result = max(result, align) else: internalError(conf, "computeSubObjectAlign") of nkRecList: result = 1 for i, child in n.sons: let align = computeSubObjectAlign(conf, n.sons[i]) if align < 0: return align result = max(result, align) of nkSym: computeSizeAlign(conf, n.sym.typ) result = n.sym.typ.align else: result = 1 proc setOffsetsToUnknown(n: PNode) = if n.kind == nkSym and n.sym.kind == skField: n.sym.offset = szUnknownSize else: for i in 0 ..< safeLen(n): setOffsetsToUnknown(n[i]) 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: raiseIllegalTypeRecursion() case n.kind of nkRecCase: assert(n.sons[0].kind == nkSym) computeObjectOffsetsFoldFunction(conf, n.sons[0], packed, accum) var maxChildAlign: int = if accum.offset == szUnknownSize: szUnknownSize else: 1 if not packed: for i in 1 ..< len(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: internalError(conf, "computeObjectOffsetsFoldFunction(record case branch)") if maxChildAlign == szUnknownSize: setOffsetsToUnknown(n) accum.offset = szUnknownSize accum.maxAlign = szUnknownSize else: # the union neds to be aligned first, before the offsets can be assigned accum.align(maxChildAlign) let accumRoot = accum # copy, because each branch should start af the same offset for i in 1 ..< len(n): var branchAccum = accumRoot computeObjectOffsetsFoldFunction(conf, n.sons[i].lastSon, packed, branchAccum) accum.mergeBranch(branchAccum) of nkRecList: for i, child in n.sons: 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 = if packed: 1 else: n.sym.typ.align.int accum.align(align) n.sym.offset = accum.offset accum.inc(size) else: accum.maxAlign = szUnknownSize accum.offset = szUnknownSize 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: accum.offset = szUnknownSize accum.maxAlign = szUnknownSize localError(conf, n.info, "Illegal use of ``case`` in union type.") of nkRecList: let accumRoot = accum # copy, because each branch should start af the same offset for i, child in n.sons: var branchAccum = accumRoot computeUnionObjectOffsetsFoldFunction(conf, child, branchAccum) accum.mergeBranch(branchAccum) 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 accum.align(align) n.sym.offset = accum.offset accum.inc(size) else: accum.maxAlign = szUnknownSize accum.offset = szUnknownSize proc computeSizeAlign(conf: ConfigRef; typ: PType) = ## computes and sets ``size`` and ``align`` members of ``typ`` assert typ != nil let hasSize = typ.size != szUncomputedSize let hasAlign = typ.align != szUncomputedSize if hasSize and hasAlign: # nothing to do, size and align already computed return # This function can only calculate both, size and align at the same time. # If one of them is already set this value is stored here and reapplied let revertSize = typ.size let revertAlign = typ.align defer: if hasSize: typ.size = revertSize if hasAlign: typ.align = revertAlign if typ.size == szIllegalRecursion or typ.align == szIllegalRecursion: # we are already computing the size of the type # --> illegal recursion in type return # mark computation in progress typ.size = szIllegalRecursion typ.align = szIllegalRecursion typ.paddingAtEnd = 0 var tk = typ.kind case tk of tyProc: if typ.callConv == ccClosure: typ.size = 2 * conf.target.ptrSize 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 else: typ.size = conf.target.ptrSize of tyArray: computeSizeAlign(conf, typ.sons[1]) let elemSize = typ.sons[1].size if elemSize < 0: typ.size = elemSize typ.align = int16(elemSize) else: typ.size = toInt64(lengthOrd(conf, typ.sons[0]) * int32(elemSize)) typ.align = typ.sons[1].align of tyUncheckedArray: let base = typ.lastSon 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: let length = toInt64(lastOrd(conf, typ)) # BUGFIX: use lastOrd! if length + 1 < `shl`(1, 8): typ.size = 1 typ.align = 1 elif length + 1 < `shl`(1, 16): typ.size = 2 typ.align = 2 elif length + 1 < `shl`(BiggestInt(1), 32): typ.size = 4 typ.align = 4 else: typ.size = 8 typ.align = int16(conf.floatInt64Align) of tySet: if typ.sons[0].kind == tyGenericParam: typ.size = szUncomputedSize typ.align = szUncomputedSize else: 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(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) for i in 0 ..< len(typ): let child = typ.sons[i] computeSizeAlign(conf, child) accum.align(child.align) if typ.n != nil: # is named tuple (has field symbols)? let sym = typ.n[i].sym sym.offset = accum.offset accum.inc(int(child.size)) 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: 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, "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, accum) elif tfPacked in typ.flags: accum.maxAlign = 1 computeObjectOffsetsFoldFunction(conf, typ.n, true, accum) else: 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 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 = 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 = szUnknownSize typ.align = szUnknownSize typ.paddingAtEnd = szUnknownSize else: typ.size = szUnknownSize typ.align = szUnknownSize typ.paddingAtEnd = szUnknownSize template foldSizeOf*(conf: ConfigRef; n: PNode; fallback: PNode): PNode = let config = conf let node = n let typ = node[1].typ computeSizeAlign(config, typ) let size = typ.size if size >= 0: let res = newIntNode(nkIntLit, size) res.info = node.info res.typ = node.typ res else: fallback template foldAlignOf*(conf: ConfigRef; n: PNode; fallback: PNode): PNode = let config = conf let node = n let typ = node[1].typ computeSizeAlign(config, typ) let align = typ.align if align >= 0: let res = newIntNode(nkIntLit, align) res.info = node.info res.typ = node.typ res else: fallback template foldOffsetOf*(conf: ConfigRef; n: PNode; fallback: PNode): PNode = ## Returns an int literal node of the given offsetof expression in `n`. ## Falls back to `fallback`, if the `offsetof` expression can't be processed. let config = conf let node : PNode = n var dotExpr: PNode block findDotExpr: if node[1].kind == nkDotExpr: dotExpr = node[1] elif node[1].kind == nkCheckedFieldExpr: dotExpr = node[1][0] else: localError(config, node.info, "can't compute offsetof on this ast") assert dotExpr != nil let value = dotExpr[0] let member = dotExpr[1] computeSizeAlign(config, value.typ) let offset = member.sym.offset if offset >= 0: let tmp = newIntNode(nkIntLit, offset) tmp.info = node.info tmp.typ = node.typ tmp else: fallback