# # # The Nim Compiler # (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # included from cgen.nim # ------------------------- Name Mangling -------------------------------- import sighashes, modulegraphs, strscans import ../dist/checksums/src/checksums/md5 type TypeDescKind = enum dkParam #skParam dkRefParam #param passed by ref when {.byref.} is used. Cpp only. C goes straight to dkParam and is handled as a regular pointer dkRefGenericParam #param passed by ref when {.byref.} is used that is also a generic. Cpp only. C goes straight to dkParam and is handled as a regular pointer dkVar #skVar dkField #skField dkResult #skResult dkConst #skConst dkOther #skType, skTemp, skLet and skForVar so far proc descKindFromSymKind(kind: TSymKind): TypeDescKind = case kind of skParam: dkParam of skVar: dkVar of skField: dkField of skResult: dkResult of skConst: dkConst else: dkOther proc isKeyword(w: PIdent): bool = # Nim and C++ share some keywords # it's more efficient to test the whole Nim keywords range case w.id of ccgKeywordsLow..ccgKeywordsHigh, nimKeywordsLow..nimKeywordsHigh, ord(wInline): return true else: return false proc mangleField(m: BModule; name: PIdent): string = result = mangle(name.s) # fields are tricky to get right and thanks to generic types producing # duplicates we can end up mangling the same field multiple times. However # if we do so, the 'cppDefines' table might be modified in the meantime # meaning we produce inconsistent field names (see bug #5404). # Hence we do not check for ``m.g.config.cppDefines.contains(result)`` here # anymore: if isKeyword(name): result.add "_0" proc mangleProc(m: BModule; s: PSym; makeUnique: bool): string = result = "_Z" # Common prefix in Itanium ABI result.add encodeSym(m, s, makeUnique) if s.typ.len > 1: #we dont care about the return param for i in 1.. proxy => reloadable # we call performCodeReload() in proxy to reload only changes in reloadable # but there is a new import which introduces an importc symbol `socket` # and a function called in main or proxy uses `socket` as a parameter name. # That would lead to either needing to reload `proxy` or to overwrite the # executable file for the main module, which is running (or both!) -> error. s.loc.r = res.rope writeMangledName(m.ndi, s, m.config) proc fillLocalName(p: BProc; s: PSym) = assert s.kind in skLocalVars+{skTemp} #assert sfGlobal notin s.flags if s.loc.r == "": var key = s.name.s.mangle let counter = p.sigConflicts.getOrDefault(key) var result = key.rope if s.kind == skTemp: # speed up conflict search for temps (these are quite common): if counter != 0: result.add "_" & rope(counter+1) elif counter != 0 or isKeyword(s.name) or p.module.g.config.cppDefines.contains(key): result.add "_" & rope(counter+1) p.sigConflicts.inc(key) s.loc.r = result if s.kind != skTemp: writeMangledName(p.module.ndi, s, p.config) proc scopeMangledParam(p: BProc; param: PSym) = ## parameter generation only takes BModule, not a BProc, so we have to ## remember these parameter names are already in scope to be able to ## generate unique identifiers reliably (consider that ``var a = a`` is ## even an idiom in Nim). var key = param.name.s.mangle p.sigConflicts.inc(key) const irrelevantForBackend = {tyGenericBody, tyGenericInst, tyGenericInvocation, tyDistinct, tyRange, tyStatic, tyAlias, tySink, tyInferred, tyOwned} proc typeName(typ: PType; result: var Rope) = let typ = typ.skipTypes(irrelevantForBackend) result.add $typ.kind if typ.sym != nil and typ.kind in {tyObject, tyEnum}: result.add "_" result.add typ.sym.name.s.mangle proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope = var t = typ while true: if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}: return t.sym.loc.r if t.kind in irrelevantForBackend: t = t.lastSon else: break let typ = if typ.kind in {tyAlias, tySink, tyOwned}: typ.lastSon else: typ if typ.loc.r == "": typ.typeName(typ.loc.r) typ.loc.r.add $sig else: when defined(debugSigHashes): # check consistency: var tn = newRopeAppender() typ.typeName(tn) assert($typ.loc.r == $(tn & $sig)) result = typ.loc.r if result == "": internalError(m.config, "getTypeName: " & $typ.kind) proc mapSetType(conf: ConfigRef; typ: PType): TCTypeKind = case int(getSize(conf, typ)) of 1: result = ctInt8 of 2: result = ctInt16 of 4: result = ctInt32 of 8: result = ctInt64 else: result = ctArray proc mapType(conf: ConfigRef; typ: PType; isParam: bool): TCTypeKind = ## Maps a Nim type to a C type case typ.kind of tyNone, tyTyped: result = ctVoid of tyBool: result = ctBool of tyChar: result = ctChar of tyNil: result = ctPtr of tySet: result = mapSetType(conf, typ) of tyOpenArray, tyVarargs: if isParam: result = ctArray else: result = ctStruct of tyArray, tyUncheckedArray: result = ctArray of tyObject, tyTuple: result = ctStruct of tyUserTypeClasses: doAssert typ.isResolvedUserTypeClass return mapType(conf, typ.lastSon, isParam) of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tySink, tyInferred, tyOwned: result = mapType(conf, lastSon(typ), isParam) of tyEnum: if firstOrd(conf, typ) < 0: result = ctInt32 else: case int(getSize(conf, typ)) of 1: result = ctUInt8 of 2: result = ctUInt16 of 4: result = ctInt32 of 8: result = ctInt64 else: result = ctInt32 of tyRange: result = mapType(conf, typ[0], isParam) of tyPtr, tyVar, tyLent, tyRef: var base = skipTypes(typ.lastSon, typedescInst) case base.kind of tyOpenArray, tyArray, tyVarargs, tyUncheckedArray: result = ctPtrToArray of tySet: if mapSetType(conf, base) == ctArray: result = ctPtrToArray else: result = ctPtr else: result = ctPtr of tyPointer: result = ctPtr of tySequence: result = ctNimSeq of tyProc: result = if typ.callConv != ccClosure: ctProc else: ctStruct of tyString: result = ctNimStr of tyCstring: result = ctCString of tyInt..tyUInt64: result = TCTypeKind(ord(typ.kind) - ord(tyInt) + ord(ctInt)) of tyStatic: if typ.n != nil: result = mapType(conf, lastSon typ, isParam) else: doAssert(false, "mapType: " & $typ.kind) else: doAssert(false, "mapType: " & $typ.kind) proc mapReturnType(conf: ConfigRef; typ: PType): TCTypeKind = #if skipTypes(typ, typedescInst).kind == tyArray: result = ctPtr #else: result = mapType(conf, typ, false) proc isImportedType(t: PType): bool = result = t.sym != nil and sfImportc in t.sym.flags proc isImportedCppType(t: PType): bool = let x = t.skipTypes(irrelevantForBackend) result = (t.sym != nil and sfInfixCall in t.sym.flags) or (x.sym != nil and sfInfixCall in x.sym.flags) proc isOrHasImportedCppType(typ: PType): bool = searchTypeFor(typ.skipTypes({tyRef}), isImportedCppType) proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDescKind): Rope proc isObjLackingTypeField(typ: PType): bool {.inline.} = result = (typ.kind == tyObject) and ((tfFinal in typ.flags) and (typ[0] == nil) or isPureObject(typ)) proc isInvalidReturnType(conf: ConfigRef; typ: PType, isProc = true): bool = # Arrays and sets cannot be returned by a C procedure, because C is # such a poor programming language. # We exclude records with refs too. This enhances efficiency and # is necessary for proper code generation of assignments. var rettype = typ var isAllowedCall = true if isProc: rettype = rettype[0] isAllowedCall = typ.callConv in {ccClosure, ccInline, ccNimCall} if rettype == nil or (isAllowedCall and getSize(conf, rettype) > conf.target.floatSize*3): result = true else: case mapType(conf, rettype, false) of ctArray: result = not (skipTypes(rettype, typedescInst).kind in {tyVar, tyLent, tyRef, tyPtr}) of ctStruct: let t = skipTypes(rettype, typedescInst) if rettype.isImportedCppType or t.isImportedCppType or (typ.callConv == ccCDecl and conf.selectedGC in {gcArc, gcAtomicArc, gcOrc}): # prevents nrvo for cdecl procs; # bug #23401 result = false else: result = containsGarbageCollectedRef(t) or (t.kind == tyObject and not isObjLackingTypeField(t)) or (getSize(conf, rettype) == szUnknownSize and (t.sym == nil or sfImportc notin t.sym.flags)) else: result = false const CallingConvToStr: array[TCallingConvention, string] = ["N_NIMCALL", "N_STDCALL", "N_CDECL", "N_SAFECALL", "N_SYSCALL", # this is probably not correct for all platforms, # but one can #define it to what one wants "N_INLINE", "N_NOINLINE", "N_FASTCALL", "N_THISCALL", "N_CLOSURE", "N_NOCONV"] proc cacheGetType(tab: TypeCache; sig: SigHash): Rope = # returns nil if we need to declare this type # since types are now unique via the ``getUniqueType`` mechanism, this slow # linear search is not necessary anymore: 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): 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 fillResult(conf: ConfigRef; param: PNode, proctype: PType) = fillLoc(param.sym.loc, locParam, param, "Result", OnStack) let t = param.sym.typ if mapReturnType(conf, t) != ctArray and isInvalidReturnType(conf, proctype): incl(param.sym.loc.flags, lfIndirect) param.sym.loc.storage = OnUnknown proc typeNameOrLiteral(m: BModule; t: PType, literal: string): Rope = if t.sym != nil and sfImportc in t.sym.flags and t.sym.magic == mNone: useHeader(m, t.sym) result = t.sym.loc.r else: result = rope(literal) proc getSimpleTypeDesc(m: BModule; typ: PType): Rope = const NumericalTypeToStr: array[tyInt..tyUInt64, string] = [ "NI", "NI8", "NI16", "NI32", "NI64", "NF", "NF32", "NF64", "NF128", "NU", "NU8", "NU16", "NU32", "NU64"] case typ.kind of tyPointer: result = typeNameOrLiteral(m, typ, "void*") of tyString: case detectStrVersion(m) of 2: cgsym(m, "NimStrPayload") cgsym(m, "NimStringV2") result = typeNameOrLiteral(m, typ, "NimStringV2") else: cgsym(m, "NimStringDesc") result = typeNameOrLiteral(m, typ, "NimStringDesc*") of tyCstring: result = typeNameOrLiteral(m, typ, "NCSTRING") of tyBool: result = typeNameOrLiteral(m, typ, "NIM_BOOL") of tyChar: result = typeNameOrLiteral(m, typ, "NIM_CHAR") of tyNil: result = typeNameOrLiteral(m, typ, "void*") of tyInt..tyUInt64: result = typeNameOrLiteral(m, typ, NumericalTypeToStr[typ.kind]) of tyDistinct, tyRange, tyOrdinal: result = getSimpleTypeDesc(m, typ[0]) of tyStatic: if typ.n != nil: result = getSimpleTypeDesc(m, lastSon typ) else: internalError(m.config, "tyStatic for getSimpleTypeDesc") of tyGenericInst, tyAlias, tySink, tyOwned: result = getSimpleTypeDesc(m, lastSon typ) else: result = "" if result != "" and typ.isImportedType(): let sig = hashType(typ, m.config) if cacheGetType(m.typeCache, sig) == "": m.typeCache[sig] = result proc pushType(m: BModule; typ: PType) = for i in 0..high(m.typeStack): # pointer equality is good enough here: if m.typeStack[i] == typ: return m.typeStack.add(typ) proc getTypePre(m: BModule; typ: PType; sig: SigHash): Rope = if typ == nil: result = rope("void") else: result = getSimpleTypeDesc(m, typ) if result == "": result = cacheGetType(m.typeCache, sig) proc structOrUnion(t: PType): Rope = let cachedUnion = rope("union") let cachedStruct = rope("struct") let t = t.skipTypes({tyAlias, tySink}) if tfUnion in t.flags: cachedUnion else: cachedStruct proc addForwardStructFormat(m: BModule; structOrUnion: Rope, typename: Rope) = if m.compileToCpp: m.s[cfsForwardTypes].addf "$1 $2;$n", [structOrUnion, typename] else: m.s[cfsForwardTypes].addf "typedef $1 $2 $2;$n", [structOrUnion, typename] proc seqStar(m: BModule): string = if optSeqDestructors in m.config.globalOptions: result = "" else: result = "*" proc getTypeForward(m: BModule; typ: PType; sig: SigHash): Rope = result = cacheGetType(m.forwTypeCache, sig) if result != "": return result = getTypePre(m, typ, sig) if result != "": return let concrete = typ.skipTypes(abstractInst) case concrete.kind of tySequence, tyTuple, tyObject: result = getTypeName(m, typ, sig) m.forwTypeCache[sig] = result if not isImportedType(concrete): addForwardStructFormat(m, structOrUnion(typ), result) else: pushType(m, concrete) doAssert m.forwTypeCache[sig] == result else: internalError(m.config, "getTypeForward(" & $typ.kind & ')') proc getTypeDescWeak(m: BModule; t: PType; check: var IntSet; kind: TypeDescKind): Rope = ## like getTypeDescAux but creates only a *weak* dependency. In other words ## we know we only need a pointer to it so we only generate a struct forward ## declaration: let etB = t.skipTypes(abstractInst) case etB.kind of tyObject, tyTuple: if isImportedCppType(etB) and t.kind == tyGenericInst: result = getTypeDescAux(m, t, check, kind) else: result = getTypeForward(m, t, hashType(t, m.config)) pushType(m, t) of tySequence: let sig = hashType(t, m.config) if optSeqDestructors in m.config.globalOptions: if skipTypes(etB[0], typedescInst).kind == tyEmpty: internalError(m.config, "cannot map the empty seq type to a C type") result = cacheGetType(m.forwTypeCache, sig) if result == "": result = getTypeName(m, t, sig) if not isImportedType(t): m.forwTypeCache[sig] = result addForwardStructFormat(m, rope"struct", result) let payload = result & "_Content" addForwardStructFormat(m, rope"struct", payload) if cacheGetType(m.typeCache, sig) == "": m.typeCache[sig] = result #echo "adding ", sig, " ", typeToString(t), " ", m.module.name.s appcg(m, m.s[cfsTypes], "struct $1 {\n" & " NI len; $1_Content* p;\n" & "};\n", [result]) pushType(m, t) else: result = getTypeForward(m, t, sig) & seqStar(m) pushType(m, t) else: result = getTypeDescAux(m, t, check, kind) proc getSeqPayloadType(m: BModule; t: PType): Rope = var check = initIntSet() result = getTypeDescWeak(m, t, check, dkParam) & "_Content" #result = getTypeForward(m, t, hashType(t)) & "_Content" proc seqV2ContentType(m: BModule; t: PType; check: var IntSet) = let sig = hashType(t, m.config) let result = cacheGetType(m.typeCache, sig) if result == "": discard getTypeDescAux(m, t, check, dkVar) else: appcg(m, m.s[cfsTypes], """ struct $2_Content { NI cap; $1 data[SEQ_DECL_SIZE]; }; """, [getTypeDescAux(m, t.skipTypes(abstractInst)[0], check, dkVar), result]) proc paramStorageLoc(param: PSym): TStorageLoc = if param.typ.skipTypes({tyVar, tyLent, tyTypeDesc}).kind notin { tyArray, tyOpenArray, tyVarargs}: result = OnStack else: result = OnUnknown macro unrollChars(x: static openArray[char], name, body: untyped) = result = newStmtList() for a in x: result.add(newBlockStmt(newStmtList( newConstStmt(name, newLit(a)), copy body ))) proc multiFormat*(frmt: var string, chars : static openArray[char], args: openArray[seq[string]]) = var res : string unrollChars(chars, c): res = "" let arg = args[find(chars, c)] var i = 0 var num = 0 while i < frmt.len: if frmt[i] == c: inc(i) case frmt[i] of c: res.add(c) inc(i) of '0'..'9': var j = 0 while true: j = j * 10 + ord(frmt[i]) - ord('0') inc(i) if i >= frmt.len or frmt[i] notin {'0'..'9'}: break num = j if j > high(arg) + 1: doAssert false, "invalid format string: " & frmt else: res.add(arg[j-1]) else: doAssert false, "invalid format string: " & frmt var start = i while i < frmt.len: if frmt[i] != c: inc(i) else: break if i - 1 >= start: res.add(substr(frmt, start, i - 1)) frmt = res template cgDeclFrmt*(s: PSym): string = s.constraint.strVal proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, params: var string, check: var IntSet, declareEnvironment=true; weakDep=false;) = let t = prc.typ let isCtor = sfConstructor in prc.flags if isCtor: rettype = "" elif t[0] == nil or isInvalidReturnType(m.config, t): rettype = "void" else: if rettype == "": rettype = getTypeDescAux(m, t[0], check, dkResult) else: rettype = runtimeFormat(rettype.replace("'0", "$1"), [getTypeDescAux(m, t[0], check, dkResult)]) var types, names, args: seq[string] if not isCtor: var this = t.n[1].sym fillParamName(m, this) fillLoc(this.loc, locParam, t.n[1], this.paramStorageLoc) if this.typ.kind == tyPtr: this.loc.r = "this" else: this.loc.r = "(*this)" names.add this.loc.r types.add getTypeDescWeak(m, this.typ, check, dkParam) let firstParam = if isCtor: 1 else: 2 for i in firstParam..