Nim icon indicating copy to clipboard operation
Nim copied to clipboard

Move analysis missing in closure iterator

Open arnetheduck opened this issue 1 month ago • 2 comments

Nim Version

2.2.4, 86bbc73b3ab281ed6f57da88d6cf05e899714c13

Description

In ORC, the following snippet does not compile, though it should.

In refc, the snippet does compile but copies NoCopy even though it should move it. In fact, 5 (!) copies of "test" happen on its journey to the echo.

type NoCopy = object
  v: string
proc `=copy`(a: var NoCopy, b: NoCopy) {.error.}

proc sinkMe(v: sink NoCopy) =
  echo v.v

proc iter(v: sink NoCopy): iterator(): int =
  iterator(): int =
    yield 1
    sinkMe(v)

proc main() =
  let vvv = iter(NoCopy(v: "test"))
  for i in vvv():
    echo i

main()

Current Output

testit.nim(13, 12) Error: '=dup' is not available for type <sink NoCopy>, which is inferred from unavailable '=copy'; requires a copy because it's not the last read of ':envP.`:up`.v0'; routine: :anonymous

Expected Output


Known Workarounds

No response

Additional Information

No response

arnetheduck avatar Dec 04 '25 18:12 arnetheduck

expansions:

while true:
  block :stateLoop:
    case :envP.`:state`
    of 0:
      :envP.`:state` = 1
      return 1
    of 1:
      sinkMe(:envP.`:up`.v0)
      :envP.`:state` = -1
      break :stateLoop
    else:
      return
CFG:
L0:                                                  
    0 use   :envP.`:state` #21                       
    1 fork  L5             2 def   :envP.`:state` #21
    3 goto  L16            4 goto  L11L5:            
    5 fork  L10            6 use   :envP.`:up`.v0 #23
    7 def   :envP.`:state` #21                       
    8 goto  L12            9 goto  L11L10:           
    10 goto L17L11:                                  
    11 goto L13L12:                                  
    12 goto L13L13:                                  
    13 loop L0             14 goto L15L15:           
    15 goto L18L16:                                  
    16 goto L18L17:                                  
    17 goto L18L18: End 

move analysis doesn't seem to think of this as a last read

ringabout avatar Dec 05 '25 12:12 ringabout

Something to be careful about is that in the case of an iterator, the iterator steps through its states in a controlled manner and reaches an end (usually - similar to the same code in a non-iterator, the point being that it can only go through the body "once" with any one particular closure environment) - a closure procedure on the other hand reuses its closure environment over and over so the analysis is different - it means, among other things, that the code is not correct for:

type NoCopy = object
  v: string
proc `=copy`(a: var NoCopy, b: NoCopy) {.error.}

proc sinkMe(v: sink NoCopy) =
  echo v.v

proc p(v: sink NoCopy): proc(): int =
  proc() int =
    sinkMe(v) # should error here
    1

proc main() =
  let vvv = p()(NoCopy(v: "test"))
  echo vvv()
  echo vvv() # Can't move v because called might do this

main()

arnetheduck avatar Dec 05 '25 13:12 arnetheduck