Re: Understanding SNAP

  • From: Peter Cawley <corsix@xxxxxxxxxx>
  • To: luajit@xxxxxxxxxxxxx
  • Date: Sun, 24 Sep 2017 18:56:20 +0100

On Sun, Sep 24, 2017 at 2:36 PM, Raj <rajlistuser@xxxxxxxxx> wrote:

My attempt is to trace backwards and to find
out what causes the snapshot to be filled up.

Lots of entries in a snapshot just means that there are lots of local
variables (*) whose values have changed since the start of the trace.

(*) I'm using "local variables" from the point of view of the
interpreter, which includes:
* Call frame metadata for function calls inlined into the trace, or
for tail calls performed on trace
* Function arguments
* Actual local variables declared with "local" which are still in scope
* Hidden for-loop control variables
* Temporary stack slots required by the current expression / statement

If my understanding is correct, in first snapshot second position is
written by IR at 0003. Going by the argument of IR at 0003 I guess
0002 is x.

Correct. For reference, I would start counting snapshot entries from
-1 (or -2 in LJ_GC64 mode), at which point non-negative indices
correspond exactly to stack slots of the function which initiated the
trace. "x" in this case is local variable number #0 - you can see this
from e.g. cross-referencing "x = x * -3" with "0006  MULVN    0   0
1  ; -3" - the first 0 is the destination slot (x), and the second 0
is the source slot (also x).

What I do not understand is that in second snapshot line
(after IR 0005) 3rd and 6th position is modified by IR at 0004. How is
that?

I wouldn't think of IR operations as modifying local variables. In
fact, there is no IR operation which can store to a local variable -
such stores are elided, and get expressed as a snapshot immediately
preceding the next operation which can fail. If there were explicit
stores in the IR, then between 0004 and SNAP #2, you'd see two SSTOREs
which stored the result of 0004 (these two stores corresponding to the
internal and external copies of the for-loop control variable, which
naturally happen after 0004 as 0004 is incrementing the loop control
variable). As it happens, SNAP #2 is somewhat unusual, in that it
precedes a loop marker rather than preceding a failable instruction,
and so it is never taken by any exit. Regardless, it describes the
interpreter state at the start of the loop body (or at the end of the
loop body, depending on your view of where the loop header occurs):
  [-1] (call frame metadata) unchanged
  [0] (x) result of 0003
  [1] (internal copy of i) result of 0004
  [2] (for loop end index) unchanged
  [3] (for loop iteration increment) unchanged
  [4] (external copy of i) result of 0004

SNAP #3 has fewer entries than SNAP #2, as it exits to after the loop
has finished (**), and and therefore all the for-loop variables have
dropped out of scope. SNAP #1 has fewer entries for the same reason:
it exits to after the loop has finished, rather than exiting into the
body of the loop.

(**) You can't actually tell where a snapshot exits to based on jdump
output - I do wish such information was included in the output.

I'm not sure whether that answers your question, but to be honest, I'm
not quite sure what exactly you were questioning.

Now, how can I trace which variables are present in a snapshot
position in above IR? For eg: in SNAP #7 [ ---- 0007 ].

Again, start counting from -1 (or -2 in LJ_GC64 mode), and
non-negative indices correspond exactly to stack slots of the function
which initiated the trace. Starting at zero, function arguments occupy
one slot each (in their declared order), and then in-scope local
variables occupy one slot each (in their declared order) (***), and
then temporary stack slots required by the current expression /
statement occupy some number of slots, then if you're in an inlined
call, call frame metadata takes one slot (2 in LJ_GC64 mode), followed
by arguments/locals/etc. of the inlined call. As your example has no
arguments, the first local is at slot 0, and so:

  [-1] (call frame metadata) unchanged
  [0] (x) result of 0007

(***) These are interleaved with for-loop control variables, if any.
Each for-loop has three hidden variables - numeric for-loops have the
internal copy of the loop variable, the end index, and the increment,
whereas generic for-loops have a function, the state, and the internal
copy of the first loop variable.

Also what does the second argument to SLOAD (flags) signify? [I, CI,
CRI, T, PI, PRI, R, RI] etc... I have also seen SLOAD with second
argument empty.

It is a bitfield of flags, corresponding to this in the LuaJIT source:

[P] #define IRSLOAD_PARENT 0x01 /* Coalesce with parent trace. */
[F] #define IRSLOAD_FRAME 0x02 /* Load 32 bits of ftsz. */
[T] #define IRSLOAD_TYPECHECK 0x04 /* Needs type check. */
[C] #define IRSLOAD_CONVERT 0x08 /* Number to integer conversion. */
[R] #define IRSLOAD_READONLY 0x10 /* Read-only, omit slot store. */
[I] #define IRSLOAD_INHERIT 0x20 /* Inherited by exits/side traces. */

Other related posts: