Files
Nim/tests/misc/tsizeof.nim
cooldome 777c28cf45 fix #15516 (#15518)
(cherry picked from commit fe187719ab)
2020-10-09 10:04:24 +02:00

705 lines
16 KiB
Nim

discard """
targets: "c cpp"
output: '''
body executed
body executed
OK
macros api OK
'''
"""
# This is for Azure. The keyword ``alignof`` only exists in ``c++11``
# and newer. On Azure gcc does not default to c++11 yet.
when defined(cpp) and not defined(windows):
{.passC: "-std=c++11".}
# Object offsets are different for inheritance objects when compiling
# to c++.
type
TMyEnum = enum
tmOne, tmTwo, tmThree, tmFour
TMyArray1 = array[3, uint8]
TMyArray2 = array[1..3, int32]
TMyArray3 = array[TMyEnum, float64]
var failed = false
const
mysize1 = sizeof(TMyArray1)
mysize2 = sizeof(TMyArray2)
mysize3 = sizeof(TMyArray3)
doAssert mysize1 == 3
doAssert mysize2 == 12
doAssert mysize3 == 32
import macros, typetraits
macro testSizeAlignOf(args: varargs[untyped]): untyped =
result = newStmtList()
for arg in args:
result.add quote do:
let
c_size = c_sizeof(`arg`)
nim_size = sizeof(`arg`)
c_align = c_alignof(type(`arg`))
nim_align = alignof(`arg`)
if nim_size != c_size or nim_align != c_align:
var msg = strAlign(`arg`.type.name & ": ")
if nim_size != c_size:
msg.add " size(got, expected): " & $nim_size & " != " & $c_size
if nim_align != c_align:
msg.add " align(get, expected): " & $nim_align & " != " & $c_align
echo msg
failed = true
macro testOffsetOf(a, b: untyped): untyped =
let typeName = newLit(a.repr)
let member = newLit(b.repr)
result = quote do:
let
c_offset = c_offsetof(`a`,`b`)
nim_offset = offsetof(`a`,`b`)
if c_offset != nim_offset:
echo `typeName`, ".", `member`, " offsetError, C: ", c_offset, " nim: ", nim_offset
failed = true
proc strAlign(arg: string): string =
const minLen = 22
result = arg
for i in 0 ..< minLen - arg.len:
result &= ' '
macro c_offsetof(fieldAccess: typed): int32 =
## Bullet proof implementation that works on actual offsetof operator
## in the c backend. Assuming of course this implementation is
## correct.
let s = if fieldAccess.kind == nnkCheckedFieldExpr: fieldAccess[0]
else: fieldAccess
let a = s[0].getTypeInst
let b = s[1]
result = quote do:
var res: int32
{.emit: [res, " = offsetof(", `a`, ", ", `b`, ");"] .}
res
template c_offsetof(t: typedesc, a: untyped): int32 =
var x: ptr t
c_offsetof(x[].a)
macro c_sizeof(a: typed): int32 =
## Bullet proof implementation that works using the sizeof operator
## in the c backend. Assuming of course this implementation is
## correct.
result = quote do:
var res: int32
{.emit: [res, " = sizeof(", `a`, ");"] .}
res
macro c_alignof(arg: untyped): untyped =
## Bullet proof implementation that works on actual alignment
## behavior measured at runtime.
let typeSym = genSym(nskType, "AlignTestType"&arg.repr)
result = quote do:
type
`typeSym` = object
causeAlign: byte
member: `arg`
c_offsetof(`typeSym`, member)
macro testAlign(arg:untyped):untyped =
let prefix = newLit(arg.lineinfo & " alignof " & arg.repr & " ")
result = quote do:
let cAlign = c_alignof(`arg`)
let nimAlign = alignof(`arg`)
if cAlign != nimAlign:
echo `prefix`, cAlign, " != ", nimAlign
failed = true
macro testSize(arg:untyped):untyped =
let prefix = newLit(arg.lineinfo & " sizeof " & arg.repr & " ")
result = quote do:
let cSize = c_sizeof(`arg`)
let nimSize = sizeof(`arg`)
if cSize != nimSize:
echo `prefix`, cSize, " != ", nimSize
failed = true
type
MyEnum {.pure.} = enum
ValueA
ValueB
ValueC
OtherEnum {.pure, size: 8.} = enum
ValueA
ValueB
Enum1 {.pure, size: 1.} = enum
ValueA
ValueB
Enum2 {.pure, size: 2.} = enum
ValueA
ValueB
Enum4 {.pure, size: 4.} = enum
ValueA
ValueB
Enum8 {.pure, size: 8.} = enum
ValueA
ValueB
# Must have more than 32 elements so that set[MyEnum33] will become compile to an int64.
MyEnum33 {.pure.} = enum
Value1, Value2, Value3, Value4, Value5, Value6,
Value7, Value8, Value9, Value10, Value11, Value12,
Value13, Value14, Value15, Value16, Value17, Value18,
Value19, Value20, Value21, Value22, Value23, Value24,
Value25, Value26, Value27, Value28, Value29, Value30,
Value31, Value32, Value33
proc transformObjectconfigPacked(arg: NimNode): NimNode =
let debug = arg.kind == nnkPragmaExpr
if arg.eqIdent("objectconfig"):
result = ident"packed"
else:
result = copyNimNode(arg)
for child in arg:
result.add transformObjectconfigPacked(child)
proc removeObjectconfig(arg: NimNode): NimNode =
if arg.kind == nnkPragmaExpr and arg[1][0].eqIdent "objectconfig":
result = arg[0]
else:
result = copyNimNode(arg)
for child in arg:
result.add removeObjectconfig(child)
macro testinstance(body: untyped): untyped =
let bodyPure = removeObjectconfig(body)
let bodyPacked = transformObjectconfigPacked(body)
result = quote do:
proc pureblock(): void =
const usePacked {.inject.} = false
`bodyPure`
pureblock()
proc packedblock(): void =
const usePacked {.inject.} = true
`bodyPacked`
packedblock()
proc testPrimitiveTypes(): void =
testAlign(pointer)
testAlign(int)
testAlign(uint)
testAlign(int8)
testAlign(int16)
testAlign(int32)
testAlign(int64)
testAlign(uint8)
testAlign(uint16)
testAlign(uint32)
testAlign(uint64)
testAlign(float)
testAlign(float32)
testAlign(float64)
testAlign(MyEnum)
testAlign(OtherEnum)
testAlign(Enum1)
testAlign(Enum2)
testAlign(Enum4)
testAlign(Enum8)
testPrimitiveTypes()
testinstance:
type
EnumObjectA {.objectconfig.} = object
a : Enum1
b : Enum2
c : Enum4
d : Enum8
EnumObjectB {.objectconfig.} = object
a : Enum8
b : Enum4
c : Enum2
d : Enum1
TrivialType {.objectconfig.} = object
x,y,z: int8
SimpleAlignment {.objectconfig.} = object
# behaves differently on 32bit Windows and 32bit Linux
a,b: int8
c: int64
AlignAtEnd {.objectconfig.} = object
a: int64
b,c: int8
SimpleBranch {.objectconfig.} = object
case kind: MyEnum
of MyEnum.ValueA:
a: int16
of MyEnum.ValueB:
b: int32
of MyEnum.ValueC:
c: int64
PaddingBeforeBranchA {.objectconfig.} = object
cause: int8
case kind: MyEnum
of MyEnum.ValueA:
a: int16
of MyEnum.ValueB:
b: int32
of MyEnum.ValueC:
c: int64
PaddingBeforeBranchB {.objectconfig.} = object
cause: int8
case kind: MyEnum
of MyEnum.ValueA:
a: int8
of MyEnum.ValueB:
b: int16
of MyEnum.ValueC:
c: int32
PaddingAfterBranch {.objectconfig.} = object
case kind: MyEnum
of MyEnum.ValueA:
a: int8
of MyEnum.ValueB:
b: int16
of MyEnum.ValueC:
c: int32
cause: int64
RecursiveStuff {.objectconfig.} = object
case kind: MyEnum # packedOffset: 0
of MyEnum.ValueA: # packedOffset:
a: int16 # packedOffset: 1
of MyEnum.ValueB: # packedOffset:
b: int32 # packedOffset: 1
of MyEnum.ValueC: # packedOffset:
case kind2: MyEnum # packedOffset: 1
of MyEnum.ValueA: # packedOffset:
ca1: int8
ca2: int32
of MyEnum.ValueB: # packedOffset:
cb: int32 # packedOffset: 2
of MyEnum.ValueC: # packedOffset:
cc: int64 # packedOffset: 2
d1: int8
d2: int64
Foobar {.objectconfig.} = object
case kind: OtherEnum
of OtherEnum.ValueA:
a: uint8
of OtherEnum.ValueB:
b: int8
c: int8
PaddingOfSetEnum33 {.objectconfig.} = object
cause: int8
theSet: set[MyEnum33]
Bazing {.objectconfig.} = object of RootObj
a: int64
# TODO test on 32 bit system
# only there the object header is smaller than the first member
InheritanceA {.objectconfig.} = object of RootObj
a: char
InheritanceB {.objectconfig.} = object of InheritanceA
b: char
InheritanceC {.objectconfig.} = object of InheritanceB
c: char
# from issue 4763
GenericObject[T] {.objectconfig.} = object
a: int32
b: T
# this type mixes `packed` with `align`.
MyCustomAlignPackedObject {.objectconfig.} = object
a: char
b {.align: 32.}: int32 # align overrides `packed` for this field.
c: char
d: int32 # unaligned
Kind = enum
K1, K2
AnotherEnum = enum
X1, X2, X3
MyObject = object
s: string
case k: Kind
of K1: nil
of K2:
x: float
y: int32
z: AnotherEnum
const trivialSize = sizeof(TrivialType) # needs to be able to evaluate at compile time
proc main(): void =
var t : TrivialType
var a : SimpleAlignment
var b : AlignAtEnd
var c : SimpleBranch
var d : PaddingBeforeBranchA
var e : PaddingBeforeBranchB
var f : PaddingAfterBranch
var g : RecursiveStuff
var ro : RootObj
var go : GenericObject[int64]
var po : PaddingOfSetEnum33
var capo: MyCustomAlignPackedObject
var issue15516: MyObject
var
e1: Enum1
e2: Enum2
e4: Enum4
e8: Enum8
var
eoa: EnumObjectA
eob: EnumObjectB
testAlign(SimpleAlignment)
# sanity check to ensure both branches are actually executed
when usePacked:
doAssert sizeof(SimpleAlignment) == 10
else:
doAssert sizeof(SimpleAlignment) > 10
testSizeAlignOf(t,a,b,c,d,e,f,g,ro,go,po, e1, e2, e4, e8, eoa, eob, capo, issue15516)
type
WithBitsize {.objectconfig.} = object
bitfieldA {.bitsize: 16.}: uint32
bitfieldB {.bitsize: 16.}: uint32
var wbs: WithBitsize
testSize(wbs)
testOffsetOf(TrivialType, x)
testOffsetOf(TrivialType, y)
testOffsetOf(TrivialType, z)
testOffsetOf(SimpleAlignment, a)
testOffsetOf(SimpleAlignment, b)
testOffsetOf(SimpleAlignment, c)
testOffsetOf(AlignAtEnd, a)
testOffsetOf(AlignAtEnd, b)
testOffsetOf(AlignAtEnd, c)
testOffsetOf(SimpleBranch, a)
testOffsetOf(SimpleBranch, b)
testOffsetOf(SimpleBranch, c)
testOffsetOf(PaddingBeforeBranchA, cause)
testOffsetOf(PaddingBeforeBranchA, a)
testOffsetOf(PaddingBeforeBranchB, cause)
testOffsetOf(PaddingBeforeBranchB, a)
testOffsetOf(PaddingAfterBranch, a)
testOffsetOf(PaddingAfterBranch, cause)
testOffsetOf(Foobar, c)
testOffsetOf(PaddingOfSetEnum33, cause)
testOffsetOf(PaddingOfSetEnum33, theSet)
testOffsetOf(Bazing, a)
testOffsetOf(InheritanceA, a)
testOffsetOf(InheritanceB, b)
testOffsetOf(InheritanceC, c)
testOffsetOf(EnumObjectA, a)
testOffsetOf(EnumObjectA, b)
testOffsetOf(EnumObjectA, c)
testOffsetOf(EnumObjectA, d)
testOffsetOf(EnumObjectB, a)
testOffsetOf(EnumObjectB, b)
testOffsetOf(EnumObjectB, c)
testOffsetOf(EnumObjectB, d)
testOffsetOf(RecursiveStuff, kind)
testOffsetOf(RecursiveStuff, a)
testOffsetOf(RecursiveStuff, b)
testOffsetOf(RecursiveStuff, kind2)
testOffsetOf(RecursiveStuff, ca1)
testOffsetOf(RecursiveStuff, ca2)
testOffsetOf(RecursiveStuff, cb)
testOffsetOf(RecursiveStuff, cc)
testOffsetOf(RecursiveStuff, d1)
testOffsetOf(RecursiveStuff, d2)
testOffsetOf(MyCustomAlignPackedObject, a)
testOffsetOf(MyCustomAlignPackedObject, b)
testOffsetOf(MyCustomAlignPackedObject, c)
testOffsetOf(MyCustomAlignPackedObject, d)
echo "body executed" # sanity check to ensure this logic isn't skipped entirely
main()
{.emit: """/*TYPESECTION*/
typedef struct{
float a; float b;
} Foo;
""".}
type
Foo {.importc.} = object
Bar = object
b: byte
foo: Foo
assert sizeof(Bar) == 12
# bug #10082
type
A = int8 # change to int16 and get sizeof(C)==6
B = int16
C {.packed.} = object
d {.bitsize: 1.}: A
e {.bitsize: 7.}: A
f {.bitsize: 16.}: B
assert sizeof(C) == 3
type
MixedBitsize {.packed.} = object
a: uint32
b {.bitsize: 8.}: uint8
c {.bitsize: 1.}: uint8
d {.bitsize: 7.}: uint8
e {.bitsize: 16.}: uint16
f: uint32
doAssert sizeof(MixedBitsize) == 12
type
MyUnionType {.union.} = object
a: int32
b: float32
MyCustomAlignUnion {.union.} = object
c: char
a {.align: 32.}: int
MyCustomAlignObject = object
c: char
a {.align: 32.}: int
doAssert sizeof(MyUnionType) == 4
doAssert sizeof(MyCustomAlignUnion) == 32
doAssert alignof(MyCustomAlignUnion) == 32
doAssert sizeof(MyCustomAlignObject) == 64
doAssert alignof(MyCustomAlignObject) == 32
##########################################
# bug #9794
##########################################
type
imported_double {.importc: "double".} = object
Pod = object
v* : imported_double
seed*: int32
Pod2 = tuple[v: imported_double, seed: int32]
proc foobar() =
testAlign(Pod)
testSize(Pod)
testAlign(Pod2)
testSize(Pod2)
doAssert sizeof(Pod) == sizeof(Pod2)
doAssert alignof(Pod) == alignof(Pod2)
foobar()
if failed:
quit("FAIL")
else:
echo "OK"
##########################################
# sizeof macros API
##########################################
import macros
type
Vec2f = object
x,y: float32
Vec4f = object
x,y,z,w: float32
# this type is constructed to have no platform depended alignment.
ParticleDataA = object
pos, vel: Vec2f
birthday: float32
padding: float32
moreStuff: Vec4f
const expected = [
# name size align offset
("pos", 8, 4, 0),
("vel", 8, 4, 8),
("birthday", 4, 4, 16),
("padding", 4, 4, 20),
("moreStuff", 16, 4, 24)
]
macro typeProcessing(arg: typed): untyped =
let recList = arg.getTypeImpl[2]
recList.expectKind nnkRecList
for i, identDefs in recList:
identDefs.expectKind nnkIdentDefs
identDefs.expectLen 3
let sym = identDefs[0]
sym.expectKind nnkSym
doAssert expected[i][0] == sym.strVal
doAssert expected[i][1] == getSize(sym)
doAssert expected[i][2] == getAlign(sym)
doAssert expected[i][3] == getOffset(sym)
result = newCall(bindSym"echo", newLit("macros api OK"))
proc main() =
var mylocal: ParticleDataA
typeProcessing(mylocal)
main()
# issue #11320 use UncheckedArray
type
Payload = object
something: int8
vals: UncheckedArray[int32]
proc payloadCheck() =
doAssert offsetOf(Payload, vals) == 4
doAssert sizeof(Payload) == 4
payloadCheck()
# offsetof tuple types
type
MyTupleType = tuple
a: float64
b: float64
c: float64
MyOtherTupleType = tuple
a: float64
b: imported_double
c: float64
MyCaseObject = object
val1: imported_double
case kind: bool
of true:
val2,val3: float32
else:
val4,val5: int32
doAssert offsetof(MyTupleType, a) == 0
doAssert offsetof(MyTupleType, b) == 8
doAssert offsetof(MyTupleType, c) == 16
doAssert offsetof(MyOtherTupleType, a) == 0
doAssert offsetof(MyOtherTupleType, b) == 8
# The following expression can only work if the offsetof expression is
# properly forwarded for the C code generator.
doAssert offsetof(MyOtherTupleType, c) == 16
doAssert offsetof(Bar, foo) == 4
doAssert offsetof(MyCaseObject, val1) == 0
doAssert offsetof(MyCaseObject, kind) == 8
doAssert offsetof(MyCaseObject, val2) == 12
doAssert offsetof(MyCaseObject, val3) == 16
doAssert offsetof(MyCaseObject, val4) == 12
doAssert offsetof(MyCaseObject, val5) == 16
template reject(e) =
static: assert(not compiles(e))
reject:
const off1 = offsetof(MyOtherTupleType, c)
reject:
const off2 = offsetof(MyOtherTupleType, b)
reject:
const off3 = offsetof(MyCaseObject, kind)
type
MyPackedCaseObject {.packed.} = object
val1: imported_double
case kind: bool
of true:
val2,val3: float32
else:
val4,val5: int32
# packed case object
doAssert offsetof(MyPackedCaseObject, val1) == 0
doAssert offsetof(MyPackedCaseObject, val2) == 9
doAssert offsetof(MyPackedCaseObject, val3) == 13
doAssert offsetof(MyPackedCaseObject, val4) == 9
doAssert offsetof(MyPackedCaseObject, val5) == 13
reject:
const off5 = offsetof(MyPackedCaseObject, val2)
reject:
const off6 = offsetof(MyPackedCaseObject, val3)
reject:
const off7 = offsetof(MyPackedCaseObject, val4)
reject:
const off8 = offsetof(MyPackedCaseObject, val5)