Exploring Watcom C/C++

December 23, 2017 dos code

A couple weeks ago I picked up a complete boxed copy of Watcom 10.0a. I was also able to find an almost complete set of the printed documentation/manuals for Watcom 9.5 from a somewhat-local seller.

I do love having physical copies of books, especially reference material like this. Most people will probably think I'm weird, but I find this more convenient to refer to in many (but not all) cases. Being able to physically flip through pages at will and back and forth between several different pages as needed just feels a ton more convenient to me then doing the same with a PDF. And I've always preferred reading text like this on real paper. The obvious downsides versus an electronic copy is the lack of ability to do a Ctrl-F to find something specific and also not being able to copy+paste something from it.

So what's so special about Watcom anyway? Well, for me personally, it was the first C compiler I ever used. Not this version though. In 1996 I remember my dad bought me a book, "C DiskTutor" by L. John Ribar (published in 1992) that came with a disk containing sample code and a stripped down copy of the Watcom C 8.5 compiler. The disk actually had a few bad spots on it and I remember he called the publisher and they sent out another disk. I didn't realize it at the time, but the version of the compiler on the disk lacked the ability to compile code with any memory model other then the small model, so it wasn't suitable for really any "real world" use. Certainly good enough for introductory exercises though, which was obviously the intended purpose.

I didn't spend a whole lot of time using it though. I remember getting frustrated with fixing my own bugs as there was no included debugger with this stripped down version. At least I don't remember there being one. But also I remember trying to make sense of the poor compiler error messages was tough for me being completely new to C at the time. Finally, I also remember really hating how I had to quit out of EDIT.COM to compile and run my code. The disk included some simple text editor which seemed to be written specifically for this book. It did have the ability to run the compiler from within the editor, but I vaguely remember arriving at the conclusion that this editor was not very good and a bit buggy. EDIT.COM ended up being a less-frustrating option for writing code. Keep in mind my only other programming experience at this time was a little over a year with QBasic which had a much faster and easier edit/compile/run cycle. I didn't understand why anyone would want to use something else when it was so much easier! Of course years later I learnt that many people at that time used better editors then EDIT.COM and actually really liked writing code in their editor of choice and using external build scripts / Makefiles. Heh.

Beyond my nostalgia though, Watcom was used by all sorts of game developers who cared about code efficiency in the early-to-mid 90's, such as id Software with Doom and 3D Realms with Duke Nukem 3D amongst many others. Plus it shipped with a royalty-free version of the well known DOS extender DOS/4GW. However, while it was better at code generation then the competition (Borland, Microsoft, etc), it lacked in the end-user experience. Borland and Microsoft had better tooling in the form of IDE's (Watcom didn't ship an IDE with their compiler until 10.0) and easier to use command line tools.

Eventually other compilers caught up and Watcom converted to an open-source project when Sybase (who owned Watcom) decided to end-of-life the compiler.

So far I've been using DJGPP (which is just a DOS port of GCC) for all my DOS coding projects. I'm using an old version that corresponds roughly with what was current when I first discovered it for no other reason then that it feels "true" to the whole spirit of these DOS coding projects.

DJGPP is pretty awesome honestly. Since it's just an older version of GCC for DOS, it feels very familiar. Lots of the same tools are present, including GDB and MAKE. And the code generation is really quite excellent. I think my only gripes with DJGPP are that it uses the AT&T syntax for inline assembly and that I kind of have a tiny bit of a love/hate relationship with the CWSDPMI DOS extender and DPMI server it uses. It's nothing major really, just minor gripes relating around how it seems more "complex" to use on the surface as compared to something like DOS/4G. But I realize that there are good reasons for it. And personally I believe that CWSDPMI is probably a bit more robust then DOS/4GW, but I could be wrong. Feels that way sometimes though.

RHIDE is an IDE for DJGPP that seems to be largely inspired by Borland's DOS IDEs. It's also pretty great and is very quick to get up and running. You don't even need to do any fiddling about with GCC or MAKE directly when using RHIDE as it will handle that all for you. Awesome. Though it's not without its quirks and limitations. The editor has some weird (to me) ideas about indentation that I've only been able to "resolve" by using a very specific combination of options that involves mixing tabs and spaces in a way that makes me sad.

Additionally, while the GDB integration is good, it definitely feels like it's missing some extra polish. There's no automatic locals watching (have to explicitly add anything you want to watch the value of). The watch viewer itself is quite simplistic and watching the values of structs is ... a poor experience. Well, not until you start wanting to watch more complex values anyway. The whole watch window really needed to be implemented as an expanding/collapsing node tree type of viewer, but it's not sadly. And there's no memory viewer. The disassembler viewer really lacks a source code + assembly code combined mode. It's not the end of the world, as when you're stepping through code you can see both and the current line is highlighted in both places. But still... it makes browsing the assembly view somewhat more annoying than it needed to be. Bleh. Oh, and there really isn't any better GDB frontend for DJGPP/DOS that I'm aware of. This is pretty much the best option. And to be fair, for the time, it wasn't bad at all. It's just that my nostalgia only goes so far.

So anyway, back to Watcom. The version I picked up (10.0a) is 4 years older then the version of DJGPP I've been using so far. So I didn't have any expectations about getting a "better" compiler or anything like that. But I was pretty excited to try it out because it really feels much more "true" to the spirit of using a 486 to do DOS programming as Watcom was the compiler that the majority of my favourite DOS games were written with. A silly reason, for sure, heh.

I'd heard the "new" IDE that Watcom introduced with 10.0 was kind of not good, and certainly that was my reaction to trying it out as well. It requires Windows 3.x and it does work. But it's pretty weird. The "IDE" is actually comprised of a series of separate executables that are all invoked from the main "IDE" (what is actually called the "IDE" by Watcom). It's more of a "project manager and GUI build tool" then anything else. You can see all the source files in your project and configure various compile and link options. Clicking on a source file causes a separate text editor application (and hence, a separate window) to open. Same goes with things like Windows resource files, etc. All separate tools. It's pretty weird. No other IDEs even at that time were like that. Certainly not any of the well known ones anyway. Kind of makes me curious what their reasons were for going with this design. Anyway, it's certainly not something I want to use. Additionally, I found that debugging graphical DOS programs launched from within Windows was problematic and resulted in crashes sometimes while stepping through code. So the Watcom IDE is out.

I had to admit at this point that I was really quite unfamiliar with what DOS text editors were out there. Turns out there was quite a lot. Many even had fancy features like syntax highlighting. Some also had fancy macro languages for additional configurability. Nice. Should be easy to find something suitable.


It would take too long to write fully about this experience, but suffice it to say that while I didn't try everything on that list, I did try quite a lot. I realized that I am quite picky about the text editor I use. In weird ways anyway, probably mostly born out of habit and fear of change. Also anything based on VIM or Emacs I disregard because that's just how I roll.

I basically wanted the following features at an absolute minimum:

  • Syntax highlighting.
  • Multiple files open at once and able to easily switch between them.
  • External tools launching (to launch batch scripts / makefiles to run/compile/debug).
  • Ability to run in 80x50 text mode.

Not too much to ask for I think. Multiple editors had support for all of these in fact. The first one I decided to look at was SETEDIT because it's what RHIDE was made from (or is that the other way around?). I quickly discovered that launching DOS/4G programs from within CWSDPMI programs can be problematic. The DOS version of SETEDIT is built with DJGPP which means it uses the CWSDPMI DOS extender. Programs that I was going to be building with Watcom would make use of DOS/4GW. Additionally some of the Watcom tools, such as the DOS debugger WD, also use DOS/4GW. I was unable to launch the Watcom WD debugger from within SETEDIT without causing crashes. While some test programs I built which also used DOS/4GW could be launched fine, I didn't like the idea that down the road there might be problems. Ugh.

As it turns out, a lot of the better text editors were also built with DJGPP. All of these that I tried which supported launching external tools/programs also exhibited the same crashing problems when launching some DOS/4GW programs like WD. Ergh.

I do want to specifically say that I really liked FTE. Just a great, simple, minimalistic, easy to use editor which for some reason really resonated with me. However, it is built with DJGPP so it was sadly a no-go. That being said, I have a "conversion" project on my backlog list now, heh. If I decide to use Watcom over the long term, I intend to fully investigate whether it's possible to convert FTE to compile with Watcom so as to hopefully remove the crashing issues with DOS/4G programs.

Another editor I wanted to point out is something called "Power View IDE" which is actually a full DOS IDE for Watcom C/C++ that some guy wrote! I was ecstatic when I found this linked on some random forum post, seemingly as an afterthought by the poster. I don't think there is any existing project website for it, but it can be downloaded here. I couldn't believe that there was so little information out there about this and basically no one ever mentioned it anywhere.

This editor I think could have been exactly what I was looking for. Except it wasn't. It had some show-stopping bugs which made me oh so very sad. For one, when scrolling through text using the arrow keys on the keyboard (as I sometimes do), the editor would periodically insert random numbers ... !? What the heck? Maybe a bug in the keyboard handler? I took a look at the code and nothing stood out to me, so not sure what to make of that one. But yeah, an editor that randomly inserts random characters into your code is instantly disregarded. Another issue was that it didn't seem to correctly handle restoring the video mode when returning from executed programs. This became problematic when running the editor in 80x50 text modes which was a requirement of mine. Oh well. I guess the buggy nature of it is why no one ever really seemed to talk about it from what I could see.

To make this discussion of text editors not go on for many more paragraphs I'll cut to the chase and say that I eventually settled on Aurora. It was actually my last resort option, heh. While it has quite a lot of weird quirks that were initially off-putting for me, it is an amazingly customizable editor due to it's weird macro language "AML." Everything in the editor itself is largely implemented in AML, from how text editor behaviours work, to the menus, to the keyboard shortcuts, to file open/close dialogs, etc, etc. The DOS executable is basically just a runtime environment for AML and the Aurora text editor just comes with a pre-built set of compiled scripts that give you a text editor. Neat. It's reasonably well documented and the macro language has support for quite a lot. It appears to be a 16-bit real-mode DOS executable so no problems executing DOS/4GW programs from within the editor.

However, it had no real built in support for some nice "quality of life" things such as capturing build errors and displaying them nicely in the editor. "Well, with all the things AML supports, how hard can it be to implement this myself in exactly the way I want it?" This line of thinking kind of spiraled out of control quickly. Suffice it to say, there were way more quirks with AML then I had expected and the debugging facilities within Aurora were barebones at best. I think the hardest part was actually managing the lifecycle of a plugin that is meant to be called again and again from a menu item. It wasn't straightforward and was complicated by the fact that the description of how AML's "objects" work made me think more of traditional OOP style programming ... and it kind of isn't like that in a lot of ways. The extensive documentation unfortunately doesn't really tell you anything about actually building plugins for the editor specifically. Nothing in the way of concrete examples with explanations for how to do common things or why it's done this way, etc. Instead, it tends to just focus on the language basics and some API stuff... nothing to tie it all together. Oh well. I should be happy that it was as well documented as it was really. After suffering through some bizarre and hard to debug editor crashes from poor lifecycle management, I got something working!

At this point, I had put off "fixing" all the subjectively weird behaviours and keybindings that Aurora has out of the box (even when selecting a keybinding scheme during installation that was supposedly based on an editor I was mostly familiar with). Finally got that all mostly sorted out and at this point was thinking to myself "wow, this REALLY all had better have been worth it!" Meaning of course, that I really hoped I wouldn't grow to hate Watcom, heh.

The "WMAKE" menu I added contains options for Build, Rebuild, Clean, Run, Debug and Show Errors. Build errors will show up automatically after any action has finished, but it's a quick key-bound menu item to easily bring back up the error window if it gets hidden. These WMAKE actions basically require a makefile that contains targets for each menu item. Which is totally fine, as that was basically how I envisioned the makefile structure I was going to use anyway.

target_config = debug

target_name = dgl_test

object_files = &
    blit.obj &
    clipping.obj &
    dgl.obj &
    draw.obj &
    gfx.obj &
    internal.obj &
    keyboard.obj &
    mathext.obj &
    mouse.obj &
    pcx.obj &
    util.obj &

cc_flags_debug = /d2 /zp4 /5r /fp3
cc_flags_release = /d1+ /zp4 /5r /fp3 /oneatx
cc_flags = /mf $(cc_flags_$(target_config))

link_flags_debug = debug all
link_flags_release = debug all
link_flags = $(link_flags_$(target_config))

    wcc386 $[. /zq $(cc_flags)

$(target_name).lnk: $(object_files)
    %create $^@
    %append $^@ NAME $(target_name).exe
    %append $^@ SYSTEM DOS4G
    %append $^@ OPTION QUIET
    @for %i in ($(object_files)) do %append $^@ FILE %i

$(target_name).exe: $(object_files) $(target_name).lnk
    wlink $(link_flags) @$(target_name).lnk

clean : .SYMBOLIC
	del *.obj
    del *.err
	del $(target_name).exe
    del $(target_name).lnk

build : $(target_name).exe

run : $(target_name).exe

debug : $(target_name).exe
	wd /swap /trap=rsi $(target_name).exe

Basically relying on WMAKE to launch what I need once builds complete.

If that one other weirdo like me who wants to use Watcom C/C++ with the Aurora Text Editor comes by this site and reads this, here's my WMAKE.AML plugin. The two main things that are currently missing are "click on error to goto sourcefile/line" support and some kind of better window management (like, e.g. automatically resizing existing editor windows to have the errors window always visible on screen). The former will likely be added soon as it should be simple to add.

So anyway ... surely I would not have to all of this effort without having some idea of whether it was going to be worth it at the end right? Of course. Heh.

I'm quite a big fan of the Watcom debugger WD (previously called WVIDEO... not sure where the "video" part of that name comes from honestly).

It's kind of got a bit of a Visual Studio C++ debugger thing going on that I like (and this was even from before Visual Studio C++'s debugger got good I think). I think the one thing I dislike about it so far is that abruptly terminating a running program can leave your system in a weird state. For example, it doesn't seem to always restore overridden interrupt handlers. So not letting your running code run to the point where it removes it's custom keyboard interrupt handler can leave you unable to do anything once the debugger exits and returns to your text editor. Oops. DJGPP and GDB (and maybe CWSDPMI?) seemed to do a much better job of cleaning things up in these cases so your system didn't crash very often (though it wasn't perfect).

As for Watcom in general, I like that DOS/4GW seems to get out of your way a little bit more than CWSDPMI does. For example, the first 1MB of memory is directly accessible out of the box, so I can write into 0xA0000 immediately, or read directly from some memory in the BIOS to e.g. read the RTC tick count, or the BIOS fonts, etc. Just as DOS should be! Additionally, I can just call _dos_setvect and _dos_getvect directly as I would in a 16-bit realmode program and it will basically "just work" when I want to implement my own interrupt handler. Of course, it's not quite that simple in all cases, like when you want to use some bit of shared DOS memory and communicate with some process or piece of hardware that is running in 16-bit mode. But for the simple cases, it is simple. Don't need to have a bunch of _go32_dpmi_blahblah calls around. This will come back to bite me in the ass though, as DJGPP provides a relatively nice API for interfacing with CWSDPMI when you (inevitably) need to for the more complicated scenarios. DOS/4GW doesn't provide you with as nice an API... you just call the standard DPMI server interrupt. However, this is mostly documented in my stack of Watcom manuals, so that's a nice plus! Even some examples such as how to implement bimodal interrupt handlers.

On the topic of code generation though, the version of GCC from DJGPP that I was using is definitely better (as expected, it is 4 years newer).

We can take a look at the following very simplistic example which shows how something simple like function inlining is handled between these two compilers. In this example, I was using GCC flags -O2 -m486 -ffast-math and WCC386 flags /zp4 /5r /fp3 /oneatx both of which are recommended flags for optimum performance (could do -O3 with GCC of course though).

This example is something I noticed while running some simplistic benchmarks to see how much performance I would lose with Watcom. This snippet isn't even what I was benchmarking... it's just some setup code to generate a random 16x16 sprite for the actual benchmark that appeared later in the code. However I noticed it wasn't inlining a call the way I was expecting when looking at the assembly output.

// in a header file
static int surface_offset(const SURFACE *surface, int x, int y) {
    return surface->width * y + x;

static byte* surface_pointer(const SURFACE *surface, int x, int y) {
    return surface->pixels + surface_offset(surface, x, y);

static void surface_pset_f(SURFACE *surface, int x, int y, int color) {
    *(surface_pointer(surface, x, y)) = (byte)color;

// elsewhere, in main()

bmp = surface_create(16, 16);
for (x = 0; x < 16; ++x) {
    for (y = 0; y < 16; ++y) {
        surface_pset_f(bmp, x, y, rand()%255);

As a quick aside, before anyone looks at the above code and says anything like "Wow, that would be slow anyway! You really don't want to be using anything like an explicit pset type of function call anywhere! Look at that slow multiplication for god's sake!" I know. For any code that's running in an inner loop in a performance-sensitive scenario, I would always use, e.g. the above surface_pointer function to get a direct pointer to the memory before the loop and then use that pointer in the inner loop itself. And if it was really important to save every last CPU cycle, I would probably write out the surface_offset calculation directly without the multiplication anyway. These are all just convenience functions to be used as appropriate. The above for loop code is just an example!

GCC inlines the call to surface_pset_f as I would expect:

  21:test.c        ****     bmp = surface_create(16, 16);
 185 007e 6A10     		pushl $16
 186 0080 6A10     		pushl $16
 187 0082 E879FFFF 		call _surface_create
 187      FF
 188 0087 8945E8   		movl %eax,-24(%ebp)
  22:test.c        ****     for (x = 0; x < 16; ++x) {
 190 008a 31F6     		xorl %esi,%esi
 191 008c 83C410   		addl $16,%esp
 192 008f 90       		.align 2,0x90
 193              	L238:
  23:test.c        **** 	    for (y = 0; y < 16; ++y) {
 195 0090 31DB     		xorl %ebx,%ebx
 196 0092 89F6     		.align 2,0x90
 197              	L242:
  24:test.c        **** 		    surface_pset_f(bmp, x, y, rand()%255);
 218 0094 E867FFFF 		call _rand
 218      FF
 219 0099 89C2     		movl %eax,%edx
 220 009b BFFF0000 		movl $255,%edi
 220      00
 221 00a0 99       		cltd
 222 00a1 F7FF     		idivl %edi
 223 00a3 8B45E8   		movl -24(%ebp),%eax
 224 00a6 8B38     		movl (%eax),%edi
 225 00a8 0FAFFB   		imull %ebx,%edi
 226 00ab 01F7     		addl %esi,%edi
 227 00ad 037808   		addl 8(%eax),%edi
 228 00b0 8817     		movb %dl,(%edi)
 230 00b2 43       		incl %ebx
 231 00b3 83FB0F   		cmpl $15,%ebx
 232 00b6 7EDC     		jle L242
 234 00b8 46       		incl %esi
 235 00b9 83FE0F   		cmpl $15,%esi
 236 00bc 7ED2     		jle L238
  25:test.c        **** 		}
  26:test.c        **** 	}

However, I noticed wcc386 did not. Not only that, no amount of tweaking of compiler settings and/or #pragmas would get it to. It only partly inlines the call to surface_pset_f:

    bmp = surface_create(16, 16);
 142a  ba 10 00 00 00                    mov     EDX,00000010H
 142f  89 44 24 04                       mov     +4H[ESP],EAX
 1433  89 d0                             mov     EAX,EDX

    for (x = 0; x < 16; ++x) {
 1435  31 f6                             xor     ESI,ESI
 1437  e8 00 00 00 00                    call    surface_create_
 143c  89 c7                             mov     EDI,EAX

	    for (y = 0; y < 16; ++y) {
 143e  31 c9             L27             xor     ECX,ECX

		    surface_pset_f(bmp, x, y, rand()%255);
 1440  e8 00 00 00 00    L28             call    rand_
 1445  89 c2                             mov     EDX,EAX
 1447  bb ff 00 00 00                    mov     EBX,000000ffH
 144c  c1 fa 1f                          sar     EDX,1fH
 144f  f7 fb                             idiv    EBX
 1451  89 14 24                          mov     [ESP],EDX
 1454  89 cb                             mov     EBX,ECX
 1456  89 f8                             mov     EAX,EDI
 1458  89 f2                             mov     EDX,ESI
 145a  e8 00 00 00 00                    call    surface_pointer_
 145f  8a 14 24                          mov     DL,[ESP]

 1462  41                                inc     ECX
 1463  88 10                             mov     [EAX],DL
 1465  83 f9 10                          cmp     ECX,00000010H
 1468  7c d6                             jl      L28


 146a  46                                inc     ESI
 146b  83 fe 10                          cmp     ESI,00000010H
 146e  7c ce                             jl      L27

Elsewhere in that same module, surface_pointer_ can be found:

0070                    surface_pointer_:
 0070  0f af 18                          imul    EBX,[EAX]
 0073  8b 40 08                          mov     EAX,+8H[EAX]
 0076  01 da                             add     EDX,EBX
 0078  01 d0                             add     EAX,EDX
 007a  c3                                ret     
 007b  90                                nop     

So, Watcom partially inlined the call to surface_pset_f but left a call to surface_pointer as a fairly lightweight function call. Note how Watcom uses registers to pass parameters to functions pretty much everywhere it can by default, thusly the surface_pointer_ function has none of the typical stack management boilerplate we might expect. From what I've read, this method of passing function parameters is one of the reasons it kept ahead of the pack regarding performance compared to other compilers at the time.

Whilst playing with compiler flags and other things, I decided to see how wpp386, the Watcom C++ compiler, would handle this:

    bmp = surface_create(16, 16);
 0063  ba 10 00 00 00                    mov     EDX,00000010H
 0068  89 44 24 08                       mov     +8H[ESP],EAX
 006c  89 d0                             mov     EAX,EDX

    for (x = 0; x < 16; ++x) {
 006e  31 f6                             xor     ESI,ESI
 0070  e8 00 00 00 00                    call    __16hkttSURFACE near * near surface_create( int, int )
 0075  89 c1                             mov     ECX,EAX
 0077  eb 06                             jmp     L3
 0079  46                L2              inc     ESI
 007a  83 fe 10                          cmp     ESI,00000010H
 007d  7d 2a                             jge     L5

	    for (y = 0; y < 16; ++y) {
 007f  31 db             L3              xor     EBX,EBX

		    surface_pset_f(bmp, x, y, rand()%255);
 0081  e8 00 00 00 00    L4              call    rand_
 0086  89 c2                             mov     EDX,EAX
 0088  c1 fa 1f                          sar     EDX,1fH
 008b  f7 fd                             idiv    EBP
 008d  89 14 24                          mov     [ESP],EDX
 0090  8b 11                             mov     EDX,[ECX]
 0092  0f af d3                          imul    EDX,EBX
 0095  8b 41 08                          mov     EAX,+8H[ECX]
 0098  01 f2                             add     EDX,ESI
 009a  01 c2                             add     EDX,EAX
 009c  8a 04 24                          mov     AL,[ESP]


 009f  43                                inc     EBX
 00a0  88 02                             mov     [EDX],AL
 00a2  83 fb 10                          cmp     EBX,00000010H
 00a5  7d d2                             jge     L2
 00a7  eb d8                             jmp     L4

Interesting! wpp386 fully inlines surface_pset_f. The exact same compiler flags were used in both cases.

After discovering this difference, I decided to compare wpp386 vs wcc386 with some other pieces of code and saw that there are definitely some other code efficiency differences. I've not investigated fully yet, but it does seem like wpp386 generates slower code in some cases too. I'll probably do a follow up post once I've dug into it a little bit more. One of the things I want to play with is re-working the code for surface_pset_f, surface_pointer and surface_offset to see if it's just a problem caused by the code optimizer "giving up" at some point because of how the code is written. It definitely was a thing that you'd have to at times help the compiler out by simplifying or otherwise breaking up complex expressions into separate chunks so it could do a better job.

Once nice thing about using wpp386 for even 100% C code is that it at least gives you more warnings and the support for toggling off warnings that you don't care about (like "W202" with wcc386 ... ugh) actually works. Turning up the warning level with wcc386 is kind of useless for this reason. I'm assuming this is just a bug and was probably fixed with 10.5 or 11.0.

Inline assembly is similar in some ways to GCC, but no need to deal with the AT&T syntax which I dislike. The way you do it with Watcom 10.0 is unfortunately more complicated then using asm as you might with other C compilers of the time (and modern compilers too), but it has it's benefits too:

void REP_MOVSD(void *src, void *dest, int num_dwords);
#pragma aux REP_MOVSD =     \
    "cld"                   \
    "rep movsd"             \
    parm [esi] [edi] [ecx]  \
    modify [esi edi ecx];

Then you could simply call REP_MOVSD in your C code in the exact same way you would call any other C function. Inline assembly written in this manner would be inserted into your code at compile time in the same way as an C function that is being inlined by the compiler would be.

Compare to how you'd do it with DJGPP/GCC:

#define REP_MOVSL(src, dest, num_dwords)            \
    __asm__ __volatile__ (                          \
        "cld\n\t"                                   \
        "rep\n\t"                                   \
        "movsl"                                     \
        : : "S" (src), "D" (dest), "c" (num_dwords) \
        : "%ecx", "%esi", "%edi" )

Works the same way when calling from your C code (REP_MOVSL can be called as any other function, and is inlined, etc.). Also the GCC __asm__ is more flexible like what you saw with other compilers and the asm keyword, so you could just use it wherever in your code with ease (so you don't have to use it in a macro as I'm doing here).

If I'm being honest, both methods are super ugly to me. I will never be able to commit either form to memory and so, every time I need to write inline assembly I'll need to be referring to some documentation to copy it out. Watcom 11.0 apparently added support for the asm keyword, but apparently even with that, Watcom recommended the #pragma method because of its ability to indicate to the compiler what registers are used to pass values in and what registers were modified by the inline assembly code, something that the compiler couldn't do with the asm keyword I guess.

My Custom 486 DX2 Build

December 2, 2017 dos hardware

Since it's basically complete now, I figured I'd post something detailing it. Actually, it's been mostly complete for a while now, but the last couple small touches came together this week.

Despite the fact that I got a perfectly capable mini 486 PC earlier this year, I still decided I wanted to build my own similar system in a baby AT form-factor case. This way I could customize it more to my liking. I also really wanted to use a specific style baby AT case that matched the old 386 PC my family used to have. It's nothing special at all, but it's kind of stuck in my head over the years and become synonymous to me with "old DOS PC", so I pretty much was unwilling to let this "requirement" of mine go.

I began picking up parts during the summer and piece by piece I've finally got everything assembled. Strangely enough, getting the particular AT case I wanted was hardest, even though from what I gather it was actually a very common style case back then. Maybe just a coincidence that I had a hard time finding one.

Actually this ended up being the second case of this style that I received. The first one was shipped and arrived broken (the plastic front was cracked and broken in multiple places). Got pretty lucky that I was able to get a second one now that I think of it.

So, what's inside? Let's take a look:

  • CPU: Intel 80486 DX2 SX911 66Mhz
  • Motherboard: FIC 486-PVT, 256KB L2 Cache
  • RAM: 16MB
  • I/O Controller: Promise Technology 560C PDC20630, VLB
  • Graphics: S3 Trio32 2MB, VLB
  • Network: 3Com 3C509B-TPC
  • Sound: Sound Blaster Pro 2.0 CT2600 and Gravis Ultrasound Classic 3.74 1MB
  • Hard Disk: Quantum Maverick ProDrive 540MB 3600RPM MV54A011
  • Floppy Drive: 3.5" 1.44MB Sony MPF920-E and 5.25" 1.2MB Teac FD-55GFR
  • CD-ROM: Torisan 6x CDR-S16
  • Power: Athena Power AP-AT30 300W

Since I ended up getting basically every piece separately, it almost certainly ended up costing a bunch more then it would have if I had bought some pre-assembled PC from eBay or some other place (which people do sell, tested and working, even today). However, given all the trouble I had to go through to get this working, it really feels a lot more satisfying having gone this route.

As one may expect given the era, contrary to building a PC today, a lot of things did not "just work" together out of the box with this PC. This is especially the case when talking about VESA local bus (VLB) hardware where stability was a common problem. Having to adjust jumpers on the motherboard and the different ISA cards to get the settings just right is quite a different problem from today's PC hardware. This can be even more difficult a task today because for some motherboards the documentation is sparse or even outright missing, leaving you to either have to figure it out by trial and error, or hope the (often limited) labels printed on the motherboard or ISA cards themselves are complete enough and self-explanatory.

Luckily for me, with this particular motherboard I was able to find PDF scans of the original manual. The other card that you'll usually want a manual for due to all the jumpers on it is the I/O controller. In my case, I was unable to find a manual, but the labels printed on the card itself were sufficient enough to get it working properly.

Well, actually, I'm not sure on that last bit to be honest! It did seem to be working perfectly fine up until a week or so ago where I started having problems with the BIOS detecting the hard disk. I thought at first that it might have been an indication that the hard disk I was using was dieing or dead, but after swapping it out for another, using a totally different I/O controller card and also trying different IDE cables, I was unable to eliminate the issue. And to make matters worse the issue only manifested itself sometimes. The only thing I've found so far that solved this problem was to connect all of the IDE devices to the IDE channel that runs on the ISA bus instead of VLB. This isn't too big a deal, as I believe that since I'm running DOS 6.22 primarily that I don't see any benefit whatsoever to having the hard disk running over VLB.

It is worrisome though that the problem only started to occur just recently and seemingly not a result of any other hardware changes (since it had been a while prior that I had made any). Either some other component in this build is starting to become a little flakey, or this is just the fabled VLB stability problems I've heard about rearing it's ugly head. At any rate, something I need to keep an eye on.

Two sound cards probably seems strange at first but there is a reason for both. Sound Blaster is a pretty typical card for the era. The Yamaha OPL chip on this one (and many other Sound Blaster cards) has a pretty recognizable sound that I quite like. And it's compatible with a great many DOS games, so it was an obvious choice.

The Gravis Ultrasound however is something I had never seen before, only heard of, and certainly was not nearly as common a choice at the time. It's an interesting card however, as it was one of the first (if not the first?) sound cards to support hardware mixing of multiple sound sources. Sound Blaster cards couldn't do this, requiring programs to implement their own software mixer which took up valuable CPU resources. In practice, this ended up not really helping many games that supported the Gravis Ultrasound though as almost all of these games supported Sound Blaster too and in an effort to keep the code simpler, would implement their sound engine to the lowest common denominator. This meant not utilizing the Gravis Ultrasounds hardware mixing support since no other card had it.

However, what I really wanted a Gravis Ultrasound for was for it's subjectively better sounding FM synthesis / MIDI playback. So far every game I've compared that supports both Sound Blaster and Gravis Ultrasound sounds better (to me) when configured to use the latter. In some cases, such as with One Must Fall 2097, the difference is objectively better.

For a 486 computer, VESA local bus graphics are pretty unique, and until PCI came around (which was quite soon after VLB was introduced), it provided the fastest graphics you could get. To me, it feels almost necessary to use VLB with a 486 machine since you cannot use VLB with anything else. Everything beyond this hardware level would be PCI and eventually AGP. Everything before, ISA. Using an S3 card is a pretty solid choice. All benchmarks I've seen and done personally have them as top-tier cards of the era. Compatibility is also excellent. Probably a more common choice would've been a Cirrus Logic card though.

Nothing much else to say really. A 6x CD-ROM feels mostly right, maybe a little bit fast for the era? I remember we had a double-speed CD-ROM in my family's 386. A 500MB hard disk is also quite large for a DOS PC really. It's funny to think of that, but then again, I remember my family's 386 had a 20MB hard disk and at the time I thought that was large! 16MB of RAM is plenty for a 486. Most DOS games would need less then 8MB of RAM, so 16MB is nice to have the option for running SMARTDRV, or using a RAM drive, etc. My DJGPP boot configuration sets up both and it makes a significant difference with compile times, still leaving 8MB of RAM free for everything else.


November 26, 2017 code dos

Been a while since I wrote about this. When it comes to my own personal coding projects, rest assured that I take it very slowly. Heh.

I released the code that I've been working on for the past month. It's a DJGPP library called libDGL. DGL stands for "DOS Gamedev Library." Yes, I am incredibly unoriginal when it comes to naming things. This is a library aimed at "retro game development" with MS-DOS, targeting VGA Mode 13h using C.

As is mentioned on the Github project page linked above (and as I've mentioned previously here as well), I am using an older version of DJGPP from the late 90's. More specifically, I am using:

15 Jan 1998   bnu281b.zip
24 Oct 1997   csdpmi4b.zip
31 Oct 1996   djdev201.zip
18 Jan 1997   faq210b.zip
 6 Jun 1998   gcc281b.zip
18 Oct 1996   gdb416b.zip
 6 Jun 1998   gpp281b.zip
 1 Mar 1998   mak3761b.zip
30 Sep 1997   rhide14b.zip

I make no guarantees that this code will work with different versions. I'll probably test it at some point, but for now I am more interested in fixing bugs and adding more features. My "todo" list for this library is quite long still. Even so, I do feel like I've got a fair bit accomplished so far.

To help me test out a bunch of this, I wrote a very simple Asteroids game over the past two days. Asteroids is a very simple game, and I feel like two days was a long time to take to write it, but in the process I uncovered and fixed a number of bugs in libDGL, so I guess I should not feel like I was too slow. Finding and fixing bugs was the WHOLE point of writing it after all.

The code is available here.

As you can imagine based on the above screenshot and the game being Asteroids, it's nothing particularly special, heh. In fact, this has not been tweaked to provide any real level of difficulty to the player at all. I was more interested in testing out libDGL then in balancing a game and providing a full layer of polish. As well, I am less than happy with how the code that handles the different game states turned out. It's fairly sloppy honestly, heh.

This game doesn't make use of any sprite blitting. Instead, it uses line drawing and 2D vector transformations for the graphics. This was useful to test out and verify the math functions I had written, and is the main reason I picked Asteroids.

Not really much to say about it honestly. The next test game I'd like to do is probably some kind of simple vertical 'shmup type of game using sprites for graphics. Probably something along the lines of what I was originally going to do on the Amiga 500 some months ago.

So, where am I going with all of this anyway? Well, I don't have any specific plan worked out, but in the back of my head I've got some grandiose ideas about writing some 2D dungeon crawler type game (something I wanted to do as a kid back in the 90's but never finished... actually, that might be a fun post to write in the future, revisiting some of that code from back then which I have sitting here now). As well, I'd like to eventually work my way up to some 3D raycasting games, with a final goal being something Doom-like but with some RPG elements thrown in (and not gritty/dark like Doom is). But this is all quite a long ways off, and first thing's first... gotta work on the foundation.

Island: A Game Of Survival

November 17, 2017 dos games

I was going through an image I took of an old hard drive that died in 2005 or so that my brother and I had in our computer starting from the late 90's. Unfortunately I made an image of the disk after it was already showing a lot of problems, so a lot of content was unrecoverable. Even still, I came across a lot of old stuff... games, old school assignments, code I had written ... and this game that I apparently had copied from one of my school's computers at some point probably in the mid-to-late 90's.

Island: A Game Of Survival, title screen and credits roll

I went to school in Ontario, Canada in Durham Region (Sunderland Public School, and then later Brock High School if anyone knows the area) where the schools are run by the Durham Board of Education. Apparently this game was produced by them. As I understand it, the game itself is a DOS recreation of the C64 game "Island" by Eleanor Rice. Was kind of funny to discover that this particular version of the Island game was written in QuickBASIC 4.5!

Anyway, all of the school computers had a number of education games on them, including this game. I remember playing this game quite a bit on the computers at school back then. Looking back at it now, it certainly wasn't the best game ever... not by a long shot! But it was a lot of fun re-discovering this recently and playing it again for the first time in 20 years or so.

Island: A Game Of Survival, title screen
What will you do today?

In this game, the player is stranded on a small island and must survive alone by collecting rain water, catching fish for food, and making sure not to die from exhaustion. The goal is to escape the island either by being saved by passing ships (possibly by random chance and/or by sending a message in a bottle), or to collect enough planks to build a raft. You can die in a number of ways, and in fact sending a message in a bottle can backfire with you getting picked up by pirates and being forced to walk the plank! After 30 days on the island a hurricane always comes and destroys the island and the game ends.

Choosing a message to send in a bottle
Catching fish
Collecting planks
Dieing from a lack of water

As you can see, it's a very simple game. I'm not actually sure how this port of the original C64 Island game compares as I've never played it. At any rate, I found it to be surprisingly still kind of a fun distraction. Might also be a fun little project to turn into a mobile game one day.

If there is anyone else out there who went to school in Durham Region that remembers this game and wants to play it again, you can download Island: A Game Of Survival right here. The game is a 16-bit DOS executable, so you won't be able to run it natively on a modern version of Windows (at least, I don't think you will be able to, but I don't use Windows so I cannot verify). But it works great in DOSBox.

UPDATE - Apr-14-2018

After I originally wrote this post, it was cool to see that at least a few other people also were interested in finding this game again! I guess no one else was weird enough back then (like me) to copy the game from the school computers to play it at home. After a fair bit of searching around I could not find a copy of this game anywhere else, nor even find any screenshots of it (other then what I had posted here). Though I did come across a number of forum posts over the years from various people reminiscing about the game (and others that were also installed on the school computers back then) asking if anyone had a copy, though never with any responses from someone who did have it. I guess that because this was produced by the Durham Board of Education and probably not commercially sold or anything that it is quite rare. Happy to do my part to ensure that this wasn't lost to time!

Also very cool to see that someone was fond enough of the game to submit a copy to My Abandonware at some point during the past month. Many thanks to the person that did that!

UPDATE - May-24-2018

Just a quick note that I've received an email from someone who was happy to find this game again who went to school in the 1990's in St. Thomas, Ontario! This would be a school under the Thames Valley District School Board. I think that's the first I heard that this game was definitely not just limited to Durham Region schools. Definitely interesting to hear!

Follow-up: Unisys CWD 4001

November 11, 2017 dos cwd4001 hardware

I figured that I would post a follow-up regarding the Unisys CWD 4001 mini 486 PC I picked up earlier this year. I've had a few people now ask me various questions about it. It is certainly an interesting PC, especially for those looking for a nice compact retro PC to play around with so I certainly don't mind posting some more information about it to help out anyone else with questions regarding it.

Almost six months later and I'm actually not using mine that much, as I ended up building another 486 PC in a baby AT case. This was mainly because I wanted to be able to toy around with different hardware customizations, have more options for sound cards and have internal CD-ROM and 5.25" floppy drives. Otherwise my CWD 4001 is still working perfectly fine.

Hard Disk

As mentioned in my previous post earlier this year, the hard drive it originally came with died so I replaced it with a CF-to-IDE adapter. I got a StarTech IDE2CF and a Transcend CF200I 512 MB Compact Flash card. I had to cover the bottom of the adapter with electrical tape as otherwise some of the pins on the bottom would short against the metal part of the case it is resting on (and I could not find any kind of mounting bracket to fit in there instead).

The jumpers I have configured on the CF to IDE adapter set it for 3.3V power and master mode, drawing external power from the adapter you can see plugged in in the photo.

The BIOS configuration that I use for this is 987 cylinders, 16 heads, 63 sectors. If you end up using a CF card to replace a hard disk, make sure you do a FDISK /MBR or you may end up puzzled for a while like I was as to why you are mysteriously unable to boot from it!

You should be able to use a larger CF card if that's all you have (for a short while I was using an 8.4GB IDE hard disk without issue). Though with MS-DOS 6.22 you will only be able to use partitions with a max size of ~500MB.


Mine came with an AMD 486 DX2-66 already and that's what is still installed as I write this. However, these should work fine with up to a DX4 (some even had a DX4 pre-installed). On my CWD-4001 there is a voltage regulator on the motherboard so this should support 5V and 3.3V CPUs just fine, but I've not actually tried this. Do your own research first before trying!

On the underside of the motherboard on my CWD 4001 there is a motherboard diagram showing jumper settings. I've heard some people didn't have this, so I'll share what mine looks like:

The CWD 4001 doesn't include any L2 cache but since these are 486 machines, I believe all CPUs that these would ever have shipped with had 8KB L1 cache. The BIOS has an option to enable CPU write-back cache so if you have, for example, an Intel 80486 SX955 (P24D) then you can make full use of it. Though you will also have to configure the jumpers as shown in the diagram above. However, probably most people won't have a CPU with write-back cache support. Not to worry if you don't, it doesn't make a massive (real world) performance difference anyway.


For MS-DOS, you'll want the NE2000 packet driver. Then you'll want to add this to your AUTOEXEC.BAT with something like the following:

C:\NET\NE2000.COM 0x60 10

Where the 10 is the IRQ for the network adapter. Mine was set to 10, yours might not be (IRQ 10 or 11 were very commonly used for network adapters). I didn't have to tinker with any BIOS settings to make this work (not that there really is much of anything that you could change that would affect this to be honest).


As mentioned in my previous post, I'm using a Creative Sound Blaster CT4170 (Vibra 16XV). Your options for sound cards are quite limited with a CWD 4001 due to the compact size of the case. As far as Creative cards go, you'll probably be stuck to only a few of the later models that were more compact. Probably the best choice is one of the AWE64 value cards that is more compact but I don't own this card personally. I'm unfamiliar with what other Sound Blaster clone cards may fit.

For me, using IRQ 5 and 7 both worked. Remember that IRQ 7 is also used for the parallel port (LPT1), so if you're using any parallel port device you may want to configure your sound card to use IRQ 5 instead.

I have the following in my AUTOEXEC.BAT for my CT4170:

SET BLASTER=A220 I5 D1 H1 P330 T6


The CWD 4001 I would say is pretty average for a 486 DX2-66 system. It does not have VLB graphics, but the Cirrus Logic GD5424 isn't too bad really. The lack of L2 cache is also unfortunate, but the RAM speed seems a bit faster than other 486 systems I've tried... maybe something specific to the chipset/motherboard? Not sure really. The two RAM sticks installed in my CWD 4001 are nothing special, two HYM532220W-70 (72-pin 8MB 70ns) sticks.

Versus the other 486 system I built (Intel 80486 SX911 CPU, FIC 486-PVT motherboard, S3 Trio32 VLB, 16MB RAM) just for a slightly apples-to-oranges comparison:

How does this translate to "real world" performance? Well, I can share with you the results from running Phil's DOS Benchmark Pack on both of these systems:

CWD 4001 FIC 486-PVT, SX911, S3 Trio32 VLB
3DBench 1.0 41.6 fps 50.0 fps
3DBench 1.0c 40.4 fps 48.2 fps
Chris's 3D Benchmark 26.9 fps 31.4 fps
Chris's 3D SVGA Benchmark 9.1 fps
PC Player Benchmark 10.1 fps 9.6 fps
PC Player Benchmark (640x480) 4.0 fps 3.8 fps
Doom (min. details) 71.4 fps 70.0 fps
Doom (max. details) 24.3 fps 26.1 fps

Not really too surprising here when comparing ISA graphics to VLB graphics (in particular the S3 VLB graphics cards tended to be top-tier, performance wise).


A few people have asked questions after obtaining their own CWD 4001 (or 4002) after seeing that they were missing some internal components. In particular I've seen some people missing the ISA riser card and/or the bracket that fits onto the inside back of the case which the back plate of an ISA card would slide into when installing one. I'm unsure what people are doing for replacements for these and even if they are easy to come by, but for people's reference I've taken a bunch of photos of the ISA riser card and the ISA back plate bracket thingy.

Anyway, I hope the above helps anyone else looking for information about these Unisys CWD mini 486 computers. Feel free to email me though if you have any additional questions of course (my email address is on the "About" page linked on the left).