# # # 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 from lowerings import createObj proc genProcHeader(m: BModule, prc: PSym, asPtr: bool = false): Rope 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 mangleName(m: BModule; s: PSym): Rope = result = s.loc.r if result == nil: result = s.name.s.mangle.rope result.add "_" result.add m.g.graph.ifaces[s.itemId.module].uniqueName result.add "_" result.add rope s.itemId.item if m.hcrOn: result.add "_" result.add(idOrSig(s, m.module.name.s.mangle, m.sigConflicts)) s.loc.r = result writeMangledName(m.ndi, s, m.config) proc mangleParamName(m: BModule; s: PSym): Rope = ## we cannot use 'sigConflicts' here since we have a BModule, not a BProc. ## Fortunately C's scoping rules are sane enough so that that doesn't ## cause any trouble. result = s.loc.r if result == nil: var res = s.name.s.mangle # Take into account if HCR is on because of the following scenario: # if a module gets imported and it has some more importc symbols in it, # some param names might receive the "_0" suffix to distinguish from what # is newly available. That might lead to changes in the C code in nimcache # that contain only a parameter name change, but that is enough to mandate # recompilation of that source file and thus a new shared object will be # relinked. That may lead to a module getting reloaded which wasn't intended # and that may be fatal when parts of the current active callstack when # performCodeReload() was called are from the module being reloaded # unintentionally - example (3 modules which import one another): # main => 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. if m.hcrOn or isKeyword(s.name) or m.g.config.cppDefines.contains(res): res.add "_0" result = res.rope s.loc.r = result writeMangledName(m.ndi, s, m.config) proc mangleLocalName(p: BProc; s: PSym): Rope = assert s.kind in skLocalVars+{skTemp} #assert sfGlobal notin s.flags result = s.loc.r if result == nil: var key = s.name.s.mangle shallow(key) let counter = p.sigConflicts.getOrDefault(key) 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 shallow(key) p.sigConflicts.inc(key) const irrelevantForBackend = {tyGenericBody, tyGenericInst, tyGenericInvocation, tyDistinct, tyRange, tyStatic, tyAlias, tySink, tyInferred, tyOwned} proc typeName(typ: PType): Rope = let typ = typ.skipTypes(irrelevantForBackend) result = if typ.sym != nil and typ.kind in {tyObject, tyEnum}: rope($typ.kind & '_' & typ.sym.name.s.mangle) else: rope($typ.kind) 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 == nil: typ.loc.r = typ.typeName & $sig else: when defined(debugSigHashes): # check consistency: assert($typ.loc.r == $(typ.typeName & $sig)) result = typ.loc.r if result == nil: 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; kind: TSymKind): 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 kind == skParam: 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, kind) of tyGenericBody, tyGenericInst, tyGenericParam, tyDistinct, tyOrdinal, tyTypeDesc, tyAlias, tySink, tyInferred, tyOwned: result = mapType(conf, lastSon(typ), kind) 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], kind) 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, kind) else: doAssert(false, "mapType") else: doAssert(false, "mapType") proc mapReturnType(conf: ConfigRef; typ: PType): TCTypeKind = #if skipTypes(typ, typedescInst).kind == tyArray: result = ctPtr #else: result = mapType(conf, typ, skResult) 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 getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet; kind: TSymKind): 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; rettype: PType): 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. if rettype == nil: result = true else: case mapType(conf, rettype, skResult) 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: return false result = containsGarbageCollectedRef(t) or (t.kind == tyObject and not isObjLackingTypeField(t)) 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 ccgIntroducedPtr(conf: ConfigRef; s: PSym, retType: PType): bool = var pt = skipTypes(s.typ, typedescInst) assert skResult != s.kind if tfByRef in pt.flags: return true elif tfByCopy in pt.flags: return false case pt.kind of tyObject: if s.typ.sym != nil and sfForward in s.typ.sym.flags: # forwarded objects are *always* passed by pointers for consistency! result = true elif (optByRef in s.options) or (getSize(conf, pt) > conf.target.floatSize * 3): result = true # requested anyway elif (tfFinal in pt.flags) and (pt[0] == nil): result = false # no need, because no subtyping possible else: result = true # ordinary objects are always passed by reference, # otherwise casting doesn't work of tyTuple: result = (getSize(conf, pt) > conf.target.floatSize*3) or (optByRef in s.options) else: result = false # first parameter and return type is 'lent T'? --> use pass by pointer if s.position == 0 and retType != nil and retType.kind == tyLent: result = not (pt.kind in {tyVar, tyArray, tyOpenArray, tyVarargs, tyRef, tyPtr, tyPointer} or pt.kind == tySet and mapSetType(conf, pt) == ctArray) proc fillResult(conf: ConfigRef; param: PNode) = fillLoc(param.sym.loc, locParam, param, ~"Result", OnStack) let t = param.sym.typ if mapReturnType(conf, t) != ctArray and isInvalidReturnType(conf, t): 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: discard cgsym(m, "NimStrPayload") discard cgsym(m, "NimStringV2") result = typeNameOrLiteral(m, typ, "NimStringV2") else: discard 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 = nil if result != nil and typ.isImportedType(): let sig = hashType typ if cacheGetType(m.typeCache, sig) == nil: 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 == nil: 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 != nil: return result = getTypePre(m, typ, sig) if result != nil: 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: TSymKind): 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)) pushType(m, t) of tySequence: let sig = hashType(t) 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 == nil: 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) == nil: 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]) 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, skParam) & "_Content" #result = getTypeForward(m, t, hashType(t)) & "_Content" proc seqV2ContentType(m: BModule; t: PType; check: var IntSet) = let sig = hashType(t) let result = cacheGetType(m.typeCache, sig) if result == nil: discard getTypeDescAux(m, t, check, skVar) else: # little hack for now to prevent multiple definitions of the same # Seq_Content: appcg(m, m.s[cfsTypes], """$N $3ifndef $2_Content_PP $3define $2_Content_PP struct $2_Content { NI cap; $1 data[SEQ_DECL_SIZE];}; $3endif$N """, [getTypeDescAux(m, t.skipTypes(abstractInst)[0], check, skVar), result, rope"#"]) proc paramStorageLoc(param: PSym): TStorageLoc = if param.typ.skipTypes({tyVar, tyLent, tyTypeDesc}).kind notin { tyArray, tyOpenArray, tyVarargs}: result = OnStack else: result = OnUnknown proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, check: var IntSet, declareEnvironment=true; weakDep=false) = params = nil if t[0] == nil or isInvalidReturnType(m.config, t[0]): rettype = ~"void" else: rettype = getTypeDescAux(m, t[0], check, skResult) for i in 1..