mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-11 22:08:54 +00:00
spec for view types (#15424)
* spec for view types * spec additions * refactoring; there are two different kinds of views * refactorings and spec additions * enforce that view types are initialized * enforce borrowing from the first formal parameter * enforce lifetimes for borrowing of locals * typo in the manual * clarify in the implementation what a borrow operation really is
This commit is contained in:
@@ -1403,9 +1403,6 @@ proc copyType*(t: PType, owner: PSym, keepId: bool): PType =
|
||||
|
||||
proc exactReplica*(t: PType): PType = copyType(t, t.owner, true)
|
||||
|
||||
template requiresInit*(t: PType): bool =
|
||||
t.flags * {tfRequiresInit, tfNotNil} != {}
|
||||
|
||||
proc copySym*(s: PSym): PSym =
|
||||
result = newSym(s.kind, s.name, s.owner, s.info, s.options)
|
||||
#result.ast = nil # BUGFIX; was: s.ast which made problems
|
||||
|
||||
@@ -1601,7 +1601,7 @@ proc takeImplicitAddr(c: PContext, n: PNode; isLent: bool): PNode =
|
||||
proc asgnToResultVar(c: PContext, n, le, ri: PNode) {.inline.} =
|
||||
if le.kind == nkHiddenDeref:
|
||||
var x = le[0]
|
||||
if (x.typ.kind in {tyVar, tyLent} or isViewType(x.typ)) and x.kind == nkSym and x.sym.kind == skResult:
|
||||
if (x.typ.kind in {tyVar, tyLent} or classifyViewType(x.typ) != noView) and x.kind == nkSym and x.sym.kind == skResult:
|
||||
n[0] = x # 'result[]' --> 'result'
|
||||
n[1] = takeImplicitAddr(c, ri, x.typ.kind == tyLent)
|
||||
x.typ.flags.incl tfVarIsPtr
|
||||
@@ -1712,8 +1712,8 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
|
||||
if le == nil:
|
||||
localError(c.config, a.info, "expression has no type")
|
||||
elif (skipTypes(le, {tyGenericInst, tyAlias, tySink}).kind notin {tyVar} and
|
||||
isAssignable(c, a) in {arNone, arLentValue}) or
|
||||
skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs}:
|
||||
isAssignable(c, a) in {arNone, arLentValue}) or (
|
||||
skipTypes(le, abstractVar).kind in {tyOpenArray, tyVarargs} and views notin c.features):
|
||||
# Direct assignment to a discriminant is allowed!
|
||||
localError(c.config, a.info, errXCannotBeAssignedTo %
|
||||
renderTree(a, {renderNoComments}))
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import
|
||||
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
|
||||
wordrecg, strutils, options, guards, lineinfos, semfold, semdata,
|
||||
modulegraphs, varpartitions
|
||||
modulegraphs, varpartitions, typeallowed
|
||||
|
||||
when defined(useDfa):
|
||||
import dfa
|
||||
@@ -1244,7 +1244,7 @@ proc trackProc*(c: PContext; s: PSym, body: PNode) =
|
||||
|
||||
var mutationInfo = MutationInfo()
|
||||
if {strictFuncs, views} * c.features != {}:
|
||||
var partitions = computeGraphPartitions(s, body)
|
||||
var partitions = computeGraphPartitions(s, body, g.config)
|
||||
if not t.hasSideEffect and t.hasDangerousAssign:
|
||||
t.hasSideEffect = varpartitions.hasSideEffect(partitions, mutationInfo)
|
||||
if views in c.features:
|
||||
|
||||
@@ -187,42 +187,77 @@ proc typeAllowed*(t: PType, kind: TSymKind; c: PContext; flags: TTypeAllowedFlag
|
||||
var marker = initIntSet()
|
||||
result = typeAllowedAux(marker, t, kind, c, flags)
|
||||
|
||||
proc isViewTypeAux(marker: var IntSet, t: PType): bool
|
||||
type
|
||||
ViewTypeKind* = enum
|
||||
noView, immutableView, mutableView
|
||||
|
||||
proc isViewTypeNode(marker: var IntSet, n: PNode): bool =
|
||||
proc combine(dest: var ViewTypeKind, b: ViewTypeKind) {.inline.} =
|
||||
case dest
|
||||
of noView, mutableView:
|
||||
dest = b
|
||||
of immutableView:
|
||||
if b == mutableView: dest = b
|
||||
|
||||
proc classifyViewTypeAux(marker: var IntSet, t: PType): ViewTypeKind
|
||||
|
||||
proc classifyViewTypeNode(marker: var IntSet, n: PNode): ViewTypeKind =
|
||||
case n.kind
|
||||
of nkSym:
|
||||
result = isViewTypeAux(marker, n.typ)
|
||||
result = classifyViewTypeAux(marker, n.typ)
|
||||
of nkOfBranch:
|
||||
result = isViewTypeNode(marker, n.lastSon)
|
||||
result = classifyViewTypeNode(marker, n.lastSon)
|
||||
else:
|
||||
result = noView
|
||||
for child in n:
|
||||
result = isViewTypeNode(marker, child)
|
||||
if result: break
|
||||
result.combine classifyViewTypeNode(marker, child)
|
||||
if result == mutableView: break
|
||||
|
||||
proc isViewTypeAux(marker: var IntSet, t: PType): bool =
|
||||
if containsOrIncl(marker, t.id): return false
|
||||
proc classifyViewTypeAux(marker: var IntSet, t: PType): ViewTypeKind =
|
||||
if containsOrIncl(marker, t.id): return noView
|
||||
case t.kind
|
||||
of tyVar, tyLent, tyVarargs, tyOpenArray:
|
||||
result = true
|
||||
of tyVar:
|
||||
result = mutableView
|
||||
of tyLent, tyVarargs, tyOpenArray:
|
||||
result = immutableView
|
||||
of tyGenericInst, tyDistinct, tyAlias, tyInferred, tySink, tyOwned,
|
||||
tyUncheckedArray, tySequence, tyArray, tyRef, tyStatic, tyFromExpr:
|
||||
result = isViewTypeAux(marker, lastSon(t))
|
||||
tyUncheckedArray, tySequence, tyArray, tyRef, tyStatic:
|
||||
result = classifyViewTypeAux(marker, lastSon(t))
|
||||
of tyFromExpr:
|
||||
if t.len > 0:
|
||||
result = classifyViewTypeAux(marker, lastSon(t))
|
||||
else:
|
||||
result = noView
|
||||
of tyTuple:
|
||||
result = noView
|
||||
for i in 0..<t.len:
|
||||
result = isViewTypeAux(marker, t[i])
|
||||
if result: break
|
||||
result.combine classifyViewTypeAux(marker, t[i])
|
||||
if result == mutableView: break
|
||||
of tyObject:
|
||||
result = false
|
||||
result = noView
|
||||
if t.n != nil:
|
||||
result = isViewTypeNode(marker, t.n)
|
||||
result = classifyViewTypeNode(marker, t.n)
|
||||
if t[0] != nil:
|
||||
result = result or isViewTypeAux(marker, t[0])
|
||||
result.combine classifyViewTypeAux(marker, t[0])
|
||||
else:
|
||||
# it doesn't matter what these types contain, 'ptr openArray' is not a
|
||||
# view type!
|
||||
result = false
|
||||
result = noView
|
||||
|
||||
proc isViewType*(t: PType): bool =
|
||||
proc classifyViewType*(t: PType): ViewTypeKind =
|
||||
var marker = initIntSet()
|
||||
result = isViewTypeAux(marker, t)
|
||||
result = classifyViewTypeAux(marker, t)
|
||||
|
||||
proc directViewType*(t: PType): ViewTypeKind =
|
||||
# does classify 't' without looking recursively into 't'.
|
||||
case t.kind
|
||||
of tyVar:
|
||||
result = mutableView
|
||||
of tyLent, tyOpenArray:
|
||||
result = immutableView
|
||||
of abstractInst-{tyTypeDesc}:
|
||||
result = directViewType(t.lastSon)
|
||||
else:
|
||||
result = noView
|
||||
|
||||
proc requiresInit*(t: PType): bool =
|
||||
(t.flags * {tfRequiresInit, tfNotNil} != {}) or classifyViewType(t) != noView
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
#
|
||||
|
||||
## Partition variables into different graphs. Used for
|
||||
## Nim's write tracking and also for the cursor inference.
|
||||
## Nim's write tracking, borrow checking and also for the
|
||||
## cursor inference.
|
||||
## The algorithm is a reinvention / variation of Steensgaard's
|
||||
## algorithm.
|
||||
## The used data structure is "union find" with path compression.
|
||||
@@ -23,12 +24,27 @@
|
||||
## borrowed from locations that are connected to a graph
|
||||
## that is mutated during the liveness of the cursor.
|
||||
## (We track all possible mutations of a graph.)
|
||||
##
|
||||
## See https://nim-lang.github.io/Nim/manual_experimental.html#view-types-algorithm
|
||||
## for a high-level description of how borrow checking works.
|
||||
|
||||
import ast, types, lineinfos, options, msgs, renderer
|
||||
import ast, types, lineinfos, options, msgs, renderer, typeallowed
|
||||
from trees import getMagic, whichPragma
|
||||
from wordrecg import wNoSideEffect
|
||||
from isolation_check import canAlias
|
||||
from typeallowed import isViewType
|
||||
|
||||
type
|
||||
AbstractTime = distinct int
|
||||
|
||||
const
|
||||
MaxTime = AbstractTime high(int)
|
||||
MinTime = AbstractTime(-1)
|
||||
|
||||
proc `<=`(a, b: AbstractTime): bool {.borrow.}
|
||||
proc `<`(a, b: AbstractTime): bool {.borrow.}
|
||||
|
||||
proc inc(x: var AbstractTime; diff = 1) {.borrow.}
|
||||
proc dec(x: var AbstractTime; diff = 1) {.borrow.}
|
||||
|
||||
type
|
||||
SubgraphFlag = enum
|
||||
@@ -56,21 +72,24 @@ type
|
||||
flags: set[VarFlag]
|
||||
sym: PSym
|
||||
reassignedTo: int
|
||||
aliveStart, aliveEnd: int # the range for which the variable is alive.
|
||||
aliveStart, aliveEnd: AbstractTime # the range for which the variable is alive.
|
||||
borrowsFrom: seq[int] # indexes into Partitions.s
|
||||
|
||||
MutationInfo* = object
|
||||
param: PSym
|
||||
mutatedHere, connectedVia: TLineInfo
|
||||
flags: set[SubgraphFlag]
|
||||
maxMutation, minConnection: int
|
||||
mutations: seq[int]
|
||||
maxMutation, minConnection: AbstractTime
|
||||
mutations: seq[AbstractTime]
|
||||
|
||||
Partitions* = object
|
||||
abstractTime: int
|
||||
abstractTime: AbstractTime
|
||||
s: seq[VarIndex]
|
||||
graphs: seq[MutationInfo]
|
||||
unanalysableMutation, performCursorInference: bool
|
||||
inAsgnSource, inConstructor, inNoSideEffectSection: int
|
||||
owner: PSym
|
||||
config: ConfigRef
|
||||
|
||||
proc mutationAfterConnection(g: MutationInfo): bool {.inline.} =
|
||||
#echo g.maxMutation, " ", g.minConnection, " ", g.param
|
||||
@@ -108,7 +127,7 @@ proc variableId(c: Partitions; x: PSym): int =
|
||||
proc registerResult(c: var Partitions; n: PNode) =
|
||||
if n.kind == nkSym:
|
||||
c.s.add VarIndex(con: Connection(kind: isEmptyRoot), sym: n.sym, reassignedTo: 0,
|
||||
aliveStart: high(int), aliveEnd: c.abstractTime)
|
||||
aliveStart: MaxTime, aliveEnd: c.abstractTime)
|
||||
|
||||
proc registerParam(c: var Partitions; n: PNode) =
|
||||
assert n.kind == nkSym
|
||||
@@ -118,7 +137,7 @@ proc registerParam(c: var Partitions; n: PNode) =
|
||||
aliveStart: c.abstractTime, aliveEnd: c.abstractTime)
|
||||
c.graphs.add MutationInfo(param: n.sym, mutatedHere: unknownLineInfo,
|
||||
connectedVia: unknownLineInfo, flags: {connectsConstParam},
|
||||
maxMutation: -1, minConnection: high(int),
|
||||
maxMutation: MinTime, minConnection: MaxTime,
|
||||
mutations: @[])
|
||||
else:
|
||||
c.s.add VarIndex(con: Connection(kind: isEmptyRoot), sym: n.sym, reassignedTo: 0,
|
||||
@@ -152,7 +171,7 @@ proc potentialMutation(v: var Partitions; s: PSym; info: TLineInfo) =
|
||||
v.s[r].con = Connection(kind: isRootOf, graphIndex: v.graphs.len)
|
||||
v.graphs.add MutationInfo(param: if isConstParam(s): s else: nil, mutatedHere: info,
|
||||
connectedVia: unknownLineInfo, flags: {isMutated},
|
||||
maxMutation: v.abstractTime, minConnection: high(int),
|
||||
maxMutation: v.abstractTime, minConnection: MaxTime,
|
||||
mutations: @[v.abstractTime])
|
||||
of isRootOf:
|
||||
let g = addr v.graphs[v.s[r].con.graphIndex]
|
||||
@@ -192,7 +211,7 @@ proc connect(v: var Partitions; a, b: PSym; info: TLineInfo) =
|
||||
# for now we always make 'rb' the slave and 'ra' the master:
|
||||
var rbFlags: set[SubgraphFlag] = {}
|
||||
var mutatedHere = unknownLineInfo
|
||||
var mut = 0
|
||||
var mut = AbstractTime 0
|
||||
var con = v.abstractTime
|
||||
var gb: ptr MutationInfo = nil
|
||||
if v.s[rb].con.kind == isRootOf:
|
||||
@@ -223,6 +242,80 @@ proc connect(v: var Partitions; a, b: PSym; info: TLineInfo) =
|
||||
else:
|
||||
assert false, "cannot happen"
|
||||
|
||||
proc borrowFromConstExpr(n: PNode): bool =
|
||||
case n.kind
|
||||
of nkCharLit..nkNilLit:
|
||||
result = true
|
||||
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv,
|
||||
nkCast, nkObjUpConv, nkObjDownConv:
|
||||
result = borrowFromConstExpr(n.lastSon)
|
||||
of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure, nkRange:
|
||||
result = true
|
||||
for i in ord(n.kind == nkObjConstr)..<n.len:
|
||||
if not borrowFromConstExpr(n[i]): return false
|
||||
of nkCallKinds:
|
||||
if getMagic(n) == mArrToSeq:
|
||||
result = true
|
||||
for i in 1..<n.len:
|
||||
if not borrowFromConstExpr(n[i]): return false
|
||||
else: discard
|
||||
|
||||
proc pathExpr(node: PNode; owner: PSym): PNode =
|
||||
#[ From the spec:
|
||||
|
||||
- ``source`` itself is a path expression.
|
||||
- Container access like ``e[i]`` is a path expression.
|
||||
- Tuple access ``e[0]`` is a path expression.
|
||||
- Object field access ``e.field`` is a path expression.
|
||||
- ``system.toOpenArray(e, ...)`` is a path expression.
|
||||
- Pointer dereference ``e[]`` is a path expression.
|
||||
- An address ``addr e``, ``unsafeAddr e`` is a path expression.
|
||||
- A type conversion ``T(e)`` is a path expression.
|
||||
- A cast expression ``cast[T](e)`` is a path expression.
|
||||
- ``f(e, ...)`` is a path expression if ``f``'s return type is a view type.
|
||||
Because the view can only have been borrowed from ``e``, we then know
|
||||
that owner of ``f(e, ...)`` is ``e``.
|
||||
|
||||
Returns the owner of the path expression. Returns ``nil``
|
||||
if it is not a valid path expression.
|
||||
]#
|
||||
var n = node
|
||||
result = nil
|
||||
while true:
|
||||
case n.kind
|
||||
of nkSym:
|
||||
case n.sym.kind
|
||||
of skParam, skTemp, skResult, skForVar:
|
||||
if n.sym.owner == owner: result = n
|
||||
of skVar:
|
||||
if n.sym.owner == owner or sfThread in n.sym.flags: result = n
|
||||
of skLet, skConst:
|
||||
if n.sym.owner == owner or {sfThread, sfGlobal} * n.sym.flags != {}:
|
||||
result = n
|
||||
else:
|
||||
discard
|
||||
break
|
||||
of nkDotExpr, nkDerefExpr, nkBracketExpr, nkHiddenDeref,
|
||||
nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
|
||||
n = n[0]
|
||||
of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkCast,
|
||||
nkObjUpConv, nkObjDownConv:
|
||||
n = n.lastSon
|
||||
of nkCallKinds:
|
||||
if n.len > 1:
|
||||
if (n.typ != nil and classifyViewType(n.typ) != noView) or getMagic(n) == mSlice:
|
||||
n = n[1]
|
||||
else:
|
||||
break
|
||||
else:
|
||||
break
|
||||
else:
|
||||
break
|
||||
# borrowFromConstExpr(n) is correct here because we need 'node'
|
||||
# stripped off the path suffixes:
|
||||
if result == nil and borrowFromConstExpr(n):
|
||||
result = n
|
||||
|
||||
proc allRoots(n: PNode; result: var seq[PSym]; followDotExpr = true) =
|
||||
case n.kind
|
||||
of nkSym:
|
||||
@@ -385,13 +478,57 @@ proc pretendOwnsData(c: var Partitions, s: PSym) =
|
||||
const
|
||||
explainCursors = false
|
||||
|
||||
proc borrowFrom(c: var Partitions; dest: PSym; src: PNode) =
|
||||
const
|
||||
url = "; see https://nim-lang.github.io/Nim/manual_experimental.html#view-types-algorithm-path-expressions for details"
|
||||
|
||||
let s = pathExpr(src, c.owner)
|
||||
if s == nil:
|
||||
localError(c.config, src.info, "cannot borrow from " & $src & ", it is not a path expression; " & url)
|
||||
elif s.kind == nkSym:
|
||||
if dest.kind == skResult:
|
||||
if s.sym.kind != skParam or s.sym.position != 0:
|
||||
localError(c.config, src.info, "'result' must borrow from the first parameter")
|
||||
|
||||
let vid = variableId(c, dest)
|
||||
if vid >= 0:
|
||||
var sourceIdx = variableId(c, s.sym)
|
||||
if sourceIdx < 0:
|
||||
sourceIdx = c.s.len
|
||||
c.s.add VarIndex(con: Connection(kind: isEmptyRoot), sym: s.sym, reassignedTo: 0,
|
||||
aliveStart: MinTime, aliveEnd: MaxTime)
|
||||
|
||||
c.s[vid].borrowsFrom.add sourceIdx
|
||||
else:
|
||||
discard "a valid borrow location that is a deeply constant expression so we have nothing to track"
|
||||
|
||||
|
||||
proc borrowingCall(c: var Partitions; destType: PType; n: PNode; i: int) =
|
||||
let v = pathExpr(n[i], c.owner)
|
||||
if v.kind == nkSym:
|
||||
for j in i+1..<n.len:
|
||||
if getMagic(n[j]) == mSlice:
|
||||
borrowFrom(c, v.sym, n[j])
|
||||
else:
|
||||
localError(c.config, n[i].info, "cannot determine the target of the borrow")
|
||||
|
||||
proc trackBorrow(c: var Partitions; dest, src: PNode) =
|
||||
if dest.kind == nkSym:
|
||||
let vk = directViewType(dest.typ)
|
||||
if vk != noView:
|
||||
borrowFrom(c, dest.sym, src)
|
||||
|
||||
proc deps(c: var Partitions; dest, src: PNode) =
|
||||
if not c.performCursorInference:
|
||||
trackBorrow(c, dest, src)
|
||||
|
||||
var targets, sources: seq[PSym]
|
||||
allRoots(dest, targets)
|
||||
allRoots(src, sources)
|
||||
|
||||
proc wrap(t: PType): bool {.nimcall.} = t.kind in {tyRef, tyPtr}
|
||||
let destIsComplex = types.searchTypeFor(dest.typ, wrap) or isViewType(dest.typ)
|
||||
|
||||
let destIsComplex = types.searchTypeFor(dest.typ, wrap)
|
||||
|
||||
for t in targets:
|
||||
if dest.kind != nkSym and c.inNoSideEffectSection == 0:
|
||||
@@ -483,6 +620,13 @@ proc traverse(c: var Partitions; n: PNode) =
|
||||
for r in roots: potentialMutation(c, r, it.info)
|
||||
for r in roots: noCursor(c, r)
|
||||
|
||||
if not c.performCursorInference:
|
||||
# a call like 'result.add toOpenArray()' can also be a borrow
|
||||
# operation. We know 'paramType' is a tyVar and we really care if
|
||||
# 'paramType[0]' is still a view type, this is not a typo!
|
||||
if directViewType(paramType[0]) == noView and classifyViewType(paramType[0]) != noView:
|
||||
borrowingCall(c, paramType[0], n, i)
|
||||
|
||||
of nkAddr, nkHiddenAddr:
|
||||
traverse(c, n[0])
|
||||
when false:
|
||||
@@ -614,8 +758,8 @@ proc computeLiveRanges(c: var Partitions; n: PNode) =
|
||||
else:
|
||||
for child in n: computeLiveRanges(c, child)
|
||||
|
||||
proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partitions =
|
||||
result = Partitions(performCursorInference: cursorInference)
|
||||
proc computeGraphPartitions*(s: PSym; n: PNode; config: ConfigRef; cursorInference = false): Partitions =
|
||||
result = Partitions(performCursorInference: cursorInference, owner: s, config: config)
|
||||
if s.kind notin {skModule, skMacro}:
|
||||
let params = s.typ.n
|
||||
for i in 1..<params.len:
|
||||
@@ -624,8 +768,8 @@ proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partit
|
||||
registerResult(result, s.ast[resultPos])
|
||||
|
||||
computeLiveRanges(result, n)
|
||||
# resart the timer for the second pass:
|
||||
result.abstractTime = 0
|
||||
# restart the timer for the second pass:
|
||||
result.abstractTime = AbstractTime 0
|
||||
traverse(result, n)
|
||||
|
||||
proc dangerousMutation(g: MutationInfo; v: VarIndex): bool =
|
||||
@@ -653,14 +797,24 @@ proc cannotBorrow(config: ConfigRef; s: PSym; g: MutationInfo) =
|
||||
|
||||
proc checkBorrowedLocations*(par: var Partitions; body: PNode; config: ConfigRef) =
|
||||
for i in 0 ..< par.s.len:
|
||||
let s = par.s[i].sym
|
||||
if s.kind != skParam and isViewType(s.typ):
|
||||
let v = par.s[i].sym
|
||||
if v.kind != skParam and classifyViewType(v.typ) != noView:
|
||||
let rid = root(par, i)
|
||||
if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]):
|
||||
cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex])
|
||||
if rid >= 0:
|
||||
for b in par.s[rid].borrowsFrom:
|
||||
let sid = root(par, b)
|
||||
if sid >= 0:
|
||||
if par.s[sid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[sid].con.graphIndex], par.s[i]):
|
||||
cannotBorrow(config, v, par.graphs[par.s[sid].con.graphIndex])
|
||||
if par.s[sid].sym.kind != skParam and par.s[sid].aliveEnd < par.s[rid].aliveEnd:
|
||||
localError(config, v.info, "'" & v.name.s & "' borrows from location '" & par.s[sid].sym.name.s &
|
||||
"' which does not live long enough")
|
||||
|
||||
#if par.s[rid].con.kind == isRootOf and dangerousMutation(par.graphs[par.s[rid].con.graphIndex], par.s[i]):
|
||||
# cannotBorrow(config, s, par.graphs[par.s[rid].con.graphIndex])
|
||||
|
||||
proc computeCursors*(s: PSym; n: PNode; config: ConfigRef) =
|
||||
var par = computeGraphPartitions(s, n, true)
|
||||
var par = computeGraphPartitions(s, n, config, true)
|
||||
for i in 0 ..< par.s.len:
|
||||
let v = addr(par.s[i])
|
||||
if v.flags * {ownsData, preventCursor} == {} and v.sym.kind notin {skParam, skResult} and
|
||||
|
||||
@@ -655,7 +655,7 @@ Precedence level Operators First
|
||||
================ ======================================================= ================== ===============
|
||||
|
||||
|
||||
Whether an operator is used a prefix operator is also affected by preceding
|
||||
Whether an operator is used as a prefix operator is also affected by preceding
|
||||
whitespace (this parsing change was introduced with version 0.13.0):
|
||||
|
||||
.. code-block:: nim
|
||||
@@ -3275,16 +3275,16 @@ programming and are inherently unsafe.
|
||||
|
||||
.. code-block:: nim
|
||||
cast[int](x)
|
||||
|
||||
|
||||
The target type of a cast must be a concrete type, for instance, a target type
|
||||
that is a type class (which is non-concrete) would be invalid:
|
||||
|
||||
.. code-block:: nim
|
||||
type Foo = int or float
|
||||
var x = cast[Foo](1) # Error: cannot cast to a non concrete type: 'Foo'
|
||||
|
||||
|
||||
Type casts should not be confused with *type conversions,* as mentioned in the
|
||||
prior section. Unlike type conversions, a type cast cannot change the underlying
|
||||
prior section. Unlike type conversions, a type cast cannot change the underlying
|
||||
bit pattern of the data being casted (aside from that the size of the target type
|
||||
may differ from the source type). Casting resembles *type punning* in other
|
||||
languages or C++'s ``reinterpret_cast`` and ``bit_cast`` features.
|
||||
|
||||
@@ -1814,18 +1814,49 @@ For example:
|
||||
# an object reachable from 'n' is potentially mutated
|
||||
|
||||
|
||||
The algorithm behind this analysis is currently not documented.
|
||||
The algorithm behind this analysis is described in
|
||||
the `view types section <#view-types-algorithm>`_.
|
||||
|
||||
|
||||
View types
|
||||
==========
|
||||
|
||||
A view type is a type that contains one of the following types:
|
||||
**Note**: ``--experimental:views`` is more effective
|
||||
with ``--experimental:strictFuncs``.
|
||||
|
||||
A view type is a type that is or contains one of the following types:
|
||||
|
||||
- ``var T`` (mutable view into ``T``)
|
||||
- ``lent T`` (immutable view into ``T``)
|
||||
- ``openArray[T]`` (pair of (pointer to array of ``T``, size))
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
View1 = var int
|
||||
View2 = openArray[byte]
|
||||
View3 = lent string
|
||||
View4 = Table[openArray[char], int]
|
||||
|
||||
|
||||
Exceptions to this rule are types constructed via ``ptr`` or ``proc``.
|
||||
For example, the following types are **not** view types:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
type
|
||||
NotView1 = proc (x: openArray[int])
|
||||
NotView2 = ptr openArray[char]
|
||||
NotView3 = ptr array[4, var int]
|
||||
|
||||
|
||||
A *mutable* view type is a type that is or contains a ``var T`` type.
|
||||
An *immutable* view type is a view type that is not a mutable view type.
|
||||
|
||||
A *view* is a symbol (a let, var, const, etc.) that has a view type.
|
||||
|
||||
Since version 1.4 Nim allows view types to be used as local variables.
|
||||
This feature needs to be enabled via ``{.experimental: "views".}``.
|
||||
|
||||
@@ -1860,10 +1891,159 @@ For example:
|
||||
main(@[11, 22, 33])
|
||||
|
||||
|
||||
A local variable of a view type can borrow from a location
|
||||
derived from a parameter, another local variable, a global ``const`` or ``let``
|
||||
symbol or a thread-local ``var`` or ``let``.
|
||||
|
||||
If a view type is used as a return type, the location must borrow from the
|
||||
first parameter that is passed to the proc.
|
||||
Let ``p`` the proc that is analysed for the correctness of the borrow operation.
|
||||
|
||||
Let ``source`` be one of:
|
||||
|
||||
- A formal parameter of ``p``. Note that this does not cover parameters of
|
||||
inner procs.
|
||||
- The ``result`` symbol of ``p``.
|
||||
- A local ``var`` or ``let`` or ``const`` of ``p``. Note that this does
|
||||
not cover locals of inner procs.
|
||||
- A thread-local ``var`` or ``let``.
|
||||
- A global ``let`` or ``const``.
|
||||
- A constant arraq/seq/object/tuple constructor.
|
||||
|
||||
|
||||
Path expressions
|
||||
----------------
|
||||
|
||||
A location derived from ``source`` is then defined as a path expression that
|
||||
has ``source`` as the owner. A path expression ``e`` is defined recursively:
|
||||
|
||||
- ``source`` itself is a path expression.
|
||||
- Container access like ``e[i]`` is a path expression.
|
||||
- Tuple access ``e[0]`` is a path expression.
|
||||
- Object field access ``e.field`` is a path expression.
|
||||
- ``system.toOpenArray(e, ...)`` is a path expression.
|
||||
- Pointer dereference ``e[]`` is a path expression.
|
||||
- An address ``addr e``, ``unsafeAddr e`` is a path expression.
|
||||
- A type conversion ``T(e)`` is a path expression.
|
||||
- A cast expression ``cast[T](e)`` is a path expression.
|
||||
- ``f(e, ...)`` is a path expression if ``f``'s return type is a view type.
|
||||
Because the view can only have been borrowed from ``e``, we then know
|
||||
that owner of ``f(e, ...)`` is ``e``.
|
||||
|
||||
|
||||
If a view type is used as a return type, the location must borrow from a location
|
||||
that is derived from the first parameter that is passed to the proc.
|
||||
See https://nim-lang.org/docs/manual.html#procedures-var-return-type for
|
||||
details about how this is done for ``var T``.
|
||||
|
||||
The algorithm behind this analysis is currently not documented.
|
||||
A mutable view can borrow from a mutable location, an immutable view can borrow
|
||||
from both a mutable or an immutable location.
|
||||
|
||||
The *duration* of a borrow is the span of commands beginning from the assignment
|
||||
to the view and ending with the last usage of the view.
|
||||
|
||||
For the duration of the borrow operation, no mutations to the borrowed locations
|
||||
may be performed except via the potentially mutable view that borrowed from the
|
||||
location. The borrowed location is said to be *sealed* during the borrow.
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
{.experimental: "views".}
|
||||
|
||||
type
|
||||
Obj = object
|
||||
field: string
|
||||
|
||||
proc dangerous(s: var seq[Obj]) =
|
||||
let v: lent Obj = s[0] # seal 's'
|
||||
s.setLen 0 # prevented at compile-time because 's' is sealed.
|
||||
echo v.field
|
||||
|
||||
|
||||
The scope of the view does not matter:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc valid(s: var seq[Obj]) =
|
||||
let v: lent Obj = s[0] # begin of borrow
|
||||
echo v.field # end of borrow
|
||||
s.setLen 0 # valid because 'v' isn't used afterwards
|
||||
|
||||
|
||||
The analysis requires as much precision about mutations as is reasonably obtainable,
|
||||
so it is more effective with the experimental `strict funcs <#strict-funcs>`_
|
||||
feature. In other words ``--experimental:views`` works better
|
||||
with ``--experimental:strictFuncs``.
|
||||
|
||||
The analysis is currently control flow insensitive:
|
||||
|
||||
.. code-block:: nim
|
||||
|
||||
proc invalid(s: var seq[Obj]) =
|
||||
let v: lent Obj = s[0]
|
||||
if false:
|
||||
s.setLen 0
|
||||
echo v.field
|
||||
|
||||
In this example, the compiler assumes that ``s.setLen 0`` invalidates the
|
||||
borrow operation of ``v`` even though a human being can easily see that it
|
||||
will never do that at runtime.
|
||||
|
||||
|
||||
Reborrows
|
||||
---------
|
||||
|
||||
A view ``v`` can borrow from multiple different locations. However, the borrow
|
||||
is always the full span of ``v``'s lifetime and every location that is borrowed
|
||||
from is sealed during ``v``'s lifetime.
|
||||
|
||||
|
||||
Algorithm
|
||||
---------
|
||||
|
||||
The following section is an outline of the algorithm that the current implementation
|
||||
uses. The algorithm performs two traversals over the AST of the procedure or global
|
||||
section of code that uses a view variable. No fixpoint iterations are performed, the
|
||||
complexity of the analysis is O(N) where N is the number of nodes of the AST.
|
||||
|
||||
The first pass over the AST computes the lifetime of each local variable based on
|
||||
a notion of an "abstract time", in the implementation it's a simple integer that is
|
||||
incremented for every visited node.
|
||||
|
||||
In the second pass information about the underlying object "graphs" is computed.
|
||||
Let ``v`` be a parameter or a local variable. Let ``G(v)`` be the graph
|
||||
that ``v`` belongs to. A graph is defined by the set of variables that belong
|
||||
to the graph. Initially for all ``v``: ``G(v) = {v}``. Every variable can only
|
||||
be part of a single graph.
|
||||
|
||||
Assignments like ``a = b`` "connect" two variables, both variables end up in the
|
||||
same graph ``{a, b} = G(a) = G(b)``. Unfortunately, the pattern to look for is
|
||||
much more complex than that and can involve multiple assignment targets
|
||||
and sources::
|
||||
|
||||
f(x, y) = g(a, b)
|
||||
|
||||
connects ``x`` and ``y`` to ``a`` and ``b``: ``G(x) = G(y) = G(a) = G(b) = {x, y, a, b}``.
|
||||
A type based alias analysis rules out some of these combinations, for example
|
||||
a ``string`` value cannot possibly be connected to a ``seq[int]``.
|
||||
|
||||
A pattern like ``v[] = value`` or ``v.field = value`` marks ``G(v)`` as mutated.
|
||||
After the second pass a set of disjoint graphs was computed.
|
||||
|
||||
For strict functions it is then enforced that there is no graph that is both mutated
|
||||
and has an element that is an immutable parameter (that is a parameter that is not
|
||||
of type ``var T``).
|
||||
|
||||
For borrow checking a different set of checks is performed. Let ``v`` be the view
|
||||
and ``b`` the location that is borrowed from.
|
||||
|
||||
- The lifetime of ``v`` must not exceed ``b``'s lifetime. Note: The lifetime of
|
||||
a parameter is the complete proc body.
|
||||
- If ``v`` is a mutable view and ``v`` is used to actually mutate the
|
||||
borrowed location, then ``b`` has to be a mutable location.
|
||||
Note: If it is not actually used for mutation, borrowing a mutable view from an
|
||||
immutable location is allowed! This allows for many important idioms and will be
|
||||
justified in an upcoming RFC.
|
||||
- During ``v``'s lifetime, ``G(b)`` can only be modified by ``v`` (and only if
|
||||
``v`` is a mutable view).
|
||||
- If ``v`` is ``result`` then ``b`` has to be a location derived from the first
|
||||
formal parameter or from a constant location.
|
||||
- A view cannot be used for a read or a write access before it was assigned to.
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
discard """
|
||||
errormsg: "cannot borrow"
|
||||
nimout: '''tcannot_borrow.nim(16, 7) Error: cannot borrow meh; what it borrows from is potentially mutated
|
||||
tcannot_borrow.nim(17, 3) the mutation is here
|
||||
tcannot_borrow.nim(16, 7) is the statement that connected the mutation to the parameter'''
|
||||
line: 16
|
||||
nimout: '''tcannot_borrow.nim(21, 7) Error: cannot borrow meh; what it borrows from is potentially mutated
|
||||
tcannot_borrow.nim(22, 3) the mutation is here'''
|
||||
line: 21
|
||||
"""
|
||||
|
||||
|
||||
{.experimental: "views".}
|
||||
|
||||
type
|
||||
Foo = object
|
||||
field: string
|
||||
|
||||
proc valid(s: var seq[Foo]) =
|
||||
let v: lent Foo = s[0] # begin of borrow
|
||||
echo v.field # end of borrow
|
||||
s.setLen 0 # valid because 'v' isn't used afterwards
|
||||
|
||||
proc dangerous(s: var seq[Foo]) =
|
||||
let meh: lent Foo = s[0]
|
||||
s.setLen 0
|
||||
|
||||
9
tests/views/tmust_borrow_from_first_parameter.nim
Normal file
9
tests/views/tmust_borrow_from_first_parameter.nim
Normal file
@@ -0,0 +1,9 @@
|
||||
discard """
|
||||
errormsg: "'result' must borrow from the first parameter"
|
||||
line: 9
|
||||
"""
|
||||
|
||||
{.experimental: "views".}
|
||||
|
||||
proc p(a, b: openArray[char]): openArray[char] =
|
||||
result = b
|
||||
@@ -23,9 +23,8 @@ proc split*(s: string, seps: set[char] = Whitespace,
|
||||
while last < len(s) and s[last] notin seps:
|
||||
inc(last)
|
||||
if splits == 0: last = len(s)
|
||||
{.noSideEffect.}:
|
||||
result.add toOpenArray(s, first, last-1)
|
||||
result.add toOpenArray(s, first, last-1)
|
||||
result.add toOpenArray(s, first, last-1)
|
||||
result.add toOpenArray(s, first, last-1)
|
||||
if splits == 0: break
|
||||
dec(splits)
|
||||
inc(last)
|
||||
|
||||
@@ -4,6 +4,7 @@ discard """
|
||||
33
|
||||
3
|
||||
2
|
||||
3
|
||||
3'''
|
||||
targets: "c cpp"
|
||||
"""
|
||||
@@ -22,6 +23,6 @@ proc main(s: seq[int]) =
|
||||
take(x.toOpenArray(0, 1))
|
||||
let y = x
|
||||
take y
|
||||
|
||||
take x
|
||||
|
||||
main(@[11, 22, 33])
|
||||
|
||||
Reference in New Issue
Block a user