The null-selector fix in the 2ine fork unblocks gspac/gspopt/gspcg, so document the full pipeline: build 2ine, extract the tools, the four C stages, absolute placement, and the firmware round-trip. Record the two gotchas found while testing: gspcl still stops after gspac because the children exit 139 at teardown, and GSPA 5.01 rejects b15, so the 32 KB round-trip differs at one SP access (same physical register, A15/B15).
8.6 KiB
Running the TMS340 r5.01 toolchain under 2ine
This is the working no-VM route to TI's 1990 TMS340 Family Code
Generation Tools r5.01. The tools are 16-bit OS/2 1.x NE console programs;
the patched fork of 2ine at
https://git.wut.ee/opencode.energia.arti.ee/2ine-tms34010 runs them on a
64-bit Linux host.
Unlike the assembler-only state documented when the fork was first published,
the full C pipeline works: front end, optimizer, code generator,
assembler, linker and converter. gspcl (the one-command driver) still does
not chain its children reliably; drive the four C stages explicitly.
Verified 2026-11-01 on x86_64 Arch, against the VT1200 firmware image.
| tool | result |
|---|---|
gspac (C front end) |
works |
gspopt (intermediate optimizer) |
works |
gspcg (code generator) |
works |
gspa (COFF assembler) |
works — 11,852-line firmware disassembly, no errors |
gsplnk (linker) |
works — absolute placement; rts.lib + flib.lib |
gsprom (COFF → Tek-hex) |
works — plain and -i EPROM pair |
gspar (archiver) |
works — lists rts.lib members |
gspcl (driver) |
partial — runs gspac, then stops (see §7) |
1. Build 2ine
The fork's main carries the three fixes this needs: the null-selector
mapping (commit 91b78f2), Arch/CMake-4 ncurses discovery (a440128), and
this document's status update (3bcdfc3).
Dependencies (Arch):
sudo pacman -S --needed base-devel cmake lib32-glibc lib32-gcc-libs \
lib32-ncurses sdl2-compat
Debian/Ubuntu names:
sudo apt install build-essential cmake libncurses-dev libncurses-dev:i386 \
libc6-dev-i386 gcc-multilib
Build 32-bit. On CMake 4.x the policy floor must be passed explicitly:
git clone https://git.wut.ee/opencode.energia.arti.ee/2ine-tms34010
cd 2ine-tms34010
cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
cmake --build build -j8
Result: build/lx_loader (32-bit), build/lx_dump, and build/lib*.so.
2. Extract the r5.01 tools
Disk 1 is the executables, disk 2 the headers, runtimes and library sources. Extract them anywhere:
cd /path/to/vt1200
SDK="tools/tms34010-sdk/1990-11 TMS340 Family SDK"
mkdir -p os2-501/bin os2-501/lib os2-501/work
unzip -o "$SDK/1990-11-19 TMS340 Family Code Generation Tools (1 of 2) r5.01 rev2564062-1641.zip" -d os2-501/bin
unzip -o "$SDK/1990-11-19 TMS340 Family Code Generation Tools (2 of 2) r5.01 rev2564062-1641.zip" -d os2-501/lib
3. Prepare the run directory
lx_loader dlopen()s the native modules as ./lib<module>.so, so the
working directory matters. The OS/2 tools also cannot open Unix absolute
paths, so keep sources, objects and libraries in the same directory and pass
bare relative names.
cd os2-501/work
ln -sf /path/to/2ine-tms34010/build/lib*.so .
cp ../bin/*.exe .
cp ../lib/rts.lib ../lib/flib.lib .
The tools consume their intermediate .if and .opt files on success — even
with -k — so regenerate them for every build.
4. End-to-end C build
Given HELLO.C:
int twice(int n) { return n + n; }
int sum(int *p, int n) {
int s = 0;
int i;
for (i = 0; i < n; i++) s += p[i];
return s + twice(n);
}
Run each stage. LX is the absolute path to lx_loader:
LX=/path/to/2ine-tms34010/build/lx_loader
$LX ./gspac.exe -k HELLO.C HELLO.IF # C front end -> intermediate
$LX ./gspopt.exe -k -O2 HELLO.IF HELLO.OPT # optimizer -> intermediate
$LX ./gspcg.exe -m HELLO.OPT HELLO.ASM HELLO.TMP # codegen -> .asm
$LX ./gspa.exe HELLO.ASM # assembler -> HELLO.obj
$LX ./gsplnk.exe HELLO.obj rts.lib flib.lib \
-o hello.out -m hello.map # linker -> absolute COFF
$LX ./gsprom.exe hello.out hello.hex # converter -> Tek-hex
Notes:
gspawrites the object as lowercaseHELLO.obj, even though the source isHELLO.ASM. Keep the names consistent on a case-sensitive FS.-O2means level 2.-O0is the lowest level, not "off"; with no-o/-Ostage at all the optimizer is not run.- Link
flib.libas well asrts.libwhen the code pulls in_sqrt,_frexp,_ldexpand friends: the float primitives (FD$ADD,FD$DIV, …) live inflib.lib.
Placing the image at a TMS34010 bit address
gsplnk accepts a command file whose SECTIONS origin is a bit address.
COFF section sizes are in bits too (16 bits per instruction word): a .text
of 0x8000 is 4096 bytes, not 32 KB.
Write the command file with CRLF, even on Linux: with LF-only line ends the
linker merges the -m line into the next one and silently loses options.
-a
-o hello.out
-m hello.map
HELLO.obj
SECTIONS
{
.text 3F208000h: {}
}
$LX ./gsplnk.exe hello.cmd
$LX ./gsprom.exe -i hello.out hi lo # high/low EPROM pair
0x3F208000 is the VT1200's firmware base. The converter's own hex record
addresses wrap at 64 KB; take placement from the COFF file or the map, never
from the hex.
5. Assembler / linker / converter on a firmware region
The 1987 round-trip flow in harness/sdk-scratch/roundtrip.sh works with the
5.01 tools, with one source-level change (b15 → sp, §6):
# disassemble 32 KB at bit 3F208000 to RT.ASM, as roundtrip.sh does
# then, in the 2ine run directory:
$LX ./gspa.exe RT.ASM
$LX ./gsplnk.exe RT.CMD
$LX ./gsprom.exe -i rt.out rthi rtlo
# interleave low/high and compare with the original region
Result on vt1200_v22.bin, region 0x1000 / 32,768 bytes / base 3F208000:
- 1987 GSPA: 0 differing bytes,
md5 43adb6669571041985f3e26fd9ff7cb9(BYTE-EXACT) - 5.01 GSPA: 32,768/32,768 bytes, 1 differing byte at
0x33ec(3fvs2f)
The difference is the A/B register-file namespace bit of the single
rl 09h, sp in the region, not a semantic difference (§6).
6. Why b15 becomes sp, and why one byte differs
dis2as34.py emits b15 for the B-file stack pointer because GSPA 2.00
accepts b15 and uses a bare sp for the A-file form. GSPA 5.01 rejects
b15 with INVALID REGISTER VALUE; its sp encodes the A15 namespace.
The TMS34010 has one stack-pointer register, addressable as A15 or B15, so
rl 09h, sp (5.01, 2f31) and rl 09h, b15 (2.00, 3f31) address the same
register. The 5.01 object is therefore functionally identical to the ROM, but
not byte-identical at that instruction. This is the one exception to the
otherwise one-way assembler compatibility noted in OS2-501.md.
7. gspcl and the exit-status trap
gspcl finds gspac and runs it, then stops without invoking gspopt /
gspcg / gspa. The individual tools frequently exit non-zero after
successful work: gspac and gspcg return 139 (segfault during teardown),
gsprom can return 1. gspcl treats the child status as failure.
Check for output files, not exit codes. The manual pipeline in §4 produces correct objects and images; the one-command driver does not yet.
8. What the null-selector fix was
The original fork asserted in lxConvert1616to32 when the C front end passed
0003:0000. That is the OS/2 null far pointer: selector index 0 with RPL
3. The converter special-cased only addr1616 == 0, so it indexed ldt[0]
and asserted.
The fix in lx_loader.c is:
const uint16 selector = (uint16) (addr1616 >> 19);
const uint16 offset = (uint16) (addr1616 % 0x10000);
if (selector == 0) // segment 0x0000 or 0x0003: the OS/2 null selector.
return NULL;
Segment index 0 is the null selector by definition, so returning NULL is
correct, not a workaround.
9. Measured results
Bench source with a sum loop, indexed loop, switch, struct update, helper
call and bit loop (BENCH.C), compiled four ways:
| build | .text |
.obj |
.asm |
|---|---|---|---|
| no optimizer | 490 B | 950 B | 4396 B |
-O0 |
394 B | 854 B | 4497 B |
-O1 |
394 B | 854 B | 4499 B |
-O2 |
376 B | 836 B | 4236 B |
-O2 is ~23 % smaller than no optimizer on this source. Assembly of the
11,852-line firmware disassembly takes ~0.22 s wall through lx_loader
(loader startup included).
10. Reproduce
A compact end-to-end run (from os2-501/work, with the libs symlinked and the
tools copied as in §3):
LX=/path/to/2ine-tms34010/build/lx_loader
$LX ./gspac.exe -k HELLO.C HELLO.IF
$LX ./gspopt.exe -k -O2 HELLO.IF HELLO.OPT
$LX ./gspcg.exe -m HELLO.OPT HELLO.ASM HELLO.TMP
$LX ./gspa.exe HELLO.ASM
$LX ./gsplnk.exe HELLO.obj rts.lib flib.lib -o hello.out -m hello.map
$LX ./gsprom.exe hello.out hello.hex