-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rework stack-frame handling in the VM #255
Conversation
4d982d0
to
9fe2b48
Compare
Hmm, it seems like |
I think tos stands for "top of stack" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me.
I do think simplifying the stack frame printing with a trivial loop and a comment saying what it's supposed to do would help a fair bit. See my review comment.
compiler/vm/vm.nim
Outdated
let bottom = high(c.sframes) | ||
let top = max(0, bottom - recursionLimit) | ||
|
||
# TODO: Skipped number of entries don't get reported? How is/was this intended to be handled? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the might be a bug, but for a comment on the whole proc it shouldn't be a recursive implementation, it can easily be a loop:
The intention:
- print the most recent frame last
- descend the stack and do so again
- until we run out of stack or reach depth
- if any frames remain, say how many we skipped
The above results in a message such as: "skipped x frames, calls that led up to printing".
9fe2b48
to
3ce065a
Compare
prc*: PSym # current prc; proc that is evaluated | ||
slots*: seq[TFullReg] # parameters passed to the proc + locals; | ||
# parameters come first | ||
next*: PStackFrame # for stacking | ||
next*: StackFrameIndex # for stacking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
next
could use Option[StackFrameIndex]
instead of just StackFrameIndex
. This would have the advantage of the default value meaning "no previous frame" (not the case right now), but would also require more space and make access a bit slower.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A sentinel index would work as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, the real trick would be extending optional, or at least a new data type, that allows for "in range" sentinel/nil like values.
Can do this later, but it's a pretty common thing in DOD land.
proc execute(c: var TCtx, start: int): PNode = | ||
var tos = PStackFrame(prc: nil, comesFrom: 0, next: nil) | ||
# XXX: instead of building the object first and then adding it to the list, it could |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the preferred style here? Adding to the list first and then modifying the element might be a bit more efficient (due to no copy/move having to take place)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not a hot loop in my opinion.
For the thought exercise:
Since it's a seq of capacity x, then it's automatically initialized to the zero value per element for each frame. Can we of we set the len to 1 afterwards, then just set the one field to -1, should do it?
Instead of having the stack frames as `ref` objects that are chained together via their `next` field, store them in a `seq` in `TCtx`. This makes it easier to reason about them, and also connects the frames to their owning context. While the frame list should ideally be first-in last-out (stack), this isn't currently possible due to how exception handling is implemented. In addition, the stack trace now always includes the entry function and also the number of skipped frames. A nil access error when no entry function exists (happens when a statement is evaluated) is also fixed.
3ce065a
to
fb03691
Compare
I transformed the recursive call into a loop and also implemented reporting for the number of skipped frames. There was also a possible nil access error that I fixed. Compilation under ORC should also succeed again. Since error reporting is the domain of @haxscramper, maybe they can weigh in on if they're okay with the reporting related changes. |
If you consider it necessary, you can add or remove report kinds and change how things are printed. If you think it would be better, then go for it. IIRC error messages from the VM are barely tested (if tested at all), so any improvement would be a win. |
|
||
var res = SemReport(kind: rsemVmStackTrace) | ||
res.currentExceptionA = c.currentExceptionA | ||
res.currentExceptionB = c.currentExceptionB | ||
|
||
aux(c, sframe, pc, 0, res) | ||
block: | ||
var i = sframe |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer the var/let block syntax, but don't change it, mostly fyi
|
||
aux(c, sframe.next, sframe.comesFrom, depth + 1, res) | ||
res.stacktrace.add((sym: sframe.prc, location: c.debug[pc])) | ||
## Generate and report the stack trace starting at frame `sframe` (inclusive). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nailed it! 🤩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bors r+
Build succeeded: |
Instead of having the stack frames as
ref
objects that are chainedtogether via their
next
field, store them in aseq
inTCtx
. Thismakes it easier to reason about them, and also connects the frames
to their owning context.
While the frame list should ideally be first-in last-out (stack), this
isn't currently possible due to how exception handling is implemented.
In addition, the stack trace now always includes the entry function
and also the number of skipped frames. A nil access error when
no entry function exists (happens when a statement is evaluated)
is also fixed.
Notes for reviewers: