3 ms·
> The UNARY_NEGATIVE opcode pops value from the stack, negates it and pushes the result. Why not have a top of stack register? Then all you need to put in yo
by DonaldFisk 6y ago
> The UNARY_NEGATIVE opcode pops value from the stack, negates it and pushes the result.
Why not have a top of stack register? Then all you need to put in your case statement (naively) is
tos = -tos;
thereby avoiding a pop and a push. In a virtual machine for a dynamically typed language such as Python, you'll need to handle different types, and for a statically typed language you'll need separate instructions for int and float, but in any case you avoid the pop and push. If the instruction takes more than one item from the stack, you at least have one fewer pop.
Incidentally, Burroughs mainframes were hardware stack machines and had two top of stack registers, A and B.
- teraflop 6y agoIn fact, that's exactly how Python implements UNARY_NEGATIVE: https://github.com/python/cpython/blob/v3.9.0/Python/ceval.c#L1538 https://github.com/python/cpython/blob/v3.9.0/Python/ceval.c... If you look a bit further down in the original article, you'll see that the BINARY_ADD instruction does something similar. It pops (a pointer to) the first operand, and modifies (a pointer to) the second one in-place. Semantically, it makes sense to define operations as popping the operand(s) and pushing a result, for simplicity. But there's no reason the interpreter has to actually be implemented that way, as long as the observable behavior is the same. In any case, I wouldn't be surprised if an extra push/pop ended up having very little performance impact. The compiler might be able to optimize away the pointer increment/decrement instructions, and if not, the stack pointer is pretty much guaranteed to be in the L1 cache.
- r4victor 6y agoAs teraflop pointed out, CPython doesn't actually do the pop and the push in this case. Sorry for being misleading. I've added an update note to clarify this.