From fff0201c5fe95715f40c5cbda1972b1e43d35d45 Mon Sep 17 00:00:00 2001 From: araq Date: Tue, 18 Nov 2025 17:06:52 +0100 Subject: [PATCH] system.nim: refactoring that removes cyclic dependencies in prep for IC --- lib/std/assertions.nim | 4 + lib/std/private/miscdollars.nim | 8 +- lib/system.nim | 230 +++++++++++++++++--------------- lib/system/excpt.nim | 54 ++++++-- lib/system/gc_interface.nim | 2 - lib/system/memalloc.nim | 10 +- lib/system/strs_v2.nim | 4 + 7 files changed, 182 insertions(+), 130 deletions(-) diff --git a/lib/std/assertions.nim b/lib/std/assertions.nim index 56c37d2057..f31a8465af 100644 --- a/lib/std/assertions.nim +++ b/lib/std/assertions.nim @@ -19,12 +19,16 @@ import std/private/miscdollars type InstantiationInfo = tuple[filename: string, line: int, column: int] +{.push overflowChecks: off, rangeChecks: off.} + proc `$`(info: InstantiationInfo): string = # The +1 is needed here # instead of overriding `$` (and changing its meaning), consider explicit name. result = "" result.toLocation(info.filename, info.line, info.column + 1) +{.pop.} + # --------------------------------------------------------------------------- diff --git a/lib/std/private/miscdollars.nim b/lib/std/private/miscdollars.nim index 06fda6fa1a..77ba158b0c 100644 --- a/lib/std/private/miscdollars.nim +++ b/lib/std/private/miscdollars.nim @@ -4,7 +4,13 @@ template toLocation*(result: var string, file: string | cstring, line: int, col: ## avoids spurious allocations # Hopefully this can be re-used everywhere so that if a user needs to customize, # it can be done in a single place. - result.add file + when file is cstring: + var i = 0 + while file[i] != '\0': + add(result, file[i]) + inc i + else: + result.add file if line > 0: result.add "(" addInt(result, line) diff --git a/lib/system.nim b/lib/system.nim index fece232b34..e6d0bd0bed 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1766,6 +1766,111 @@ template newException*(exceptn: typedesc, message: string; ## to `message`. Returns the new exception object. (ref exceptn)(msg: message, parent: parentException) +# we have to compute this here before turning it off in except.nim anyway ... +const NimStackTrace = compileOption("stacktrace") +const + usesDestructors = defined(gcDestructors) or defined(gcHooks) + +when notJSnotNims: + proc setControlCHook*(hook: proc () {.noconv.}) {.raises: [], gcsafe.} + ## Allows you to override the behaviour of your application when CTRL+C + ## is pressed. Only one such hook is supported. + ## + ## The handler runs inside a C signal handler and comes with similar + ## limitations. + ## + ## Allocating memory and interacting with most system calls, including using + ## `echo`, `string`, `seq`, raising or catching exceptions etc is undefined + ## behavior and will likely lead to application crashes. + ## + ## The OS may call the ctrl-c handler from any thread, including threads + ## that were not created by Nim, such as happens on Windows. + ## + ## ## Example: + ## + ## ```nim + ## var stop: Atomic[bool] + ## proc ctrlc() {.noconv.} = + ## # Using atomics types is safe! + ## stop.store(true) + ## + ## setControlCHook(ctrlc) + ## + ## while not stop.load(): + ## echo "Still running.." + ## sleep(1000) + ## ``` + + when not defined(noSignalHandler) and not defined(useNimRtl): + proc unsetControlCHook*() + ## Reverts a call to setControlCHook. + + when hostOS != "standalone": + proc getStackTrace*(): string {.gcsafe.} + ## Gets the current stack trace. This only works for debug builds. + + proc getStackTrace*(e: ref Exception): string {.gcsafe.} + ## Gets the stack trace associated with `e`, which is the stack that + ## lead to the `raise` statement. This only works for debug builds. + + var + globalRaiseHook*: proc (e: ref Exception): bool {.nimcall, benign.} + ## With this hook you can influence exception handling on a global level. + ## If not nil, every 'raise' statement ends up calling this hook. + ## + ## .. warning:: Ordinary application code should never set this hook! You better know what you do when setting this. + ## + ## If `globalRaiseHook` returns false, the exception is caught and does + ## not propagate further through the call stack. + + localRaiseHook* {.threadvar.}: proc (e: ref Exception): bool {.nimcall, benign.} + ## With this hook you can influence exception handling on a + ## thread local level. + ## If not nil, every 'raise' statement ends up calling this hook. + ## + ## .. warning:: Ordinary application code should never set this hook! You better know what you 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: [], benign, raises: [].} + ## 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 be used to raise an exception in case of OOM like so: + ## + ## ```nim + ## var gOutOfMem: ref EOutOfMemory + ## new(gOutOfMem) # need to be allocated *before* OOM really happened! + ## gOutOfMem.msg = "out of memory" + ## + ## proc handleOOM() = + ## raise gOutOfMem + ## + ## system.outOfMemHook = handleOOM + ## ``` + ## + ## If the handler does not raise an exception, ordinary control flow + ## continues and the program is terminated. + + unhandledExceptionHook*: proc (e: ref Exception) {.nimcall, tags: [], benign, raises: [].} + ## Set this variable to provide a procedure that should be called + ## in case of an `unhandle exception` event. The standard handler + ## writes an error message and terminates the program, except when + ## using `--os:any` + + {.push stackTrace: off, profiler: off.} + when defined(memtracker): + include "system/memtracker" + + when hostOS == "standalone": + include "system/embedded" + else: + include "system/excpt" + {.pop.} + + when not defined(nimPreviewSlimSystem): import std/assertions export assertions @@ -1844,9 +1949,6 @@ proc `<`*[T: tuple](x, y: T): bool = include "system/gc_interface" -# we have to compute this here before turning it off in except.nim anyway ... -const NimStackTrace = compileOption("stacktrace") - import system/coro_detection {.push checks: off.} @@ -1855,53 +1957,6 @@ import system/coro_detection # however, stack-traces are available for most parts # of the code -when notJSnotNims: - var - globalRaiseHook*: proc (e: ref Exception): bool {.nimcall, benign.} - ## With this hook you can influence exception handling on a global level. - ## If not nil, every 'raise' statement ends up calling this hook. - ## - ## .. warning:: Ordinary application code should never set this hook! You better know what you do when setting this. - ## - ## If `globalRaiseHook` returns false, the exception is caught and does - ## not propagate further through the call stack. - - localRaiseHook* {.threadvar.}: proc (e: ref Exception): bool {.nimcall, benign.} - ## With this hook you can influence exception handling on a - ## thread local level. - ## If not nil, every 'raise' statement ends up calling this hook. - ## - ## .. warning:: Ordinary application code should never set this hook! You better know what you 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: [], benign, raises: [].} - ## 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 be used to raise an exception in case of OOM like so: - ## - ## ```nim - ## var gOutOfMem: ref EOutOfMemory - ## new(gOutOfMem) # need to be allocated *before* OOM really happened! - ## gOutOfMem.msg = "out of memory" - ## - ## proc handleOOM() = - ## raise gOutOfMem - ## - ## system.outOfMemHook = handleOOM - ## ``` - ## - ## If the handler does not raise an exception, ordinary control flow - ## continues and the program is terminated. - unhandledExceptionHook*: proc (e: ref Exception) {.nimcall, tags: [], benign, raises: [].} - ## Set this variable to provide a procedure that should be called - ## in case of an `unhandle exception` event. The standard handler - ## writes an error message and terminates the program, except when - ## using `--os:any` - when defined(js) or defined(nimdoc): proc add*(x: var string, y: cstring) {.asmNoStackFrame.} = ## Appends `y` to `x` in place. @@ -2028,6 +2083,16 @@ template unlikely*(val: bool): bool = import system/dollars export dollars +when notJSnotNims: + {.push stackTrace: off, profiler: off.} + + include "system/chcks" + + # we cannot compile this with stack tracing on + # as it would recurse endlessly! + include "system/integerops" + {.pop.} + when defined(nimAuditDelete): {.pragma: auditDelete, deprecated: "review this call for out of bounds behavior".} else: @@ -2110,17 +2175,17 @@ when notJSnotNims: nimZeroMem(p, size) when declared(memTrackerOp): memTrackerOp("zeroMem", p, size) - proc copyMem(dest, source: pointer, size: Natural) = + proc copyMem(dest, source: pointer, size: Natural) {.enforceNoRaises.} = nimCopyMem(dest, source, size) when declared(memTrackerOp): memTrackerOp("copyMem", dest, size) - proc moveMem(dest, source: pointer, size: Natural) = + proc moveMem(dest, source: pointer, size: Natural) {.enforceNoRaises.} = c_memmove(dest, source, csize_t(size)) when declared(memTrackerOp): memTrackerOp("moveMem", dest, size) - proc equalMem(a, b: pointer, size: Natural): bool = + proc equalMem(a, b: pointer, size: Natural): bool {.enforceNoRaises.} = nimCmpMem(a, b, size) == 0 - proc cmpMem(a, b: pointer, size: Natural): int = + proc cmpMem(a, b: pointer, size: Natural): int {.enforceNoRaises.} = nimCmpMem(a, b, size).int when not defined(js) or defined(nimscript): @@ -2194,63 +2259,6 @@ when not defined(js): when not defined(useNimRtl) and not defined(createNimRtl): initStackBottom() when declared(initGC): initGC() -when notJSnotNims: - proc setControlCHook*(hook: proc () {.noconv.}) {.raises: [], gcsafe.} - ## Allows you to override the behaviour of your application when CTRL+C - ## is pressed. Only one such hook is supported. - ## - ## The handler runs inside a C signal handler and comes with similar - ## limitations. - ## - ## Allocating memory and interacting with most system calls, including using - ## `echo`, `string`, `seq`, raising or catching exceptions etc is undefined - ## behavior and will likely lead to application crashes. - ## - ## The OS may call the ctrl-c handler from any thread, including threads - ## that were not created by Nim, such as happens on Windows. - ## - ## ## Example: - ## - ## ```nim - ## var stop: Atomic[bool] - ## proc ctrlc() {.noconv.} = - ## # Using atomics types is safe! - ## stop.store(true) - ## - ## setControlCHook(ctrlc) - ## - ## while not stop.load(): - ## echo "Still running.." - ## sleep(1000) - ## ``` - - when not defined(noSignalHandler) and not defined(useNimRtl): - proc unsetControlCHook*() - ## Reverts a call to setControlCHook. - - when hostOS != "standalone": - proc getStackTrace*(): string {.gcsafe.} - ## Gets the current stack trace. This only works for debug builds. - - proc getStackTrace*(e: ref Exception): string {.gcsafe.} - ## Gets the stack trace associated with `e`, which is the stack that - ## lead to the `raise` statement. This only works for debug builds. - - {.push stackTrace: off, profiler: off.} - when defined(memtracker): - include "system/memtracker" - - when hostOS == "standalone": - include "system/embedded" - else: - include "system/excpt" - include "system/chcks" - - # we cannot compile this with stack tracing on - # as it would recurse endlessly! - include "system/integerops" - {.pop.} - when not defined(js): # this is a hack: without this when statement, you would get: diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim index 511839914f..18ebc1c609 100644 --- a/lib/system/excpt.nim +++ b/lib/system/excpt.nim @@ -42,11 +42,13 @@ proc writeToStdErr(msg: string) {.inline.} = # fix bug #13115: handles correctly '\0' unlike default implicit conversion to cstring writeToStdErr(msg.cstring, msg.len) +proc cstrToStrBuiltin(x: cstring): string {.magic: "CStrToStr", noSideEffect.} + proc showErrorMessage(data: cstring, length: int) {.gcsafe, raises: [].} = var toWrite = true if errorMessageWriter != nil: try: - errorMessageWriter($data) + errorMessageWriter(cstrToStrBuiltin data) toWrite = false except: discard @@ -261,7 +263,10 @@ template addFrameEntry(s: var string, f: StackTraceEntry|PFrame) = var oldLen = s.len s.toLocation(f.filename, f.line, 0) for k in 1..max(1, 25-(s.len-oldLen)): add(s, ' ') - add(s, f.procname) + var i = 0 + while f.procname[i] != '\0': + add(s, f.procname[i]) + inc i when NimStackTraceMsgs: when typeof(f) is StackTraceEntry: add(s, f.frameMsg) @@ -283,8 +288,34 @@ proc `$`(stackTraceEntries: seq[StackTraceEntry]): string = else: addFrameEntry(result, s[i]) when hasSomeStackTrace: + const + Ten = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"] - proc auxWriteStackTrace(f: PFrame, s: var string) = + proc i2s(x: int64): string = + # quick reimplementation; optimized for code size, no dependencies + if x < 0: + if x == -9223372036854775808: + result = "-9223372036854775808" + else: + result = "-" & i2s(0-x) + elif x < 10: + result = Ten[int x] # saves allocations + else: + var y = x + while true: + result.add char((y mod 10) + int('0')) + y = y div 10 + if y == 0: break + let last = result.len-1 + var i = 0 + let b = result.len div 2 + while i < b: + let ch = result[i] + result[i] = result[last-i] + result[last-i] = ch + inc i + + proc auxWriteStackTrace(f: PFrame, s: var string) {.raises: [].} = when hasThreadSupport: var tempFrames: array[maxStackTraceLines, PFrame] # but better than a threadvar @@ -322,14 +353,14 @@ when hasSomeStackTrace: for j in countdown(i-1, 0): if tempFrames[j] == nil: add(s, "(") - add(s, $skipped) + s.add(i2s(skipped)) add(s, " calls omitted) ...\n") else: addFrameEntry(s, tempFrames[j]) proc stackTraceAvailable*(): bool - proc rawWriteStackTrace(s: var string) = + proc rawWriteStackTrace(s: var string) {.raises: [].} = when defined(nimStackTraceOverride): add(s, "Traceback (most recent call last, using override)\n") auxWriteStackTraceWithOverride(s) @@ -388,7 +419,7 @@ proc reportUnhandledErrorAux(e: ref Exception) {.nodestroy, gcsafe.} = add(buf, "Error: unhandled exception: ") add(buf, e.msg) add(buf, " [") - add(buf, $e.name) + add(buf, cstrToStrBuiltin(e.name)) add(buf, "]\n") if onUnhandledException != nil: @@ -408,7 +439,7 @@ proc reportUnhandledErrorAux(e: ref Exception) {.nodestroy, gcsafe.} = var buf: array[0..2000, char] var L = 0 if e.trace.len != 0: - var trace = $e.trace + var trace = cstrToStrBuiltin(e.trace) add(buf, trace) {.gcsafe.}: `=destroy`(trace) @@ -559,10 +590,11 @@ const nimCallDepthLimit {.intdefine.} = 2000 proc callDepthLimitReached() {.noinline.} = writeStackTrace() - let msg = "Error: call depth limit reached in a debug build (" & - $nimCallDepthLimit & " function calls). You can change it with " & - "-d:nimCallDepthLimit= but really try to avoid deep " & - "recursions instead.\n" + var msg = "Error: call depth limit reached in a debug build (" + msg.add(i2s(nimCallDepthLimit)) + msg.add(" function calls). You can change it with " & + "-d:nimCallDepthLimit= but really try to avoid deep " & + "recursions instead.\n") showErrorMessage2(msg) rawQuit(1) diff --git a/lib/system/gc_interface.nim b/lib/system/gc_interface.nim index 4540db21f2..b34ce4a566 100644 --- a/lib/system/gc_interface.nim +++ b/lib/system/gc_interface.nim @@ -1,6 +1,4 @@ # ----------------- GC interface --------------------------------------------- -const - usesDestructors = defined(gcDestructors) or defined(gcHooks) when not usesDestructors: {.pragma: nodestroy.} diff --git a/lib/system/memalloc.nim b/lib/system/memalloc.nim index 6347357347..b26f3af24d 100644 --- a/lib/system/memalloc.nim +++ b/lib/system/memalloc.nim @@ -1,13 +1,13 @@ when notJSnotNims: proc zeroMem*(p: pointer, size: Natural) {.inline, noSideEffect, - tags: [], raises: [].} + tags: [], raises: [], enforceNoRaises.} ## 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: Natural) {.inline, benign, - tags: [], raises: [].} + tags: [], raises: [], enforceNoRaises.} ## Copies the contents from the memory at `source` to the memory ## at `dest`. ## Exactly `size` bytes will be copied. The memory @@ -15,7 +15,7 @@ when notJSnotNims: ## memory this is **unsafe**. proc moveMem*(dest, source: pointer, size: Natural) {.inline, benign, - tags: [], raises: [].} + tags: [], raises: [], enforceNoRaises.} ## Copies the contents from the memory at `source` to the memory ## at `dest`. ## @@ -25,7 +25,7 @@ when notJSnotNims: ## dealing with raw memory this is still **unsafe**, though. proc equalMem*(a, b: pointer, size: Natural): bool {.inline, noSideEffect, - tags: [], raises: [].} + tags: [], raises: [], enforceNoRaises.} ## Compares the memory blocks `a` and `b`. `size` bytes will ## be compared. ## @@ -34,7 +34,7 @@ when notJSnotNims: ## **unsafe**. proc cmpMem*(a, b: pointer, size: Natural): int {.inline, noSideEffect, - tags: [], raises: [].} + tags: [], raises: [], enforceNoRaises.} ## Compares the memory blocks `a` and `b`. `size` bytes will ## be compared. ## diff --git a/lib/system/strs_v2.nim b/lib/system/strs_v2.nim index 1b44e9123c..1bc8fb7d78 100644 --- a/lib/system/strs_v2.nim +++ b/lib/system/strs_v2.nim @@ -23,6 +23,8 @@ type const nimStrVersion {.core.} = 2 +{.push overflowChecks: off, rangeChecks: off.} + template isLiteral(s): bool = (s.p == nil) or (s.p.cap and strlitFlag) == strlitFlag template contentSize(cap): int = cap + 1 + sizeof(NimStrPayloadBase) @@ -227,3 +229,5 @@ func capacity*(self: string): int {.inline.} = let str = cast[ptr NimStringV2](unsafeAddr self) result = if str.p != nil: str.p.cap and not strlitFlag else: 0 + +{.pop.}