mirror of
https://github.com/nim-lang/Nim.git
synced 2025-12-29 01:14:41 +00:00
This pr redefines the relation between lambda lifting and closureiter transformation. Key takeaways: - Lambdalifting now has less distinction between closureiters and regular closures. Namely instead of lifting _all_ closureiter variables, it lifts only those variables it would also lift for simple closure, i.e. those not owned by the closure. - It is now closureiter transformation's responsibility to lift all the locals that need lifting and are not lifted by lambdalifting. So now we lift only those locals that appear in more than one state. The rest remains on stack, yay! - Closureiter transformation always relies on the closure env param created by lambdalifting. Special care taken to make lambdalifting create it even in cases when it's "too early" to lift. - Environments created by lambdalifting will contain `:state` only for closureiters, whereas previously any closure env contained it. IMO this is a more reasonable approach as it simplifies not only lambdalifting, but transf too (e.g. freshVarsForClosureIters is now gone for good). I tried to organize the changes logically by commits, so it might be easier to review this on per commit basis. Some ugliness: - Adding lifting to closureiters transformation I had to repeat this matching of `return result = value` node. I tried to understand why it is needed, but that was just another rabbit hole, so I left it for another time. @Araq your input is welcome. - In the last commit I've reused currently undocumented `liftLocals` pragma for symbols so that closureiter transformation will forcefully lift those even if they don't require lifting otherwise. This is needed for [yasync](https://github.com/yglukhov/yasync) or else it will be very sad. Overall I'm quite happy with the results, I'm seeing some noticeable code size reductions in my projects. Heavy closureiter/async users, please give it a go.
530 lines
9.4 KiB
Nim
530 lines
9.4 KiB
Nim
discard """
|
|
matrix: "; --experimental:strictdefs"
|
|
targets: "c cpp"
|
|
"""
|
|
|
|
var closureIterResult = newSeq[int]()
|
|
|
|
proc checkpoint(arg: int) =
|
|
closureIterResult.add(arg)
|
|
|
|
type
|
|
TestError = object of CatchableError
|
|
AnotherError = object of CatchableError
|
|
|
|
proc testClosureIterAux(it: iterator(): int, exceptionExpected: bool, expectedResults: varargs[int]) =
|
|
closureIterResult.setLen(0)
|
|
|
|
var exceptionCaught = false
|
|
|
|
try:
|
|
for i in it():
|
|
closureIterResult.add(i)
|
|
except TestError:
|
|
exceptionCaught = true
|
|
|
|
if closureIterResult != @expectedResults or exceptionCaught != exceptionExpected:
|
|
if closureIterResult != @expectedResults:
|
|
echo "Expected: ", @expectedResults
|
|
echo "Actual: ", closureIterResult
|
|
if exceptionCaught != exceptionExpected:
|
|
echo "Expected exception: ", exceptionExpected
|
|
echo "Got exception: ", exceptionCaught
|
|
doAssert(false)
|
|
|
|
proc test(it: iterator(): int, expectedResults: varargs[int]) =
|
|
testClosureIterAux(it, false, expectedResults)
|
|
|
|
proc testExc(it: iterator(): int, expectedResults: varargs[int]) =
|
|
testClosureIterAux(it, true, expectedResults)
|
|
|
|
proc raiseTestError() =
|
|
raise newException(TestError, "Test exception!")
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
var i = 5
|
|
while i != 0:
|
|
yield i
|
|
if i == 3:
|
|
yield 123
|
|
dec i
|
|
|
|
test(it, 5, 4, 3, 123, 2, 1)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
yield 0
|
|
try:
|
|
checkpoint(1)
|
|
raiseTestError()
|
|
except TestError:
|
|
checkpoint(2)
|
|
yield 3
|
|
checkpoint(4)
|
|
finally:
|
|
checkpoint(5)
|
|
|
|
checkpoint(6)
|
|
|
|
test(it, 0, 1, 2, 3, 4, 5, 6)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
yield 0
|
|
try:
|
|
yield 1
|
|
checkpoint(2)
|
|
finally:
|
|
checkpoint(3)
|
|
yield 4
|
|
checkpoint(5)
|
|
yield 6
|
|
|
|
test(it, 0, 1, 2, 3, 4, 5, 6)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
yield 0
|
|
try:
|
|
yield 1
|
|
raiseTestError()
|
|
yield 2
|
|
finally:
|
|
checkpoint(3)
|
|
yield 4
|
|
checkpoint(5)
|
|
yield 6
|
|
|
|
testExc(it, 0, 1, 3, 4, 5, 6)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
try:
|
|
try:
|
|
raiseTestError()
|
|
except AnotherError:
|
|
yield 123
|
|
finally:
|
|
checkpoint(3)
|
|
finally:
|
|
checkpoint(4)
|
|
|
|
testExc(it, 3, 4)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
try:
|
|
yield 1
|
|
raiseTestError()
|
|
except AnotherError:
|
|
checkpoint(123)
|
|
finally:
|
|
checkpoint(2)
|
|
checkpoint(3)
|
|
|
|
testExc(it, 1, 2)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
try:
|
|
yield 0
|
|
try:
|
|
yield 1
|
|
try:
|
|
yield 2
|
|
raiseTestError()
|
|
except AnotherError:
|
|
yield 123
|
|
finally:
|
|
yield 3
|
|
except AnotherError:
|
|
yield 124
|
|
finally:
|
|
yield 4
|
|
checkpoint(1234)
|
|
except:
|
|
yield 5
|
|
checkpoint(6)
|
|
finally:
|
|
checkpoint(7)
|
|
yield 8
|
|
checkpoint(9)
|
|
|
|
test(it, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
try:
|
|
yield 0
|
|
return 2
|
|
finally:
|
|
checkpoint(1)
|
|
checkpoint(123)
|
|
|
|
test(it, 0, 1)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
try:
|
|
try:
|
|
yield 0
|
|
raiseTestError()
|
|
finally:
|
|
checkpoint(1)
|
|
except TestError:
|
|
yield 2
|
|
return
|
|
finally:
|
|
yield 3
|
|
|
|
checkpoint(123)
|
|
|
|
test(it, 0, 1, 2, 3)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
try:
|
|
try:
|
|
yield 0
|
|
raiseTestError()
|
|
finally:
|
|
return # Return in finally should stop exception propagation
|
|
except AnotherError:
|
|
yield 2
|
|
return
|
|
finally:
|
|
yield 3
|
|
checkpoint(123)
|
|
|
|
test(it, 0, 3)
|
|
|
|
block: # Yield in yield
|
|
iterator it(): int {.closure.} =
|
|
template foo(): int =
|
|
yield 1
|
|
2
|
|
|
|
for i in 0 .. 2:
|
|
checkpoint(0)
|
|
yield foo()
|
|
|
|
test(it, 0, 1, 2, 0, 1, 2, 0, 1, 2)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
let i = if true:
|
|
yield 0
|
|
1
|
|
else:
|
|
2
|
|
yield i
|
|
|
|
test(it, 0, 1)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
var foo = 123
|
|
let i = try:
|
|
yield 0
|
|
raiseTestError()
|
|
1
|
|
except TestError as e:
|
|
assert(e.msg == "Test exception!")
|
|
case foo
|
|
of 1:
|
|
yield 123
|
|
2
|
|
of 123:
|
|
yield 5
|
|
6
|
|
else:
|
|
7
|
|
yield i
|
|
|
|
test(it, 0, 5, 6)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
proc voidFoo(i1, i2, i3: int) =
|
|
checkpoint(i1)
|
|
checkpoint(i2)
|
|
checkpoint(i3)
|
|
|
|
proc foo(i1, i2, i3: int): int =
|
|
voidFoo(i1, i2, i3)
|
|
i3
|
|
|
|
proc bar(i1: int): int =
|
|
checkpoint(i1)
|
|
|
|
template tryexcept: int =
|
|
try:
|
|
yield 1
|
|
raiseTestError()
|
|
123
|
|
except TestError:
|
|
yield 2
|
|
checkpoint(3)
|
|
4
|
|
|
|
let e1 = true
|
|
|
|
template ifelse1: int =
|
|
if e1:
|
|
yield 10
|
|
11
|
|
else:
|
|
12
|
|
|
|
template ifelse2: int =
|
|
if ifelse1() == 12:
|
|
yield 20
|
|
21
|
|
else:
|
|
yield 22
|
|
23
|
|
|
|
let i = foo(bar(0), tryexcept, ifelse2)
|
|
discard foo(bar(0), tryexcept, ifelse2)
|
|
voidFoo(bar(0), tryexcept, ifelse2)
|
|
yield i
|
|
|
|
test(it,
|
|
|
|
# let i = foo(bar(0), tryexcept, ifelse2)
|
|
0, # bar(0)
|
|
1, 2, 3, # tryexcept
|
|
10, # ifelse1
|
|
22, # ifelse22
|
|
0, 4, 23, # foo
|
|
|
|
# discard foo(bar(0), tryexcept, ifelse2)
|
|
0, # bar(0)
|
|
1, 2, 3, # tryexcept
|
|
10, # ifelse1
|
|
22, # ifelse22
|
|
0, 4, 23, # foo
|
|
|
|
# voidFoo(bar(0), tryexcept, ifelse2)
|
|
0, # bar(0)
|
|
1, 2, 3, # tryexcept
|
|
10, # ifelse1
|
|
22, # ifelse22
|
|
0, 4, 23, # foo
|
|
|
|
23 # i
|
|
)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
checkpoint(0)
|
|
for i in 0 .. 1:
|
|
try:
|
|
yield 1
|
|
raiseTestError()
|
|
except TestError as e:
|
|
doAssert(e.msg == "Test exception!")
|
|
yield 2
|
|
except AnotherError:
|
|
yield 123
|
|
except:
|
|
yield 1234
|
|
finally:
|
|
yield 3
|
|
checkpoint(4)
|
|
yield 5
|
|
|
|
test(it, 0, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
var i = 5
|
|
template foo(): bool =
|
|
yield i
|
|
true
|
|
|
|
while foo():
|
|
dec i
|
|
if i == 0:
|
|
break
|
|
|
|
test(it, 5, 4, 3, 2, 1)
|
|
|
|
block: # Short cirquits
|
|
iterator it(): int {.closure.} =
|
|
template trueYield: bool =
|
|
yield 1
|
|
true
|
|
|
|
template falseYield: bool =
|
|
yield 0
|
|
false
|
|
|
|
if trueYield or falseYield:
|
|
discard falseYield and trueYield
|
|
|
|
if falseYield and trueYield:
|
|
checkpoint(123)
|
|
|
|
test(it, 1, 0, 0)
|
|
|
|
block: #7969
|
|
type
|
|
SomeObj = object
|
|
id: int
|
|
|
|
iterator it(): int {.closure.} =
|
|
template yieldAndSomeObj: SomeObj =
|
|
var s: SomeObj
|
|
s.id = 2
|
|
yield 1
|
|
s
|
|
|
|
checkpoint(yieldAndSomeObj().id)
|
|
|
|
var i = 5
|
|
case i
|
|
of 0:
|
|
checkpoint(123)
|
|
of 1, 2, 5:
|
|
checkpoint(3)
|
|
else:
|
|
checkpoint(123)
|
|
|
|
test(it, 1, 2, 3)
|
|
|
|
block: # yield in blockexpr
|
|
iterator it(): int {.closure.} =
|
|
yield(block:
|
|
checkpoint(1)
|
|
yield 2
|
|
3
|
|
)
|
|
|
|
test(it, 1, 2, 3)
|
|
|
|
block: #8851
|
|
type
|
|
Foo = ref object of RootObj
|
|
template someFoo(): Foo =
|
|
var f: Foo
|
|
yield 1
|
|
f
|
|
iterator it(): int {.closure.} =
|
|
var o: RootRef
|
|
o = someFoo()
|
|
|
|
test(it, 1)
|
|
|
|
block: # 8243
|
|
iterator it(): int {.closure.} =
|
|
template yieldAndSeq: seq[int] =
|
|
yield 1
|
|
@[123, 5, 123]
|
|
|
|
checkpoint(yieldAndSeq[1])
|
|
|
|
test(it, 1, 5)
|
|
|
|
block:
|
|
iterator it(): int {.closure.} =
|
|
template yieldAndSeq: seq[int] =
|
|
yield 1
|
|
@[123, 5, 123]
|
|
|
|
template yieldAndNum: int =
|
|
yield 2
|
|
1
|
|
|
|
checkpoint(yieldAndSeq[yieldAndNum])
|
|
|
|
test(it, 1, 2, 5)
|
|
|
|
block: #9694 - yield in ObjConstr
|
|
type Foo = object
|
|
a, b: int
|
|
|
|
template yieldAndNum: int =
|
|
yield 1
|
|
2
|
|
|
|
iterator it(): int {.closure.} =
|
|
let a = Foo(a: 5, b: yieldAndNum())
|
|
checkpoint(a.b)
|
|
|
|
test(it, 1, 2)
|
|
|
|
block: #9716
|
|
iterator it(): int {.closure.} =
|
|
var a = 0
|
|
for i in 1 .. 3:
|
|
var a: int # Make sure the "local" var is reset
|
|
var b: string # ditto
|
|
yield 1
|
|
a += 5
|
|
b &= "hello"
|
|
doAssert(a == 5)
|
|
doAssert(b == "hello")
|
|
test(it, 1, 1, 1)
|
|
|
|
block: # nnkChckRange
|
|
type Foo = distinct uint64
|
|
template yieldDistinct: Foo =
|
|
yield 2
|
|
Foo(0)
|
|
|
|
iterator it(): int {.closure.} =
|
|
yield 1
|
|
var a: int
|
|
a = int(yieldDistinct())
|
|
yield 3
|
|
|
|
test(it, 1, 2, 3)
|
|
|
|
block: #17849 - yield in case subject
|
|
template yieldInCase: int =
|
|
yield 2
|
|
3
|
|
|
|
iterator it(): int {.closure.} =
|
|
yield 1
|
|
case yieldInCase()
|
|
of 1: checkpoint(11)
|
|
of 3: checkpoint(13)
|
|
else: checkpoint(14)
|
|
yield 5
|
|
|
|
test(it, 1, 2, 13, 5)
|
|
|
|
block: # void iterator
|
|
iterator it() {.closure.} =
|
|
try:
|
|
yield
|
|
except:
|
|
discard
|
|
var a = it
|
|
|
|
block: # Locals present in only 1 state should be on the stack
|
|
proc checkOnStack(a: pointer, shouldBeOnStack: bool) =
|
|
# Quick and dirty way to check if a points to stack
|
|
var dummy = 0
|
|
let dummyAddr = addr dummy
|
|
let distance = abs(cast[int](dummyAddr) - cast[int](a))
|
|
const requiredDistance = 300
|
|
if shouldBeOnStack:
|
|
doAssert(distance <= requiredDistance, "a is not on stack, but should")
|
|
else:
|
|
doAssert(distance > requiredDistance, "a is on stack, but should not")
|
|
|
|
iterator it(): int {.closure.} =
|
|
var a = 1
|
|
var b = 2
|
|
var c {.liftLocals.} = 3
|
|
checkOnStack(addr a, true)
|
|
checkOnStack(addr b, false)
|
|
checkOnStack(addr c, false)
|
|
yield a
|
|
yield b
|
|
test(it, 1, 2)
|