Performance Regression: Less optimal register allocation and branch inversion on 8086 (-ox -ok)
Description
With the Open Watcom Beta release (April 2026), testing an 8086 C++ code base targeting DOS revealed a performance regression compared to the March 2026 release. Through disassembly diffing, I traced the regression strictly to how the clampi logic (a small inline function) was lowered to assembly inside a complex translation unit (for reference: Object3D::addDirtyRect), when compiled with maximal optimisations -bt=dos -0 -ml -wx -oa -oe -oh -oi+ -ok -ol -os -ox -oz -s -zm -zq. This issue is specific to the space-optimisation flag -os.
Detailed Observation
The regression takes the form of replacing fast arithmetic (e.g. dec accompanied by optimal register-to-register moves) with lea effective-address instructions, shifting which register holds values in branches, and generating flipped jg/jle condition blocks that result in slightly worse throughput on an actual 8086 processor.
C++ Source extract:
static inline int clampi(int v, int lo, int hi) {
if (v < lo) return lo;
if (v > hi) return hi;
return v;
}
void Object3D::addDirtyRect(const RenderContext &ctx, short minX, short minY,
short maxX, short maxY) {
const short w = (short)ctx.screenW;
const short h = (short)ctx.screenH;
if (maxX < 0 || minX >= w || maxY < 0 || minY >= h) return;
minX = clampi(minX, 0, w - 1);
minY = clampi(minY, 0, h - 1);
maxX = clampi(maxX, 0, w - 1);
maxY = clampi(maxY, 0, h - 1);
// ...
}
March Release Generated Code (Good)
mov dx,ax
dec dx ; dx = w - 1 (2 clocks)
test bx,bx
jge L$54
xor bx,bx
jmp L$55
L$54:
cmp bx,dx
jle L$55
mov bx,dx
L$55:
April Release Generated Code (Regressed)
lea ax,-1[di] ; ax = w - 1 (slower LEA on 8086)
test bx,bx
jge L$54
xor ax,ax
jmp L$55
L$54:
cmp bx,ax
jg L$55
mov ax,bx
L$55:
Additionally, more values spill from registers requiring reload across similar clamping calls further down.
Steps to Reproduce
Unfortunately, the codebase which exhibits this behaviour is complex and I wasn't able to extract a reliable test case. Because global register flow optimisations (-ok, -ox) are stateful with respect to the translation unit, extracting just the clampi function into a trivial test.cpp causes the compiler to magically perfectly allocate registers due to the lack of surrounding register pressure.
Expected Behaviour
The new release should ideally preserve the optimal instruction selection/register mapping of the March compile, avoiding slower lea instructions compared to simple register arithmetic (dec).
Closing notes
Please note that the compiler "traditionally" produces faster code with -os than -ot for the 8086, which is the motivation behind this report. I casually observed that the April release produces faster output for -ot than the March release, though for both March and April -os is faster than -ot, so if speed is an objective, the April release is for my code base a regression as there is no workaround.
Performance Regression: Less optimal register allocation and branch inversion on 8086 (-ox -ok)
Description
With the Open Watcom Beta release (April 2026), testing an 8086 C++ code base targeting DOS revealed a performance regression compared to the March 2026 release. Through disassembly diffing, I traced the regression strictly to how the
clampilogic (a small inline function) was lowered to assembly inside a complex translation unit (for reference:Object3D::addDirtyRect), when compiled with maximal optimisations-bt=dos -0 -ml -wx -oa -oe -oh -oi+ -ok -ol -os -ox -oz -s -zm -zq. This issue is specific to the space-optimisation flag-os.Detailed Observation
The regression takes the form of replacing fast arithmetic (e.g.
decaccompanied by optimal register-to-register moves) withleaeffective-address instructions, shifting which register holds values in branches, and generating flippedjg/jlecondition blocks that result in slightly worse throughput on an actual 8086 processor.C++ Source extract:
March Release Generated Code (Good)
April Release Generated Code (Regressed)
Additionally, more values spill from registers requiring reload across similar clamping calls further down.
Steps to Reproduce
Unfortunately, the codebase which exhibits this behaviour is complex and I wasn't able to extract a reliable test case. Because global register flow optimisations (
-ok,-ox) are stateful with respect to the translation unit, extracting just theclampifunction into a trivialtest.cppcauses the compiler to magically perfectly allocate registers due to the lack of surrounding register pressure.Expected Behaviour
The new release should ideally preserve the optimal instruction selection/register mapping of the March compile, avoiding slower
leainstructions compared to simple register arithmetic (dec).Closing notes
Please note that the compiler "traditionally" produces faster code with
-osthan-otfor the 8086, which is the motivation behind this report. I casually observed that the April release produces faster output for-otthan the March release, though for both March and April-osis faster than-ot, so if speed is an objective, the April release is for my code base a regression as there is no workaround.