Skipping instructions
Skipping instructions are opcode fragments that combine with one or more subsequent opcodes. The subsequent opcodes can be used with a different entrypoint than the prepended skipping instruction. Using a skipping instruction instead of an unconditional short jump can save code space, be faster, and set up incidental state such as NC (No Carry).
My examples are all for 16-bit Real/Virtual 86 Mode, but a lot of these techniques can be used similarly in 16-bit Protected Mode, or 32- or 64-bit modes.
Quoting from my ACEGALS guide:
11: Skipping instructions
The constants __TEST_IMM8, __TEST_IMM16, and __TEST_OFS16_IMM8 are defined to the respective byte strings for these instructions. They can be used to skip subsequent instructions that fit into the following 1, 2, or 3 bytes. However, note that they modify the flags register, including always setting NC. The 16-bit offset plus 16-bit immediate test instruction is not included for these purposes because it might access a word at offset 0FFFFh in a segment. Also, the __TEST_OFS16_IMM8 as provided should only be used in 86M, to avoid accessing data beyond a segment limit. After the db instruction using one of these constants, a parenthetical remark should list which instructions are skipped.
The 86 Mode defines in lmacros1.mac 323cc150061e (2021-08-29 21:45:54 +0200):
%define __TEST_IMM8 0A8h ; changes flags, NC %define __TEST_IMM16 0A9h ; changes flags, NC ; Longer NOPs require two bytes, like a short jump does. ; However they execute faster than unconditional jumps. ; This one reads random data in the stack segment. ; (Search for better ones.) %define __TEST_OFS16_IMM8 0F6h,86h ; changes flags, NC
The 0F6h,86h opcode in 16-bit modes is a test byte [bp + disp16], imm8 instruction. I believe I am not using this one anywhere actually. (A stack memory access might actually be slower than an unconditional short jump, in fact.)
0A8h is the opcode for test al, imm8 in any mode. The 0A9h opcode changes to an instruction of the form test eax, imm32 in 32- and 64-bit modes.
Two use cases in ldosboot boot32.asm 07f4ba0ef8cd (2021-09-10 22:45:32 +0200):
First, chain two different entrypoints for a common function which both need to initialise a byte-sized register. The mov al, X instructions take 2 bytes each, so __TEST_IMM16 can be used to skip one such instruction. (This pattern can be repeated if there are more than two entrypoints.)
error_fsiboot: mov al,'I' db __TEST_IMM16 ; (skip mov) read_sector.err: mov al, 'R' ; Disk 'R'ead error error:
Second, a certain entrypoint that needs two bytes worth of additional teardown but can otherwise be shared with the fallthrough case of a later code part.
mov bx, [VAR(para_per_sector)] sub word [VAR(paras_left)], bx jbe @F ; read enough --> loop @BB pop bx pop cx call clust_next jnc next_load_cluster inc ax inc ax test al, 8 ; set in 0FFF_FFF8h--0FFF_FFFFh, ; clear in 0, 1, and 0FFF_FFF7h jz fsiboot_error_badchain db __TEST_IMM16 @@: pop bx pop cx call check_enough jmp near word [VAR(fsiboot_table.success)]
Here's a use case in inicomp lz4.asm 4d568330924c (2021-09-03 16:59:42 +0200) where we depend on the test al, X instruction clearing the Carry Flag:
.success: db __TEST_IMM8 ; (NC) .error: stc retn
Further, here's a very similar use of a skipping instruction in DOSLFN Version 0.41c (11/2012). Instead of test ax, imm16 they're using mov cx, imm16 which has no effect on the status flags but clobbers the cx register instead. (Opcode 0B9h is mov ecx, imm32 in non-16-bit modes, and writes to the full ecx or rcx register.)
;THROW-Geschichten... [english: THROW stories...] SetErr18: mov al,18 db 0B9h ;mov cx,nnnn SetErr5: mov al,5 db 0B9h ;mov cx,nnnn SetErr3: mov al,3 db 0B9h ;mov cx,nnnn SetErr2: mov al,2 SetError:
Finally, the FAT12 boot loader released on 2002-11-26 as fatboot.zip/fat12.asm by Chris Giese (which I based my FAT12, FAT16, and FAT32 loaders on) uses cmp ax, imm16 as a skipping instruction in its error handler. This is similar to my lDOS boot error handlers but cmp leaves an indeterminate Carry Flag state rather than always setting up No Carry. Also note the comment referring to "Microsoft's Color Computer BASIC":
mov al,'F' ; file not found; display blinking 'F' ; 'hide' the next 2-byte instruction by converting it to CMP AX,NNNN ; I learned this trick from Microsoft's Color Computer BASIC :) db 3Dh disk_error: mov al,'R' ; disk read error; display blinking 'R' error: