Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

isolate happily compiles despite not being able to prove the absence of captured refs #19013

Closed
obadz opened this issue Oct 19, 2021 · 2 comments

Comments

@obadz
Copy link

obadz commented Oct 19, 2021

To set the stage, this works fine:

import std/isolation

type Z = ref object
  i: int

type A = object
  z: Z

func z_to_a(z: Z): A =
  result = A(z: z)

let z = Z(i: 3)
let a = isolate(z_to_a(z)) # Error: expression cannot be isolated: z_to_a(z)

Example

On the other hand, these two cases are unsafe:

Returning a subgraph:

import std/isolation

type Z = ref object
  i: int

type A = object
  z: Z

func a_to_z(a: A): Z =
  result = a.z

let a = A(z: Z(i: 3))
let z = isolate(a_to_z(a))

echo repr(z) # [value = ref 0x7fca2c6d9050 --> [i = 3]]
inc a.z.i
echo repr(z) # [value = ref 0x7fca2c6d9050 --> [i = 4]]

Overlapping subgraphs:

import std/isolation

type Z = ref object
  i: int

type A = object
  z: Z

type B = object
  z: Z

func a_to_b(a: A): B =
  result = B(z: a.z)

let a = A(z: Z(i: 3))
let b = isolate(a_to_b(a))

echo repr(b) # [value = [z = ref 0x7f8650b6b050 --> [i = 3]]]
inc a.z.i
echo repr(b) # [value = [z = ref 0x7f8650b6b050 --> [i = 4]]]

Current Output

Compiles fine and I expect would produce refcount corruption in a multi threaded environment, exposing the user to memory related crashes.

Expected Output

Refuse to compile with cannot be isolated.

Possible Solution

I think isolate should rule out any cases where the input & output types share a common ref subgraph.

Ideally an isolate_copy function would always succeed and create fresh copies of every ref whose count is > 1. In fact maybe the current function should be isolate_nocopy and the default behaviour should just be to copy the refs when the RC is > 1?

Additional Information

Mentionned in nim-lang/RFCs#244 (comment)

$ nim -v
Nim Compiler Version 1.4.8 [Linux: amd64]
@deech
Copy link
Contributor

deech commented Oct 19, 2021

I think the problem here is the isolation check passes alias analysis when it shouldn't. I was able get to the desired behavior by annotating the return types with lent. Maybe the solution is when an nnkCall node is passed to isolate it should pretend that the return type is annotated with lent but that might be too strict when the function does actually create a fresh ref. While this is just a quick-and-dirty suggestion the compiler's lifetime analysis machinery can statically track provenance so it shouldn't be hard to fix this generally.

@deech
Copy link
Contributor

deech commented Oct 19, 2021

Also I recommend that since Isolate is essentially a compiler primitive and the recommended way of protecting a data structure from mutation an error message that points to the specific part of the input that cannot be isolated is necessary.

Araq added a commit that referenced this issue Nov 9, 2021
@Araq Araq closed this as completed in b7c66ce Nov 9, 2021
narimiran pushed a commit that referenced this issue Nov 11, 2021
* fixes #19013 [backport:1.6]

* added test case

(cherry picked from commit b7c66ce)
PMunch pushed a commit to PMunch/Nim that referenced this issue Mar 28, 2022
* fixes nim-lang#19013 [backport:1.6]

* added test case
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants