From e43a51fcf3dff166838cdc3f2fc9690c5fa24846 Mon Sep 17 00:00:00 2001 From: Juan M Gómez Date: Tue, 30 May 2023 20:47:26 +0100 Subject: Implements: [C++] constructor pragma improvement (fix #21921) (#21916) * implements: [C++] constructor pragma improvement (fix #21921) t * fix test so it doesnt use echo in globals * Update compiler/ccgtypes.nim * Update lib/std/private/dragonbox.nim --------- Co-authored-by: Andreas Rumpf --- compiler/ccgstmts.nim | 18 ++++--- compiler/ccgtypes.nim | 120 +++++++++++++++++++++++++++--------------- compiler/cgen.nim | 16 +++--- compiler/modulegraphs.nim | 2 +- compiler/pragmas.nim | 6 ++- compiler/semstmts.nim | 59 ++++++++++++++------- lib/std/private/dragonbox.nim | 4 +- lib/std/private/schubfach.nim | 4 +- tests/cpp/tconstructor.nim | 35 +++++++++++- 9 files changed, 180 insertions(+), 84 deletions(-) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 1ed546256..36ef4f3ea 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -35,7 +35,9 @@ proc isAssignedImmediately(conf: ConfigRef; n: PNode): bool {.inline.} = if n.kind == nkEmpty: result = false elif n.kind in nkCallKinds and n[0] != nil and n[0].typ != nil and n[0].typ.skipTypes(abstractInst).kind == tyProc: - if isInvalidReturnType(conf, n[0].typ, true): + if n[0].kind == nkSym and sfConstructor in n[0].sym.flags: + result = true + elif isInvalidReturnType(conf, n[0].typ, true): # var v = f() # is transformed into: var v; f(addr v) # where 'f' **does not** initialize the result! @@ -288,7 +290,7 @@ proc potentialValueInit(p: BProc; v: PSym; value: PNode; result: var Rope) = #echo "New code produced for ", v.name.s, " ", p.config $ value.info genBracedInit(p, value, isConst = false, v.typ, result) -proc genCppVarForConstructor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope) = +proc genCppVarForCtor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope) = var params = newRopeAppender() var argsCounter = 0 let typ = skipTypes(value[0].typ, abstractInst) @@ -307,7 +309,7 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) = genGotoVar(p, value) return let imm = isAssignedImmediately(p.config, value) - let isCppConstructorCall = p.module.compileToCpp and imm and + let isCppCtorCall = p.module.compileToCpp and imm and value.kind in nkCallKinds and value[0].kind == nkSym and v.typ.kind != tyPtr and sfConstructor in value[0].sym.flags var targetProc = p @@ -321,8 +323,8 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) = if sfPure in v.flags: # v.owner.kind != skModule: targetProc = p.module.preInitProc - if isCppConstructorCall and not containsHiddenPointer(v.typ): - callGlobalVarCppConstructor(targetProc, v, vn, value) + if isCppCtorCall and not containsHiddenPointer(v.typ): + callGlobalVarCppCtor(targetProc, v, vn, value) else: assignGlobalVar(targetProc, vn, valueAsRope) @@ -356,8 +358,8 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) = genLineDir(p, vn) var decl = localVarDecl(p, vn) var tmp: TLoc - if isCppConstructorCall: - genCppVarForConstructor(p, v, vn, value, decl) + if isCppCtorCall: + genCppVarForCtor(p, v, vn, value, decl) line(p, cpsStmts, decl) else: initLocExprSingleUse(p, value, tmp) @@ -388,7 +390,7 @@ proc genSingleVar(p: BProc, v: PSym; vn, value: PNode) = startBlock(targetProc) if value.kind != nkEmpty and valueAsRope.len == 0: genLineDir(targetProc, vn) - if not isCppConstructorCall: + if not isCppCtorCall: loadInto(targetProc, vn, value, v.loc) if forHcr: endBlock(targetProc) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index d6835cc50..9bcfa41ef 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -11,8 +11,7 @@ # ------------------------- Name Mangling -------------------------------- -import sighashes, modulegraphs -import strscans +import sighashes, modulegraphs, strscans import ../dist/checksums/src/checksums/md5 type @@ -488,30 +487,36 @@ proc multiFormat*(frmt: var string, chars : static openArray[char], args: openAr res.add(substr(frmt, start, i - 1)) frmt = res -proc genVirtualProcParams(m: BModule; t: PType, rettype, params: var string, +proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, params: var string, check: var IntSet, declareEnvironment=true; weakDep=false;) = - if t[0] == nil or isInvalidReturnType(m.config, t): + 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 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)" - - var types = @[getTypeDescWeak(m, this.typ, check, dkParam)] - var names = @[this.loc.r] + 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) - for i in 2.. -1 isOverride = afterParams.find("override") > -1 - discard scanf(afterParams, "->$s$* ", retType) + if isCtor: + discard scanf(afterParams, ":$s$*", superCall) + else: + discard scanf(afterParams, "->$s$* ", retType) + params = "(" & params & ")" -proc genVirtualProcHeader(m: BModule; prc: PSym; result: var Rope; asPtr: bool = false, isFwdDecl : bool = false) = - assert sfVirtual in prc.flags - # using static is needed for inline procs +proc genMemberProcHeader(m: BModule; prc: PSym; result: var Rope; asPtr: bool = false, isFwdDecl : bool = false) = + assert {sfVirtual, sfConstructor} * prc.flags != {} + let isCtor = sfConstructor in prc.flags + let isVirtual = not isCtor var check = initIntSet() fillBackendName(m, prc) fillLoc(prc.loc, locProc, prc.ast[namePos], OnUnknown) - var typ = prc.typ.n[1].sym.typ - var memberOp = "#." + var memberOp = "#." #only virtual + var typ: PType + if isCtor: + typ = prc.typ.sons[0] + else: + typ = prc.typ.sons[1] if typ.kind == tyPtr: typ = typ[0] memberOp = "#->" var typDesc = getTypeDescWeak(m, typ, check, dkParam) let asPtrStr = rope(if asPtr: "_PTR" else: "") - var name, params, rettype: string + var name, params, rettype, superCall: string var isFnConst, isOverride: bool - parseVFunctionDecl(prc.constraint.strVal, name, params, rettype, isFnConst, isOverride) - genVirtualProcParams(m, prc.typ, rettype, params, check, true, false) + parseVFunctionDecl(prc.constraint.strVal, name, params, rettype, superCall, isFnConst, isOverride, isCtor) + genMemberProcParams(m, prc, superCall, rettype, params, check, true, false) var fnConst, override: string + if isCtor: + name = typDesc if isFnConst: fnConst = " const" if isFwdDecl: - rettype = "virtual " & rettype - if isOverride: - override = " override" - else: - prc.loc.r = "$1 $2 (@)" % [memberOp, name] + if isVirtual: + rettype = "virtual " & rettype + if isOverride: + override = " override" + superCall = "" + else: + if isVirtual: + prc.loc.r = "$1$2(@)" % [memberOp, name] + elif superCall != "": + superCall = " : " & superCall + name = "$1::$2" % [typDesc, name] - + result.add "N_LIB_PRIVATE " - result.addf("$1$2($3, $4)$5$6$7", + result.addf("$1$2($3, $4)$5$6$7$8", [rope(CallingConvToStr[prc.typ.callConv]), asPtrStr, rettype, name, - params, fnConst, override]) + params, fnConst, override, superCall]) proc genProcHeader(m: BModule; prc: PSym; result: var Rope; asPtr: bool = false) = # using static is needed for inline procs diff --git a/compiler/cgen.nim b/compiler/cgen.nim index fe5c253dd..e79081dc6 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -640,9 +640,9 @@ proc genGlobalVarDecl(p: BProc, n: PNode; td, value: Rope; decl: var Rope) = else: decl = runtimeFormat(s.cgDeclFrmt & ";$n", [td, s.loc.r]) -proc genCppVarForConstructor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope) +proc genCppVarForCtor(p: BProc, v: PSym; vn, value: PNode; decl: var Rope) -proc callGlobalVarCppConstructor(p: BProc; v: PSym; vn, value: PNode) = +proc callGlobalVarCppCtor(p: BProc; v: PSym; vn, value: PNode) = let s = vn.sym fillBackendName(p.module, s) fillLoc(s.loc, locGlobalVar, vn, OnHeap) @@ -650,7 +650,7 @@ proc callGlobalVarCppConstructor(p: BProc; v: PSym; vn, value: PNode) = let td = getTypeDesc(p.module, vn.sym.typ, dkVar) genGlobalVarDecl(p, vn, td, "", decl) decl.add " " & $s.loc.r - genCppVarForConstructor(p, v, vn, value, decl) + genCppVarForCtor(p, v, vn, value, decl) p.module.s[cfsVars].add decl proc assignGlobalVar(p: BProc, n: PNode; value: Rope) = @@ -1143,8 +1143,8 @@ proc isNoReturn(m: BModule; s: PSym): bool {.inline.} = proc genProcAux*(m: BModule, prc: PSym) = var p = newProc(prc, m) var header = newRopeAppender() - if m.config.backend == backendCpp and sfVirtual in prc.flags: - genVirtualProcHeader(m, prc, header) + if m.config.backend == backendCpp and {sfVirtual, sfConstructor} * prc.flags != {}: + genMemberProcHeader(m, prc, header) else: genProcHeader(m, prc, header) var returnStmt: Rope = "" @@ -1162,7 +1162,7 @@ proc genProcAux*(m: BModule, prc: PSym) = internalError(m.config, prc.info, "proc has no result symbol") let resNode = prc.ast[resultPos] let res = resNode.sym # get result symbol - if not isInvalidReturnType(m.config, prc.typ): + if not isInvalidReturnType(m.config, prc.typ) and sfConstructor notin prc.flags: if sfNoInit in prc.flags: incl(res.flags, sfNoInit) if sfNoInit in prc.flags and p.module.compileToCpp and (let val = easyResultAsgn(procBody); val != nil): var decl = localVarDecl(p, resNode) @@ -1175,6 +1175,8 @@ proc genProcAux*(m: BModule, prc: PSym) = assert(res.loc.r != "") initLocalVar(p, res, immediateAsgn=false) returnStmt = ropecg(p.module, "\treturn $1;$n", [rdLoc(res.loc)]) + elif sfConstructor in prc.flags: + fillLoc(resNode.sym.loc, locParam, resNode, "this", OnHeap) else: fillResult(p.config, resNode, prc.typ) assignParam(p, res, prc.typ[0]) @@ -1252,7 +1254,7 @@ proc requiresExternC(m: BModule; sym: PSym): bool {.inline.} = proc genProcPrototype(m: BModule, sym: PSym) = useHeader(m, sym) - if lfNoDecl in sym.loc.flags or sfVirtual in sym.flags: return + if lfNoDecl in sym.loc.flags or {sfVirtual, sfConstructor} * sym.flags != {}: return if lfDynamicLib in sym.loc.flags: if sym.itemId.module != m.module.position and not containsOrIncl(m.declaredThings, sym.id): diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index de97ced99..08cdbfd0d 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -79,7 +79,7 @@ type procInstCache*: Table[ItemId, seq[LazyInstantiation]] # A symbol's ItemId. attachedOps*: array[TTypeAttachedOp, Table[ItemId, LazySym]] # Type ID, destructors, etc. methodsPerType*: Table[ItemId, seq[(int, LazySym)]] # Type ID, attached methods - virtualProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached virtual procs + memberProcsPerType*: Table[ItemId, seq[PSym]] # Type ID, attached member procs (only c++, virtual and ctor so far) enumToStringProcs*: Table[ItemId, LazySym] emittedTypeInfo*: Table[string, FileIndex] diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index 11305db2a..158e68eef 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -971,8 +971,12 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int, # only supported for backwards compat, doesn't do anything anymore noVal(c, it) of wConstructor: - noVal(c, it) incl(sym.flags, sfConstructor) + if sfImportc notin sym.flags: + sym.constraint = newEmptyStrNode(c, it, getOptionalStr(c, it, "")) + sym.constraint.strVal = sym.constraint.strVal + sym.flags.incl {sfExportc, sfMangleCpp} + sym.typ.callConv = ccNoConvention of wHeader: var lib = getLib(c, libHeader, getStrLitNode(c, it)) addToLib(lib, sym) diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 6e2fb9252..6cf9c6f7a 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1654,6 +1654,16 @@ proc swapResult(n: PNode, sRes: PSym, dNode: PNode) = n[i] = dNode swapResult(n[i], sRes, dNode) + +proc addThis(c: PContext, n: PNode, t: PType, owner: TSymKind) = + var s = newSym(skResult, getIdent(c.cache, "this"), c.idgen, + getCurrOwner(c), n.info) + s.typ = t + incl(s.flags, sfUsed) + c.p.resultSym = s + n.add newSymNode(c.p.resultSym) + addParamOrResult(c, c.p.resultSym, owner) + proc addResult(c: PContext, n: PNode, t: PType, owner: TSymKind) = template genResSym(s) = var s = newSym(skResult, getIdent(c.cache, "result"), c.idgen, @@ -2189,24 +2199,33 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if sfBorrow in s.flags and c.config.cmd notin cmdDocLike: result[bodyPos] = c.graph.emptyNode - if sfVirtual in s.flags: + if {sfVirtual, sfConstructor} * s.flags != {} and sfImportc notin s.flags: + let isVirtual = sfVirtual in s.flags + let pragmaName = if isVirtual: "virtual" else: "constructor" if c.config.backend == backendCpp: + if s.typ.sons.len < 2 and isVirtual: + localError(c.config, n.info, "virtual must have at least one parameter") for son in s.typ.sons: if son!=nil and son.isMetaType: - localError(c.config, n.info, "virtual unsupported for generic routine") - - var typ = s.typ.sons[1] - if typ.kind == tyPtr: + localError(c.config, n.info, pragmaName & " unsupported for generic routine") + var typ: PType + if sfConstructor in s.flags: + typ = s.typ.sons[0] + if typ == nil or typ.kind != tyObject: + localError(c.config, n.info, "constructor must return an object") + else: + typ = s.typ.sons[1] + if typ.kind == tyPtr and isVirtual: typ = typ[0] if typ.kind != tyObject: - localError(c.config, n.info, "virtual must be a non ref object type") + localError(c.config, n.info, "virtual must be either ptr to object or object type.") if typ.owner.id == s.owner.id and c.module.id == s.owner.id: - c.graph.virtualProcsPerType.mgetOrPut(typ.itemId, @[]).add s + c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s else: localError(c.config, n.info, - "virtual procs must be defined in the same scope as the type they are virtual for and it must be a top level scope") + pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope") else: - localError(c.config, n.info, "virtual procs are only supported in C++") + localError(c.config, n.info, pragmaName & " procs are only supported in C++") if n[bodyPos].kind != nkEmpty and sfError notin s.flags: # for DLL generation we allow sfImportc to have a body, for use in VM @@ -2232,15 +2251,19 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, # Macros and Templates can have generic parameters, but they are only # used for overload resolution (there is no instantiation of the symbol) if s.kind notin {skMacro, skTemplate} and s.magic == mNone: paramsTypeCheck(c, s.typ) - - maybeAddResult(c, s, n) - let resultType = - if s.kind == skMacro: - sysTypeFromName(c.graph, n.info, "NimNode") - elif not isInlineIterator(s.typ): - s.typ[0] - else: - nil + var resultType: PType + if sfConstructor in s.flags: + resultType = makePtrType(c, s.typ[0]) + addThis(c, n, resultType, skProc) + else: + maybeAddResult(c, s, n) + resultType = + if s.kind == skMacro: + sysTypeFromName(c.graph, n.info, "NimNode") + elif not isInlineIterator(s.typ): + s.typ[0] + else: + nil # semantic checking also needed with importc in case used in VM s.ast[bodyPos] = hloBody(c, semProcBody(c, n[bodyPos], resultType)) # unfortunately we cannot skip this step when in 'system.compiles' diff --git a/lib/std/private/dragonbox.nim b/lib/std/private/dragonbox.nim index 2ba22a751..e39ffd9a3 100644 --- a/lib/std/private/dragonbox.nim +++ b/lib/std/private/dragonbox.nim @@ -75,10 +75,10 @@ const const signMask*: BitsType = not (not BitsType(0) shr 1) -proc constructDouble*(bits: BitsType): Double {.constructor.} = +proc constructDouble*(bits: BitsType): Double = result.bits = bits -proc constructDouble*(value: ValueType): Double {.constructor.} = +proc constructDouble*(value: ValueType): Double = result.bits = cast[typeof(result.bits)](value) proc physicalSignificand*(this: Double): BitsType {.noSideEffect.} = diff --git a/lib/std/private/schubfach.nim b/lib/std/private/schubfach.nim index dad8363ba..194fb4bfa 100644 --- a/lib/std/private/schubfach.nim +++ b/lib/std/private/schubfach.nim @@ -39,10 +39,10 @@ const exponentMask: BitsType = maxIeeeExponent shl (significandSize - 1) signMask: BitsType = not (not BitsType(0) shr 1) -proc constructSingle(bits: BitsType): Single {.constructor.} = +proc constructSingle(bits: BitsType): Single = result.bits = bits -proc constructSingle(value: ValueType): Single {.constructor.} = +proc constructSingle(value: ValueType): Single = result.bits = cast[typeof(result.bits)](value) proc physicalSignificand(this: Single): BitsType {.noSideEffect.} = diff --git a/tests/cpp/tconstructor.nim b/tests/cpp/tconstructor.nim index 8489c71d3..d4d6a7ccf 100644 --- a/tests/cpp/tconstructor.nim +++ b/tests/cpp/tconstructor.nim @@ -1,6 +1,9 @@ discard """ targets: "cpp" cmd: "nim cpp $file" + output: ''' +1 +''' """ {.emit:"""/*TYPESECTION*/ @@ -15,10 +18,38 @@ struct CppClass { }; """.} -type CppClass* {.importcpp.} = object +type CppClass* {.importcpp, inheritable.} = object x: int32 y: int32 proc makeCppClass(x, y: int32): CppClass {.importcpp: "CppClass(@)", constructor.} +#test globals are init with the constructor call +var shouldCompile {.used.} = makeCppClass(1, 2) -var shouldCompile = makeCppClass(1, 2) +proc newCpp*[T](): ptr T {.importcpp:"new '*0()".} + +#creation +type NimClassNoNarent* = object + x: int32 + +proc makeNimClassNoParent(x:int32): NimClassNoNarent {. constructor.} = + this.x = x + discard + +let nimClassNoParent = makeNimClassNoParent(1) +echo nimClassNoParent.x #acess to this just fine. Notice the field will appear last because we are dealing with constructor calls here + +var nimClassNoParentDef {.used.}: NimClassNoNarent #test has a default constructor. + +#inheritance +type NimClass* = object of CppClass + +proc makeNimClass(x:int32): NimClass {. constructor:"NimClass('1 #1) : CppClass(0, #1) ".} = + this.x = x + +#optinially define the default constructor so we get rid of the cpp warn and we can declare the obj (note: default constructor of 'tyObject_NimClass__apRyyO8cfRsZtsldq1rjKA' is implicitly deleted because base class 'CppClass' has no default constructor) +proc makeCppClass(): NimClass {. constructor: "NimClass() : CppClass(0, 0) ".} = + this.x = 1 + +let nimClass = makeNimClass(1) +var nimClassDef {.used.}: NimClass #since we explictly defined the default constructor we can declare the obj \ No newline at end of file -- cgit 1.4.1-2-gfad0