# # # Nim's Runtime Library # (c) Copyright 2012 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # # string & sequence handling procedures needed by the code generator # strings are dynamically resized, have a length field # and are zero-terminated, so they can be casted to C # strings easily # we don't use refcounts because that's a behaviour # the programmer may not want proc dataPointer(a: PGenericSeq, elemAlign: int): pointer = cast[pointer](cast[ByteAddress](a) +% align(GenericSeqSize, elemAlign)) proc dataPointer(a: PGenericSeq, elemAlign, elemSize, index: int): pointer = cast[pointer](cast[ByteAddress](a) +% align(GenericSeqSize, elemAlign) +% (index*%elemSize)) proc resize(old: int): int {.inline.} = if old <= 0: result = 4 elif old < 65536: result = old * 2 else: result = old * 3 div 2 # for large arrays * 3/2 is better when declared(allocAtomic): template allocStr(size: untyped): untyped = cast[NimString](allocAtomic(size)) template allocStrNoInit(size: untyped): untyped = cast[NimString](boehmAllocAtomic(size)) elif defined(gcRegions): template allocStr(size: untyped): untyped = cast[NimString](newStr(addr(strDesc), size, true)) template allocStrNoInit(size: untyped): untyped = cast[NimString](newStr(addr(strDesc), size, false)) else: template allocStr(size: untyped): untyped = cast[NimString](newObj(addr(strDesc), size)) template allocStrNoInit(size: untyped): untyped = cast[NimString](newObjNoInit(addr(strDesc), size)) proc rawNewStringNoInit(space: int): NimString {.compilerproc.} = var s = space if s < 7: s = 7 result = allocStrNoInit(sizeof(TGenericSeq) + s + 1) result.reserved = s result.len = 0 when defined(gogc): result.elemSize = 1 proc rawNewString(space: int): NimString {.compilerproc.} = var s = space if s < 7: s = 7 result = allocStr(sizeof(TGenericSeq) + s + 1) result.reserved = s result.len = 0 when defined(gogc): result.elemSize = 1 proc mnewString(len: int): NimString {.compilerproc.} = result = rawNewString(len) result.len = len proc copyStrLast(s: NimString, start, last: int): NimString {.compilerproc.} = # This is not used by most recent versions of the compiler anymore, but # required for bootstrapping purposes. let start = max(start, 0) if s == nil: return nil let len = min(last, s.len-1) - start + 1 if len > 0: result = rawNewStringNoInit(len) result.len = len copyMem(addr(result.data), addr(s.data[start]), len) result.data[len] = '\0' else: result = rawNewString(len) proc copyStr(s: NimString, start: int): NimString {.compilerproc.} = # This is not used by most recent versions of the compiler anymore, but # required for bootstrapping purposes. if s == nil: return nil result = copyStrLast(s, start, s.len-1) proc nimToCStringConv(s: NimString): cstring {.compilerproc, nonReloadable, inline.} = if s == nil or s.len == 0: result = cstring"" else: result = cstring(addr s.data) proc toNimStr(str: cstring, len: int): NimString {.compilerproc.} = result = rawNewStringNoInit(len) result.len = len copyMem(addr(result.data), str, len + 1) proc cstrToNimstr(str: cstring): NimString {.compilerRtl.} = if str == nil: NimString(nil) else: toNimStr(str, str.len) proc copyString(src: NimString): NimString {.compilerRtl.} = if src != nil: if (src.reserved and seqShallowFlag) != 0: result = src else: result = rawNewStringNoInit(src.len) result.len = src.len copyMem(addr(result.data), addr(src.data), src.len + 1) sysAssert((seqShallowFlag and result.reserved) == 0, "copyString") when defined(nimShallowStrings): if (src.reserved and strlitFlag) != 0: result.reserved = (result.reserved and not strlitFlag) or seqShallowFlag proc newOwnedString(src: NimString; n: int): NimString = result = rawNewStringNoInit(n) result.len = n copyMem(addr(result.data), addr(src.data), n) result.data[n] = '\0' proc copyStringRC1(src: NimString): NimString {.compilerRtl.} = if src != nil: if (src.reserved and seqShallowFlag) != 0: result = src when declared(incRef): incRef(usrToCell(result)) else: when declared(newObjRC1) and not defined(gcRegions): var s = src.len if s < 7: s = 7 result = cast[NimString](newObjRC1(addr(strDesc), sizeof(TGenericSeq) + s+1)) result.reserved = s when defined(gogc): result.elemSize = 1 else: result = rawNewStringNoInit(src.len) result.len = src.len copyMem(addr(result.data), addr(src.data), src.len + 1) sysAssert((seqShallowFlag and result.reserved) == 0, "copyStringRC1") when defined(nimShallowStrings): if (src.reserved and strlitFlag) != 0: result.reserved = (result.reserved and not strlitFlag) or seqShallowFlag proc copyDeepString(src: NimString): NimString {.inline.} = if src != nil: result = rawNewStringNoInit(src.len) result.len = src.len copyMem(addr(result.data), addr(src.data), src.len + 1) proc addChar(s: NimString, c: char): NimString = # is compilerproc! if s == nil: result = rawNewStringNoInit(1) result.len = 0 else: result = s if result.len >= result.space: let r = resize(result.space) when defined(nimIncrSeqV3): result = rawNewStringNoInit(r) result.len = s.len copyMem(addr result.data[0], unsafeAddr(s.data[0]), s.len+1) else: result = cast[NimString](growObj(result, sizeof(TGenericSeq) + r + 1)) result.reserved = r result.data[result.len] = c result.data[result.len+1] = '\0' inc(result.len) # These routines should be used like following: # # s &= "Hello " & name & ", how do you feel?" # # # { # s = resizeString(s, 6 + name->len + 17); # appendString(s, strLit1); # appendString(s, strLit2); # appendString(s, strLit3); # } # # # s = "Hello " & name & ", how do you feel?" # # # { # string tmp0; # tmp0 = rawNewString(6 + name->len + 17); # appendString(s, strLit1); # appendString(s, strLit2); # appendString(s, strLit3); # s = tmp0; # } # # # s = "" # # # s = rawNewString(0); proc resizeString(dest: NimString, addlen: int): NimString {.compilerRtl.} = if dest == nil: result = rawNewStringNoInit(addlen) elif dest.len + addlen <= dest.space: result = dest else: # slow path: let sp = max(resize(dest.space), dest.len + addlen) when defined(nimIncrSeqV3): result = rawNewStringNoInit(sp) result.len = dest.len copyMem(addr result.data[0], unsafeAddr(dest.data[0]), dest.len+1) else: result = cast[NimString](growObj(dest, sizeof(TGenericSeq) + sp + 1)) result.reserved = sp #result = rawNewString(sp) #copyMem(result, dest, dest.len + sizeof(TGenericSeq)) # DO NOT UPDATE LEN YET: dest.len = newLen proc appendString(dest, src: NimString) {.compilerproc, inline.} = if src != nil: copyMem(addr(dest.data[dest.len]), addr(src.data), src.len + 1) inc(dest.len, src.len) proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} = dest.data[dest.len] = c dest.data[dest.len+1] = '\0' inc(dest.len) proc setLengthStr(s: NimString, newLen: int): NimString {.compilerRtl.} = let n = max(newLen, 0) if s == nil: result = mnewString(newLen) elif n <= s.space: result = s else: let sp = max(resize(s.space), newLen) when defined(nimIncrSeqV3): result = rawNewStringNoInit(sp) result.len = s.len copyMem(addr result.data[0], unsafeAddr(s.data[0]), s.len+1) zeroMem(addr result.data[s.len], newLen - s.len) result.reserved = sp else: result = resizeString(s, n) result.len = n result.data[n] = '\0' # ----------------- sequences ---------------------------------------------- proc incrSeq(seq: PGenericSeq, elemSize, elemAlign: int): PGenericSeq {.compilerproc.} = # increments the length by one: # this is needed for supporting ``add``; # # add(seq, x) generates: # seq = incrSeq(seq, sizeof(x)); # seq[seq->len-1] = x; result = seq if result.len >= result.space: let r = resize(result.space) result = cast[PGenericSeq](growObj(result, align(GenericSeqSize, elemAlign) + elemSize * r)) result.reserved = r inc(result.len) proc incrSeqV2(seq: PGenericSeq, elemSize, elemAlign: int): PGenericSeq {.compilerproc.} = # incrSeq version 2 result = seq if result.len >= result.space: let r = resize(result.space) result = cast[PGenericSeq](growObj(result, align(GenericSeqSize, elemAlign) + elemSize * r)) result.reserved = r proc incrSeqV3(s: PGenericSeq, typ: PNimType): PGenericSeq {.compilerproc.} = if s == nil: result = cast[PGenericSeq](newSeq(typ, 1)) result.len = 0 else: result = s if result.len >= result.space: let r = resize(result.space) when defined(nimIncrSeqV3): result = cast[PGenericSeq](newSeq(typ, r)) result.len = s.len copyMem(dataPointer(result, typ.base.align), dataPointer(s, typ.base.align), s.len * typ.base.size) # since we steal the content from 's', it's crucial to set s's len to 0. s.len = 0 else: result = cast[PGenericSeq](growObj(result, align(GenericSeqSize, typ.base.align) + typ.base.size * r)) result.reserved = r proc setLengthSeq(seq: PGenericSeq, elemSize, elemAlign, newLen: int): PGenericSeq {. compilerRtl, inl.} = result = seq if result.space < newLen: let r = max(resize(result.space), newLen) result = cast[PGenericSeq](growObj(result, align(GenericSeqSize, elemAlign) + elemSize * r)) result.reserved = r elif newLen < result.len: # we need to decref here, otherwise the GC leaks! when not defined(boehmGC) and not defined(nogc) and not defined(gcMarkAndSweep) and not defined(gogc) and not defined(gcRegions): when false: # compileOption("gc", "v2"): for i in newLen..result.len-1: let len0 = gch.tempStack.len forAllChildrenAux(dataPointer(result, elemAlign, elemSize, i), extGetCellType(result).base, waPush) let len1 = gch.tempStack.len for i in len0 ..< len1: doDecRef(gch.tempStack.d[i], LocalHeap, MaybeCyclic) gch.tempStack.len = len0 else: if ntfNoRefs notin extGetCellType(result).base.flags: for i in newLen..result.len-1: forAllChildrenAux(dataPointer(result, elemAlign, elemSize, i), extGetCellType(result).base, waZctDecRef) # XXX: zeroing out the memory can still result in crashes if a wiped-out # cell is aliased by another pointer (ie proc parameter or a let variable). # This is a tough problem, because even if we don't zeroMem here, in the # presence of user defined destructors, the user will expect the cell to be # "destroyed" thus creating the same problem. We can destroy the cell in the # finalizer of the sequence, but this makes destruction non-deterministic. zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize) result.len = newLen proc setLengthSeqV2(s: PGenericSeq, typ: PNimType, newLen: int): PGenericSeq {. compilerRtl.} = sysAssert typ.kind == tySequence, "setLengthSeqV2: type is not a seq" if s == nil: result = cast[PGenericSeq](newSeq(typ, newLen)) else: when defined(nimIncrSeqV3): let elemSize = typ.base.size let elemAlign = typ.base.align if s.space < newLen: let r = max(resize(s.space), newLen) result = cast[PGenericSeq](newSeq(typ, r)) copyMem(dataPointer(result, elemAlign), dataPointer(s, elemAlign), s.len * elemSize) # since we steal the content from 's', it's crucial to set s's len to 0. s.len = 0 elif newLen < s.len: result = s # we need to decref here, otherwise the GC leaks! when not defined(boehmGC) and not defined(nogc) and not defined(gcMarkAndSweep) and not defined(gogc) and not defined(gcRegions): if ntfNoRefs notin typ.base.flags: for i in newLen..result.len-1: forAllChildrenAux(dataPointer(result, elemAlign, elemSize, i), extGetCellType(result).base, waZctDecRef) # XXX: zeroing out the memory can still result in crashes if a wiped-out # cell is aliased by another pointer (ie proc parameter or a let variable). # This is a tough problem, because even if we don't zeroMem here, in the # presence of user defined destructors, the user will expect the cell to be # "destroyed" thus creating the same problem. We can destroy the cell in the # finalizer of the sequence, but this makes destruction non-deterministic. zeroMem(dataPointer(result, elemAlign, elemSize, newLen), (result.len-%newLen) *% elemSize) else: result = s zeroMem(dataPointer(result, elemAlign, elemSize, result.len), (newLen-%result.len) *% elemSize) result.len = newLen else: result = setLengthSeq(s, typ.base.size, newLen)