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 <rumpf_a@web.de>
This commit is contained in:
Juan M Gómez
2023-05-30 20:47:26 +01:00
committed by GitHub
parent 20446b437b
commit e43a51fcf3
9 changed files with 180 additions and 84 deletions

View File

@@ -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)

View File

@@ -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..<t.n.len:
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genVirtualProcParams")
let firstParam = if isCtor: 1 else: 2
for i in firstParam..<t.n.len:
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genMemberProcParams")
var param = t.n[i].sym
var descKind = dkParam
if optByRef in param.options:
@@ -534,9 +539,15 @@ proc genVirtualProcParams(m: BModule; t: PType, rettype, params: var string,
name = param.loc.r
types.add typ
names.add name
args.add types[^1] & " " & names[^1]
multiFormat(params, @['\'', '#'], [types, names])
multiFormat(superCall, @['\'', '#'], [types, names])
if params == "()":
params = "(void)"
if types.len == 0:
params = "(void)"
else:
params = "(" & args.join(", ") & ")"
if tfVarargs in t.flags:
if params != "(":
params[^1] = ','
@@ -687,17 +698,25 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
result.addf("\t$1$3 $2;$n", [getTypeDescAux(m, field.loc.t, check, dkField), sname, noAlias])
else: internalError(m.config, n.info, "genRecordFieldsAux()")
proc genVirtualProcHeader(m: BModule; prc: PSym; result: var Rope; asPtr: bool = false, isFwdDecl:bool = false)
proc genMemberProcHeader(m: BModule; prc: PSym; result: var Rope; asPtr: bool = false, isFwdDecl:bool = false)
proc getRecordFields(m: BModule; typ: PType, check: var IntSet): Rope =
result = newRopeAppender()
genRecordFieldsAux(m, typ.n, typ, check, result)
if typ.itemId in m.g.graph.virtualProcsPerType:
let procs = m.g.graph.virtualProcsPerType[typ.itemId]
if typ.itemId in m.g.graph.memberProcsPerType:
let procs = m.g.graph.memberProcsPerType[typ.itemId]
var isDefaultCtorGen, isCtorGen: bool
for prc in procs:
var header: Rope
genVirtualProcHeader(m, prc, header, false, true)
result.add "\t" & header & ";\n"
if sfConstructor in prc.flags:
isCtorGen = true
if prc.typ.n.len == 1:
isDefaultCtorGen = true
genMemberProcHeader(m, prc, header, false, true)
result.addf "$1;$n", [header]
if isCtorGen and not isDefaultCtorGen:
var ch: IntSet
result.addf "$1() = default;$n", [getTypeDescAux(m, typ, ch, dkOther)]
proc fillObjectFields*(m: BModule; typ: PType) =
# sometimes generic objects are not consistently merged. We patch over
@@ -825,8 +844,6 @@ proc getOpenArrayDesc(m: BModule; t: PType, check: var IntSet; kind: TypeDescKin
proc getTypeDescAux(m: BModule; origTyp: PType, check: var IntSet; kind: TypeDescKind): Rope =
# returns only the type's name
if kind == dkRefParam:
echo "llega con typedesc aux:", $origTyp.kind
var t = origTyp.skipTypes(irrelevantForBackend-{tyOwned})
if containsOrIncl(check, t.id):
if not (isImportedCppType(origTyp) or isImportedCppType(t)):
@@ -1106,46 +1123,63 @@ proc isReloadable(m: BModule; prc: PSym): bool =
proc isNonReloadable(m: BModule; prc: PSym): bool =
return m.hcrOn and sfNonReloadable in prc.flags
proc parseVFunctionDecl(val: string; name, params, retType: var string; isFnConst, isOverride: var bool) =
proc parseVFunctionDecl(val: string; name, params, retType, superCall: var string; isFnConst, isOverride: var bool; isCtor: bool) =
var afterParams: string
if scanf(val, "$*($*)$s$*", name, params, afterParams):
isFnConst = afterParams.find("const") > -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

View File

@@ -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):

View File

@@ -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]

View File

@@ -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)

View File

@@ -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'

View File

@@ -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.} =

View File

@@ -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.} =

View File

@@ -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