Skip to content
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

Merged
merged 1 commit into from
Mar 22, 2022
Merged

Conversation

zerbina
Copy link
Collaborator

@zerbina zerbina commented Mar 18, 2022

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.


Notes for reviewers:

  • The name "tos" is still used for variables/parameters storing the stack frame index. I was not able to figure out what it's supposed to mean/stand for. If wanted, I can replace it's usages with something more descriptive

compiler/vm/vm.nim Outdated Show resolved Hide resolved
@zerbina
Copy link
Collaborator Author

zerbina commented Mar 18, 2022

Hmm, it seems like shallowCopy for seq doesn't work as I expected it to with ORC

compiler/vm/vm.nim Outdated Show resolved Hide resolved
@saem
Copy link
Collaborator

saem commented Mar 20, 2022

I think tos stands for "top of stack"

Copy link
Collaborator

@saem saem left a 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 Show resolved Hide resolved
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?
Copy link
Collaborator

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".

compiler/vm/vm.nim Outdated Show resolved Hide resolved
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
Copy link
Collaborator Author

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.

Copy link
Collaborator

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?

Copy link
Collaborator

@saem saem Mar 22, 2022

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
Copy link
Collaborator Author

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)

Copy link
Collaborator

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.
@zerbina
Copy link
Collaborator Author

zerbina commented Mar 21, 2022

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.

@haxscramper
Copy link
Collaborator

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
Copy link
Collaborator

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).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nailed it! 🤩

Copy link
Collaborator

@saem saem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bors r+

@bors
Copy link
Contributor

bors bot commented Mar 22, 2022

Build succeeded:

@bors bors bot merged commit f9a6578 into nim-works:devel Mar 22, 2022
@zerbina zerbina deleted the vm-stack-frames branch March 25, 2022 18:23
@haxscramper haxscramper added this to the VM backend refactoring milestone Nov 21, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants