CT sizeof(+friends) for {.importc, completeStruct.} types, enable ABI static checks (#13926)
* -d:checkabi obsolete (ABI check now enforced); add `addTypeHeader` helper
* cleanups
* import sizeof at CT for {.completeType.}
* address comments; revert default enabling of -d:checkAbi for now
* mimportc_size_check.nim => msizeof5.nim; merge mabi_check.nim into msizeof5.nim; refactor
* all pragmas in errmsgs should be written: '.importc' (un-ambiguous and less verbose than {.importc.})
This commit is contained in:
parent
5c534b2943
commit
66db9de714
14 changed files with 221 additions and 39 deletions
|
|
@ -553,6 +553,9 @@ type
|
||||||
# If it has one, t.destructor is not nil.
|
# If it has one, t.destructor is not nil.
|
||||||
tfAcyclic # object type was annotated as .acyclic
|
tfAcyclic # object type was annotated as .acyclic
|
||||||
tfIncompleteStruct # treat this type as if it had sizeof(pointer)
|
tfIncompleteStruct # treat this type as if it had sizeof(pointer)
|
||||||
|
tfCompleteStruct
|
||||||
|
# (for importc types); type is fully specified, allowing to compute
|
||||||
|
# sizeof, alignof, offsetof at CT
|
||||||
|
|
||||||
TTypeFlags* = set[TTypeFlag]
|
TTypeFlags* = set[TTypeFlag]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,10 @@ const
|
||||||
NimMergeEndMark = "/*\tNIM_merge_END:*/"
|
NimMergeEndMark = "/*\tNIM_merge_END:*/"
|
||||||
|
|
||||||
proc genSectionStart*(fs: TCFileSection; conf: ConfigRef): Rope =
|
proc genSectionStart*(fs: TCFileSection; conf: ConfigRef): Rope =
|
||||||
|
# useful for debugging and only adds at most a few lines in each file
|
||||||
|
result.add("\n/* section: ")
|
||||||
|
result.add(CFileSectionNames[fs])
|
||||||
|
result.add(" */\n")
|
||||||
if compilationCachePresent(conf):
|
if compilationCachePresent(conf):
|
||||||
result = nil
|
result = nil
|
||||||
result.add("\n/*\t")
|
result.add("\n/*\t")
|
||||||
|
|
|
||||||
|
|
@ -247,8 +247,13 @@ proc cacheGetType(tab: TypeCache; sig: SigHash): Rope =
|
||||||
result = tab.getOrDefault(sig)
|
result = tab.getOrDefault(sig)
|
||||||
|
|
||||||
proc addAbiCheck(m: BModule, t: PType, name: Rope) =
|
proc addAbiCheck(m: BModule, t: PType, name: Rope) =
|
||||||
if isDefined(m.config, "checkabi") and (let size = getSize(m.config, t); size != szUnknownSize):
|
if isDefined(m.config, "checkAbi") and (let size = getSize(m.config, t); size != szUnknownSize):
|
||||||
m.s[cfsTypeInfo].addf("NIM_CHECK_SIZE($1, $2);$n", [name, rope(size)])
|
var msg = "backend & Nim disagree on size for: "
|
||||||
|
msg.addTypeHeader(m.config, t)
|
||||||
|
var msg2 = ""
|
||||||
|
msg2.addQuoted msg # not a hostspot so extra allocation doesn't matter
|
||||||
|
m.s[cfsTypeInfo].addf("NIM_STATIC_ASSERT(sizeof($1) == $2, $3);$n", [name, rope(size), msg2.rope])
|
||||||
|
# see `testCodegenABICheck` for example error message it generates
|
||||||
|
|
||||||
proc ccgIntroducedPtr(conf: ConfigRef; s: PSym, retType: PType): bool =
|
proc ccgIntroducedPtr(conf: ConfigRef; s: PSym, retType: PType): bool =
|
||||||
var pt = skipTypes(s.typ, typedescInst)
|
var pt = skipTypes(s.typ, typedescInst)
|
||||||
|
|
@ -328,7 +333,6 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope =
|
||||||
let sig = hashType typ
|
let sig = hashType typ
|
||||||
if cacheGetType(m.typeCache, sig) == nil:
|
if cacheGetType(m.typeCache, sig) == nil:
|
||||||
m.typeCache[sig] = result
|
m.typeCache[sig] = result
|
||||||
addAbiCheck(m, typ, result)
|
|
||||||
|
|
||||||
proc pushType(m: BModule, typ: PType) =
|
proc pushType(m: BModule, typ: PType) =
|
||||||
for i in 0..high(m.typeStack):
|
for i in 0..high(m.typeStack):
|
||||||
|
|
@ -676,6 +680,7 @@ proc resolveStarsInCppType(typ: PType, idx, stars: int): PType =
|
||||||
|
|
||||||
proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
|
proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
|
||||||
# returns only the type's name
|
# returns only the type's name
|
||||||
|
|
||||||
var t = origTyp.skipTypes(irrelevantForBackend-{tyOwned})
|
var t = origTyp.skipTypes(irrelevantForBackend-{tyOwned})
|
||||||
if containsOrIncl(check, t.id):
|
if containsOrIncl(check, t.id):
|
||||||
if not (isImportedCppType(origTyp) or isImportedCppType(t)):
|
if not (isImportedCppType(origTyp) or isImportedCppType(t)):
|
||||||
|
|
@ -686,6 +691,11 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
|
||||||
if t.sym != nil: useHeader(m, t.sym)
|
if t.sym != nil: useHeader(m, t.sym)
|
||||||
if t != origTyp and origTyp.sym != nil: useHeader(m, origTyp.sym)
|
if t != origTyp and origTyp.sym != nil: useHeader(m, origTyp.sym)
|
||||||
let sig = hashType(origTyp)
|
let sig = hashType(origTyp)
|
||||||
|
|
||||||
|
defer: # defer is the simplest in this case
|
||||||
|
if isImportedType(t) and not m.typeABICache.containsOrIncl(sig):
|
||||||
|
addAbiCheck(m, t, result)
|
||||||
|
|
||||||
result = getTypePre(m, t, sig)
|
result = getTypePre(m, t, sig)
|
||||||
if result != nil:
|
if result != nil:
|
||||||
excl(check, t.id)
|
excl(check, t.id)
|
||||||
|
|
@ -818,7 +828,6 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
|
||||||
let foo = getTypeDescAux(m, t[1], check)
|
let foo = getTypeDescAux(m, t[1], check)
|
||||||
m.s[cfsTypes].addf("typedef $1 $2[$3];$n",
|
m.s[cfsTypes].addf("typedef $1 $2[$3];$n",
|
||||||
[foo, result, rope(n)])
|
[foo, result, rope(n)])
|
||||||
else: addAbiCheck(m, t, result)
|
|
||||||
of tyObject, tyTuple:
|
of tyObject, tyTuple:
|
||||||
if isImportedCppType(t) and origTyp.kind == tyGenericInst:
|
if isImportedCppType(t) and origTyp.kind == tyGenericInst:
|
||||||
let cppName = getTypeName(m, t, sig)
|
let cppName = getTypeName(m, t, sig)
|
||||||
|
|
@ -879,7 +888,8 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope =
|
||||||
else: getTupleDesc(m, t, result, check)
|
else: getTupleDesc(m, t, result, check)
|
||||||
if not isImportedType(t):
|
if not isImportedType(t):
|
||||||
m.s[cfsTypes].add(recdesc)
|
m.s[cfsTypes].add(recdesc)
|
||||||
elif tfIncompleteStruct notin t.flags: addAbiCheck(m, t, result)
|
elif tfIncompleteStruct notin t.flags:
|
||||||
|
discard # addAbiCheck(m, t, result) # already handled elsewhere
|
||||||
of tySet:
|
of tySet:
|
||||||
# Don't use the imported name as it may be scoped: 'Foo::SomeKind'
|
# Don't use the imported name as it may be scoped: 'Foo::SomeKind'
|
||||||
result = $t.kind & '_' & t.lastSon.typeName & $t.lastSon.hashType
|
result = $t.kind & '_' & t.lastSon.typeName & $t.lastSon.hashType
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
import
|
import
|
||||||
ast, ropes, options, intsets,
|
ast, ropes, options, intsets,
|
||||||
tables, ndi, lineinfos, pathutils, modulegraphs
|
tables, ndi, lineinfos, pathutils, modulegraphs, sets
|
||||||
|
|
||||||
type
|
type
|
||||||
TLabel* = Rope # for the C generator a label is just a rope
|
TLabel* = Rope # for the C generator a label is just a rope
|
||||||
|
|
@ -25,7 +25,7 @@ type
|
||||||
# this is needed for strange type generation
|
# this is needed for strange type generation
|
||||||
# reasons
|
# reasons
|
||||||
cfsFieldInfo, # section for field information
|
cfsFieldInfo, # section for field information
|
||||||
cfsTypeInfo, # section for type information
|
cfsTypeInfo, # section for type information (ag ABI checks)
|
||||||
cfsProcHeaders, # section for C procs prototypes
|
cfsProcHeaders, # section for C procs prototypes
|
||||||
cfsData, # section for C constant data
|
cfsData, # section for C constant data
|
||||||
cfsVars, # section for C variable declarations
|
cfsVars, # section for C variable declarations
|
||||||
|
|
@ -144,6 +144,10 @@ type
|
||||||
# without extension)
|
# without extension)
|
||||||
tmpBase*: Rope # base for temp identifier generation
|
tmpBase*: Rope # base for temp identifier generation
|
||||||
typeCache*: TypeCache # cache the generated types
|
typeCache*: TypeCache # cache the generated types
|
||||||
|
typeABICache*: HashSet[SigHash] # cache for ABI checks; reusing typeCache
|
||||||
|
# would be ideal but for some reason enums
|
||||||
|
# don't seem to get cached so it'd generate
|
||||||
|
# 1 ABI check per occurence in code
|
||||||
forwTypeCache*: TypeCache # cache for forward declarations of types
|
forwTypeCache*: TypeCache # cache for forward declarations of types
|
||||||
declaredThings*: IntSet # things we have declared in this .c file
|
declaredThings*: IntSet # things we have declared in this .c file
|
||||||
declaredProtos*: IntSet # prototypes we have declared in this .c file
|
declaredProtos*: IntSet # prototypes we have declared in this .c file
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ const
|
||||||
wGcSafe, wCodegenDecl} - {wExportNims, wError, wUsed} # why exclude these?
|
wGcSafe, wCodegenDecl} - {wExportNims, wError, wUsed} # why exclude these?
|
||||||
typePragmas* = declPragmas + {wMagic, wAcyclic,
|
typePragmas* = declPragmas + {wMagic, wAcyclic,
|
||||||
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
|
wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
|
||||||
wIncompleteStruct, wByCopy, wByRef,
|
wIncompleteStruct, wCompleteStruct, wByCopy, wByRef,
|
||||||
wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
|
wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
|
||||||
wBorrow, wGcSafe, wPartial, wExplain, wPackage}
|
wBorrow, wGcSafe, wPartial, wExplain, wPackage}
|
||||||
fieldPragmas* = declPragmas + {
|
fieldPragmas* = declPragmas + {
|
||||||
|
|
@ -1072,6 +1072,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||||
noVal(c, it)
|
noVal(c, it)
|
||||||
if sym.typ == nil: invalidPragma(c, it)
|
if sym.typ == nil: invalidPragma(c, it)
|
||||||
else: incl(sym.typ.flags, tfIncompleteStruct)
|
else: incl(sym.typ.flags, tfIncompleteStruct)
|
||||||
|
of wCompleteStruct:
|
||||||
|
noVal(c, it)
|
||||||
|
if sym.typ == nil: invalidPragma(c, it)
|
||||||
|
else: incl(sym.typ.flags, tfCompleteStruct)
|
||||||
of wUnchecked:
|
of wUnchecked:
|
||||||
noVal(c, it)
|
noVal(c, it)
|
||||||
if sym.typ == nil or sym.typ.kind notin {tyArray, tyUncheckedArray}:
|
if sym.typ == nil or sym.typ.kind notin {tyArray, tyUncheckedArray}:
|
||||||
|
|
|
||||||
|
|
@ -382,7 +382,8 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
|
||||||
computeObjectOffsetsFoldFunction(conf, typ.n, false, accum)
|
computeObjectOffsetsFoldFunction(conf, typ.n, false, accum)
|
||||||
let paddingAtEnd = int16(accum.finish())
|
let paddingAtEnd = int16(accum.finish())
|
||||||
if typ.sym != nil and
|
if typ.sym != nil and
|
||||||
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc}:
|
typ.sym.flags * {sfCompilerProc, sfImportc} == {sfImportc} and
|
||||||
|
tfCompleteStruct notin typ.flags:
|
||||||
typ.size = szUnknownSize
|
typ.size = szUnknownSize
|
||||||
typ.align = szUnknownSize
|
typ.align = szUnknownSize
|
||||||
typ.paddingAtEnd = szUnknownSize
|
typ.paddingAtEnd = szUnknownSize
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ type
|
||||||
preferGenericArg,
|
preferGenericArg,
|
||||||
preferTypeName,
|
preferTypeName,
|
||||||
preferResolved, # fully resolved symbols
|
preferResolved, # fully resolved symbols
|
||||||
preferMixed, # show symbol + resolved symbols if it differs, eg: seq[cint{int32}, float]
|
preferMixed,
|
||||||
|
# most useful, shows: symbol + resolved symbols if it differs, eg:
|
||||||
|
# tuple[a: MyInt{int}, b: float]
|
||||||
|
|
||||||
proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string
|
proc typeToString*(typ: PType; prefer: TPreferedDesc = preferName): string
|
||||||
template `$`*(typ: PType): string = typeToString(typ)
|
template `$`*(typ: PType): string = typeToString(typ)
|
||||||
|
|
@ -121,6 +123,13 @@ proc isIntLit*(t: PType): bool {.inline.} =
|
||||||
proc isFloatLit*(t: PType): bool {.inline.} =
|
proc isFloatLit*(t: PType): bool {.inline.} =
|
||||||
result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit
|
result = t.kind == tyFloat and t.n != nil and t.n.kind == nkFloatLit
|
||||||
|
|
||||||
|
proc addDeclaredLoc(result: var string, conf: ConfigRef; sym: PSym) =
|
||||||
|
result.add " [declared in " & conf$sym.info & "]"
|
||||||
|
|
||||||
|
proc addTypeHeader*(result: var string, conf: ConfigRef; typ: PType; prefer: TPreferedDesc = preferMixed; getDeclarationPath = true) =
|
||||||
|
result.add typeToString(typ, prefer)
|
||||||
|
if getDeclarationPath: result.addDeclaredLoc(conf, typ.sym)
|
||||||
|
|
||||||
proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferName; getDeclarationPath = true): string =
|
proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferName; getDeclarationPath = true): string =
|
||||||
assert sym != nil
|
assert sym != nil
|
||||||
# consider using `skipGenericOwner` to avoid fun2.fun2 when fun2 is generic
|
# consider using `skipGenericOwner` to avoid fun2.fun2 when fun2 is generic
|
||||||
|
|
@ -140,10 +149,7 @@ proc getProcHeader*(conf: ConfigRef; sym: PSym; prefer: TPreferedDesc = preferNa
|
||||||
result.add(')')
|
result.add(')')
|
||||||
if n[0].typ != nil:
|
if n[0].typ != nil:
|
||||||
result.add(": " & typeToString(n[0].typ, prefer))
|
result.add(": " & typeToString(n[0].typ, prefer))
|
||||||
if getDeclarationPath:
|
if getDeclarationPath: result.addDeclaredLoc(conf, sym)
|
||||||
result.add " [declared in "
|
|
||||||
result.add(conf$sym.info)
|
|
||||||
result.add "]"
|
|
||||||
|
|
||||||
proc elemType*(t: PType): PType =
|
proc elemType*(t: PType): PType =
|
||||||
assert(t != nil)
|
assert(t != nil)
|
||||||
|
|
|
||||||
|
|
@ -726,6 +726,9 @@ proc genBinaryABCD(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
|
||||||
c.freeTemp(tmp2)
|
c.freeTemp(tmp2)
|
||||||
c.freeTemp(tmp3)
|
c.freeTemp(tmp3)
|
||||||
|
|
||||||
|
template sizeOfLikeMsg(name): string =
|
||||||
|
"'$1' requires '.importc' types to be '.completeStruct'" % [name]
|
||||||
|
|
||||||
proc genNarrow(c: PCtx; n: PNode; dest: TDest) =
|
proc genNarrow(c: PCtx; n: PNode; dest: TDest) =
|
||||||
let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
|
let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
|
||||||
# uint is uint64 in the VM, we we only need to mask the result for
|
# uint is uint64 in the VM, we we only need to mask the result for
|
||||||
|
|
@ -1317,11 +1320,11 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
|
||||||
else:
|
else:
|
||||||
globalError(c.config, n.info, "expandToAst requires a call expression")
|
globalError(c.config, n.info, "expandToAst requires a call expression")
|
||||||
of mSizeOf:
|
of mSizeOf:
|
||||||
globalError(c.config, n.info, "cannot evaluate 'sizeof' because its type is not defined completely")
|
globalError(c.config, n.info, sizeOfLikeMsg("sizeof"))
|
||||||
of mAlignOf:
|
of mAlignOf:
|
||||||
globalError(c.config, n.info, "cannot evaluate 'alignof' because its type is not defined completely")
|
globalError(c.config, n.info, sizeOfLikeMsg("alignof"))
|
||||||
of mOffsetOf:
|
of mOffsetOf:
|
||||||
globalError(c.config, n.info, "cannot evaluate 'offsetof' because its type is not defined completely")
|
globalError(c.config, n.info, sizeOfLikeMsg("offsetof"))
|
||||||
of mRunnableExamples:
|
of mRunnableExamples:
|
||||||
discard "just ignore any call to runnableExamples"
|
discard "just ignore any call to runnableExamples"
|
||||||
of mDestroy: discard "ignore calls to the default destructor"
|
of mDestroy: discard "ignore calls to the default destructor"
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,10 @@ type
|
||||||
wImmediate, wConstructor, wDestructor, wDelegator, wOverride,
|
wImmediate, wConstructor, wDestructor, wDelegator, wOverride,
|
||||||
wImportCpp, wImportObjC,
|
wImportCpp, wImportObjC,
|
||||||
wImportCompilerProc,
|
wImportCompilerProc,
|
||||||
wImportc, wImportJs, wExportc, wExportCpp, wExportNims, wIncompleteStruct, wRequiresInit,
|
wImportc, wImportJs, wExportc, wExportCpp, wExportNims,
|
||||||
|
wIncompleteStruct, # deprecated
|
||||||
|
wCompleteStruct,
|
||||||
|
wRequiresInit,
|
||||||
wAlign, wNodecl, wPure, wSideEffect, wHeader,
|
wAlign, wNodecl, wPure, wSideEffect, wHeader,
|
||||||
wNoSideEffect, wGcSafe, wNoreturn, wNosinks, wMerge, wLib, wDynlib,
|
wNoSideEffect, wGcSafe, wNoreturn, wNosinks, wMerge, wLib, wDynlib,
|
||||||
wCompilerProc, wCore, wProcVar, wBase, wUsed,
|
wCompilerProc, wCore, wProcVar, wBase, wUsed,
|
||||||
|
|
@ -128,6 +131,7 @@ const
|
||||||
"importcpp", "importobjc",
|
"importcpp", "importobjc",
|
||||||
"importcompilerproc", "importc", "importjs", "exportc", "exportcpp", "exportnims",
|
"importcompilerproc", "importc", "importjs", "exportc", "exportcpp", "exportnims",
|
||||||
"incompletestruct",
|
"incompletestruct",
|
||||||
|
"completestruct",
|
||||||
"requiresinit", "align", "nodecl", "pure", "sideeffect",
|
"requiresinit", "align", "nodecl", "pure", "sideeffect",
|
||||||
"header", "nosideeffect", "gcsafe", "noreturn", "nosinks", "merge", "lib", "dynlib",
|
"header", "nosideeffect", "gcsafe", "noreturn", "nosinks", "merge", "lib", "dynlib",
|
||||||
"compilerproc", "core", "procvar", "base", "used",
|
"compilerproc", "core", "procvar", "base", "used",
|
||||||
|
|
@ -186,6 +190,7 @@ proc canonPragmaSpelling*(w: TSpecialWord): string =
|
||||||
of wNoSideEffect: "noSideEffect"
|
of wNoSideEffect: "noSideEffect"
|
||||||
of wImportCompilerProc: "importCompilerProc"
|
of wImportCompilerProc: "importCompilerProc"
|
||||||
of wIncompleteStruct: "incompleteStruct"
|
of wIncompleteStruct: "incompleteStruct"
|
||||||
|
of wCompleteStruct: "completeStruct"
|
||||||
of wRequiresInit: "requiresInit"
|
of wRequiresInit: "requiresInit"
|
||||||
of wSideEffect: "sideEffect"
|
of wSideEffect: "sideEffect"
|
||||||
of wLineDir: "lineDir"
|
of wLineDir: "lineDir"
|
||||||
|
|
|
||||||
|
|
@ -438,9 +438,8 @@ Define Effect
|
||||||
``memProfiler`` Enables memory profiling for the native GC.
|
``memProfiler`` Enables memory profiling for the native GC.
|
||||||
``uClibc`` Use uClibc instead of libc. (Relevant for Unix-like OSes)
|
``uClibc`` Use uClibc instead of libc. (Relevant for Unix-like OSes)
|
||||||
``checkAbi`` When using types from C headers, add checks that compare
|
``checkAbi`` When using types from C headers, add checks that compare
|
||||||
what's in the Nim file with what's in the C header
|
what's in the Nim file with what's in the C header.
|
||||||
(requires a C compiler with _Static_assert support, like
|
This may become enabled by default in the future.
|
||||||
any C11 compiler)
|
|
||||||
``tempDir`` This symbol takes a string as its value, like
|
``tempDir`` This symbol takes a string as its value, like
|
||||||
``--define:tempDir:/some/temp/path`` to override the
|
``--define:tempDir:/some/temp/path`` to override the
|
||||||
temporary directory returned by ``os.getTempDir()``.
|
temporary directory returned by ``os.getTempDir()``.
|
||||||
|
|
|
||||||
|
|
@ -562,10 +562,6 @@ NIM_STATIC_ASSERT(sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8, ""
|
||||||
# include <sys/types.h>
|
# include <sys/types.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Compile with -d:checkAbi and a sufficiently C11:ish compiler to enable */
|
|
||||||
#define NIM_CHECK_SIZE(typ, sz) \
|
|
||||||
_Static_assert(sizeof(typ) == sz, "Nim & C disagree on type size")
|
|
||||||
|
|
||||||
/* these exist to make the codegen logic simpler */
|
/* these exist to make the codegen logic simpler */
|
||||||
#define nimModInt(a, b, res) (((*res) = (a) % (b)), 0)
|
#define nimModInt(a, b, res) (((*res) = (a) % (b)), 0)
|
||||||
#define nimModInt64(a, b, res) (((*res) = (a) % (b)), 0)
|
#define nimModInt64(a, b, res) (((*res) = (a) % (b)), 0)
|
||||||
|
|
|
||||||
131
tests/misc/msizeof5.nim
Normal file
131
tests/misc/msizeof5.nim
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
## tests for -d:checkAbi used by addAbiCheck via NIM_STATIC_ASSERT
|
||||||
|
|
||||||
|
{.emit:"""/*TYPESECTION*/
|
||||||
|
struct Foo1{
|
||||||
|
int a;
|
||||||
|
};
|
||||||
|
struct Foo2{
|
||||||
|
int a;
|
||||||
|
};
|
||||||
|
enum Foo3{k1, k2};
|
||||||
|
typedef enum Foo3 Foo3b;
|
||||||
|
typedef enum Foo4{k3, k4} Foo4;
|
||||||
|
|
||||||
|
typedef int Foo5[3];
|
||||||
|
|
||||||
|
typedef struct Foo6{
|
||||||
|
int a1;
|
||||||
|
bool a2;
|
||||||
|
double a3;
|
||||||
|
struct Foo6* a4;
|
||||||
|
} Foo6;
|
||||||
|
""".}
|
||||||
|
|
||||||
|
template ensureCgen(T: typedesc) =
|
||||||
|
## ensures cgen
|
||||||
|
var a {.volatile.}: T
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo1Alias{.importc: "struct Foo1", size: sizeof(cint).} = object
|
||||||
|
a: cint
|
||||||
|
ensureCgen Foo1Alias
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo3Alias{.importc: "enum Foo3", size: sizeof(cint).} = enum
|
||||||
|
k1, k2
|
||||||
|
ensureCgen Foo3Alias
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo3bAlias{.importc: "Foo3b", size: sizeof(cint).} = enum
|
||||||
|
k1, k2
|
||||||
|
ensureCgen Foo3bAlias
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo3b{.importc, size: sizeof(cint).} = enum
|
||||||
|
k1, k2
|
||||||
|
ensureCgen Foo3b
|
||||||
|
static:
|
||||||
|
doAssert Foo3b.sizeof == cint.sizeof
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo4{.importc, size: sizeof(cint).} = enum
|
||||||
|
k3, k4
|
||||||
|
# adding entries should not yield duplicate ABI checks, as enforced by
|
||||||
|
# `typeABICache`.
|
||||||
|
# Currently the test doesn't check for this but you can inspect the cgen'd file
|
||||||
|
ensureCgen Foo4
|
||||||
|
ensureCgen Foo4
|
||||||
|
ensureCgen Foo4
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo5{.importc.} = array[3, cint]
|
||||||
|
ensureCgen Foo5
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo5{.importc.} = array[3, cint]
|
||||||
|
ensureCgen Foo5
|
||||||
|
|
||||||
|
block: # CT sizeof
|
||||||
|
type Foo6GT = object # grountruth
|
||||||
|
a1: cint
|
||||||
|
a2: bool
|
||||||
|
a3: cfloat
|
||||||
|
a4: ptr Foo6GT
|
||||||
|
|
||||||
|
type Foo6{.importc, completeStruct.} = object
|
||||||
|
a1: cint
|
||||||
|
a2: bool
|
||||||
|
a3: cfloat
|
||||||
|
a4: ptr Foo6
|
||||||
|
|
||||||
|
static: doAssert compiles(static(Foo6.sizeof))
|
||||||
|
static: doAssert Foo6.sizeof == Foo6GT.sizeof
|
||||||
|
static: doAssert (Foo6, int, array[2, Foo6]).sizeof ==
|
||||||
|
(Foo6GT, int, array[2, Foo6GT]).sizeof
|
||||||
|
|
||||||
|
block:
|
||||||
|
type GoodImportcType {.importc: "signed char", nodecl.} = char
|
||||||
|
# "good" in sense the sizeof will match
|
||||||
|
ensureCgen GoodImportcType
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo6{.importc.} = object
|
||||||
|
a1: cint
|
||||||
|
doAssert compiles(Foo6.sizeof)
|
||||||
|
static: doAssert not compiles(static(Foo6.sizeof))
|
||||||
|
|
||||||
|
when defined caseBad:
|
||||||
|
# Each case below should give a static cgen assert fail message
|
||||||
|
|
||||||
|
block:
|
||||||
|
type BadImportcType {.importc: "unsigned char", nodecl.} = uint64
|
||||||
|
# "sizeof" check will fail
|
||||||
|
ensureCgen BadImportcType
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo2AliasBad{.importc: "struct Foo2", size: 1.} = object
|
||||||
|
a: cint
|
||||||
|
ensureCgen Foo2AliasBad
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo5{.importc.} = array[4, cint]
|
||||||
|
ensureCgen Foo5
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo5{.importc.} = array[3, bool]
|
||||||
|
ensureCgen Foo5
|
||||||
|
|
||||||
|
block:
|
||||||
|
type Foo6{.importc, completeStruct.} = object
|
||||||
|
a1: cint
|
||||||
|
# a2: bool # missing this should trigger assert fail
|
||||||
|
a3: cfloat
|
||||||
|
a4: ptr Foo6
|
||||||
|
ensureCgen Foo6
|
||||||
|
|
||||||
|
when false:
|
||||||
|
block:
|
||||||
|
# pre-existing BUG: this should give a CT error in semcheck because `size`
|
||||||
|
# disagrees with `array[3, cint]`
|
||||||
|
type Foo5{.importc, size: 1.} = array[3, cint]
|
||||||
|
ensureCgen Foo5
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
discard """
|
discard """
|
||||||
errormsg: "cannot evaluate 'sizeof' because its type is not defined completely"
|
errormsg: "'sizeof' requires '.importc' types to be '.completeStruct'"
|
||||||
line: 9
|
line: 9
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,8 @@ proc runCmd(file, options = ""): auto =
|
||||||
echo result[0]
|
echo result[0]
|
||||||
echo result[1]
|
echo result[1]
|
||||||
|
|
||||||
proc testCodegenStaticAssert() =
|
when defined(nimHasLibFFIEnabled):
|
||||||
let (output, exitCode) = runCmd("ccgbugs/mstatic_assert.nim")
|
block: # mevalffi
|
||||||
doAssert "sizeof(bool) == 2" in output
|
|
||||||
doAssert exitCode != 0
|
|
||||||
|
|
||||||
proc testCTFFI() =
|
|
||||||
let (output, exitCode) = runCmd("vm/mevalffi.nim", "--experimental:compiletimeFFI")
|
let (output, exitCode) = runCmd("vm/mevalffi.nim", "--experimental:compiletimeFFI")
|
||||||
let expected = """
|
let expected = """
|
||||||
hello world stderr
|
hello world stderr
|
||||||
|
|
@ -40,7 +36,27 @@ ret={s1:foobar s2:foobar age:25 pi:3.14}
|
||||||
doAssert output == expected, output
|
doAssert output == expected, output
|
||||||
doAssert exitCode == 0
|
doAssert exitCode == 0
|
||||||
|
|
||||||
when defined(nimHasLibFFIEnabled):
|
|
||||||
testCTFFI()
|
|
||||||
else: # don't run twice the same test
|
else: # don't run twice the same test
|
||||||
testCodegenStaticAssert()
|
template check(msg) = doAssert msg in output, output
|
||||||
|
|
||||||
|
block: # mstatic_assert
|
||||||
|
let (output, exitCode) = runCmd("ccgbugs/mstatic_assert.nim", "-d:caseBad")
|
||||||
|
check "sizeof(bool) == 2"
|
||||||
|
doAssert exitCode != 0
|
||||||
|
|
||||||
|
block: # ABI checks
|
||||||
|
let file = "misc/msizeof5.nim"
|
||||||
|
block:
|
||||||
|
let (output, exitCode) = runCmd(file, "-d:checkAbi")
|
||||||
|
doAssert exitCode == 0, output
|
||||||
|
block:
|
||||||
|
let (output, exitCode) = runCmd(file, "-d:checkAbi -d:caseBad")
|
||||||
|
# on platforms that support _StaticAssert natively, errors will show full context, eg:
|
||||||
|
# error: static_assert failed due to requirement 'sizeof(unsigned char) == 8'
|
||||||
|
# "backend & Nim disagree on size for: BadImportcType{int64} [declared in mabi_check.nim(1, 6)]"
|
||||||
|
check "sizeof(unsigned char) == 8"
|
||||||
|
check "sizeof(struct Foo2) == 1"
|
||||||
|
check "sizeof(Foo5) == 16"
|
||||||
|
check "sizeof(Foo5) == 3"
|
||||||
|
check "sizeof(struct Foo6) == "
|
||||||
|
doAssert exitCode != 0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue