summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorTimothee Cour <timothee.cour2@gmail.com>2020-04-23 02:24:09 -0700
committerGitHub <noreply@github.com>2020-04-23 11:24:09 +0200
commit66db9de714be7c2f4cf1f2fb0a0a99145baf6acb (patch)
tree8675842299fe22ad237b06bb923a880789c9faa4
parent5c534b2943145106db99212fb9bb75fde289b6f5 (diff)
downloadNim-66db9de714be7c2f4cf1f2fb0a0a99145baf6acb.tar.gz
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.})
-rw-r--r--compiler/ast.nim3
-rw-r--r--compiler/ccgmerge.nim4
-rw-r--r--compiler/ccgtypes.nim20
-rw-r--r--compiler/cgendata.nim8
-rw-r--r--compiler/pragmas.nim6
-rw-r--r--compiler/sizealignoffsetimpl.nim3
-rw-r--r--compiler/types.nim16
-rw-r--r--compiler/vmgen.nim9
-rw-r--r--compiler/wordrecg.nim7
-rw-r--r--doc/nimc.rst5
-rw-r--r--lib/nimbase.h4
-rw-r--r--tests/misc/msizeof5.nim131
-rw-r--r--tests/misc/tsizeof2.nim2
-rw-r--r--tests/trunner.nim42
14 files changed, 221 insertions, 39 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 110ee79e3..2e3169665 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -553,6 +553,9 @@ type
                            # If it has one, t.destructor is not nil.
     tfAcyclic # object type was annotated as .acyclic
     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]
 
diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim
index 2be34aace..c12d6c0b2 100644
--- a/compiler/ccgmerge.nim
+++ b/compiler/ccgmerge.nim
@@ -48,6 +48,10 @@ const
   NimMergeEndMark = "/*\tNIM_merge_END:*/"
 
 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):
     result = nil
     result.add("\n/*\t")
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index 46949831e..c4836c447 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -247,8 +247,13 @@ proc cacheGetType(tab: TypeCache; sig: SigHash): Rope =
   result = tab.getOrDefault(sig)
 
 proc addAbiCheck(m: BModule, t: PType, name: Rope) =
-  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)])
+  if isDefined(m.config, "checkAbi") and (let size = getSize(m.config, t); size != szUnknownSize):
+    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 =
   var pt = skipTypes(s.typ, typedescInst)
@@ -328,7 +333,6 @@ proc getSimpleTypeDesc(m: BModule, typ: PType): Rope =
     let sig = hashType typ
     if cacheGetType(m.typeCache, sig) == nil:
       m.typeCache[sig] = result
-      addAbiCheck(m, typ, result)
 
 proc pushType(m: BModule, typ: PType) =
   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 =
   # returns only the type's name
+
   var t = origTyp.skipTypes(irrelevantForBackend-{tyOwned})
   if containsOrIncl(check, t.id):
     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 != origTyp and origTyp.sym != nil: useHeader(m, origTyp.sym)
   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)
   if result != nil:
     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)
       m.s[cfsTypes].addf("typedef $1 $2[$3];$n",
            [foo, result, rope(n)])
-    else: addAbiCheck(m, t, result)
   of tyObject, tyTuple:
     if isImportedCppType(t) and origTyp.kind == tyGenericInst:
       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)
         if not isImportedType(t):
           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:
     # Don't use the imported name as it may be scoped: 'Foo::SomeKind'
     result = $t.kind & '_' & t.lastSon.typeName & $t.lastSon.hashType
diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim
index bf7daeea6..96e382873 100644
--- a/compiler/cgendata.nim
+++ b/compiler/cgendata.nim
@@ -11,7 +11,7 @@
 
 import
   ast, ropes, options, intsets,
-  tables, ndi, lineinfos, pathutils, modulegraphs
+  tables, ndi, lineinfos, pathutils, modulegraphs, sets
 
 type
   TLabel* = Rope              # for the C generator a label is just a rope
@@ -25,7 +25,7 @@ type
                               # this is needed for strange type generation
                               # reasons
     cfsFieldInfo,             # section for field information
-    cfsTypeInfo,              # section for type information
+    cfsTypeInfo,              # section for type information (ag ABI checks)
     cfsProcHeaders,           # section for C procs prototypes
     cfsData,                  # section for C constant data
     cfsVars,                  # section for C variable declarations
@@ -144,6 +144,10 @@ type
                               # without extension)
     tmpBase*: Rope            # base for temp identifier generation
     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
     declaredThings*: IntSet   # things we have declared in this .c file
     declaredProtos*: IntSet   # prototypes we have declared in this .c file
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index ffe93a9f3..465608b10 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -61,7 +61,7 @@ const
     wGcSafe, wCodegenDecl} - {wExportNims, wError, wUsed}  # why exclude these?
   typePragmas* = declPragmas + {wMagic, wAcyclic,
     wPure, wHeader, wCompilerProc, wCore, wFinal, wSize, wShallow,
-    wIncompleteStruct, wByCopy, wByRef,
+    wIncompleteStruct, wCompleteStruct, wByCopy, wByRef,
     wInheritable, wGensym, wInject, wRequiresInit, wUnchecked, wUnion, wPacked,
     wBorrow, wGcSafe, wPartial, wExplain, wPackage}
   fieldPragmas* = declPragmas + {
@@ -1072,6 +1072,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
         noVal(c, it)
         if sym.typ == nil: invalidPragma(c, it)
         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:
         noVal(c, it)
         if sym.typ == nil or sym.typ.kind notin {tyArray, tyUncheckedArray}:
diff --git a/compiler/sizealignoffsetimpl.nim b/compiler/sizealignoffsetimpl.nim
index 81d2f5faf..dc3fefeea 100644
--- a/compiler/sizealignoffsetimpl.nim
+++ b/compiler/sizealignoffsetimpl.nim
@@ -382,7 +382,8 @@ proc computeSizeAlign(conf: ConfigRef; typ: PType) =
         computeObjectOffsetsFoldFunction(conf, typ.n, false, accum)
       let paddingAtEnd = int16(accum.finish())
       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.align = szUnknownSize
         typ.paddingAtEnd = szUnknownSize
diff --git a/compiler/types.nim b/compiler/types.nim
index c55ecd2aa..eac9695b6 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -22,7 +22,9 @@ type
     preferGenericArg,
     preferTypeName,
     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
 template `$`*(typ: PType): string = typeToString(typ)
@@ -121,6 +123,13 @@ proc isIntLit*(t: PType): bool {.inline.} =
 proc isFloatLit*(t: PType): bool {.inline.} =
   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 =
   assert sym != nil
   # 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(')')
     if n[0].typ != nil:
       result.add(": " & typeToString(n[0].typ, prefer))
-  if getDeclarationPath:
-    result.add " [declared in "
-    result.add(conf$sym.info)
-    result.add "]"
+  if getDeclarationPath: result.addDeclaredLoc(conf, sym)
 
 proc elemType*(t: PType): PType =
   assert(t != nil)
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index 5b355bcab..b408db08b 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -726,6 +726,9 @@ proc genBinaryABCD(c: PCtx; n: PNode; dest: var TDest; opc: TOpcode) =
   c.freeTemp(tmp2)
   c.freeTemp(tmp3)
 
+template sizeOfLikeMsg(name): string =
+  "'$1' requires '.importc' types to be '.completeStruct'" % [name]
+
 proc genNarrow(c: PCtx; n: PNode; dest: TDest) =
   let t = skipTypes(n.typ, abstractVar-{tyTypeDesc})
   # 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:
       globalError(c.config, n.info, "expandToAst requires a call expression")
   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:
-    globalError(c.config, n.info, "cannot evaluate 'alignof' because its type is not defined completely")
+    globalError(c.config, n.info, sizeOfLikeMsg("alignof"))
   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:
     discard "just ignore any call to runnableExamples"
   of mDestroy: discard "ignore calls to the default destructor"
diff --git a/compiler/wordrecg.nim b/compiler/wordrecg.nim
index 30a003dee..446897844 100644
--- a/compiler/wordrecg.nim
+++ b/compiler/wordrecg.nim
@@ -40,7 +40,10 @@ type
     wImmediate, wConstructor, wDestructor, wDelegator, wOverride,
     wImportCpp, wImportObjC,
     wImportCompilerProc,
-    wImportc, wImportJs, wExportc, wExportCpp, wExportNims, wIncompleteStruct, wRequiresInit,
+    wImportc, wImportJs, wExportc, wExportCpp, wExportNims,
+    wIncompleteStruct, # deprecated
+    wCompleteStruct,
+    wRequiresInit,
     wAlign, wNodecl, wPure, wSideEffect, wHeader,
     wNoSideEffect, wGcSafe, wNoreturn, wNosinks, wMerge, wLib, wDynlib,
     wCompilerProc, wCore, wProcVar, wBase, wUsed,
@@ -128,6 +131,7 @@ const
     "importcpp", "importobjc",
     "importcompilerproc", "importc", "importjs", "exportc", "exportcpp", "exportnims",
     "incompletestruct",
+    "completestruct",
     "requiresinit", "align", "nodecl", "pure", "sideeffect",
     "header", "nosideeffect", "gcsafe", "noreturn", "nosinks", "merge", "lib", "dynlib",
     "compilerproc", "core", "procvar", "base", "used",
@@ -186,6 +190,7 @@ proc canonPragmaSpelling*(w: TSpecialWord): string =
   of wNoSideEffect: "noSideEffect"
   of wImportCompilerProc: "importCompilerProc"
   of wIncompleteStruct: "incompleteStruct"
+  of wCompleteStruct: "completeStruct"
   of wRequiresInit: "requiresInit"
   of wSideEffect: "sideEffect"
   of wLineDir: "lineDir"
diff --git a/doc/nimc.rst b/doc/nimc.rst
index 138f1e7f0..7fdceacf5 100644
--- a/doc/nimc.rst
+++ b/doc/nimc.rst
@@ -438,9 +438,8 @@ Define                   Effect
 ``memProfiler``          Enables memory profiling for the native GC.
 ``uClibc``               Use uClibc instead of libc. (Relevant for Unix-like OSes)
 ``checkAbi``             When using types from C headers, add checks that compare
-                         what's in the Nim file with what's in the C header
-                         (requires a C compiler with _Static_assert support, like
-                         any C11 compiler)
+                         what's in the Nim file with what's in the C header.
+                         This may become enabled by default in the future.
 ``tempDir``              This symbol takes a string as its value, like
                          ``--define:tempDir:/some/temp/path`` to override the
                          temporary directory returned by ``os.getTempDir()``.
diff --git a/lib/nimbase.h b/lib/nimbase.h
index d4fa10050..0b0188b55 100644
--- a/lib/nimbase.h
+++ b/lib/nimbase.h
@@ -562,10 +562,6 @@ NIM_STATIC_ASSERT(sizeof(NI) == sizeof(void*) && NIM_INTBITS == sizeof(NI)*8, ""
 #  include <sys/types.h>
 #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 */
 #define nimModInt(a, b, res) (((*res) = (a) % (b)), 0)
 #define nimModInt64(a, b, res) (((*res) = (a) % (b)), 0)
diff --git a/tests/misc/msizeof5.nim b/tests/misc/msizeof5.nim
new file mode 100644
index 000000000..c833d1e43
--- /dev/null
+++ b/tests/misc/msizeof5.nim
@@ -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
diff --git a/tests/misc/tsizeof2.nim b/tests/misc/tsizeof2.nim
index a193cf7c3..da28de508 100644
--- a/tests/misc/tsizeof2.nim
+++ b/tests/misc/tsizeof2.nim
@@ -1,5 +1,5 @@
 discard """
-errormsg: "cannot evaluate 'sizeof' because its type is not defined completely"
+errormsg: "'sizeof' requires '.importc' types to be '.completeStruct'"
 line: 9
 """
 
diff --git a/tests/trunner.nim b/tests/trunner.nim
index 7e1468e84..263184571 100644
--- a/tests/trunner.nim
+++ b/tests/trunner.nim
@@ -19,14 +19,10 @@ proc runCmd(file, options = ""): auto =
     echo result[0]
     echo result[1]
 
-proc testCodegenStaticAssert() =
-  let (output, exitCode) = runCmd("ccgbugs/mstatic_assert.nim")
-  doAssert "sizeof(bool) == 2" in output
-  doAssert exitCode != 0
-
-proc testCTFFI() =
-  let (output, exitCode) = runCmd("vm/mevalffi.nim", "--experimental:compiletimeFFI")
-  let expected = """
+when defined(nimHasLibFFIEnabled):
+  block: # mevalffi
+    let (output, exitCode) = runCmd("vm/mevalffi.nim", "--experimental:compiletimeFFI")
+    let expected = """
 hello world stderr
 hi stderr
 foo
@@ -37,10 +33,30 @@ foo:102:103:104
 foo:0.03:asdf:103:105
 ret={s1:foobar s2:foobar age:25 pi:3.14}
 """
-  doAssert output == expected, output
-  doAssert exitCode == 0
+    doAssert output == expected, output
+    doAssert exitCode == 0
 
-when defined(nimHasLibFFIEnabled):
-  testCTFFI()
 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