# # # The Nim Compiler # (c) Copyright 2015 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## This module implements the C code generator. import ast, astalgo, trees, platform, magicsys, extccomp, options, nversion, nimsets, msgs, bitsets, idents, types, ccgutils, ropes, wordrecg, treetab, cgmeth, rodutils, renderer, cgendata, aliases, lowerings, lineinfos, pathutils, transf, injectdestructors, astmsgs, modulepaths, pushpoppragmas, mangleutils, cbuilderbase, modulegraphs from expanddefaults import caseObjDefaultBranch from ast2nif import globalName, toNifFilename, icNifTypeName from typekeys import modname from std/algorithm import sort import cnif import pipelineutils when defined(nimPreviewSlimSystem): import std/assertions when not defined(leanCompiler): import spawn, semparallel import std/strutils except `%`, addf # collides with ropes.`%` import std/[dynlib, math, tables, sets, os, intsets, hashes] const # we use some ASCII control characters to insert directives that will be converted to real code in a postprocessing pass postprocessDirStart = '\1' postprocessDirSep = '\31' postprocessDirEnd = '\23' when not declared(dynlib.libCandidates): proc libCandidates(s: string, dest: var seq[string]) = ## given a library name pattern `s` write possible library names to `dest`. var le = strutils.find(s, '(') var ri = strutils.find(s, ')', le+1) if le >= 0 and ri > le: var prefix = substr(s, 0, le - 1) var suffix = substr(s, ri + 1) for middle in split(substr(s, le + 1, ri - 1), '|'): libCandidates(prefix & middle & suffix, dest) else: dest.add(s) when defined(tinyc): # == hasTinyCBackend; spelled out for the IC dep scanner import tccgen proc hcrOn(m: BModule): bool = m.config.hcrOn proc hcrOn(p: BProc): bool = p.module.config.hcrOn proc addForwardedProc(m: BModule, prc: PSym) = m.g.forwardedProcs.add(prc) proc newModule*(g: BModuleList; module: PSym; conf: ConfigRef; idgen: IdGenerator): BModule proc getCFile*(m: BModule): AbsoluteFile proc findPendingModule(m: BModule, s: PSym): BModule = # TODO fixme if m.config.cmd == cmdNifC and m.config.icBackendStage == "cg": # Per-module backend codegen: only module M (`m`) is emitted in this # process, so every demanded definition — whether a normal proc owned by # another (here unwritten) module or a minted instance/hook — is emitted # into M's TU. Definitions owned elsewhere are emitted again by their own # module's cg process; the merge stage keeps one per C name and turns the # rest into prototypes (which already live in the unmarked protos section). return m if m.config.symbolFiles == v2Sf or optCompress in m.config.globalOptions: let ms = s.itemId.module #getModule(s) result = m.g.mods[ms] elif m.config.cmd in {cmdNifC, cmdM}: var ms = getModule(s) registerModule m.g.graph, ms if ms.position >= m.g.mods.len: result = newModule(m.g, ms, m.config, idGeneratorForBackend(ms)) else: result = m.g.mods[ms.position] if result == nil: result = newModule(m.g, ms, m.config, idGeneratorForBackend(ms)) else: var ms = getModule(s) result = m.g.mods[ms.position] proc icNifName(m: BModule; s: PSym): string = ## The serialized NIF name of `s`, recorded next to its C name in the cnif ## artifact so a later run can re-demand the definition when a reused TU ## still references it (the def-retention check). Backend-minted symbols ## have no NIF name. if m.config.cmd == cmdNifC and s != nil and not isBackendMinted(s.itemId): result = globalName(s, m.config) else: result = "" proc icNifName(m: BModule; t: PType): string = ## The type flavor: recorded next to RTTI data definitions so the ## def-retention check can re-demand the typeinfo of a regenerating TU's ## previous artifact (`genTypeInfo` is type-driven, not symbol-driven). if m.config.cmd == cmdNifC: result = icNifTypeName(t, m.config) else: result = "" proc emitsBodyInThisModule(m: BModule, prc: PSym): bool = ## Per-module backend codegen is concerned with ONE module: it emits the ## bodies of the routines that module OWNS (its own top-level defs) and only ## *prototypes* a routine owned by another module — that routine's body is ## emitted by its own module's `cg` process, and the merge stage's DCE prunes ## whatever ends up globally dead. The funnel where the main module re-emitted ## its entire transitive closure (≈1.8 GB, a 56 MB `.c.nif`) is exactly this ## rule being absent. ## ## Generic instances and synthesized hooks (`=destroy`, `$`, …) have no single ## owning-module top-level — they are minted on demand — so each demander emits ## them and the merge stage deduplicates by their content-addressed C name. ## ## A NESTED routine is not emitted on its own: it is lambda-lifted and emitted ## as part of its ENCLOSING routine's body, into the same TU. So the decision ## must follow the OUTERMOST enclosing routine (the one directly under the ## module — `skipGenericOwner` stops at a generic *instance*, not its ## originating generic), never the nested symbol's own identity. Otherwise a ## nested proc whose enclosing is a generic instance (content-addressed, ## emitted by every demander) — e.g. nim-serialization's per-field `readField` ## inside the `makeFieldReadersTable[R,W]` instance, whose address fills the ## returned table — is gated out (its own `itemId.module` is the minting module ## and its disamb is a plain counter), so the enclosing's lift degrades it to a ## prototype and its body lands in no TU → undefined at link. if not (m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"): return true # The symbol may ITSELF be content-addressed (a synthesized hook or a generic # instance carries `Hook/InstanceDisambBit` on its OWN `disamb`): then it has no # single owning module and every demander emits it (merge dedups by C name), # regardless of what it is nested under. This must be checked on `prc` directly, # not on `top`: a `=destroy`/`=sink` lifted while compiling some enclosing proc # (e.g. system's `isZeroMemory` destroying a `ptr array`) has that PROC as its # `skipGenericOwner`, so `top` walks up to a plain routine whose own disamb has # no bit — gating the hook to that routine's owner module, which mints it # on demand and emits it nowhere → undefined at link. if (prc.disamb and (InstanceDisambBit or HookDisambBit)) != 0'i32: return true var top = prc while top.skipGenericOwner != nil and top.skipGenericOwner.kind != skModule: top = top.skipGenericOwner result = top.itemId.module == m.module.position or (top.disamb and (InstanceDisambBit or HookDisambBit)) != 0'i32 or # An INLINE iterator has no standalone body — it is expanded at each # call site — so it is materialized in every module that iterates over # it, never in its owner. A proc nested in one (e.g. std/uri's # `parseData` inside `iterator decodeQuery`) is lambda-lifted into each # of those consumer TUs and must be emitted there (its stable # owner-suffixed name + `'u'` flag let the merge stage keep one); gating # it to the iterator's owner module leaves it in no TU → undefined. (top.kind == skIterator and top.typ != nil and top.typ.callConv != ccClosure) proc initLoc(k: TLocKind, lode: PNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc = result = TLoc(k: k, storage: s, lode: lode, snippet: "", flags: flags) proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, r: Rope, s: TStorageLoc) {.inline.} = # fills the loc if it is not already initialized if a.k == locNone: a.k = k a.lode = lode a.storage = s if a.snippet == "": a.snippet = r proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, s: TStorageLoc) {.inline.} = # fills the loc if it is not already initialized if a.k == locNone: a.k = k a.lode = lode a.storage = s proc t(a: TLoc): PType {.inline.} = if a.lode.kind == nkSym and a.lode.sym.typ != nil: result = a.lode.sym.typ else: # Under `nim ic` an object-field reference is a typeless leaf stub (its def # lives in another seek; see ast2nif `FieldMarker`) that carries its type on # the NODE instead. Fall back to the node type. Byte-neutral for non-IC, where # a real sym always has a type. result = a.lode.typ proc lodeTyp(t: PType): PNode = result = newNode(nkEmpty) result.typ = t proc isSimpleConst(typ: PType): bool = let t = skipTypes(typ, abstractVar) result = t.kind notin {tyTuple, tyObject, tyArray, tySet, tySequence} and not (t.kind == tyProc and t.callConv == ccClosure) proc useHeader(m: BModule, sym: PSym) = if lfHeader in sym.loc.flags: assert(sym.annex != nil) let str = getStr(sym.annex.path) m.includeHeader(str) proc cgsym(m: BModule, name: string) proc cgsymValue(m: BModule, name: string): Rope proc getModuleDllPath(m: BModule): Rope = let (dir, name, ext) = splitFile(getCFile(m)) let filename = strutils.`%`(platform.OS[m.g.config.target.targetOS].dllFrmt, [name & ext]) result = makeCString(dir.string & "/" & filename) proc getModuleDllPath(m: BModule, module: int): Rope = result = getModuleDllPath(m.g.mods[module]) proc getModuleDllPath(m: BModule, s: PSym): Rope = result = getModuleDllPath(m.g.mods[s.itemId.module]) import std/macros proc cgFormatValue(result: var string; value: string) = result.add value proc cgFormatValue(result: var string; value: BiggestInt) = result.addInt value proc cgFormatValue(result: var string; value: Int128) = result.addInt128 value template addf(result: var Builder, args: varargs[untyped]) = result.buf.addf(args) # TODO: please document macro ropecg(m: BModule, frmt: static[FormatStr], args: untyped): Rope = args.expectKind nnkBracket # echo "ropecg ", newLit(frmt).repr, ", ", args.repr var i = 0 result = nnkStmtListExpr.newTree() result.add quote do: assert `m` != nil let resVar = genSym(nskVar, "res") # during `koch boot` the median of all generates strings from this # macro is around 40 bytes in length. result.add newVarStmt(resVar, newCall(bindSym"newStringOfCap", newLit(80))) let formatValue = bindSym"cgFormatValue" var num = 0 var strLit = "" template flushStrLit() = if strLit != "": result.add newCall(ident "add", resVar, newLit(strLit)) strLit.setLen 0 while i < frmt.len: if frmt[i] == '$': inc(i) # skip '$' case frmt[i] of '$': strLit.add '$' inc(i) of '#': flushStrLit() inc(i) result.add newCall(formatValue, resVar, args[num]) inc(num) of '^': flushStrLit() inc(i) result.add newCall(formatValue, resVar, args[^1]) inc(num) of '0'..'9': var j = 0 while true: j = (j * 10) + ord(frmt[i]) - ord('0') inc(i) if i >= frmt.len or not (frmt[i] in {'0'..'9'}): break num = j if j > args.len: error("ropes: invalid format string " & newLit(frmt).repr & " args.len: " & $args.len) flushStrLit() result.add newCall(formatValue, resVar, args[j-1]) of 'n': flushStrLit() result.add quote do: if optLineDir notin `m`.config.options: `resVar`.add("\L") inc(i) of 'N': strLit.add "\L" inc(i) else: error("ropes: invalid format string $" & frmt[i]) elif frmt[i] == '#' and frmt[i+1] in IdentStartChars: inc(i) var j = i while frmt[j] in IdentChars: inc(j) var ident = newLit(substr(frmt, i, j-1)) i = j flushStrLit() result.add newCall(formatValue, resVar, newCall(ident"cgsymValue", m, ident)) elif frmt[i] == '#' and frmt[i+1] == '$': inc(i, 2) var j = 0 while frmt[i] in Digits: j = (j * 10) + ord(frmt[i]) - ord('0') inc(i) let ident = args[j-1] flushStrLit() result.add newCall(formatValue, resVar, newCall(ident"cgsymValue", m, ident)) elif frmt[i] == '#' and frmt[i+1] == '#': inc(i, 2) strLit.add("#") else: strLit.add(frmt[i]) inc(i) flushStrLit() result.add newCall(ident"rope", resVar) proc addIndent(p: BProc; result: var Rope) = var i = result.len let newLen = i + p.blocks.len result.setLen newLen while i < newLen: result[i] = '\t' inc i proc addIndent(p: BProc; result: var Builder) = var i = result.buf.len let newLen = i + p.blocks.len result.buf.setLen newLen while i < newLen: result.buf[i] = '\t' inc i template appcg(m: BModule, c: var (Rope | Builder), frmt: FormatStr, args: untyped) = c.add(ropecg(m, frmt, args)) template appcg(m: BModule, sec: TCFileSection, frmt: FormatStr, args: untyped) = m.s[sec].add(ropecg(m, frmt, args)) template appcg(p: BProc, sec: TCProcSection, frmt: FormatStr, args: untyped) = p.s(sec).add(ropecg(p.module, frmt, args)) template line(p: BProc, sec: TCProcSection, r: string) = addIndent p, p.s(sec) p.s(sec).add(r) template lineF(p: BProc, sec: TCProcSection, frmt: FormatStr, args: untyped) = addIndent p, p.s(sec) p.s(sec).add(frmt % args) template lineCg(p: BProc, sec: TCProcSection, frmt: FormatStr, args: untyped) = addIndent p, p.s(sec) p.s(sec).add(ropecg(p.module, frmt, args)) template linefmt(p: BProc, sec: TCProcSection, frmt: FormatStr, args: untyped) = addIndent p, p.s(sec) p.s(sec).add(ropecg(p.module, frmt, args)) proc safeLineNm(info: TLineInfo): int = result = toLinenumber(info) if result < 0: result = 0 # negative numbers are not allowed in #line proc genPostprocessDir(field1, field2, field3: string): string = result = postprocessDirStart & field1 & postprocessDirSep & field2 & postprocessDirSep & field3 & postprocessDirEnd proc genCLineDir(r: var Builder, fileIdx: FileIndex, line: int; conf: ConfigRef) = assert line >= 0 if optLineDir in conf.options and line > 0: if fileIdx == InvalidFileIdx: r.add(rope("\n#line " & $line & " \"generated_not_to_break_here\"\n")) else: r.add(rope("\n#line " & $line & " FX_" & $fileIdx.int32 & "\n")) proc genCLineDir(r: var Builder, fileIdx: FileIndex, line: int; p: BProc; info: TLineInfo; lastFileIndex: FileIndex) = assert line >= 0 if optLineDir in p.config.options and line > 0: if fileIdx == InvalidFileIdx: r.add(rope("\n#line " & $line & " \"generated_not_to_break_here\"\n")) else: r.add(rope("\n#line " & $line & " FX_" & $fileIdx.int32 & "\n")) proc genCLineDir(r: var Builder, info: TLineInfo; conf: ConfigRef) = if optLineDir in conf.options: genCLineDir(r, info.fileIndex, info.safeLineNm, conf) proc freshLineInfo(p: BProc; info: TLineInfo): bool = if p.lastLineInfo.line != info.line or p.lastLineInfo.fileIndex != info.fileIndex: p.lastLineInfo.line = info.line p.lastLineInfo.fileIndex = info.fileIndex result = true else: result = false proc genCLineDir(r: var Builder, p: BProc, info: TLineInfo; conf: ConfigRef) = if optLineDir in conf.options: let lastFileIndex = p.lastLineInfo.fileIndex if freshLineInfo(p, info): genCLineDir(r, info.fileIndex, info.safeLineNm, p, info, lastFileIndex) proc genLineDir(p: BProc, t: PNode) = if p == p.module.preInitProc: return let line = t.info.safeLineNm if optEmbedOrigSrc in p.config.globalOptions: var code = sourceLine(p.config, t.info) if code.endsWith('\\'): code.add "#" p.s(cpsStmts).add("// " & code & "\L") let lastFileIndex = p.lastLineInfo.fileIndex let freshLine = freshLineInfo(p, t.info) if freshLine: genCLineDir(p.s(cpsStmts), t.info.fileIndex, line, p, t.info, lastFileIndex) if ({optLineTrace, optStackTrace} * p.options == {optLineTrace, optStackTrace}) and (p.prc == nil or sfPure notin p.prc.flags) and t.info.fileIndex != InvalidFileIdx: if freshLine: line(p, cpsStmts, genPostprocessDir("nimln", $line, $t.info.fileIndex.int32)) proc accessThreadLocalVar(p: BProc, s: PSym) proc emulatedThreadVars(conf: ConfigRef): bool {.inline.} proc genProc(m: BModule, prc: PSym) proc raiseInstr(p: BProc; result: var Builder) template compileToCpp(m: BModule): untyped = m.config.backend == backendCpp or sfCompileToCpp in m.module.flags proc getTempName(m: BModule): Rope = result = m.tmpBase & rope(m.labels) inc m.labels proc isNoReturn(m: BModule; s: PSym): bool {.inline.} = sfNoReturn in s.flags and m.config.exc != excGoto include cbuilderexprs include cbuilderdecls include cbuilderstmts proc rdLoc(a: TLoc): Rope = # 'read' location (deref if indirect) if lfIndirect in a.flags: result = cDeref(a.snippet) else: result = a.snippet proc addRdLoc(a: TLoc; result: var Builder) = if lfIndirect in a.flags: result.add cDeref(a.snippet) else: result.add a.snippet proc lenField(p: BProc, val: Rope): Rope {.inline.} = if p.module.compileToCpp: result = derefField(val, "len") else: result = dotField(derefField(val, "Sup"), "len") proc lenExpr(p: BProc; a: TLoc): Rope = if optSeqDestructors in p.config.globalOptions: if p.config.usesSso() and a.lode != nil and a.t != nil and a.t.skipTypes(abstractInst).kind == tyString: result = cCall(cgsymValue(p.module, "nimStrLen"), rdLoc(a)) else: result = dotField(rdLoc(a), "len") else: let ra = rdLoc(a) result = cIfExpr(ra, lenField(p, ra), cIntValue(0)) proc dataFieldAccessor(p: BProc, sym: Rope): Rope = if optSeqDestructors in p.config.globalOptions: result = dotField(wrapPar(sym), "p") else: result = sym proc dataField(p: BProc, val: Rope): Rope {.inline.} = result = derefField(dataFieldAccessor(p, val), "data") proc genProcPrototype(m: BModule, sym: PSym) include ccgliterals include ccgtypes # ------------------------------ Manager of temporaries ------------------ template mapTypeChooser(n: PNode): TSymKind = (if n.kind == nkSym: n.sym.kind else: skVar) template mapTypeChooser(a: TLoc): TSymKind = mapTypeChooser(a.lode) proc addAddrLoc(conf: ConfigRef; a: TLoc; result: var Builder) = if lfIndirect notin a.flags and mapType(conf, a.t, mapTypeChooser(a) == skParam) != ctArray: result.add wrapPar(cAddr(a.snippet)) else: result.add a.snippet proc addrLoc(conf: ConfigRef; a: TLoc): Rope = if lfIndirect notin a.flags and mapType(conf, a.t, mapTypeChooser(a) == skParam) != ctArray: result = wrapPar(cAddr(a.snippet)) else: result = a.snippet proc byRefLoc(p: BProc; a: TLoc): Rope = if lfIndirect notin a.flags and mapType(p.config, a.t, mapTypeChooser(a) == skParam) != ctArray and not p.module.compileToCpp: result = wrapPar(cAddr(a.snippet)) else: result = a.snippet proc rdCharLoc(a: TLoc): Rope = # read a location that may need a char-cast: result = rdLoc(a) if skipTypes(a.t, abstractRange).kind == tyChar: result = cCast(NimUint8, result) type TAssignmentFlag = enum needToCopy needTempForOpenArray needAssignCall TAssignmentFlags = set[TAssignmentFlag] proc genObjConstr(p: BProc, e: PNode, d: var TLoc) proc rawConstExpr(p: BProc, n: PNode; d: var TLoc) proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) type ObjConstrMode = enum constructObj, constructRefObj proc genObjectInit(p: BProc, section: TCProcSection, t: PType, a: var TLoc, mode: ObjConstrMode) = #if optNimV2 in p.config.globalOptions: return case analyseObjectWithTypeField(t) of frNone: discard of frHeader: var r = rdLoc(a) if mode == constructRefObj: r = cDeref(r) var s = skipTypes(t, abstractInst) if not p.module.compileToCpp: while s.kind == tyObject and s[0] != nil: r = dotField(r, "Sup") s = skipTypes(s[0], skipPtrs) if optTinyRtti in p.config.globalOptions: p.s(section).addFieldAssignment(r, "m_type", genTypeInfoV2(p.module, t, a.lode.info)) else: p.s(section).addFieldAssignment(r, "m_type", genTypeInfoV1(p.module, t, a.lode.info)) of frEmbedded: if optTinyRtti in p.config.globalOptions: var tmp: TLoc = default(TLoc) if mode == constructRefObj: let objType = t.skipTypes(abstractInst+{tyRef}) rawConstExpr(p, newNodeIT(nkType, a.lode.info, objType), tmp) let ra = rdLoc(a) let rtmp = rdLoc(tmp) let rt = getTypeDesc(p.module, objType, descKindFromSymKind mapTypeChooser(a)) p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimCopyMem"), cCast(CPointer, ra), cCast(CConstPointer, cAddr(rtmp)), cSizeof(rt)) else: rawConstExpr(p, newNodeIT(nkType, a.lode.info, t), tmp) genAssignment(p, a, tmp, {}) else: # worst case for performance: var r = if mode == constructObj: addrLoc(p.config, a) else: rdLoc(a) p.s(section).addCallStmt(cgsymValue(p.module, "objectInit"), r, genTypeInfoV1(p.module, t, a.lode.info)) if isException(t): var r = rdLoc(a) if mode == constructRefObj: r = cDeref(r) var s = skipTypes(t, abstractInst) if not p.module.compileToCpp: while s.kind == tyObject and s[0] != nil and s.sym.magic != mException: r = dotField(r, "Sup") s = skipTypes(s[0], skipPtrs) p.s(section).addFieldAssignment(r, "name", makeCString(t.skipTypes(abstractInst).sym.name.s)) proc genRefAssign(p: BProc, dest, src: TLoc) proc isComplexValueType(t: PType): bool {.inline.} = let t = t.skipTypes(abstractInst + tyUserTypeClasses) result = t.kind in {tyArray, tySet, tyTuple, tyObject, tyOpenArray} or (t.kind == tyProc and t.callConv == ccClosure) include ccgreset proc resetLoc(p: BProc, loc: var TLoc) = let containsGcRef = optSeqDestructors notin p.config.globalOptions and containsGarbageCollectedRef(loc.t) let typ = skipTypes(loc.t, abstractVarRange) if isImportedCppType(typ): var didGenTemp = false let rl = rdLoc(loc) let init = genCppConstructorExpr(p.module, p, typ, didGenTemp) p.s(cpsStmts).addAssignment(rl, init) return if optSeqDestructors in p.config.globalOptions and typ.kind in {tyString, tySequence}: assert loc.snippet != "" let atyp = skipTypes(loc.t, abstractInst) let rl = rdLoc(loc) if typ.kind == tyString and p.config.usesSso(): # SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed) if atyp.kind in {tyVar, tyLent}: p.s(cpsStmts).addAssignment(derefField(rl, "bytes"), cIntValue(0)) p.s(cpsStmts).addAssignment(derefField(rl, "more"), NimNil) else: p.s(cpsStmts).addAssignment(dotField(rl, "bytes"), cIntValue(0)) p.s(cpsStmts).addAssignment(dotField(rl, "more"), NimNil) elif atyp.kind in {tyVar, tyLent}: p.s(cpsStmts).addAssignment(derefField(rl, "len"), cIntValue(0)) p.s(cpsStmts).addAssignment(derefField(rl, "p"), NimNil) else: p.s(cpsStmts).addAssignment(dotField(rl, "len"), cIntValue(0)) p.s(cpsStmts).addAssignment(dotField(rl, "p"), NimNil) elif not isComplexValueType(typ): if containsGcRef: var nilLoc: TLoc = initLoc(locTemp, loc.lode, OnStack) nilLoc.snippet = NimNil genRefAssign(p, loc, nilLoc) else: p.s(cpsStmts).addAssignment(rdLoc(loc), cIntValue(0)) else: if loc.storage != OnStack and containsGcRef: specializeReset(p, loc) when false: linefmt(p, cpsStmts, "#genericReset((void*)$1, $2);$n", [addrLoc(p.config, loc), genTypeInfoV1(p.module, loc.t, loc.lode.info)]) # XXX: generated reset procs should not touch the m_type # field, so disabling this should be safe: genObjectInit(p, cpsStmts, loc.t, loc, constructObj) else: # array passed as argument decayed into pointer, bug #7332 # so we use getTypeDesc here rather than rdLoc(loc) let tyDesc = getTypeDesc(p.module, loc.t, descKindFromSymKind mapTypeChooser(loc)) if p.module.compileToCpp and isOrHasImportedCppType(typ): if lfIndirect in loc.flags: #C++ cant be just zeroed. We need to call the ctors var tmp = getTemp(p, loc.t) let ral = addrLoc(p.config, loc) let ratmp = addrLoc(p.config, tmp) p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimCopyMem"), cCast(CPointer, ral), cCast(CConstPointer, ratmp), cSizeof(tyDesc)) else: let ral = addrLoc(p.config, loc) p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"), cCast(CPointer, ral), cSizeof(tyDesc)) # XXX: We can be extra clever here and call memset only # on the bytes following the m_type field? genObjectInit(p, cpsStmts, loc.t, loc, constructObj) proc constructLoc(p: BProc, loc: var TLoc, isTemp = false) = let typ = loc.t if optSeqDestructors in p.config.globalOptions and skipTypes(typ, abstractInst + {tyStatic}).kind in {tyString, tySequence}: let rl = rdLoc(loc) if skipTypes(typ, abstractInst + {tyStatic}).kind == tyString and p.config.usesSso(): # SmallString zero state: bytes=0 (slen=0 in low byte, all inline chars zeroed) p.s(cpsStmts).addFieldAssignment(rl, "bytes", cIntValue(0)) p.s(cpsStmts).addFieldAssignment(rl, "more", NimNil) else: p.s(cpsStmts).addFieldAssignment(rl, "len", cIntValue(0)) p.s(cpsStmts).addFieldAssignment(rl, "p", NimNil) elif not isComplexValueType(typ): if containsGarbageCollectedRef(loc.t): var nilLoc: TLoc = initLoc(locTemp, loc.lode, OnStack) nilLoc.snippet = NimNil genRefAssign(p, loc, nilLoc) else: let rl = rdLoc(loc) let rt = getTypeDesc(p.module, typ, descKindFromSymKind mapTypeChooser(loc)) p.s(cpsStmts).addAssignment(rl, cCast(rt, cIntValue(0))) else: if (not isTemp or containsGarbageCollectedRef(loc.t)) and not hasNoInit(loc.t): # don't use nimZeroMem for temporary values for performance if we can # avoid it: if not isOrHasImportedCppType(typ): let ral = addrLoc(p.config, loc) let rt = getTypeDesc(p.module, typ, descKindFromSymKind mapTypeChooser(loc)) p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimZeroMem"), cCast(CPointer, ral), cSizeof(rt)) genObjectInit(p, cpsStmts, loc.t, loc, constructObj) proc initLocalVar(p: BProc, v: PSym, immediateAsgn: bool) = if sfNoInit notin v.flags: # we know it is a local variable and thus on the stack! # If ``not immediateAsgn`` it is not initialized in a binding like # ``var v = X`` and thus we need to init it. # If ``v`` contains a GC-ref we may pass it to ``unsureAsgnRef`` somehow # which requires initialization. However this can really only happen if # ``var v = X()`` gets transformed into ``X(&v)``. # Nowadays the logic in ccgcalls deals with this case however. if not immediateAsgn: backendEnsureMutable v constructLoc(p, v.locImpl) proc getTemp(p: BProc, t: PType, needsInit=false): TLoc = inc(p.labels) result = TLoc(snippet: "T" & rope(p.labels) & "_", k: locTemp, lode: lodeTyp t, storage: OnStack, flags: {}) if p.module.compileToCpp and isOrHasImportedCppType(t): var didGenTemp = false linefmt(p, cpsLocals, "$1 $2$3;$n", [getTypeDesc(p.module, t, dkVar), result.snippet, genCppInitializer(p.module, p, t, didGenTemp)]) else: p.s(cpsLocals).addVar(kind = Local, name = result.snippet, typ = getTypeDesc(p.module, t, dkVar)) constructLoc(p, result, not needsInit) when false: # XXX Introduce a compiler switch in order to detect these easily. if getSize(p.config, t) > 1024 * 1024: if p.prc != nil: echo "ENORMOUS TEMPORARY! ", p.config $ p.prc.info else: echo "ENORMOUS TEMPORARY! ", p.config $ p.lastLineInfo writeStackTrace() proc getTempCpp(p: BProc, t: PType, value: Rope): TLoc = inc(p.labels) result = TLoc(snippet: "T" & rope(p.labels) & "_", k: locTemp, lode: lodeTyp t, storage: OnStack, flags: {}) p.s(cpsStmts).addVar(kind = Local, name = result.snippet, typ = "auto", initializer = value) proc getIntTemp(p: BProc): TLoc = inc(p.labels) result = TLoc(snippet: "T" & rope(p.labels) & "_", k: locTemp, storage: OnStack, lode: lodeTyp getSysType(p.module.g.graph, unknownLineInfo, tyInt), flags: {}) p.s(cpsLocals).addVar(kind = Local, name = result.snippet, typ = NimInt) proc localVarDecl(res: var Builder, p: BProc; n: PNode, initializer: Snippet = "", initializerKind: VarInitializerKind = Assignment) = let s = n.sym if s.loc.k == locNone: fillLocalName(p, s) backendEnsureMutable s fillLoc(s.locImpl, locLocalVar, n, OnStack) if s.kind == skLet: incl(s, lfNoDeepCopy) genCLineDir(res, p, n.info, p.config) res.addVar(p.module, s, name = s.loc.snippet, typ = getTypeDesc(p.module, s.typ, dkVar), initializer = initializer, initializerKind = initializerKind) proc assignLocalVar(p: BProc, n: PNode) = #assert(s.loc.k == locNone) # not yet assigned # this need not be fulfilled for inline procs; they are regenerated # for each module that uses them! var initializer: Snippet = "" var initializerKind: VarInitializerKind = Assignment if p.module.compileToCpp and isOrHasImportedCppType(n.typ): var didGenTemp = false initializer = genCppInitializer(p.module, p, n.typ, didGenTemp) initializerKind = CppConstructor localVarDecl(p.s(cpsLocals), p, n, initializer, initializerKind) if optLineDir in p.config.options: p.s(cpsLocals).add("\n") include ccgthreadvars proc varInDynamicLib(m: BModule, sym: PSym) proc treatGlobalDifferentlyForHCR(m: BModule, s: PSym): bool = return m.hcrOn and {sfThread, sfGlobal} * s.flags == {sfGlobal} and ({lfNoDecl, lfHeader} * s.loc.flags == {}) # and s.owner.kind == skModule # owner isn't always a module (global pragma on local var) # and s.loc.k == locGlobalVar # loc isn't always initialized when this proc is used proc genGlobalVarDecl(res: var Builder, p: BProc, n: PNode; td: Snippet; initializer: Snippet = "", initializerKind: VarInitializerKind = Assignment, allowConst = true) = let s = n.sym let vis = if p.hcrOn: StaticProc elif sfImportc in s.flags: Extern elif lfExportLib in s.loc.flags: ExportLibVar else: Private var typ = td if allowConst and s.kind == skLet and initializer.len != 0: typ = constType(typ) if p.hcrOn: typ = ptrType(typ) if p.config.cmd == cmdNifC and vis == Private and sfImportc notin s.flags: # A `{.global.}` var (e.g. chronos's per-call-site `var loc {.global.} = # SrcLoc(...)`, or a gensym'd `var dummy`/`var topic` with no initializer) # declared inside a routine is emitted by every module that emit-everywhere's # its enclosing routine; its content-addressed name then collides at link. # Declare it `extern` + wrap the definition as a droppable `'d'` unit so the # merge stage keeps exactly one (like consts / TNimType / the NimDT # discriminator tables / the threadvar path). This covers no-initializer # globals too — they collide just the same. A module-level global has a # single claimant → its sole emitter is the owner merge keeps. let cname = stripCnifMarks(s.loc.snippet) res.addDeclWithVisibility(Extern): res.addVar(kind = Local, name = s.loc.snippet, typ = typ) res.add(cnifDefDirective(cname, "d", icNifName(p.module, s))) res.addVar(p.module, s, name = s.loc.snippet, typ = typ, visibility = vis, initializer = initializer, initializerKind = initializerKind) res.add(cnifEndDefs()) else: res.addVar(p.module, s, name = s.loc.snippet, typ = typ, visibility = vis, initializer = initializer, initializerKind = initializerKind) proc assignGlobalVar(p: BProc, n: PNode; value: Rope) = let s = n.sym if s.loc.k == locNone: fillBackendName(p.module, s) backendEnsureMutable s fillLoc(s.locImpl, locGlobalVar, n, OnHeap) if treatGlobalDifferentlyForHCR(p.module, s): incl(s, lfIndirect) if lfDynamicLib in s.loc.flags: var q = findPendingModule(p.module, s) if q != nil and not containsOrIncl(q.declaredThings, s.id): varInDynamicLib(q, s) else: backendEnsureMutable s s.locImpl.snippet = mangleDynLibProc(s) if value != "": internalError(p.config, n.info, ".dynlib variables cannot have a value") return useHeader(p.module, s) if lfNoDecl in s.loc.flags: return if not containsOrIncl(p.module.declaredThings, s.id): if p.config.cmd == cmdNifC and sfImportc notin s.flags: p.module.icDataDefs.add (stripCnifMarks(s.loc.snippet), icNifName(p.module, s)) if sfThread in s.flags: declareThreadVar(p.module, s, sfImportc in s.flags) if value != "": internalError(p.config, n.info, ".threadvar variables cannot have a value") else: let td = getTypeDesc(p.module, s.loc.t, dkVar) var initializer: Snippet = "" if s.constraint.isNil: if value != "": if p.module.compileToCpp and value.startsWith "{{}": # TODO: taking this branch, re"\{\{\}(,\s\{\})*\}" might be emitted, resulting in # either warnings (GCC 12.2+) or errors (Clang 15, MSVC 19.3+) of C++11+ compilers **when # explicit constructors are around** due to overload resolution rules in place [^0][^1][^2] # *Workaround* here: have C++'s static initialization mechanism do the default init work, # for us lacking a deeper knowledge of an imported object's constructors' ex-/implicitness # (so far) *and yet* trying to achieve default initialization. # Still, generating {}s in genConstObjConstr() just to omit them here is faaaar from ideal; # need to figure out a better way, possibly by keeping around more data about the # imported objects' contructors? # # [^0]: https://en.cppreference.com/w/cpp/language/aggregate_initialization # [^1]: https://cplusplus.github.io/CWG/issues/1518.html # [^2]: https://eel.is/c++draft/over.match.ctor discard else: initializer = value else: discard else: initializer = value genGlobalVarDecl(p.module.s[cfsVars], p, n, td, initializer = initializer) if p.withinLoop > 0 and value == "" and s.loc.t.skipTypes(abstractInst).kind notin {tyVar, tyLent}: # fixes tests/run/tzeroarray: # Don't reset borrowed references (var/lent): the pointer itself is still # uninitialized here, so resetLoc would dereference garbage. Such variables # (e.g. the loop var of `mitems`) are always assigned before use anyway. backendEnsureMutable s resetLoc(p, s.locImpl) proc callGlobalVarCppCtor(p: BProc; v: PSym; vn, value: PNode; didGenTemp: var bool) = let s = vn.sym fillBackendName(p.module, s) backendEnsureMutable s fillLoc(s.locImpl, locGlobalVar, vn, OnHeap) let td = getTypeDesc(p.module, vn.sym.typ, dkVar) var val = genCppParamsForCtor(p, value, didGenTemp) if didGenTemp: return # generated in the caller if val.len != 0: val = "(" & val & ")" genGlobalVarDecl(p.module.s[cfsVars], p, vn, td, initializer = val, initializerKind = CppConstructor, allowConst = false) proc assignParam(p: BProc, s: PSym, retType: PType) = assert(s.loc.snippet != "") scopeMangledParam(p, s) proc fillProcLoc(m: BModule; n: PNode) = let sym = n.sym if sym.loc.k == locNone: fillBackendName(m, sym) backendEnsureMutable sym fillLoc(sym.locImpl, locProc, n, OnStack) proc getLabel(p: BProc): TLabel = inc(p.labels) result = "LA" & rope(p.labels) & "_" proc fixLabel(p: BProc, labl: TLabel) = p.s(cpsStmts).addLabel(labl) proc genVarPrototype(m: BModule, n: PNode) proc requestConstImpl(p: BProc, sym: PSym) proc genStmts(p: BProc, t: PNode) proc expr(p: BProc, n: PNode, d: var TLoc) proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) proc genLiteral(p: BProc, n: PNode; result: var Builder) proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder; argBuilder: var CallBuilder) proc raiseExit(p: BProc) proc raiseExitCleanup(p: BProc, destroy: string) proc initLocExpr(p: BProc, e: PNode, flags: TLocFlags = {}): TLoc = result = initLoc(locNone, e, OnUnknown, flags) expr(p, e, result) proc initLocExprSingleUse(p: BProc, e: PNode): TLoc = result = initLoc(locNone, e, OnUnknown) if e.kind in nkCallKinds and (e[0].kind != nkSym or e[0].sym.magic == mNone): # We cannot check for tfNoSideEffect here because of mutable parameters. discard "bug #8202; enforce evaluation order for nested calls for C++ too" # We may need to consider that 'f(g())' cannot be rewritten to 'tmp = g(); f(tmp)' # if 'tmp' lacks a move/assignment operator. if e[0].kind == nkSym and sfCompileToCpp in e[0].sym.flags: result.flags.incl lfSingleUse else: result.flags.incl lfSingleUse expr(p, e, result) include ccgcalls, "ccgstmts.nim" proc initFrame(p: BProc, procname, filename: Rope): Rope = # XXX cbuilder const frameDefines = """ $1define nimfr_(proc, file) \ TFrame FR_; \ FR_.procname = proc; FR_.filename = file; FR_.line = 0; FR_.len = 0; #nimFrame(&FR_); $1define nimln_(n) \ FR_.line = n; $1define nimlf_(n, file) \ FR_.line = n; FR_.filename = file; """ if p.module.s[cfsFrameDefines].buf.len == 0: appcg(p.module, p.module.s[cfsFrameDefines], frameDefines, ["#"]) cgsym(p.module, "nimFrame") result = ropecg(p.module, "\tnimfr_($1, $2);$n", [procname, filename]) proc initFrameNoDebug(p: BProc; frame, procname, filename: Snippet; line: int): Snippet = cgsym(p.module, "nimFrame") p.blocks[0].sections[cpsLocals].addVar(name = frame, typ = "TFrame") var res = newBuilder("") res.add('\t') res.addFieldAssignment(frame, "procname", procname) res.add('\t') res.addFieldAssignment(frame, "filename", filename) res.add('\t') res.addFieldAssignment(frame, "line", cIntValue(line)) res.add('\t') res.addFieldAssignment(frame, "len", cIntValue(-1)) res.add('\t') res.addCallStmt("nimFrame", cAddr(frame)) result = extract(res) proc deinitFrameNoDebug(p: BProc; frame: Snippet): Snippet = var res = newBuilder("") res.add('\t') res.addCallStmt(cgsymValue(p.module, "popFrameOfAddr"), cAddr(frame)) result = extract(res) proc deinitFrame(p: BProc): Snippet = var res = newBuilder("") res.add('\t') res.addCallStmt(cgsymValue(p.module, "popFrame")) result = extract(res) include ccgexprs # ----------------------------- dynamic library handling ----------------- # We don't finalize dynamic libs as the OS does this for us. proc isGetProcAddr(lib: PLib): bool = let n = lib.path result = n.kind in nkCallKinds and n.typ != nil and n.typ.kind in {tyPointer, tyProc} proc loadDynamicLib(m: BModule, lib: PLib) = assert(lib != nil) if not lib.generated: lib.generated = true var tmp = getTempName(m) assert(lib.name == "") lib.name = tmp # BUGFIX: cgsym has awful side-effects let loadFn = cgsymValue(m, "nimLoadLibrary") let loadErrorFn = cgsymValue(m, "nimLoadLibraryError") m.s[cfsVars].addVar(Global, name = tmp, typ = CPointer) if lib.path.kind in {nkStrLit..nkTripleStrLit}: var s: TStringSeq = @[] libCandidates(lib.path.strVal, s) rawMessage(m.config, hintDependency, lib.path.strVal) let last = high(s) for i in 0..last: inc(m.labels) template doLoad(j: int) = let n = newStrNode(nkStrLit, s[j]) n.info = lib.path.info m.s[cfsDynLibInit].addAssignmentWithValue(tmp): var call: CallBuilder m.s[cfsDynLibInit].addCall(call, loadFn): m.s[cfsDynLibInit].addArgument(call): genStringLiteral(m, n, m.s[cfsDynLibInit]) if i == 0: doLoad(i) m.s[cfsDynLibInit].addSingleIfStmt(cOp(Not, tmp)): if i == last: m.s[cfsDynLibInit].addStmt(): var call: CallBuilder m.s[cfsDynLibInit].addCall(call, loadErrorFn): m.s[cfsDynLibInit].addArgument(call): genStringLiteral(m, lib.path, m.s[cfsDynLibInit]) else: doLoad(i + 1) else: var p = newProc(nil, m) p.options.excl optStackTrace p.flags.incl nimErrorFlagDisabled var dest: TLoc = initLoc(locTemp, lib.path, OnStack) dest.snippet = getTempName(m) m.s[cfsDynLibInit].addVar(name = rdLoc(dest), typ = getTypeDesc(m, lib.path.typ, dkVar)) expr(p, lib.path, dest) m.s[cfsVars].add(extract(p.s(cpsLocals))) m.s[cfsDynLibInit].add(extract(p.s(cpsInit))) m.s[cfsDynLibInit].add(extract(p.s(cpsStmts))) let rd = rdLoc(dest) m.s[cfsDynLibInit].addAssignment(tmp, cCall(loadFn, rd)) m.s[cfsDynLibInit].addSingleIfStmt(cOp(Not, tmp)): m.s[cfsDynLibInit].addCallStmt(loadErrorFn, rd) if lib.name == "": internalError(m.config, "loadDynamicLib") proc mangleDynLibProc(sym: PSym): Rope = # we have to build this as a single rope in order not to trip the # optimization in genInfixCall, see test tests/cpp/t8241.nim if sfCompilerProc in sym.flags: # NOTE: sym.loc.snippet is the external name! result = rope(sym.name.s) else: result = rope(strutils.`%`("Dl_$1_", $sym.id)) proc symInDynamicLib(m: BModule, sym: PSym) = var lib = sym.annex let isCall = isGetProcAddr(lib) var extname = sym.loc.snippet if not isCall: loadDynamicLib(m, lib) var tmp = mangleDynLibProc(sym) backendEnsureMutable sym sym.locImpl.snippet = tmp # from now on we only need the internal name sym.typ.sym = nil # generate a new name inc(m.labels, 2) if isCall: let n = lib.path var a: TLoc = initLocExpr(m.initProc, n[0]) let callee = rdLoc(a) var params: seq[Snippet] = @[] for i in 1..