VOGONS


First post, by TinPlat

User metadata
Rank Newbie
Rank
Newbie

Hi everyone,

Over the past few months I've been working on a project to bring several modern web engine components to 16-bit Windows for Workgroups 3.11. The goal was to make it possible to parse HTML5, CSS, and run JavaScript directly on period hardware without relying on a proxy or modern machine.

Here's what I've ported:

- FreeType (compiled with MSVC 1.52c) – TrueType/OpenType font rendering
- Gumbo-parser (compiled with Open Watcom 2.0) – HTML5 parser
- LibCSS + libparserutils + libwapcaplet (Open Watcom 2.0) – CSS parsing and selection engine
- QuickJS (Open Watcom 2.0) – modern JavaScript engine (ES2020 support)

All of these are compiled as 16-bit DLLs and can be loaded dynamically or statically by Win16 applications (FreeType is dynamic-load only).

Current status:

- FreeType, Gumbo-parser, and the LibCSS family successfully compile and can be initialized from a test program. Basic functionality should work, though not every feature has been thoroughly tested.
- QuickJS also compiles and JS_NewRuntime / JS_NewContext work without GPF. However, JS_Eval currently hits a GPF when performing string concatenation inside an OP_add bytecode handler. The root cause appears to be a stack-pointer corruption issue when the JavaScript code uses string operations.

The project is hosted on GitHub:
Win16WebStack

Each subdirectory contains the original project's license and the modified source files.

I'm sharing this in the hope that the retro computing community finds it useful, and maybe someone with a deep knowledge of Open Watcom's code generation or QuickJS internals might be interested in taking a look at the remaining QuickJS bug. I've spent a lot of time on this, but I'm not sure how much more I can personally dedicate to it. That said, I'd be very happy if others want to pick it up, improve it, or even build something cool on top of it.

Building:

I've kept the project files intact, so you can open and compile them directly in the IDE.

If you have questions, feel free to open an issue on GitHub or reply here.

Thanks!

Reply 1 of 7, by leon22

User metadata
Rank Newbie
Rank
Newbie

Thank you so much for your work!

Hopefully I have time to test the WebStack. Will let you know.

Best regards

PS: maybe it's better to use plain English in the Readme

https://funwithretrocomputers.blogspot.com/

Reply 2 of 7, by TinPlat

User metadata
Rank Newbie
Rank
Newbie
leon22 wrote on 2026-08-05, 20:20:
Thank you so much for your work! […]
Show full quote

Thank you so much for your work!

Hopefully I have time to test the WebStack. Will let you know.

Best regards

PS: maybe it's better to use plain English in the Readme

Thanks for your kind words! I look forward to your feedback when you have time to test it. Good suggestion about the README, I have updated it to English. Thanks for the advice! I just published a Release, feel free to check it out.

Reply 3 of 7, by youxiaojie

User metadata
Rank Newbie
Rank
Newbie

great work!

Reply 5 of 7, by OzzFan

User metadata
Rank Member
Rank
Member

This sounds like a very cool project!

Hobbyist Developer : https://www.shelteringoak.com/du/

Reply 6 of 7, by TinPlat

User metadata
Rank Newbie
Rank
Newbie

I want to share a frustrating issue I ran into while porting QuickJS to 16-bit Windows, and ask if anyone knows of a more portable JavaScript engine.

QuickJS assumes that int is 4 bytes. On 16-bit compilers like Open Watcom, int is only 2 bytes. This is not a small issue, it causes serious memory corruption.

For example, JS_DefineProperty takes a flags argument. The engine defines a flag called JS_PROP_NO_EXOTIC as (1L << 16), which is 65536. But when flags is declared as int, it is truncated to 16 bits. The condition !(flags & JS_PROP_NO_EXOTIC) then always returns true, because the high bit of the flag is lost. This leads to infinite recursion and a crash.

This assumption causes problems in many other places too, and I have spent a lot of time fighting them.

So, does anyone know of a JavaScript engine that does not assume int is 4 bytes? One designed for portability and using explicit 32-bit types like int32_t where needed? It would be even better if it supports a reasonably modern version of JavaScript.

I will try to port such a project when I have time, and I will drop QuickJS support and updates.

Reply 7 of 7, by TinPlat

User metadata
Rank Newbie
Rank
Newbie
Gumbo HTML5 parser — first stable Win16 release […]
Show full quote

Gumbo HTML5 parser — first stable Win16 release

Quick update on the browser project: the HTML5 parser is now fully working on Windows 3.11 for Workgroups.

GUMBO.DLL (built with Open Watcom 2.0, Large model) passes 94/94 tests covering:

  • HTML5 tree construction (implied tags, foster parenting, adoption agency)
  • Character entity decoding (&amp;lt; &amp;gt; &amp;amp; and 2000+ named refs)
  • SVG / MathML namespace handling
  • Error recovery for malformed input
  • gumbo_parse_with_options, gumbo_get_attribute, all public API

Source will be on GitHub tomorrow, link to follow.

Three Win16-specific landmines we hit

1. C99 compound literals are not implemented in Open Watcom 2.0

Gumbo uses ~50 instances of (gumbo_tagset){TAG(A), TAG(B), ...} which have to be manually rewritten as named static arrays. This is a high-risk manual process — I accidentally bound one call site to kTagSet_ForeignStartTags (which contains TAG(SPAN)) instead of a formatting-elements set. The result: &lt;b&gt; inside &lt;div&gt;&lt;span&gt;...&lt;/span&gt;&lt;b&gt;... got treated as a formatting element, triggerd reconstruct_active_formatting_elements, and ended up nested inside the span. Took two evenings of open_elements stack tracing to find.

/* Original C99 */
tag_in(token, kStartTag,
(gumbo_tagset){TAG(B), TAG(BIG), TAG(CODE), TAG(EM), TAG(FONT),
TAG(I), TAG(S), TAG(SMALL), TAG(STRIKE), TAG(STRONG), TAG(TT), TAG(U)})

/* After manual rewrite — MUST be this exact set */
static const gumbo_tagset kTagSet_FormattingStartTags = {
TAG(B), TAG(BIG), TAG(CODE), TAG(EM), TAG(FONT), TAG(I),
TAG(S), TAG(SMALL), TAG(STRIKE), TAG(STRONG), TAG(TT), TAG(U)
};

2. gperf stringpool offsets overflow 16-bit int

char_ref_gperf.c has entries like:

{(int)(size_t)&((struct stringpool_t *)0)->stringpool_str12646, 120010, -1},

The stringpool is 18872 bytes, so the largest member offset is 18871. When cast to int (16-bit on Win16), values above 32767 wrap to negative numbers and all named character references silently fail to resolve.

Fix: change struct GumboNamedCharRef.name from int to long, and the literals to (long)(size_t).

3. wordlist[] is 151 KB and quietly exceeds segment size

gperf's generated wordlist[] has 12647 entries × 12 bytes = 151764 bytes. Open Watcom in Large model puts this in FAR_DATA (not DGROUP), so it doesn't overflow the 64 KB DGROUP limit. But each FAR_DATA segment is still limited to 64 KB, and the compiler gives no error — it silently discards the initialization data. All lookups return zero.

Fix: script the array into 4 blocks of 3200 entries each (38 KB per block), plus a small dispatch function:

if      (key &lt; 3200u)  entry = &amp;wordlist_0[key];
else if (key &lt; 6400u) entry = &amp;wordlist_1[key - 3200u];
else if (key &lt; 9600u) entry = &amp;wordlist_2[key - 6400u];
else entry = &amp;wordlist_3[key - 9600u];

DGROUP vs FAR_DATA in the map file

For anyone curious where the memory actually goes:

_NULL                  BEGDATA  DGROUP  0008:0000   00000010
_DATA DATA DGROUP 0008:0010 000007b8
CONST DATA DGROUP 0008:07e0 00003ac4
CONST2 DATA DGROUP 0008:42a4 0000aab1
_BSS BSS DGROUP 0008:ed56 00000112
^-- DGROUP total ~60.7 KB / 64 KB

char_ref_gperf13_DATA FAR_DATA AUTO 0004:0000 00009600
char_ref_gperf14_DATA FAR_DATA AUTO 0005:0000 00009600
char_ref_gperf15_DATA FAR_DATA AUTO 0006:0000 00009600
char_ref_gperf16_DATA FAR_DATA AUTO 0007:0000 00008ed4

DGROUP is at ~95% capacity. Open Watcom automatically spills oversized static const arrays into FAR_DATA, which is why the split wordlist still links cleanly.

Call to Open Watcom devs

Please, please add C99 compound literals to -zastd=c99. I have manually replaced ~50 of them in Gumbo, and the one I got wrong cost me two evenings. Every other feature in C99 works; this one is a real portability blocker for bringing 32-bit C libraries to Win16.

What's next

Continue testing and fixing LibCSS and Duktape. QuickJS is no longer supported.

Source: https://github.com/qv1145145/Win16WebStack (uploading tomorrow)