diff options
Diffstat (limited to 'lib')
42 files changed, 926 insertions, 401 deletions
diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 79bf34b7c..09dc341df 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -7,6 +7,7 @@ # distribution, for details about the copyright. # +include "system/inclrtl" ## This module contains the interface to the compiler's abstract syntax ## tree (`AST`:idx:). Macros operate on this tree. @@ -109,19 +110,20 @@ const nnkCallKinds* = {nnkCall, nnkInfix, nnkPrefix, nnkPostfix, nnkCommand, nnkCallStrLit} -proc `[]`*(n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild".} +proc `[]`*(n: PNimrodNode, i: int): PNimrodNode {.magic: "NChild", noSideEffect.} ## get `n`'s `i`'th child. -proc `[]=`*(n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild".} +proc `[]=`*(n: PNimrodNode, i: int, child: PNimrodNode) {.magic: "NSetChild", + noSideEffect.} ## set `n`'s `i`'th child to `child`. -proc `!`*(s: string): TNimrodIdent {.magic: "StrToIdent".} +proc `!`*(s: string): TNimrodIdent {.magic: "StrToIdent", noSideEffect.} ## constructs an identifier from the string `s` -proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr".} +proc `$`*(i: TNimrodIdent): string {.magic: "IdentToStr", noSideEffect.} ## converts a Nimrod identifier to a string -proc `$`*(s: PNimrodSymbol): string {.magic: "IdentToStr".} +proc `$`*(s: PNimrodSymbol): string {.magic: "IdentToStr", noSideEffect.} ## converts a Nimrod symbol to a string proc `==`*(a, b: TNimrodIdent): bool {.magic: "EqIdent", noSideEffect.} @@ -130,35 +132,36 @@ proc `==`*(a, b: TNimrodIdent): bool {.magic: "EqIdent", noSideEffect.} proc `==`*(a, b: PNimrodNode): bool {.magic: "EqNimrodNode", noSideEffect.} ## compares two Nimrod nodes -proc len*(n: PNimrodNode): int {.magic: "NLen".} +proc len*(n: PNimrodNode): int {.magic: "NLen", noSideEffect.} ## returns the number of children of `n`. -proc add*(father, child: PNimrodNode): PNimrodNode {.magic: "NAdd", discardable.} +proc add*(father, child: PNimrodNode): PNimrodNode {.magic: "NAdd", discardable, + noSideEffect.} ## Adds the `child` to the `father` node. Returns the ## father node so that calls can be nested. proc add*(father: PNimrodNode, children: varargs[PNimrodNode]): PNimrodNode {. - magic: "NAddMultiple", discardable.} + magic: "NAddMultiple", discardable, noSideEffect.} ## Adds each child of `children` to the `father` node. ## Returns the `father` node so that calls can be nested. -proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel".} +proc del*(father: PNimrodNode, idx = 0, n = 1) {.magic: "NDel", noSideEffect.} ## deletes `n` children of `father` starting at index `idx`. -proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind".} +proc kind*(n: PNimrodNode): TNimrodNodeKind {.magic: "NKind", noSideEffect.} ## returns the `kind` of the node `n`. -proc intVal*(n: PNimrodNode): BiggestInt {.magic: "NIntVal".} -proc floatVal*(n: PNimrodNode): BiggestFloat {.magic: "NFloatVal".} -proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol".} -proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent".} -proc typ*(n: PNimrodNode): typedesc {.magic: "NGetType".} -proc strVal*(n: PNimrodNode): string {.magic: "NStrVal".} - -proc `intVal=`*(n: PNimrodNode, val: BiggestInt) {.magic: "NSetIntVal".} -proc `floatVal=`*(n: PNimrodNode, val: BiggestFloat) {.magic: "NSetFloatVal".} -proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol".} -proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent".} +proc intVal*(n: PNimrodNode): BiggestInt {.magic: "NIntVal", noSideEffect.} +proc floatVal*(n: PNimrodNode): BiggestFloat {.magic: "NFloatVal", noSideEffect.} +proc symbol*(n: PNimrodNode): PNimrodSymbol {.magic: "NSymbol", noSideEffect.} +proc ident*(n: PNimrodNode): TNimrodIdent {.magic: "NIdent", noSideEffect.} +proc typ*(n: PNimrodNode): typedesc {.magic: "NGetType", noSideEffect.} +proc strVal*(n: PNimrodNode): string {.magic: "NStrVal", noSideEffect.} + +proc `intVal=`*(n: PNimrodNode, val: BiggestInt) {.magic: "NSetIntVal", noSideEffect.} +proc `floatVal=`*(n: PNimrodNode, val: BiggestFloat) {.magic: "NSetFloatVal", noSideEffect.} +proc `symbol=`*(n: PNimrodNode, val: PNimrodSymbol) {.magic: "NSetSymbol", noSideEffect.} +proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent", noSideEffect.} #proc `typ=`*(n: PNimrodNode, typ: typedesc) {.magic: "NSetType".} # this is not sound! Unfortunately forbidding 'typ=' is not enough, as you # can easily do: @@ -166,24 +169,24 @@ proc `ident=`*(n: PNimrodNode, val: TNimrodIdent) {.magic: "NSetIdent".} # let fake = semCheck(2.0) # bracket[0] = fake # constructs a mixed array with ints and floats! -proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal".} +proc `strVal=`*(n: PNimrodNode, val: string) {.magic: "NSetStrVal", noSideEffect.} proc newNimNode*(kind: TNimrodNodeKind, - n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode".} + n: PNimrodNode=nil): PNimrodNode {.magic: "NNewNimNode", noSideEffect.} -proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode".} -proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree".} +proc copyNimNode*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimNode", noSideEffect.} +proc copyNimTree*(n: PNimrodNode): PNimrodNode {.magic: "NCopyNimTree", noSideEffect.} -proc error*(msg: string) {.magic: "NError".} +proc error*(msg: string) {.magic: "NError", gcsafe.} ## writes an error message at compile time -proc warning*(msg: string) {.magic: "NWarning".} +proc warning*(msg: string) {.magic: "NWarning", gcsafe.} ## writes a warning message at compile time -proc hint*(msg: string) {.magic: "NHint".} +proc hint*(msg: string) {.magic: "NHint", gcsafe.} ## writes a hint message at compile time -proc newStrLitNode*(s: string): PNimrodNode {.compileTime.} = +proc newStrLitNode*(s: string): PNimrodNode {.compileTime, noSideEffect.} = ## creates a string literal node from `s` result = newNimNode(nnkStrLit) result.strVal = s @@ -219,7 +222,7 @@ type ## any other means in the language currently) proc bindSym*(ident: string, rule: TBindSymRule = brClosed): PNimrodNode {. - magic: "NBindSym".} + magic: "NBindSym", noSideEffect.} ## creates a node that binds `ident` to a symbol node. The bound symbol ## may be an overloaded symbol. ## If ``rule == brClosed`` either an ``nkClosedSymChoice`` tree is @@ -230,11 +233,11 @@ proc bindSym*(ident: string, rule: TBindSymRule = brClosed): PNimrodNode {. ## returned even if the symbol is not ambiguous. proc genSym*(kind: TNimrodSymKind = nskLet; ident = ""): PNimrodNode {. - magic: "NGenSym".} + magic: "NGenSym", noSideEffect.} ## generates a fresh symbol that is guaranteed to be unique. The symbol ## needs to occur in a declaration context. -proc callsite*(): PNimrodNode {.magic: "NCallSite".} +proc callsite*(): PNimrodNode {.magic: "NCallSite", gcsafe.} ## returns the AST if the invokation expression that invoked this macro. proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} = @@ -242,19 +245,19 @@ proc toStrLit*(n: PNimrodNode): PNimrodNode {.compileTime.} = ## in a string literal node return newStrLitNode(repr(n)) -proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo".} +proc lineinfo*(n: PNimrodNode): string {.magic: "NLineInfo", noSideEffect.} ## returns the position the node appears in the original source file ## in the form filename(line, col) -proc parseExpr*(s: string): PNimrodNode {.magic: "ParseExprToAst".} +proc parseExpr*(s: string): PNimrodNode {.magic: "ParseExprToAst", noSideEffect.} ## Compiles the passed string to its AST representation. ## Expects a single expression. -proc parseStmt*(s: string): PNimrodNode {.magic: "ParseStmtToAst".} +proc parseStmt*(s: string): PNimrodNode {.magic: "ParseStmtToAst", noSideEffect.} ## Compiles the passed string to its AST representation. ## Expects one or more statements. -proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst".} +proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst", noSideEffect.} ## Obtains the AST nodes returned from a macro or template invocation. ## Example: ## @@ -263,7 +266,7 @@ proc getAst*(macroOrTemplate: expr): PNimrodNode {.magic: "ExpandToAst".} ## macro FooMacro() = ## var ast = getAst(BarTemplate()) -proc quote*(bl: stmt, op = "``"): PNimrodNode {.magic: "QuoteAst".} +proc quote*(bl: stmt, op = "``"): PNimrodNode {.magic: "QuoteAst", noSideEffect.} ## Quasi-quoting operator. ## Accepts an expression or a block and returns the AST that represents it. ## Within the quoted AST, you are able to interpolate PNimrodNode expressions diff --git a/lib/packages/docutils/highlite.nim b/lib/packages/docutils/highlite.nim index c507f5e1c..80fbf3a51 100644 --- a/lib/packages/docutils/highlite.nim +++ b/lib/packages/docutils/highlite.nim @@ -52,7 +52,7 @@ const "finally", "for", "from", "generic", "if", "import", "in", "include", "interface", "is", "isnot", "iterator", "lambda", "let", "macro", "method", "mixin", "mod", "nil", "not", "notin", "object", "of", "or", "out", "proc", - "ptr", "raise", "ref", "return", "shared", "shl", "shr", "static", + "ptr", "raise", "ref", "return", "shl", "shr", "static", "template", "try", "tuple", "type", "using", "var", "when", "while", "with", "without", "xor", "yield"] @@ -61,6 +61,7 @@ proc getSourceLanguage*(name: string): TSourceLanguage = if cmpIgnoreStyle(name, sourceLanguageToStr[i]) == 0: return i result = langNone + proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: cstring) = g.buf = buf g.kind = low(TTokenClass) @@ -70,6 +71,7 @@ proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: cstring) = var pos = 0 # skip initial whitespace: while g.buf[pos] in {' ', '\x09'..'\x0D'}: inc(pos) g.pos = pos + proc initGeneralTokenizer*(g: var TGeneralTokenizer, buf: string) = initGeneralTokenizer(g, cstring(buf)) @@ -554,7 +556,7 @@ when isMainModule: let input = string(readFile(filename)) keywords = input.split() break - doAssert (not keywords.isNil, "Couldn't read any keywords.txt file!") + doAssert(not keywords.isNil, "Couldn't read any keywords.txt file!") doAssert keywords.len == nimrodKeywords.len, "No matching lengths" for i in 0..keywords.len-1: #echo keywords[i], " == ", nimrodKeywords[i] diff --git a/lib/packages/docutils/rst.nim b/lib/packages/docutils/rst.nim index 30cc9026b..23459ade6 100644 --- a/lib/packages/docutils/rst.nim +++ b/lib/packages/docutils/rst.nim @@ -311,8 +311,8 @@ proc newSharedState(options: TRstParseOptions, result.subs = @[] result.refs = @[] result.options = options - result.msgHandler = if isNil(msgHandler): defaultMsgHandler else: msgHandler - result.findFile = if isNil(findFile): defaultFindFile else: findFile + result.msgHandler = if not isNil(msgHandler): msgHandler else: defaultMsgHandler + result.findFile = if not isNil(findFile): findFile else: defaultFindFile proc rstMessage(p: TRstParser, msgKind: TMsgKind, arg: string) = p.s.msgHandler(p.filename, p.line + p.tok[p.idx].line, diff --git a/lib/posix/epoll.nim b/lib/posix/epoll.nim index 57a2f001f..ee04348e8 100644 --- a/lib/posix/epoll.nim +++ b/lib/posix/epoll.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +{.deadCodeElim:on.} + from posix import TSocketHandle const diff --git a/lib/posix/inotify.nim b/lib/posix/inotify.nim index 28dcd652f..852eb12fa 100644 --- a/lib/posix/inotify.nim +++ b/lib/posix/inotify.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +{.deadCodeElim:on.} + # Get the platform-dependent flags. # Structure describing an inotify event. type diff --git a/lib/posix/linux.nim b/lib/posix/linux.nim index 1ed1af3b6..be591e29a 100644 --- a/lib/posix/linux.nim +++ b/lib/posix/linux.nim @@ -1,3 +1,5 @@ +{.deadCodeElim:on.} + import posix const @@ -22,4 +24,5 @@ const # fn should be of type proc (a2: pointer): void {.cdecl.} proc clone*(fn: pointer; child_stack: pointer; flags: cint; - arg: pointer; ptid: ptr TPid; tls: pointer; ctid: ptr TPid): cint {.importc, header: "<sched.h>".} + arg: pointer; ptid: ptr TPid; tls: pointer; + ctid: ptr TPid): cint {.importc, header: "<sched.h>".} diff --git a/lib/posix/posix.nim b/lib/posix/posix.nim index 131f23fdd..e206447cc 100644 --- a/lib/posix/posix.nim +++ b/lib/posix/posix.nim @@ -27,6 +27,8 @@ ## resulting C code will just ``#include <XYZ.h>`` and *not* define the ## symbols declared here. +{.deadCodeElim:on.} + from times import TTime const diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index 5e638dc74..f50383038 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -7,6 +7,8 @@ # distribution, for details about the copyright. # +include "system/inclrtl" + import os, oids, tables, strutils, macros import rawsockets @@ -31,7 +33,7 @@ export TPort type PFutureBase* = ref object of PObject - cb: proc () {.closure.} + cb: proc () {.closure,gcsafe.} finished: bool PFuture*[T] = ref object of PFutureBase @@ -68,7 +70,7 @@ proc fail*[T](future: PFuture[T], error: ref EBase) = if future.cb != nil: future.cb() -proc `callback=`*(future: PFutureBase, cb: proc () {.closure.}) = +proc `callback=`*(future: PFutureBase, cb: proc () {.closure,gcsafe.}) = ## Sets the callback proc to be called when the future completes. ## ## If future has already completed then ``cb`` will be called immediately. @@ -80,7 +82,7 @@ proc `callback=`*(future: PFutureBase, cb: proc () {.closure.}) = future.cb() proc `callback=`*[T](future: PFuture[T], - cb: proc (future: PFuture[T]) {.closure.}) = + cb: proc (future: PFuture[T]) {.closure,gcsafe.}) = ## Sets the callback proc to be called when the future completes. ## ## If future has already completed then ``cb`` will be called immediately. @@ -122,7 +124,7 @@ when defined(windows) or defined(nimdoc): TCompletionData* = object sock: TAsyncFD cb: proc (sock: TAsyncFD, bytesTransferred: DWORD, - errcode: TOSErrorCode) {.closure.} + errcode: TOSErrorCode) {.closure,gcsafe.} PDispatcher* = ref object ioPort: THandle @@ -237,7 +239,7 @@ when defined(windows) or defined(nimdoc): let func = cast[proc (s: TSocketHandle, name: ptr TSockAddr, namelen: cint, lpSendBuffer: pointer, dwSendDataLength: dword, - lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool {.stdcall.}](connectExPtr) + lpdwBytesSent: PDWORD, lpOverlapped: POverlapped): bool {.stdcall,gcsafe.}](connectExPtr) result = func(s, name, namelen, lpSendBuffer, dwSendDataLength, lpdwBytesSent, lpOverlapped) @@ -251,7 +253,7 @@ when defined(windows) or defined(nimdoc): cast[proc (listenSock, acceptSock: TSocketHandle, lpOutputBuffer: pointer, dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: DWORD, lpdwBytesReceived: PDWORD, - lpOverlapped: POverlapped): bool {.stdcall.}](acceptExPtr) + lpOverlapped: POverlapped): bool {.stdcall,gcsafe.}](acceptExPtr) result = func(listenSock, acceptSock, lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength, lpdwBytesReceived, lpOverlapped) @@ -268,7 +270,7 @@ when defined(windows) or defined(nimdoc): dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength: DWORD, LocalSockaddr: ptr ptr TSockAddr, LocalSockaddrLength: lpint, RemoteSockaddr: ptr ptr TSockAddr, - RemoteSockaddrLength: lpint) {.stdcall.}](getAcceptExSockAddrsPtr) + RemoteSockaddrLength: lpint) {.stdcall,gcsafe.}](getAcceptExSockAddrsPtr) func(lpOutputBuffer, dwReceiveDataLength, dwLocalAddressLength, dwRemoteAddressLength, LocalSockaddr, LocalSockaddrLength, @@ -537,7 +539,7 @@ else: from posix import EINTR, EAGAIN, EINPROGRESS, EWOULDBLOCK, MSG_PEEK type TAsyncFD* = distinct cint - TCallback = proc (sock: TAsyncFD): bool {.closure.} + TCallback = proc (sock: TAsyncFD): bool {.closure,gcsafe.} PData* = ref object of PObject sock: TAsyncFD @@ -757,7 +759,7 @@ proc accept*(socket: TAsyncFD): PFuture[TAsyncFD] = template createCb*(retFutureSym, iteratorNameSym: expr): stmt {.immediate.} = var nameIterVar = iteratorNameSym - proc cb {.closure.} = + proc cb {.closure,gcsafe.} = if not nameIterVar.finished: var next = nameIterVar() if next == nil: diff --git a/lib/pure/asyncio.nim b/lib/pure/asyncio.nim index ab09dc860..c68ca4350 100644 --- a/lib/pure/asyncio.nim +++ b/lib/pure/asyncio.nim @@ -6,6 +6,8 @@ # distribution, for details about the copyright. # +include "system/inclrtl" + import sockets, os ## This module implements an asynchronous event loop together with asynchronous sockets @@ -98,13 +100,13 @@ type fd*: TSocketHandle deleVal*: PObject - handleRead*: proc (h: PObject) {.nimcall.} - handleWrite*: proc (h: PObject) {.nimcall.} - handleError*: proc (h: PObject) {.nimcall.} - hasDataBuffered*: proc (h: PObject): bool {.nimcall.} + handleRead*: proc (h: PObject) {.nimcall, gcsafe.} + handleWrite*: proc (h: PObject) {.nimcall, gcsafe.} + handleError*: proc (h: PObject) {.nimcall, gcsafe.} + hasDataBuffered*: proc (h: PObject): bool {.nimcall, gcsafe.} open*: bool - task*: proc (h: PObject) {.nimcall.} + task*: proc (h: PObject) {.nimcall, gcsafe.} mode*: TFileMode PDelegate* = ref TDelegate @@ -118,13 +120,13 @@ type socket: TSocket info: TInfo - handleRead*: proc (s: PAsyncSocket) {.closure.} - handleWrite: proc (s: PAsyncSocket) {.closure.} - handleConnect*: proc (s: PAsyncSocket) {.closure.} + handleRead*: proc (s: PAsyncSocket) {.closure, gcsafe.} + handleWrite: proc (s: PAsyncSocket) {.closure, gcsafe.} + handleConnect*: proc (s: PAsyncSocket) {.closure, gcsafe.} - handleAccept*: proc (s: PAsyncSocket) {.closure.} + handleAccept*: proc (s: PAsyncSocket) {.closure, gcsafe.} - handleTask*: proc (s: PAsyncSocket) {.closure.} + handleTask*: proc (s: PAsyncSocket) {.closure, gcsafe.} lineBuffer: TaintedString ## Temporary storage for ``readLine`` sendBuffer: string ## Temporary storage for ``send`` @@ -213,7 +215,7 @@ proc asyncSockHandleRead(h: PObject) = else: PAsyncSocket(h).handleAccept(PAsyncSocket(h)) -proc close*(sock: PAsyncSocket) +proc close*(sock: PAsyncSocket) {.gcsafe.} proc asyncSockHandleWrite(h: PObject) = when defined(ssl): if PAsyncSocket(h).socket.isSSL and not @@ -254,7 +256,7 @@ proc asyncSockHandleWrite(h: PObject) = PAsyncSocket(h).deleg.mode = fmRead when defined(ssl): - proc asyncSockDoHandshake(h: PObject) = + proc asyncSockDoHandshake(h: PObject) {.gcsafe.} = if PAsyncSocket(h).socket.isSSL and not PAsyncSocket(h).socket.gotHandshake: if PAsyncSocket(h).sslNeedAccept: @@ -437,7 +439,7 @@ proc isSendDataBuffered*(s: PAsyncSocket): bool = return s.sendBuffer.len != 0 proc setHandleWrite*(s: PAsyncSocket, - handleWrite: proc (s: PAsyncSocket) {.closure.}) = + handleWrite: proc (s: PAsyncSocket) {.closure, gcsafe.}) = ## Setter for the ``handleWrite`` event. ## ## To remove this event you should use the ``delHandleWrite`` function. diff --git a/lib/pure/cgi.nim b/lib/pure/cgi.nim index 4e2b6f5f8..31fb24eef 100644 --- a/lib/pure/cgi.nim +++ b/lib/pure/cgi.nim @@ -377,7 +377,7 @@ proc setCookie*(name, value: string) = write(stdout, "Set-Cookie: ", name, "=", value, "\n") var - gcookies: PStringTable = nil + gcookies {.threadvar.}: PStringTable proc getCookie*(name: string): TaintedString = ## Gets a cookie. If no cookie of `name` exists, "" is returned. diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 412bebeee..cd28f9af0 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -196,7 +196,7 @@ proc `==`*[A, B](s, t: TTable[A, B]): bool = # to use the slow route here: for key, val in s: if not hasKey(t, key): return false - if mget(t, key) != val: return false + if t[key] != val: return false return true proc indexBy*[A, B, C](collection: A, index: proc(x: B): C): TTable[C, B] = diff --git a/lib/pure/ftpclient.nim b/lib/pure/ftpclient.nim index 3bb55239b..53f6688b9 100644 --- a/lib/pure/ftpclient.nim +++ b/lib/pure/ftpclient.nim @@ -6,6 +6,8 @@ # distribution, for details about the copyright. # +include "system/inclrtl" + import sockets, strutils, parseutils, times, os, asyncio ## This module **partially** implements an FTP client as specified @@ -41,7 +43,7 @@ type dummyA, dummyB: pointer # workaround a Nimrod API issue asyncCSock: PAsyncSocket asyncDSock: PAsyncSocket - handleEvent*: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} + handleEvent*: proc (ftp: PAsyncFTPClient, ev: TFTPEvent){.closure,gcsafe.} disp: PDispatcher asyncDSockID: PDelegate user, pass: string @@ -59,7 +61,7 @@ type JRetrText, JRetr, JStore TFTPJob = object - prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall.} + prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall, gcsafe.} case typ*: FTPJobType of JRetrText: lines: string @@ -148,7 +150,8 @@ proc assertReply(received: TaintedString, expected: varargs[string]) = [expected.join("' or '"), received.string]) proc createJob(ftp: PFTPClient, - prc: proc (ftp: PFTPClient, async: bool): bool {.nimcall.}, + prc: proc (ftp: PFTPClient, async: bool): bool {. + nimcall,gcsafe.}, cmd: FTPJobType) = if ftp.jobInProgress: raise newException(EFTP, "Unable to do two jobs at once.") @@ -558,7 +561,7 @@ proc csockHandleRead(s: PAsyncSocket, ftp: PAsyncFTPClient) = proc asyncFTPClient*(address: string, port = TPort(21), user, pass = "", - handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure.} = + handleEvent: proc (ftp: PAsyncFTPClient, ev: TFTPEvent) {.closure,gcsafe.} = (proc (ftp: PAsyncFTPClient, ev: TFTPEvent) = discard)): PAsyncFTPClient = ## Create a ``PAsyncFTPClient`` object. ## diff --git a/lib/pure/future.nim b/lib/pure/future.nim new file mode 100644 index 000000000..73c20e708 --- /dev/null +++ b/lib/pure/future.nim @@ -0,0 +1,118 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Dominik Picheta +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module implements experimental features which may soon be moved to +## the system module (or other more appropriate modules). + +import macros + +proc createProcType(p, b: PNimrodNode): PNimrodNode {.compileTime.} = + #echo treeRepr(p) + #echo treeRepr(b) + result = newNimNode(nnkProcTy) + var formalParams = newNimNode(nnkFormalParams) + + expectKind(b, nnkIdent) + formalParams.add b + + case p.kind + of nnkPar: + for i in 0 .. <p.len: + let ident = p[i] + var identDefs = newNimNode(nnkIdentDefs) + case ident.kind + of nnkExprColonExpr: + identDefs.add ident[0] + identDefs.add ident[1] + of nnkIdent: + identDefs.add newIdentNode("i" & $i) + identDefs.add(ident) + else: + error("Incorrect type list in proc type declaration.") + identDefs.add newEmptyNode() + formalParams.add identDefs + of nnkIdent: + var identDefs = newNimNode(nnkIdentDefs) + identDefs.add newIdentNode("i0") + identDefs.add(p) + identDefs.add newEmptyNode() + formalParams.add identDefs + else: + error("Incorrect type list in proc type declaration.") + + result.add formalParams + result.add newEmptyNode() + #echo(treeRepr(result)) + #echo(result.toStrLit()) + +macro `=>`*(p, b: expr): expr {.immediate.} = + ## Syntax sugar for anonymous procedures. + ## + ## .. code-block:: nimrod + ## + ## proc passTwoAndTwo(f: (int, int) -> int): int = + ## f(2, 2) + ## + ## passTwoAndTwo((x, y) => x + y) # 4 + + #echo treeRepr(p) + #echo(treeRepr(b)) + var params: seq[PNimrodNode] = @[newIdentNode("auto")] + + case p.kind + of nnkPar: + for c in children(p): + var identDefs = newNimNode(nnkIdentDefs) + case c.kind + of nnkExprColonExpr: + identDefs.add(c[0]) + identDefs.add(c[1]) + identDefs.add(newEmptyNode()) + of nnkIdent: + identDefs.add(c) + identDefs.add(newEmptyNode()) + identDefs.add(newEmptyNode()) + else: + error("Incorrect procedure parameter list.") + params.add(identDefs) + of nnkIdent: + var identDefs = newNimNode(nnkIdentDefs) + identDefs.add(p) + identDefs.add(newEmptyNode()) + identDefs.add(newEmptyNode()) + params.add(identDefs) + of nnkInfix: + if p[0].kind == nnkIdent and p[0].ident == !"->": + var procTy = createProcType(p[1], p[2]) + params[0] = procTy[0][0] + for i in 1 .. <procTy[0].len: + params.add(procTy[0][i]) + else: + error("Expected proc type (->) got (" & $p[0].ident & ").") + else: + error("Incorrect procedure parameter list.") + result = newProc(params = params, body = b, procType = nnkLambda) + #echo(result.treeRepr) + #echo(result.toStrLit()) + #return result # TODO: Bug? + +macro `->`*(p, b: expr): expr {.immediate.} = + ## Syntax sugar for procedure types. + ## + ## .. code-block:: nimrod + ## + ## proc pass2(f: (float, float) -> float): float = + ## f(2, 2) + ## + ## # is the same as: + ## + ## proc pass2(f: proc (x, y: float): float): float = + ## f(2, 2) + + createProcType(p, b) diff --git a/lib/pure/httpserver.nim b/lib/pure/httpserver.nim index 901fdc779..8de708c5d 100644 --- a/lib/pure/httpserver.nim +++ b/lib/pure/httpserver.nim @@ -477,7 +477,7 @@ proc nextAsync(s: PAsyncHTTPServer) = s.path = data.substr(i, last-1) proc asyncHTTPServer*(handleRequest: proc (server: PAsyncHTTPServer, client: TSocket, - path, query: string): bool {.closure.}, + path, query: string): bool {.closure, gcsafe.}, port = TPort(80), address = "", reuseAddr = false): PAsyncHTTPServer = ## Creates an Asynchronous HTTP server at ``port``. diff --git a/lib/pure/irc.nim b/lib/pure/irc.nim index 31a673210..49d9a9a34 100644 --- a/lib/pure/irc.nim +++ b/lib/pure/irc.nim @@ -32,6 +32,8 @@ ## **Warning:** The API of this module is unstable, and therefore is subject ## to change. +include "system/inclrtl" + import sockets, strutils, parseutils, times, asyncio, os type @@ -41,7 +43,7 @@ type nick, user, realname, serverPass: string case isAsync: bool of true: - handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.} + handleEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure, gcsafe.} asyncSock: PAsyncSocket myDispatcher: PDispatcher of false: @@ -445,7 +447,7 @@ proc asyncIRC*(address: string, port: TPort = 6667.TPort, realname = "NimrodBot", serverPass = "", joinChans: seq[string] = @[], msgLimit: bool = true, - ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure.} + ircEvent: proc (irc: PAsyncIRC, ev: TIRCEvent) {.closure,gcsafe.} ): PAsyncIRC = ## Use this function if you want to use asyncio's dispatcher. ## diff --git a/lib/pure/math.nim b/lib/pure/math.nim index 3997b059f..e4aecd272 100644 --- a/lib/pure/math.nim +++ b/lib/pure/math.nim @@ -12,6 +12,8 @@ ## Basic math routines for Nimrod. ## This module is available for the JavaScript target. +include "system/inclrtl" + {.push debugger:off .} # the user does not want to trace a part # of the standard library! @@ -127,25 +129,25 @@ proc variance*(x: openArray[float]): float {.noSideEffect.} = result = result + diff*diff result = result / toFloat(len(x)) -proc random*(max: int): int +proc random*(max: int): int {.gcsafe.} ## returns a random number in the range 0..max-1. The sequence of ## random number is always the same, unless `randomize` is called ## which initializes the random number generator with a "random" ## number, i.e. a tickcount. when not defined(windows): - proc random*(max: float): float + proc random*(max: float): float {.gcsafe.} ## returns a random number in the range 0..<max. The sequence of ## random number is always the same, unless `randomize` is called ## which initializes the random number generator with a "random" ## number, i.e. a tickcount. This is currently not supported for windows. -proc randomize*() +proc randomize*() {.gcsafe.} ## initializes the random number generator with a "random" ## number, i.e. a tickcount. Note: Does nothing for the JavaScript target, ## as JavaScript does not support this. -proc randomize*(seed: int) +proc randomize*(seed: int) {.gcsafe.} ## initializes the random number generator with a specific seed. ## Note: Does nothing for the JavaScript target, ## as JavaScript does not support this. @@ -227,8 +229,8 @@ else: result = int(floor(mathrandom() * float(max))) proc random(max: float): float = result = float(mathrandom() * float(max)) - proc randomize() = nil - proc randomize(seed: int) = nil + proc randomize() = discard + proc randomize(seed: int) = discard proc sqrt*(x: float): float {.importc: "Math.sqrt", nodecl.} proc ln*(x: float): float {.importc: "Math.log", nodecl.} diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 30a5ae949..00a33db75 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -1567,7 +1567,7 @@ when defined(windows): # ourselves. This has the additional benefit that the program's behaviour # is always the same -- independent of the used C compiler. var - ownArgv: seq[string] + ownArgv {.threadvar.}: seq[string] proc paramCount*(): int {.rtl, extern: "nos$1", tags: [FReadIO].} = ## Returns the number of `command line arguments`:idx: given to the diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index 5d6848565..6e250f9d5 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -137,6 +137,14 @@ proc startProcess*(command: string, ## to `startProcess`. See the documentation of ``TProcessOption`` for the ## meaning of these flags. You need to `close` the process when done. ## + ## Note that you can't pass any `args` if you use the option + ## ``poEvalCommand``, which invokes the system shell to run the specified + ## `command`. In this situation you have to concatenate manually the contents + ## of `args` to `command` carefully escaping/quoting any special characters, + ## since it will be passed *as is* to the system shell. Each system/shell may + ## feature different escaping rules, so try to avoid this kind of shell + ## invokation if possible as it leads to non portable software. + ## ## Return value: The newly created process object. Nil is never returned, ## but ``EOS`` is raised in case of an error. @@ -607,11 +615,13 @@ elif not defined(useNimRtl): optionPoStdErrToStdOut: bool when not defined(useFork): - proc startProcessAuxSpawn(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} - proc startProcessAuxFork(data: TStartProcessData): TPid {.tags: [FExecIO, FReadEnv].} + proc startProcessAuxSpawn(data: TStartProcessData): TPid {. + tags: [FExecIO, FReadEnv], gcsafe.} + proc startProcessAuxFork(data: TStartProcessData): TPid {. + tags: [FExecIO, FReadEnv], gcsafe.} {.push stacktrace: off, profiler: off.} proc startProcessAfterFork(data: ptr TStartProcessData) {. - tags: [FExecIO, FReadEnv], cdecl.} + tags: [FExecIO, FReadEnv], cdecl, gcsafe.} {.pop.} proc startProcess(command: string, @@ -633,7 +643,7 @@ elif not defined(useNimRtl): if poEvalCommand in options: sysCommand = "/bin/sh" sysArgsRaw = @[sysCommand, "-c", command] - assert args.len == 0 + assert args.len == 0, "`args` has to be empty when using poEvalCommand." else: sysCommand = command sysArgsRaw = @[command] @@ -938,7 +948,7 @@ elif not defined(useNimRtl): proc execCmdEx*(command: string, options: set[TProcessOption] = { poStdErrToStdOut, poUsePath}): tuple[ output: TaintedString, - exitCode: int] {.tags: [FExecIO, FReadIO].} = + exitCode: int] {.tags: [FExecIO, FReadIO], gcsafe.} = ## a convenience proc that runs the `command`, grabs all its output and ## exit code and returns both. var p = startCmd(command, options) diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index f3249b107..727a8efd8 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -70,7 +70,7 @@ const SymChars: TCharSet = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\x80'..'\xFF', '.', '/', '\\'} -proc rawGetTok(c: var TCfgParser, tok: var TToken) +proc rawGetTok(c: var TCfgParser, tok: var TToken) {.gcsafe.} proc open*(c: var TCfgParser, input: PStream, filename: string, lineOffset = 0) {. diff --git a/lib/pure/redis.nim b/lib/pure/redis.nim index f4c45b99c..959f5c6ef 100644 --- a/lib/pure/redis.nim +++ b/lib/pure/redis.nim @@ -19,10 +19,21 @@ import sockets, os, strutils, parseutils const redisNil* = "\0\0" +type + PPipeline = ref object + enabled: bool + buffer: string + expected: int ## number of replies expected if pipelined + +type + TSendMode = enum + normal, pipelined, multiple + type TRedis* {.pure, final.} = object socket: TSocket connected: bool + pipeline: PPipeline TRedisStatus* = string TRedisInteger* = biggestInt @@ -32,25 +43,42 @@ type EInvalidReply* = object of ESynch ## Invalid reply from redis ERedis* = object of ESynch ## Error in redis +proc newPipeline(): PPipeline = + new(result) + result.buffer = "" + result.enabled = false + result.expected = 0 + proc open*(host = "localhost", port = 6379.TPort): TRedis = ## Opens a connection to the redis server. result.socket = socket(buffered = false) if result.socket == InvalidSocket: OSError(OSLastError()) result.socket.connect(host, port) + result.pipeline = newPipeline() proc raiseInvalidReply(expected, got: char) = raise newException(EInvalidReply, "Expected '$1' at the beginning of a status reply got '$2'" % [$expected, $got]) -proc raiseNoOK(status: string) = - if status != "QUEUED" and status != "OK": +proc raiseNoOK(status: string, pipelineEnabled:bool) = + if pipelineEnabled and not (status == "QUEUED" or status == "PIPELINED"): + raise newException(EInvalidReply, "Expected \"QUEUED\" or \"PIPELINED\" got \"$1\"" % status) + elif not pipelineEnabled and status != "OK": raise newException(EInvalidReply, "Expected \"OK\" got \"$1\"" % status) -proc parseStatus(r: TRedis): TRedisStatus = - var line = "" - r.socket.readLine(line) +template readSocket(r: TRedis, dummyVal:expr): stmt = + var line {.inject.} :TaintedString = "" + if r.pipeline.enabled: + return dummyVal + else: + readLine(r.socket, line) + +proc parseStatus(r: TRedis, line: string = ""): TRedisStatus = + if r.pipeline.enabled: + return "PIPELINED" + if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -60,13 +88,16 @@ proc parseStatus(r: TRedis): TRedisStatus = raiseInvalidReply('+', line[0]) return line.substr(1) # Strip '+' - -proc parseInteger(r: TRedis): TRedisInteger = - var line = "" - r.socket.readLine(line) - if line == "+QUEUED": # inside of multi - return -1 +proc readStatus(r:TRedis): TRedisStatus = + r.readSocket("PIPELINED") + return r.parseStatus(line) + +proc parseInteger(r: TRedis, line: string = ""): TRedisInteger = + if r.pipeline.enabled: return -1 + + #if line == "+QUEUED": # inside of multi + # return -1 if line == "": raise newException(ERedis, "Server closed connection prematurely") @@ -80,12 +111,18 @@ proc parseInteger(r: TRedis): TRedisInteger = if parseBiggestInt(line, result, 1) == 0: raise newException(EInvalidReply, "Unable to parse integer.") +proc readInteger(r: TRedis): TRedisInteger = + r.readSocket(-1) + return r.parseInteger(line) + proc recv(sock: TSocket, size: int): TaintedString = result = newString(size).TaintedString if sock.recv(cstring(result), size) != size: raise newException(EInvalidReply, "recv failed") -proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = +proc parseSingleString(r: TRedis, line:string, allowMBNil = False): TRedisString = + if r.pipeline.enabled: return "" + # Error. if line[0] == '-': raise newException(ERedis, strip(line)) @@ -95,9 +132,6 @@ proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = if line == "*-1": return RedisNil - if line == "+QUEUED" or line == "+OK" : # inside of a transaction (multi) - return nil - if line[0] != '$': raiseInvalidReply('$', line[0]) @@ -108,41 +142,84 @@ proc parseSingle(r: TRedis, line:string, allowMBNil = False): TRedisString = var s = r.socket.recv(numBytes+2) result = strip(s.string) -proc parseMultiLines(r: TRedis, countLine:string): TRedisList = +proc readSingleString(r: TRedis): TRedisString = + r.readSocket("") + return r.parseSingleString(line) + +proc readNext(r: TRedis): TRedisList + +proc parseArrayLines(r: TRedis, countLine:string): TRedisList = if countLine.string[0] != '*': raiseInvalidReply('*', countLine.string[0]) var numElems = parseInt(countLine.string.substr(1)) if numElems == -1: return nil result = @[] + for i in 1..numElems: - var line = "" - r.socket.readLine(line.TaintedString) - if line[0] == '*': # after exec() may contain more multi-bulk replies - var parsed = r.parseMultiLines(line) + var parsed = r.readNext() + if not isNil(parsed): for item in parsed: result.add(item) - else: - result.add(r.parseSingle(line)) - -proc parseBulk(r: TRedis, allowMBNil = False): TRedisString = - var line = "" - r.socket.readLine(line.TaintedString) - if line == "+QUEUED" or line == "+OK": # inside of a transaction (multi) - return nil - - return r.parseSingle(line, allowMBNil) +proc readArrayLines(r: TRedis): TRedisList = + r.readSocket(nil) + return r.parseArrayLines(line) + +proc parseBulkString(r: TRedis, allowMBNil = False, line:string = ""): TRedisString = + if r.pipeline.enabled: return "" + + return r.parseSingleString(line, allowMBNil) + +proc readBulkString(r: TRedis, allowMBNil = false): TRedisString = + r.readSocket("") + return r.parseBulkString(allowMBNil, line) + +proc readArray(r: TRedis): TRedisList = + r.readSocket(@[]) + return r.parseArrayLines(line) + +proc readNext(r: TRedis): TRedisList = + r.readSocket(@[]) + + var res = case line[0] + of '+', '-': @[r.parseStatus(line)] + of ':': @[$(r.parseInteger(line))] + of '$': @[r.parseBulkString(true,line)] + of '*': r.parseArrayLines(line) + else: + raise newException(EInvalidReply, "readNext failed on line: " & line) + nil + r.pipeline.expected -= 1 + return res + +proc flushPipeline*(r: TRedis, wasMulti = false): TRedisList = + ## Send buffered commands, clear buffer, return results + if r.pipeline.buffer.len > 0: + r.socket.send(r.pipeline.buffer) + r.pipeline.buffer = "" + + r.pipeline.enabled = false + result = @[] + + var tot = r.pipeline.expected -proc parseMultiBulk(r: TRedis): TRedisList = - var line = TaintedString"" - r.socket.readLine(line) + for i in 0..tot-1: + var ret = r.readNext() + for item in ret: + if not (item.contains("OK") or item.contains("QUEUED")): + result.add(item) - if line == "+QUEUED": # inside of a transaction (multi) - return nil - - return r.parseMultiLines(line) + r.pipeline.expected = 0 +proc startPipelining*(r: TRedis) = + ## Enable command pipelining (reduces network roundtrips). + ## Note that when enabled, you must call flushPipeline to actually send commands, except + ## for multi/exec() which enable and flush the pipeline automatically. + ## Commands return immediately with dummy values; actual results returned from + ## flushPipeline() or exec() + r.pipeline.expected = 0 + r.pipeline.enabled = true proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = var request = "*" & $(1 + args.len()) & "\c\L" @@ -151,7 +228,12 @@ proc sendCommand(r: TRedis, cmd: string, args: varargs[string]) = for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - r.socket.send(request) + + if r.pipeline.enabled: + r.pipeline.buffer.add(request) + r.pipeline.expected += 1 + else: + r.socket.send(request) proc sendCommand(r: TRedis, cmd: string, arg1: string, args: varargs[string]) = @@ -163,75 +245,80 @@ proc sendCommand(r: TRedis, cmd: string, arg1: string, for i in items(args): request.add("$" & $i.len() & "\c\L") request.add(i & "\c\L") - r.socket.send(request) + + if r.pipeline.enabled: + r.pipeline.expected += 1 + r.pipeline.buffer.add(request) + else: + r.socket.send(request) # Keys proc del*(r: TRedis, keys: varargs[string]): TRedisInteger = ## Delete a key or multiple keys r.sendCommand("DEL", keys) - return r.parseInteger() + return r.readInteger() proc exists*(r: TRedis, key: string): bool = ## Determine if a key exists r.sendCommand("EXISTS", key) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc expire*(r: TRedis, key: string, seconds: int): bool = ## Set a key's time to live in seconds. Returns `false` if the key could ## not be found or the timeout could not be set. r.sendCommand("EXPIRE", key, $seconds) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc expireAt*(r: TRedis, key: string, timestamp: int): bool = ## Set the expiration for a key as a UNIX timestamp. Returns `false` ## if the key could not be found or the timeout could not be set. r.sendCommand("EXPIREAT", key, $timestamp) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc keys*(r: TRedis, pattern: string): TRedisList = ## Find all keys matching the given pattern r.sendCommand("KEYS", pattern) - return r.parseMultiBulk() + return r.readArray() proc move*(r: TRedis, key: string, db: int): bool = ## Move a key to another database. Returns `true` on a successful move. r.sendCommand("MOVE", key, $db) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc persist*(r: TRedis, key: string): bool = ## Remove the expiration from a key. ## Returns `true` when the timeout was removed. r.sendCommand("PERSIST", key) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc randomKey*(r: TRedis): TRedisString = ## Return a random key from the keyspace r.sendCommand("RANDOMKEY") - return r.parseBulk() + return r.readBulkString() proc rename*(r: TRedis, key, newkey: string): TRedisStatus = ## Rename a key. ## ## **WARNING:** Overwrites `newkey` if it exists! r.sendCommand("RENAME", key, newkey) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc renameNX*(r: TRedis, key, newkey: string): bool = ## Same as ``rename`` but doesn't continue if `newkey` exists. ## Returns `true` if key was renamed. r.sendCommand("RENAMENX", key, newkey) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc ttl*(r: TRedis, key: string): TRedisInteger = ## Get the time to live for a key r.sendCommand("TTL", key) - return r.parseInteger() + return r.readInteger() proc keyType*(r: TRedis, key: string): TRedisStatus = ## Determine the type stored at key r.sendCommand("TYPE", key) - return r.parseStatus() + return r.readStatus() # Strings @@ -239,125 +326,125 @@ proc keyType*(r: TRedis, key: string): TRedisStatus = proc append*(r: TRedis, key, value: string): TRedisInteger = ## Append a value to a key r.sendCommand("APPEND", key, value) - return r.parseInteger() + return r.readInteger() proc decr*(r: TRedis, key: string): TRedisInteger = ## Decrement the integer value of a key by one r.sendCommand("DECR", key) - return r.parseInteger() + return r.readInteger() proc decrBy*(r: TRedis, key: string, decrement: int): TRedisInteger = ## Decrement the integer value of a key by the given number r.sendCommand("DECRBY", key, $decrement) - return r.parseInteger() + return r.readInteger() proc get*(r: TRedis, key: string): TRedisString = ## Get the value of a key. Returns `redisNil` when `key` doesn't exist. r.sendCommand("GET", key) - return r.parseBulk() + return r.readBulkString() proc getBit*(r: TRedis, key: string, offset: int): TRedisInteger = ## Returns the bit value at offset in the string value stored at key r.sendCommand("GETBIT", key, $offset) - return r.parseInteger() + return r.readInteger() proc getRange*(r: TRedis, key: string, start, stop: int): TRedisString = ## Get a substring of the string stored at a key r.sendCommand("GETRANGE", key, $start, $stop) - return r.parseBulk() + return r.readBulkString() proc getSet*(r: TRedis, key: string, value: string): TRedisString = ## Set the string value of a key and return its old value. Returns `redisNil` ## when key doesn't exist. r.sendCommand("GETSET", key, value) - return r.parseBulk() + return r.readBulkString() proc incr*(r: TRedis, key: string): TRedisInteger = ## Increment the integer value of a key by one. r.sendCommand("INCR", key) - return r.parseInteger() + return r.readInteger() proc incrBy*(r: TRedis, key: string, increment: int): TRedisInteger = ## Increment the integer value of a key by the given number r.sendCommand("INCRBY", key, $increment) - return r.parseInteger() + return r.readInteger() proc setk*(r: TRedis, key, value: string) = ## Set the string value of a key. ## ## NOTE: This function had to be renamed due to a clash with the `set` type. r.sendCommand("SET", key, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc setNX*(r: TRedis, key, value: string): bool = ## Set the value of a key, only if the key does not exist. Returns `true` ## if the key was set. r.sendCommand("SETNX", key, value) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc setBit*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Sets or clears the bit at offset in the string value stored at key r.sendCommand("SETBIT", key, $offset, value) - return r.parseInteger() + return r.readInteger() proc setEx*(r: TRedis, key: string, seconds: int, value: string): TRedisStatus = ## Set the value and expiration of a key r.sendCommand("SETEX", key, $seconds, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc setRange*(r: TRedis, key: string, offset: int, value: string): TRedisInteger = ## Overwrite part of a string at key starting at the specified offset r.sendCommand("SETRANGE", key, $offset, value) - return r.parseInteger() + return r.readInteger() proc strlen*(r: TRedis, key: string): TRedisInteger = ## Get the length of the value stored in a key. Returns 0 when key doesn't ## exist. r.sendCommand("STRLEN", key) - return r.parseInteger() + return r.readInteger() # Hashes proc hDel*(r: TRedis, key, field: string): bool = ## Delete a hash field at `key`. Returns `true` if the field was removed. r.sendCommand("HDEL", key, field) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc hExists*(r: TRedis, key, field: string): bool = ## Determine if a hash field exists. r.sendCommand("HEXISTS", key, field) - return r.parseInteger() == 1 + return r.readInteger() == 1 proc hGet*(r: TRedis, key, field: string): TRedisString = ## Get the value of a hash field r.sendCommand("HGET", key, field) - return r.parseBulk() + return r.readBulkString() proc hGetAll*(r: TRedis, key: string): TRedisList = ## Get all the fields and values in a hash r.sendCommand("HGETALL", key) - return r.parseMultiBulk() + return r.readArray() proc hIncrBy*(r: TRedis, key, field: string, incr: int): TRedisInteger = ## Increment the integer value of a hash field by the given number r.sendCommand("HINCRBY", key, field, $incr) - return r.parseInteger() + return r.readInteger() proc hKeys*(r: TRedis, key: string): TRedisList = ## Get all the fields in a hash r.sendCommand("HKEYS", key) - return r.parseMultiBulk() + return r.readArray() proc hLen*(r: TRedis, key: string): TRedisInteger = ## Get the number of fields in a hash r.sendCommand("HLEN", key) - return r.parseInteger() + return r.readInteger() proc hMGet*(r: TRedis, key: string, fields: varargs[string]): TRedisList = ## Get the values of all the given hash fields r.sendCommand("HMGET", key, fields) - return r.parseMultiBulk() + return r.readArray() proc hMSet*(r: TRedis, key: string, fieldValues: openarray[tuple[field, value: string]]) = @@ -367,22 +454,22 @@ proc hMSet*(r: TRedis, key: string, args.add(field) args.add(value) r.sendCommand("HMSET", args) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc hSet*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the string value of a hash field r.sendCommand("HSET", key, field, value) - return r.parseInteger() + return r.readInteger() proc hSetNX*(r: TRedis, key, field, value: string): TRedisInteger = ## Set the value of a hash field, only if the field does **not** exist r.sendCommand("HSETNX", key, field, value) - return r.parseInteger() + return r.readInteger() proc hVals*(r: TRedis, key: string): TRedisList = ## Get all the values in a hash r.sendCommand("HVALS", key) - return r.parseMultiBulk() + return r.readArray() # Lists @@ -393,7 +480,7 @@ proc bLPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BLPOP", args) - return r.parseMultiBulk() + return r.readArray() proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = ## Remove and get the *last* element in a list, or block until one @@ -402,7 +489,7 @@ proc bRPop*(r: TRedis, keys: varargs[string], timeout: int): TRedisList = for i in items(keys): args.add(i) args.add($timeout) r.sendCommand("BRPOP", args) - return r.parseMultiBulk() + return r.readArray() proc bRPopLPush*(r: TRedis, source, destination: string, timeout: int): TRedisString = @@ -411,29 +498,29 @@ proc bRPopLPush*(r: TRedis, source, destination: string, ## ## http://redis.io/commands/brpoplpush r.sendCommand("BRPOPLPUSH", source, destination, $timeout) - return r.parseBulk(true) # Multi-Bulk nil allowed. + return r.readBulkString(true) # Multi-Bulk nil allowed. proc lIndex*(r: TRedis, key: string, index: int): TRedisString = ## Get an element from a list by its index r.sendCommand("LINDEX", key, $index) - return r.parseBulk() + return r.readBulkString() proc lInsert*(r: TRedis, key: string, before: bool, pivot, value: string): TRedisInteger = ## Insert an element before or after another element in a list var pos = if before: "BEFORE" else: "AFTER" r.sendCommand("LINSERT", key, pos, pivot, value) - return r.parseInteger() + return r.readInteger() proc lLen*(r: TRedis, key: string): TRedisInteger = ## Get the length of a list r.sendCommand("LLEN", key) - return r.parseInteger() + return r.readInteger() proc lPop*(r: TRedis, key: string): TRedisString = ## Remove and get the first element in a list r.sendCommand("LPOP", key) - return r.parseBulk() + return r.readBulkString() proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Prepend a value to a list. Returns the length of the list after the push. @@ -444,39 +531,39 @@ proc lPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = r.sendCommand("LPUSH", key, value) else: r.sendCommand("LPUSHX", key, value) - return r.parseInteger() + return r.readInteger() proc lRange*(r: TRedis, key: string, start, stop: int): TRedisList = ## Get a range of elements from a list. Returns `nil` when `key` ## doesn't exist. r.sendCommand("LRANGE", key, $start, $stop) - return r.parseMultiBulk() + return r.readArray() proc lRem*(r: TRedis, key: string, value: string, count: int = 0): TRedisInteger = ## Remove elements from a list. Returns the number of elements that have been ## removed. r.sendCommand("LREM", key, $count, value) - return r.parseInteger() + return r.readInteger() proc lSet*(r: TRedis, key: string, index: int, value: string) = ## Set the value of an element in a list by its index r.sendCommand("LSET", key, $index, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) -proc lTrim*(r: TRedis, key: string, start, stop: int) = +proc lTrim*(r: TRedis, key: string, start, stop: int) = ## Trim a list to the specified range r.sendCommand("LTRIM", key, $start, $stop) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc rPop*(r: TRedis, key: string): TRedisString = ## Remove and get the last element in a list r.sendCommand("RPOP", key) - return r.parseBulk() + return r.readBulkString() proc rPopLPush*(r: TRedis, source, destination: string): TRedisString = ## Remove the last element in a list, append it to another list and return it r.sendCommand("RPOPLPUSH", source, destination) - return r.parseBulk() + return r.readBulkString() proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = ## Append a value to a list. Returns the length of the list after the push. @@ -487,106 +574,106 @@ proc rPush*(r: TRedis, key, value: string, create: bool = True): TRedisInteger = r.sendCommand("RPUSH", key, value) else: r.sendCommand("RPUSHX", key, value) - return r.parseInteger() + return r.readInteger() # Sets proc sadd*(r: TRedis, key: string, member: string): TRedisInteger = ## Add a member to a set r.sendCommand("SADD", key, member) - return r.parseInteger() + return r.readInteger() proc scard*(r: TRedis, key: string): TRedisInteger = ## Get the number of members in a set r.sendCommand("SCARD", key) - return r.parseInteger() + return r.readInteger() proc sdiff*(r: TRedis, keys: varargs[string]): TRedisList = ## Subtract multiple sets r.sendCommand("SDIFF", keys) - return r.parseMultiBulk() + return r.readArray() proc sdiffstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = ## Subtract multiple sets and store the resulting set in a key r.sendCommand("SDIFFSTORE", destination, keys) - return r.parseInteger() + return r.readInteger() proc sinter*(r: TRedis, keys: varargs[string]): TRedisList = ## Intersect multiple sets r.sendCommand("SINTER", keys) - return r.parseMultiBulk() + return r.readArray() proc sinterstore*(r: TRedis, destination: string, keys: varargs[string]): TRedisInteger = ## Intersect multiple sets and store the resulting set in a key r.sendCommand("SINTERSTORE", destination, keys) - return r.parseInteger() + return r.readInteger() proc sismember*(r: TRedis, key: string, member: string): TRedisInteger = ## Determine if a given value is a member of a set r.sendCommand("SISMEMBER", key, member) - return r.parseInteger() + return r.readInteger() proc smembers*(r: TRedis, key: string): TRedisList = ## Get all the members in a set r.sendCommand("SMEMBERS", key) - return r.parseMultiBulk() + return r.readArray() proc smove*(r: TRedis, source: string, destination: string, member: string): TRedisInteger = ## Move a member from one set to another r.sendCommand("SMOVE", source, destination, member) - return r.parseInteger() + return r.readInteger() proc spop*(r: TRedis, key: string): TRedisString = ## Remove and return a random member from a set r.sendCommand("SPOP", key) - return r.parseBulk() + return r.readBulkString() proc srandmember*(r: TRedis, key: string): TRedisString = ## Get a random member from a set r.sendCommand("SRANDMEMBER", key) - return r.parseBulk() + return r.readBulkString() proc srem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a set r.sendCommand("SREM", key, member) - return r.parseInteger() + return r.readInteger() proc sunion*(r: TRedis, keys: varargs[string]): TRedisList = ## Add multiple sets r.sendCommand("SUNION", keys) - return r.parseMultiBulk() + return r.readArray() proc sunionstore*(r: TRedis, destination: string, key: varargs[string]): TRedisInteger = ## Add multiple sets and store the resulting set in a key r.sendCommand("SUNIONSTORE", destination, key) - return r.parseInteger() + return r.readInteger() # Sorted sets proc zadd*(r: TRedis, key: string, score: int, member: string): TRedisInteger = ## Add a member to a sorted set, or update its score if it already exists r.sendCommand("ZADD", key, $score, member) - return r.parseInteger() + return r.readInteger() proc zcard*(r: TRedis, key: string): TRedisInteger = ## Get the number of members in a sorted set r.sendCommand("ZCARD", key) - return r.parseInteger() + return r.readInteger() proc zcount*(r: TRedis, key: string, min: string, max: string): TRedisInteger = ## Count the members in a sorted set with scores within the given values r.sendCommand("ZCOUNT", key, min, max) - return r.parseInteger() + return r.readInteger() proc zincrby*(r: TRedis, key: string, increment: string, member: string): TRedisString = ## Increment the score of a member in a sorted set r.sendCommand("ZINCRBY", key, increment, member) - return r.parseBulk() + return r.readBulkString() proc zinterstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -605,7 +692,7 @@ proc zinterstore*(r: TRedis, destination: string, numkeys: string, r.sendCommand("ZINTERSTORE", args) - return r.parseInteger() + return r.readInteger() proc zrange*(r: TRedis, key: string, start: string, stop: string, withScores: bool): TRedisList = @@ -614,7 +701,7 @@ proc zrange*(r: TRedis, key: string, start: string, stop: string, r.sendCommand("ZRANGE", key, start, stop) else: r.sendCommand("ZRANGE", "WITHSCORES", key, start, stop) - return r.parseMultiBulk() + return r.readArray() proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, @@ -629,29 +716,29 @@ proc zrangebyscore*(r: TRedis, key: string, min: string, max: string, args.add($limitCount) r.sendCommand("ZRANGEBYSCORE", args) - return r.parseMultiBulk() + return r.readArray() proc zrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set r.sendCommand("ZRANK", key, member) - return r.parseBulk() + return r.readBulkString() proc zrem*(r: TRedis, key: string, member: string): TRedisInteger = ## Remove a member from a sorted set r.sendCommand("ZREM", key, member) - return r.parseInteger() + return r.readInteger() proc zremrangebyrank*(r: TRedis, key: string, start: string, stop: string): TRedisInteger = ## Remove all members in a sorted set within the given indexes r.sendCommand("ZREMRANGEBYRANK", key, start, stop) - return r.parseInteger() + return r.readInteger() proc zremrangebyscore*(r: TRedis, key: string, min: string, max: string): TRedisInteger = ## Remove all members in a sorted set within the given scores r.sendCommand("ZREMRANGEBYSCORE", key, min, max) - return r.parseInteger() + return r.readInteger() proc zrevrange*(r: TRedis, key: string, start: string, stop: string, withScore: bool): TRedisList = @@ -660,7 +747,7 @@ proc zrevrange*(r: TRedis, key: string, start: string, stop: string, if withScore: r.sendCommand("ZREVRANGE", "WITHSCORE", key, start, stop) else: r.sendCommand("ZREVRANGE", key, start, stop) - return r.parseMultiBulk() + return r.readArray() proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, withScore: bool = false, limit: bool = False, @@ -676,18 +763,18 @@ proc zrevrangebyscore*(r: TRedis, key: string, min: string, max: string, args.add($limitCount) r.sendCommand("ZREVRANGEBYSCORE", args) - return r.parseMultiBulk() + return r.readArray() proc zrevrank*(r: TRedis, key: string, member: string): TRedisString = ## Determine the index of a member in a sorted set, with ## scores ordered from high to low r.sendCommand("ZREVRANK", key, member) - return r.parseBulk() + return r.readBulkString() proc zscore*(r: TRedis, key: string, member: string): TRedisString = ## Get the score associated with the given member in a sorted set r.sendCommand("ZSCORE", key, member) - return r.parseBulk() + return r.readBulkString() proc zunionstore*(r: TRedis, destination: string, numkeys: string, keys: openarray[string], weights: openarray[string] = [], @@ -705,7 +792,7 @@ proc zunionstore*(r: TRedis, destination: string, numkeys: string, r.sendCommand("ZUNIONSTORE", args) - return r.parseInteger() + return r.readInteger() # Pub/Sub @@ -720,7 +807,7 @@ proc psubscribe*(r: TRedis, pattern: openarray[string]): ???? = proc publish*(r: TRedis, channel: string, message: string): TRedisInteger = ## Post a message to a channel r.socket.send("PUBLISH $# $#\c\L" % [channel, message]) - return r.parseInteger() + return r.readInteger() proc punsubscribe*(r: TRedis, [pattern: openarray[string], : string): ???? = ## Stop listening for messages posted to channels matching the given patterns @@ -744,92 +831,96 @@ proc unsubscribe*(r: TRedis, [channel: openarray[string], : string): ???? = proc discardMulti*(r: TRedis) = ## Discard all commands issued after MULTI r.sendCommand("DISCARD") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc exec*(r: TRedis): TRedisList = ## Execute all commands issued after MULTI - r.sendCommand("EXEC") - - return r.parseMultiBulk() + r.sendCommand("EXEC") + r.pipeline.enabled = false + # Will reply with +OK for MULTI/EXEC and +QUEUED for every command + # between, then with the results + return r.flushPipeline(true) + proc multi*(r: TRedis) = ## Mark the start of a transaction block + r.startPipelining() r.sendCommand("MULTI") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc unwatch*(r: TRedis) = ## Forget about all watched keys r.sendCommand("UNWATCH") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc watch*(r: TRedis, key: varargs[string]) = ## Watch the given keys to determine execution of the MULTI/EXEC block r.sendCommand("WATCH", key) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) # Connection proc auth*(r: TRedis, password: string) = ## Authenticate to the server r.sendCommand("AUTH", password) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc echoServ*(r: TRedis, message: string): TRedisString = ## Echo the given string r.sendCommand("ECHO", message) - return r.parseBulk() + return r.readBulkString() proc ping*(r: TRedis): TRedisStatus = ## Ping the server r.sendCommand("PING") - return r.parseStatus() + return r.readStatus() proc quit*(r: TRedis) = ## Close the connection r.sendCommand("QUIT") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc select*(r: TRedis, index: int): TRedisStatus = ## Change the selected database for the current connection r.sendCommand("SELECT", $index) - return r.parseStatus() + return r.readStatus() # Server proc bgrewriteaof*(r: TRedis) = ## Asynchronously rewrite the append-only file r.sendCommand("BGREWRITEAOF") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc bgsave*(r: TRedis) = ## Asynchronously save the dataset to disk r.sendCommand("BGSAVE") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc configGet*(r: TRedis, parameter: string): TRedisList = ## Get the value of a configuration parameter r.sendCommand("CONFIG", "GET", parameter) - return r.parseMultiBulk() + return r.readArray() proc configSet*(r: TRedis, parameter: string, value: string) = ## Set a configuration parameter to the given value r.sendCommand("CONFIG", "SET", parameter, value) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc configResetStat*(r: TRedis) = ## Reset the stats returned by INFO r.sendCommand("CONFIG", "RESETSTAT") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc dbsize*(r: TRedis): TRedisInteger = ## Return the number of keys in the selected database r.sendCommand("DBSIZE") - return r.parseInteger() + return r.readInteger() proc debugObject*(r: TRedis, key: string): TRedisStatus = ## Get debugging information about a key r.sendCommand("DEBUG", "OBJECT", key) - return r.parseStatus() + return r.readStatus() proc debugSegfault*(r: TRedis) = ## Make the server crash @@ -838,34 +929,34 @@ proc debugSegfault*(r: TRedis) = proc flushall*(r: TRedis): TRedisStatus = ## Remove all keys from all databases r.sendCommand("FLUSHALL") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc flushdb*(r: TRedis): TRedisStatus = ## Remove all keys from the current database r.sendCommand("FLUSHDB") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc info*(r: TRedis): TRedisString = ## Get information and statistics about the server r.sendCommand("INFO") - return r.parseBulk() + return r.readBulkString() proc lastsave*(r: TRedis): TRedisInteger = ## Get the UNIX time stamp of the last successful save to disk r.sendCommand("LASTSAVE") - return r.parseInteger() + return r.readInteger() discard """ proc monitor*(r: TRedis) = ## Listen for all requests received by the server in real time r.socket.send("MONITOR\c\L") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) """ proc save*(r: TRedis) = ## Synchronously save the dataset to disk r.sendCommand("SAVE") - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) proc shutdown*(r: TRedis) = ## Synchronously save the dataset to disk and then shut down the server @@ -877,7 +968,7 @@ proc shutdown*(r: TRedis) = proc slaveof*(r: TRedis, host: string, port: string) = ## Make the server a slave of another instance, or promote it as master r.sendCommand("SLAVEOF", host, port) - raiseNoOK(r.parseStatus()) + raiseNoOK(r.readStatus(), r.pipeline.enabled) iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = ## Iterator for keys and values in a hash. @@ -890,33 +981,72 @@ iterator hPairs*(r: TRedis, key: string): tuple[key, value: string] = else: yield (k, i) k = "" - -when false: - # sorry, deactivated for the test suite - var r = open() - r.auth("pass") +proc someTests(r: TRedis, how: TSendMode):seq[string] = + var list:seq[string] = @[] + if how == pipelined: + r.startPipelining() + elif how == multiple: + r.multi() + r.setk("nim:test", "Testing something.") r.setk("nim:utf8", "こんにちは") r.setk("nim:esc", "\\ths ągt\\") - - echo r.get("nim:esc") - echo r.incr("nim:int") - echo r.incr("nim:int") - echo r.get("nim:int") - echo r.get("nim:utf8") - echo repr(r.get("blahasha")) - echo r.randomKey() - + r.setk("nim:int", "1") + list.add(r.get("nim:esc")) + list.add($(r.incr("nim:int"))) + list.add(r.get("nim:int")) + list.add(r.get("nim:utf8")) + list.add($(r.hSet("test1", "name", "A Test"))) + var res = r.hGetAll("test1") + for r in res: + list.add(r) + list.add(r.get("invalid_key")) + list.add($(r.lpush("mylist","itema"))) + list.add($(r.lpush("mylist","itemb"))) + r.ltrim("mylist",0,1) var p = r.lrange("mylist", 0, -1) + for i in items(p): - echo(" ", i) + if not isNil(i): + list.add(i) - echo(r.debugObject("test")) + list.add(r.debugObject("mylist")) r.configSet("timeout", "299") - for i in items(r.configGet("timeout")): echo ">> ", i + var g = r.configGet("timeout") + for i in items(g): + list.add(i) + + list.add(r.echoServ("BLAH")) + + case how + of normal: + return list + of pipelined: + return r.flushPipeline() + of multiple: + return r.exec() + +proc assertListsIdentical(listA, listB: seq[string]) = + assert(listA.len == listB.len) + var i = 0 + for item in listA: + assert(item == listB[i]) + i = i + 1 + +when isMainModule: + when false: + var r = open() + + # Test with no pipelining + var listNormal = r.someTests(normal) - echo r.echoServ("BLAH") + # Test with pipelining enabled + var listPipelined = r.someTests(pipelined) + assertListsIdentical(listNormal, listPipelined) + # Test with multi/exec() (automatic pipelining) + var listMulti = r.someTests(multiple) + assertListsIdentical(listNormal, listMulti) diff --git a/lib/pure/scgi.nim b/lib/pure/scgi.nim index a6a0faabc..45f837833 100644 --- a/lib/pure/scgi.nim +++ b/lib/pure/scgi.nim @@ -26,6 +26,8 @@ ## **Warning:** The API of this module is unstable, and therefore is subject ## to change. +include "system/inclrtl" + import sockets, strutils, os, strtabs, asyncio type @@ -82,7 +84,7 @@ type TAsyncScgiState = object handleRequest: proc (client: PAsyncSocket, - input: string, headers: PStringTable) {.closure.} + input: string, headers: PStringTable) {.closure,gcsafe.} asyncServer: PAsyncSocket disp: PDispatcher PAsyncScgiState* = ref TAsyncScgiState @@ -150,7 +152,7 @@ proc writeStatusOkTextContent*(c: TSocket, contentType = "text/html") = "Content-Type: $1\r\L\r\L" % contentType) proc run*(handleRequest: proc (client: TSocket, input: string, - headers: PStringTable): bool {.nimcall.}, + headers: PStringTable): bool {.nimcall,gcsafe.}, port = TPort(4000)) = ## encapsulates the SCGI object and main loop. var s: TScgiState @@ -246,7 +248,8 @@ proc handleAccept(sock: PAsyncSocket, s: PAsyncScgiState) = s.disp.register(client) proc open*(handleRequest: proc (client: PAsyncSocket, - input: string, headers: PStringTable) {.closure.}, + input: string, headers: PStringTable) {. + closure, gcsafe.}, port = TPort(4000), address = "127.0.0.1", reuseAddr = false): PAsyncScgiState = ## Creates an ``PAsyncScgiState`` object which serves as a SCGI server. diff --git a/lib/pure/sockets.nim b/lib/pure/sockets.nim index b6acc329f..8d96cbaaf 100644 --- a/lib/pure/sockets.nim +++ b/lib/pure/sockets.nim @@ -24,6 +24,8 @@ ## Asynchronous sockets are supported, however a better alternative is to use ## the `asyncio <asyncio.html>`_ module. +include "system/inclrtl" + {.deadCodeElim: on.} when hostOS == "solaris": @@ -544,7 +546,7 @@ proc acceptAddr*(server: TSocket, client: var TSocket, address: var string) {. else: SSLError("Unknown error") -proc setBlocking*(s: TSocket, blocking: bool) {.tags: [].} +proc setBlocking*(s: TSocket, blocking: bool) {.tags: [], gcsafe.} ## Sets blocking mode on socket when defined(ssl): diff --git a/lib/pure/streams.nim b/lib/pure/streams.nim index 3b6dc87a5..63622a26c 100644 --- a/lib/pure/streams.nim +++ b/lib/pure/streams.nim @@ -12,6 +12,8 @@ ## interface for Nimrod file objects (`TFile`) and strings. Other modules ## may provide other implementations for this standard stream interface. +include "system/inclrtl" + proc newEIO(msg: string): ref EIO = new(result) result.msg = msg @@ -23,15 +25,15 @@ type ## here shouldn't be used directly. They are ## accessible so that a stream implementation ## can override them. - closeImpl*: proc (s: PStream) {.nimcall, tags: [].} - atEndImpl*: proc (s: PStream): bool {.nimcall, tags: [].} - setPositionImpl*: proc (s: PStream, pos: int) {.nimcall, tags: [].} - getPositionImpl*: proc (s: PStream): int {.nimcall, tags: [].} + closeImpl*: proc (s: PStream) {.nimcall, tags: [], gcsafe.} + atEndImpl*: proc (s: PStream): bool {.nimcall, tags: [], gcsafe.} + setPositionImpl*: proc (s: PStream, pos: int) {.nimcall, tags: [], gcsafe.} + getPositionImpl*: proc (s: PStream): int {.nimcall, tags: [], gcsafe.} readDataImpl*: proc (s: PStream, buffer: pointer, - bufLen: int): int {.nimcall, tags: [FReadIO].} + bufLen: int): int {.nimcall, tags: [FReadIO], gcsafe.} writeDataImpl*: proc (s: PStream, buffer: pointer, bufLen: int) {.nimcall, - tags: [FWriteIO].} - flushImpl*: proc (s: PStream) {.nimcall, tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} + flushImpl*: proc (s: PStream) {.nimcall, tags: [FWriteIO], gcsafe.} proc flush*(s: PStream) = ## flushes the buffers that the stream `s` might use. diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 2fce235e2..fdff06b2a 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -135,38 +135,38 @@ type months*: int ## The number of months years*: int ## The number of years -proc getTime*(): TTime {.tags: [FTime].} +proc getTime*(): TTime {.tags: [FTime], gcsafe.} ## gets the current calendar time as a UNIX epoch value (number of seconds ## elapsed since 1970) with integer precission. Use epochTime for higher ## resolution. -proc getLocalTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [].} +proc getLocalTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [], gcsafe.} ## converts the calendar time `t` to broken-time representation, ## expressed relative to the user's specified time zone. -proc getGMTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [].} +proc getGMTime*(t: TTime): TTimeInfo {.tags: [FTime], raises: [], gcsafe.} ## converts the calendar time `t` to broken-down time representation, ## expressed in Coordinated Universal Time (UTC). -proc timeInfoToTime*(timeInfo: TTimeInfo): TTime {.tags: [].} +proc timeInfoToTime*(timeInfo: TTimeInfo): TTime {.tags: [], gcsafe.} ## converts a broken-down time structure to ## calendar time representation. The function ignores the specified ## contents of the structure members `weekday` and `yearday` and recomputes ## them from the other information in the broken-down time structure. -proc fromSeconds*(since1970: float): TTime {.tags: [], raises: [].} +proc fromSeconds*(since1970: float): TTime {.tags: [], raises: [], gcsafe.} ## Takes a float which contains the number of seconds since the unix epoch and ## returns a time object. -proc fromSeconds*(since1970: int64): TTime {.tags: [], raises: [].} = +proc fromSeconds*(since1970: int64): TTime {.tags: [], raises: [], gcsafe.} = ## Takes an int which contains the number of seconds since the unix epoch and ## returns a time object. fromSeconds(float(since1970)) -proc toSeconds*(time: TTime): float {.tags: [], raises: [].} +proc toSeconds*(time: TTime): float {.tags: [], raises: [], gcsafe.} ## Returns the time in seconds since the unix epoch. -proc `$` *(timeInfo: TTimeInfo): string {.tags: [], raises: [].} +proc `$` *(timeInfo: TTimeInfo): string {.tags: [], raises: [], gcsafe.} ## converts a `TTimeInfo` object to a string representation. -proc `$` *(time: TTime): string {.tags: [], raises: [].} +proc `$` *(time: TTime): string {.tags: [], raises: [], gcsafe.} ## converts a calendar time to a string representation. proc `-`*(a, b: TTime): int64 {. @@ -189,14 +189,15 @@ proc `==`*(a, b: TTime): bool {. result = a - b == 0 when not defined(JS): - proc getTzname*(): tuple[nonDST, DST: string] {.tags: [FTime], raises: [].} + proc getTzname*(): tuple[nonDST, DST: string] {.tags: [FTime], raises: [], + gcsafe.} ## returns the local timezone; ``nonDST`` is the name of the local non-DST ## timezone, ``DST`` is the name of the local DST timezone. -proc getTimezone*(): int {.tags: [FTime], raises: [].} +proc getTimezone*(): int {.tags: [FTime], raises: [], gcsafe.} ## returns the offset of the local (non-DST) timezone in seconds west of UTC. -proc getStartMilsecs*(): int {.deprecated, tags: [FTime].} +proc getStartMilsecs*(): int {.deprecated, tags: [FTime], gcsafe.} ## get the miliseconds from the start of the program. **Deprecated since ## version 0.8.10.** Use ``epochTime`` or ``cpuTime`` instead. diff --git a/lib/system.nim b/lib/system.nim index 3cb6b08d5..273820b82 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -77,7 +77,7 @@ type TNumber* = TInteger|TReal ## type class matching all number types - + proc defined*(x: expr): bool {.magic: "Defined", noSideEffect.} ## Special compile-time procedure that checks whether `x` is ## defined. `x` has to be an identifier or a qualified identifier. @@ -188,6 +188,13 @@ when not defined(niminheritable): when not defined(nimunion): {.pragma: unchecked.} +when defined(nimNewShared): + type + `shared`* {.magic: "Shared".} + guarded* {.magic: "Guarded".} + +include "system/inclrtl" + const NoFakeVars* = defined(NimrodVM) ## true if the backend doesn't support \ ## "fake variables" like 'var EBADF {.importc.}: cint'. @@ -784,7 +791,8 @@ proc `is` *[T, S](x: T, y: S): bool {.magic: "Is", noSideEffect.} ## assert(test[int](3) == 3) ## assert(test[string]("xyz") == 0) template `isnot` *(x, y: expr): expr {.immediate.} = not (x is y) - ## Negated version of `is`. Equivalent to `not(is(x,y))` + ## Negated version of `is`. Equivalent to ``not(x is y)``. + proc `of` *[T, S](x: T, y: S): bool {.magic: "Of", noSideEffect.} ## Checks if `x` has a type of `y` ## @@ -1021,8 +1029,6 @@ template sysAssert(cond: bool, msg: string) = echo "[SYSASSERT] ", msg quit 1 -include "system/inclrtl" - when not defined(JS) and not defined(nimrodVm) and hostOS != "standalone": include "system/cgprocs" @@ -1201,20 +1207,20 @@ proc substr*(s: string, first, last: int): string {. ## or `limit`:idx: a string's length. when not defined(nimrodVM): - proc zeroMem*(p: pointer, size: int) {.importc, noDecl.} + proc zeroMem*(p: pointer, size: int) {.importc, noDecl, gcsafe.} ## overwrites the contents of the memory at ``p`` with the value 0. ## Exactly ``size`` bytes will be overwritten. Like any procedure ## dealing with raw memory this is *unsafe*. proc copyMem*(dest, source: pointer, size: int) {. - importc: "memcpy", header: "<string.h>".} + importc: "memcpy", header: "<string.h>", gcsafe.} ## copies the contents from the memory at ``source`` to the memory ## at ``dest``. Exactly ``size`` bytes will be copied. The memory ## regions may not overlap. Like any procedure dealing with raw ## memory this is *unsafe*. proc moveMem*(dest, source: pointer, size: int) {. - importc: "memmove", header: "<string.h>".} + importc: "memmove", header: "<string.h>", gcsafe.} ## copies the contents from the memory at ``source`` to the memory ## at ``dest``. Exactly ``size`` bytes will be copied. The memory ## regions may overlap, ``moveMem`` handles this case appropriately @@ -1229,14 +1235,14 @@ when not defined(nimrodVM): ## *unsafe*. when hostOS != "standalone": - proc alloc*(size: int): pointer {.noconv, rtl, tags: [].} + proc alloc*(size: int): pointer {.noconv, rtl, tags: [], gcsafe.} ## allocates a new memory block with at least ``size`` bytes. The ## block has to be freed with ``realloc(block, 0)`` or ## ``dealloc(block)``. The block is not initialized, so reading ## from it before writing to it is undefined behaviour! ## The allocated memory belongs to its allocating thread! ## Use `allocShared` to allocate from a shared heap. - proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline.} = + proc createU*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe.} = ## allocates a new memory block with at least ``T.sizeof * size`` ## bytes. The block has to be freed with ``resize(block, 0)`` or ## ``free(block)``. The block is not initialized, so reading @@ -1244,14 +1250,14 @@ when not defined(nimrodVM): ## The allocated memory belongs to its allocating thread! ## Use `createSharedU` to allocate from a shared heap. cast[ptr T](alloc(T.sizeof * size)) - proc alloc0*(size: int): pointer {.noconv, rtl, tags: [].} + proc alloc0*(size: int): pointer {.noconv, rtl, tags: [], gcsafe.} ## allocates a new memory block with at least ``size`` bytes. The ## block has to be freed with ``realloc(block, 0)`` or ## ``dealloc(block)``. The block is initialized with all bytes ## containing zero, so it is somewhat safer than ``alloc``. ## The allocated memory belongs to its allocating thread! ## Use `allocShared0` to allocate from a shared heap. - proc create*(T: typedesc, size = 1.Positive): ptr T {.inline.} = + proc create*(T: typedesc, size = 1.Positive): ptr T {.inline, gcsafe.} = ## allocates a new memory block with at least ``T.sizeof * size`` ## bytes. The block has to be freed with ``resize(block, 0)`` or ## ``free(block)``. The block is initialized with all bytes @@ -1259,7 +1265,8 @@ when not defined(nimrodVM): ## The allocated memory belongs to its allocating thread! ## Use `createShared` to allocate from a shared heap. cast[ptr T](alloc0(T.sizeof * size)) - proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [].} + proc realloc*(p: pointer, newSize: int): pointer {.noconv, rtl, tags: [], + gcsafe.} ## grows or shrinks a given memory block. If p is **nil** then a new ## memory block is returned. In either way the block has at least ## ``newSize`` bytes. If ``newSize == 0`` and p is not **nil** @@ -1267,7 +1274,7 @@ when not defined(nimrodVM): ## be freed with ``dealloc``. ## The allocated memory belongs to its allocating thread! ## Use `reallocShared` to reallocate from a shared heap. - proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline.} = + proc resize*[T](p: ptr T, newSize: Natural): ptr T {.inline, gcsafe.} = ## grows or shrinks a given memory block. If p is **nil** then a new ## memory block is returned. In either way the block has at least ## ``T.sizeof * newSize`` bytes. If ``newSize == 0`` and p is not @@ -1276,7 +1283,7 @@ when not defined(nimrodVM): ## its allocating thread! ## Use `resizeShared` to reallocate from a shared heap. cast[ptr T](realloc(p, T.sizeof * newSize)) - proc dealloc*(p: pointer) {.noconv, rtl, tags: [].} + proc dealloc*(p: pointer) {.noconv, rtl, tags: [], gcsafe.} ## frees the memory allocated with ``alloc``, ``alloc0`` or ## ``realloc``. This procedure is dangerous! If one forgets to ## free the memory a leak occurs; if one tries to access freed @@ -1284,22 +1291,23 @@ when not defined(nimrodVM): ## or other memory may be corrupted. ## The freed memory must belong to its allocating thread! ## Use `deallocShared` to deallocate from a shared heap. - proc free*[T](p: ptr T) {.inline.} = + proc free*[T](p: ptr T) {.inline, gcsafe.} = dealloc(p) - proc allocShared*(size: int): pointer {.noconv, rtl.} + proc allocShared*(size: int): pointer {.noconv, rtl, gcsafe.} ## allocates a new memory block on the shared heap with at ## least ``size`` bytes. The block has to be freed with ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. The block ## is not initialized, so reading from it before writing to it is ## undefined behaviour! - proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline.} = + proc createSharedU*(T: typedesc, size = 1.Positive): ptr T {.inline, + gcsafe.} = ## allocates a new memory block on the shared heap with at ## least ``T.sizeof * size`` bytes. The block has to be freed with ## ``resizeShared(block, 0)`` or ``freeShared(block)``. The block ## is not initialized, so reading from it before writing to it is ## undefined behaviour! cast[ptr T](allocShared(T.sizeof * size)) - proc allocShared0*(size: int): pointer {.noconv, rtl.} + proc allocShared0*(size: int): pointer {.noconv, rtl, gcsafe.} ## allocates a new memory block on the shared heap with at ## least ``size`` bytes. The block has to be freed with ## ``reallocShared(block, 0)`` or ``deallocShared(block)``. @@ -1312,7 +1320,8 @@ when not defined(nimrodVM): ## The block is initialized with all bytes ## containing zero, so it is somewhat safer than ``createSharedU``. cast[ptr T](allocShared0(T.sizeof * size)) - proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl.} + proc reallocShared*(p: pointer, newSize: int): pointer {.noconv, rtl, + gcsafe.} ## grows or shrinks a given memory block on the heap. If p is **nil** ## then a new memory block is returned. In either way the block has at ## least ``newSize`` bytes. If ``newSize == 0`` and p is not **nil** @@ -1325,13 +1334,13 @@ when not defined(nimrodVM): ## not **nil** ``resizeShared`` calls ``freeShared(p)``. In other ## cases the block has to be freed with ``freeShared``. cast[ptr T](reallocShared(p, T.sizeof * newSize)) - proc deallocShared*(p: pointer) {.noconv, rtl.} + proc deallocShared*(p: pointer) {.noconv, rtl, gcsafe.} ## frees the memory allocated with ``allocShared``, ``allocShared0`` or ## ``reallocShared``. This procedure is dangerous! If one forgets to ## free the memory a leak occurs; if one tries to access freed ## memory (or just freeing it twice!) a core dump may happen ## or other memory may be corrupted. - proc freeShared*[T](p: ptr T) {.inline.} = + proc freeShared*[T](p: ptr T) {.inline, gcsafe.} = ## frees the memory allocated with ``createShared``, ``createSharedU`` or ## ``resizeShared``. This procedure is dangerous! If one forgets to ## free the memory a leak occurs; if one tries to access freed @@ -1892,7 +1901,7 @@ const nimrodStackTrace = compileOption("stacktrace") # of the code var - globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall.} + globalRaiseHook*: proc (e: ref E_Base): bool {.nimcall, gcsafe.} ## with this hook you can influence exception handling on a global level. ## If not nil, every 'raise' statement ends up calling this hook. Ordinary ## application code should never set this hook! You better know what you @@ -1900,7 +1909,7 @@ var ## exception is caught and does not propagate further through the call ## stack. - localRaiseHook* {.threadvar.}: proc (e: ref E_Base): bool {.nimcall.} + localRaiseHook* {.threadvar.}: proc (e: ref E_Base): bool {.nimcall, gcsafe.} ## with this hook you can influence exception handling on a ## thread local level. ## If not nil, every 'raise' statement ends up calling this hook. Ordinary @@ -1908,7 +1917,7 @@ var ## do when setting this. If ``localRaiseHook`` returns false, the exception ## is caught and does not propagate further through the call stack. - outOfMemHook*: proc () {.nimcall, tags: [].} + outOfMemHook*: proc () {.nimcall, tags: [], gcsafe.} ## set this variable to provide a procedure that should be called ## in case of an `out of memory`:idx: event. The standard handler ## writes an error message and terminates the program. `outOfMemHook` can @@ -1959,7 +1968,7 @@ elif hostOS != "standalone": inc(i) {.pop.} -proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO].} +proc echo*[T](x: varargs[T, `$`]) {.magic: "Echo", tags: [FWriteIO], gcsafe.} ## special built-in that takes a variable number of arguments. Each argument ## is converted to a string via ``$``, so it works for user-defined ## types that have an overloaded ``$`` operator. @@ -2006,7 +2015,7 @@ when not defined(sysFatal): e.msg = message & arg raise e -proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo".} +proc getTypeInfo*[T](x: T): pointer {.magic: "GetTypeInfo", gcsafe.} ## get type information for `x`. Ordinary code should not use this, but ## the `typeinfo` module instead. @@ -2113,14 +2122,15 @@ when not defined(JS): #and not defined(NimrodVM): ## `useStdoutAsStdmsg` compile-time switch. proc open*(f: var TFile, filename: string, - mode: TFileMode = fmRead, bufSize: int = -1): bool {.tags: [].} + mode: TFileMode = fmRead, bufSize: int = -1): bool {.tags: [], + gcsafe.} ## Opens a file named `filename` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. ## This throws no exception if the file could not be opened. proc open*(f: var TFile, filehandle: TFileHandle, - mode: TFileMode = fmRead): bool {.tags: [].} + mode: TFileMode = fmRead): bool {.tags: [], gcsafe.} ## Creates a ``TFile`` from a `filehandle` with given `mode`. ## ## Default mode is readonly. Returns true iff the file could be opened. @@ -2135,7 +2145,7 @@ when not defined(JS): #and not defined(NimrodVM): sysFatal(EIO, "cannot open: ", filename) proc reopen*(f: TFile, filename: string, mode: TFileMode = fmRead): bool {. - tags: [].} + tags: [], gcsafe.} ## reopens the file `f` with given `filename` and `mode`. This ## is often used to redirect the `stdin`, `stdout` or `stderr` ## file variables. @@ -2145,7 +2155,7 @@ when not defined(JS): #and not defined(NimrodVM): proc close*(f: TFile) {.importc: "fclose", header: "<stdio.h>", tags: [].} ## Closes the file. - proc endOfFile*(f: TFile): bool {.tags: [].} + proc endOfFile*(f: TFile): bool {.tags: [], gcsafe.} ## Returns true iff `f` is at the end. proc readChar*(f: TFile): char {. @@ -2155,39 +2165,40 @@ when not defined(JS): #and not defined(NimrodVM): importc: "fflush", header: "<stdio.h>", tags: [FWriteIO].} ## Flushes `f`'s buffer. - proc readAll*(file: TFile): TaintedString {.tags: [FReadIO].} + proc readAll*(file: TFile): TaintedString {.tags: [FReadIO], gcsafe.} ## Reads all data from the stream `file`. ## ## Raises an IO exception in case of an error. It is an error if the ## current file position is not at the beginning of the file. - proc readFile*(filename: string): TaintedString {.tags: [FReadIO].} + proc readFile*(filename: string): TaintedString {.tags: [FReadIO], gcsafe.} ## Opens a file named `filename` for reading. Then calls `readAll` ## and closes the file afterwards. Returns the string. ## Raises an IO exception in case of an error. - proc writeFile*(filename, content: string) {.tags: [FWriteIO].} + proc writeFile*(filename, content: string) {.tags: [FWriteIO], gcsafe.} ## Opens a file named `filename` for writing. Then writes the ## `content` completely to the file and closes the file afterwards. ## Raises an IO exception in case of an error. - proc write*(f: TFile, r: float32) {.tags: [FWriteIO].} - proc write*(f: TFile, i: int) {.tags: [FWriteIO].} - proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO].} - proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO].} - proc write*(f: TFile, s: string) {.tags: [FWriteIO].} - proc write*(f: TFile, b: bool) {.tags: [FWriteIO].} - proc write*(f: TFile, c: char) {.tags: [FWriteIO].} - proc write*(f: TFile, c: cstring) {.tags: [FWriteIO].} - proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO].} + proc write*(f: TFile, r: float32) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, i: int) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, i: BiggestInt) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, r: BiggestFloat) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, s: string) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, b: bool) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, c: char) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, c: cstring) {.tags: [FWriteIO], gcsafe.} + proc write*(f: TFile, a: varargs[string, `$`]) {.tags: [FWriteIO], gcsafe.} ## Writes a value to the file `f`. May throw an IO exception. - proc readLine*(f: TFile): TaintedString {.tags: [FReadIO].} + proc readLine*(f: TFile): TaintedString {.tags: [FReadIO], gcsafe.} ## reads a line of text from the file `f`. May throw an IO exception. ## A line of text may be delimited by ``CR``, ``LF`` or ## ``CRLF``. The newline character(s) are not part of the returned string. - proc readLine*(f: TFile, line: var TaintedString): bool {.tags: [FReadIO].} + proc readLine*(f: TFile, line: var TaintedString): bool {.tags: [FReadIO], + gcsafe.} ## reads a line of text from the file `f` into `line`. `line` must not be ## ``nil``! May throw an IO exception. ## A line of text may be delimited by ``CR``, ``LF`` or @@ -2195,53 +2206,59 @@ when not defined(JS): #and not defined(NimrodVM): ## Returns ``false`` if the end of the file has been reached, ``true`` ## otherwise. If ``false`` is returned `line` contains no new data. - proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, tags: [FWriteIO].} - ## writes the values `x` to `f` and then writes "\n". - ## May throw an IO exception. + when not defined(booting): + proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, + tags: [FWriteIO], gcsafe.} + ## writes the values `x` to `f` and then writes "\n". + ## May throw an IO exception. + else: + proc writeln*[Ty](f: TFile, x: varargs[Ty, `$`]) {.inline, + tags: [FWriteIO].} - proc getFileSize*(f: TFile): int64 {.tags: [FReadIO].} + proc getFileSize*(f: TFile): int64 {.tags: [FReadIO], gcsafe.} ## retrieves the file size (in bytes) of `f`. proc readBytes*(f: TFile, a: var openArray[int8], start, len: int): int {. - tags: [FReadIO].} + tags: [FReadIO], gcsafe.} ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. proc readChars*(f: TFile, a: var openArray[char], start, len: int): int {. - tags: [FReadIO].} + tags: [FReadIO], gcsafe.} ## reads `len` bytes into the buffer `a` starting at ``a[start]``. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. - proc readBuffer*(f: TFile, buffer: pointer, len: int): int {.tags: [FReadIO].} + proc readBuffer*(f: TFile, buffer: pointer, len: int): int {. + tags: [FReadIO], gcsafe.} ## reads `len` bytes into the buffer pointed to by `buffer`. Returns ## the actual number of bytes that have been read which may be less than ## `len` (if not as many bytes are remaining), but not greater. proc writeBytes*(f: TFile, a: openArray[int8], start, len: int): int {. - tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns ## the number of actual written bytes, which may be less than `len` in case ## of an error. proc writeChars*(f: TFile, a: openArray[char], start, len: int): int {. - tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} ## writes the bytes of ``a[start..start+len-1]`` to the file `f`. Returns ## the number of actual written bytes, which may be less than `len` in case ## of an error. proc writeBuffer*(f: TFile, buffer: pointer, len: int): int {. - tags: [FWriteIO].} + tags: [FWriteIO], gcsafe.} ## writes the bytes of buffer pointed to by the parameter `buffer` to the ## file `f`. Returns the number of actual written bytes, which may be less ## than `len` in case of an error. - proc setFilePos*(f: TFile, pos: int64) + proc setFilePos*(f: TFile, pos: int64) {.gcsafe.} ## sets the position of the file pointer that is used for read/write ## operations. The file's first byte has the index zero. - proc getFilePos*(f: TFile): int64 + proc getFilePos*(f: TFile): int64 {.gcsafe.} ## retrieves the current position of the file pointer that is used to ## read from the file `f`. The file's first byte has the index zero. @@ -2284,10 +2301,12 @@ when not defined(JS): #and not defined(NimrodVM): dealloc(a) when not defined(NimrodVM): - proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, discardable.} + proc atomicInc*(memLoc: var int, x: int = 1): int {.inline, + discardable, gcsafe.} ## atomic increment of `memLoc`. Returns the value after the operation. - proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, discardable.} + proc atomicDec*(memLoc: var int, x: int = 1): int {.inline, + discardable, gcsafe.} ## atomic decrement of `memLoc`. Returns the value after the operation. include "system/atomics" @@ -2917,3 +2936,6 @@ when not defined(booting): template isStatic*(x): expr = compiles(static(x)) # checks whether `x` is a value known at compile-time + +when hasThreadSupport: + when hostOS != "standalone": include "system/sysspawn" diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index 954485eb4..eaef6cd95 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -722,10 +722,13 @@ proc alloc0(allocator: var TMemRegion, size: int): pointer = zeroMem(result, size) proc dealloc(allocator: var TMemRegion, p: pointer) = + sysAssert(p != nil, "dealloc 0") var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell)) - sysAssert(cast[ptr TFreeCell](x).zeroField == 1, "dealloc 1") + sysAssert(x != nil, "dealloc 1") + sysAssert(isAccessible(allocator, x), "is not accessible") + sysAssert(cast[ptr TFreeCell](x).zeroField == 1, "dealloc 2") rawDealloc(allocator, x) - sysAssert(not isAllocatedPtr(allocator, x), "dealloc 2") + sysAssert(not isAllocatedPtr(allocator, x), "dealloc 3") proc realloc(allocator: var TMemRegion, p: pointer, newsize: int): pointer = if newsize > 0: diff --git a/lib/system/assign.nim b/lib/system/assign.nim index bed8820be..75c749633 100644 --- a/lib/system/assign.nim +++ b/lib/system/assign.nim @@ -7,10 +7,11 @@ # distribution, for details about the copyright. # -proc genericResetAux(dest: pointer, n: ptr TNimNode) +proc genericResetAux(dest: pointer, n: ptr TNimNode) {.gcsafe.} -proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) -proc genericAssignAux(dest, src: pointer, n: ptr TNimNode, shallow: bool) = +proc genericAssignAux(dest, src: pointer, mt: PNimType, shallow: bool) {.gcsafe.} +proc genericAssignAux(dest, src: pointer, n: ptr TNimNode, + shallow: bool) {.gcsafe.} = var d = cast[TAddress](dest) s = cast[TAddress](src) @@ -139,8 +140,8 @@ proc genericAssignOpenArray(dest, src: pointer, len: int, genericAssign(cast[pointer](d +% i*% mt.base.size), cast[pointer](s +% i*% mt.base.size), mt.base) -proc objectInit(dest: pointer, typ: PNimType) {.compilerProc.} -proc objectInitAux(dest: pointer, n: ptr TNimNode) = +proc objectInit(dest: pointer, typ: PNimType) {.compilerProc, gcsafe.} +proc objectInitAux(dest: pointer, n: ptr TNimNode) {.gcsafe.} = var d = cast[TAddress](dest) case n.kind of nkNone: sysAssert(false, "objectInitAux") @@ -184,7 +185,7 @@ else: mixin destroy for i in countup(0, r.len - 1): destroy(r[i]) -proc genericReset(dest: pointer, mt: PNimType) {.compilerProc.} +proc genericReset(dest: pointer, mt: PNimType) {.compilerProc, gcsafe.} proc genericResetAux(dest: pointer, n: ptr TNimNode) = var d = cast[TAddress](dest) case n.kind diff --git a/lib/system/avltree.nim b/lib/system/avltree.nim index fc965d6aa..bced15d6a 100644 --- a/lib/system/avltree.nim +++ b/lib/system/avltree.nim @@ -51,7 +51,7 @@ proc split(t: var PAvlNode) = t.link[0] = temp inc t.level -proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) = +proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) {.gcsafe.} = if t == bottom: t = allocAvlNode(a, key, upperBound) else: @@ -64,7 +64,7 @@ proc add(a: var TMemRegion, t: var PAvlNode, key, upperBound: int) = skew(t) split(t) -proc del(a: var TMemRegion, t: var PAvlNode, x: int) = +proc del(a: var TMemRegion, t: var PAvlNode, x: int) {.gcsafe.} = if t == bottom: return a.last = t if x <% t.key: diff --git a/lib/system/cgprocs.nim b/lib/system/cgprocs.nim index e30cfa469..d483c61bd 100644 --- a/lib/system/cgprocs.nim +++ b/lib/system/cgprocs.nim @@ -9,7 +9,7 @@ # Headers for procs that the code generator depends on ("compilerprocs") -proc addChar(s: NimString, c: char): NimString {.compilerProc.} +proc addChar(s: NimString, c: char): NimString {.compilerProc, gcsafe.} type TLibHandle = pointer # private type @@ -21,5 +21,5 @@ proc nimGetProcAddr(lib: TLibHandle, name: cstring): TProcAddr {.compilerproc.} proc nimLoadLibraryError(path: string) {.compilerproc, noinline.} -proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline.} +proc setStackBottom(theStackBottom: pointer) {.compilerRtl, noinline, gcsafe.} diff --git a/lib/system/channels.nim b/lib/system/channels.nim index bf949529b..e5535dbdc 100644 --- a/lib/system/channels.nim +++ b/lib/system/channels.nim @@ -1,7 +1,7 @@ # # # Nimrod's Runtime Library -# (c) Copyright 2012 Andreas Rumpf +# (c) Copyright 2014 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. @@ -29,7 +29,7 @@ type region: TMemRegion PRawChannel = ptr TRawChannel TLoadStoreMode = enum mStore, mLoad - TChannel*[TMsg] = TRawChannel ## a channel for thread communication + TChannel* {.gcsafe.}[TMsg] = TRawChannel ## a channel for thread communication const ChannelDeadMask = -2 @@ -49,9 +49,9 @@ proc deinitRawChannel(p: pointer) = deinitSysCond(c.cond) proc storeAux(dest, src: pointer, mt: PNimType, t: PRawChannel, - mode: TLoadStoreMode) + mode: TLoadStoreMode) {.gcsafe.} proc storeAux(dest, src: pointer, n: ptr TNimNode, t: PRawChannel, - mode: TLoadStoreMode) = + mode: TLoadStoreMode) {.gcsafe.} = var d = cast[TAddress](dest) s = cast[TAddress](src) @@ -209,7 +209,6 @@ proc send*[TMsg](c: var TChannel[TMsg], msg: TMsg) = proc llRecv(q: PRawChannel, res: pointer, typ: PNimType) = # to save space, the generic is as small as possible - acquireSys(q.lock) q.ready = true while q.count <= 0: waitSysCond(q.cond, q.lock) @@ -218,17 +217,29 @@ proc llRecv(q: PRawChannel, res: pointer, typ: PNimType) = releaseSys(q.lock) sysFatal(EInvalidValue, "cannot receive message of wrong type") rawRecv(q, res, typ) - releaseSys(q.lock) proc recv*[TMsg](c: var TChannel[TMsg]): TMsg = ## receives a message from the channel `c`. This blocks until ## a message has arrived! You may use ``peek`` to avoid the blocking. var q = cast[PRawChannel](addr(c)) + acquireSys(q.lock) llRecv(q, addr(result), cast[PNimType](getTypeInfo(result))) + releaseSys(q.lock) + +proc tryRecv*[TMsg](c: var TChannel[TMsg]): tuple[dataAvaliable: bool, + msg: TMsg] = + ## try to receives a message from the channel `c` if available. Otherwise + ## it returns ``(false, default(msg))``. + var q = cast[PRawChannel](addr(c)) + if q.mask != ChannelDeadMask: + lockChannel(q): + llRecv(q, addr(result.msg), cast[PNimType](getTypeInfo(result.msg))) + result.dataAvaliable = true proc peek*[TMsg](c: var TChannel[TMsg]): int = ## returns the current number of messages in the channel `c`. Returns -1 - ## if the channel has been closed. + ## if the channel has been closed. **Note**: This is dangerous to use + ## as it encourages races. It's much better to use ``tryRecv`` instead. var q = cast[PRawChannel](addr(c)) if q.mask != ChannelDeadMask: lockChannel(q): diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index e50ba7b9f..612a9e729 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -11,7 +11,7 @@ # use the heap (and nor exceptions) do not include the GC or memory allocator. var - errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO].}) + errorMessageWriter*: (proc(msg: string) {.tags: [FWriteIO], gcsafe.}) ## Function that will be called ## instead of stdmsg.write when printing stacktrace. ## Unstable API. @@ -32,10 +32,10 @@ proc showErrorMessage(data: cstring) = else: writeToStdErr(data) -proc chckIndx(i, a, b: int): int {.inline, compilerproc.} -proc chckRange(i, a, b: int): int {.inline, compilerproc.} -proc chckRangeF(x, a, b: float): float {.inline, compilerproc.} -proc chckNil(p: pointer) {.noinline, compilerproc.} +proc chckIndx(i, a, b: int): int {.inline, compilerproc, gcsafe.} +proc chckRange(i, a, b: int): int {.inline, compilerproc, gcsafe.} +proc chckRangeF(x, a, b: float): float {.inline, compilerproc, gcsafe.} +proc chckNil(p: pointer) {.noinline, compilerproc, gcsafe.} var framePtr {.rtlThreadVar.}: PFrame @@ -322,5 +322,5 @@ when not defined(noSignalHandler): proc setControlCHook(hook: proc () {.noconv.}) = # ugly cast, but should work on all architectures: - type TSignalHandler = proc (sig: cint) {.noconv.} + type TSignalHandler = proc (sig: cint) {.noconv, gcsafe.} c_signal(SIGINT, cast[TSignalHandler](hook)) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index ec1760914..3b85fe600 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -51,7 +51,7 @@ type waZctDecRef, waPush, waCycleDecRef, waMarkGray, waScan, waScanBlack, waCollectWhite, - TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall.} + TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe.} # A ref type can have a finalizer that is called before the object's # storage is freed. @@ -152,11 +152,11 @@ template gcTrace(cell, state: expr): stmt {.immediate.} = when traceGC: traceCell(cell, state) # forward declarations: -proc collectCT(gch: var TGcHeap) -proc isOnStack*(p: pointer): bool {.noinline.} -proc forAllChildren(cell: PCell, op: TWalkOp) -proc doOperation(p: pointer, op: TWalkOp) -proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) +proc collectCT(gch: var TGcHeap) {.gcsafe.} +proc isOnStack*(p: pointer): bool {.noinline, gcsafe.} +proc forAllChildren(cell: PCell, op: TWalkOp) {.gcsafe.} +proc doOperation(p: pointer, op: TWalkOp) {.gcsafe.} +proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) {.gcsafe.} # we need the prototype here for debugging purposes when hasThreadSupport and hasSharedHeap: @@ -294,7 +294,7 @@ proc initGC() = when useMarkForDebug or useBackupGc: type - TGlobalMarkerProc = proc () {.nimcall.} + TGlobalMarkerProc = proc () {.nimcall, gcsafe.} var globalMarkersLen: int globalMarkers: array[0.. 7_000, TGlobalMarkerProc] @@ -311,7 +311,7 @@ proc cellsetReset(s: var TCellSet) = deinit(s) init(s) -proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) = +proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) {.gcsafe.} = var d = cast[TAddress](dest) case n.kind of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op) @@ -680,10 +680,11 @@ proc doOperation(p: pointer, op: TWalkOp) = proc nimGCvisit(d: pointer, op: int) {.compilerRtl.} = doOperation(d, TWalkOp(op)) -proc collectZCT(gch: var TGcHeap): bool +proc collectZCT(gch: var TGcHeap): bool {.gcsafe.} when useMarkForDebug or useBackupGc: - proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl.} + proc markStackAndRegistersForSweep(gch: var TGcHeap) {.noinline, cdecl, + gcsafe.} proc collectRoots(gch: var TGcHeap) = for s in elements(gch.cycleRoots): diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index e78a4e5cd..3c99a57e1 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -32,11 +32,11 @@ type # local waMarkPrecise # fast precise marking - TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall.} + TFinalizer {.compilerproc.} = proc (self: pointer) {.nimcall, gcsafe.} # A ref type can have a finalizer that is called before the object's # storage is freed. - TGlobalMarkerProc = proc () {.nimcall.} + TGlobalMarkerProc = proc () {.nimcall, gcsafe.} TGcStat = object collections: int # number of performed full collections @@ -113,11 +113,11 @@ when BitsPerPage mod (sizeof(int)*8) != 0: {.error: "(BitsPerPage mod BitsPerUnit) should be zero!".} # forward declarations: -proc collectCT(gch: var TGcHeap) -proc isOnStack*(p: pointer): bool {.noinline.} -proc forAllChildren(cell: PCell, op: TWalkOp) -proc doOperation(p: pointer, op: TWalkOp) -proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) +proc collectCT(gch: var TGcHeap) {.gcsafe.} +proc isOnStack*(p: pointer): bool {.noinline, gcsafe.} +proc forAllChildren(cell: PCell, op: TWalkOp) {.gcsafe.} +proc doOperation(p: pointer, op: TWalkOp) {.gcsafe.} +proc forAllChildrenAux(dest: pointer, mt: PNimType, op: TWalkOp) {.gcsafe.} # we need the prototype here for debugging purposes proc prepareDealloc(cell: PCell) = @@ -150,7 +150,7 @@ proc initGC() = Init(gch.allocated) init(gch.marked) -proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) = +proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) {.gcsafe.} = var d = cast[TAddress](dest) case n.kind of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op) diff --git a/lib/system/hti.nim b/lib/system/hti.nim index 9d8ece7df..64174e60f 100644 --- a/lib/system/hti.nim +++ b/lib/system/hti.nim @@ -85,7 +85,7 @@ type base: ptr TNimType node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum finalizer: pointer # the finalizer for the type - marker: proc (p: pointer, op: int) {.nimcall.} # marker proc for GC + marker: proc (p: pointer, op: int) {.nimcall, gcsafe.} # marker proc for GC PNimType = ptr TNimType # node.len may be the ``first`` element of a set diff --git a/lib/system/inclrtl.nim b/lib/system/inclrtl.nim index 9831130e2..475a09686 100644 --- a/lib/system/inclrtl.nim +++ b/lib/system/inclrtl.nim @@ -16,6 +16,8 @@ # -> defined(useNimRtl) or appType == "lib" and not defined(createNimRtl) # 3) Exported into nimrtl. # -> appType == "lib" and defined(createNimRtl) +when not defined(nimNewShared): + {.pragma: gcsafe.} when defined(createNimRtl): when defined(useNimRtl): @@ -24,7 +26,7 @@ when defined(createNimRtl): {.error: "nimrtl must be built as a library!".} when defined(createNimRtl): - {.pragma: rtl, exportc: "nimrtl_$1", dynlib.} + {.pragma: rtl, exportc: "nimrtl_$1", dynlib, gcsafe.} {.pragma: inl.} {.pragma: compilerRtl, compilerproc, exportc: "nimrtl_$1", dynlib.} elif defined(useNimRtl): @@ -34,11 +36,11 @@ elif defined(useNimRtl): const nimrtl* = "nimrtl.dylib" else: const nimrtl* = "libnimrtl.so" - {.pragma: rtl, importc: "nimrtl_$1", dynlib: nimrtl.} + {.pragma: rtl, importc: "nimrtl_$1", dynlib: nimrtl, gcsafe.} {.pragma: inl.} {.pragma: compilerRtl, compilerproc, importc: "nimrtl_$1", dynlib: nimrtl.} else: - {.pragma: rtl.} + {.pragma: rtl, gcsafe.} {.pragma: inl, inline.} {.pragma: compilerRtl, compilerproc.} diff --git a/lib/system/repr.nim b/lib/system/repr.nim index 7c1a68bc7..487bac052 100644 --- a/lib/system/repr.nim +++ b/lib/system/repr.nim @@ -10,7 +10,7 @@ # The generic ``repr`` procedure. It is an invaluable debugging tool. when not defined(useNimRtl): - proc reprAny(p: pointer, typ: PNimType): string {.compilerRtl.} + proc reprAny(p: pointer, typ: PNimType): string {.compilerRtl, gcsafe.} proc reprInt(x: int64): string {.compilerproc.} = return $x proc reprFloat(x: float): string {.compilerproc.} = return $x @@ -78,7 +78,7 @@ proc reprEnum(e: int, typ: PNimType): string {.compilerRtl.} = type PByteArray = ptr array[0.. 0xffff, int8] -proc addSetElem(result: var string, elem: int, typ: PNimType) = +proc addSetElem(result: var string, elem: int, typ: PNimType) {.gcsafe.} = case typ.kind of tyEnum: add result, reprEnum(elem, typ) of tyBool: add result, reprBool(bool(elem)) @@ -147,7 +147,7 @@ when not defined(useNimRtl): for i in 0..cl.indent-1: add result, ' ' proc reprAux(result: var string, p: pointer, typ: PNimType, - cl: var TReprClosure) + cl: var TReprClosure) {.gcsafe.} proc reprArray(result: var string, p: pointer, typ: PNimType, cl: var TReprClosure) = @@ -172,7 +172,7 @@ when not defined(useNimRtl): add result, "]" proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode, - cl: var TReprClosure) = + cl: var TReprClosure) {.gcsafe.} = case n.kind of nkNone: sysAssert(false, "reprRecordAux") of nkSlot: diff --git a/lib/system/syslocks.nim b/lib/system/syslocks.nim index 5e3b04b7f..b8ed29cfc 100644 --- a/lib/system/syslocks.nim +++ b/lib/system/syslocks.nim @@ -52,7 +52,7 @@ when defined(Windows): proc closeHandle(hObject: THandle) {.stdcall, noSideEffect, dynlib: "kernel32", importc: "CloseHandle".} proc waitForSingleObject(hHandle: THandle, dwMilliseconds: int32): int32 {. - stdcall, dynlib: "kernel32", importc: "WaitForSingleObject".} + stdcall, dynlib: "kernel32", importc: "WaitForSingleObject", noSideEffect.} proc signalSysCond(hEvent: TSysCond) {.stdcall, noSideEffect, dynlib: "kernel32", importc: "SetEvent".} @@ -89,16 +89,16 @@ else: proc releaseSys(L: var TSysLock) {.noSideEffect, importc: "pthread_mutex_unlock", header: "<pthread.h>".} - proc deinitSys(L: var TSysLock) {. + proc deinitSys(L: var TSysLock) {.noSideEffect, importc: "pthread_mutex_destroy", header: "<pthread.h>".} proc initSysCond(cond: var TSysCond, cond_attr: pointer = nil) {. - importc: "pthread_cond_init", header: "<pthread.h>".} + importc: "pthread_cond_init", header: "<pthread.h>", noSideEffect.} proc waitSysCond(cond: var TSysCond, lock: var TSysLock) {. - importc: "pthread_cond_wait", header: "<pthread.h>".} + importc: "pthread_cond_wait", header: "<pthread.h>", noSideEffect.} proc signalSysCond(cond: var TSysCond) {. - importc: "pthread_cond_signal", header: "<pthread.h>".} + importc: "pthread_cond_signal", header: "<pthread.h>", noSideEffect.} - proc deinitSysCond(cond: var TSysCond) {. + proc deinitSysCond(cond: var TSysCond) {.noSideEffect, importc: "pthread_cond_destroy", header: "<pthread.h>".} diff --git a/lib/system/sysspawn.nim b/lib/system/sysspawn.nim new file mode 100644 index 000000000..dabf35a3e --- /dev/null +++ b/lib/system/sysspawn.nim @@ -0,0 +1,195 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2014 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## Implements Nimrod's 'spawn'. + +when not defined(NimString): + {.error: "You must not import this module explicitly".} + +{.push stackTrace:off.} + +when (defined(x86) or defined(amd64)) and defined(gcc): + proc cpuRelax {.inline.} = + {.emit: """asm volatile("pause" ::: "memory");""".} +elif (defined(x86) or defined(amd64)) and defined(vcc): + proc cpuRelax {.importc: "YieldProcessor", header: "<windows.h>".} +elif defined(intelc): + proc cpuRelax {.importc: "_mm_pause", header: "xmmintrin.h".} +elif false: + from os import sleep + + proc cpuRelax {.inline.} = os.sleep(1) + +when defined(windows) and not defined(gcc): + proc interlockedCompareExchange(p: pointer; exchange, comparand: int32): int32 + {.importc: "InterlockedCompareExchange", header: "<windows.h>", cdecl.} + + proc cas(p: ptr bool; oldValue, newValue: bool): bool = + interlockedCompareExchange(p, newValue.int32, oldValue.int32) != 0 + +else: + # this is valid for GCC and Intel C++ + proc cas(p: ptr bool; oldValue, newValue: bool): bool + {.importc: "__sync_bool_compare_and_swap", nodecl.} + +# We declare our own condition variables here to get rid of the dummy lock +# on Windows: + +type + CondVar = object + c: TSysCond + when defined(posix): + stupidLock: TSysLock + counter: int + +proc createCondVar(): CondVar = + initSysCond(result.c) + when defined(posix): + initSysLock(result.stupidLock) + #acquireSys(result.stupidLock) + +proc await(cv: var CondVar) = + when defined(posix): + acquireSys(cv.stupidLock) + while cv.counter <= 0: + waitSysCond(cv.c, cv.stupidLock) + dec cv.counter + releaseSys(cv.stupidLock) + else: + waitSysCondWindows(cv.c) + +proc signal(cv: var CondVar) = + when defined(posix): + acquireSys(cv.stupidLock) + inc cv.counter + releaseSys(cv.stupidLock) + signalSysCond(cv.c) + +type + FastCondVar = object + event, slowPath: bool + slow: CondVar + +proc createFastCondVar(): FastCondVar = + initSysCond(result.slow.c) + when defined(posix): + initSysLock(result.slow.stupidLock) + #acquireSys(result.slow.stupidLock) + result.event = false + result.slowPath = false + +proc await(cv: var FastCondVar) = + #for i in 0 .. 50: + # if cas(addr cv.event, true, false): + # # this is a HIT: Triggers > 95% in my tests. + # return + # cpuRelax() + #cv.slowPath = true + # XXX For some reason this crashes some test programs + await(cv.slow) + cv.event = false + +proc signal(cv: var FastCondVar) = + cv.event = true + #if cas(addr cv.slowPath, true, false): + signal(cv.slow) + +{.pop.} + +# ---------------------------------------------------------------------------- + +type + WorkerProc = proc (thread, args: pointer) {.nimcall, gcsafe.} + Worker = object + taskArrived: CondVar + taskStarted: FastCondVar #\ + # task data: + f: WorkerProc + data: pointer + ready: bool # put it here for correct alignment! + +proc nimArgsPassingDone(p: pointer) {.compilerProc.} = + let w = cast[ptr Worker](p) + signal(w.taskStarted) + +var gSomeReady = createFastCondVar() + +proc slave(w: ptr Worker) {.thread.} = + while true: + w.ready = true # If we instead signal "workerReady" we need the scheduler + # to notice this. The scheduler could then optimize the + # layout of the worker threads (e.g. keep the list sorted) + # so that no search for a "ready" thread is necessary. + # This might be implemented later, but is more tricky than + # it looks because 'spawn' itself can run concurrently. + signal(gSomeReady) + await(w.taskArrived) + assert(not w.ready) + # shield against spurious wakeups: + if w.data != nil: + w.f(w, w.data) + w.data = nil + +const NumThreads = 4 + +var + workers: array[NumThreads, TThread[ptr Worker]] + workersData: array[NumThreads, Worker] + +proc setup() = + for i in 0.. <NumThreads: + workersData[i].taskArrived = createCondVar() + workersData[i].taskStarted = createFastCondVar() + createThread(workers[i], slave, addr(workersData[i])) + +proc preferSpawn*(): bool = + ## Use this proc to determine quickly if a 'spawn' or a direct call is + ## preferable. If it returns 'true' a 'spawn' may make sense. In general + ## it is not necessary to call this directly; use 'spawnX' instead. + result = gSomeReady.event + +proc spawn*(call: stmt) {.magic: "Spawn".} + ## always spawns a new task, so that the 'call' is never executed on + ## the calling thread. 'call' has to be proc call 'p(...)' where 'p' + ## is gcsafe and has 'void' as the return type. + +template spawnX*(call: stmt) = + ## spawns a new task if a CPU core is ready, otherwise executes the + ## call in the calling thread. Usually it is advised to + ## use 'spawn' in order to not block the producer for an unknown + ## amount of time. 'call' has to be proc call 'p(...)' where 'p' + ## is gcsafe and has 'void' as the return type. + if preferSpawn(): spawn call + else: call + +proc nimSpawn(fn: WorkerProc; data: pointer) {.compilerProc.} = + # implementation of 'spawn' that is used by the code generator. + while true: + for i in 0.. high(workers): + let w = addr(workersData[i]) + if cas(addr w.ready, true, false): + w.data = data + w.f = fn + signal(w.taskArrived) + await(w.taskStarted) + return + await(gSomeReady) + +proc sync*() = + ## a simple barrier to wait for all spawn'ed tasks. If you need more elaborate + ## waiting, you have to use an explicit barrier. + while true: + var allReady = true + for i in 0 .. high(workers): + if not allReady: break + allReady = allReady and workersData[i].ready + if allReady: break + await(gSomeReady) + +setup() diff --git a/lib/system/sysstr.nim b/lib/system/sysstr.nim index eb9d2000b..4244bae4c 100644 --- a/lib/system/sysstr.nim +++ b/lib/system/sysstr.nim @@ -252,10 +252,8 @@ proc nimIntToStr(x: int): string {.compilerRtl.} = proc nimFloatToStr(x: float): string {.compilerproc.} = var buf: array [0..59, char] - c_sprintf(buf, "%#.f", x) - result = $buf - if result[len(result)-1] == '.': - result.add("0") + c_sprintf(buf, "%#.16e", x) + return $buf proc nimInt64ToStr(x: int64): string {.compilerRtl.} = result = newString(sizeof(x)*4) diff --git a/lib/system/threads.nim b/lib/system/threads.nim index ff9ab6cc0..0d52e4d09 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -256,9 +256,9 @@ type ## that **must not** be part of a message! Use ## a ``TThreadId`` for that. when TArg is void: - dataFn: proc () {.nimcall.} + dataFn: proc () {.nimcall, gcsafe.} else: - dataFn: proc (m: TArg) {.nimcall.} + dataFn: proc (m: TArg) {.nimcall, gcsafe.} data: TArg TThreadId*[TArg] = ptr TThread[TArg] ## the current implementation uses ## a pointer as a thread ID. diff --git a/lib/windows/winlean.nim b/lib/windows/winlean.nim index 3449a3eba..4ce2f11b4 100644 --- a/lib/windows/winlean.nim +++ b/lib/windows/winlean.nim @@ -522,7 +522,7 @@ proc inet_addr*(cp: cstring): int32 {. stdcall, importc: "inet_addr", dynlib: ws2dll.} proc WSAFDIsSet(s: TSocketHandle, FDSet: var TFdSet): bool {. - stdcall, importc: "__WSAFDIsSet", dynlib: ws2dll.} + stdcall, importc: "__WSAFDIsSet", dynlib: ws2dll, noSideEffect.} proc FD_ISSET*(Socket: TSocketHandle, FDSet: var TFdSet): cint = result = if WSAFDIsSet(Socket, FDSet): 1'i32 else: 0'i32 @@ -718,4 +718,4 @@ proc WSASend*(s: TSocketHandle, buf: ptr TWSABuf, bufCount: DWORD, stdcall, importc: "WSASend", dynlib: "Ws2_32.dll".} proc get_osfhandle*(fd:TFileHandle): THandle {. - importc:"_get_osfhandle", header:"<io.h>".} \ No newline at end of file + importc:"_get_osfhandle", header:"<io.h>".} diff --git a/lib/wrappers/sqlite3.nim b/lib/wrappers/sqlite3.nim index 8ff1da1d1..586f763ae 100644 --- a/lib/wrappers/sqlite3.nim +++ b/lib/wrappers/sqlite3.nim @@ -90,6 +90,7 @@ const SQLITE_IGNORE* = 2 # Original from sqlite3.h: ##define SQLITE_STATIC ((void(*)(void *))0) ##define SQLITE_TRANSIENT ((void(*)(void *))-1) + SQLITE_DETERMINISTIC* = 0x800 const SQLITE_STATIC* = nil |