diff options
Diffstat (limited to 'lib/system')
-rw-r--r-- | lib/system/alloc.nim | 6 | ||||
-rw-r--r-- | lib/system/ansi_c.nim | 21 | ||||
-rw-r--r-- | lib/system/arithm.nim | 44 | ||||
-rw-r--r-- | lib/system/chcks.nim | 2 | ||||
-rw-r--r-- | lib/system/dyncalls.nim | 32 | ||||
-rw-r--r-- | lib/system/embedded.nim | 1 | ||||
-rw-r--r-- | lib/system/endb.nim | 153 | ||||
-rw-r--r-- | lib/system/excpt.nim | 89 | ||||
-rw-r--r-- | lib/system/gc.nim | 32 | ||||
-rw-r--r-- | lib/system/gc_common.nim | 6 | ||||
-rw-r--r-- | lib/system/gc_ms.nim | 6 | ||||
-rw-r--r-- | lib/system/gc_regions.nim | 49 | ||||
-rw-r--r-- | lib/system/helpers.nim | 23 | ||||
-rw-r--r-- | lib/system/helpers2.nim | 5 | ||||
-rw-r--r-- | lib/system/indexerrors.nim | 7 | ||||
-rw-r--r-- | lib/system/io.nim (renamed from lib/system/sysio.nim) | 329 | ||||
-rw-r--r-- | lib/system/jssys.nim | 2 | ||||
-rw-r--r-- | lib/system/mmdisp.nim | 5 | ||||
-rw-r--r-- | lib/system/nimscript.nim | 35 | ||||
-rw-r--r-- | lib/system/strmantle.nim | 2 | ||||
-rw-r--r-- | lib/system/threads.nim | 7 | ||||
-rw-r--r-- | lib/system/widestrs.nim | 4 |
22 files changed, 593 insertions, 267 deletions
diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index b090117a9..e938dc475 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -20,7 +20,7 @@ template track(op, address, size) = # Each chunk starts at an address that is divisible by the page size. const - InitialMemoryRequest = 128 * PageSize # 0.5 MB + nimMinHeapPages {.intdefine.} = 128 # 0.5 MB SmallChunkSize = PageSize MaxFli = 30 MaxLog2Sli = 5 # 32, this cannot be increased without changing 'uint32' @@ -588,8 +588,8 @@ proc getBigChunk(a: var MemRegion, size: int): PBigChunk = sysAssert((size and PageMask) == 0, "getBigChunk: unaligned chunk") result = findSuitableBlock(a, fl, sl) if result == nil: - if size < InitialMemoryRequest: - result = requestOsChunks(a, InitialMemoryRequest) + if size < nimMinHeapPages * PageSize: + result = requestOsChunks(a, nimMinHeapPages * PageSize) splitChunk(a, result, size) else: result = requestOsChunks(a, size) diff --git a/lib/system/ansi_c.nim b/lib/system/ansi_c.nim index af34060d8..552962775 100644 --- a/lib/system/ansi_c.nim +++ b/lib/system/ansi_c.nim @@ -111,22 +111,27 @@ type c_sighandler_t = proc (a: cint) {.noconv.} proc c_signal(sign: cint, handler: proc (a: cint) {.noconv.}): c_sighandler_t {. importc: "signal", header: "<signal.h>", discardable.} -proc c_fprintf(f: File, frmt: cstring): cint {. +type + CFile {.importc: "FILE", header: "<stdio.h>", + incompletestruct.} = object + CFilePtr* = ptr CFile ## The type representing a file handle. + +var + cstderr* {.importc: "stderr", header: "<stdio.h>".}: CFilePtr + cstdout* {.importc: "stdout", header: "<stdio.h>".}: CFilePtr + +proc c_fprintf(f: CFilePtr, frmt: cstring): cint {. importc: "fprintf", header: "<stdio.h>", varargs, discardable.} proc c_printf(frmt: cstring): cint {. importc: "printf", header: "<stdio.h>", varargs, discardable.} +proc c_fputs(c: cstring, f: CFilePtr): cint {. + importc: "fputs", header: "<stdio.h>", discardable.} + proc c_sprintf(buf, frmt: cstring): cint {. importc: "sprintf", header: "<stdio.h>", varargs, noSideEffect.} # we use it only in a way that cannot lead to security issues -when defined(windows): - proc c_fileno(f: File): cint {. - importc: "_fileno", header: "<stdio.h>".} -else: - proc c_fileno(f: File): cint {. - importc: "fileno", header: "<fcntl.h>".} - proc c_malloc(size: csize): pointer {. importc: "malloc", header: "<stdlib.h>".} proc c_free(p: pointer) {. diff --git a/lib/system/arithm.nim b/lib/system/arithm.nim index 69c558799..a875e95a7 100644 --- a/lib/system/arithm.nim +++ b/lib/system/arithm.nim @@ -197,26 +197,40 @@ when asmVersion and not defined(gcc) and not defined(llvm_gcc): proc divInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, ecx - mov ecx, edx - xor edx, edx - idiv ecx - jno theEnd - call `raiseOverflow` - theEnd: + test edx, edx + jne L_NOT_ZERO + call `raiseDivByZero` + L_NOT_ZERO: + cmp ecx, 0x80000000 + jne L_DO_DIV + cmp edx, -1 + jne L_DO_DIV + call `raiseOverflow` + L_DO_DIV: + mov eax, ecx + mov ecx, edx + cdq + idiv ecx ret """ proc modInt(a, b: int): int {.compilerProc, asmNoStackFrame.} = asm """ - mov eax, ecx - mov ecx, edx - xor edx, edx - idiv ecx - jno theEnd - call `raiseOverflow` - theEnd: - mov eax, edx + test edx, edx + jne L_NOT_ZERO + call `raiseDivByZero` + L_NOT_ZERO: + cmp ecx, 0x80000000 + jne L_DO_DIV + cmp edx, -1 + jne L_DO_DIV + call `raiseOverflow` + L_DO_DIV: + mov eax, ecx + mov ecx, edx + cdq + idiv ecx + mov eax, edx ret """ diff --git a/lib/system/chcks.nim b/lib/system/chcks.nim index 6f4e8ce37..0840d863a 100644 --- a/lib/system/chcks.nim +++ b/lib/system/chcks.nim @@ -8,7 +8,7 @@ # # Implementation of some runtime checks. -import system/helpers2 +import system/indexerrors proc raiseRangeError(val: BiggestInt) {.compilerproc, noinline.} = when hostOS == "standalone": diff --git a/lib/system/dyncalls.nim b/lib/system/dyncalls.nim index 70bdc429b..528587d05 100644 --- a/lib/system/dyncalls.nim +++ b/lib/system/dyncalls.nim @@ -19,11 +19,11 @@ const proc nimLoadLibraryError(path: string) = # carefully written to avoid memory allocation: - stderr.rawWrite("could not load: ") - stderr.rawWrite(path) - stderr.rawWrite("\n") + cstderr.rawWrite("could not load: ") + cstderr.rawWrite(path) + cstderr.rawWrite("\n") when not defined(nimDebugDlOpen) and not defined(windows): - stderr.rawWrite("compile with -d:nimDebugDlOpen for more information\n") + cstderr.rawWrite("compile with -d:nimDebugDlOpen for more information\n") when defined(windows) and defined(guiapp): # Because console output is not shown in GUI apps, display error as message box: const prefix = "could not load: " @@ -35,9 +35,9 @@ proc nimLoadLibraryError(path: string) = proc procAddrError(name: cstring) {.noinline.} = # carefully written to avoid memory allocation: - stderr.rawWrite("could not import: ") - stderr.rawWrite(name) - stderr.rawWrite("\n") + cstderr.rawWrite("could not import: ") + cstderr.rawWrite(name) + cstderr.rawWrite("\n") quit(1) # this code was inspired from Lua's source code: @@ -79,8 +79,8 @@ when defined(posix): when defined(nimDebugDlOpen): let error = dlerror() if error != nil: - stderr.rawWrite(error) - stderr.rawWrite("\n") + cstderr.rawWrite(error) + cstderr.rawWrite("\n") proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr = result = dlsym(lib, name) @@ -162,20 +162,20 @@ elif defined(genode): elif defined(nintendoswitch): proc nimUnloadLibrary(lib: LibHandle) = - stderr.rawWrite("nimUnLoadLibrary not implemented") - stderr.rawWrite("\n") + cstderr.rawWrite("nimUnLoadLibrary not implemented") + cstderr.rawWrite("\n") quit(1) proc nimLoadLibrary(path: string): LibHandle = - stderr.rawWrite("nimLoadLibrary not implemented") - stderr.rawWrite("\n") + cstderr.rawWrite("nimLoadLibrary not implemented") + cstderr.rawWrite("\n") quit(1) proc nimGetProcAddr(lib: LibHandle, name: cstring): ProcAddr = - stderr.rawWrite("nimGetProAddr not implemented") - stderr.rawWrite(name) - stderr.rawWrite("\n") + cstderr.rawWrite("nimGetProAddr not implemented") + cstderr.rawWrite(name) + cstderr.rawWrite("\n") quit(1) else: diff --git a/lib/system/embedded.nim b/lib/system/embedded.nim index 4d453fcca..fb89e7f0f 100644 --- a/lib/system/embedded.nim +++ b/lib/system/embedded.nim @@ -40,6 +40,7 @@ proc reraiseException() {.compilerRtl.} = proc writeStackTrace() = discard +proc unsetControlCHook() = discard proc setControlCHook(hook: proc () {.noconv.}) = discard proc closureIterSetupExc(e: ref Exception) {.compilerproc, inline.} = diff --git a/lib/system/endb.nim b/lib/system/endb.nim index 257ee3fea..6c99f8d12 100644 --- a/lib/system/endb.nim +++ b/lib/system/endb.nim @@ -76,29 +76,52 @@ proc `==`(a, b: StaticStr): bool = proc `==`(a: StaticStr, b: cstring): bool = result = c_strcmp(unsafeAddr a.data, b) == 0 -proc write(f: File, s: StaticStr) = +proc write(f: CFilePtr, s: cstring) = c_fputs(s, f) +proc writeLine(f: CFilePtr, s: cstring) = + c_fputs(s, f) + c_fputs("\n", f) + +proc write(f: CFilePtr, s: StaticStr) = write(f, cstring(unsafeAddr s.data)) +proc write(f: CFilePtr, i: int) = + when sizeof(int) == 8: + discard c_fprintf(f, "%lld", i) + else: + discard c_fprintf(f, "%ld", i) + +proc close(f: CFilePtr): cint {. + importc: "fclose", header: "<stdio.h>", discardable.} + +proc c_fgetc(stream: CFilePtr): cint {. + importc: "fgetc", header: "<stdio.h>".} +proc c_ungetc(c: cint, f: CFilePtr): cint {. + importc: "ungetc", header: "<stdio.h>", discardable.} + +var + cstdin* {.importc: "stdin", header: "<stdio.h>".}: CFilePtr + proc listBreakPoints() = - write(stdout, EndbBeg) - write(stdout, "| Breakpoints:\n") + write(cstdout, EndbBeg) + write(cstdout, "| Breakpoints:\n") for b in listBreakpoints(): - write(stdout, abs(b.low)) + write(cstdout, abs(b.low)) if b.high != b.low: - write(stdout, "..") - write(stdout, abs(b.high)) - write(stdout, " ") - write(stdout, b.filename) + write(cstdout, "..") + write(cstdout, abs(b.high)) + write(cstdout, " ") + write(cstdout, b.filename) if b.isActive: - write(stdout, " [disabled]\n") + write(cstdout, " [disabled]\n") else: - write(stdout, "\n") - write(stdout, EndbEnd) + write(cstdout, "\n") + write(cstdout, EndbEnd) + +proc openAppend(filename: cstring): CFilePtr = + proc fopen(filename, mode: cstring): CFilePtr {.importc: "fopen", header: "<stdio.h>".} -proc openAppend(filename: cstring): File = - var p: pointer = fopen(filename, "ab") - if p != nil: - result = cast[File](p) + result = fopen(filename, "ab") + if result != nil: write(result, "----------------------------------------\n") proc dbgRepr(p: pointer, typ: PNimType): string = @@ -112,12 +135,12 @@ proc dbgRepr(p: pointer, typ: PNimType): string = # dec(recGcLock) deinitReprClosure(cl) -proc writeVariable(stream: File, slot: VarSlot) = +proc writeVariable(stream: CFilePtr, slot: VarSlot) = write(stream, slot.name) write(stream, " = ") writeLine(stream, dbgRepr(slot.address, slot.typ)) -proc listFrame(stream: File, f: PFrame) = +proc listFrame(stream: CFilePtr, f: PFrame) = write(stream, EndbBeg) write(stream, "| Frame (") write(stream, f.len) @@ -126,7 +149,7 @@ proc listFrame(stream: File, f: PFrame) = writeLine(stream, getLocal(f, i).name) write(stream, EndbEnd) -proc listLocals(stream: File, f: PFrame) = +proc listLocals(stream: CFilePtr, f: PFrame) = write(stream, EndbBeg) write(stream, "| Frame (") write(stream, f.len) @@ -135,7 +158,7 @@ proc listLocals(stream: File, f: PFrame) = writeVariable(stream, getLocal(f, i)) write(stream, EndbEnd) -proc listGlobals(stream: File) = +proc listGlobals(stream: CFilePtr) = write(stream, EndbBeg) write(stream, "| Globals:\n") for i in 0 .. getGlobalLen()-1: @@ -145,10 +168,10 @@ proc listGlobals(stream: File) = proc debugOut(msg: cstring) = # the *** *** markers are for easy recognition of debugger # output for external frontends. - write(stdout, EndbBeg) - write(stdout, "| ") - write(stdout, msg) - write(stdout, EndbEnd) + write(cstdout, EndbBeg) + write(cstdout, "| ") + write(cstdout, msg) + write(cstdout, EndbEnd) proc dbgFatal(msg: cstring) = debugOut(msg) @@ -157,20 +180,20 @@ proc dbgFatal(msg: cstring) = proc dbgShowCurrentProc(dbgFramePointer: PFrame) = if dbgFramePointer != nil: - write(stdout, "*** endb| now in proc: ") - write(stdout, dbgFramePointer.procname) - write(stdout, " ***\n") + write(cstdout, "*** endb| now in proc: ") + write(cstdout, dbgFramePointer.procname) + write(cstdout, " ***\n") else: - write(stdout, "*** endb| (proc name not available) ***\n") + write(cstdout, "*** endb| (proc name not available) ***\n") proc dbgShowExecutionPoint() = - write(stdout, "*** endb| ") - write(stdout, framePtr.filename) - write(stdout, "(") - write(stdout, framePtr.line) - write(stdout, ") ") - write(stdout, framePtr.procname) - write(stdout, " ***\n") + write(cstdout, "*** endb| ") + write(cstdout, framePtr.filename) + write(cstdout, "(") + write(cstdout, framePtr.line) + write(cstdout, ") ") + write(cstdout, framePtr.procname) + write(cstdout, " ***\n") proc scanAndAppendWord(src: cstring, a: var StaticStr, start: int): int = result = start @@ -279,7 +302,7 @@ proc breakpointToggle(s: cstring, start: int) = if not b.isNil: b.flip else: debugOut("[Warning] unknown breakpoint ") -proc dbgEvaluate(stream: File, s: cstring, start: int, f: PFrame) = +proc dbgEvaluate(stream: CFilePtr, s: cstring, start: int, f: PFrame) = var dbgTemp: StaticStr var i = scanWord(s, dbgTemp, start) while s[i] in {' ', '\t'}: inc(i) @@ -315,8 +338,8 @@ proc dbgStackFrame(s: cstring, start: int, currFrame: PFrame) = var dbgTemp: StaticStr var i = scanFilename(s, dbgTemp, start) if dbgTemp.len == 0: - # just write it to stdout: - listFrame(stdout, currFrame) + # just write it to cstdout: + listFrame(cstdout, currFrame) else: var stream = openAppend(addr dbgTemp.data) if stream == nil: @@ -325,7 +348,7 @@ proc dbgStackFrame(s: cstring, start: int, currFrame: PFrame) = listFrame(stream, currFrame) close(stream) -proc readLine(f: File, line: var StaticStr): bool = +proc readLine(f: CFilePtr, line: var StaticStr): bool = while true: var c = c_fgetc(f) if c < 0'i32: @@ -340,16 +363,16 @@ proc readLine(f: File, line: var StaticStr): bool = result = true proc listFilenames() = - write(stdout, EndbBeg) - write(stdout, "| Files:\n") + write(cstdout, EndbBeg) + write(cstdout, "| Files:\n") var i = 0 while true: let x = dbgFilenames[i] if x.isNil: break - write(stdout, x) - write(stdout, "\n") + write(cstdout, x) + write(cstdout, "\n") inc i - write(stdout, EndbEnd) + write(cstdout, EndbEnd) proc dbgWriteStackTrace(f: PFrame) proc commandPrompt() = @@ -361,10 +384,10 @@ proc commandPrompt() = dbgTemp: StaticStr while again: - write(stdout, "*** endb| >>") + write(cstdout, "*** endb| >>") let oldLen = dbgUser.len dbgUser.len = 0 - if not readLine(stdin, dbgUser): break + if not readLine(cstdin, dbgUser): break if dbgUser.len == 0: dbgUser.len = oldLen # now look what we have to do: var i = scanWord(addr dbgUser.data, dbgTemp, 0) @@ -398,7 +421,7 @@ proc commandPrompt() = prevState = dbgState prevSkipFrame = dbgSkipToFrame dbgState = dbSkipCurrent - dbgEvaluate(stdout, addr dbgUser.data, i, dbgFramePtr) + dbgEvaluate(cstdout, addr dbgUser.data, i, dbgFramePtr) dbgState = prevState dbgSkipToFrame = prevSkipFrame elif ?"o" or ?"out": @@ -412,7 +435,7 @@ proc commandPrompt() = prevState = dbgState prevSkipFrame = dbgSkipToFrame dbgState = dbSkipCurrent - listLocals(stdout, dbgFramePtr) + listLocals(cstdout, dbgFramePtr) dbgState = prevState dbgSkipToFrame = prevSkipFrame elif ?"g" or ?"globals": @@ -420,7 +443,7 @@ proc commandPrompt() = prevState = dbgState prevSkipFrame = dbgSkipToFrame dbgState = dbSkipCurrent - listGlobals(stdout) + listGlobals(cstdout) dbgState = prevState dbgSkipToFrame = prevSkipFrame elif ?"u" or ?"up": @@ -501,29 +524,29 @@ proc dbgWriteStackTrace(f: PFrame) = b = b.prev for j in countdown(i-1, 0): if tempFrames[j] == nil: - write(stdout, "(") - write(stdout, skipped) - write(stdout, " calls omitted) ...") + write(cstdout, "(") + write(cstdout, skipped) + write(cstdout, " calls omitted) ...") else: - write(stdout, tempFrames[j].filename) + write(cstdout, tempFrames[j].filename) if tempFrames[j].line > 0: - write(stdout, '(') - write(stdout, tempFrames[j].line) - write(stdout, ')') - write(stdout, ' ') - write(stdout, tempFrames[j].procname) - write(stdout, "\n") + write(cstdout, "(") + write(cstdout, tempFrames[j].line) + write(cstdout, ")") + write(cstdout, " ") + write(cstdout, tempFrames[j].procname) + write(cstdout, "\n") proc checkForBreakpoint = let b = checkBreakpoints(framePtr.filename, framePtr.line) if b != nil: - write(stdout, "*** endb| reached ") - write(stdout, framePtr.filename) - write(stdout, "(") - write(stdout, framePtr.line) - write(stdout, ") ") - write(stdout, framePtr.procname) - write(stdout, " ***\n") + write(cstdout, "*** endb| reached ") + write(cstdout, framePtr.filename) + write(cstdout, "(") + write(cstdout, framePtr.line) + write(cstdout, ") ") + write(cstdout, framePtr.procname) + write(cstdout, " ***\n") commandPrompt() proc lineHookImpl() {.nimcall.} = diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 3c0e42c6e..fbfdc2532 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -17,15 +17,15 @@ var ## instead of stdmsg.write when printing stacktrace. ## Unstable API. -proc c_fwrite(buf: pointer, size, n: csize, f: File): cint {. +proc c_fwrite(buf: pointer, size, n: csize, f: CFilePtr): cint {. importc: "fwrite", header: "<stdio.h>".} -proc rawWrite(f: File, s: string|cstring) = +proc rawWrite(f: CFilePtr, s: string|cstring) = # we cannot throw an exception here! discard c_fwrite(cstring(s), 1, s.len, f) when not defined(windows) or not defined(guiapp): - proc writeToStdErr(msg: cstring) = rawWrite(stdmsg, msg) + proc writeToStdErr(msg: cstring) = rawWrite(cstderr, msg) else: proc MessageBoxA(hWnd: cint, lpText, lpCaption: cstring, uType: int): int32 {. @@ -220,11 +220,12 @@ proc auxWriteStackTrace(f: PFrame; s: var seq[StackTraceEntry]) = inc(i) it = it.prev var last = i-1 - if s.len == 0: - s = newSeq[StackTraceEntry](i) - else: - last = s.len + i - 1 - s.setLen(last+1) + when true: # not defined(gcDestructors): + if s.len == 0: + s = newSeq[StackTraceEntry](i) + else: + last = s.len + i - 1 + s.setLen(last+1) it = f while it != nil: s[last] = StackTraceEntry(procname: it.procname, @@ -347,26 +348,19 @@ proc raiseExceptionAux(e: ref Exception) = if globalRaiseHook != nil: if not globalRaiseHook(e): return when defined(cpp) and not defined(noCppExceptions): - if e[] of OutOfMemError: - showErrorMessage(e.name) - quitOrDebug() - else: - pushCurrentException(e) - raiseCounter.inc - if raiseCounter == 0: - raiseCounter.inc # skip zero at overflow - e.raiseId = raiseCounter - {.emit: "`e`->raise();".} + pushCurrentException(e) + raiseCounter.inc + if raiseCounter == 0: + raiseCounter.inc # skip zero at overflow + e.raiseId = raiseCounter + {.emit: "`e`->raise();".} elif defined(nimQuirky): - if currException == nil: currException = e + pushCurrentException(e) else: if excHandler != nil: if not excHandler.hasRaiseAction or excHandler.raiseAction(e): pushCurrentException(e) c_longjmp(excHandler.context, 1) - elif e[] of OutOfMemError: - showErrorMessage(e.name) - quitOrDebug() else: when hasSomeStackTrace: var buf = newStringOfCap(2000) @@ -449,33 +443,29 @@ proc getStackTrace(e: ref Exception): string = else: result = "" -when not defined(gcDestructors): - proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] = - ## Returns the attached stack trace to the exception ``e`` as - ## a ``seq``. This is not yet available for the JS backend. +proc getStackTraceEntries*(e: ref Exception): seq[StackTraceEntry] = + ## Returns the attached stack trace to the exception ``e`` as + ## a ``seq``. This is not yet available for the JS backend. + when not defined(gcDestructors): shallowCopy(result, e.trace) + else: + result = move(e.trace) -when defined(nimRequiresNimFrame): - const nimCallDepthLimit {.intdefine.} = 2000 - - proc callDepthLimitReached() {.noinline.} = - writeStackTrace() - showErrorMessage("Error: call depth limit reached in a debug build (" & - $nimCallDepthLimit & " function calls). You can change it with " & - "-d:nimCallDepthLimit=<int> but really try to avoid deep " & - "recursions instead.\n") - quitOrDebug() - - proc nimFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} = - s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1 - s.prev = framePtr - framePtr = s - if s.calldepth == nimCallDepthLimit: callDepthLimitReached() -else: - proc pushFrame(s: PFrame) {.compilerRtl, inl, exportc: "nimFrame".} = - # XXX only for backwards compatibility - s.prev = framePtr - framePtr = s +const nimCallDepthLimit {.intdefine.} = 2000 + +proc callDepthLimitReached() {.noinline.} = + writeStackTrace() + showErrorMessage("Error: call depth limit reached in a debug build (" & + $nimCallDepthLimit & " function calls). You can change it with " & + "-d:nimCallDepthLimit=<int> but really try to avoid deep " & + "recursions instead.\n") + quitOrDebug() + +proc nimFrame(s: PFrame) {.compilerRtl, inl.} = + s.calldepth = if framePtr == nil: 0 else: framePtr.calldepth+1 + s.prev = framePtr + framePtr = s + if s.calldepth == nimCallDepthLimit: callDepthLimitReached() when defined(endb): var @@ -538,3 +528,8 @@ proc setControlCHook(hook: proc () {.noconv.}) = # ugly cast, but should work on all architectures: type SignalHandler = proc (sign: cint) {.noconv, benign.} c_signal(SIGINT, cast[SignalHandler](hook)) + +when not defined(noSignalHandler) and not defined(useNimRtl): + proc unsetControlCHook() = + # proc to unset a hook set by setControlCHook + c_signal(SIGINT, signalHandler) diff --git a/lib/system/gc.nim b/lib/system/gc.nim index 9ae388aa9..d6d7da66e 100644 --- a/lib/system/gc.nim +++ b/lib/system/gc.nim @@ -104,7 +104,11 @@ when not defined(useNimRtl): template gcAssert(cond: bool, msg: string) = when defined(useGcAssert): if not cond: - echo "[GCASSERT] ", msg + cstderr.rawWrite "[GCASSERT] " + cstderr.rawWrite msg + when defined(logGC): + cstderr.rawWrite "[GCASSERT] statistics:\L" + cstderr.rawWrite GC_getStatistics() GC_disable() writeStackTrace() #var x: ptr int @@ -159,6 +163,10 @@ when defined(logGC): c_fprintf(stdout, "[GC] %s: %p %d %s rc=%ld; thread=%ld\n", msg, c, kind, typName, c.refcount shr rcShift, gch.gcThreadId) +template logCell(msg: cstring, c: PCell) = + when defined(logGC): + writeCell(msg, c) + template gcTrace(cell, state: untyped) = when traceGC: traceCell(cell, state) @@ -174,7 +182,7 @@ proc incRef(c: PCell) {.inline.} = gcAssert(isAllocatedPtr(gch.region, c), "incRef: interiorPtr") c.refcount = c.refcount +% rcIncrement # and not colorMask - #writeCell("incRef", c) + logCell("incRef", c) proc nimGCref(p: pointer) {.compilerProc.} = # we keep it from being collected by pretending it's not even allocated: @@ -192,6 +200,7 @@ proc decRef(c: PCell) {.inline.} = c.refcount = c.refcount -% rcIncrement if c.refcount <% rcIncrement: rtlAddZCT(c) + logCell("decRef", c) proc nimGCunref(p: pointer) {.compilerProc.} = let cell = usrToCell(p) @@ -410,7 +419,7 @@ proc rawNewObj(typ: PNimType, size: int, gch: var GcHeap): pointer = sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") # its refcount is zero, so add it to the ZCT: addNewObjToZCT(res, gch) - when logGC: writeCell("new cell", res) + logCell("new cell", res) track("rawNewObj", res, size) gcTrace(res, csAllocated) when useCellIds: @@ -455,7 +464,7 @@ proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} = setFrameInfo(res) res.refcount = rcIncrement # refcount is 1 sysAssert(isAllocatedPtr(gch.region, res), "newObj: 3") - when logGC: writeCell("new cell", res) + logCell("new cell", res) track("newObjRC1", res, size) gcTrace(res, csAllocated) when useCellIds: @@ -493,9 +502,8 @@ proc growObj(old: pointer, newsize: int, gch: var GcHeap): pointer = # This can be wrong for intermediate temps that are nevertheless on the # heap because of lambda lifting: #gcAssert(res.refcount shr rcShift <=% 1, "growObj: 4") - when logGC: - writeCell("growObj old cell", ol) - writeCell("growObj new cell", res) + logCell("growObj old cell", ol) + logCell("growObj new cell", res) gcTrace(ol, csZctFreed) gcTrace(res, csAllocated) track("growObj old", ol, 0) @@ -547,7 +555,7 @@ proc freeCyclicCell(gch: var GcHeap, c: PCell) = prepareDealloc(c) gcTrace(c, csCycFreed) track("cycle collector dealloc cell", c, 0) - when logGC: writeCell("cycle collector dealloc cell", c) + logCell("cycle collector dealloc cell", c) when reallyDealloc: sysAssert(allocInv(gch.region), "free cyclic cell") beforeDealloc(gch, c, "freeCyclicCell: stack trash") @@ -616,7 +624,7 @@ proc doOperation(p: pointer, op: WalkOp) = # c_fprintf(stdout, "[GC] decref bug: %p", c) gcAssert(isAllocatedPtr(gch.region, c), "decRef: waZctDecRef") gcAssert(c.refcount >=% rcIncrement, "doOperation 2") - when logGC: writeCell("decref (from doOperation)", c) + logCell("decref (from doOperation)", c) track("waZctDecref", p, 0) decRef(c) of waPush: @@ -704,7 +712,7 @@ proc collectZCT(gch: var GcHeap): bool = # as this might be too slow. # In any case, it should be removed from the ZCT. But not # freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!** - when logGC: writeCell("zct dealloc cell", c) + logCell("zct dealloc cell", c) track("zct dealloc cell", c, 0) gcTrace(c, csZctFreed) # We are about to free the object, call the finalizer BEFORE its @@ -858,6 +866,10 @@ when not defined(useNimRtl): for stack in items(gch.stack): result.add "[GC] stack " & stack.bottom.repr & "[GC] max stack size " & cast[pointer](stack.maxStackSize).repr & "\n" else: + # this caused memory leaks, see #10488 ; find a way without `repr` + # maybe using a local copy of strutils.toHex or snprintf + when defined(logGC): + result.add "[GC] stack bottom: " & gch.stack.bottom.repr result.add "[GC] max stack size: " & $gch.stat.maxStackSize & "\n" {.pop.} # profiler: off, stackTrace: off diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 565453298..5af64ae20 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -418,7 +418,7 @@ proc prepareDealloc(cell: PCell) = decTypeSize(cell, t) proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = - ## Frees the thread local heap. Runs every finalizer if ``runFinalizers``` + ## Frees the thread local heap. Runs every finalizer if ``runFinalizers`` ## is true. If ``allowGcAfterwards`` is true, a minimal amount of allocation ## happens to ensure the GC can continue to work after the call ## to ``deallocHeap``. @@ -457,7 +457,7 @@ proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = globalMarkers[globalMarkersLen] = markerProc inc globalMarkersLen else: - echo "[GC] cannot register global variable; too many global variables" + cstderr.rawWrite("[GC] cannot register global variable; too many global variables") quit 1 proc nimRegisterThreadLocalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = @@ -465,5 +465,5 @@ proc nimRegisterThreadLocalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} threadLocalMarkers[threadLocalMarkersLen] = markerProc inc threadLocalMarkersLen else: - echo "[GC] cannot register thread local variable; too many thread local variables" + cstderr.rawWrite("[GC] cannot register thread local variable; too many thread local variables") quit 1 diff --git a/lib/system/gc_ms.nim b/lib/system/gc_ms.nim index d8cb3e2d0..bd08eedf0 100644 --- a/lib/system/gc_ms.nim +++ b/lib/system/gc_ms.nim @@ -88,7 +88,8 @@ when not defined(useNimRtl): template gcAssert(cond: bool, msg: string) = when defined(useGcAssert): if not cond: - echo "[GCASSERT] ", msg + cstderr.rawWrite "[GCASSERT] " + cstderr.rawWrite msg quit 1 proc cellToUsr(cell: PCell): pointer {.inline.} = @@ -485,8 +486,9 @@ proc collectCTBody(gch: var GcHeap) = sysAssert(allocInv(gch.region), "collectCT: end") proc collectCT(gch: var GcHeap; size: int) = + let fmem = getFreeMem(gch.region) if (getOccupiedMem(gch.region) >= gch.cycleThreshold or - size > getFreeMem(gch.region)) and gch.recGcLock == 0: + size > fmem and fmem > InitialThreshold) and gch.recGcLock == 0: collectCTBody(gch) when not defined(useNimRtl): diff --git a/lib/system/gc_regions.nim b/lib/system/gc_regions.nim index 8a1446944..797eeeebf 100644 --- a/lib/system/gc_regions.nim +++ b/lib/system/gc_regions.nim @@ -23,6 +23,21 @@ when defined(nimphpext): proc osDeallocPages(p: pointer, size: int) {.inline.} = efree(p) +elif defined(useMalloc): + proc roundup(x, v: int): int {.inline.} = + result = (x + (v-1)) and not (v-1) + proc emalloc(size: int): pointer {.importc: "malloc", header: "<stdlib.h>".} + proc efree(mem: pointer) {.importc: "free", header: "<stdlib.h>".} + + proc osAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osTryAllocPages(size: int): pointer {.inline.} = + emalloc(size) + + proc osDeallocPages(p: pointer, size: int) {.inline.} = + efree(p) + else: include osalloc @@ -108,6 +123,8 @@ template `+!`(p: pointer, s: int): pointer = template `-!`(p: pointer, s: int): pointer = cast[pointer](cast[int](p) -% s) +const nimMinHeapPages {.intdefine.} = 4 + proc allocSlowPath(r: var MemRegion; size: int) = # we need to ensure that the underlying linked list # stays small. Say we want to grab 16GB of RAM with some @@ -116,9 +133,8 @@ proc allocSlowPath(r: var MemRegion; size: int) = # 8MB, 16MB, 32MB, 64MB, 128MB, 512MB, 1GB, 2GB, 4GB, 8GB, # 16GB --> list contains only 20 elements! That's reasonable. if (r.totalSize and 1) == 0: - r.nextChunkSize = - if r.totalSize < 64 * 1024: PageSize*4 - else: r.nextChunkSize*2 + r.nextChunkSize = if r.totalSize < 64 * 1024: PageSize*nimMinHeapPages + else: r.nextChunkSize*2 var s = roundup(size+sizeof(BaseChunk), PageSize) var fresh: Chunk if s > r.nextChunkSize: @@ -179,6 +195,19 @@ proc runFinalizers(c: Chunk) = (cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader)) it = it.nextFinal +proc runFinalizers(c: Chunk; newbump: pointer) = + var it = c.head + var prev: ptr ObjHeader = nil + while it != nil: + let nxt = it.nextFinal + if it >= newbump: + if it.typ != nil and it.typ.finalizer != nil: + (cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader)) + elif prev != nil: + prev.nextFinal = nil + prev = it + it = nxt + proc dealloc(r: var MemRegion; p: pointer; size: int) = let it = cast[ptr ObjHeader](p-!sizeof(ObjHeader)) if it.typ != nil and it.typ.finalizer != nil: @@ -221,16 +250,15 @@ template computeRemaining(r): untyped = proc setObstackPtr*(r: var MemRegion; sp: StackPtr) = # free everything after 'sp': - if sp.current.next != nil: + if sp.current != nil and sp.current.next != nil: deallocAll(r, sp.current.next) sp.current.next = nil when false: # better leak this memory than be sorry: for i in 0..high(r.freeLists): r.freeLists[i] = nil r.holes = nil - #else: - # deallocAll(r, r.head) - # r.head = nil + if r.tail != nil: runFinalizers(r.tail, sp.bump) + r.bump = sp.bump r.tail = sp.current r.remaining = sp.remaining @@ -241,6 +269,13 @@ proc deallocAll*() = tlRegion.deallocAll() proc deallocOsPages(r: var MemRegion) = r.deallocAll() +when false: + let obs = obstackPtr() + try: + body + finally: + setObstackPtr(obs) + template withScratchRegion*(body: untyped) = var scratch: MemRegion let oldRegion = tlRegion diff --git a/lib/system/helpers.nim b/lib/system/helpers.nim index fb1218684..a7e47915e 100644 --- a/lib/system/helpers.nim +++ b/lib/system/helpers.nim @@ -1,11 +1,28 @@ -## helpers used system.nim and other modules, avoids code duplication while -## also minimizing symbols exposed in system.nim +# helpers used system.nim and other modules, avoids code duplication while +# also minimizing symbols exposed in system.nim # # TODO: move other things here that should not be exposed in system.nim proc lineInfoToString(file: string, line, column: int): string = file & "(" & $line & ", " & $column & ")" -proc `$`(info: type(instantiationInfo(0))): string = +type InstantiationInfo = tuple[filename: string, line: int, column: int] + +proc `$`(info: InstantiationInfo): string = # The +1 is needed here + # instead of overriding `$` (and changing its meaning), consider explicit name. lineInfoToString(info.fileName, info.line, info.column+1) + +proc isNamedTuple(T: type): bool = + ## return true for named tuples, false for any other type. + when T isnot tuple: result = false + else: + var t: T + for name, _ in t.fieldPairs: + when name == "Field0": + return compiles(t.Field0) + else: + return true + # empty tuple should be un-named, + # see https://github.com/nim-lang/Nim/issues/8861#issue-356631191 + return false diff --git a/lib/system/helpers2.nim b/lib/system/helpers2.nim deleted file mode 100644 index 1c9e7c068..000000000 --- a/lib/system/helpers2.nim +++ /dev/null @@ -1,5 +0,0 @@ -template formatErrorIndexBound*[T](i, a, b: T): string = - "index out of bounds: (a:" & $a & ") <= (i:" & $i & ") <= (b:" & $b & ") " - -template formatErrorIndexBound*[T](i, n: T): string = - "index out of bounds: (i:" & $i & ") <= (n:" & $n & ") " diff --git a/lib/system/indexerrors.nim b/lib/system/indexerrors.nim new file mode 100644 index 000000000..8bd69ad71 --- /dev/null +++ b/lib/system/indexerrors.nim @@ -0,0 +1,7 @@ +# imported by other modules, unlike helpers.nim which is included + +template formatErrorIndexBound*[T](i, a, b: T): string = + "index out of bounds: (a: " & $a & ") <= (i: " & $i & ") <= (b: " & $b & ") " + +template formatErrorIndexBound*[T](i, n: T): string = + "index out of bounds: (i: " & $i & ") <= (n: " & $n & ") " diff --git a/lib/system/sysio.nim b/lib/system/io.nim index 5b0278d74..e93f602ae 100644 --- a/lib/system/sysio.nim +++ b/lib/system/io.nim @@ -1,19 +1,60 @@ # # # Nim's Runtime Library -# (c) Copyright 2013 Andreas Rumpf +# (c) Copyright 2019 Nim contributors # # See the file "copying.txt", included in this # distribution, for details about the copyright. # +include inclrtl + +# ----------------- IO Part ------------------------------------------------ +type + CFile {.importc: "FILE", header: "<stdio.h>", + incompletestruct.} = object + File* = ptr CFile ## The type representing a file handle. + + FileMode* = enum ## The file mode when opening a file. + fmRead, ## Open the file for read access only. + fmWrite, ## Open the file for write access only. + ## If the file does not exist, it will be + ## created. Existing files will be cleared! + fmReadWrite, ## Open the file for read and write access. + ## If the file does not exist, it will be + ## created. Existing files will be cleared! + fmReadWriteExisting, ## Open the file for read and write access. + ## If the file does not exist, it will not be + ## created. The existing file will not be cleared. + fmAppend ## Open the file for writing only; append data + ## at the end. + + FileHandle* = cint ## type that represents an OS file handle; this is + ## useful for low-level file access + +# text file handling: +when not defined(nimscript) and not defined(js): + var + stdin* {.importc: "stdin", header: "<stdio.h>".}: File + ## The standard input stream. + stdout* {.importc: "stdout", header: "<stdio.h>".}: File + ## The standard output stream. + stderr* {.importc: "stderr", header: "<stdio.h>".}: File + ## The standard error stream. + +when defined(useStdoutAsStdmsg): + template stdmsg*: File = stdout +else: + template stdmsg*: File = stderr + ## Template which expands to either stdout or stderr depending on + ## `useStdoutAsStdmsg` compile-time switch. -# Nim's standard IO library. It contains high-performance -# routines for reading and writing data to (buffered) files or -# TTYs. - -{.push debugger:off .} # the user does not want to trace a part - # of the standard library! +when defined(windows): + proc c_fileno(f: File): cint {. + importc: "_fileno", header: "<stdio.h>".} +else: + proc c_fileno(f: File): cint {. + importc: "fileno", header: "<fcntl.h>".} when defined(windows): proc c_fdopen(filehandle: cint, mode: cstring): File {. @@ -68,6 +109,12 @@ proc c_ferror(f: File): cint {. proc c_setvbuf(f: File, buf: pointer, mode: cint, size: csize): cint {. importc: "setvbuf", header: "<stdio.h>", tags: [].} +proc c_fprintf(f: File, frmt: cstring): cint {. + importc: "fprintf", header: "<stdio.h>", varargs, discardable.} + +template sysFatal(exc, msg) = + raise newException(exc, msg) + proc raiseEIO(msg: string) {.noinline, noreturn.} = sysFatal(IOError, msg) @@ -91,34 +138,63 @@ proc checkErr(f: File) = quit(1) {.push stackTrace:off, profiler:off.} -proc readBuffer(f: File, buffer: pointer, len: Natural): int = +proc readBuffer*(f: File, buffer: pointer, len: Natural): int {. + tags: [ReadIOEffect], benign.} = + ## 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. result = c_fread(buffer, 1, len, f) if result != len: checkErr(f) -proc readBytes(f: File, a: var openArray[int8|uint8], start, len: Natural): int = +proc readBytes*(f: File, a: var openArray[int8|uint8], start, len: Natural): int {. + tags: [ReadIOEffect], benign.} = + ## 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. result = readBuffer(f, addr(a[start]), len) -proc readChars(f: File, a: var openArray[char], start, len: Natural): int = +proc readChars*(f: File, a: var openArray[char], start, len: Natural): int {. + tags: [ReadIOEffect], benign.} = + ## 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. + ## + ## **Warning:** The buffer `a` must be pre-allocated. This can be done + ## using, for example, ``newString``. if (start + len) > len(a): raiseEIO("buffer overflow: (start+len) > length of openarray buffer") result = readBuffer(f, addr(a[start]), len) -proc write(f: File, c: cstring) = +proc write*(f: File, c: cstring) {.tags: [WriteIOEffect], benign.} = + ## Writes a value to the file `f`. May throw an IO exception. discard c_fputs(c, f) checkErr(f) -proc writeBuffer(f: File, buffer: pointer, len: Natural): int = +proc writeBuffer*(f: File, buffer: pointer, len: Natural): int {. + tags: [WriteIOEffect], benign.} = + ## 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. result = c_fwrite(buffer, 1, len, f) checkErr(f) -proc writeBytes(f: File, a: openArray[int8|uint8], start, len: Natural): int = +proc writeBytes*(f: File, a: openArray[int8|uint8], start, len: Natural): int {. + tags: [WriteIOEffect], benign.} = + ## 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. var x = cast[ptr UncheckedArray[int8]](a) result = writeBuffer(f, addr(x[int(start)]), len) -proc writeChars(f: File, a: openArray[char], start, len: Natural): int = + +proc writeChars*(f: File, a: openArray[char], start, len: Natural): int {. + tags: [WriteIOEffect], benign.} = + ## 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. var x = cast[ptr UncheckedArray[int8]](a) result = writeBuffer(f, addr(x[int(start)]), len) -proc write(f: File, s: string) = +proc write*(f: File, s: string) {.tags: [WriteIOEffect], benign.} = if writeBuffer(f, cstring(s), s.len) != s.len: raiseEIO("cannot write string to file") {.pop.} @@ -141,21 +217,40 @@ else: const BufSize = 4000 -proc close*(f: File) = +proc close*(f: File) {.tags: [], gcsafe.} = + ## Closes the file. if not f.isNil: discard c_fclose(f) -proc readChar(f: File): char = +proc readChar*(f: File): char {.tags: [ReadIOEffect].} = + ## Reads a single character from the stream `f`. Should not be used in + ## performance sensitive code. let x = c_fgetc(f) if x < 0: checkErr(f) raiseEOF() result = char(x) -proc flushFile*(f: File) = discard c_fflush(f) -proc getFileHandle*(f: File): FileHandle = c_fileno(f) +proc flushFile*(f: File) {.tags: [WriteIOEffect].} = + ## Flushes `f`'s buffer. + discard c_fflush(f) + +proc getFileHandle*(f: File): FileHandle = + ## returns the OS file handle of the file ``f``. This is only useful for + ## platform specific programming. + c_fileno(f) + +proc readLine*(f: File, line: var TaintedString): bool {.tags: [ReadIOEffect], + benign.} = + ## reads a line of text from the file `f` into `line`. May throw an IO + ## exception. + ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline + ## character(s) are not part of the returned string. Returns ``false`` + ## if the end of the file has been reached, ``true`` otherwise. If + ## ``false`` is returned `line` contains no new data. + proc c_memchr(s: pointer, c: cint, n: csize): pointer {. + importc: "memchr", header: "<string.h>".} -proc readLine(f: File, line: var TaintedString): bool = var pos = 0 # Use the currently reserved space for a first try @@ -165,7 +260,8 @@ proc readLine(f: File, line: var TaintedString): bool = while true: # memset to \L so that we can tell how far fgets wrote, even on EOF, where # fgets doesn't append an \L - nimSetMem(addr line.string[pos], '\L'.ord, sp) + for i in 0..<sp: line.string[pos+i] = '\L' + var fgetsSuccess = c_fgets(addr line.string[pos], sp.cint, f) != nil if not fgetsSuccess: checkErr(f) let m = c_memchr(addr line.string[pos], '\L'.ord, sp) @@ -191,32 +287,37 @@ proc readLine(f: File, line: var TaintedString): bool = sp = 128 # read in 128 bytes at a time line.string.setLen(pos+sp) -proc readLine(f: File): TaintedString = +proc readLine*(f: File): TaintedString {.tags: [ReadIOEffect], benign.} = + ## reads a line of text from the file `f`. May throw an IO exception. + ## A line of text may be delimited by ``LF`` or ``CRLF``. The newline + ## character(s) are not part of the returned string. result = TaintedString(newStringOfCap(80)) if not readLine(f, result): raiseEOF() -proc write(f: File, i: int) = +proc write*(f: File, i: int) {.tags: [WriteIOEffect], benign.} = when sizeof(int) == 8: if c_fprintf(f, "%lld", i) < 0: checkErr(f) else: if c_fprintf(f, "%ld", i) < 0: checkErr(f) -proc write(f: File, i: BiggestInt) = +proc write*(f: File, i: BiggestInt) {.tags: [WriteIOEffect], benign.} = when sizeof(BiggestInt) == 8: if c_fprintf(f, "%lld", i) < 0: checkErr(f) else: if c_fprintf(f, "%ld", i) < 0: checkErr(f) -proc write(f: File, b: bool) = +proc write*(f: File, b: bool) {.tags: [WriteIOEffect], benign.} = if b: write(f, "true") else: write(f, "false") -proc write(f: File, r: float32) = +proc write*(f: File, r: float32) {.tags: [WriteIOEffect], benign.} = if c_fprintf(f, "%.16g", r) < 0: checkErr(f) -proc write(f: File, r: BiggestFloat) = +proc write*(f: File, r: BiggestFloat) {.tags: [WriteIOEffect], benign.} = if c_fprintf(f, "%.16g", r) < 0: checkErr(f) -proc write(f: File, c: char) = discard c_putc(cint(c), f) -proc write(f: File, a: varargs[string, `$`]) = +proc write*(f: File, c: char) {.tags: [WriteIOEffect], benign.} = + discard c_putc(cint(c), f) + +proc write*(f: File, a: varargs[string, `$`]) {.tags: [WriteIOEffect], benign.} = for x in items(a): write(f, x) proc readAllBuffer(file: File): string = @@ -240,7 +341,8 @@ proc rawFileSize(file: File): int64 = result = c_ftell(file) discard c_fseek(file, oldPos, 0) -proc endOfFile(f: File): bool = +proc endOfFile*(f: File): bool {.tags: [], benign.} = + ## Returns true iff `f` is at the end. var c = c_fgetc(f) discard c_ungetc(c, f) return c < 0'i32 @@ -263,7 +365,12 @@ proc readAllFile(file: File): string = var len = rawFileSize(file) result = readAllFile(file, len) -proc readAll(file: File): TaintedString = +proc readAll*(file: File): TaintedString {.tags: [ReadIOEffect], benign.} = + ## 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. + # Separate handling needed because we need to buffer when we # don't know the overall length of the File. when declared(stdin): @@ -280,19 +387,16 @@ proc writeLn[Ty](f: File, x: varargs[Ty, `$`]) = write(f, i) write(f, "\n") -proc writeLine[Ty](f: File, x: varargs[Ty, `$`]) = +proc writeLine*[Ty](f: File, x: varargs[Ty, `$`]) {.inline, + tags: [WriteIOEffect], benign.} = + ## writes the values `x` to `f` and then writes "\\n". + ## May throw an IO exception. for i in items(x): write(f, i) write(f, "\n") -when declared(stdout): - proc rawEcho(x: string) {.inline, compilerproc.} = write(stdout, x) - proc rawEchoNL() {.inline, compilerproc.} = write(stdout, "\n") - # interface to the C procs: -include "system/widestrs" - when defined(windows) and not defined(useWinAnsi): when defined(cpp): proc wfopen(filename, mode: WideCString): pointer {. @@ -356,9 +460,14 @@ when defined(posix) and not defined(nimscript): proc c_fstat(a1: cint, a2: var Stat): cint {. importc: "fstat", header: "<sys/stat.h>".} -proc open(f: var File, filename: string, + +proc open*(f: var File, filename: string, mode: FileMode = fmRead, - bufSize: int = -1): bool = + bufSize: int = -1): bool {.tags: [], raises: [], benign.} = + ## 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. var p: pointer = fopen(filename, FormatOpen[mode]) if p != nil: when defined(posix) and not defined(nimscript): @@ -377,49 +486,55 @@ proc open(f: var File, filename: string, elif bufSize == 0: discard c_setvbuf(f, nil, IONBF, 0) -proc reopen(f: File, filename: string, mode: FileMode = fmRead): bool = +proc reopen*(f: File, filename: string, mode: FileMode = fmRead): bool {. + tags: [], benign.} = + ## reopens the file `f` with given `filename` and `mode`. This + ## is often used to redirect the `stdin`, `stdout` or `stderr` + ## file variables. + ## + ## Default mode is readonly. Returns true iff the file could be reopened. var p: pointer = freopen(filename, FormatOpen[mode], f) result = p != nil -proc open(f: var File, filehandle: FileHandle, mode: FileMode): bool = +proc open*(f: var File, filehandle: FileHandle, + mode: FileMode = fmRead): bool {.tags: [], raises: [], benign.} = + ## Creates a ``File`` from a `filehandle` with given `mode`. + ## + ## Default mode is readonly. Returns true iff the file could be opened. + f = c_fdopen(filehandle, FormatOpen[mode]) result = f != nil -proc setFilePos(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) = +proc open*(filename: string, + mode: FileMode = fmRead, bufSize: int = -1): File = + ## Opens a file named `filename` with given `mode`. + ## + ## Default mode is readonly. Raises an ``IOError`` if the file + ## could not be opened. + if not open(result, filename, mode, bufSize): + sysFatal(IOError, "cannot open: " & filename) + +proc setFilePos*(f: File, pos: int64, relativeTo: FileSeekPos = fspSet) {.benign.} = + ## sets the position of the file pointer that is used for read/write + ## operations. The file's first byte has the index zero. if c_fseek(f, pos, cint(relativeTo)) != 0: raiseEIO("cannot set file position") -proc getFilePos(f: File): int64 = +proc getFilePos*(f: File): int64 {.benign.} = + ## 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. result = c_ftell(f) if result < 0: raiseEIO("cannot retrieve file position") -proc getFileSize(f: File): int64 = +proc getFileSize*(f: File): int64 {.tags: [ReadIOEffect], benign.} = + ## retrieves the file size (in bytes) of `f`. var oldPos = getFilePos(f) discard c_fseek(f, 0, 2) # seek the end of the file result = getFilePos(f) setFilePos(f, oldPos) -proc readFile(filename: string): TaintedString = - var f: File - if open(f, filename): - try: - result = readAll(f).TaintedString - finally: - close(f) - else: - sysFatal(IOError, "cannot open: ", filename) - -proc writeFile(filename, content: string) = - var f: File - if open(f, filename, fmWrite): - try: - f.write(content) - finally: - close(f) - else: - sysFatal(IOError, "cannot open: ", filename) - -proc setStdIoUnbuffered() = +proc setStdIoUnbuffered*() {.tags: [], benign.} = + ## Configures `stdin`, `stdout` and `stderr` to be unbuffered. when declared(stdout): discard c_setvbuf(stdout, nil, IONBF, 0) when declared(stderr): @@ -429,6 +544,9 @@ proc setStdIoUnbuffered() = when declared(stdout): when defined(windows) and compileOption("threads"): + const insideRLocksModule = false + include "system/syslocks" + var echoLock: SysLock initSysLock echoLock @@ -450,4 +568,81 @@ when declared(stdout): when defined(windows) and compileOption("threads"): releaseSys echoLock -{.pop.} + +when defined(windows) and not defined(nimscript): + # work-around C's sucking abstraction: + # BUGFIX: stdin and stdout should be binary files! + proc c_setmode(handle, mode: cint) {. + importc: when defined(bcc): "setmode" else: "_setmode", + header: "<io.h>".} + var + O_BINARY {.importc: "_O_BINARY", header:"<fcntl.h>".}: cint + + # we use binary mode on Windows: + c_setmode(c_fileno(stdin), O_BINARY) + c_setmode(c_fileno(stdout), O_BINARY) + c_setmode(c_fileno(stderr), O_BINARY) + + +proc readFile*(filename: string): TaintedString {.tags: [ReadIOEffect], benign.} = + ## Opens a file named `filename` for reading, calls `readAll + ## <#readAll>`_ and closes the file afterwards. Returns the string. + ## Raises an IO exception in case of an error. If # you need to call + ## this inside a compile time macro you can use `staticRead + ## <#staticRead>`_. + var f: File + if open(f, filename): + try: + result = readAll(f).TaintedString + finally: + close(f) + else: + sysFatal(IOError, "cannot open: " & filename) + +proc writeFile*(filename, content: string) {.tags: [WriteIOEffect], benign.} = + ## 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. + var f: File + if open(f, filename, fmWrite): + try: + f.write(content) + finally: + close(f) + else: + sysFatal(IOError, "cannot open: " & filename) + +iterator lines*(filename: string): TaintedString {.tags: [ReadIOEffect].} = + ## Iterates over any line in the file named `filename`. + ## + ## If the file does not exist `IOError` is raised. The trailing newline + ## character(s) are removed from the iterated lines. Example: + ## + ## .. code-block:: nim + ## import strutils + ## + ## proc transformLetters(filename: string) = + ## var buffer = "" + ## for line in filename.lines: + ## buffer.add(line.replace("a", "0") & '\x0A') + ## writeFile(filename, buffer) + var f = open(filename, bufSize=8000) + defer: close(f) + var res = TaintedString(newStringOfCap(80)) + while f.readLine(res): yield res + +iterator lines*(f: File): TaintedString {.tags: [ReadIOEffect].} = + ## Iterate over any line in the file `f`. + ## + ## The trailing newline character(s) are removed from the iterated lines. + ## Example: + ## + ## .. code-block:: nim + ## proc countZeros(filename: File): tuple[lines, zeros: int] = + ## for line in filename.lines: + ## for letter in line: + ## if letter == '0': + ## result.zeros += 1 + ## result.lines += 1 + var res = TaintedString(newStringOfCap(80)) + while f.readLine(res): yield res diff --git a/lib/system/jssys.nim b/lib/system/jssys.nim index 8be19e5b8..d7718e4f4 100644 --- a/lib/system/jssys.nim +++ b/lib/system/jssys.nim @@ -46,7 +46,7 @@ proc nimCharToStr(x: char): string {.compilerproc.} = result[0] = x proc isNimException(): bool {.asmNoStackFrame.} = - asm "return `lastJSError`.m_type;" + asm "return `lastJSError` && `lastJSError`.m_type;" proc getCurrentException*(): ref Exception {.compilerRtl, benign.} = if isNimException(): result = cast[ref Exception](lastJSError) diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim index 89bc11d2c..9284f07d2 100644 --- a/lib/system/mmdisp.nim +++ b/lib/system/mmdisp.nim @@ -62,7 +62,7 @@ const proc raiseOutOfMem() {.noinline.} = if outOfMemHook != nil: outOfMemHook() - echo("out of memory") + cstderr.rawWrite("out of memory") quit(1) when defined(boehmgc): @@ -72,6 +72,8 @@ when defined(boehmgc): proc boehmGCincremental {. importc: "GC_enable_incremental", boehmGC.} proc boehmGCfullCollect {.importc: "GC_gcollect", boehmGC.} + proc boehmGC_set_all_interior_pointers(flag: cint) {. + importc: "GC_set_all_interior_pointers", boehmGC.} proc boehmAlloc(size: int): pointer {.importc: "GC_malloc", boehmGC.} proc boehmAllocAtomic(size: int): pointer {. importc: "GC_malloc_atomic", boehmGC.} @@ -148,6 +150,7 @@ when defined(boehmgc): proc nimGC_setStackBottom(theStackBottom: pointer) = discard proc initGC() = + boehmGC_set_all_interior_pointers(0) boehmGCinit() when hasThreadSupport: boehmGC_allow_register_threads() diff --git a/lib/system/nimscript.nim b/lib/system/nimscript.nim index 34daf30a9..1c4986aa4 100644 --- a/lib/system/nimscript.nim +++ b/lib/system/nimscript.nim @@ -7,6 +7,7 @@ # distribution, for details about the copyright. # +## To learn about scripting in Nim see `NimScript<nims.html>`_ # Nim's configuration system now uses Nim for scripting. This module provides # a few things that are required for this to work. @@ -46,7 +47,7 @@ proc copyDir(src, dest: string) {. tags: [ReadIOEffect, WriteIOEffect], raises: [OSError].} = builtin proc createDir(dir: string) {.tags: [WriteIOEffect], raises: [OSError].} = builtin -proc getOsError: string = builtin +proc getError: string = builtin proc setCurrentDir(dir: string) = builtin proc getCurrentDir*(): string = ## Retrieves the current working directory. @@ -178,9 +179,12 @@ var mode*: ScriptMode ## Set this to influence how mkDir, rmDir, rmFile etc. ## behave +template checkError(exc: untyped): untyped = + let err = getError() + if err.len > 0: raise newException(exc, err) + template checkOsError = - let err = getOsError() - if err.len > 0: raise newException(OSError, err) + checkError(OSError) template log(msg: string, body: untyped) = if mode in {ScriptMode.Verbose, ScriptMode.Whatif}: @@ -332,6 +336,26 @@ proc cppDefine*(define: string) = ## needs to be mangled. builtin +proc stdinReadLine(): TaintedString {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc stdinReadAll(): TaintedString {. + tags: [ReadIOEffect], raises: [IOError].} = + builtin + +proc readLineFromStdin*(): TaintedString {.raises: [IOError].} = + ## Reads a line of data from stdin - blocks until \n or EOF which happens when stdin is closed + log "readLineFromStdin": + result = stdinReadLine() + checkError(EOFError) + +proc readAllFromStdin*(): TaintedString {.raises: [IOError].} = + ## Reads all data from stdin - blocks until EOF which happens when stdin is closed + log "readAllFromStdin": + result = stdinReadAll() + checkError(EOFError) + when not defined(nimble): template `==?`(a, b: string): bool = cmpIgnoreStyle(a, b) == 0 template task*(name: untyped; description: string; body: untyped): untyped = @@ -341,14 +365,15 @@ when not defined(nimble): ## .. code-block:: nim ## task build, "default build is via the C backend": ## setCommand "c" - proc `name Task`*() = body + proc `name Task`*() = + setCommand "nop" + body let cmd = getCommand() if cmd.len == 0 or cmd ==? "help": setCommand "help" writeTask(astToStr(name), description) elif cmd ==? astToStr(name): - setCommand "nop" `name Task`() # nimble has its own implementation for these things. diff --git a/lib/system/strmantle.nim b/lib/system/strmantle.nim index ceaecb4f9..3c681fc53 100644 --- a/lib/system/strmantle.nim +++ b/lib/system/strmantle.nim @@ -7,7 +7,7 @@ # distribution, for details about the copyright. # -## Compilerprocs for strings that do not depend on the string implementation. +# Compilerprocs for strings that do not depend on the string implementation. proc cmpStrings(a, b: string): int {.inline, compilerProc.} = let alen = a.len diff --git a/lib/system/threads.nim b/lib/system/threads.nim index f89de4376..bbe170376 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -325,11 +325,8 @@ when not defined(useNimRtl): when emulatedThreadVars: if nimThreadVarsSize() > sizeof(ThreadLocalStorage): - echo "too large thread local storage size requested ", - "(", nimThreadVarsSize(), "/", sizeof(ThreadLocalStorage), "). ", - "Use -d:\"nimTlsSize=", nimThreadVarsSize(), - "\" to preallocate sufficient storage." - + c_fprintf(cstderr, """too large thread local storage size requested, +use -d:\"nimTlsSize=X\" to setup even more or stop using unittest.nim""") quit 1 when hasSharedHeap and not defined(boehmgc) and not defined(gogc) and not defined(nogc): diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim index 85e5e1462..a52e58ac3 100644 --- a/lib/system/widestrs.nim +++ b/lib/system/widestrs.nim @@ -10,8 +10,8 @@ # Nim support for C/C++'s `wide strings`:idx:. This is part of the system # module! Do not import it directly! -when not declared(ThisIsSystem): - {.error: "You must not import this module explicitly".} +#when not declared(ThisIsSystem): +# {.error: "You must not import this module explicitly".} type Utf16Char* = distinct int16 |