Previous | Table of Contents | Next

Well, there’s only one instruction other than POPF that loads the FLAGS register directly from the stack, and that’s IRET, which loads the FLAGS register from the stack as it branches, as shown in Figure 11.5. iret has no known bugs of the sort that plague POPF, so it’s certainly a candidate to replace popf in non-interruptible applications. Unfortunately, IRET loads the FLAGS register with the third word down on the stack, not the word on top of the stack, as is the case with POPF; the far return address that IRET pops into CS:IP lies between the top of the stack and the word popped into the FLAGS register.

Obviously, the segment:offset that IRET expects to find on the stack above the pushed flags isn’t present when the stack is set up for POPF, so we’ll have to adjust the stack a bit before we can substitute IRET for POPF. What we’ll have to do is push the segment:offset of the instruction after our workaround code onto the stack right above the pushed flags. IRET will then branch to that address and pop the flags, ending up at the instruction after the workaround code with the flags popped. That’s just the result that would have occurred had we executed POPF—WITH the bonus that no interrupts can accidentally occur when the Interrupt flag is 0 both before and after the pop.

Figure 11.4 The operation of POPF.

How can we push the segment:offset of the next instruction? Well, finding the offset of the next instruction by performing a near call to that instruction is a tried-and-true trick. We can do something similar here, but in this case we need a far call, since IRET requires both a segment and an offset. We’ll also branch backward so that the address pushed on the stack will point to the instruction we want to continue with. The code works out like this:

          jmpshort popfskip
    popfiret:
          iret;      branches to the instruction after the
                     ; call, popping the word below the address
                     ; pushed by CALL into the FLAGS register
    popfskip:
          call  far ptr popfiret
                     ;pushes the segment:offset of the next
                     ; instruction on the stack just above
                     ; the flags word, setting things up so
                     ; that IRET will branch to the next
                     ; instruction and pop the flags
    ; When execution reaches the instruction following this comment,
    ; the word that was on top of the stack when JMP SHORT POPFSKIP
    ; was reached has been popped into the FLAGS register, just as
    ; if a POPF instruction had been executed.

Figure 11.5 The operation of IRET.

The operation of this code is illustrated in Figure 11.6.

The POPF workaround can best be implemented as a macro; we can also emulate a far call by pushing CS and performing a near call, thereby shrinking the workaround code by 1 byte:

    EMULATE_POPF             macro
         local popfskip, popfiret
         jmp   short popfskip
    popfiret:
         iret
    popfskip:
         push  cs
         call  popfiret
         endm

By the way, the flags can be popped much more quickly if you’re willing to alter a register in the process. For example, the following macro emulates POPF with just one branch, but wipes out AX:

    EMULATE_POPF_TRASH_AX   macro
       push  cs
       mov   ax,offset $+5
       push  ax
       iret
       endm

It’s not a perfect substitute for POPF, since POPF doesn’t alter any registers, but it’s faster and shorter than EMULATE_POPF when you can spare the register. If you’re using 286-specific instructions, you can use which is shorter still, alters no registers, and branches just once. (Of course, this version of EMULATE_POPF won’t work on an 8088.)

          .286
                     :
    EMULATE_POPFmacro
          pushcs
          pushoffset $+4
          iret
          endm

Figure 11.6 Workaround code for the POPF bug.

The standard version of EMULATE_POPF is 6 bytes longer than POPF and much slower, as you’d expect given that it involves three branches. Anyone in his/her right mind would prefer POPF to a larger, slower, three-branch macro—given a choice. In noncode, however, there’s no choice here; the safer—if slower—approach is the best. (Having people associate your programs with crashed computers is not a desirable situation, no matter how unfair the circumstances under which it occurs.)

And now you know the nature of and the workaround for the POPF bug. Whether you ever need the workaround or not, it’s a neatly packaged example of the tremendous flexibility of the x86 instruction set.

Previous | Table of Contents | Next

Graphics Programming Black Book © 2001 Michael Abrash