I am trying to recreate the exact C source code from some 16bit DOS 8086 assembly generated by the MS C 5.0 compiler. After making some progress, I've hit a wall with the following code (annotated in IDA):
code:0100 mov timerCounter, 0
code:0105 waitForKey:
code:0105 cmp timerCounter, 78h ; this is increased in a timer IRQ service routine elsewhere
code:010A jnb short keyOrTimeout ; timeout of 78h (120 ticks) exceeded, break out of loop
code:010C call far ptr check_keybuf ; check for keypress
code:0111 or ax, ax ; check for zero return value (faster than cmp reg,imm)
code:0113 jnz short continue ; ax != 0: no keypress, continue spinning
code:0115 call far ptr getkey ; else: fetch key
code:011A jmp short keyOrTimeout ; ...and break out of loop
code:011C continue:
code:011C jmp short waitForKey ; try again
code:011E keyOrTimeout:
code:011E cmp timerCounter, 78h ; out of the loop, check reason (timeout or not)
This seems quite trivial to rewrite into C:
static char volatile timerCounter;
for (timerCounter = 0; timerCounter < timeout;) {
if (check_keybuf() == 0) {
getkey();
break;
}
// trying to force the extra jump step, doesn't make a difference
// else continue;
}
if (timerCounter >= timeout) {
// ...
}
However, it generates the following assembly when compiled with the default compiler flags:
00FD C6060C0400 mov byte [0x40c],0x0
0102 803E0C0478 cmp byte [0x40c],0x78
0107 730E jnc 0x117 ; break on timeout
0109 9A0E026500 call 0x65:0x20e ; check_keybuf()
010E 0BC0 or ax,ax
0110 75F0 jnz 0x102 ; continue directly to loop condition above
0112 9A13026500 call 0x65:0x213 ; getkey();
0117 803E0C0478 cmp byte [0x40c],0x78
The problem is that the instruction at 0x110 jumps directly to the loop condition, without the extra step like 0x113 in the original code. No matter how smart I try to be, the compiler optimizes my code into an identical sequence of instructions:
timerCounter = 0;
waitForKey:
if (timerCounter < timeout) {
if (check_keybuf() == 0) {
getkey();
goto keyOrTimeout;
}
else { // this always gets eliminated
;
(void*)NULL;
goto continue;
}
}
else goto keyOrTimeout;
continue: // try to force the extra step of an intermediate continue location
goto waitForKey;
keyOrTimeout:
if (timerCounter >= timeout) {
// ...
}
This compiles to the exact same code as the for loop above.
The CL
compiler supports some optimization options, until now I've used no explicit options which is equivalent to /Ot
- "optimize for speed". Other supported options are as follows:
/Od
- disable optimizations: it generates code where the condtions from the C code are not inverted (e.g>=
->jae
), theor ax, ax
optimization for the zero check is replaced by the slowercmp ax, 0
, and conditional jumps are always followed by unconditional jumps for theelse
case. After inverting the conditions manually, it looks even worse, so this doesn't look like something I could make match the original output./Ol
,/Ox
- loop optimize/full optimizations: it completely messes up the code, moving the conditions to the end and doing a lot of other wild reordering, doesn't look like I could get it to emit what I want either./Os
- optimize for size,/Or
- disable inline return,/On
- disable unsafe optimizations,/Oa
- assume no pointer aliasing: same as the default.
The compiler installation also includes the QuickC compiler, which is a different, pared-down C compiler for non-professional use, which can be selected by using the /qc
option to the CL
frontend, but the code it generates looks even worse than that from CL /Od
.
I've also tried using MS C 5.10 which is a slighly newer release with minor improvements, but the code it generates is exactly the same.
I have no idea how the compiler was made to emit this code, it looks like partly optimized (due to the or ax,ax
use) and partly not (because of the intermediate jump that I cannot replicate).
Is there a way to force the compiler to emit this specific code? I'm out of ideas.
goto keyOrTimeout;
statement in{
and}
braces. Some people prefer that as a coding style. and with these old compilers, it occasionally makes a difference.