Early mid February extensions to WarpLink
This is not actually a regulation Sunday blog post as I didn't prepare it on the HP 95LX, due to the place I usually write these being closed for the carnival days.
This week I prepared some new extensions to the WarpLink OMF linker, most of which was originally written in 1989 through 1993, and released to the public domain in 1999. I have been working on WarpLink since 2023.
The new extensions I did as yet are split into three categories:
wlemitalign, sections which emit their alignment even if they are empty or part of the nobits trail.
wlcalc, post-link calculations that are applied to the executable file after the main linking is done.
wllist, allowing to collect patch lists pointing into certain segments without having to manually update the lists.
lDOS kernel
lmacros
lmacros1.mac: As stated in the changeset message,
delete the bytes() smacro because it interfered with a warning message in lDebug. There are no known users of this smacro and its counterpart, frombytes(), which both were no-op passthroughs.
WarpLink
Add wlcalc_word_seg_ operation, which creates a segment relocation. Note that this adds the relative segment value (build/link time) to the word value in the usual wlcalc step, but applies a segment relocation to this spot. The relocation is
written in an earlier pass so that it gets included into the relocation table. However, the actual resolving of the relocation of course happens at run/load time. Because this relocation is always applied to a word, only a word-size variable is allowed for this type of calculation.
Support wlcalc PASS after the size, because the SEG operation doesn't support putting the PASS keyword after the segment name. To avoid misinterpreting "ADD" operations as hexadecimal literals, this use of PASS only supports decimal numbers. Like the other PASS keyword, the pass number is limited to 16 bits and the default pass is pass 0.
-
Add a bunch of .COM file switches. These are intended to replace the x2b2 utility by specifying WarpLink's /C switch (create a .COM file) along with some of the following:
/CZ: Accept entry point of (only) 0:0 rather than 0:256, and do not cut the 256-byte prefix off the executable image.
/CS: Ignore a stack if present.
/CB: Allow a big file (exceeding 64 KiB).
/CL=: Allow relocations, with the given number as a relocation factor to apply.
Add wlcalc SEGREL operation. This adds the relative segment value at link time, but does not create a relocation. This is expected to be needed for porting lDebug to build with WarpLink. Unlike the SEG operation, any variable size (1 to 4 bytes) is allowed.
wlcalc MINUSSEGREL operation: This
subtracts the relative segment value at link time. It would be possible to emulate this using SUBR 0, SEGREL, SUBR 0 but it is much nicer to add this operation type.
Disable listing of files included by an
%include directive. As stated in the changeset message, this shrinks the trace listing (.tls) file from 8.7
MB to 3.2
MB, and it makes it much less annoying to search for labels that occur in the macro files. Scriptlets to generate the change are included in the changeset message too.
-
-
-
-
Begin preparations for wllist support. Increase ecm release to r10a. Create wllist temporary list file and object file, and save the temporary object file's pathname to the object name list. Write the wllist-prefixed labels to stdout (unconditionally). Write a MODEND record to the temporary object file, before it is opened in pass 1 of the linker.
-
-
Fix the so_xl function, it shouldn't chain to so_xc. The so_xl code wasn't enabled yet and won't be for several dozen more changesets after this one.
Add /XS switch, which instructs the linker not to delete the temporary files it creates, to help with debugging them. It also displays the pathnames used for the files, at the point it would otherwise delete them.
Add the Translator comment record to the temporary object file. The record is stored within the mlcredit.nas source text file so as to simplify updating the release number string that's part of the record.
-
-
Detect segment LNAMES for the wllist N labels, and write unique new LNAMES to the object file. "N" stands for "New list" and an N label must include an "S" key letter followed by a segment name, then an "L" key letter followed by a list name. The segment name is terminated by NUL,
?, or
_. The list name is terminated by NUL or
?. Also increments ecm release to r10c. Adds I/O read functions to read back from the object file, and makes it so all read functions come in two variants: One that treats
EOF (short reads) as an error, and the other that allows the caller to decide how to treat
EOF.
-
-
Create wllist SEGDEF records. So far these indicate a size of 0 bytes. The creation is managed by two nested loops. The outer loop finds temporary list file entries with twlListUsed of zero and a twlSegmentIndex that's nonzero. The inner loop starts behind the currently processed temporary list file entry, and finds entries with twlListUsed of zero and a twlSegmentIndex matching the LNAMES index of the currently processed entry. In both loops, the matching entries have their twlListUsed set to 1 and the twlSegmentIndex updated from LNAMES indices to SEGDEF indices.
Create wllist GRPDEF records. This is the first user of the second part of the file buffer, above the TEMPWLLIST structure at offset zero. It matches temporary list file entries with twlGroupIndex nonzero and twlListUsed zero. An inner loop finds the SEGDEF records that correspond to the G label's "S" key letters, and adds their SEGDEF indices to the GRPDEF record being built in the second buffer part.
-
Fix to add a cpu 8086 directive to the nasm.mac macro file. Avoids assembling near jcc into the 386+ single-instruction form, falling back to the 8086-compatible dual-instruction form.
-
-
Use equates for the twlListUsed status. This byte can take on the values twlUsedInitial (written during pass 1 processing of all non-temporary object files), twlUsedNewSeg (to indicate a new list specifier with a segment LNAMES or SEGDEF index), or twlUsedNewGroup (to indicate a group specifier with a group LNAMES or GRPDEF index).
-
-
Add the twlSegdefSizeOffset dword field to the TEMPWLLIST structure. For any twlUsedNewSeg entry that refers to a SEGDEF, this points to the word-sized "segment size" field within the SEGDEF record in the object file. This is needed because the segment size must be updated by later loops.
-
Enumerate the twlUsedInitial wllist entries matching the list names of all twlUsedNewSeg entries, and check that no twlUsedInitial entries remain unprocessed after that loop. During enumeration, the used labels are displayed to stdout with a "Used wllist label:" prefix. Increase ecm release to r10d.
-
Support the TEMP= variable as a fallback if TMP= isn't defined, and set the default path for the temporary files to
.\ (only used if no /TL=, /TO=, TMP=, or TEMP= applies). Increment ecm release to 10e.
-
-
-
Create EXTDEF records for all list items. List items are here identified by temporary list file entries that still have a twlUsedInitial value in the twlListUsed field. A single EXTDEF record can hold multiple EXTDEF names, up to the second buffer part size or 1024 bytes, whichever is smaller. The EXTDEF record is partially written within the second buffer part and flushed to the object file either when too full or after the loop finishes. Adds the twlExtdefIndex word field to the TEMPWLLIST structure, which overlaps with twlSegdefSizeOffset as they aren't used at the same time.
-
Optimisation: Emit multiple LNAMES into the same record rather than creating a single record per. After every
.enter_lname call, the partial record is temporarily completed and written to the object file at the last prepared position. The
.prepare_lname function updates the last prepared position variable.
Emit mix of LEDATA and FIXUPP records. Turns out, a FIXUPP record always references the very prior LEDATA record. (Page 44 of the
OMF: Relocatable Object Module Format document, "Each subrecord in a FIXUPP object record either defines a thread for subsequent use, or refers to a data location in the nearest previous LEDATA or LIDATA record.") So in case of multiple LEDATA records it isn't valid to write all LEDATA records first and then all FIXUPP records. Multiple FIXUP entries will be written to the same FIXUPP record.
-
Extract three functions for working with PUBDEF records. This is the
.prepare_one_pubdef,
.index_one_pubdef, and
.finish_one_pubdef functions. (The "one" in their name indicates that they're used to create a single PUBDEF per record, rather than possibly compacting several ones.)
Create a PUBDEF record for the list end. This re-uses all three PUBDEF functions. It uses the
?ledatabase variable after all list entries have been collected. The name that it writes is the list name with ".END" appended.
Create a PUBDEF record for the list amount. This re-uses two of the PUBDEF functions, writing four zero bytes for the group, segment, and frame fields. It uses the total LEDATA size determined from the first
.inner_collect call, shifted right by 1 bit. The name it writes is the list name with ".AMOUNT" appended.
-
Implement repetition. This is special in that a thrice nested loop occurs, calling
.inner_collect_partial from within
.emit_ledata which can be called by
.collect_list (itself a callback called by
.inner_collect). The most deeply nested loop is being called with the current seek and current
?seektempinner value preserved on the stack, calling
.collect_repeated_ledata to initialise the data of the LEDATA record that is currently being built. This function has a special exit condition: If the current LEDATA record is fully initialised, then it discards a word off the stack (its near return address into the innermost loop) before returning. So, it returns to the loop caller rather than the loop itself.
Implement repetition step width. The "W" (twlWidth) key letter must occur after an "R" key letter. For repetition, it sets the step width of the repeated entries, which defaults to 1.
-
-
Add the /XLD switch, which can be used to display debugging output of the wllist facility. The debugging output now defaults to not being displayed. One /XLD displays the "Used wllist label:" lines, while two or more /XLD switches also display the messages in mlpass1a.nas for every wllist label entered into the temporary list file.
Fix wllist debug output, specifically the "Used wllist label:" messages. DX was no longer valid at the point the display happened. Also, the display occurs earlier in
.collect_list now.
lDebug
I am planning on porting lDebug so it can build as a number of object files then linked by WarpLink. Both wlcalc and wllist will be needed for this, along with the absolute $ trick to enter a nobits section mode in NASM where the nobits section overlaps with a progbits section.