VX/SOR - OpenVMS SORT MERGE
Overview
VX/SOR is a complete reimplementation of the OpenVMS SORT/MERGE utility on Linux. It ships two cooperating parts: the callable SOR$ Sort/Merge run-time library that OpenVMS BASIC, COBOL, FORTRAN and C programs call directly, and the SORT and MERGE DCL verbs that parse the VMS command line and drive that same library. Existing calls to SOR$ routines and existing DCL sort scripts move across without changing sort logic.
Under the hood VX/SOR is a classic external merge sort: records accumulate into an in-memory block, each full block is sorted and written to a SORTWORK swap file, then the swap files are combined with a fast buffered k-way merge. The /KEY, /SPECIFICATION, STABLE and NODUPLICATES behaviour matches VMS, and the qualifiers the engine acts on produce identical results to OpenVMS.
<!-- Webflow embed (min). Source: sor-page-technical/sor-tech-body-embed-dark.html → python3 website/embeds/minify_webflow_embed.py … -->
<div class="s7-tech-embed" data-vxsord-body-embed="1" data-technical-mega-embed="1" lang="en"><h2 id="architecture">What VX/SOR is</h2><p>
VX/SOR is two cooperating things inside the VX/RT runtime. Together they replace the
OpenVMS SORT/MERGE utility for migrated applications on Linux, at both the API and the
command-line level.
</p><div class="table-wrapper"><table><thead><tr><th>Component</th><th>Role</th><th>Status</th></tr></thead><tbody><tr><td><code>SOR$</code> callable library</td><td>The Sort/Merge run-time library compiled into <code>libvxrt.a</code>. One public entry point per module. This is the engine every other part sits on, and the API that OpenVMS BASIC, COBOL, FORTRAN and C programs call directly.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SORT</code> / <code>MERGE</code> DCL verbs</td><td>Thin command front-ends that parse the VMS SORT/MERGE command line and drive the <code>SOR$</code> API. They are callers of the library, not a separate engine.</td><td><span class="ac-live">live</span></td></tr></tbody></table></div><h6>The sort engine</h6><p>
The engine is a classic external merge sort. Records are accumulated into an in-memory
logical block; each full block is sorted and written to a <code>SORTWORK</code> swap file;
the swap files are then combined with a fast buffered k-way heap merge (a legacy N-way
merge remains as a fallback). This is the same shape as the OpenVMS Sort/Merge engine, so
result ordering, duplicate handling and stability all match.
</p><style>.s7-tech-embed .sor-flow {margin: 1.5rem 0;padding: 16px;border: 1px solid var(--s7t-border);border-radius: 10px;background: rgba(255, 255, 255, 0.01);}.s7-tech-embed .sor-flow-inputs,.s7-tech-embed .sor-flow-choices {display: grid;grid-template-columns: repeat(2, minmax(0, 1fr));gap: 10px;}.s7-tech-embed .sor-flow-node {min-width: 0;padding: 14px 16px;border-radius: 8px;background: var(--s7t-surface);color: var(--s7t-text);}.s7-tech-embed .sor-flow-node strong,.s7-tech-embed .sor-flow-node span {display: block;}.s7-tech-embed .sor-flow-node strong {margin-bottom: 4px;}.s7-tech-embed .sor-flow-node span {color: var(--s7t-muted);font-size: 0.9em;line-height: 1.45;}.s7-tech-embed .sor-flow-stage {border-top: 3px solid var(--s7t-accent);background: var(--s7t-surface2);}.s7-tech-embed .sor-flow-output {border: 1px solid var(--s7t-note-bd);background: var(--s7t-note-bg);}.s7-tech-embed .sor-flow-label {margin: 8px 0;color: var(--s7t-accent);font-family: var(--s7t-mono);font-size: 0.76em;font-weight: 700;letter-spacing: 0.08em;text-align: center;text-transform: uppercase;}.s7-tech-embed .sor-flow-split {display: grid;grid-template-columns: minmax(0, 1.5fr) minmax(180px, 0.7fr);gap: 10px;}.s7-tech-embed .sor-flow-side {border: 1px dashed var(--s7t-border);}.s7-tech-embed .sor-flow-sequence {display: grid;grid-template-columns: repeat(4, minmax(0, 1fr));gap: 10px;}.s7-tech-embed .sor-flow-note {margin-top: 10px;color: var(--s7t-muted);font-size: 0.9em;line-height: 1.45;}@media (max-width: 800px) {.s7-tech-embed .sor-flow-inputs,.s7-tech-embed .sor-flow-choices,.s7-tech-embed .sor-flow-split,.s7-tech-embed .sor-flow-sequence {grid-template-columns: 1fr;}}</style><div class="sor-flow" aria-label="VX/SOR external merge sort topology"><div class="sor-flow-inputs"><div class="sor-flow-node"><strong>SORT or MERGE</strong><span>VX/DCL command front end</span></div><div class="sor-flow-node"><strong>Direct <code>SOR$</code> calls</strong><span>Migrated application API</span></div></div><div class="sor-flow-label">Both inputs converge</div><div class="sor-flow-node sor-flow-stage"><strong><code>SOR$</code> library in <code>libvxrt.a</code></strong><span>Accumulates records into an in-memory logical block</span></div><div class="sor-flow-label">When a logical block is full</div><div class="sor-flow-split"><div class="sor-flow-node sor-flow-stage"><strong>Sort the block</strong><span>Write the sorted run to work storage</span></div><div class="sor-flow-node sor-flow-side"><strong><code>SORTWORK0..9</code></strong><span>Side output: swap files for sorted runs</span></div></div><div class="sor-flow-label">After all blocks are written</div><div class="sor-flow-node sor-flow-stage"><strong>k-way heap merge</strong><span>Combines every sorted run in order</span></div><div class="sor-flow-label">Produces</div><div class="sor-flow-node sor-flow-output"><strong>Output file</strong><span>Final ordered records with duplicate and stability rules applied</span></div></div><p>
Because both the DCL verbs and direct <code>SOR$</code> callers reach the same library, a
migrated estate can keep its DCL sort scripts and its compiled programs that call the sort
API, with no rewrite of either.
</p><h2 id="callable-api">The SOR$ callable API</h2><p>
The callable interface is ten public entry points. Each routine is defined once as the
real C++ function and wired to its VMS-style aliases, so existing object references resolve
and existing source compiles unchanged. Applications that call these routines migrate to
Linux without changing their sort logic.
</p><h6>Routine table</h6><div class="table-wrapper"><table><thead><tr><th>Routine</th><th>Purpose</th><th>Status</th></tr></thead><tbody><tr><td><code>SOR$INIT_SORT</code></td><td>Prepare a sort, treating key offsets as 1-based, then forward to BEGIN_SORT. This is the only functional difference from BEGIN_SORT.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$BEGIN_SORT</code></td><td>Initialise the context: build the key table, validate options, allocate the logical-block, duplicate and merge buffers, and generate SORTWORK names.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$PASS_FILES</code></td><td>Register one input file (wildcards expanded through the VMS parse/search path) and optionally the output file. Up to 10 input files per sort.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$RELEASE_REC</code></td><td>Push one record into the sort. RECORD process type only.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$SORT_MERGE</code></td><td>Run the back end: file-driven read, sort the trailing block, k-way merge, and optional direct write to the output file.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$RETURN_REC</code></td><td>Return the next sorted record; end-of-file status is returned once the stream is drained.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$END_SORT</code></td><td>Release the context, unlink the SORTWORK files, and free the specification tree.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$SPEC_FILE</code></td><td>Parse a VMS Sort specification file or string (FIELD, KEY, CONDITION, INCLUDE, OMIT, DATA, COLLATING_SEQUENCE) into a specification node tree.</td><td><span class="ac-live">live</span></td></tr><tr><td><code>SOR$STAT</code></td><td>Return one sort statistic selected by a code. A subset of codes is wired; unrecognised codes return zero with a success status.</td><td><span class="ac-v1">partial: subset of codes</span></td></tr><tr><td><code>SOR$DTYPE</code></td><td>Register a user compare routine for one of ten user-defined key data types.</td><td><span class="ac-v1">live: see alias note</span></td></tr></tbody></table></div><h6>Name forms and aliasing</h6><p>Every routine is exposed under several names, so callers from any supported language link against it:</p><div class="kw-grid"><div>SOR_D_xxx (C++ symbol)</div><div>SOR$xxx (upper-case VMS)</div><div>sor$xxx (lower-case VMS)</div><div>F4_SOR_D_xxx (FORTRAN thunk)</div></div><p><code>SOR_D_xxx</code> is the canonical internal C++ symbol. <code>SOR$xxx</code> and
<code>sor$xxx</code> are the VMS-style upper and lower-case aliases. <code>F4_SOR_D_xxx</code>
is a FORTRAN thunk that unpacks trailing string-length arguments, provided for the
descriptor-taking routines (PASS_FILES, RELEASE_REC, RETURN_REC and SPEC_FILE).
</p><div class="callout warn"><strong>Alias note for SOR$DTYPE.</strong> The user-compare registration routine is
currently exported under the upper-case alias <code>SYR$DTYPE</code> rather than
<code>SOR$DTYPE</code>. Until this is corrected, call <code>SYR$DTYPE</code> or the
lower-case <code>sor$dtype</code>; the routine itself works normally.
</div><h6>Record-interface example</h6><p>
The RECORD interface releases records one at a time and pulls sorted records back until
end-of-file. This example sorts on a 10-byte text key at 1-based position 1, the account
number in a banking record:
</p>
<pre>#include "sor$routines.h"
#include "ssdef.h"
U32 ctx = 0; <span class="cmt">/* 0 => allocate a context slot */</span>
U16 lrl = 80; <span class="cmt">/* longest record */</span>
U32 opt = SOR$M_STABLE; <span class="cmt">/* stable sort */</span>
U16 keybuf[1 + 4] = {
1, <span class="cmt">/* one key descriptor */</span>
DSC$K_DTYPE_T, <span class="cmt">/* key_type = text */</span>
0, <span class="cmt">/* key_order = ascending */</span>
1, <span class="cmt">/* key_offset = 1-based (INIT_SORT) */</span>
10 <span class="cmt">/* key_length = 10-byte account number */</span>
};
<span class="cmt">/* 1. initialise (INIT_SORT: offsets are 1-based) */</span>
U32 st = <span class="kw">SOR$INIT_SORT</span>(keybuf, &lrl, &opt, 0, 0, 0, 0, 0, &ctx);
if (!(st & 1)) return st;
<span class="cmt">/* 2. release records (RECORD process only) */</span>
struct dsc$descriptor rec_d;
<span class="cmt">/* ... fill rec_d for each ACCOUNTS record ... */</span>
st = <span class="kw">SOR$RELEASE_REC</span>(&rec_d, &ctx); <span class="cmt">/* repeat per input record */</span>
<span class="cmt">/* 3. run the sort/merge */</span>
st = <span class="kw">SOR$SORT_MERGE</span>(&ctx);
if (!(st & 1)) { SOR$END_SORT(&ctx); return st; }
<span class="cmt">/* 4. pull sorted records back until end-of-file */</span>
char out[80];
U16 olen;
struct dsc$descriptor out_d; <span class="cmt">/* points at out[] */</span>
while ((st = <span class="kw">SOR$RETURN_REC</span>(&out_d, &olen, &ctx)) == SS$_NORMAL) {
<span class="cmt">/* ... consume out[0..olen) ... */</span>
}
<span class="cmt">/* st == SS$_ENDOFFILE here */</span>
<span class="cmt">/* 5. always tear down */</span>
<span class="kw">SOR$END_SORT</span>(&ctx);</pre>
<p>
The file-driven alternative is what the DCL verbs use: call <code>SOR$PASS_FILES</code> once
per input file, then <code>SOR$BEGIN_SORT</code>, then <code>SOR$SORT_MERGE</code> (which
reads the inputs and writes the output file directly), then <code>SOR$END_SORT</code>, with
no release or return loop.
</p><h6>Pluggable sort engine</h6><p>
Each entry point first checks for a replacement engine. On the first call it loads a
customer-supplied shared library named by the <code>SORLIB</code> logical or environment
variable and looks up the matching lower-case <code>sor$xxx</code> symbol. If it is present,
the call is forwarded to that engine and the in-tree engine is bypassed, so a site can drop
in an alternative sort implementation without relinking the runtime.
</p><h2 id="data-model">Key and record definitions</h2><p>
A developer driving the callable API needs to understand three things: how keys are
described, what options control the sort, and the limits the context enforces. All three
match the OpenVMS Sort/Merge data model.
</p><h6>Key buffer</h6><p>
Keys are described by a compact unsigned 16-bit array. The first element is the number of
key descriptors; each descriptor is then four elements: data type, order, offset and length.
</p>
<pre>key_buffer[0] = N, the number of key descriptors
key_buffer[1 + 4*i + 0] = key_type (a DSC$K_DTYPE_* code)
key_buffer[1 + 4*i + 1] = key_order (0 = ascending, 1 = descending)
key_buffer[1 + 4*i + 2] = key_offset (0-based for BEGIN_SORT, 1-based for INIT_SORT)
key_buffer[1 + 4*i + 3] = key_length</pre>
<p><code>SOR$INIT_SORT</code> treats offsets as 1-based and decrements each one before
forwarding to BEGIN_SORT, so an offset of 0 there is a fatal bad-key error. If no key is
supplied at all, BEGIN_SORT synthesises a single ascending text key spanning the whole
record. A context can hold up to 50 keys.
</p><h6>Per-record header</h6><p>
Inside the work buffer each record is prefixed with a packed header. The sequence number is
what makes STABLE sorts stable: records that compare equal on all keys break the tie on
insertion order.
</p>
<pre>typedef struct {
U32 recnum; <span class="cmt">// duplicate flag = recnum |= 0x80000000</span>
U32 seqnum; <span class="cmt">// preserves insertion order for STABLE</span>
U16 ctx_num;
U16 reclen;
off_t offset_into_swap_file;
char data[0];
} sort_header_t;</pre>
<h6>Context limits</h6><div class="table-wrapper"><table><thead><tr><th>Constant</th><th>Value</th><th>Meaning</th></tr></thead><tbody><tr><td><code>MAX_CONTEXT</code></td><td>64</td><td>Concurrent sort contexts</td></tr><tr><td><code>MAX_INPUT_FILES</code></td><td>10</td><td>Input files per sort</td></tr><tr><td><code>MAX_SORTWORK</code></td><td>11</td><td>SORTWORK swap-file slots</td></tr><tr><td><code>MAX_COND</code></td><td>100</td><td>Conditions per specification node</td></tr><tr><td>keys per context</td><td>50</td><td>Maximum key descriptors</td></tr><tr><td>default page / swap I/O</td><td>4 MiB</td><td>In-memory block and swap-file I/O unit</td></tr></tbody></table></div><p>
A context is referenced by a 32-bit handle. Passing a context value of 0 on the first call
allocates a free slot from the 64 available.
</p><h6>Options and process types</h6><p>
The options bitmask controls sort behaviour. Only three bits are legal; any other bit is
rejected. The default when no options are supplied is STABLE.
</p><div class="table-wrapper"><table><thead><tr><th>Option</th><th>Meaning</th></tr></thead><tbody><tr><td><code>STABLE</code></td><td>Preserve insertion order for records with equal keys (default).</td></tr><tr><td><code>NOSIGNAL</code></td><td>Accepted for compatibility; the engine returns conditions as values and never signals.</td></tr><tr><td><code>NODUPS</code></td><td>Drop records that are equal on all keys.</td></tr></tbody></table></div><p>
Setting STABLE and NODUPS together is rejected as an exclusive-option error. The process
type can be RECORD (the default), TAG, INDEX or ADDRESS: all four are accepted by
BEGIN_SORT, but <code>SOR$RELEASE_REC</code> handles RECORD only, so TAG, INDEX and ADDRESS
are reached through the file interface (<code>SOR$PASS_FILES</code>).
</p><h6>Statistics</h6><p><code>SOR$STAT</code> returns one statistic selected by a code. The counters below are
wired; the remaining OpenVMS codes (nodes, initial runs, merge order, work allocation, merge
buffer count) return zero with a success status, so a missing statistic is a zero rather than
an error.
</p><div class="table-wrapper"><table><thead><tr><th>Statistic</th><th>Returned value</th></tr></thead><tbody><tr><td><code>SOR$K_IDENT</code></td><td>Implementation identifier</td></tr><tr><td><code>SOR$K_REC_INP</code></td><td>Records released to the sort</td></tr><tr><td><code>SOR$K_REC_SOR</code></td><td>Records actually sorted</td></tr><tr><td><code>SOR$K_REC_OUT</code></td><td>Records returned</td></tr><tr><td><code>SOR$K_LRL_INP</code></td><td>Largest input record length</td></tr><tr><td><code>SOR$K_LRL_OUT</code></td><td>Largest output record length</td></tr><tr><td><code>SOR$K_MRG_PASSES</code></td><td>Number of merge passes</td></tr></tbody></table></div><h2 id="dcl">SORT and MERGE from the command line</h2><p>
VX/SOR defines <code>SORT</code> and <code>MERGE</code> as VX/DCL verbs that replicate the
VMS command syntax. They parse the qualifiers, expand VMS wildcards on the input files, map
the output specification, and then drive the same <code>SOR$</code> library described above.
<code>MERGE</code> is identical to <code>SORT</code> except that it opens the output to append
or create rather than supersede, and it expects inputs that are already sorted on the same
keys.
</p><h6>Command syntax</h6>
<pre>$ <span class="kw">SORT</span>/KEY=(POSITION:1,SIZE:10) [/DESCENDING] [/STABLE] [/NODUPLICATES]
[/STATISTICS] [/SPECIFICATION=RULES.SPC] ACCOUNTS.DAT SORTED_ACCOUNTS.DAT
$ <span class="kw">MERGE</span>/KEY=(POSITION:1,SIZE:10) BRANCH_EAST.DAT,BRANCH_WEST.DAT ACCOUNTS.DAT</pre>
<h6>Key sub-keywords</h6><p>
The <code>/KEY</code> clause is where most of the sort is defined. The keywords below change
the key descriptor and behave exactly as on OpenVMS:
</p><div class="table-wrapper"><table><thead><tr><th>Keyword</th><th>Effect</th></tr></thead><tbody><tr><td><code>POSITION</code></td><td>1-based key start position; a value below 1 is a key-position error.</td></tr><tr><td><code>SIZE</code></td><td>Key length in bytes.</td></tr><tr><td><code>NUMBER</code></td><td>Key ordinal.</td></tr><tr><td><code>ASCENDING</code> / <code>DESCENDING</code></td><td>Sets the sort direction.</td></tr><tr><td><code>CHARACTER</code></td><td>Text key (the default data type).</td></tr><tr><td><code>D_FLOATING</code>, <code>F_FLOATING</code>, <code>G_FLOATING</code>, <code>H_FLOATING</code></td><td>The VAX floating-point key data types.</td></tr><tr><td><code>PACKED_DECIMAL</code></td><td>Packed decimal key data type.</td></tr></tbody></table></div><p>
For source compatibility, VX/SOR also parses and consumes a further set of <code>/KEY</code>
sub-keywords that have no effect on the sort, so existing command lines carry across without
edits. These accepted-for-compatibility keywords are:
</p><div class="kw-grid"><div>BINARY</div><div>DECIMAL</div><div>ZONED</div><div>SIGNED</div><div>UNSIGNED</div><div>LEADING_SIGN</div><div>TRAILING_SIGN</div><div>OVERPUNCHED_SIGN</div><div>SEPARATE_SIGN</div><div>ONYOMI</div><div>KUNYOMI</div><div>BUSHU</div><div>SOKAKU</div><div>KOKUGO</div><div>KANA8BIT</div><div>JISCODE</div></div><h6>Command-level qualifiers</h6><div class="table-wrapper"><table><thead><tr><th>Qualifier</th><th>Behaviour</th><th>Status</th></tr></thead><tbody><tr><td><code>/KEY=(...)</code></td><td>Builds the key table and drives BEGIN_SORT.</td><td><span class="ac-live">acted on</span></td></tr><tr><td><code>/STABLE</code></td><td>Preserves insertion order for equal keys.</td><td><span class="ac-live">acted on</span></td></tr><tr><td><code>/[NO]DUPLICATES</code></td><td>The negated form drops records equal on all keys.</td><td><span class="ac-live">acted on</span></td></tr><tr><td><code>/STATISTICS</code></td><td>Prints records read, sorted and output plus elapsed and CPU time.</td><td><span class="ac-live">acted on</span></td></tr><tr><td><code>/SEQUENTIAL</code></td><td>Sets the output file organisation to sequential.</td><td><span class="ac-live">acted on</span></td></tr><tr><td><code>/SPECIFICATION=file</code></td><td>Parses a specification file that drives fields, conditions and include/omit rules.</td><td><span class="ac-live">acted on</span></td></tr><tr><td><code>/PROCESS=(...)</code></td><td>Parsed into RECORD/TAG/ADDRESS/INDEX but not forwarded to the engine.</td><td><span class="ac-v1">accepted for compatibility</span></td></tr><tr><td><code>/FORMAT=(...)</code></td><td>FIXED/VARIABLE/CONTROLLED and the size keywords parse but do not change the sort.</td><td><span class="ac-v1">accepted for compatibility</span></td></tr></tbody></table></div><h6>Specification files</h6><p>
A specification file lets you drive the sort declaratively instead of on the command line.
<code>/SPECIFICATION</code> reads FIELD, CONDITION and INCLUDE/OMIT directives (handled by
<code>SOR$SPEC_FILE</code>), so you can define fields and record-selection conditions in one
place and reuse them across jobs.
</p><h6>Statistics output</h6><p>The <code>/STATISTICS</code> qualifier prints a banner after the run:</p>
<pre> VX/SOR Sort and Merge Utility v<Version_Id>
Records read: <n>
Records sorted: <n>
Records output: <n>
Elapsed time (Secs) <n>
Elapsed CPU (Secs) <f></pre>
<h2 id="compatibility">Compatibility and status</h2><p>
VX/SOR targets Linux. The callable API and the DCL verbs are compatible with OpenVMS
Sort/Merge for the features an application actually relies on to sort and merge records. The
matrix below is an honest account of what is live, what is partial, and the handful of areas
that are accepted for compatibility but do not change results.
</p><div class="table-wrapper"><table><thead><tr><th>Capability</th><th>Status</th><th>Note</th></tr></thead><tbody><tr><td>Ten callable <code>SOR$</code> routines</td><td><span class="ac-live">live</span></td><td>All built into the VX/RT runtime library.</td></tr><tr><td><code>SORT</code> / <code>MERGE</code> DCL verbs</td><td><span class="ac-live">live</span></td><td>Full VMS command-line parsing, driving the same engine.</td></tr><tr><td>External merge sort with fast k-way merge</td><td><span class="ac-live">live</span></td><td>Legacy N-way merge retained as a fallback.</td></tr><tr><td><code>/SPECIFICATION</code> file parser</td><td><span class="ac-live">live</span></td><td>FIELD, CONDITION and INCLUDE/OMIT directives.</td></tr><tr><td>STABLE, NODUPLICATES options</td><td><span class="ac-live">live</span></td><td>Default is STABLE; STABLE with NODUPS is rejected as on VMS.</td></tr><tr><td><code>SOR$STAT</code> counters</td><td><span class="ac-v1">partial</span></td><td>Core record and length counters are wired; other codes return zero.</td></tr><tr><td>User data-type compare via DTYPE</td><td><span class="ac-v1">partial</span></td><td>Works, but currently exported as <code>SYR$DTYPE</code> (see the callable API section).</td></tr><tr><td>TAG / INDEX / ADDRESS process types</td><td><span class="ac-v1">partial</span></td><td>Reachable through the file interface; <code>SOR$RELEASE_REC</code> handles RECORD only.</td></tr><tr><td><code>/PROCESS</code>, <code>/FORMAT</code> qualifiers</td><td><span class="ac-v1">accepted</span></td><td>Parsed for compatibility; values do not change the sort.</td></tr><tr><td><code>NOSIGNAL</code> option</td><td><span class="ac-v1">accepted</span></td><td>Accepted; the engine returns conditions as values and never signals.</td></tr><tr><td>Windows build</td><td><span class="ac-no">not offered</span></td><td>VX/SOR runs on Linux.</td></tr></tbody></table></div><h6>Behaviour notes for a migrating team</h6><ul><li>Register a user compare routine through <code>SYR$DTYPE</code> or the lower-case <code>sor$dtype</code> until the upper-case <code>SOR$DTYPE</code> alias is corrected.</li><li>TAG, INDEX and ADDRESS sorts are driven through <code>SOR$PASS_FILES</code>, not through the release/return record loop.</li><li>The <code>NOSIGNAL</code> option and the disk-allocation arguments are accepted but have no observable effect, because the engine reports every condition as a returned status value.</li><li><code>/PROCESS</code> and <code>/FORMAT</code> at the DCL layer parse cleanly and are ignored, so existing command lines run without edits.</li></ul><h2 id="quickref">Quick reference</h2><h6>Call lifecycle</h6><div class="sor-flow" aria-label="VX/SOR callable API lifecycle"><div class="sor-flow-sequence"><div class="sor-flow-node sor-flow-stage"><strong><code>INIT_SORT</code> or <code>BEGIN_SORT</code></strong><span>Initialize the sort context</span></div><div class="sor-flow-node sor-flow-stage"><strong>Supply input</strong><span><code>RELEASE_REC</code> x N or <code>PASS_FILES</code> x N</span></div><div class="sor-flow-node sor-flow-stage"><strong><code>SORT_MERGE</code></strong><span>Sort records and merge runs</span></div><div class="sor-flow-node sor-flow-stage"><strong><code>RETURN_REC</code> x N</strong><span>Read until end-of-file, then call <code>END_SORT</code></span></div></div><div class="sor-flow-note">
Call <code>SPEC_FILE</code> or <code>DTYPE</code> before <code>BEGIN_SORT</code>.
Call <code>STAT</code> at any time after initialization.
</div></div><h6>DCL syntax</h6>
<pre>$ SORT/KEY=(POSITION:1,SIZE:10) [/DESCENDING] [/STABLE]
[/NODUPLICATES] [/STATISTICS] [/SPECIFICATION=RULES.SPC] in.dat out.dat
$ MERGE/KEY=(POSITION:1,SIZE:10) in1.dat,in2.dat out.dat</pre>
<h6>Callable routines</h6><div class="kw-grid"><div>SOR$INIT_SORT</div><div>SOR$BEGIN_SORT</div><div>SOR$PASS_FILES</div><div>SOR$RELEASE_REC</div><div>SOR$SORT_MERGE</div><div>SOR$RETURN_REC</div><div>SOR$END_SORT</div><div>SOR$SPEC_FILE</div><div>SOR$STAT</div><div>SOR$DTYPE</div></div><h6>Key buffer</h6>
<pre>[N, {type, order, offset, length} x N]
INIT_SORT offsets 1-based; BEGIN_SORT offsets 0-based.</pre>
<h6>Tuning environment</h6><div class="table-wrapper"><table><thead><tr><th>Variable</th><th>Effect</th></tr></thead><tbody><tr><td><code>SOR_PAGE_SIZE</code></td><td>In-memory logical-block size (bytes, above 1024; default 4 MiB).</td></tr><tr><td><code>SOR_SWAP_IO_SIZE</code></td><td>Swap-file I/O unit and maximum record size (bytes, above 1024; default 4 MiB).</td></tr><tr><td><code>SORTWORK0..9</code></td><td>Placement of the SORTWORK swap files.</td></tr><tr><td><code>VXSOR_MERGE_ENGINE</code></td><td>Set to <code>legacy</code> to force the older N-way merge.</td></tr><tr><td><code>SORLIB</code></td><td>Names a replacement sort engine shared library.</td></tr></tbody></table></div><h6>Limits</h6><div class="kw-grid"><div>64 contexts</div><div>10 input files</div><div>11 SORTWORK files</div><div>50 keys per context</div><div>100 conditions per spec node</div></div></div>