Why does making the FETCH part of the cycle a macro make it faster? Surely the branch predictor is fine with unconditional immediate branches. What am I missing here? Also, it has jump-to-register immediately after the instruction that sets that register. Wouldn't it be faster if it went like: get jmp address execute VM opcode jmp to next instruction So the pipeline can fetch it ahead of time?
The M1's frontend at least 24 instruction past the unconditional branch before the early possible moment it can even see it.
So the branch predictor isn't just responsible for predicting which way conditional branches go. It must remember where all branches are, and their target so that the front end can follow them with zero cycle delay. This means all branches, including call and return instructions too.
Which means that unconditional immediate branches cost about the same as a correctly predicted conditional branch.
But that's not actually why the fetch has been moved.
The other thing to note is that the frontend and backend of a modern CPU are completely disconnected. The frontend doesn't even try to get the correct address of an indirect jump from the backend. It always uses the branch predictor to predict the indirect branch.
And by inlining, each VM instruction has its own indirect jump, which means it gets different slot in the branch predictor allowing for better predictions.
At least that's the theory behind threaded code. I'm unsure how much of this speedup is coming from eliminating the extra unconditional immediate branch and how much is from better prediction of indirect branches.