diff options
Diffstat (limited to 'compiler/sizealignoffsetimpl.nim')
-rw-r--r-- | compiler/sizealignoffsetimpl.nim | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim new file mode 100644 index 000000000..1dd481ec0 --- /dev/null +++ b/compiler/sizealignoffsetimpl.nim @@ -0,0 +1,525 @@ +# +# +# 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: int32): int32 = + result = (address + (alignment - 1)) and not (alignment - 1) + +const + ## a size is considered "unknown" when it is an imported type from C + ## or C++. + szUnknownSize* = -3 + szIllegalRecursion* = -2 + szUncomputedSize* = -1 + szTooBigSize* = -4 + +type IllegalTypeRecursionError = object of ValueError + +proc raiseIllegalTypeRecursion() = + raise newException(IllegalTypeRecursionError, "illegal type recursion") + +type + OffsetAccum* = object + maxAlign*: int32 + offset*: int32 + +proc inc*(arg: var OffsetAccum; value: int32) = + if unlikely(value == szIllegalRecursion): raiseIllegalTypeRecursion() + if value == szUnknownSize or arg.offset == szUnknownSize: + arg.offset = szUnknownSize + else: + arg.offset += value + +proc alignmentMax(a, b: int32): int32 = + 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: int32) = + 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): int32 = + 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[0].kind == nkSym) + result = computeSubObjectAlign(conf, n[0]) + for i in 1..<n.len: + let child = n[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[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..<n.safeLen: + setOffsetsToUnknown(n[i]) + +proc computeObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode; packed: bool; accum: var OffsetAccum) = + ## ``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[0].kind == nkSym) + computeObjectOffsetsFoldFunction(conf, n[0], packed, accum) + var maxChildAlign = if accum.offset == szUnknownSize: szUnknownSize.int32 else: 1'i32 + if not packed: + for i in 1..<n.len: + let child = n[i] + case child.kind + of nkOfBranch, nkElse: + # offset parameter cannot be known yet, it needs to know the alignment first + let align = int32(computeSubObjectAlign(conf, n[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 needs 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..<n.len: + var branchAccum = OffsetAccum(offset: accumRoot.offset, maxAlign: 1) + computeObjectOffsetsFoldFunction(conf, n[i].lastSon, packed, branchAccum) + discard finish(branchAccum) + accum.mergeBranch(branchAccum) + of nkRecList: + for i, child in n.sons: + computeObjectOffsetsFoldFunction(conf, child, packed, accum) + of nkSym: + var size = szUnknownSize.int32 + var align = szUnknownSize.int32 + if n.sym.bitsize == 0: # 0 represents bitsize not set + computeSizeAlign(conf, n.sym.typ) + size = n.sym.typ.size.int32 + align = if packed: 1 else: n.sym.typ.align.int32 + accum.align(align) + if n.sym.alignment > 0: + accum.align(n.sym.alignment.int32) + n.sym.offset = accum.offset + accum.inc(size) + else: + accum.maxAlign = szUnknownSize + accum.offset = szUnknownSize + +proc computeUnionObjectOffsetsFoldFunction(conf: ConfigRef; n: PNode; packed: bool; 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 child in n.sons: + var branchAccum = OffsetAccum(offset: accumRoot.offset, maxAlign: 1) + computeUnionObjectOffsetsFoldFunction(conf, child, packed, branchAccum) + discard finish(branchAccum) + accum.mergeBranch(branchAccum) + of nkSym: + var size = szUnknownSize.int32 + var align = szUnknownSize.int32 + if n.sym.bitsize == 0: # 0 represents bitsize not set + computeSizeAlign(conf, n.sym.typ) + size = n.sym.typ.size.int32 + align = if packed: 1 else: n.sym.typ.align.int32 + accum.align(align) + if n.sym.alignment > 0: + accum.align(n.sym.alignment.int32) + n.sym.offset = accum.offset + accum.inc(size) + else: + accum.maxAlign = szUnknownSize + accum.offset = szUnknownSize + +proc computeSizeAlign(conf: ConfigRef; typ: PType) = + template setSize(typ, s) = + typ.size = s + typ.align = s + typ.paddingAtEnd = 0 + + ## 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 optSeqDestructors in conf.globalOptions: + 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: + let base = typ.last + 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 optSeqDestructors in conf.globalOptions: + typ.size = conf.target.ptrSize * 2 + else: + typ.size = conf.target.ptrSize + + of tyArray: + computeSizeAlign(conf, typ.elementType) + let elemSize = typ.elementType.size + let len = lengthOrd(conf, typ.indexType) + if elemSize < 0: + typ.size = elemSize + typ.align = int16(elemSize) + elif len < 0: + typ.size = szUnknownSize + typ.align = szUnknownSize + else: + typ.size = toInt64Checked(len * int32(elemSize), szTooBigSize) + typ.align = typ.elementType.align + + of tyUncheckedArray: + let base = typ.last + 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 lastOrd = toInt64(lastOrd(conf, typ)) # BUGFIX: use lastOrd! + if lastOrd < `shl`(1, 8): + typ.size = 1 + typ.align = 1 + elif lastOrd < `shl`(1, 16): + typ.size = 2 + typ.align = 2 + elif lastOrd < `shl`(BiggestInt(1), 32): + typ.size = 4 + typ.align = 4 + else: + typ.size = 8 + typ.align = int16(conf.floatInt64Align) + of tySet: + if typ.elementType.kind == tyGenericParam: + typ.size = szUncomputedSize + typ.align = szUncomputedSize + else: + let length = toInt64(lengthOrd(conf, typ.elementType)) + 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 = 1 + else: + typ.size = align(length, 8) div 8 + 1 + typ.align = 1 + of tyRange: + computeSizeAlign(conf, typ.elementType) + typ.size = typ.elementType.size + typ.align = typ.elementType.align + typ.paddingAtEnd = typ.elementType.paddingAtEnd + + of tyTuple: + try: + var accum = OffsetAccum(maxAlign: 1) + for i, child in typ.ikids: + 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(int32(child.size)) + typ.paddingAtEnd = int16(accum.finish()) + typ.size = if accum.offset == 0: 1 else: 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.baseClass != nil: + # compute header size + var st = typ.baseClass + while st.kind in skipPtrs: + st = st.skipModifier + computeSizeAlign(conf, st) + if conf.backend == backendCpp: + OffsetAccum( + offset: int32(st.size) - int32(st.paddingAtEnd), + maxAlign: st.align + ) + else: + OffsetAccum( + offset: int32(st.size), + maxAlign: st.align + ) + elif isObjectWithTypeFieldPredicate(typ): + # this branch is taken for RootObj + OffsetAccum( + offset: conf.target.intSize.int32, + maxAlign: conf.target.intSize.int32 + ) + else: + OffsetAccum(maxAlign: 1) + if tfUnion in typ.flags: + if 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, tfPacked in typ.flags, accum) + elif tfPacked in typ.flags: + accum.maxAlign = 1 + computeObjectOffsetsFoldFunction(conf, typ.n, true, accum) + else: + if typ.baseClass == nil and lacksMTypeField(typ) and typ.n.len == 1 and + typ.n[0].kind == nkSym and + typ.n[0].sym.typ.skipTypes(abstractInst).kind == tyUncheckedArray: + # a dummy field is generated for an object with a single field + # with an UncheckedArray type + assert accum.offset == 0 + accum.offset = 1 + computeObjectOffsetsFoldFunction(conf, typ.n, false, accum) + let paddingAtEnd = int16(accum.finish()) + if typ.sym != nil and + typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc} and + tfCompleteStruct notin typ.flags: + typ.size = szUnknownSize + typ.align = szUnknownSize + typ.paddingAtEnd = szUnknownSize + else: + typ.size = if accum.offset == 0: 1 else: 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.hasElementType: + computeSizeAlign(conf, typ.last) + typ.size = typ.last.size + typ.align = typ.last.align + typ.paddingAtEnd = typ.last.paddingAtEnd + + of tyGenericInst, tyDistinct, tyGenericBody, tyAlias, tySink, tyOwned: + computeSizeAlign(conf, typ.skipModifier) + typ.size = typ.skipModifier.size + typ.align = typ.skipModifier.align + typ.paddingAtEnd = typ.last.paddingAtEnd + + of tyTypeClasses: + if typ.isResolvedUserTypeClass: + computeSizeAlign(conf, typ.last) + typ.size = typ.last.size + typ.align = typ.last.align + typ.paddingAtEnd = typ.last.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: + typ.size = szUnknownSize + typ.align = szUnknownSize + typ.paddingAtEnd = szUnknownSize + + of tyStatic: + if typ.n != nil: + computeSizeAlign(conf, typ.last) + typ.size = typ.last.size + typ.align = typ.last.align + typ.paddingAtEnd = typ.last.paddingAtEnd + else: + typ.size = szUnknownSize + typ.align = szUnknownSize + typ.paddingAtEnd = szUnknownSize + of tyInt, tyUInt: + setSize typ, conf.target.intSize.int16 + of tyBool, tyChar, tyUInt8, tyInt8: + setSize typ, 1 + of tyInt16, tyUInt16: + setSize typ, 2 + of tyInt32, tyUInt32, tyFloat32: + setSize typ, 4 + of tyInt64, tyUInt64, tyFloat64, tyFloat: + setSize typ, 8 + 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 = n + var dotExpr: PNode + block findDotExpr: + if node[1].kind == nkDotExpr: + dotExpr = node[1] + elif node[1].kind == nkCheckedFieldExpr: + dotExpr = node[1][0] + else: + dotExpr = nil + 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 |