2019/09/06

Perl Parser Plugins 3a - The Stack

<< First | < Prev | Next >

In the previous article we looked at the implementation of a keyword that acts like a constant, providing a new value tau. We briefly saw how it is implemented with an op called OP_CONST, which pushes a value to "the stack". Lets now look in more detail at what we mean by operators having an effect on the stack.

For example, during the execution of a piece of code such as

  my $double = $x * 2;

the value stored in $x and the constant 2 get pushed to the value stack, so that the OP_MULTIPLY operator can multiply them, leaving the product on the stack. If the $x lexical held the value 5 before this code is executed, then the stack would see the following activity:

    |         |
    | (empty) |

  -> OP_PADSV; OP_CONST ->

    | IV=2 |
    | IV=5 |

  -> OP_MULTIPLY ->

    |       |
    | IV=10 |

Elements on the value stack are SV pointers, SV *, and refer directly to the SVs involved. As the stack points to SVs, it doesn't need to only store temporary values, but can refer to any SV in the perl interpreter. As the statement continues, it fetches the address of the SV representing the $double lexical from the pad and pushes that to the stack as well, so that the OP_SASSIGN operator can pop them both to see what to assign to where.

    |       |
    | IV=10 |

  -> OP_PADSV ->

    | (addr of $double) |
    | IV=10             |

  -> OP_SASSIGN ->

    |         |
    | (empty) |    $double now contains IV=10

We see here that at the end of the statement the stack has become empty again. This is its usual state - the stack stores temporary values of expressions evaluated during a single statement, but normally it is empty between statements.

In XS or core perl interpreter code, basic SV values are pushed to the stack using the PUSHs() macro, which takes an SV. There are also convenience macros which construct new SVs to contain integers, floating-point values, strings, etc; they are named PUSHi(), PUSHn(), PUSHp(), and so on.

  SV *sv = ...
  PUSHs(sv);

Note that pointers on the value stack are not reference counted - this is rare in Perl. Normally any SV pointer that points to an SV contributes to the value of the SvREFCNT() of that SV, but in the case of stack operations there is a performance benefit of not having to adjust the count all the time. For this reason, code that operates on stack values needs to be careful to mortalise or otherwise handle reference counting issues. There is an entire second set of push macros that mortalize the pushed value by calling sv_2mortal() on it. they are named with a prefixed m - mPUSHs(), mPUSHi(), etc.

  SV *tmp_sv = ...
  mPUSHs(tmp_sv);

  mPUSHi(123);

  mPUSHp("Hello, world", 12);

The stack is stored as a single contiguous array of SV pointers, whose base is given by the PL_stack_base interpreter variable. Again, there is a performance benefit from not actually checking the size of the stack before pushing a value to it, as many operators result in an overall reduction in the number of elements on the stack (for example, the OP_MULTIPLY described above). The EXTEND macro checks that there is room for at least the given number of elements and is commonly used in combination with the PUSH macros when returning a fixed-sized list of values.

  EXTEND(SP, 3);
  mPUSHi(t->tm_hour);
  mPUSHi(t->tm_min);
  mPUSHi(t->tm_sec);

There is an entire alternate set of push macros that extend the stack before pushing, in both regular and mortalizing variants; they are named XPUSH... and mXPUSH.... In general it is better to EXTEND once and use the non-X--variants, but at times when the required size is not known upfront these variants can be handy.

  while(p) {
    mXPUSHi(p->value);
    p = p->next;
  }

In core perl interpreter code, many operators inspect values on the top of the stack and remove them using the TOPs and POPs macros. TOPs simply returns the SV pointer at the top of the stack, and POPs removes it; being the inverse of PUSHs. As with the push macro, there are convenient type-converting macros here too which directly yield integers, floating-points, and so on from the top of the stack; they are named POPi, POPn, etc...

For example, a simple version of the OP_MULTIPLY operator could be implemented by a C function that performs the following:

  NV left = POPn;
  NV right = POPn;
  mPUSHn(left * right);

In actual fact the real OP_MULTIPLY has to handle a lot of other cases such as operator overloading and the various kinds of SV that might be found, but at its core this is the basic principle.

<< First | < Prev | Next >

No comments:

Post a Comment