#
#
#            Nim's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

# This file implements the Nim profiler. The profiler needs support by the
# code generator. The idea is to inject the instruction stream
# with 'nimProfile()' calls. These calls are injected at every loop end
# (except perhaps loops that have no side-effects). At every Nth call a
# stack trace is taken. A stack tace is a list of cstrings.

when defined(profiler) and defined(memProfiler):
  {.error: "profiler and memProfiler cannot be defined at the same time (See Embedded Stack Trace Profiler (ESTP) User Guide) for more details".}

{.push profiler: off.}

const
  MaxTraceLen = 20 # tracking the last 20 calls is enough

type
  StackTrace* = object
    lines*: array[0..MaxTraceLen-1, cstring]
    files*: array[0..MaxTraceLen-1, cstring]
  ProfilerHook* = proc (st: StackTrace) {.nimcall.}

proc `[]`*(st: StackTrace, i: int): cstring = st.lines[i]

proc captureStackTrace(f: PFrame, st: var StackTrace) =
  const
    firstCalls = 5
  var
    it = f
    i = 0
    total = 0
  while it != nil and i <= high(st.lines)-(firstCalls-1):
    # the (-1) is for the "..." entry
    st.lines[i] = it.procname
    st.files[i] = it.filename
    inc(i)
    inc(total)
    it = it.prev
  var b = it
  while it != nil:
    inc(total)
    it = it.prev
  for j in 1..total-i-(firstCalls-1):
    if b != nil: b = b.prev
  if total != i:
    st.lines[i] = "..."
    st.files[i] = "..."
    inc(i)
  while b != nil and i <= high(st.lines):
    st.lines[i] = b.procname
    st.files[i] = b.filename
    inc(i)
    b = b.prev

var
  profilingRequestedHook*: proc (): bool {.nimcall, gcsafe.}
    ## set this variable to provide a procedure that implements a profiler in
    ## user space. See the `nimprof` module for a reference implementation.

when defined(memProfiler):
  type
    MemProfilerHook* = proc (st: StackTrace, requestedSize: int) {.nimcall, gcsafe.}

  var
    profilerHook*: MemProfilerHook
      ## set this variable to provide a procedure that implements a profiler in
      ## user space. See the `nimprof` module for a reference implementation.

  proc callProfilerHook(hook: MemProfilerHook, requestedSize: int) =
    var st: StackTrace
    captureStackTrace(framePtr, st)
    hook(st, requestedSize)

  proc nimProfile(requestedSize: int) =
    if not isNil(profilingRequestedHook) and profilingRequestedHook():
      callProfilerHook(profilerHook, requestedSize)
else:
  var
    profilerHook*: ProfilerHook
      ## set this variable to provide a procedure that implements a profiler in
      ## user space. See the `nimprof` module for a reference implementation.

  proc callProfilerHook(hook: ProfilerHook) {.noinline.} =
    # 'noinline' so that 'nimProfile' does not perform the stack allocation
    # in the common case.
    when not defined(nimdoc):
      var st: StackTrace
      captureStackTrace(framePtr, st)
      hook(st)

  proc nimProfile() =
    ## This is invoked by the compiler in every loop and on every proc entry!
    if not isNil(profilingRequestedHook) and profilingRequestedHook():
      callProfilerHook(profilerHook)

{.pop.}
