Intel 286 secrets: ICE mode and F1 0F 04
rep-lodsb.mataroa.blog121 pointsby rep_lodsb44 comments
9B5 BIST1 -> TMPD 0x0303 PASS2
9B6 SIGMA -> EDX
9B7 BIST2 -> TMPE TMPD XOR
9B8 SIGMA 0x3ddc0c2c XOR
9B9 SIGMA -> EAX BOOTUP_JUMP JFPUOK
0x303 = family 3, model 0, stepping id 3. CP 10 / CMP AL,10 ;set carry if valid decimal digit
SBC A,69H / SBB AL,69H ;0..9 => 96h..9Fh (auxC=1), 10..15 => A1h..A6h (auxC=0)
DAA / DAS ;subtract 66h if auxC set, 60h if clear cmp dword [rax], 'XAUT'
jne .rxa_next
cmp dword [rax+4], 'HORI'
jne .rxa_next
cmp word [rax+8], 'TY'
jne .rxa_next
cmp byte [rax+10], '='
jne .rxa_next
; Found XAUTHORITY=path
Okay, this is setup code that only runs once at startup - but that would be a reason to optimize it for size and/or readability! REPE CMPSB exists, and may not be the fastest, but certainly the most compact and idiomatic way to compare strings. Or write a subroutine to do it! ; Dispatch based on state
mov rcx, [vt_state]
cmp rcx, VT_ESC
je .vtp_esc
cmp rcx, VT_CSI
je .vtp_csi
cmp rcx, VT_CSI_PARAM
...
In total, there are 7 compares + conditional jumps, one after another. Compilers would generate a jump table for this, and a better option in assembly might be to make vt_state a pointer to the label we want to go to. Branch predictors nowadays can handle indirect jumps, and may actually have more trouble with such tightly clustered conditionals as seen in this code. cmp qword [vt_state], 0 ; VT_NORMAL == 0
jne .vtp_loop_slow
cmp dword [utf8_remaining], 0
jne .vtp_loop_slow
cmp byte [pending_wrap], 0
jne .vtp_loop_slow
These could likely all be condensed into a single test or indirect jump via the state variable, by introducing just a few more states for UTF-8 decoding and wrap. Following this, here's a "useless use of TEST" (the subtraction already set the flags): mov rbx, [grid_cols]
sub rbx, [cursor_col] ; rbx = cells left on this row
test rbx, rbx
jle .vtp_loop_slow ; no room (or already past)
This also again shows the compulsive use of 64-bit registers and variables for values that should never be this big. It's not the "natural" data size on x86-64 at all, every such instruction requires an extra prefix byte. 0A28 8A 0E D4 1B 1353 MOV CL,[RELOC]
0A2C D1 C1 1354 ROL CX
0A2E D1 C1 1355 ROL CX
0A30 88 0E D4 1B 1356 MOV [RELOC],CL
So I don't know why this version of the code wouldn't have worked. Maybe the penciled-in "fix" was to free up CL for some other purpose?
Much of the instruction set was carried over from the 8008, that's where you get MOV & ALU operations, the pseudo-register M standing for memory at address HL, etc. The 8080 then added to that some extensions that weren't as orthogonal, but greatly improved the usability.
That memory copy example, rewritten for the 8008, would take more than 30 instructions, constantly juggling pointers in and out of HL, because there was absolutely no other way to access memory. For that you needed an extra free register as well (no XCHG instruction!), so only a single 8 bit register would be available to use as a counter. Or you could store the counter in memory, but in that case there would be even more instructions to first load the address of that variable into HL!
In 8080 assembly as defined by Intel, each of these extended instructions has a unique mnemonic. LDAX = load A extended, etc. There's a one-to-one correspondence with the opcodes, so it's easy to memorize the encoding (best in octal), and what register can be used for what purpose.
Zilog added even more unorthogonal extensions to this set, but "simplified" the assembly so that one mnemonic could produce many different opcodes, some with an additional prefix. Most of these don't provide any benefit over using the existing 8080 opcodes, and you have to memorize lots of seemingly arbitrary restrictions. If you first learn Z80, those make no sense at all.