Turbo Pascal Overlay Tutorial: Build, Package, and Debug an OVR Application

C:\RETRO\DOS\TP>type turbop~5.htm

Turbo Pascal Overlay Tutorial: Build, Package, and Debug an OVR Application

This tutorial is intentionally practical. You will build a small Turbo Pascal program with one resident path and one overlayed path, then test deployment and failure behavior.

If your install names/options differ, keep the process and adapt the exact menu or command names.

Goal and expected outcomes

Goal: move a cold code path out of always-resident memory and verify it loads on demand from .OVR.

Expected outcomes before you start:

  1. build output includes both .EXE and .OVR
  2. startup succeeds only when overlay initialization succeeds
  3. cold feature call has first-hit latency and warm-hit improvement
  4. removing .OVR produces controlled error path, not random crash

Minimal project layout

1
2
3
4
OVRDEMO/
  MAIN.PAS
  REPORTS.PAS
  BUILD.BAT

Step 1: write resident core and cold module

REPORTS.PAS (cold path candidate):

{$O+}  { TP5 requirement: unit may be overlaid }
{$F+}  { TP5 requirement for safe calls in overlaid programs }
unit Reports;

interface
procedure RunMonthlyReport;

implementation

procedure RunMonthlyReport;
var
  I: Integer;
  S: LongInt;
begin
  S := 0;
  for I := 1 to 25000 do
    S := S + I;
end;

end.

MAIN.PAS:

program OvrDemo;
{$F+}  { TP5: use FAR call model in non-overlaid code as well }
{$O+}  { keep overlay directives enabled in this module }

uses
  Overlay, Crt, Dos, Reports;
{$O Reports}  { select this used unit for overlay linking }

var
  Ch: Char;
  ExeDir, ExeName, ExeExt: PathStr;
  OvrFile: PathStr;

procedure InitOverlays;
begin
  FSplit(ParamStr(0), ExeDir, ExeName, ExeExt);
  OvrFile := ExeDir + ExeName + '.OVR';
  OvrInit(OvrFile);
  if OvrResult <> ovrOk then
  begin
    Writeln('Overlay init failed for ', OvrFile, ', code=', OvrResult);
    Halt(1);
  end;
  OvrSetBuf(60000);
end;

begin
  InitOverlays;
  Writeln('Press R to run report, ESC to exit');
  repeat
    Ch := ReadKey;
    case UpCase(Ch) of
      'R':
        begin
          Writeln('Running report...');
          RunMonthlyReport;
          Writeln('Done.');
        end;
    end;
  until Ch = #27;
end.

Step 2: enable overlay policy

Overlay output is not triggered by uses Overlay alone. You need both:

  1. mark unit as overlay-eligible at compile time
  2. select unit for overlaying from the main program

For Turbo Pascal 5.0 (per Reference Guide), these are hard rules:

  1. all overlaid units must be compiled with {$O+}
  2. active call chain must use FAR call model in overlaid programs
  3. practical safe pattern: {$O+,F+} in overlaid units, {$F+} in other units and main
  4. {$O UnitName} must appear after uses
  5. uses must name Overlay before any overlaid unit
  6. build must be to disk (not memory)

The full REPORTS.PAS and MAIN.PAS examples above include these directives directly.

Why {$O+} exists (TP5 technical reason)

In TP5, {$O+} is not just a “permission bit” for overlaying. It also changes code generation for calls between overlaid units to keep parameter pointers safe.

Classic hazard:

  • caller unit passes pointer to a code-segment-based constant (for example a string/set constant)
  • callee is in another overlaid unit
  • overlay swap can overwrite caller code segment region
  • raw pointer becomes invalid

TP5 {$O+}-aware code generation mitigates this by copying such constants into stack temporaries before passing pointers in overlaid-to-overlaid scenarios.

Typical source-level shape:

In REPORTS.PAS:

{$O+}  { TP5 mandatory for overlaid units }
{$F+}  { TP5 FAR-call requirement }
unit Reports;
...

In MAIN.PAS:

program OvrDemo;
uses Overlay, Crt, Dos, Reports;
{$O Reports}  { overlay unit-name directive: mark Reports for overlay link }

Without the unit-name selection ({$O Reports} or equivalent IDE setting), the unit can stay fully linked into the EXE even if {$O+} is present.

TP5 constraint from the same documentation set: among standard units, only Dos is overlayable; System, Overlay, Crt, Graph, Turbo3, and Graph3 cannot be overlaid.

Step 2.5: when the .OVR file is actually created

This is the key technical point that is often misunderstood:

  1. REPORTS.PAS compiles to REPORTS.TPU (unit artifact).
  2. MAIN.PAS is compiled and then linked with all used units.
  3. During link, overlay-managed code is split out and written to one overlay file.

So .OVR is a link-time output, not a unit-compile output.

How code is selected into .OVR

Selection is not by “file extension magic” and not by uses Overlay. The link pipeline does this:

  1. mark used code blocks from reachable entry points
  2. check units marked for overlaying (via overlay unit-name directive/options)
  3. for callable routines in those units, emit call stubs in EXE and write overlayed code blocks to .OVR

So:

  • unused routines can be omitted entirely
  • selected routines from one or more units can end up in the same .OVR
  • unit selection is explicit, routine placement is linker-driven from that set

Naming rule

The overlay file is tied to the final executable base name, not to a single unit.

  • compile/link target MAIN.EXE -> overlay file MAIN.OVR
  • compile/link target APP.EXE -> overlay file APP.OVR

It is not REPORTS.OVR just because Reports contains overlayed routines. One executable can include overlayed code from multiple units, and they are packed into that executable’s single overlay payload.

When .OVR may not appear

If no code is actually emitted as overlayed in the final link result, no .OVR file is produced. In that case, check project options/directives first.

Step 3: build and verify artifacts

Build with your normal tool path (IDE or CLI). After successful build:

  • verify your output executable exists (for example MAIN.EXE if compiling MAIN.PAS)
  • verify matching overlay file exists with the same base name (for example MAIN.OVR)
  • record file sizes and timestamp

If .OVR is missing, your overlay profile is not active.

Step 4: runtime tests

Test A - healthy run

Expected:

  • startup prints no overlay error
  • first R call may be slower
  • repeated R calls are often faster (buffer reuse)

Test B - missing OVR

Temporarily rename the generated overlay file (for example MAIN.OVR).

Expected:

  • startup exits with explicit overlay init error
  • no undefined behavior

If it crashes instead, fix error handling before continuing.

Step 4.5: initialization variants (OvrInit, OvrInitEMS, OvrSetBuf)

Minimal initialization:

OvrInit(OvrFile);

If initialization fails and you still call an overlaid routine, TP5 behavior is runtime failure (the reference guide calls out runtime error 208).

OvrInit practical lookup behavior (TP5): if OvrFile has no drive/path, the manager searches current directory, then EXE directory (DOS 3.x), then PATH.

OvrInit result handling (OvrResult):

  • ovrOk: initialized
  • ovrNotFound: overlay file not found
  • ovrError: invalid overlay format or program has no overlays

EMS-assisted initialization:

OvrInit(OvrFile);
OvrInitEMS;

OvrInitEMS can move overlay backing storage to EMS (when available), but execution still requires copying overlays into the normal-memory overlay buffer.

OvrInitEMS result handling (OvrResult):

  • ovrOk: overlays loaded into EMS
  • ovrIOError: read error while loading overlay file
  • ovrNoEMSDriver: no EMS driver detected
  • ovrNoEMSMemory: insufficient free EMS

On OvrInitEMS errors, overlay manager still runs from disk-backed loading.

Buffer sizing:

  • TP5 starts with a minimal overlay buffer (large enough for largest overlay).
  • For cross-calling overlay groups, this can cause excessive swapping.
  • OvrSetBuf increases buffer by shrinking heap.
  • legal range (TP5): BufSize >= initial and BufSize <= MemAvail + OvrGetBuf
  • if you increase buffer, adjust {$M ...} heap minimum accordingly

Important ordering rule (TP5): call OvrSetBuf while heap is effectively empty. If using Graph, call OvrSetBuf before InitGraph, because InitGraph allocates heap memory and can prevent buffer growth.

Step 5: tune overlay buffer with measurement

Run the same interaction script while changing OvrSetBuf:

  • small buffer (for example 16K)
  • medium buffer (for example 32K)
  • larger buffer (for example 60K)

Expected pattern:

  • too small: frequent reload stalls
  • too large: less stall, but memory pressure elsewhere

Choose by measured latency and memory headroom, not by guess.

Step 6: boundary correction when overlay thrashes

If one action triggers repeated slowdowns:

  1. move shared helpers from overlay unit to resident unit
  2. keep deep cold logic in overlay unit
  3. reduce cross-calls between overlay units

Overlay design is call-graph design.

Troubleshooting matrix

  • check unit/object participation in link graph
  • check far/near and declaration compatibility

Symptom: startup overlay error

  • check .OVR filename/path assumptions
  • check deployment directory, not just dev directory

Symptom: intermittent slowdown

  • profile call path for overlay churn
  • increase buffer or move hot helpers resident

What this tutorial teaches beyond overlays

You practice four skills that transfer everywhere:

  1. define expected behavior before test
  2. verify artifact set before runtime
  3. isolate runtime dependencies explicitly
  4. tune with measured data, not assumptions

Related reading:

2026-02-22 | MOD 2026-03-09