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:
Timothee Cour 2020-04-23 02:24:09 -07:00 • committed by GitHub
commit 66db9de714
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 221 additions and 39 deletions

View file

@ -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]

View file

@ -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")

View file

@ -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

View file

@ -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

View 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}:

View file

@ -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

View file

@ -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)

View file

@ -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"

View file

@ -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"

View file

@ -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()``.

View file

@ -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
View 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

View file

@ -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
""" """

View file

@ -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