First, most uops can't throw exceptions, so fusing a shift and an add instruction together doesn't require any complex tracking here.
If a uop throws an exception (let's say a fused add+ld), each uop can have a tag that helps you backtrace to its PC (instruction address) of let's say the start of the sequence, so you know what to inform the Privileged Architecture as to what "instruction" excepted. For many reasons, you need to store a list of PCs of the inflight instructions somewhere (although it is heavily compressed), so having a small ID tag to help reconstruct a given uop's PC isn't too onerous.
Ideally, multiple instructions may map to a single uop, but either none (or up to one) can throw an exception. The hard one here is something like load-pair uops; since each load can throw an exception. Some machines, if a fault is encountered, will refetch the pair and re-execute as independent/unfused loads. Other designs will just pay the pain of tracking which of the pair excepted and do some simple arithmetic off of that.
Instructions such as shift and add that have memory operands can throw exceptions on x86/amd64. (This was some of the motivations of RISC, separating loads/stores from ALU ops made exception handling cleaner).
Heh, random note, I just looked up shift instructions on x86, there are 6 different ones, not RISC. But today there's a lot over 1000 instructions so a few shift variants are peanuts.
Correcting myself, the issue was uops and not instructions. But this turns out to be still similar: at least intel nowadays keeps the memory addressing part in uops (most cases) and doesn't split instructions into load/store uops + alu ops.
I thought there was two different uop ISAs on big x86 cores with two different purposes these days. One is pretty close to the original instructions, just decoded and fixed width (on AMD at least, this is what's in the uCode ROM). Then those are cracked to another ISA that the ROB knows about because instructions will cross functional unit boundaries.
So in say 'rol mem_addr, shift', your inner ISA would be cracked to something like.
ld reg_temp0, mem_addr
rol reg_temp0, shift
st reg_temp0, mem_addr
This is all hearsay though; I could have certainly misheard/misremembered.
If a uop throws an exception (let's say a fused add+ld), each uop can have a tag that helps you backtrace to its PC (instruction address) of let's say the start of the sequence, so you know what to inform the Privileged Architecture as to what "instruction" excepted. For many reasons, you need to store a list of PCs of the inflight instructions somewhere (although it is heavily compressed), so having a small ID tag to help reconstruct a given uop's PC isn't too onerous.
Ideally, multiple instructions may map to a single uop, but either none (or up to one) can throw an exception. The hard one here is something like load-pair uops; since each load can throw an exception. Some machines, if a fault is encountered, will refetch the pair and re-execute as independent/unfused loads. Other designs will just pay the pain of tracking which of the pair excepted and do some simple arithmetic off of that.