13
13
# # algorithm.
14
14
# # The used data structure is "union find" with path compression.
15
15
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
+
16
27
import ast, types, lineinfos, options, msgs, renderer
17
28
from trees import getMagic, whichPragma
18
29
from wordrecg import wNoSideEffect
26
37
27
38
VarFlag = enum
28
39
ownsData,
29
- preventCursor
40
+ preventCursor,
41
+ isReassigned
30
42
31
43
VarIndexKind = enum
32
44
isEmptyRoot,
@@ -266,44 +278,46 @@ proc allRoots(n: PNode; result: var seq[PSym]; followDotExpr = true) =
266
278
else :
267
279
discard " nothing to do"
268
280
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'.
270
284
case n.kind
271
285
of nkEmpty, nkCharLit.. nkNilLit:
272
286
# primitive literals including the empty are harmless:
273
287
discard
274
288
275
289
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv, nkCast, nkConv:
276
- analyseAsgn (c, dest, n[1 ])
290
+ destMightOwn (c, dest, n[1 ])
277
291
278
292
of nkIfStmt, nkIfExpr:
279
293
for i in 0 ..< n.len:
280
- analyseAsgn (c, dest, n[i].lastSon)
294
+ destMightOwn (c, dest, n[i].lastSon)
281
295
282
296
of nkCaseStmt:
283
297
for i in 1 ..< n.len:
284
- analyseAsgn (c, dest, n[i].lastSon)
298
+ destMightOwn (c, dest, n[i].lastSon)
285
299
286
300
of nkStmtList, nkStmtListExpr:
287
301
if n.len > 0 :
288
- analyseAsgn (c, dest, n[^ 1 ])
302
+ destMightOwn (c, dest, n[^ 1 ])
289
303
290
304
of nkClosure:
291
305
for i in 1 ..< n.len:
292
- analyseAsgn (c, dest, n[i])
306
+ destMightOwn (c, dest, n[i])
293
307
# you must destroy a closure:
294
308
dest.flags.incl ownsData
295
309
296
310
of nkObjConstr:
297
311
for i in 1 ..< n.len:
298
- analyseAsgn (c, dest, n[i])
312
+ destMightOwn (c, dest, n[i])
299
313
if hasDestructor (n.typ):
300
314
# you must destroy a ref object:
301
315
dest.flags.incl ownsData
302
316
303
317
of nkCurly, nkBracket, nkPar, nkTupleConstr:
304
318
inc c.inConstructor
305
319
for son in n:
306
- analyseAsgn (c, dest, son)
320
+ destMightOwn (c, dest, son)
307
321
dec c.inConstructor
308
322
if n.typ.skipTypes (abstractInst).kind == tySequence:
309
323
# you must destroy a sequence:
@@ -322,7 +336,7 @@ proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
322
336
323
337
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
324
338
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkAddr, nkHiddenAddr:
325
- analyseAsgn (c, dest, n[0 ])
339
+ destMightOwn (c, dest, n[0 ])
326
340
327
341
of nkCallKinds:
328
342
if hasDestructor (n.typ):
@@ -348,7 +362,7 @@ proc analyseAsgn(c: var Partitions; dest: var VarIndex; n: PNode) =
348
362
# list of dependencies via the 'hasDestructor' check for
349
363
# the root's symbol.
350
364
if hasDestructor (n[i].typ.skipTypes ({tyVar, tySink, tyLent, tyGenericInst, tyAlias})):
351
- analyseAsgn (c, dest, n[i])
365
+ destMightOwn (c, dest, n[i])
352
366
353
367
else :
354
368
# something we cannot handle:
@@ -389,24 +403,36 @@ proc deps(c: var Partitions; dest, src: PNode) =
389
403
if dest.kind == nkSym:
390
404
let vid = variableId (c, dest.sym)
391
405
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)
395
407
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.
398
412
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
406
421
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)
407
429
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}
410
436
411
437
proc traverse (c: var Partitions ; n: PNode ) =
412
438
inc c.abstractTime
@@ -418,11 +444,11 @@ proc traverse(c: var Partitions; n: PNode) =
418
444
if child.kind == nkVarTuple and last.kind in {nkPar, nkTupleConstr}:
419
445
if child.len- 2 != last.len: return
420
446
for i in 0 ..< child.len- 2 :
421
- registerVariable (c, child[i])
447
+ # registerVariable(c, child[i])
422
448
deps (c, child[i], last[i])
423
449
else :
424
450
for i in 0 ..< child.len- 2 :
425
- registerVariable (c, child[i])
451
+ # registerVariable(c, child[i])
426
452
deps (c, child[i], last)
427
453
of nkAsgn, nkFastAsgn:
428
454
traverse (c, n[0 ])
@@ -432,15 +458,8 @@ proc traverse(c: var Partitions; n: PNode) =
432
458
deps (c, n[0 ], n[1 ])
433
459
of nkSym:
434
460
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)
439
461
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:
444
463
dec c.abstractTime
445
464
discard " do not follow the construct"
446
465
of nkCallKinds:
@@ -514,6 +533,79 @@ proc traverse(c: var Partitions; n: PNode) =
514
533
else :
515
534
for child in n: traverse (c, child)
516
535
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
+
517
609
proc computeGraphPartitions * (s: PSym ; n: PNode ; cursorInference = false ): Partitions =
518
610
result = Partitions (performCursorInference: cursorInference)
519
611
if s.kind notin {skModule, skMacro}:
@@ -523,6 +615,9 @@ proc computeGraphPartitions*(s: PSym; n: PNode; cursorInference = false): Partit
523
615
if resultPos < s.ast.safeLen:
524
616
registerVariable (result , s.ast[resultPos])
525
617
618
+ computeLiveRanges (result , n)
619
+ # resart the timer for the second pass:
620
+ result .abstractTime = 0
526
621
traverse (result , n)
527
622
528
623
proc dangerousMutation (g: MutationInfo ; v: VarIndex ): bool =
@@ -560,7 +655,7 @@ proc computeCursors*(s: PSym; n: PNode; config: ConfigRef) =
560
655
var par = computeGraphPartitions (s, n, true )
561
656
for i in 0 ..< par.s.len:
562
657
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
564
659
v.sym.flags * {sfThread, sfGlobal} == {} and hasDestructor (v.sym.typ) and
565
660
v.sym.typ.skipTypes ({tyGenericInst, tyAlias}).kind != tyOwned:
566
661
let rid = root (par, i)
0 commit comments