IRET pops three words: IP, then CS, then FLAGS. That is the reverse of what INT (or a hardware interrupt) pushed, so execution resumes after the interrupted instruction with every flag as it was, including IF and TF. Interrupts that were enabled before are enabled again, whatever the handler did with CLI and STI.
The pushes of an INT can be imitated by hand: PUSHF, PUSH CS and a near CALL leave FLAGS, CS and IP on the stack in the same order. That lets a program call a handler without touching the vector table, and shows IRET putting the old flags back:
CLC ; CF = 0 before the "interrupt"PUSHF ; what INT pushes: FLAGS,PUSH CS ; then CS,CALL HANDLER1 ; then IP (a near CALL pushes it)MOV AL, 0ADC AL, 0 ; AL = CFADD AL, "0" ; digit -> characterMOV DL, ALMOV AH, 02h ; DOS: print the character in DLINT 21hHANDLER1 PROC NEARMOV DL, "*" ; show that the handler ranMOV AH, 02hINT 21hSTC ; set CF in here...IRET ; ...IRET pops IP, CS, FLAGS: CF = 0 againHANDLER1 ENDP
Pitfalls
Ending a handler with RET or RETF leaves the flags word on the stack. A handler for a hardware interrupt must tell the 8259 it is done (MOV AL, 20h / OUT 20h, AL) before IRET, or no further interrupts of that level arrive. A service that reports errors in CF cannot simply set CF, since IRET overwrites it; it either changes the saved flags on the stack or returns with RETF 2, which pops CS:IP and discards the saved flags. A handler installed with DOS function 25h looks like this:
TICK1 PROC FAR ; INT 1Ch: called by the BIOS timer handlerINC CS:TICKS1 ; DS is unknown here: go through CSIRETTICK1 ENDPTICKS1 DW 0
Later CPUs
The 386 adds IRETD for 32-bit code, which pops EIP, CS and EFLAGS. In 16-bit code IRET is unchanged.