EDIT: This is the smallest version that produces the crash. I'm going to fill-in an issue on github with it:
type
StringValue*[LEN: static[Natural]] = array[LEN+Natural(2),char]
StringValue16* = StringValue[14]
I'm trying to create a small module that represents "strings" as "values" rather than "ref objects". I had this working for a "fixed size" type (254 chars + "0" + "length field"), and now tried to define the size as a generic parameter.
First, I had issues trying to get this to compile:
type
StringValue*[LEN: Natural] = distinct array[LEN+2,char]
But the compiler refuses to do "LEN+2". I even tried to define a compile-time proc to add Naturals, but it still would not compile (did not seem to see my + proc). So what I then tried, was to replace "[LEN: Natural]" with "[LEN: static[Natural]]", and now the compiler crashes.
Here the stringvalue module, with no test code. It seems to compile.
# Module: stringvalue
## This module can be used to pass small "strings" across threads, or store
## them globally, without having to worry about the local GC (as Nim strings
## are local GCed objects). The "strings" are meant to be used as "values",
## rather than "reference objects", but can also be allocated on the shared
## heap.
when isMainModule:
echo("COMPILING StringValue ...")
import hashes
export hash, `==`
proc c_strcmp(a, b: cstring): cint {.
importc: "strcmp", header: "<string.h>", noSideEffect.}
type
StringValue*[LEN: static[Natural]] = distinct array[LEN+Natural(2),char]
## Represents a "string value" of up to 254 characters (excluding
## terminating '\0' and length).
proc cstr*[LEN: static[Natural]](sv: var StringValue[LEN]): cstring {.inline, noSideEffect.} =
## Returns the 'raw' cstring of the StringValue
result = cast[cstring](addr sv)
proc `[]`*[LEN: static[Natural],I: Ordinal](sv: var StringValue[LEN]; i: I): char {.inline, noSideEffect.} =
## Returns a char of the StringValue
cast[ptr char](cast[ByteAddress](addr sv) +% i * sizeof(char))[]
proc `[]`*[LEN: static[Natural],I: Ordinal](sv: StringValue[LEN]; i: I): char {.inline, noSideEffect.} =
## Returns a char of the StringValue
cast[ptr char](cast[ByteAddress](unsafeAddr sv) +% i * sizeof(char))[]
proc `[]=`*[LEN: static[Natural],I: Ordinal](sv: var StringValue[LEN]; i: I; c: char) {.inline, noSideEffect.} =
## Returns a char of the StringValue
cast[ptr char](cast[ByteAddress](addr sv) +% i * sizeof(char))[] = c
proc len*[LEN: static[Natural]](sv: var StringValue[LEN]): int {.inline, noSideEffect.} =
## Returns the len of the StringValue
int(uint8(sv[LEN+1]))
proc len*[LEN: static[Natural]](sv: StringValue[LEN]): int {.inline, noSideEffect.} =
## Returns the len of the StringValue
int(uint8(sv[LEN+1]))
proc `$`*[LEN: static[Natural]](sv: var StringValue[LEN]): string {.inline.} =
## Returns the string representation of the StringValue
result = $sv.cstr
proc `$`*[LEN: static[Natural]](sv: StringValue[LEN]): string {.inline.} =
## Returns the string representation of the StringValue
result = $sv.cstr
proc hash*[LEN: static[Natural]](sv: var StringValue[LEN]): Hash {.inline, noSideEffect.} =
## Returns the hash of the StringValue
result = hash($sv)
proc hash*[LEN: static[Natural]](sv: StringValue[LEN]): Hash {.inline, noSideEffect.} =
## Returns the hash of the StringValue
result = hash($sv)
proc `==`*[LEN: static[Natural]](a, b: var StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares NoStrings
(a.len == b.len) and (c_strcmp(a.cstr, b.cstr) == 0)
proc `==`*[LEN: static[Natural]](a, b: StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares NoStrings
result = false
if a.len == b.len:
result = (c_strcmp(a.cstr, b.cstr) == 0)
proc `==`*[LEN: static[Natural]](sv: var StringValue[LEN], cs: cstring): bool {.inline, noSideEffect.} =
## Compares a StringValue to a cstring
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `==`*[LEN: static[Natural]](sv: StringValue[LEN], cs: cstring): bool {.inline, noSideEffect.} =
## Compares a StringValue to a cstring
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `==`*[LEN: static[Natural]](cs: cstring, sv: var StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares a cstring to a StringValue
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `==`*[LEN: static[Natural]](cs: cstring, sv: StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares a cstring to a StringValue
result = (cast[pointer](cs) != nil) and (c_strcmp(sv.cstr, cs) == 0)
proc `<`*[LEN: static[Natural]](a, b: var StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) < 0)
proc `<`*[LEN: static[Natural]](a, b: StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) < 0)
proc `<=`*[LEN: static[Natural]](a, b: var StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) <= 0)
proc `<=`*[LEN: static[Natural]](a, b: StringValue[LEN]): bool {.inline, noSideEffect.} =
## Compares NoStrings
(c_strcmp(a.cstr, b.cstr) <= 0)
proc initNoString*[LEN: static[Natural]](s: cstring): StringValue[LEN] {.inline, noSideEffect.} =
## Creates a StringValue
static:
assert(LEN <= Natural(254))
let p = cast[pointer](s)
let len = if p == nil: 0 else: len(s)
if len > LEN:
raise newException(Exception, "s is too big: " & $len)
result[LEN+1] = char(len)
if len == 0:
result[0] = char(0)
else:
copyMem(result.cstr, p, len+1)
converter toNoString*[LEN: static[Natural]](s: cstring): StringValue[LEN] {.inline, noSideEffect.} =
## Converts a cstring to a StringValue.
result = initNoString(s)
converter toNoString*[LEN: static[Natural]](s: string): StringValue[LEN] {.inline, noSideEffect.} =
## Converts a string to a StringValue.
result = initNoString(s)
And here the test code, which crashes the compiler.
import stringvalue
type
StringValue16* = StringValue[14]
## A 16-bytes StringValue (maximum length is 14).
StringValue32* = StringValue[30]
## A 32-bytes StringValue (maximum length is 30).
StringValue64* = StringValue[62]
## A 64-bytes StringValue (maximum length is 62).
StringValue128* = StringValue[126]
## A 128-bytes StringValue (maximum length is 126).
StringValue256* = StringValue[254]
## A 256-bytes StringValue (maximum length is 254).
when isMainModule:
echo("TESTING StringValue ...")
let text1 = "abc"
let text2 = "def"
var st1 = initNoString[3](text1)
var st2 = initNoString[3](text2)
assert(st1.len == 3)
assert(st1.cstr == text1.cstring)
assert(st2.cstr == text2.cstring)
assert(st1 == text1)
assert(text1 == st1)
assert($st1 == text1)
assert(text1 == $st1)
assert(st1 != st2)
assert(st1 < st2)
assert(st1 <= st2)
assert(st2 > st1)
assert(st2 >= st1)
assert(st1[1] == 'b')
assert(hash(st1) == hash(text1))
var st3: StringValue = "abc"
assert(st3 == text1)
st3[0] = 'd'
st3[1] = 'e'
st3[2] = 'f'
assert(st3 == text2)
I'm using nim 0.17.2 on Windows 10, and vcc:
Visual Studio 2017 Developer Command Prompt v15.0.26228.13
Copyright (c) 2017 Microsoft Corporation
[vcvarsall.bat] Environment initialized for: 'x64'
I think the main issue is (beyond the compiler crash), how to get this to compile:
type
StringValue*[LEN: Natural] = distinct array[LEN+2,char]
type
StringValue*[LEN: static[Natural]] = array[LEN + 2, char]
var
s: StringValue[8]
s[0] = 'x'
echo s[0]
No crash and works fine for me.
$ nim -v
Nim Compiler Version 0.17.3 [Linux: amd64]
Copyright (c) 2006-2017 by Andreas Rumpf
git hash: b7e69e7cbbf6139316841bcc7c596e93ac78d994
active boot switches: -d:release
But indeed, with distinct it does not work any more...
I just tested this code, but it does not compile successfully:
type
StringValue*[LEN: static[Natural]] = distinct array[LEN + 2, char]
proc `[]=`*[LEN: static[Natural],I: Natural](sv: var StringValue[LEN]; i: I; c: char) = discard
var
s: StringValue[8]
#s[0] = 'x'
`[]=`(s, 0, 'x')
#echo s[0]
@monster, when it does crash, I suggest you run WinCrashReport so you can see the description such like this
Process Filename: d:\installer\nim\bin\nim.exe
Process ID: 4084
Thread ID: 2596
Process Description:
Process Version:
Process Company:
Product Name:
Product Version:
Crash Address: 00000000`004F7E0A
Crash Address (Relative): nim.exe+0xf7e0a
Exception Code: C00000FD
Exception Description: A new guard page for the stack cannot be created.
Exception Parameter (1): 00000000`00000001
Exception Parameter (2): 00000000`00693FD8
Crash Code Bytes:
E8 91 5A F5 FF 48 85 C0 48 89 84 24 A8 00 00 00
...
In my case, I did something "really" unsafe operation so the compiler crash.
My system is Windows 10 64 bit, GCC 7.3.0 Mingw-w64
@Stefan_Salewski I managed to reduce the crash to this (no distinct required in my setup):
type
StringValue*[LEN: static[Natural]] = array[LEN+Natural(2),char]
StringValue16* = StringValue[14]
@mashingan I've installed WinCrashReport as you suggested, and got a report. But TBH, the only thing I can decipher from it is that there was a stack-overflow (I can't interpret assembler code, and don't really want to learn it either). Is there any point in posting that report together with a github issue?
nim.exe c -o:../bin/compilercrash compilercrash.nim
Hint: used config file 'C:\nim-0.17.2\config\nim.cfg' [Conf]
Hint: system [Processing]
Hint: compilercrash [Processing]
The terminal process terminated with exit code: 3221225725
I managed to reduce the crash to this
Great. Maybe test if it only occurs for Natural or also for int, and create an issue for github issue tracker. Should be a task for Zachary.
@Stefan_Salewski It compiles with int instead of Natural.
@mratsim I just updated my Nim sources (I normally use the binary distribution), and rebuilt it, and still get the crash. So I'll post the issue.