Turbo Pascal Toolchain, Part 3: Overlays, Memory Models, and Link Strategy

C:\RETRO\DOS\TP\TOOLCH~1>type turbop~3.htm

Turbo Pascal Toolchain, Part 3: Overlays, Memory Models, and Link Strategy

This article is rewritten to be explicitly source-grounded against the Turbo Pascal 5.0 Reference Guide (1989), Chapter 13 (“Overlays”) plus Appendix B directive entries.

Structure map. 1) Why overlays existed—mechanism, DOS memory pressure, design tradeoffs. 2) TP5 hard rules and directive semantics. 3) FAR/near call model and memory implications. 4) Build and link strategy for overlaid programs. 5) Runtime initialization: OvrInit, OvrInitEMS, OvrSetBuf usage and diagnostics. 6) Overlay buffer economics and memory budget math. 7) Failure triage and performance profiling mindset. 8) Migration from non-overlay projects. 9) Engineering checklist and boundary caveats.

Version note. This article is grounded in the TP5 Reference Guide. Borland Pascal 7 and later overlay implementations may differ in details (e.g. EMS handling, buffer API). The core rules—{$O+}, FAR chain, compile-to-disk, init-before-use—tend to hold across versions, but when in doubt, consult the manual for your specific toolchain. TP6/TP7 improvements are beyond the scope of this piece; the TP5 baseline remains the most widely documented and forms a stable reference.

Why overlays existed

In TP5 real-mode DOS workflows, overlays are a memory-management strategy: keep non-hot code out of always-resident memory and load it on demand. Conventional memory in DOS is capped at roughly 640 KB; TSRs, drivers, and stack/heap shrink the usable space. A large application can easily exceed that budget if all code is resident.

Mechanism. The overlay manager maintains a buffer in conventional memory. Overlaid routines live in a separate .OVR file on disk. On first call into an overlaid routine, the manager loads the appropriate block into the buffer and transfers control. Subsequent calls to already-loaded overlays execute in-place; no disk access. When the buffer fills and a new overlay must load, the manager discards inactive overlays first (least-recently-used policy), then loads the requested block.

Constraints. The buffer must hold at least the largest overlay (including fix-up data). Overlay call-path constraints matter: cross-calling overlay clusters—routines in overlay A calling routines in overlay B—force repeated swaps if the buffer is too small. Design the call graph so overlay entry points are used in bursts; avoid ping-pong patterns (A→B→A→B) where each transition evicts the previous overlay. Cold code that runs infrequently benefits most; hot paths that recur in tight loops should stay resident. A report generator that runs once per session is an ideal overlay candidate; a validation routine called on every keystroke is not.

Failure modes. Undersized buffer: visible thrashing, multi-hundred-millisecond stalls on each swap. Missing .OVR at runtime: init fails, calling overlaid code yields error 208. Incorrect FAR-call chain: corruption or crash when control returns through a near-call frame.

Design tradeoffs. Overlays reduce resident footprint at the cost of latency on first use and complexity in build and deployment. They help when (a) total code size exceeds available conventional memory, or (b) resident footprint must shrink to coexist with TSRs or other programs. They hurt when cold code is called frequently in alternation—e.g. A→B→A→B—because each transition may force a reload. Packaging and deployment hazards: the .OVR file must deploy alongside the .EXE with a matching base name. ZIP extracts that place .EXE in one folder and .OVR in another, or installers that omit the .OVR, produce ovrNotFound at startup. Document in release notes that both files must stay together; test packaging on a clean directory.

TP5 hard rules (not optional style)

For TP5 overlaid programs, these are the baseline rules:

  1. Overlaid units must be compiled with {$O+}.
  2. At any call to an overlaid routine in another module, all active routines in the current call chain must use FAR call model.
  3. Use {$O unitname} in the program (after uses) to select overlaid units.
  4. uses must list Overlay before overlaid units.
  5. Programs with overlaid units must compile to disk (not memory).

TP5 also states that among the listed standard units only Dos is overlayable; System, Overlay, Crt, Graph, Turbo3, and Graph3 are not.

Tuning workflow. Before enabling overlays, identify cold units (e.g. report generators, rarely-used wizards) and compile them with {$O+}. Add {$O unitname} one unit at a time and rebuild; verify .OVR appears and size changes as expected. Link-map triage: with -Fm (or equivalent map-file option) the linker produces a .MAP file. Overlaid segments appear in a dedicated overlay region; resident segments stay in the main program listing. If you add {$O UnitName} but the map shows that unit’s code still in the main program, the directive did not take effect—often due to placement after uses or a compile-to-memory build. If link fails or .OVR is missing, the overlay selection is not taking effect—check directive placement and uses order.

Failure when rules are violated. Omitting {$O+} on an overlaid unit: compiler error. Omitting {$F+} on a caller in the chain: link may succeed but runtime can corrupt. Forgetting uses Overlay before overlaid units: the Overlay unit’s runtime is not linked; overlay manager never initializes. Compiling to memory: overlay linker path is bypassed; no .OVR produced.

What {$O+} actually changes

{$O+} is not just a marker. TP5 documents concrete codegen precautions: when calls cross units compiled with {$O+} and string/set constants are passed, the compiler copies code-segment-based constants into stack temporaries before passing pointers. This prevents invalid pointers if overlay swaps replace caller unit code areas.

That detail is the reason “works in tiny test, crashes in integrated flow” happens when overlay directives are inconsistent.

Mechanism. Without {$O+}, a call like DoReport('Monthly') may pass a pointer to a constant in the code segment. If DoReport is overlaid and triggers a swap, the caller’s code segment can be evicted; the pointer then points at overlay buffer contents, not the original string. With {$O+}, the compiler emits logic to copy the constant to the stack and pass that address instead.

Constraint. {$O unitname} has no effect inside a unit—it is a program-level directive. The unit must already be compiled with {$O+} or the compiler reports an error. Mixing {$O+} and {$O-} inconsistently across a call chain is a common source of intermittent corruption. The same rule applies to sets passed by reference: set constants in the code segment can become invalid if the caller is evicted during an overlay swap. TP5 copies both strings and sets into stack temporaries when the callee may be overlaid.

Example of the constant-copy hazard. In a unit compiled without {$O+}, WriteReport(HeaderText) might pass the address of HeaderText as stored in the code segment. If WriteReport is overlaid and triggers a swap, the caller’s code may be evicted; the callee then reads from wrong memory. With {$O+}, the compiler generates a copy to a stack temporary and passes that address—safe regardless of overlay activity.

FAR-call requirement explained operationally

Manual example pattern: MainC -> MainB -> OvrA where OvrA is in an overlaid unit. At call to OvrA, both MainB and MainC are active, so they must use FAR model too.

Practical TP5-safe strategy:

  • {$O+,F+} in overlaid units
  • {$F+} in main program and other units

TP5 notes the cost is usually limited: one extra stack word per active routine and one extra byte per call.

FAR vs near implications. Near calls use a 2-byte return address (offset only); FAR calls use 4 bytes (segment:offset). Each active frame on the stack therefore costs one extra word (2 bytes) with {$F+}. For deeply nested call chains—e.g. main → menu → dialog → validator → report—the stack growth is 2 * depth bytes. In a 64 KB stack, that is rarely the bottleneck; the overlay buffer and heap compete more for conventional memory.

Memory budget math. A rough breakdown for a typical overlaid TP5 app:

  • DOS + drivers + TSRs: ~100–200 KB (varies)
  • Resident code (main, Crt, Graph init, hot units): ~80–150 KB
  • Overlay buffer (OvrSetBuf): 32–64 KB typical, up to MemAvail + OvrGetBuf
  • Heap ({$M min,max}): remaining conventional memory
  • Stack: usually 16–32 KB

If MemAvail at startup is small, increasing overlay buffer via OvrSetBuf reduces heap. Tune with MemAvail and OvrGetBuf diagnostics before and after OvrSetBuf. Runtime initialization variants: OvrSetBuf must run while the heap is empty. Two common orderings: (a) OvrInitOvrSetBuf → heap consumers (Graph, etc.); or (b) OvrInit only, accepting the default buffer. If your program uses Graph, call OvrSetBuf before InitGraph—the Graph unit allocates large video and font buffers from the heap, which locks in the overlay buffer size. Late OvrSetBuf after any heap allocation has no effect; no runtime error, but the buffer stays at its initial minimum.

Segment implications. In real-mode 8086, a FAR call pushes segment and offset; a near call pushes only offset. When resident code calls overlay code, control crosses segment boundaries. The overlay buffer lives in a different segment than the main code segment. A near return in the caller would pop only 2 bytes—the offset—and jump back with a stale segment, typically causing an immediate crash or wild jump. FAR ensures the full return address is preserved. This is why the rule applies to the entire call chain, not just the immediate caller.

Build and selection flow (TP5)

Minimal structure:

program App;
{$F+}
uses Overlay, Dos, MyColdUnit, MyHotUnit;
{$O MyColdUnit}

Key nuances:

  • {$O unitname} has no effect inside a unit.
  • It only selects used program units for placement in .OVR.
  • Unit must already be compiled in {$O+} state or compiler errors.

Build/link strategy. Overlays are a link-time feature. The pipeline:

  1. Compile each unit to .TPU (with correct {$O+} for overlaid units).
  2. Compile the main program; the compiler records overlay directives.
  3. Link produces .EXE and .OVR. The linker segregates code marked for overlay into the .OVR file and emits call stubs in the .EXE.

A minimal batch build for an overlaid project:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@echo off
rem Overlaid build: units first, then main, linker produces EXE+OVR
tpc -B MyColdUnit.pas
tpc -B MyHotUnit.pas
tpc -B Main.pas
if errorlevel 1 goto fail
if not exist Main.OVR echo WARNING: No .OVR produced - overlay selection may be inactive
goto ok
:fail
echo Build failed
exit /b 1
:ok
echo Main.EXE + Main.OVR ready

Checklist. After a clean build: (1) .EXE and .OVR exist; (2) .OVR size roughly matches sum of overlaid unit contributions; (3) running without .OVR fails explicitly at init, not later with corruption; (4) if using external .OBJ modules that participate in overlay call chains, ensure they use FAR call/return conventions compatible with TP’s expectations; (5) for release builds, confirm both artifacts are present in the output directory and in any packaging script—CI or automated build pipelines that copy only .EXE will ship a broken product.

IDE vs CLI parity. Overlay options in the IDE (Compiler → Overlay unit names, Memory compilation off) must match what a batch build does. If the IDE build produces .OVR but the CLI build does not, the IDE may have overlay settings that are not reflected in project files. Document the exact options and replicate them in the batch script.

Using .MAP for overlay forensics. With link map output enabled (e.g. tpc -Fm or IDE Linker → Map file), the map file shows segment addresses and symbol placement. Overlaid segments appear in the overlay region; resident segments in the main program. Link-map-based triage: (1) Compare map before and after adding {$O unitname}—overlaid units should move from main-program segments into the overlay section. (2) If a unit’s code remains in the main program despite {$O unitname}, the directive was ignored (check placement, compile-to-disk, uses order). (3) Use segment sizes in the map to estimate .OVR size and the minimum OvrSetBuf; the largest overlay block sets the floor. Comparing map before and after adding {$O unitname} confirms which code moved to the overlay file.

Runtime initialization contract

Overlay manager must be initialized before first overlaid call:

OvrInit('APP.OVR');
if OvrResult <> ovrOk then Halt(1);

If initialization fails and you still call overlaid code, TP5 behavior is runtime error 208 (“Overlay manager not installed”).

OvrInit behavior (TP5)

  • Opens/initializes overlay file.
  • If filename has no path, search includes current directory, EXE directory (DOS 3.x), and PATH.
  • Typical errors: ovrError, ovrNotFound.

OvrInitEMS behavior (TP5)

  • Attempts to load overlay file into EMS.
  • On success, subsequent loads become in-memory transfers from EMS to the overlay buffer—faster than disk, but overlays still execute from conventional memory. EMS acts as a paging store, not execution space.
  • On error, manager keeps functioning with disk-backed overlay loading.

EMS usage pattern. Call OvrInit first, then OvrInitEMS. If OvrResult is ovrOk after OvrInitEMS, the manager uses EMS for overlay storage. On ovrNoEMSDriver or ovrNoEMSMemory, the program continues with disk loading; no need to fail. EMS reduces load latency on machines with expanded memory but is optional for correctness. EMS tradeoffs: EMS removes disk I/O from overlay loads—a floppy or slow hard disk can add 100–500 ms per swap; EMS cuts that to a few milliseconds. The tradeoff is memory pressure: the full .OVR is duplicated in EMS. On a machine with limited EMS (e.g. 256 KB), loading a 120 KB overlay file may exhaust EMS and force fallback to disk anyway. Check OvrResult after OvrInitEMS; if it is ovrNoEMSMemory, consider reducing overlay count or advising users with low EMS to free expanded memory. On machines without EMS, OvrInitEMS returns ovrNoEMSDriver and the program silently continues with disk—no special handling required.

OvrResult semantics

Unlike IOResult, TP5 documents that OvrResult is not auto-cleared when read. You can inspect it directly without first copying.

Usage patterns and diagnostics

Pattern 1: minimal init with explicit path. Avoid search-order surprises by building the overlay path from the executable location:

procedure InitOverlays;
var ExeDir, ExeName, ExeExt: PathStr;
begin
  FSplit(ParamStr(0), ExeDir, ExeName, ExeExt);
  OvrInit(ExeDir + ExeName + '.OVR');
  if OvrResult <> ovrOk then
  begin
    case OvrResult of
      ovrError:   WriteLn('Overlay format error or program has no overlays');
      ovrNotFound: WriteLn('Overlay file not found: ', ExeDir + ExeName + '.OVR');
      else       WriteLn('OvrResult=', OvrResult);
    end;
    Halt(1);
  end;
end;

Pattern 2: EMS-optional with fallback. Try EMS first; if it fails, disk loading still works:

OvrInit(ExeDir + ExeName + '.OVR');
if OvrResult <> ovrOk then Halt(1);
OvrInitEMS;  { ignore result: disk loading remains available }

Pattern 3: buffer tuning before heap allocation. Call OvrSetBuf while the heap is empty. With Graph unit:

OvrInit(OvrFile);
if OvrResult <> ovrOk then Halt(1);
OvrSetBuf(50000);   { before InitGraph }
InitGraph(...);     { Graph allocates from heap }

OvrResult reference (TP5 manual-confirmed): ovrOk, ovrError, ovrNotFound, ovrIOError, ovrNoEMSDriver, ovrNoEMSMemory.

OvrSetBuf diagnostics. The call can fail if the heap is not empty or BufSize is out of range. TP5 does not document a dedicated OvrResult for OvrSetBuf failure; practical approach: call OvrSetBuf(DesiredSize) early, then check OvrGetBuf to see if the buffer actually increased. If OvrGetBuf stays at the initial size, the request was rejected (heap in use or size constraint). Add a diagnostic mode that prints MemAvail, OvrGetBuf, and MaxAvail at startup to support troubleshooting.

Initialization ordering variants. Three common patterns: (a) Minimal: OvrInit(path) only, accept default buffer—works when overlays are small and rarely cross-call. (b) Buffer-tuned: OvrInitOvrSetBuf(n) before any heap use—required when Graph or other heap consumers follow. (c) EMS-aware: OvrInitOvrInitEMSOvrSetBuf—EMS can speed loads, but OvrSetBuf still controls conventional-memory buffer size. In all cases, init must complete before the first overlaid call; unit initializations that invoke overlaid code will fail with error 208.

How the Overlay unit lays out memory (brief)

TP5 splits resident and overlaid code at artifact level:

  • .EXE: resident (non-overlaid) program parts
  • .OVR: overlaid units selected by {$O unitname}

At runtime, overlaid code executes from a dedicated overlay buffer in conventional memory. Manual-confirmed points:

  • initial buffer size is the smallest workable value: the largest overlay (including fix-up information)
  • OvrSetBuf changes buffer size by taking/releasing heap space
  • OvrSetBuf requires an empty heap to take effect
  • manager tries to keep as many overlays resident as possible and discards inactive overlays first when space is needed
  • with EMS (OvrInitEMS), overlays are still copied into normal memory buffer before execution

Linker behavior (manual-confirmed). The TP5 overlay linker produces one .OVR per executable. All units marked with {$O unitname} contribute code to that file. The linker decides the layout; you do not control which routines share overlay blocks. Unused routines in overlaid units may be omitted (dead-code elimination). The .OVR is loaded as a whole or in logical chunks depending on the manager implementation—TP5 docs do not specify the exact block structure, but the runtime behavior (LRU discard, buffer sizing) is documented. When sizing the overlay buffer, use the largest single overlay block; the linker may pack multiple small routines into one loadable block, so OvrGetBuf after init reflects the runtime’s minimum—the size of the largest block the manager must load in one swap.

FAR/near and overlay placement. Overlaid code runs in a separate buffer; the linker emits FAR calls to reach it from resident code. Resident routines that call overlaid routines must use FAR so the return address correctly restores the caller’s segment. Near calls in that chain would leave a truncated return address and corrupt the stack. The constraint applies to the entire active call chain at the moment of the overlaid call: main → menu → dialog → validator → report. If report is overlaid, every routine in that path must use FAR. A single near caller in the chain—e.g. a quick helper compiled with {$F-}—can cause intermittent crashes when control returns through that frame; the stack ends up with a mismatched segment.

Buffer economics: OvrGetBuf and OvrSetBuf

TP5 starts with a minimal buffer sized to the largest overlay (including fix-up data). For cross-calling overlay clusters, this can thrash badly.

OvrSetBuf tunes buffer size, with constraints:

  • BufSize must be >= initial size
  • BufSize must be <= MemAvail + OvrGetBuf
  • heap must be empty, otherwise call returns error/has no effect

Important ordering rule: if Graph is used, call OvrSetBuf before InitGraph because Graph allocates heap memory.

Tuning workflow. (1) At startup, log MemAvail and OvrGetBuf before any OvrSetBuf. (2) Run a representative workload (menu navigation, report run, etc.) and note perceived stalls. (3) Increase buffer in steps (e.g. 16K → 32K → 48K → 64K) and re-test. (4) Stop when stalls disappear or MemAvail drops unsafely. (5) Adjust {$M min,max} if the larger buffer causes heap shortage during normal operation.

Practical overlay tuning checklist:

Step Action Success criteria
1 OvrGetBuf after init Know baseline buffer size
2 Run cold-path sequence 3× Count noticeable pauses
3 OvrSetBuf(2 * OvrGetBuf) Fewer pauses
4 Iterate until smooth or MemAvail < 20K Balanced

Concrete sizing examples. If the largest overlay is 24 KB, the initial buffer is ~24 KB. With two overlays that cross-call (e.g. Report → Chart), a 24 KB buffer forces a swap on every transition. OvrSetBuf(48000) holds both; transitions become in-memory. If MemAvail at startup is 120 KB, reserving 48 KB for overlays leaves ~72 KB for heap—adequate for many apps. If MemAvail is 40 KB, a 48 KB buffer request may fail or leave almost no heap; tune down or reduce resident code.

Buffer and Graph/BGI interaction. The Graph unit allocates video buffers, font caches, and driver data from the heap at InitGraph time. If you call OvrSetBuf after InitGraph, the heap is no longer empty; the call has no effect and the buffer stays at its initial size. Always initialize overlays and set buffer size before any substantial heap allocation. Order: OvrInitOvrSetBufInitGraph (or other heap consumers). See Part 4: BGI integration for graphics-specific overlay notes.

Failure triage and performance profiling mindset

Symptom → check → fix:

  • Link error / unresolved overlay symbol: Unit not in overlay selection, or mixed far/near in external .OBJ. Verify {$O unitname} and {$F+} on all units in the call chain.

  • Error 208 at runtime: Overlay manager not installed. Either OvrInit was never called, or it failed and execution continued. Add init check before any overlaid call.

  • ovrNotFound at startup: Path wrong. Use FSplit(ParamStr(0), ...) to build overlay path from EXE location; avoid relying on current directory.

  • ovrError at startup: .OVR does not match .EXE (rebuilt one but not the other), or program has no overlays. Clean rebuild both, verify .OVR exists.

  • Intermittent slowdown / visible stalls: Buffer thrashing. Profile by repeating the slow action and measuring; increase OvrSetBuf or move hot helpers to resident units. Cross-reference with the link map: if the buffer is smaller than the sum of frequently-used overlay block sizes, thrashing is expected. Increase buffer until it holds the active set, or consolidate overlays to reduce cross-calling.

Performance profiling mindset. Overlay cost is load time, not execution time. A loaded overlay runs at full speed. Latency profiling workflow: (1) isolate the user action that triggers the stall; (2) wrap the suspect call in GetTime/GetMsCount timing; (3) run the action multiple times—first call (cold) vs later calls (warm); (4) if cold is 100+ ms and warm is under 5 ms, the stall is overlay load; (5) trace the call path to see which overlaid units participate; (6) either enlarge buffer (to hold multiple overlays) or move frequently-alternating code to resident units. Simple timing around suspect calls (GetTime before/after) confirms whether the stall aligns with overlay load. Minimal diagnostic snippet:

var Hour, Min, Sec, Sec100: Word;
    StartTotal, EndTotal: LongInt;
begin
  GetTime(Hour, Min, Sec, Sec100);
  StartTotal := LongInt(Sec)*100 + Sec100;
  RunSuspectedOverlaidRoutine;
  GetTime(Hour, Min, Sec, Sec100);
  EndTotal := LongInt(Sec)*100 + Sec100;
  WriteLn('Elapsed: ', EndTotal - StartTotal, ' centiseconds');
end;

If the first call shows hundreds of centiseconds and later calls are near zero, the overlay load is the bottleneck. Disk-based loads on a 360K floppy can reach 500 ms or more; EMS typically drops that to under 20 ms. Use this to correlate user-reported “slow menu” complaints with overlay activity.

LRU behavior in practice. The overlay manager keeps the most recently used overlays in the buffer. Alternating rapidly between overlay A and overlay B with a buffer that holds only one forces a load on every switch. Holding both in buffer (or reducing cross-calls) eliminates that cost. Profile the actual call sequence during representative use; if the user typically runs Report then Chart then Report again, a buffer large enough for both pays off.

Migration from non-overlay projects

Converting a working non-overlaid program to use overlays:

  1. Identify cold units. Report generators, rarely-used dialogs, optional modules. Do not overlay hot loops (main menu, render loop, I/O). Practical heuristic: if a routine runs on every frame or in a tight loop, keep it resident. If it runs only when the user selects a specific menu item or triggers an infrequent action, it is a cold-path candidate. Use profiler or manual instrumentation if unsure.

  2. Add {$O+} and {$F+} to candidate units. Add {$F+} to main program and any unit that calls overlaid code (directly or transitively).

  3. Add uses Overlay as first unit in the main program. Add {$O UnitName}
    for each cold unit, one at a time.

  4. Enable compile-to-disk if building in IDE (Options → Compiler → Directories or equivalent).

  5. Add init block before first overlaid call. Use FSplit + OvrInit + OvrResult check.

  6. Clean rebuild. Verify .EXE and .OVR both produced. Run missing-OVR test. Run overlay-thrash test and tune OvrSetBuf.

  7. Regression test the full feature set. Overlays change memory layout; subtle bugs (e.g. uninitialized pointers, stack overflow) can surface.

Rollback: Remove uses Overlay, {$O unitname}, and {$O+} from overlaid units; reduce {$F+} if no longer needed. Rebuild; .OVR will not be produced, all code returns to .EXE.

Incremental migration. Do not overlay everything at once. Start with one clearly cold unit. Validate build, init, and runtime. Add a second; re-validate. If a new overlay causes problems, the failure is localized to that unit or its callers. Batch migration makes triage much harder.

Common migration pitfalls. (a) Overlaying a unit that is used by many others—transitive callers all need {$F+}. (b) Forgetting {$O+} on one unit in a cluster—inconsistent codegen can cause pointer corruption. (c) Deploying .EXE without .OVR—build and packaging scripts must include both. (d) Calling overlaid code before OvrInit—e.g. from unit initialization sections—crashes; init must run in the main program before any overlaid routine is invoked. (e) Packaging hazards: self-extracting archives that copy only .EXE files, installers with file filters that exclude .OVR, or ZIP-based distributions where users extract to different folders—all produce ovrNotFound. Include both files in every distribution artifact; add a post-install check that verifies EXE_dir + base_name + '.OVR' exists, or document clearly that the program requires both files in the same directory.

What is manual-confirmed vs inferred

Manual-confirmed in TP5:

  • directive rules ($O+, $O unitname, $F+ guidance)
  • compile-to-disk requirement
  • runtime API behavior (OvrInit, OvrInitEMS, OvrSetBuf, OvrResult)
  • FAR-chain safety requirement and consequences

Intentionally not claimed here as fixed TP5 public spec:

  • detailed byte-level .OVR file format guarantees
  • universal behavior across TP6/TP7/BP7 variants without version checks

Those may be explored, but should be treated as version-scoped reverse engineering.

Engineering checklist

Before shipping an overlaid TP5 build:

  1. verify overlaid units compiled with {$O+}
  2. verify FAR-call policy ({$F+} strategy) across active-call paths
  3. verify {$O unitname} directives and uses Overlay ordering
  4. verify .EXE and .OVR artifact pair in package
  5. run one missing-OVR startup test and confirm controlled failure path
  6. run one overlay-thrash workload and tune with OvrSetBuf
  7. log MemAvail and OvrGetBuf at startup for support diagnostics
  8. document OvrSetBuf value and {$M} in build notes
  9. include .OVR in installer and distribution package; document that .EXE and .OVR must stay together

Deployment note. End users rarely see .OVR files. Installer scripts and ZIP distributions must include both .EXE and .OVR with matching base names. A self-extracting archive or installer that only grabs the .EXE will produce a program that fails at startup with ovrNotFound. Packaging/deployment hazards: (1) Build scripts that copy *.EXE but not *.OVR into a release directory. (2) Version-control or backup systems that ignore *.OVR by default. (3) Users running from a network drive where the .OVR lives on a different path than the .EXE. (4) Multi-directory installs (e.g. EXE in \bin, OVR in \data) without updating the overlay search path—OvrInit with no path uses current directory and EXE directory; explicit path construction via ParamStr(0) avoids ambiguity. Add a pre-release checklist item: verify both artifacts exist in the shipped package.

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