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.
|
||||
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]
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue