Cpp Vfunctions draft (#21790)

* introduces virtual pragma, modifies proc def, prevents proc decl

* marks virtual procs as infix

* forward declare vfuncs inside the typedef

* adds naked callConv to virtual

* virtual proc error if not defined in the same top level scope as the type

* first param is now this. extracts genvirtualheaderproc

* WIP syntax

* supports obj. Removes the need for the prefix

* parameter count starts as this. Cleanup

* clean up

* sem tests

* adds integration tests

* uses constraint to store the virtual content

* introduces genVirtualProcParams

---------

Co-authored-by: Andreas Rumpf <rumpf_a@web.de>
This commit is contained in:
Juan M Gómez
2023-05-17 10:44:42 +01:00
committed by GitHub
parent 1314ea7516
commit 02a10ec379
8 changed files with 260 additions and 13 deletions

View File

@@ -231,7 +231,7 @@ type
TNodeKinds* = set[TNodeKind]
type
TSymFlag* = enum # 49 flags!
TSymFlag* = enum # 50 flags!
sfUsed, # read access of sym (for warnings) or simply used
sfExported, # symbol is exported from module
sfFromGeneric, # symbol is instantiation of a generic; this is needed
@@ -312,6 +312,7 @@ type
#
# This is disallowed but can cause the typechecking to go into
# an infinite loop, this flag is used as a sentinel to stop it.
sfVirtual # proc is a C++ virtual function
TSymFlags* = set[TSymFlag]
@@ -929,7 +930,7 @@ type
cname*: string # resolved C declaration name in importc decl, e.g.:
# proc fun() {.importc: "$1aux".} => cname = funaux
constraint*: PNode # additional constraints like 'lit|result'; also
# misused for the codegenDecl pragma in the hope
# misused for the codegenDecl and virtual pragmas in the hope
# it won't cause problems
# for skModule the string literal to output for
# deprecated modules.

View File

@@ -12,6 +12,7 @@
# ------------------------- Name Mangling --------------------------------
import sighashes, modulegraphs
import strscans
import ../dist/checksums/src/checksums/md5
proc isKeyword(w: PIdent): bool =
@@ -424,9 +425,105 @@ proc paramStorageLoc(param: PSym): TStorageLoc =
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
proc genVirtualProcParams(m: BModule; t: PType, rettype, params: var string,
check: var IntSet, declareEnvironment=true;
weakDep=false;) =
if t[0] == nil or isInvalidReturnType(m.config, t):
rettype = "void"
else:
if rettype == "":
rettype = getTypeDescAux(m, t[0], check, skResult)
else:
rettype = runtimeFormat(rettype.replace("'0", "$1"), [getTypeDescAux(m, t[0], check, skResult)])
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, skParam)]
var names = @[this.loc.r]
for i in 2..<t.n.len:
if t.n[i].kind != nkSym: internalError(m.config, t.n.info, "genVirtualProcParams")
var param = t.n[i].sym
var typ, name : string
fillParamName(m, param)
fillLoc(param.loc, locParam, t.n[i],
param.paramStorageLoc)
if ccgIntroducedPtr(m.config, param, t[0]):
typ = getTypeDescWeak(m, param.typ, check, skParam) & "*"
incl(param.loc.flags, lfIndirect)
param.loc.storage = OnUnknown
elif weakDep:
typ = getTypeDescWeak(m, param.typ, check, skParam)
else:
typ = getTypeDescAux(m, param.typ, check, skParam)
if sfNoalias in param.flags:
typ.add("NIM_NOALIAS ")
name = param.loc.r
types.add typ
names.add name
multiFormat(params, @['\'', '#'], [types, names])
if params == "()":
params = "(void)"
if tfVarargs in t.flags:
if params != "(":
params[^1] = ','
else:
params.delete(params.len()-1..params.len()-1)
params.add("...)")
proc genProcParams(m: BModule; t: PType, rettype, params: var Rope,
check: var IntSet, declareEnvironment=true;
weakDep=false) =
weakDep=false;) =
params = "("
if t[0] == nil or isInvalidReturnType(m.config, t):
rettype = "void"
@@ -564,10 +661,18 @@ proc genRecordFieldsAux(m: BModule; n: PNode,
result.addf("\t$1$3 $2;$n", [getTypeDescAux(m, field.loc.t, check, skField), 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 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]
for prc in procs:
var header: Rope
genVirtualProcHeader(m, prc, header, false, true)
result.add "\t" & header & ";\n"
proc fillObjectFields*(m: BModule; typ: PType) =
# sometimes generic objects are not consistently merged. We patch over
# this fact here.
@@ -971,19 +1076,58 @@ 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) =
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)
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
var check = initIntSet()
fillBackendName(m, prc)
fillLoc(prc.loc, locProc, prc.ast[namePos], OnUnknown)
var typ = prc.typ.n[1].sym.typ
var memberOp = "#."
if typ.kind == tyPtr:
typ = typ[0]
memberOp = "#->"
var typDesc = getTypeDescWeak(m, typ, check, skParam)
let asPtrStr = rope(if asPtr: "_PTR" else: "")
var name, params, rettype: string
var isFnConst, isOverride: bool
parseVFunctionDecl(prc.constraint.strVal, name, params, rettype, isFnConst, isOverride)
genVirtualProcParams(m, prc.typ, rettype, params, check, true, false)
var fnConst, override: string
if isFnConst:
fnConst = " const"
if isFwdDecl:
rettype = "virtual " & rettype
if isOverride:
override = " override"
else:
prc.loc.r = "$1 $2 (@)" % [memberOp, name]
name = "$1::$2" % [typDesc, name]
result.add "N_LIB_PRIVATE "
result.addf("$1$2($3, $4)$5$6$7",
[rope(CallingConvToStr[prc.typ.callConv]), asPtrStr, rettype, name,
params, fnConst, override])
proc genProcHeader(m: BModule; prc: PSym; result: var Rope; asPtr: bool = false) =
# using static is needed for inline procs
var check = initIntSet()
fillBackendName(m, prc)
fillLoc(prc.loc, locProc, prc.ast[namePos], OnUnknown)
var rettype, params: Rope
genProcParams(m, prc.typ, rettype, params, check)
genProcParams(m, prc.typ, rettype, params, check, true, false)
# handle the 2 options for hotcodereloading codegen - function pointer
# (instead of forward declaration) or header for function body with "_actual" postfix
let asPtrStr = rope(if asPtr: "_PTR" else: "")
var name = prc.loc.r
if isReloadable(m, prc) and not asPtr:
name.add("_actual")
# careful here! don't access ``prc.ast`` as that could reload large parts of
# the object graph!
if prc.constraint.isNil:
@@ -1003,6 +1147,7 @@ proc genProcHeader(m: BModule; prc: PSym; result: var Rope; asPtr: bool = false)
let asPtrStr = if asPtr: (rope("(*") & name & ")") else: name
result.add runtimeFormat(prc.cgDeclFrmt, [rettype, asPtrStr, params])
# ------------------ type info generation -------------------------------------
proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope

View File

@@ -1128,7 +1128,10 @@ proc isNoReturn(m: BModule; s: PSym): bool {.inline.} =
proc genProcAux*(m: BModule, prc: PSym) =
var p = newProc(prc, m)
var header = newRopeAppender()
genProcHeader(m, prc, header)
if m.config.backend == backendCpp and sfVirtual in prc.flags:
genVirtualProcHeader(m, prc, header)
else:
genProcHeader(m, prc, header)
var returnStmt: Rope = ""
assert(prc.ast != nil)
@@ -1234,7 +1237,7 @@ proc requiresExternC(m: BModule; sym: PSym): bool {.inline.} =
proc genProcPrototype(m: BModule, sym: PSym) =
useHeader(m, sym)
if lfNoDecl in sym.loc.flags: return
if lfNoDecl in sym.loc.flags or sfVirtual in 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,6 +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
enumToStringProcs*: Table[ItemId, LazySym]
emittedTypeInfo*: Table[string, FileIndex]

View File

@@ -34,7 +34,7 @@ const
wAsmNoStackFrame, wDiscardable, wNoInit, wCodegenDecl,
wGensym, wInject, wRaises, wEffectsOf, wTags, wForbids, wLocks, wDelegator, wGcSafe,
wConstructor, wLiftLocals, wStackTrace, wLineTrace, wNoDestroy,
wRequires, wEnsures, wEnforceNoRaises, wSystemRaisesDefect}
wRequires, wEnsures, wEnforceNoRaises, wSystemRaisesDefect, wVirtual}
converterPragmas* = procPragmas
methodPragmas* = procPragmas+{wBase}-{wImportCpp}
templatePragmas* = {wDeprecated, wError, wGensym, wInject, wDirty,
@@ -211,9 +211,9 @@ proc processImportObjC(c: PContext; s: PSym, extname: string, info: TLineInfo) =
let m = s.getModule()
incl(m.flags, sfCompileToObjc)
proc newEmptyStrNode(c: PContext; n: PNode): PNode {.noinline.} =
proc newEmptyStrNode(c: PContext; n: PNode, strVal: string = ""): PNode {.noinline.} =
result = newNodeIT(nkStrLit, n.info, getSysType(c.graph, n.info, tyString))
result.strVal = ""
result.strVal = strVal
proc getStrLitNode(c: PContext, n: PNode): PNode =
if n.kind notin nkPragmaCallKinds or n.len != 2:
@@ -245,6 +245,14 @@ proc getOptionalStr(c: PContext, n: PNode, defaultStr: string): string =
if n.kind in nkPragmaCallKinds: result = expectStrLit(c, n)
else: result = defaultStr
proc processVirtual(c: PContext, n: PNode, s: PSym) =
s.constraint = newEmptyStrNode(c, n, getOptionalStr(c, n, "$1"))
s.constraint.strVal = s.constraint.strVal % s.name.s
s.flags.incl {sfVirtual, sfInfixCall, sfExportc, sfMangleCpp}
s.typ.callConv = ccNoConvention
incl c.config.globalOptions, optMixedMode
proc processCodegenDecl(c: PContext, n: PNode, sym: PSym) =
sym.constraint = getStrLitNode(c, n)
@@ -1263,6 +1271,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
sym.flags.incl sfNeverRaises
of wSystemRaisesDefect:
sym.flags.incl sfSystemRaisesDefect
of wVirtual:
processVirtual(c, it, sym)
else: invalidPragma(c, it)
elif comesFromPush and whichKeyword(ident) != wInvalid:
discard "ignore the .push pragma; it doesn't apply"

View File

@@ -833,7 +833,6 @@ proc trackCall(tracked: PEffects; n: PNode) =
# and it's not a recursive call:
if not (a.kind == nkSym and a.sym == tracked.owner):
markSideEffect(tracked, a, n.info)
# p's effects are ours too:
var a = n[0]
#if canRaise(a):

View File

@@ -2188,6 +2188,25 @@ 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 c.config.backend == backendCpp:
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:
typ = typ[0]
if typ.kind != tyObject:
localError(c.config, n.info, "virtual must be a non ref object type")
if typ.owner.id == s.owner.id and c.module.id == s.owner.id:
c.graph.virtualProcsPerType.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")
else:
localError(c.config, n.info, "virtual 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

68
tests/cpp/tvirtual.nim Normal file
View File

@@ -0,0 +1,68 @@
discard """
targets: "cpp"
cmd: "nim cpp $file"
output: '''
hello foo
hello boo
hello boo
Const Message: hello world
NimPrinter: hello world
NimPrinterConstRef: hello world
'''
"""
{.emit:"""/*TYPESECTION*/
#include <iostream>
class CppPrinter {
public:
virtual void printConst(char* message) const {
std::cout << "Const Message: " << message << std::endl;
}
virtual void printConstRef(char* message, const int& flag) const {
std::cout << "Const Ref Message: " << message << std::endl;
}
};
""".}
proc newCpp*[T](): ptr T {.importcpp:"new '*0()".}
type
Foo = object of RootObj
FooPtr = ptr Foo
Boo = object of Foo
BooPtr = ptr Boo
CppPrinter {.importcpp, inheritable.} = object
NimPrinter {.exportc.} = object of CppPrinter
proc salute(self:FooPtr) {.virtual.} =
echo "hello foo"
proc salute(self:BooPtr) {.virtual.} =
echo "hello boo"
let foo = newCpp[Foo]()
let boo = newCpp[Boo]()
let booAsFoo = cast[FooPtr](newCpp[Boo]())
#polymorphism works
foo.salute()
boo.salute()
booAsFoo.salute()
let message = "hello world".cstring
proc printConst(self:CppPrinter, message:cstring) {.importcpp.}
CppPrinter().printConst(message)
#notice override is optional.
#Will make the cpp compiler to fail if not virtual function with the same signature if found in the base type
proc printConst(self:NimPrinter, message:cstring) {.virtual:"$1('2 #2) const override".} =
echo "NimPrinter: " & $message
proc printConstRef(self:NimPrinter, message:cstring, flag:int32) {.virtual:"$1('2 #2, const '3& #3 ) const override".} =
echo "NimPrinterConstRef: " & $message
NimPrinter().printConst(message)
var val : int32 = 10
NimPrinter().printConstRef(message, val)