nim-lang / Nim

Nim is a statically typed compiled systems programming language. It combines successful concepts from mature languages like Python, Ada and Modula. Its design focuses on efficiency, expressiveness, and elegance (in that order of priority).
https://nim-lang.org
Other
16.6k stars 1.47k forks source link

Clang backend in release mode produces code that seg faults for object variant... #10206

Open GordonBGood opened 5 years ago

GordonBGood commented 5 years ago

The problem

TLDR; the following code compiles and runs both in debug and release modes with the gcc backend, and is fine with the clang backend in debug mode but crashes with a seg fault with clang and release mode.

Environment: Windows 10 64-bit, updated latest build up to the October update. Nim version 0.19 64-bit. Clang version 7.0.0 64-bit installed with MSYS2 64-bit.

Synopsis: I have been working with building lazy lists in Nim for years, but decided to convert the code to use object variants rather than testing for nil, as it logically seemed to make sense in the use case. The code comments pretty well say it all:

Example

## ObjectVariantBugNim.nim
## compile with: nim -d:release c -r Eratosthenes
## compiling with clang and releaee it crashes with seg fault
## with clang debug or with gcc in either mode it doesn't...

import sugar

## define a general purpose lazy list and iterator
## NOT thread safe; needs a Mutex gate to make it so...
type
  Thunk[T] = () -> T # {.closure.} # I hope
  LazyState = enum
    lsUnevaluated, # unevaluated containing a Thunk[T]
    lsEvaluated    # when evaluated contains the T value
  Lazy[T] = object
    case kind: LazyState
    of lsUnevaluated: thunk: Thunk[T] # fully defined here
    of lsEvaluated: value: T

## testing Lazy

type
  Test = ref object of RootObj # if plain object, must be of RootObj or fails,  
    hidden: int # ref object even of RootObj always fails with clang/release

let lazy0 = Lazy[Test](kind: lsEvaluated, value: Test(hidden: 42))
echo lazy0.value.hidden # doesn't work with a ref or non-ref object type

## let lazy1 = Lazy[Test](kind: lsUnevaluated, thunk: proc (): Test = Test(hidden: 37))
## echo lazy1.thunk().hidden # also doesn't work

#### The problem code is above

#### The following code shows how it is used to build a lazy list if it worked!
type
  LazyList[T] = ref object
    head: T
    tail: Lazy[LazyList[T]]

proc lazylist[T](hd: T,
                 thnk: Thunk[LazyList[T]]): LazyList[T] = # factory constructor
  LazyList[T](head: hd,
              tail: Lazy[LazyList[T]](kind: lsUnevaluated, thunk: thnk))
proc rest[T](lzylst: var LazyList[T]): LazyList[T] =
  case lzylst.tail.kind
  of lsEvaluated: lzylst.tail.value
  of lsUnevaluated:
    let v = lzylst.tail.thunk()
    lzylst.tail = Lazy[LazyList[T]](kind: lsEvaluated, value: v)
    v
iterator lazylistIter[T](lzylst: LazyList[T]): T {.closure.} =
  var ll = lzylst
  while ll != nil: yield ll.head; ll = ll.rest()

#[] # # testing lazy list

proc numbers(): LazyList[int] =
  proc nmbrs(n: int): LazyList[int] =
    lazylist(n, () => nmbrs(n + 1))
  nmbrs(1)

let nums = numbers()
for n in nums.lazylistIter:
  if n >= 1000000: echo n; break
#]#

Current Output

Error reported on crash: SIGSEGV: Illegal storage access. (Attempt to read from nil?)

The problem is entirely within the Lazy object variant, as it can even be created separately from the LazyList container, as in the given test lines for Lazy, which won't run when compiled with clang in release mode even with the later lines commented out.

Expected Output

Expected output is just to print "42" to the console.

Work around

The work around has been to go back to a version of the lazy list using nil/not nil or true/false testing for whether the lazy value has been evaluated yet, which works fine just not as elegantly.

The reason for using clang as a back end is that it compiles a little faster, as the quality of the generated code is generally good from either gcc or clang.

GordonBGood commented 5 years ago

I think this is likely due to some interaction with the version of Clang used: version 7.0.0 (possibly only the MSYS2 version on windows?) but it might be helpful to know why and whether it will carry forward to newer versions...