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.