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

[ARC] Memory zeroed after =sink even for self assignment #16185

Closed
Vindaar opened this issue Nov 29, 2020 · 3 comments · Fixed by #16195 or #16746
Closed

[ARC] Memory zeroed after =sink even for self assignment #16185

Vindaar opened this issue Nov 29, 2020 · 3 comments · Fixed by #16195 or #16746

Comments

@Vindaar
Copy link
Contributor

Vindaar commented Nov 29, 2020

Working on mratsim/Arraymancer#477 I've chased a weird segfault originating for self assignments of tensors, which only happens for tensors stored in objects.
I've reduced it to a relatively simple piece of code now, which mostly reproduces the bug. However, in my actual code the issue also happens if compiled without --gc:arc(sink is used in that case even for default GC. Does that make sense? Or should the =sink procs always be guarded by defined(gcDestructors)?).

Or maybe I still misunderstand how to use ARC?

Example

type
  CpuStorage*[T] = ref CpuStorageObj[T]
  CpuStorageObj[T] = object
    size*: int
    raw_buffer*: ptr UncheckedArray[T]
  Tensor[T] = object
    buf*: CpuStorage[T]
  TestObject = object
    x: Tensor[float]

proc `=destroy`[T](s: var CpuStorageObj[T]) =
  s.raw_buffer.deallocShared()
  s.size = 0

proc `=destroy`[T](t: var Tensor[T]) =
  `=destroy`(t.buf)

proc `=destroy`(t: var TestObject) =
  `=destroy`(t.x)

proc `=`[T](a: var CpuStorageObj[T]; b: CpuStorageObj[T]) {.error.}

#proc `=sink`[T](a: var CpuStorageObj[T], b: CpuStorageObj[T]) =
#  if a.raw_buffer != b.raw_buffer:
#    `=destroy`(a)
#    wasMoved(a)
#    a.raw_buffer = b.raw_buffer
#    a.size = b.size
#
#proc `=sink`[T](a: var Tensor[T], b: Tensor[T]) =
#  if a.buf != b.buf:
#    `=destroy`(a)
#    wasMoved(a)
#    `=sink`(a.buf, b.buf)

proc allocCpuStorage[T](s: var CpuStorage[T], size: int) =
  new(s)
  s.raw_buffer = cast[ptr UncheckedArray[T]](allocShared0(sizeof(T) * size))
  s.size = size

proc newTensor[T](size: int): Tensor[T] =
  allocCpuStorage(result.buf, size)

proc `[]`[T](t: Tensor[T], idx: int): T = t.buf.raw_buffer[idx]
proc `[]=`[T](t: Tensor[T], idx: int, val: T) = t.buf.raw_buffer[idx] = val

proc toTensor[T](s: seq[T]): Tensor[T] =
  result = newTensor[T](s.len)
  for i, x in s:
    result[i] = x

proc main() =
  var t: TestObject
  t.x = toTensor(@[1.0, 2, 3, 4])
  t.x = t.x
  # the following line holds true even if it shouldn't of course
  doAssert t.x.buf.isNil

main()

# does ``not`` happen if the code is at top level!
#var t: TestObject
#t.x = toTensor(@[1.0, 2, 3, 4])
#t.x = t.x
#doAssert t.x.buf.isNil

Current Output

Nothing, because the doAssert falsely holds.

Expected Output

doAssert should not hold, thus program should fail.

Additional Information

It seems to be a more complex version of #10689 to me.

The generated C code (using --gc:arc -d:danger):

	nimZeroMem((void*)(&T1_), sizeof(tyObject_Tensor__IQAwMnmu4uBKMjqXsM9adCA));
	toTensor__UpFt1bYA1ehyF1G4AabWcQ(colontmpD_, (&T1_));
	if (NIM_UNLIKELY(*nimErr_)) goto BeforeRet_;
	eqsink___UU2NWj0I19chxYozquBBXMA((&t.x), T1_);
	eqsink___UU2NWj0I19chxYozquBBXMA((&t.x), t.x);
	nimZeroMem((void*)(&t.x), sizeof(tyObject_Tensor__IQAwMnmu4uBKMjqXsM9adCA));
	{
		if (!!((t.x.buf == 0))) goto LA4_;
		failedAssertImpl__W9cjVocn1tjhW7p7xohJj6A(TM__zIQ3yoETjuVGvXrPQFKeSQ_3);
		if (NIM_UNLIKELY(*nimErr_)) goto BeforeRet_;
	}

The latter nimZeroMem shouldn't be there, because the eqsink before is a self assignment. It doesn't matter if I define the =sink in the code or not.

I just pulled the newest devel:

Nim Compiler Version 1.5.1 [Linux: amd64]
Compiled at 2020-11-29
Copyright (c) 2006-2020 by Andreas Rumpf

git hash: d29eddf92a1b8147c4709fd828f77152c1f3aab2
active boot switches: -d:release -d:danger
@cooldome
Copy link
Member

FYI, Expected way of writing this example:

type
  CpuStorage*[T] = ref CpuStorageObj[T]
  CpuStorageObj[T] = object
    size*: int
    raw_buffer*: ptr UncheckedArray[T]
  Tensor[T] = object
    buf*: CpuStorage[T]
  TestObject = object
    x: Tensor[float]

proc `=destroy`[T](s: var CpuStorageObj[T]) =
  if s.raw_buffer != nil:
    s.raw_buffer.deallocShared()
    s.size = 0
    s.raw_buffer = nil

proc `=`[T](a: var CpuStorageObj[T]; b: CpuStorageObj[T]) {.error.}

proc allocCpuStorage[T](s: var CpuStorage[T], size: int) =
  new(s)
  s.raw_buffer = cast[ptr UncheckedArray[T]](allocShared0(sizeof(T) * size))
  s.size = size

proc newTensor[T](size: int): Tensor[T] =
  allocCpuStorage(result.buf, size)

proc `[]`[T](t: Tensor[T], idx: int): T = t.buf.raw_buffer[idx]
proc `[]=`[T](t: Tensor[T], idx: int, val: T) = t.buf.raw_buffer[idx] = val

proc toTensor[T](s: seq[T]): Tensor[T] =
  result = newTensor[T](s.len)
  for i, x in s:
    result[i] = x

proc main() =
  var t: TestObject
  t.x = toTensor(@[1.0, 2, 3, 4])
  t.x = t.x
  # the following line holds true even if it shouldn't of course
  echo t.x.buf.isNil

main()

Bug, i will take a look

cooldome added a commit that referenced this issue Nov 30, 2020
@cooldome cooldome mentioned this issue Nov 30, 2020
@Vindaar
Copy link
Contributor Author

Vindaar commented Nov 30, 2020

Thanks for the super quick response and the example!

The additional =destroy I wrote are what's used by default anyway, right? And yes, completely forgot to nil the pointer.

Clyybber pushed a commit that referenced this issue Nov 30, 2020
* fix #16185

* fix test

* fix comment

* fix comment

* better approach
Araq added a commit that referenced this issue Nov 30, 2020
This reverts commit bb4b27a.
Araq added a commit that referenced this issue Nov 30, 2020
@Araq Araq reopened this Nov 30, 2020
@Vindaar
Copy link
Contributor Author

Vindaar commented Dec 4, 2020

I've just stumbled on a much simpler version of this:

type
  ValueKind* = enum
    VNull,
    VFloat,
    VString

  Value* = object
    case kind*: ValueKind
    of VString:
      str*: string
    of VFloat:
      fnum*: float
    of VNull:
      discard

proc `%~`*(v: string): Value =
  result = Value(kind: VString, str: v)

proc `%~`*(v: SomeFloat): Value =
  result = Value(kind: VFloat, fnum: v.float)

proc main =
  var p: tuple[x: Value, y: Value]
  p.x = %~ 1.2
  p.y = %~ 5.5
  echo p # all fine
  p.y = if p.y.kind == VNull: %~ 0.0 else: p.y
  echo p.y # p.y is going to be `VNull` after this self assignment

main()

If the string branch of the Value object is removed the problem also disappears (but I can add another int branch just fine!).

mildred pushed a commit to mildred/Nim that referenced this issue Jan 11, 2021
* fix nim-lang#16185

* fix test

* fix comment

* fix comment

* better approach
mildred pushed a commit to mildred/Nim that referenced this issue Jan 11, 2021
Clyybber pushed a commit to Clyybber/Nim that referenced this issue Jan 18, 2021
Clyybber pushed a commit to Clyybber/Nim that referenced this issue Jan 19, 2021
Araq pushed a commit that referenced this issue Jan 20, 2021
* fix #16185

* fix test

* fix comment

* fix comment

* better approach

* Add more tests and move sameLocation to injectdestructors

* Better and more strict sameLocation

* Small cleanup and preliminary spec clarification

* Fix

* Fix doc

* Expand test

Co-authored-by: Andrey R (cooldome) <[email protected]>
ardek66 pushed a commit to ardek66/Nim that referenced this issue Mar 26, 2021
* fix nim-lang#16185

* fix test

* fix comment

* fix comment

* better approach
ardek66 pushed a commit to ardek66/Nim that referenced this issue Mar 26, 2021
ardek66 pushed a commit to ardek66/Nim that referenced this issue Mar 26, 2021
* fix nim-lang#16185

* fix test

* fix comment

* fix comment

* better approach

* Add more tests and move sameLocation to injectdestructors

* Better and more strict sameLocation

* Small cleanup and preliminary spec clarification

* Fix

* Fix doc

* Expand test

Co-authored-by: Andrey R (cooldome) <[email protected]>
ringabout added a commit to ringabout/Arraymancer that referenced this issue Sep 14, 2022
mratsim pushed a commit to mratsim/Arraymancer that referenced this issue Sep 15, 2022
* test arrayamncer with ORC

* fixes shallowCopy

* one more fix

* one more fix

* nim-lang/Nim#16185 has been fixed

* it works since 1.6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants