summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2024-02-21 16:58:30 +0100
committerGitHub <noreply@github.com>2024-02-21 16:58:30 +0100
commit59c65009a57ba7f9677590c5066496bb85864ab8 (patch)
treeb4df9a04a84f0c5506cedb46e916c5dc9ef97882
parent773c066634d831a968bb464eab35b25a00026525 (diff)
downloadNim-59c65009a57ba7f9677590c5066496bb85864ab8.tar.gz
ORC: added -d:nimOrcStats switch and related API (#23272)
-rw-r--r--changelog.md7
-rw-r--r--lib/system.nim30
-rw-r--r--lib/system/arc.nim17
-rw-r--r--lib/system/excpt.nim6
-rw-r--r--lib/system/orc.nim27
5 files changed, 67 insertions, 20 deletions
diff --git a/changelog.md b/changelog.md
index f4a9d627c..e26e984e6 100644
--- a/changelog.md
+++ b/changelog.md
@@ -30,6 +30,9 @@ slots when enlarging a sequence.
 - Added `hasDefaultValue` to `std/typetraits` to check if a type has a valid default value.
 - Added Viewport API for the JavaScript targets in the `dom` module.
 - Added `toSinglyLinkedRing` and `toDoublyLinkedRing` to `std/lists` to convert from `openArray`s.
+- ORC: To be enabled via `nimOrcStats` there is a new API called `GC_orcStats` that can be used to query how many
+  objects the cyclic collector did free. If the number is zero that is a strong indicator that you can use `--mm:arc`
+  instead of `--mm:orc`.
 
 [//]: # "Deprecations:"
 
@@ -40,7 +43,7 @@ slots when enlarging a sequence.
 
 ## Language changes
 
-- `noInit` can be used in types and fields to disable member initializers in the C++ backend. 
+- `noInit` can be used in types and fields to disable member initializers in the C++ backend.
 - C++ custom constructors initializers see https://nim-lang.org/docs/manual_experimental.htm#constructor-initializer
 - `member` can be used to attach a procedure to a C++ type.
 - C++ `constructor` now reuses `result` instead creating `this`.
@@ -62,7 +65,7 @@ slots when enlarging a sequence.
   symbols in generic routine bodies to be replaced by symbols injected locally
   by templates/macros at instantiation time. `bind` may be used to keep the
   captured symbols over the injected ones regardless of enabling the option.
-  
+
   Since this change may affect runtime behavior, the experimental switch
   `genericsOpenSym` needs to be enabled, and a warning is given in the case
   where an injected symbol would replace a captured symbol not bound by `bind`
diff --git a/lib/system.nim b/lib/system.nim
index b90d525b5..9b556d008 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -1703,8 +1703,24 @@ when not defined(nimscript):
 when not declared(sysFatal):
   include "system/fatal"
 
+type
+  PFrame* = ptr TFrame  ## Represents a runtime frame of the call stack;
+                        ## part of the debugger API.
+  # keep in sync with nimbase.h `struct TFrame_`
+  TFrame* {.importc, nodecl, final.} = object ## The frame itself.
+    prev*: PFrame       ## Previous frame; used for chaining the call stack.
+    procname*: cstring  ## Name of the proc that is currently executing.
+    line*: int          ## Line number of the proc that is currently executing.
+    filename*: cstring  ## Filename of the proc that is currently executing.
+    len*: int16         ## Length of the inspectable slots.
+    calldepth*: int16   ## Used for max call depth checking.
+    when NimStackTraceMsgs:
+      frameMsgLen*: int   ## end position in frameMsgBuf for this frame.
 
 when defined(nimV2):
+  var
+    framePtr {.threadvar.}: PFrame
+
   include system/arc
 
 template newException*(exceptn: typedesc, message: string;
@@ -1849,20 +1865,6 @@ when notJSnotNims:
       ## writes an error message and terminates the program, except when
       ## using `--os:any`
 
-type
-  PFrame* = ptr TFrame  ## Represents a runtime frame of the call stack;
-                        ## part of the debugger API.
-  # keep in sync with nimbase.h `struct TFrame_`
-  TFrame* {.importc, nodecl, final.} = object ## The frame itself.
-    prev*: PFrame       ## Previous frame; used for chaining the call stack.
-    procname*: cstring  ## Name of the proc that is currently executing.
-    line*: int          ## Line number of the proc that is currently executing.
-    filename*: cstring  ## Filename of the proc that is currently executing.
-    len*: int16         ## Length of the inspectable slots.
-    calldepth*: int16   ## Used for max call depth checking.
-    when NimStackTraceMsgs:
-      frameMsgLen*: int   ## end position in frameMsgBuf for this frame.
-
 when defined(js) or defined(nimdoc):
   proc add*(x: var string, y: cstring) {.asmNoStackFrame.} =
     ## Appends `y` to `x` in place.
diff --git a/lib/system/arc.nim b/lib/system/arc.nim
index b788ac664..99c892676 100644
--- a/lib/system/arc.nim
+++ b/lib/system/arc.nim
@@ -26,6 +26,9 @@ else:
     rcMask = 0b111
     rcShift = 3      # shift by rcShift to get the reference counter
 
+const
+  orcLeakDetector = defined(nimOrcLeakDetector)
+
 type
   RefHeader = object
     rc: int # the object header is now a single RC field.
@@ -36,9 +39,21 @@ type
                    # in O(1) without doubly linked lists
     when defined(nimArcDebug) or defined(nimArcIds):
       refId: int
+    when defined(gcOrc) and orcLeakDetector:
+      filename: cstring
+      line: int
 
   Cell = ptr RefHeader
 
+template setFrameInfo(c: Cell) =
+  when orcLeakDetector:
+    if framePtr != nil and framePtr.prev != nil:
+      c.filename = framePtr.prev.filename
+      c.line = framePtr.prev.line
+    else:
+      c.filename = nil
+      c.line = 0
+
 template head(p: pointer): Cell =
   cast[Cell](cast[int](p) -% sizeof(RefHeader))
 
@@ -87,6 +102,7 @@ proc nimNewObj(size, alignment: int): pointer {.compilerRtl.} =
       cfprintf(cstderr, "[nimNewObj] %p %ld\n", result, head(result).count)
   when traceCollector:
     cprintf("[Allocated] %p result: %p\n", result -! sizeof(RefHeader), result)
+  setFrameInfo head(result)
 
 proc nimNewObjUninit(size, alignment: int): pointer {.compilerRtl.} =
   # Same as 'newNewObj' but do not initialize the memory to zero.
@@ -109,6 +125,7 @@ proc nimNewObjUninit(size, alignment: int): pointer {.compilerRtl.} =
 
   when traceCollector:
     cprintf("[Allocated] %p result: %p\n", result -! sizeof(RefHeader), result)
+  setFrameInfo head(result)
 
 proc nimDecWeakRef(p: pointer) {.compilerRtl, inl.} =
   decrement head(p)
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index 210b1a525..dae5c4a4a 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -73,8 +73,12 @@ type
 
 when NimStackTraceMsgs:
   var frameMsgBuf* {.threadvar.}: string
+
+when not defined(nimV2):
+  var
+    framePtr {.threadvar.}: PFrame
+
 var
-  framePtr {.threadvar.}: PFrame
   currException {.threadvar.}: ref Exception
 
 when not gotoBasedExceptions:
diff --git a/lib/system/orc.nim b/lib/system/orc.nim
index 335d49d0f..463c40c4d 100644
--- a/lib/system/orc.nim
+++ b/lib/system/orc.nim
@@ -81,10 +81,14 @@ proc trace(s: Cell; desc: PNimTypeV2; j: var GcEnv) {.inline.} =
 
 include threadids
 
-when logOrc:
+when logOrc or orcLeakDetector:
   proc writeCell(msg: cstring; s: Cell; desc: PNimTypeV2) =
-    cfprintf(cstderr, "%s %s %ld root index: %ld; RC: %ld; color: %ld; thread: %ld\n",
-      msg, desc.name, s.refId, s.rootIdx, s.rc shr rcShift, s.color, getThreadId())
+    when orcLeakDetector:
+      cfprintf(cstderr, "%s %s file: %s:%ld; color: %ld; thread: %ld\n",
+        msg, desc.name, s.filename, s.line, s.color, getThreadId())
+    else:
+      cfprintf(cstderr, "%s %s %ld root index: %ld; RC: %ld; color: %ld; thread: %ld\n",
+        msg, desc.name, s.refId, s.rootIdx, s.rc shr rcShift, s.color, getThreadId())
 
 proc free(s: Cell; desc: PNimTypeV2) {.inline.} =
   when traceCollector:
@@ -338,6 +342,8 @@ proc collectCyclesBacon(j: var GcEnv; lowMark: int) =
     collectColor(s, roots.d[i][1], colToCollect, j)
 
   for i in 0 ..< j.toFree.len:
+    when orcLeakDetector:
+      writeCell("CYCLIC OBJECT FREED", j.toFree.d[i][0], j.toFree.d[i][1])
     free(j.toFree.d[i][0], j.toFree.d[i][1])
 
   inc j.freed, j.toFree.len
@@ -352,6 +358,9 @@ when defined(nimStressOrc):
 else:
   var rootsThreshold {.threadvar.}: int
 
+when defined(nimOrcStats):
+  var freedCyclicObjects {.threadvar.}: int
+
 proc partialCollect(lowMark: int) =
   when false:
     if roots.len < 10 + lowMark: return
@@ -365,6 +374,8 @@ proc partialCollect(lowMark: int) =
       roots.len - lowMark)
   roots.len = lowMark
   deinit j.traceStack
+  when defined(nimOrcStats):
+    inc freedCyclicObjects, j.freed
 
 proc collectCycles() =
   ## Collect cycles.
@@ -405,6 +416,16 @@ proc collectCycles() =
   when logOrc:
     cfprintf(cstderr, "[collectCycles] end; freed %ld new threshold %ld touched: %ld mem: %ld rcSum: %ld edges: %ld\n", j.freed, rootsThreshold, j.touched,
       getOccupiedMem(), j.rcSum, j.edges)
+  when defined(nimOrcStats):
+    inc freedCyclicObjects, j.freed
+
+when defined(nimOrcStats):
+  type
+    OrcStats* = object ## Statistics of the cycle collector subsystem.
+      freedCyclicObjects*: int ## Number of freed cyclic objects.
+  proc GC_orcStats*(): OrcStats =
+    ## Returns the statistics of the cycle collector subsystem.
+    result = OrcStats(freedCyclicObjects: freedCyclicObjects)
 
 proc registerCycle(s: Cell; desc: PNimTypeV2) =
   s.rootIdx = roots.len+1