Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth

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

Turbo Pascal Toolchain, Part 5: From 6.0 to 7.0 - Compiler, Linker, and Language Growth

Parts 1-4 covered workflow, artifacts, overlays, and BGI integration. This last part goes inside the compiler/language boundary: memory assumptions, type layout, calling conventions, and assembler integration from TP6-era practice to TP7/BP7 scope.

Structure map

  1. Version framing — TP6 vs TP7/BP7 scope, continuity and deltas
  2. Execution model — real-mode assumptions, segmentation, near/far
  3. Data type layout — size table, alignment, layout probe harness
  4. Memory layout consequences — ShortString, sets, records, arrays
  5. Procedures vs functions — semantics and ABI implications
  6. Calling conventions — stack layout, parameter order, return strategy
  7. Compiler directives — policy, safety controls, project-wide usage
  8. Assembler integration — inline blocks, external OBJ, boundary contracts
  9. TP6→TP7 migration — pipeline evolution, artifact implications, language growth
  10. Protected mode and OOP — BP7 context, object layout, VMT considerations
  11. Migration checklist — risk controls, test loops, regression traps, common pitfalls

Version framing: what changed and what stayed stable

The TP6 to TP7 shift was less a language revolution and more an expansion of operational surface:

  • larger project/tooling workflows became easier
  • artifact and mixed-language integration became more central
  • language core stayed recognizably Turbo Pascal

So the technical model below is largely continuous across this generation, with feature breadth increasing in later packaging. Borland Pascal 7 (BP7) extended this further with protected-mode compilation, built-in debugging support, and richer IDE integration, while TP7 remained primarily a real-mode product.

Version-specific nuances — TP7.0 (1990) stabilized the TP6 object model and improved unit compilation speed. TP7.1 addressed bugs and refinements; some teams held at 7.0 for compatibility with shared codebases. BP7 (1992) bundled Turbo Debugger, expanded the RTL, and introduced DPMI target support. Exact behavior of directives like {$G+} (80286 instructions), {$A+} (record alignment), and codegen choices can vary between these builds; when precise version behavior matters, treat claims here as a starting point and validate against your toolchain.

Execution model assumptions (the non-negotiables)

Real-mode DOS assumptions drive everything:

  • Segmented memory model — 64 KB segments, selector:offset addressing, 20-bit physical address space. DS usually points at the program’s data; SS at the stack; CS at code. Overlays swap code segments in and out of a single overlay area.
  • 16-bit register-centric calling paths — AX, BX, CX, DX, SI, DI, BP, SP; segment registers CS, DS, SS, ES.
  • Near vs far distinctions — near calls use same segment (16-bit offset), far calls require segment:offset (32-bit); overlay units demand far entry points.
  • Conventional memory pressure — first 640 KB shared by DOS, TSRs, drivers, and your program; overlays and heap compete for the same pool.

The linker’s choice of memory model (Tiny, Small, Medium, Large, Huge) constrains code and data segment layout. TP6 and TP7 both default to Small model in typical configurations: one code segment, one data segment, with near pointers. Tiny folds code and data into one segment (for .COM output); Medium allows multiple code segments (far code, near data); Large/Huge allow multiple data segments with far pointers—changing pointer size from 2 to 4 bytes. Switching to Large model changes pointer sizes and call conventions; map-file analysis becomes essential when hunting link errors or unexpected runtime behavior.

Artifact implications — Small model yields a single .EXE with code and data in separate segments. Overlays add .OVR files; each overlay is its own code segment loaded on demand. The linker produces a .MAP file listing segment addresses and public symbols; use it to verify overlay boundaries and diagnose “fixup overflow” or segment-order issues. Segment order in the map (CODE, DATA, overlay segments) affects load addresses; changing unit compile order can shift symbols and break code that assumes fixed offsets. A typical map lists segments in load order with start/stop addresses; overlays appear as named segments with their size—verify overlay sizes match expectations before debugging load failures.

Data layout and ABI — Record fields, set bit layouts, and string formats are stable within a compiler version but can differ across TP6, TP7, and BP7. When sharing binary structures (e.g., files or shared memory) between programs built with different toolchains, define a canonical layout and validate with layout probes. Ignoring these constraints while reading Pascal source leads to wrong performance and ABI conclusions. A layout-probe program that prints SizeOf for all shared types, run under each toolchain, gives a quick compatibility report before committing to a cross-toolchain design.

Data type layout: practical table

Common TP-era sizes in real-mode profiles:

  • Byte: 1 byte
  • ShortInt: 1 byte
  • Word: 2 bytes
  • Integer: 2 bytes
  • LongInt: 4 bytes
  • Char: 1 byte
  • Boolean: 1 byte
  • Pointer: 4 bytes (segment:offset in real mode)
  • String[N]: N+1 bytes (length byte + payload)

Floating-point and extended numeric types (Real, Single, Double, Extended, Comp) exist with version/profile-specific behavior and FPU/emulation settings, so treat exact codegen cost as configuration dependent. With {$N+}, the compiler uses native FPU instructions; with {$N-}, software emulation (via runtime library) is typical. Real is typically 6-byte BCD in older profiles and can map to Single or a software type in others—verify in your build. Extended is 10 bytes (80-bit); Comp is 8-byte integer format often used for currency. Set types use one bit per element; set of 0..7 is 1 byte, set of 0..15 is 2 bytes, up to 32 bytes for set of 0..255.

Alignment and packing — Turbo Pascal generally packs record fields without inserting padding; fields align to their natural size (1, 2, 4 bytes). The {$A+/-} (Align Records) directive, where available, can change this—{$A+} may align record fields to word boundaries for faster access on some processors. Packed records (packed record) minimize size at potential performance cost. For structures crossing the Pascal–C–assembly boundary, explicit layout verification is mandatory; C struct alignment rules often differ.

Quick layout probe harness

If binary layout matters, measure your exact compiler profile:

program LayoutProbe;
type
  TRec = record
    B: Byte;
    W: Word;
    L: LongInt;
  end;
  TPackedRec = packed record
    B: Byte;
    W: Word;
  end;
begin
  Writeln('SizeOf(Integer)=', SizeOf(Integer));
  Writeln('SizeOf(Pointer)=', SizeOf(Pointer));
  Writeln('SizeOf(String[20])=', SizeOf(String[20]));
  Writeln('SizeOf(TRec)=', SizeOf(TRec));
  Writeln('SizeOf(TPackedRec)=', SizeOf(TPackedRec));
  Writeln('SizeOf(Single)=', SizeOf(Single), ' SizeOf(Real)=', SizeOf(Real));
end.

Expected outcome: concrete numbers for your environment. Never assume layout from memory when ABI compatibility is at stake.

Memory layout consequences developers felt daily

ShortString behavior

String in classic Turbo Pascal is a short string (length-prefixed), not a null-terminated C string. Consequences:

  • O(1) length read via byte 0
  • max 255 characters; String[80] is 81 bytes
  • direct interop with C APIs needs conversion: either build a null-terminated copy or pass Str[1] and ensure the C side respects the length byte

A simple conversion helper for C library calls (TP7’s Strings unit has StrPCopy; this illustrates the manual pattern):

procedure PascalToCString(const S: String; var Buf; MaxLen: Byte);
var
  I: Byte;
  P: ^Char;
begin
  P := @Buf;
  I := 0;
  while (I < S[0]) and (I < MaxLen - 1) do begin
    P^ := S[I + 1];
    Inc(P); Inc(I);
  end;
  P^ := #0;
end;

Set and record layout

Set/record memory footprint is compact but sensitive to declared ranges and packing decisions. A set of 0..255 consumes up to 32 bytes (one bit per element); smaller ranges use fewer bytes (e.g., set of 0..15 is 2 bytes). Record alignment follows the directive and packing mode. Bit ordering within set bytes is implementation-defined; when exchanging set values with C or assembly, document and test the mapping. If binary compatibility matters, verify layout with SizeOf tests in a dedicated compatibility harness. TP6 and TP7 generally match on these layouts, but mixed toolchains (e.g., C object modules) may introduce padding differences.

Arrays

Arrays are contiguous. High-throughput code benefits from locality, but segment boundaries and index range checks (if enabled) influence speed and safety. Multi-dimensional arrays are stored in row-major order. Static arrays and open-array parameters have different calling semantics: open arrays pass a hidden length (typically as the last parameter or in a known slot), which affects the ABI at procedure boundaries. Example:

procedure Process(const Arr: array of Integer);  { Arr: ptr + hidden High(Len) }

String parameters pass by reference (address of the length byte); value parameters of record type may be copied onto the stack or via a hidden pointer, depending on size—records larger than a few bytes often use a hidden var parameter to avoid stack bloat. When interfacing with assembly, document how each parameter type is passed.

Procedures vs functions: not just syntax

Difference in language semantics:

  • procedure: action with no return value
  • function: returns value and can appear in expressions

Difference in engineering use:

  • procedures often model side-effecting operations
  • functions often model value computation or query paths

In low-level interop, function return strategy and calling convention details matter for ABI compatibility with external objects. Scalars (Byte, Word, Integer, LongInt, pointers) typically return in registers: Byte in AL, Word/Integer in AX, LongInt in DX:AX (high word in DX, low in AX), pointers in DX:AX (segment in DX, offset in AX). Larger types (records, arrays) may use a hidden var parameter or a caller-allocated temporary; the threshold and mechanism vary by version and type size—commonly, records exceeding 4 bytes use a hidden first parameter for the return buffer.

When calling or implementing assembly routines that mimic Pascal functions, match the return mechanism or corruption is likely. A function declared external in Pascal must place its return value where the Pascal caller expects it; an inline asm block that computes a LongInt return should leave the result in DX:AX before the block ends. For Word returns, ensure the high byte of AX is clean if the caller extends the value.

Calling conventions and ABI boundaries

Turbo Pascal default calling convention differs from C conventions commonly used by external modules. Pascal uses left-to-right parameter push order; C typically uses right-to-left (cdecl). Pascal procedures usually clean the stack (ret N); C callers often clean (cdecl). Name mangling can differ: Pascal may export symbols with no decoration or with a leading underscore; C compilers vary. At integration boundaries, define explicitly:

  1. Parameter order — left-to-right (Pascal) vs right-to-left (C)
  2. Stack cleanup responsibility — callee (Pascal-style) vs caller (cdecl)
  3. Near vs far procedure model — must match declaration and link unit
  4. Value return mechanism — register vs stack for large returns

If any of these is ambiguous, “link succeeds but runtime breaks” is predictable.

Stack frame layout — The compiler sets up BP as a frame pointer; parameters are accessed via positive offsets from BP. For a near call, the return address occupies 2 bytes (IP only); for a far call, 4 bytes (CS:IP). Parameter offsets shift accordingly. Typical Pascal caller view: parameters pushed left-to-right, then call. Callee sees highest parameter at lowest address. Example frame for Proc(A: Word; B: LongInt) (near call):

{ Stack grows down. After PUSH BP; MOV BP, SP: BP+2 = ret addr, BP+4 = first param }
{ BP+4 = A (Word), BP+6 = B low, BP+8 = B high. Callee uses RET 6. }
{ For far call: BP+4 = CS, BP+6 = IP; first param at BP+8. }

Near procedures use CALL near ptr and RET; far procedures use CALL far ptr and RETF. The callee must not change BP, SP, or segment registers except as permitted by the convention. For external C routines, use cdecl or equivalent where the object was built with C; otherwise stack imbalance or wrong parameter binding occurs. Inline assembly that calls external code must replicate the expected convention:

function CStrLen(P: PChar): Word; cdecl; external 'CLIB';
// or, if linking C OBJ directly:
{$L mystr.obj}
function CStrLen(P: PChar): Word; cdecl; external;

In mixed-language projects, write one tiny ABI verification test per external routine family before integrating into real logic—e.g., call with known inputs, assert expected output. Example harness: a small program that calls MulAcc(100, 200, 50), expects a known result, and exits with code 0 on success; run it immediately after linking a new assembly module to catch offset or cleanup mismatches before they surface in production.

Compiler directives as architecture controls

Directives are not cosmetic. They change behavior and generated code.

Examples frequently used in serious projects:

  • {$R+/-}: range checking — array bounds, subrange
  • {$Q+/-}: overflow checking — integer arithmetic
  • {$S+/-}: stack checking — overflow sentinel
  • {$I+/-}: I/O checking — handle errors vs continue
  • {$G+/-}: 80286+ instructions (in BP7/profile-dependent builds)
  • {$N+/-} and related: FPU vs software float

Exact availability/effects vary by version/profile, so freeze directive policy per build profile and avoid per-file drift. A project-wide policy file or leading include can enforce consistency:

{ GLOBAL.INC - lock directives for release build }
{$R+}  { Range check in debug only if you prefer; some teams use {$R-} for ship }
{$Q-}  { Overflow off for speed in release }
{$S+}  { Stack overflow detection recommended }
{$I+}  { I/O errors as exceptions or Check(IOResult) }
{$F+}  { FAR calls if using overlays }

TP5/TP6/TP7 anchor points:

  • {$F+/-} (Force FAR Calls) is a local directive with default {$F-}.
  • In {$F-} state, compiler chooses FAR for interface-declared unit routines and NEAR otherwise.
  • Overlay-heavy programs are advised to use {$F+} broadly to satisfy overlay FAR-call requirements.

For {$DEFINE} and conditional compilation, centralize symbols (e.g., DEBUG, USE_OVERLAYS) so builds stay reproducible. Avoid scattering version-specific {$IFDEF} blocks without documentation. Use {$IFOPT R+} to check directive state rather than relying on a separate define when debugging build configuration.

Directive gotchas{$R+} adds runtime cost; many shipped builds use {$R-}. {$I+} makes I/O failures raise runtime errors; {$I-} requires explicit IOResult checks. Switching these mid-project causes subtle bugs. Directive scope matters: a unit’s directives do not always affect the main program unless inherited via include. Document the chosen directive set in a README or build script so new contributors do not override them.

Assembler integration paths

Turbo Pascal projects typically used two integration patterns:

  1. Inline assembler blocks inside Pascal source — asm ... end
  2. External object modules linked with {$L filename.OBJ} declarations

Inline path is great for small hot routines where Pascal symbol visibility helps; you can reference parameters and locals by name. External path is better for larger modules and reuse across projects. Both require strict stack discipline and adherence to the chosen calling convention. Inline blocks cannot use RET or RETF to exit the routine—control must flow to the block end so the compiler can emit the standard epilogue. For conditional exit, use goto to a label after the block or restructure the logic.

Inline assembler

Minimal inline shape:

function BiosTicks: LongInt;
begin
  asm
    mov ah, $00
    int $1A
    mov word ptr [BiosTicks], dx
    mov word ptr [BiosTicks+2], cx
  end;
end;

This style keeps the function contract in Pascal while performing low-level work in assembly. It is ideal for small hardware-touching routines. The compiler generates prologue/epilogue; your inline block must preserve BP, SP, and segment registers as required. Do not assume register contents on entry except parameters passed in. DS and SS are typically valid for data/stack access; ES may be used for string operations or be undefined—save and restore if you modify it.

External OBJ integration

{$L FASTMATH.OBJ}
function MulAcc(A, B, C: Word): Word; external;

The OBJ must export a public symbol matching the Pascal identifier. Calling convention (parameter order, stack cleanup, near/far) must match. If the OBJ was built with TASM or MASM, ensure the PROC declaration uses the right model (e.g., NEAR/FAR) and that parameter offsets line up with Pascal’s push order. Example TASM side for function MulAcc(A, B, C: Word): Word:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
.MODEL small
.CODE
PUBLIC MulAcc
MulAcc PROC
  push bp
  mov  bp, sp
  mov  ax, [bp+4]   ; A
  mov  bx, [bp+6]   ; B
  mov  cx, [bp+8]   ; C
  ; ... compute result in AX ...
  pop  bp
  ret  6
MulAcc ENDP
END

Pascal passes A, B, C left-to-right (A at lowest offset); callee cleans with RET 6. Mismatch in offset or cleanup causes wrong results or stack crash. Note: BP+4 assumes a 2-byte return address for near calls; far calls use 4 bytes, so offsets shift—for the same routine declared far, A would be at BP+8. Always verify against the generated Pascal code or map file. A quick sanity check: compile a trivial Pascal wrapper that calls the external routine with known values, run it, and assert the result before integrating into production.

Boundary contract checklist

Before relying on an external routine:

  • symbol resolves at link (no “undefined external” or mangling mismatch)
  • stack discipline preserved (balanced push/pop, correct ret form)
  • deterministic output under vector tests

If the third condition fails, ABI mismatch is the first suspect. Add a minimal harness that calls the routine with known inputs and asserts the result before integrating into production code. Record the test in the project so future linker or compiler upgrades can re-validate. Mixed Pascal–C–assembly projects benefit from a single “ABI smoke” program that exercises every external boundary with canned inputs.

TP6→TP7 migration: pipeline evolution and artifact implications

Compiler and linker pipeline

From TP6 to TP7, the pipeline stayed conceptually the same: compile units to OBJ, link OBJ with RTL and any external modules to EXE. The flow is: source (.PAS) → compiler (TPC.EXE / TPCX.EXE) → object (.OBJ) → linker (TLINK.EXE) → executable (.EXE) and optional map (.MAP). Overlay units add an extra overlay manager and .OVR files produced during linking. Command-line builds typically use TPC with options for target model and overlays; the IDE invokes the same tools under the hood. Saving OBJ files from each compile allows incremental linking and faster iteration, but migration should start from a full clean rebuild.

Behavioral shifts — TP7’s compiler produced more consistent symbol naming and improved handling of large unit graphs. The linker remained TLINK; its /m, /s, and overlay options work similarly across TP6 and TP7, but segment ordering and fixup resolution can produce different map layouts. When comparing before/after migration, expect segment addresses to change even when logic is identical.

What changed was robustness and integration:

  • Larger projects — TP7 handled more units and larger dependency graphs without tripping over internal limits. Map file output and symbol resolution improved.
  • Object file compatibility — TP6 OBJs generally link with TP7, but the reverse is not guaranteed; TP7 may emit slightly different record layouts or name mangling in edge cases. Recompile from source when migrating, do not mix TP6 and TP7 object files.
  • RTL and units — Standard units (Crt, Dos, Graph, etc.) evolved; some routines gained parameters or changed behavior. Re-test code that relies on unit internals. Graph unit BGI handling, Dos unit path parsing, and Crt screen buffer assumptions are frequent sources of minor incompatibility.
  • OBJ linkage — TP7’s TLink (or TLINK) remained compatible with TASM/MASM object format. Mixed Pascal–assembly projects typically compile Pascal to OBJ, assemble .ASM to OBJ, then link together. Ensure segment naming and model (SMALL, MEDIUM, etc.) match across all modules. Use PUBLIC and EXTRN in assembly to mirror Pascal’s external declarations; symbol names must match exactly. A “Fixup overflow” or “Segment alignment” error often indicates model or segment-name mismatch between modules.

Language and OOP growth

TP6 introduced objects; TP7 refined them. Object layout (VMT, instance size) generally remained compatible, but virtual method tables and constructor/destructor semantics can vary. BP7 added further extensions. For migration:

  • Recompile all object-based units under TP7.
  • Run targeted tests on inheritance chains and virtual overrides.
  • Avoid depending on undocumented VMT layout.

Objects store the VMT pointer at a fixed offset (often the first field); virtual methods are dispatched through it. When writing assembly that allocates or manipulates object instances, preserve that layout:

type
  TBase = object
    X: Integer;
    procedure DoSomething; virtual;
  end;
  PBase = ^TBase;

Instance size and VMT offset are compiler-dependent; use SizeOf(TBase) and avoid hardcoding. Constructor calls initialize the VMT pointer; manual allocation (e.g., New or heap blocks) requires proper init. Descendant objects add their fields after the parent’s; single inheritance keeps layout predictable. Multiple inheritance was not part of classic Turbo Pascal objects, so no VMT merging concerns apply. When passing object instances to assembly, pass the pointer (^TBase) and treat the first word/dword as the VMT pointer.

Constructor and destructor order — Turbo Pascal objects use Constructor Init and Destructor Done (or custom names). Call order matters: base constructors before derived, destructors in reverse. Failing to call the destructor on heap-allocated objects leaks memory. TP7 tightened some edge cases around constructor chaining; if migration reveals odd behavior in object init, compare TP6 and TP7 object code for the constructor to spot differences.

Protected mode and BP7 context

Borland Pascal 7 added protected-mode compilation, producing DPMI-compatible executables that can access extended memory. Key implications:

  • Segment model — 32-bit selectors instead of 16-bit segments; pointer representation and segment arithmetic differ. Code that assumes real-mode segment:offset layout may fail. Far pointers in protected mode are selector:offset but the selector is a DPMI descriptor, not a physical segment. Near pointers remain 32-bit offsets within a segment; the segment limit is 4 GB in 32-bit mode, changing allocation and pointer-arithmetic assumptions.
  • RTL differences — Protected-mode RTL uses DPMI calls for memory and interrupts; DOS file I/O and system calls go through the DPMI host. Heap allocation, overlay loading, and BGI driver loading all route differently than in real mode.
  • Assembly interop — Inline and external assembly must use 32-bit-safe patterns; some real-mode tricks (segment manipulation, direct ports) require different handling. Real-mode int instructions work via DPMI emulation but with different semantics for protected-mode interrupts.

OOP in protected mode — Object and VMT layout are compatible with real-mode BP7, but instance allocation and constructor behavior may differ when the RTL uses DPMI memory services. Virtual method dispatch itself is unchanged; problems typically arise from code that reads segment values or assumes physical addresses. If your project stays in real mode, TP7 is sufficient. Moving to BP7 protected mode is a larger migration: treat it as a separate phase with dedicated tests. Real-mode TP7 binaries remain the norm for DOS-targeted applications; BP7 protected-mode targets DPMI-aware environments (e.g., Windows 3.x, OS/2, or standalone DPMI hosts like 386MAX).

Practical migration checklist (technical, not nostalgic)

1
2
3
4
5
6
1) Freeze known-good TP6 artifacts and checksums.
2) Rebuild clean under target TP7/BP7 environment.
3) Compare executable and map deltas.
4) Re-validate external OBJ ABI assumptions.
5) Re-test overlays + graphics + TSR-heavy runtime profile.
6) Lock directives/options into documented profile files.

Each step is auditable: (1) gives a baseline; (2) isolates toolchain change; (3) surfaces size and symbol shifts; (4) catches OBJ/ABI drift; (5) exercises integration points; (6) prevents future drift from stray directive changes. Run the checklist in order; skipping (1) or (2) makes later steps harder to interpret when regressions appear.

Risk controls

  • Baseline capture — Checksum the TP6 EXE and map before migration; record build date and compiler version. Store baseline outputs in a known location; diff tools and checksum utilities should be available for comparison.
  • Incremental migration — Migrate one unit or subsystem at a time where possible; isolate changes to reduce debugging scope. Migrate leaf units (those with no dependencies on other project units) first; then work inward toward the main program.
  • Fallback — Keep TP6 build environment available until TP7 build is validated. If TP7 regression appears, you can bisect by reverting units. Preserve TP6 compiler, linker, and RTL paths; document them so the fallback is reproducible.

Test loops

  • Smoke — Program starts, minimal user path completes. Include at least one path that loads overlays and one that uses BGI if the project employs them; silent failure on init is common.
  • Regression traps — Known inputs that produced known outputs under TP6; re-run and compare under TP7. Capture checksums or golden files for critical outputs (reports, exports, screenshots). Automate where possible: a batch script that runs the program with canned input and diffs output against baseline catches many regressions.
  • Boundary tests — Overlay load/unload, BGI init/close, TSR hooks, assembly entry points. Exercise code paths that touch segmented memory or far pointers. Add a dedicated test that calls every external assembly routine with edge values (0, -1, max) to uncover ABI mismatches.

Expected outcome

  • Same behavior with clarified build policy, or
  • Explicit, measurable deltas you can explain and document.

Not acceptable: “it feels mostly fine” without verification. Aim for a migration report that states: baseline version, target version, checksum deltas (or “identical”), test results (pass/fail counts), and any known behavioral differences with root cause. Future maintainers will thank you.

Common migration pitfalls

  • Mixed OBJ versions — Linking TP6 units with TP7-built units can produce subtle ABI mismatches. Clean rebuild from source.
  • Directive inheritance — Unit A’s directives can affect units that use it; a stray {$R-} in a deeply included file can disable range checks project-wide.
  • Overlay entry points — Overlays require far calls; if {$F-} is set where overlay code is invoked, near calls hit the wrong segment and crash.
  • BGI driver paths — TP7 may look for .BGI files in different locations; verify InitGraph and driver loading.
  • FPU detection{$N+} assumes FPU present; on 8086/8088, use {$N-} or runtime detection to avoid invalid opcode traps.
  • Map file drift — After migration, diff the new map against the baseline. Segment order and symbol addresses may shift; large or unexpected changes warrant investigation. If overlay segment names or orders change, overlay load addresses will differ—ensure overlay manager configuration matches the new map.

Where this series goes next

You asked for practical depth, so this series now has dedicated companion labs:

Full series index

If you want the next layer, I recommend one additional article focused only on calling-convention lab work with map-file-backed stack tracing across Pascal and assembly boundaries.

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