An optimizer for ARC (#14962)

* WIP: an optimizer for ARC
* do not optimize away destructors in 'finally' if unstructured control flow is involved
* optimized the optimizer
* minor code cleanup
* first steps to .cursor inference
* cursor inference: big steps to a working solution
* baby steps
* better .cursor inference
* new feature: expandArc for easy inspection of the AST after ARC transformations
* added topt_cursor test
* adapt tests
* cleanups, make tests green
* optimize common traversal patterns
* moved test case
* fixes .cursor inference so that npeg compiles once again
* cursor inference: more bugfixes

Co-authored-by: Clyybber <darkmine956@gmail.com>
This commit is contained in:
Andreas Rumpf 2020-07-15 23:00:06 +02:00 • committed by GitHub
commit c5358b0d4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 895 additions and 62 deletions

View file

@ -1,15 +1,7 @@
discard """
cmd: '''nim c --newruntime $file'''
output: '''copied
copied
2
destroyed
destroyed
copied
copied
2
destroyed
destroyed'''
output: '''2
2'''
"""
type

35
tests/arc/topt_cursor.nim Normal file
View file

@ -0,0 +1,35 @@
discard """
output: '''("string here", 80)'''
cmd: '''nim c --gc:arc --expandArc:main --hint:Performance:off $file'''
nimout: '''--expandArc: main
var
:tmpD
:tmpD_1
:tmpD_2
try:
var x = ("hi", 5)
x = if cond:
:tmpD = ("different", 54)
:tmpD else:
:tmpD_1 = ("string here", 80)
:tmpD_1
echo [
:tmpD_2 = `$`(x)
:tmpD_2]
finally:
`=destroy`(:tmpD_2)
-- end of expandArc ------------------------'''
"""
proc main(cond: bool) =
var x = ("hi", 5) # goal: computed as cursor
x = if cond:
("different", 54)
else:
("string here", 80)
echo x
main(false)

View file

@ -0,0 +1,37 @@
discard """
output: '''(repo: "", package: "meo", ext: "")'''
cmd: '''nim c --gc:arc --expandArc:newTarget --hint:Performance:off $file'''
nimout: '''--expandArc: newTarget
var
splat
:tmp
:tmp_1
:tmp_2
splat = splitFile(path)
:tmp = splat.dir
wasMoved(splat.dir)
:tmp_1 = splat.name
wasMoved(splat.name)
:tmp_2 = splat.ext
wasMoved(splat.ext)
result = (
let blitTmp = :tmp
blitTmp,
let blitTmp_1 = :tmp_1
blitTmp_1,
let blitTmp_2 = :tmp_2
blitTmp_2)
`=destroy`(splat)
-- end of expandArc ------------------------'''
"""
import os
type Target = tuple[repo, package, ext: string]
proc newTarget*(path: string): Target =
let splat = path.splitFile
result = (repo: splat.dir, package: splat.name, ext: splat.ext)
echo newTarget("meo")

View file

@ -0,0 +1,39 @@
discard """
output: ''''''
cmd: '''nim c --gc:arc --expandArc:traverse --hint:Performance:off $file'''
nimout: '''--expandArc: traverse
var it = root
block :tmp:
while (
not (it == nil)):
echo [it.s]
it = it.ri
var jt = root
block :tmp_1:
while (
not (jt == nil)):
let ri_1 = jt.ri
echo [jt.s]
jt = ri_1
-- end of expandArc ------------------------'''
"""
type
Node = ref object
le, ri: Node
s: string
proc traverse(root: Node) =
var it = root
while it != nil:
echo it.s
it = it.ri
var jt = root
while jt != nil:
let ri = jt.ri
echo jt.s
jt = ri
traverse(nil)

View file

@ -0,0 +1,92 @@
discard """
output: ''''''
cmd: '''nim c --gc:arc --expandArc:main --expandArc:tfor --hint:Performance:off $file'''
nimout: '''--expandArc: main
var
a
b
x
x = f()
if cond:
add(a):
let blitTmp = x
blitTmp
else:
add(b):
let blitTmp_1 = x
blitTmp_1
`=destroy`(b)
`=destroy`(a)
-- end of expandArc ------------------------
--expandArc: tfor
var
a
b
x
try:
x = f()
block :tmp:
var i
var i_1 = 0
block :tmp_1:
while i_1 < 4:
var :tmpD
i = i_1
if i == 2:
return
add(a):
wasMoved(:tmpD)
`=`(:tmpD, x)
:tmpD
inc i_1, 1
if cond:
add(a):
let blitTmp = x
wasMoved(x)
blitTmp
else:
add(b):
let blitTmp_1 = x
wasMoved(x)
blitTmp_1
finally:
`=destroy`(x)
`=destroy_1`(b)
`=destroy_1`(a)
-- end of expandArc ------------------------'''
"""
proc f(): seq[int] =
@[1, 2, 3]
proc main(cond: bool) =
var a, b: seq[seq[int]]
var x = f()
if cond:
a.add x
else:
b.add x
# all paths move 'x' so no wasMoved(x); destroy(x) pair should be left in the
# AST.
main(false)
proc tfor(cond: bool) =
var a, b: seq[seq[int]]
var x = f()
for i in 0 ..< 4:
if i == 2: return
a.add x
if cond:
a.add x
else:
b.add x
tfor(false)

View file

@ -138,16 +138,16 @@ proc search*[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; b: Box[D, RT]): seq[LT]
# a R*TREE proc
proc chooseSubtree[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; b: Box[D, RT]; level: int): H[M, D, RT, LT] =
assert level >= 0
var n = t.root
while n.level > level:
let nn = Node[M, D, RT, LT](n)
var it = t.root
while it.level > level:
let nn = Node[M, D, RT, LT](it)
var i0 = 0 # selected index
var minLoss = type(b[0].a).high
if n.level == 1: # childreen are leaves -- determine the minimum overlap costs
for i in 0 ..< n.numEntries:
if it.level == 1: # childreen are leaves -- determine the minimum overlap costs
for i in 0 ..< it.numEntries:
let nx = union(nn.a[i].b, b)
var loss = 0
for j in 0 ..< n.numEntries:
for j in 0 ..< it.numEntries:
if i == j: continue
loss += (overlap(nx, nn.a[j].b) - overlap(nn.a[i].b, nn.a[j].b)) # overlap (i, j) == (j, i), so maybe cache that?
var rep = loss < minLoss
@ -163,7 +163,7 @@ proc chooseSubtree[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; b: Box[D, RT]; lev
i0 = i
minLoss = loss
else:
for i in 0 ..< n.numEntries:
for i in 0 ..< it.numEntries:
let loss = enlargement(nn.a[i].b, b)
var rep = loss < minLoss
if loss == minLoss:
@ -174,8 +174,8 @@ proc chooseSubtree[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; b: Box[D, RT]; lev
if rep:
i0 = i
minLoss = loss
n = nn.a[i0].n
return n
it = nn.a[i0].n
return it
proc pickSeeds[M, D: Dim; RT, LT](t: RTree[M, D, RT, LT]; n: Node[M, D, RT, LT] | Leaf[M, D, RT, LT]; bx: Box[D, RT]): (int, int) =
var i0, j0: int

View file

@ -1,5 +1,6 @@
discard """
output: '''assign
output: '''
assign
destroy
destroy
5
@ -104,12 +105,12 @@ test()
#------------------------------------------------------------
# Issue #12883
type
type
TopObject = object
internal: UniquePtr[int]
proc deleteTop(p: ptr TopObject) =
if p != nil:
if p != nil:
`=destroy`(p[]) # !!! this operation used to leak the integer
deallocshared(p)
@ -117,12 +118,12 @@ proc createTop(): ptr TopObject =
result = cast[ptr TopObject](allocShared0(sizeof(TopObject)))
result.internal = newUniquePtr(1)
proc test2() =
proc test2() =
let x = createTop()
echo $x.internal
deleteTop(x)
echo "---------------"
echo "---------------"
echo "app begin"
test2()
echo "app end"

View file

@ -21,11 +21,13 @@ proc `=sink`(dest: var Foo, src: Foo) =
proc `=`(dest: var Foo, src: Foo) =
assign_counter.inc
proc createFoo(): Foo = Foo(boo: 0)
proc test(): auto =
var a, b: Foo
var a, b = createFoo()
return (a, b, Foo(boo: 5))
var (a, b, _) = test()
var (ag, bg, _) = test()
doAssert assign_counter == 0
doAssert sink_counter == 0

View file

@ -17,15 +17,20 @@ proc `=sink`(self: var Foo; other: Foo) =
proc `=destroy`(self: var Foo) = discard
template preventCursorInference(x) =
let p = unsafeAddr(x)
proc test(): Foo =
result = Foo()
let temp = result
preventCursorInference temp
doAssert temp.i > 0
return result
proc testB(): Foo =
result = Foo()
let temp = result
preventCursorInference temp
doAssert temp.i > 0
discard test()