Skip to content

Commit afc812a

Browse files
Araqmildred
authored andcommitted
* fixes nim-lang#15361; better cursor inference
1 parent 4b7880c commit afc812a

File tree

5 files changed

+196
-45
lines changed

5 files changed

+196
-45
lines changed

compiler/varpartitions.nim

+131-36
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313
## algorithm.
1414
## The used data structure is "union find" with path compression.
1515

16+
## We perform two passes over the AST:
17+
## - Pass one (``computeLiveRanges``): collect livetimes of local
18+
## variables and whether they are potentially re-assigned.
19+
## - Pass two (``traverse``): combine local variables to abstract "graphs".
20+
## Strict func checking: Ensure that graphs that are connected to
21+
## const parameters are not mutated.
22+
## Cursor inference: Ensure that potential cursors are not
23+
## borrowed from locations that are connected to a graph
24+
## that is mutated during the liveness of the cursor.
25+
## (We track all possible mutations of a graph.)
26+
1627
import ast, types, lineinfos, options, msgs, renderer
1728
from trees import getMagic, whichPragma
1829
from wordrecg import wNoSideEffect
@@ -26,7 +37,8 @@ type
2637

2738
VarFlag = enum
2839
ownsData,
29-
preventCursor
40+
preventCursor,
41+
isReassigned
3042

3143
VarIndexKind = enum
3244
isEmptyRoot,
@@ -266,44 +278,46 @@ proc allRoots(n: PNode; result: var seq[PSym]; followDotExpr = true) =
266278
else:
267279
discard "nothing to do"
268280

269-
proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
281+
proc destMightOwn(c: var Partitions; dest: var VarIndex; n: PNode) =
282+
## Analyse if 'n' is an expression that owns the data, if so mark 'dest'
283+
## with 'ownsData'.
270284
case n.kind
271285
of nkEmpty, nkCharLit..nkNilLit:
272286
# primitive literals including the empty are harmless:
273287
discard
274288

275289
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv:
276-
analyseAsgn(c, dest, n[1])
290+
destMightOwn(c, dest, n[1])
277291

278292
of nkIfStmt, nkIfExpr:
279293
for i in 0..<n.len:
280-
analyseAsgn(c, dest, n[i].lastSon)
294+
destMightOwn(c, dest, n[i].lastSon)
281295

282296
of nkCaseStmt:
283297
for i in 1..<n.len:
284-
analyseAsgn(c, dest, n[i].lastSon)
298+
destMightOwn(c, dest, n[i].lastSon)
285299

286300
of nkStmtList, nkStmtListExpr:
287301
if n.len > 0:
288-
analyseAsgn(c, dest, n[^1])
302+
destMightOwn(c, dest, n[^1])
289303

290304
of nkClosure:
291305
for i in 1..<n.len:
292-
analyseAsgn(c, dest, n[i])
306+
destMightOwn(c, dest, n[i])
293307
# you must destroy a closure:
294308
dest.flags.incl ownsData
295309

296310
of nkObjConstr:
297311
for i in 1..<n.len:
298-
analyseAsgn(c, dest, n[i])
312+
destMightOwn(c, dest, n[i])
299313
if hasDestructor(n.typ):
300314
# you must destroy a ref object:
301315
dest.flags.incl ownsData
302316

303317
of nkCurly, nkBracket, nkPar, nkTupleConstr:
304318
inc c.inConstructor
305319
for son in n:
306-
analyseAsgn(c, dest, son)
320+
destMightOwn(c, dest, son)
307321
dec c.inConstructor
308322
if n.typ.skipTypes(abstractInst).kind == tySequence:
309323
# you must destroy a sequence:
@@ -322,7 +336,7 @@ proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
322336

323337
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
324338
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
325-
analyseAsgn(c, dest, n[0])
339+
destMightOwn(c, dest, n[0])
326340

327341
of nkCallKinds:
328342
if hasDestructor(n.typ):
@@ -348,7 +362,7 @@ proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
348362
# list of dependencies via the 'hasDestructor' check for
349363
# the root's symbol.
350364
if hasDestructor(n[i].typ.skipTypes({tyVar, tySink, tyLent, tyGenericInst, tyAlias})):
351-
analyseAsgn(c, dest, n[i])
365+
destMightOwn(c, dest, n[i])
352366

353367
else:
354368
# something we cannot handle:
@@ -389,24 +403,36 @@ proc deps(c: var Partitions; dest, src: PNode) =
389403
if dest.kind == nkSym:
390404
let vid = variableId(c, dest.sym)
391405
if vid >= 0:
392-
analyseAsgn(c, c.s[vid], src)
393-
# do not borrow from a different local variable, this is easier
394-
# than tracking reassignments, consider 'var cursor = local; local = newNode()'
406+
destMightOwn(c, c.s[vid], src)
395407
if src.kind == nkSym:
396-
if (src.sym.kind in {skVar, skResult, skTemp} or
397-
(src.sym.kind in {skLet, skParam, skForVar} and hasDisabledAsgn(src.sym.typ))):
408+
let s = src.sym
409+
if {sfGlobal, sfThread} * s.flags != {} or hasDisabledAsgn(s.typ):
410+
# do not borrow from a global variable or from something with a
411+
# disabled assignment operator.
398412
c.s[vid].flags.incl preventCursor
399-
elif src.sym.kind in {skVar, skResult, skTemp, skLet, skForVar}:
400-
# XXX: we need to compute variable alive ranges before doing anything else:
401-
let srcid = variableId(c, src.sym)
402-
if srcid >= 0 and preventCursor in c.s[srcid].flags:
403-
# you cannot borrow from a local that lives shorter than 'vid':
404-
if c.s[srcid].aliveStart > c.s[vid].aliveStart or
405-
c.s[srcid].aliveEnd < c.s[vid].aliveEnd:
413+
when false: echo "A not a cursor: ", dest.sym, " ", s
414+
else:
415+
let srcid = variableId(c, s)
416+
if srcid >= 0:
417+
if s.kind notin {skResult, skParam} and (
418+
c.s[srcid].aliveEnd < c.s[vid].aliveEnd):
419+
# you cannot borrow from a local that lives shorter than 'vid':
420+
when false: echo "B not a cursor ", dest.sym, " ", c.s[srcid].aliveEnd, " ", c.s[vid].aliveEnd
406421
c.s[vid].flags.incl preventCursor
422+
elif {isReassigned, preventCursor} * c.s[srcid].flags != {}:
423+
# you cannot borrow from something that is re-assigned:
424+
when false: echo "C not a cursor ", dest.sym, " ", c.s[srcid].flags
425+
c.s[vid].flags.incl preventCursor
426+
427+
#if src.kind == nkSym and hasDestructor(src.typ):
428+
# rhsIsSink(c, src)
407429

408-
if src.kind == nkSym and hasDestructor(src.typ):
409-
rhsIsSink(c, src)
430+
const
431+
nodesToIgnoreSet = {nkNone..pred(nkSym), succ(nkSym)..nkNilLit,
432+
nkTypeSection, nkProcDef, nkConverterDef,
433+
nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
434+
nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
435+
nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr}
410436

411437
proc traverse(c: var Partitions; n: PNode) =
412438
inc c.abstractTime
@@ -418,11 +444,11 @@ proc traverse(c: var Partitions; n: PNode) =
418444
if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
419445
if child.len-2 != last.len: return
420446
for i in 0..<child.len-2:
421-
registerVariable(c, child[i])
447+
#registerVariable(c, child[i])
422448
deps(c, child[i], last[i])
423449
else:
424450
for i in 0..<child.len-2:
425-
registerVariable(c, child[i])
451+
#registerVariable(c, child[i])
426452
deps(c, child[i], last)
427453
of nkAsgn, nkFastAsgn:
428454
traverse(c, n[0])
@@ -432,15 +458,8 @@ proc traverse(c: var Partitions; n: PNode) =
432458
deps(c, n[0], n[1])
433459
of nkSym:
434460
dec c.abstractTime
435-
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
436-
let id = variableId(c, n.sym)
437-
if id >= 0:
438-
c.s[id].aliveEnd = max(c.s[id].aliveEnd, c.abstractTime)
439461

440-
of nkNone..pred(nkSym), succ(nkSym)..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
441-
nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
442-
nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
443-
nkExportStmt, nkPragma, nkCommentStmt, nkBreakState, nkTypeOfExpr:
462+
of nodesToIgnoreSet:
444463
dec c.abstractTime
445464
discard "do not follow the construct"
446465
of nkCallKinds:
@@ -514,6 +533,79 @@ proc traverse(c: var Partitions; n: PNode) =
514533
else:
515534
for child in n: traverse(c, child)
516535

536+
proc computeLiveRanges(c: var Partitions; n: PNode) =
537+
# first pass: Compute live ranges for locals.
538+
# **Watch out!** We must traverse the tree like 'traverse' does
539+
# so that the 'c.abstractTime' is consistent.
540+
inc c.abstractTime
541+
case n.kind
542+
of nkLetSection, nkVarSection:
543+
for child in n:
544+
let last = lastSon(child)
545+
computeLiveRanges(c, last)
546+
if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
547+
if child.len-2 != last.len: return
548+
for i in 0..<child.len-2:
549+
registerVariable(c, child[i])
550+
#deps(c, child[i], last[i])
551+
else:
552+
for i in 0..<child.len-2:
553+
registerVariable(c, child[i])
554+
#deps(c, child[i], last)
555+
556+
of nkAsgn, nkFastAsgn:
557+
computeLiveRanges(c, n[0])
558+
computeLiveRanges(c, n[1])
559+
if n[0].kind == nkSym:
560+
let vid = variableId(c, n[0].sym)
561+
if vid >= 0:
562+
c.s[vid].flags.incl isReassigned
563+
564+
of nkSym:
565+
dec c.abstractTime
566+
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
567+
let id = variableId(c, n.sym)
568+
if id >= 0:
569+
c.s[id].aliveEnd = max(c.s[id].aliveEnd, c.abstractTime)
570+
571+
of nodesToIgnoreSet:
572+
dec c.abstractTime
573+
discard "do not follow the construct"
574+
of nkCallKinds:
575+
for child in n: computeLiveRanges(c, child)
576+
577+
let parameters = n[0].typ
578+
let L = if parameters != nil: parameters.len else: 0
579+
580+
for i in 1..<n.len:
581+
let it = n[i]
582+
if it.kind == nkSym and i < L:
583+
let paramType = parameters[i].skipTypes({tyGenericInst, tyAlias})
584+
if not paramType.isCompileTimeOnly and paramType.kind == tyVar:
585+
let vid = variableId(c, it.sym)
586+
if vid >= 0:
587+
c.s[vid].flags.incl isReassigned
588+
589+
of nkAddr, nkHiddenAddr:
590+
computeLiveRanges(c, n[0])
591+
if n[0].kind == nkSym:
592+
let vid = variableId(c, n[0].sym)
593+
if vid >= 0:
594+
c.s[vid].flags.incl preventCursor
595+
596+
of nkPragmaBlock:
597+
computeLiveRanges(c, n.lastSon)
598+
of nkWhileStmt, nkForStmt, nkParForStmt:
599+
for child in n: computeLiveRanges(c, child)
600+
# analyse loops twice so that 'abstractTime' suffices to detect cases
601+
# like:
602+
# while cond:
603+
# mutate(graph)
604+
# connect(graph, cursorVar)
605+
for child in n: computeLiveRanges(c, child)
606+
else:
607+
for child in n: computeLiveRanges(c, child)
608+
517609
proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partitions =
518610
result = Partitions(performCursorInference: cursorInference)
519611
if s.kind notin {skModule, skMacro}:
@@ -523,6 +615,9 @@ proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partit
523615
if resultPos < s.ast.safeLen:
524616
registerVariable(result, s.ast[resultPos])
525617

618+
computeLiveRanges(result, n)
619+
# resart the timer for the second pass:
620+
result.abstractTime = 0
526621
traverse(result, n)
527622

528623
proc dangerousMutation(g: MutationInfo; v: VarIndex): bool =
@@ -560,7 +655,7 @@ proc computeCursors*(s: PSym; n: PNode; config: ConfigRef) =
560655
var par = computeGraphPartitions(s, n, true)
561656
for i in 0 ..< par.s.len:
562657
let v = addr(par.s[i])
563-
if v.flags == {} and v.sym.kind notin {skParam, skResult} and
658+
if v.flags * {ownsData, preventCursor} == {} and v.sym.kind notin {skParam, skResult} and
564659
v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor(v.sym.typ) and
565660
v.sym.typ.skipTypes({tyGenericInst, tyAlias}).kind != tyOwned:
566661
let rid = root(par, i)

tests/arc/top_no_cursor2.nim

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
discard """
2+
output: '''true
3+
true
4+
true
5+
true
6+
true'''
7+
cmd: "nim c --gc:arc $file"
8+
"""
9+
# bug #15361
10+
11+
type
12+
ErrorNodeKind = enum Branch, Leaf
13+
Error = ref object
14+
case kind: ErrorNodeKind
15+
of Branch:
16+
left: Error
17+
right: Error
18+
of Leaf:
19+
leafError: string
20+
input: string
21+
22+
proc ret(input: string, lefterr, righterr: Error): Error =
23+
result = Error(kind: Branch, left: lefterr, right: righterr, input: input)
24+
25+
proc parser() =
26+
var rerrors: Error
27+
let lerrors = Error(
28+
kind: Leaf,
29+
leafError: "first error",
30+
input: "123 ;"
31+
)
32+
# If you remove "block" - everything works
33+
block:
34+
let rresult = Error(
35+
kind: Leaf,
36+
leafError: "second error",
37+
input: ";"
38+
)
39+
# this assignment is needed too
40+
rerrors = rresult
41+
42+
# Returns Error(kind: Branch, left: lerrors, right: rerrors, input: "some val")
43+
# needs to be a proc call for some reason, can't inline the result
44+
var data = ret(input = "some val", lefterr = lerrors, righterr = rerrors)
45+
46+
echo data.left.leafError == "first error"
47+
echo data.left.input == "123 ;"
48+
# stacktrace shows this line
49+
echo data.right.leafError == "second error"
50+
echo data.right.input == ";"
51+
echo data.input == "some val"
52+
53+
parser()

tests/arc/topt_no_cursor.nim

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ _ = (
5252
blitTmp, ";")
5353
lvalue = _[0]
5454
lnext_cursor = _[1]
55-
`=sink`(result.value, lvalue)
55+
`=sink`(result.value, move lvalue)
5656
-- end of expandArc ------------------------
5757
--expandArc: tt
5858
@@ -148,7 +148,7 @@ proc p1(): Maybe =
148148
var lnext: string
149149
(lvalue, lnext) = (lresult, ";")
150150

151-
result.value = lvalue
151+
result.value = move lvalue
152152

153153
proc tissue15130 =
154154
doAssert p1().value == @[123]

tests/destructor/tdestructor3.nim

+6-5
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,18 @@ joinable: false
2323
type T = object
2424

2525
proc `=`(lhs: var T, rhs: T) =
26-
echo "assign"
26+
echo "assign"
2727

2828
proc `=destroy`(v: var T) =
29-
echo "destroy"
29+
echo "destroy"
3030

3131
proc use(x: T) = discard
3232

3333
proc usedToBeBlock =
34-
var v1 : T
35-
var v2 : T = v1
36-
use v1
34+
var v1 = T()
35+
var v2: T = v1
36+
discard addr(v2) # prevent cursorfication
37+
use v1
3738

3839
usedToBeBlock()
3940

tests/destructor/tmatrix.nim

+4-2
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,16 @@ proc info =
9696
allocCount = 0
9797
deallocCount = 0
9898

99+
proc copy(a: Matrix): Matrix = a
100+
99101
proc test1 =
100102
var a = matrix(5, 5, 1.0)
101-
var b = a
103+
var b = copy a
102104
var c = a + b
103105

104106
proc test2 =
105107
var a = matrix(5, 5, 1.0)
106-
var b = a
108+
var b = copy a
107109
var c = -a
108110

109111
proc test3 =

0 commit comments

Comments
 (0)