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`), the `or ax, ax` optimization for the zero check is replaced by the slower `cmp ax, 0`, and conditional jumps are always followed by unconditional jumps for the `else` 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.