Closed gvanrossum closed 1 year ago
FWIW, Mark anticipated this when he wrote:
Stack-based instructions are much more easily composed than register-based ones.
We probably need to see use cases for macro(). I don’t know if it makes sense to have any where the second instruction doesn’t consume the output of the first.
There are some use cases for macro in the Examples section of the Instruction Definitions doc. There's this macro:
macro ( LOAD_ATTR_INSTANCE_VALUE ) =
counter/1 + CHECK_OBJECT_TYPE + CHECK_HAS_INSTANCE_VALUES +
LOAD_INSTANCE_VALUE + unused/4 ;
where the first two components "pass through" owner
, which finally is consumed by LOAD_INSTANCE_VALUE
:
op ( CHECK_OBJECT_TYPE, (owner, type_version/2 -- owner) ) { ... }
op ( CHECK_HAS_INSTANCE_VALUES, (owner -- owner) ) { ... }
op ( LOAD_INSTANCE_VALUE, (owner, index/1 -- null if (oparg & 1), res) ) { ... }
I guess the generator should pay attention to name correspondence between input and output effects of an op as well as between the output effect of one op and the input of the next. In the stack world the convention is that if an input has the same name as an output (and they refer to the same stack entry) the instruction is not supposed to write to the output, we could keep that convention. But we still need to add the convention that name correspondence between different components means "use the same oparg/register".
Should we close this? Should we remove register instructions from the generator for 3.12?
I just ignore them for new features, so they probably don't work any more. Feel free to remove what's left of them, we will be able to use the git history to see what was there.
Removed in https://github.com/python/cpython/pull/102739.
It's pretty straightforward to add register instructions to the DSL. We can allow
which generates code like this:
There's an obvious restriction that there can be at most three I/O effects altogether (because there are only three opargs).
But what should we do for micro-ops? In the stack VM, the effect of two micro-ops is easily combined into the effect of a single instruction by simulating the stack effects in the generator: each input effect is a POP, each output effect is a PUSH. However, this doesn't work the same way for registers. E.g. if we had two register micro-ops,
what should the register effect of Z be? I guess this could be
(arg1, arg2 -- res1)
. But how can we pass info from one micro-op into the next? In the stack world the first op can PUSH something that the second op will POP, and since the PUSH and POP cancel each other out these are not reflected in the overall stack effect.I can punt for now and only add
register inst(...)
, so Irit can use the generator to define new register ops more easily, but this reduces the power of the story about combining micro-ops into macro-instructions, so I think eventually we will need an answer.One possibility would be to use name correspondence, so that e.g.
implies that Z has one register input (oparg1==arg1) and one register output (oparg2==res1) and one value that gets transferred between the micro-ops (temp1) -- in the current composition model the latter will just become a local variable that is never spilled to the register array. (In a world where we have a separate micro-op interpreter, we'd just have to spill it. This will be interesting because the bytecode->micro-code translator would have to allocate a register in this case.)
Note that in the register world we're probably not going to have super-instructions, so we won't have to think about those (but since this made me think of them, they would not have this problem because each component has its own three opargs).
Thoughts (@markshannon, @brandtbucher, @iritkatriel)?