-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
## Summary Fix the compiler crashing when enabling the VM profiler (`--profilevm`) and fully restore the latter's original functionality. In addition, the profiler's overhead is significantly reduced. ## Details The VM profiler stopped working when the stack-frames moved from being `ref` based to being `seq` based (fb03691). This is due to the profiler expecting the frame index specified at the start of a measurement (`enter`) still being valid at the end of a measurement (`leave`), but this is not true when executing, for example, a `Ret` instruction, which led to an out-of-bounds error. As a simple and low impact fix, instead of remembering the index, `leave` now takes the list of current stack frames (i.e., the stack) as input, treating the last item as the current frame. A minor side-effect of this is that a `Ret` instruction now counts for the caller, while a call instruction counts for the callee. There was also a bug in the rendering logic: `min` was used instead of `max`, meaning that the entry limiting didn't work. In addition, the documentation of the `vmprofiler` module is improved and the implementation is refactored: * the `data` table now uses a `PSym` as the key. The intention of the table was always to associate data with a procedure, but `TLineInfo` and later `SourceLinePosition` only approximated this. Using a `PSym` also makes it possible to render more information (such as the procedure's name) in the future * whether profiling is enabled is now a property of the `Profiler` instance, removing the dependency on a `ConfigRef`. This also slightly reduces the overhead of `enter`/`leave` * the `leaveImpl` procedure is improved. The time difference is only computed once, and instead of temporarily assigning each frame to a local (which incurred a very costly full copy), only `prc` is assigned to a local With access to the symbol, rendering now also renders the full file, line, and column of the profiled procedures again (instead of only the containing file). Finally, an integration test (`tprofiler.nim`) and a basic unit test (`tvmprofiler.nim`) are added in order to prevent future regressions.
- Loading branch information
Showing
5 changed files
with
165 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
discard """ | ||
description: ''' | ||
Basic unit test for making sure that the VM profiler works as expected | ||
''' | ||
targets: native | ||
""" | ||
|
||
import | ||
std/[ | ||
os, | ||
strscans, | ||
strutils | ||
], | ||
compiler/ast/[ | ||
ast, | ||
lineinfos | ||
], | ||
compiler/front/[ | ||
options | ||
], | ||
compiler/utils/[ | ||
pathutils | ||
], | ||
compiler/vm/[ | ||
vmdef, | ||
vmprofiler | ||
] | ||
|
||
var | ||
conf = newConfigRef(nil) | ||
# get a ``FileInfoIdx``. The file's content doesn't matter, it only needs to | ||
# exist | ||
let self = conf.fileInfoIdx(currentSourcePath().AbsoluteFile) | ||
|
||
conf.filenameOption = foName # render the filepath as only the name | ||
|
||
# setup an enabled profiler: | ||
var profiler = Profiler(enabled: true) | ||
|
||
# setup some pseudo symbols to represent the procedures: | ||
var syms: seq[PSym] | ||
for i in 1..5: | ||
syms.add PSym(itemId: ItemId(module: 0, item: int32(i-1)), | ||
info: newLineInfo(self, i, i-1)) | ||
|
||
var frames: seq[TStackFrame] | ||
|
||
# "enter" multiple procedures: | ||
for it in syms.items: | ||
frames.add TStackFrame(prc: it) | ||
|
||
# take a sample on each frame: | ||
while frames.len > 0: | ||
profiler.enter() | ||
# sleep for a bit. The exact amount doesn't matter | ||
sleep(1) | ||
profiler.leave(frames) | ||
# "leave" the frame | ||
discard frames.pop() | ||
|
||
# render the data and verify the output: | ||
let output = dump(conf, profiler).splitLines() | ||
# the output must contain a header + 5 entries + trailing newline | ||
doAssert output.len == 7, $output | ||
doAssert output[^1] == "", output[^1] | ||
|
||
# verify the entries. Each must have exactly one sample taken, and the | ||
# procedure with the most time spent must be the first symbol, followed by | ||
# second one, etc. | ||
for i in 1..<output.len - 1: | ||
var | ||
time, num, line, col: int | ||
path: string | ||
doAssert scanf(output[i], "$s$i$s$i$s$w.nim($i,$s$i)$s$.", | ||
time, num, path, line, col) | ||
doAssert num == 1 # one sample is taken within each procedure | ||
doAssert path == "tvmprofiler" | ||
# check that the line + column are correct: | ||
doAssert line == i | ||
doAssert col == i |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
discard """ | ||
description: ''' | ||
Ensure that the VM profiler doesn't crash with exceptional control-flow | ||
''' | ||
matrix: "--profileVM" | ||
""" | ||
|
||
proc doSomething() = | ||
let f = 0.5 | ||
# further call nesting: | ||
discard $f | ||
|
||
proc p3() = | ||
# call some procedure | ||
doSomething() | ||
|
||
raise CatchableError.newException("") | ||
|
||
proc p2() = | ||
try: | ||
p3() | ||
finally: | ||
# procedure call in finally block during unwinding | ||
doSomething() | ||
|
||
proc p1() = | ||
try: | ||
p2() | ||
except: | ||
# procedure call in exception handler | ||
doSomething() | ||
|
||
static: | ||
p1() |