Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CT sizeof(+friends) for {.importc, completeStruct.} types, enable ABI static checks #13926

Merged
merged 16 commits into from
Apr 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions compiler/ast.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
4 changes: 4 additions & 0 deletions compiler/ccgmerge.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
20 changes: 15 additions & 5 deletions compiler/ccgtypes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)):
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions compiler/cgendata.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion compiler/pragmas.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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 + {
Expand Down Expand Up @@ -1073,6 +1073,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}:
Expand Down
3 changes: 2 additions & 1 deletion compiler/sizealignoffsetimpl.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 11 additions & 5 deletions compiler/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down
9 changes: 6 additions & 3 deletions compiler/vmgen.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"))
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
of mRunnableExamples:
discard "just ignore any call to runnableExamples"
of mDestroy: discard "ignore calls to the default destructor"
Expand Down
7 changes: 6 additions & 1 deletion compiler/wordrecg.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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"
Expand Down
5 changes: 2 additions & 3 deletions doc/nimc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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()``.
Expand Down
4 changes: 0 additions & 4 deletions lib/nimbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -561,10 +561,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)
Expand Down
131 changes: 131 additions & 0 deletions tests/misc/msizeof5.nim
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion tests/misc/tsizeof2.nim
Original file line number Diff line number Diff line change
@@ -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
"""

Expand Down
Loading