# # # The Nim Compiler # (c) Copyright 2020 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Cursor inference: ## The basic idea was like this: Elide 'destroy(x)' calls if only ## special literals are assigned to 'x' and 'x' is not mutated or ## passed by 'var T' to something else. Special literals are string literals or ## arrays / tuples of string literals etc. ## ## However, there is a much more general rule here: Compute which variables ## can be annotated with `.cursor`. To see how and when we can do that, ## think about this question: In `dest = src` when do we really have to ## *materialize* the full copy? - Only if `dest` or `src` are mutated ## afterwards. `dest` is the potential cursor variable, so that is ## simple to analyse. And if `src` is a location derived from a ## formal parameter, we also know it is not mutated! In other words, we ## do a compile-time copy-on-write analysis. import ast, types, renderer, idents, intsets, options, msgs type Cursor = object s: PSym deps: IntSet Con = object cursors: seq[Cursor] mayOwnData: IntSet mutations: IntSet reassigns: IntSet config: ConfigRef inAsgnSource: int proc locationRoot(e: PNode; followDotExpr = true): PSym = var n = e while true: case n.kind of nkSym: if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}: return n.sym else: return nil of nkDotExpr, nkDerefExpr, nkBracketExpr, nkHiddenDeref, nkCheckedFieldExpr, nkAddr, nkHiddenAddr: if followDotExpr: n = n[0] else: return nil of nkObjUpConv, nkObjDownConv: n = n[0] of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkCast: n = n[1] of nkStmtList, nkStmtListExpr: if n.len > 0: n = n[^1] else: return nil else: return nil proc addDep(c: var Con; dest: var Cursor; dependsOn: PSym) = if dest.s != dependsOn: dest.deps.incl dependsOn.id proc cursorId(c: Con; x: PSym): int = for i in 0.. 0: analyseAsgn(c, dest, n[^1]) of nkClosure: for i in 1.. treat it like a sink parameter c.mayOwnData.incl r.id c.mutations.incl r.id proc analyse(c: var Con; n: PNode) = case n.kind of nkCallKinds: let parameters = n[0].typ let L = if parameters != nil: parameters.len else: 0 analyse(c, n[0]) for i in 1..= 0: analyseAsgn(c, c.cursors[idx], n[1]) c.reassigns.incl n[0].sym.id else: # assignments like 'x.field = value' mean that 'x' itself cannot # be a cursor: let r = locationRoot(n[0]) if r != nil: # however, an assignment like 'it.field = x' does not influence r's # cursorness property: if r.typ.skipTypes(abstractInst).kind notin {tyPtr, tyRef}: c.mayOwnData.incl r.id c.mutations.incl r.id if hasDestructor(n[1].typ): rhsIsSink(c, n[1]) of nkAddr, nkHiddenAddr: analyse(c, n[0]) let r = locationRoot(n[0]) if r != nil: c.mayOwnData.incl r.id c.mutations.incl r.id of nkTupleConstr, nkBracket, nkObjConstr: for child in n: analyse(c, child) if c.inAsgnSource > 0: for i in ord(n.kind == nkObjConstr)..