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.43k stars 1.47k forks source link

segfault with ARC/ORC on large ropes #18131

Open n5m opened 3 years ago

n5m commented 3 years ago

Using --gc:arc or --gc:orc with ropes produces a segmentation fault at values of Limit >= 524k, but --gc:none and --gc:refc do not.

Example

# ropetest.nim

import std/ropes

const Limit {.intdefine.}: int = 1000
const Content {.strdefine.}: string = "abc"

var response: Rope
for i in 0 ..< Limit:
  response.add(Content)
echo $response.len

Current Output

Using -d:Limit=600_000

100% failure rate.

$ nim compile --gc:none --run -d:Limit=600_000 -d:Content:"abc" -d:release --hints:off --warnings:off ropetest.nim
1800000
$ nim compile --gc:arc --run -d:Limit=600_000 -d:Content:"abc" -d:release --hints:off --warnings:off ropetest.nim
1800000
Segmentation fault
Error: execution of an external program failed: '/home/user/ropetest '

Neither -d:Content:"" nor -d:useMalloc make any difference.

Using -d:Limit=500_000

At smaller values, however, both --gc:none and --gc:arc pass. 100% success rate.

$ nim compile --gc:none --run -d:Limit=500_000 -d:Content:"abc" -d:release --hints:off --warnings:off ropetest.nim
1500000
$ nim compile --gc:arc --run -d:Limit=500_000 -d:Content:"abc" -d:release --hints:off --warnings:off ropetest.nim
1500000

Smallest Limit

On my machine, any value of Limit below 523.6k always works, and any value of Limit above 524k always segfaults. Any Limit value within that range can go either way.

To help track this range down, this Python 3 script might be useful ```python import subprocess lo = 0 hi = 1_000_000 def worked(limit: int) -> bool: p = subprocess.run( [ 'nim', 'compile', '--gc:arc', '--run', '-d:useMalloc', '-d:Limit={}'.format(limit), '-d:Content="abc"', '-d:release', 'ropetest.nim', ], stderr=subprocess.DEVNULL ) try: p.check_returncode() except subprocess.CalledProcessError: return False else: return True while lo < hi - 1: print(lo, hi) mid = (lo + hi) // 2 if worked(mid): lo = mid else: hi = mid assert hi - 1 <= lo <= hi for i in range(hi+5, hi-5, -1): if worked(i): print('Final:', i) break ```

Expected Output

Since 600k copies of the string "abc" is less than 2Mb, I would expect this to never segfault.

Additional Information

Git hash cfe19247e856950f7facaf7adbc50bca8dec3990

$ nim -v
Nim Compiler Version 1.5.1 [Linux: amd64]
Compiled at 2021-05-30
Copyright (c) 2006-2021 by Andreas Rumpf

git hash: cfe19247e856950f7facaf7adbc50bca8dec3990
active boot switches: -d:release
Araq commented 3 years ago

Isn't this simply a stack overflow caused by the destructors recursive nature?

n5m commented 3 years ago

@Araq yes, I imagine you are correct. Does that mean this is not a bug?

Araq commented 3 years ago

It's an improvement that I really want to implement soon (tm) fwiw.