Closed
Description
Splitting these micro-ops will improve performance by reducing the number of branches, the size of code generated, and the number of holes in the JIT stencils. There is no real downside; the increase in complexity at runtime is negligible and there isn't much increased complexity in the tooling.
Taking _LOAD_ATTR_INSTANCE_VALUE
as an example, as it is the dynamically most common.
op(_LOAD_ATTR_INSTANCE_VALUE, (index/1, owner -- attr, null if (oparg & 1))) {
...
can be split into
op(_LOAD_ATTR_INSTANCE_VALUE_0, (index/1, owner -- attr)) {
assert((oparg & 1) == 0);
...
and
op(_LOAD_ATTR_INSTANCE_VALUE_1, (index/1, owner -- attr, null)) {
assert((oparg & 1) == 1);
...
Each of these is simpler, thus smaller and faster than the base version.
We can always choose one of the two split version when projecting the trace, so we don't need an implementation of the base version at all. This means that the tier 2 interpreter and stencils aren't much bigger than before.