TriOS-alt/zubehör/sphinx/hive-port/linker/link.spin

616 lines
21 KiB
Plaintext

{
.sob linker
2009
April
10 Successfully linked two test programs
15 SOB1 format:
#args in exported pubs now just one byte
added byte to imports for possible future use with object "pointers"
rearranged fields in SOB file header so that longs are long-aligned in memory
June
9 Sphinxified
timestamp comparison
12 Removed /s and /t options
18 Don't stop on first out-of-date error.
To do:
removal of duplicate sobs
Usage: link sobname [options]
sobname is name of .sob file, with or without .sob suffix.
Options start with / or - and are case-insensitive:
/v <n> -- Sets verbosity level. Higher values of n => more verbose. Default is 0.
SOB file format
0 4 bytes: SOB file format version #
4 4 bytes: OBJ timestamp or version
8 4 bytes: hash of OBJ's binary code.
12 2 bytes: NUMEXPORTS
14 2 bytes: EXPORTSIZE
16 2 bytes: NUMIMPORTS
18 2 bytes: IMPORTSIZE
20 2 bytes: OBJBINSIZE (this is always a multiple of 4)
22 1 byte: checksum
23 1 byte: reserved
24 2 bytes: size of OBJ's VAR space.
26 EXPORTSIZE bytes: exported symbols: CONs, PUBs, maybe PRIs.
IMPORTSIZE bytes: OBJ's sub-OBJs.
OBJBINSIZE bytes: the compiled OBJ: header followed by methods.
Export record format
name + null + 0 + 4-byte int CON int
name + null + 1 + 4-byte float CON float
name + null + 2 + index + #args PUB
name + null + 3 + index + #args PRI (not exported)
Import record format
name + null + 2-byte count + reserved
Export and import names are uppercase. Import name must not include ".SOB" or other suffix.
}
SIZEOFSOBHEADER = 26
obj
term: "isxtv"
fs[2]: "sxfile"
str: "stringx"
pub Main | err
err := \Try
if err
if err > 0
term.str( err )
else
term.str( string("Error ") )
term.dec( err )
term.out( 13 )
fs[0].Close
fs[0].Open( string("sphinx.bin"), "R" )
fs[0].Execute( 0 )
dat
STACKSPACE long 500 ' in bytes; must be multiple of 4
TABLESPACE long 1000 ' in bytes; must be multiple of 4
WORKSPACE long 0
verbosity byte 0
outOfDate byte 0
ignoreOutOfDate byte 0
pri Try | nArgs, pTable, pWork, l, p, totalVarSize
fs.Open( string("args.d8a"), "R" )
nArgs := fs.ReadByte
ifnot nArgs--
abort string("usage: link sobname [options]")
fs.ReadStringUpperCase( @sobName, MAXFILENAMELENGTH )
l := strsize( @sobName )
if sobName[l-4] == "." and sobName[l-3] == "S" and sobName[l-2] == "O" and sobName[l-1] == "B"
sobName[l-4]~
if strsize( @sobName ) > 8
term.str( @sobName )
abort string(" -- sobname too long")
str.Copy( @outputName, @sobName )
' Process command line arguments
repeat while nArgs--
fs.ReadStringUpperCase( @stringBuffer, 20 )
if stringBuffer[0] == "-"
stringBuffer[0] := "/"
if strcomp( @stringBuffer, string("/V") )
ifnot nArgs--
abort string("/V must be followed by a number")
verbosity := fs.ReadNumber
elseif strcomp( @stringBuffer, string("/I") )
ignoreOutOfDate~~
{
elseif strcomp( @stringBuffer, string("/S") )
ifnot nArgs--
abort string("/S must be followed by a number")
STACKSPACE := fs.ReadNumber
if STACKSPACE & 3
abort string("/S argument must be a multiple of 4")
elseif strcomp( @stringBuffer, string("/T") )
ifnot nArgs--
abort string("/T must be followed by a number")
TABLESPACE := fs.ReadNumber
if TABLESPACE & 3
abort string("/T argument must be a multiple of 4")
}
'else
'ignore
fs.Close
if verbosity => 1
term.str( string("link 090627", 13) )
pTable := word[$000a] + STACKSPACE
pWork := pTable + TABLESPACE
WORKSPACE := 32768-pWork
if verbosity => 2
term.dec( WORKSPACE )
term.str( string(" bytes of work space", 13) )
if WORKSPACE =< 0
abort string("No work space")
SobInit( pTable, TABLESPACE )
AddSob( @sobName )
ProcessSob( pTable, true )
checksum~
totalVarSize := ComputeAddressAndTotalVarSize( pTable, $0010 )
word[@header][3] := $0010
word[@header[$08]] := objBinEndAddress
word[@header[$0a]] := objBinEndAddress + totalVarSize + 8
word[@header[$0c]] := firstPubOffset + $0010
word[@header[$0e]] := word[@header[$0a]] + firstPubLocalsSize + (firstPubNumArgs + 1) << 2
AddToChecksum( @header, $10 )
AddToChecksum( @footer, 8 )
header[5] := -checksum
if verbosity => 3
p := pTable
repeat while p
term.str( word[p +_pName] )
term.out( " " )
term.hex( word[p +_startAddress], 4 )
term.out( " " )
term.dec( word[p +_totalVarSize] )
term.out( " " )
term.hex( byte[p +_checksum], 2 )
term.out( 13 )
p := word[p +_pNextSorted]
if not outOfDate or ignoreOutOfDate
str.Append( @outputName, string(".BIN") )
if verbosity => 2
term.str( string("Writing ") )
term.str( @outputName )
term.out( 13 )
WriteBinaryFile( @outputName, pTable, pWork, WORKSPACE )
else
term.str( string("No .bin written", 13) )
pri WriteBinaryFile( pFilename, pSob, pBuff, buffsize ) | n, p, pImports, pCounts, interObjectOffset, varOffset
{{
Go down the priority-sorted list of sobs assigning them hub addresses such that they follow one
another in memory. On the way back up the list, compute each sob's total VAR size (the sob's
own VARs plus its imported sobs' VARs.)
Also updates checksum.
Return value is the current sob's total VAR size. Only the top object's return value is looked at.
}}
fs[0].Open( pFilename, "W" )
fs[0].Write( @header, 16 )
repeat while pSob
str.Copy( @stringBuffer, word[pSob +_pName] )
str.Append( @stringBuffer, string(".SOB") )
if verbosity => 2
term.str( @stringBuffer )
term.str( string(" -- copying", 13) )
n := word[pSob +_objBinSize]
if n > buffsize
abort string("work area too small")
fs[1].Open( @stringBuffer, "R" )
fs[1].SkipBytes( SIZEOFSOBHEADER + word[pSob +_exportImportSize] )
fs[1].Read( pBuff, n )
fs[1].Close
p := pBuff + byte[pBuff][2] << 2 ' byte[2] is index of 1st obj entry; multiply by 4 bytes/entry
varOffset := word[pSob +_varSize]
pImports := word[pSob +_pImports]
pCounts := word[pSob +_pCounts]
repeat word[pSob +_nImports]
interObjectOffset := word[ word[pImports] +_startAddress] - word[pSob +_startAddress]
repeat word[pCounts]
word[p] := interObjectOffset
p += 2
word[p] := varOffset
p += 2
varOffset += word[ word[pImports] +_totalVarSize]
pImports += 2
pCounts += 2
fs[0].Write( pBuff, n )
pSob := word[pSob +_pNextSorted]
fs[0].Close
{{
objBinEndAddress := address ' end address will point just beyond the last obj in hub memory.
' Here we're just overwriting as we go and keeping the last one.
ComputeAddressAndTotalVarSize( word[p +_pNextSorted], address )
checksum += byte[p +_checksum] ' this is the partial checksum of the obj (doesn't count its sub-object table because
' sub-object links are not known until link time (i.e., now))
totalVarSize := word[p +_varSize]
pImports := word[p +_pImports]
pCounts := word[p +_pCounts]
repeat word[p +_nImports] ' for each import, add the import's VAR size multiplied by its count
interObjectOffset := word[ word[pImports] +_startAddress] - word[p +_startAddress]
repeat word[pCounts]
AddToChecksum( @totalVarSize, 2 ) ' checksum needs to include this half of an object table entry
AddToChecksum( @interObjectOffset, 2 ) ' and this other half of an object table entry
totalVarSize += word[ word[pImports] +_totalVarSize]
pImports += 2
pCounts += 2
word[p +_totalVarSize] := totalVarSize
}}
var
word firstPubLocalsSize
word firstPubOffset
word firstPubNumArgs
word objBinEndAddress
byte checksum
long clk_freq
long xin_freq
long clk_mode
long free
long stack
pri ReadExports( numExports ) | firstPub, type, val, index, nArgs, i, p, f, frequency
f~
firstPub~~
stack := 16
frequency := 12_000_000
repeat numExports
fs.ReadStringUpperCase( @stringBuffer, MAXEXPORTLENGTH )
case type := fs.ReadByte
0, 1: ' int or float CON
val := fs.ReadLong
2, 3: ' PUB or PRI
index := fs.ReadByte
nArgs := fs.ReadByte
if firstPub~
firstPubNumArgs := nArgs
repeat i from 0 to 4
if strcomp( @stringBuffer, @@ptrs[i] )
if type
term.str( @stringBuffer )
abort string(" -- not an int CON")
clk_freq[i] := val
f |= |< i
f &= 7
if clk_mode & 3
f |= 8
case f ' four bits: rc|clkmode|xinfreq|clkfreq
%0000: ' none of clkmode/xinfreq/clkfreq specified
%0001..%0011: abort string("_CLKMODE must be specified")
%0100: abort string("_CLKFREQ or _XINFREQ must be specified")
%0101: frequency := clk_freq
%0110: frequency := xin_freq * ((clk_mode >> 6) #> 1)
%0111: if clk_freq <> xin_freq * ((clk_mode >> 6) #> 1)
abort string("conflicting _CLKFREQ and _XINFREQ")
%1000..%1011: ' these cases shouldn't happen
%1100: ' this case is OK
%1101..%1111: abort string("RCFAST/SLOW incompatible with _CLKFREQ/_XINFREQ")
long[@header] := frequency
header[4] := ComputeClkmodeByte( clk_mode )
pri ComputeClkmodeByte( mode ) : m | b1, b2, i
{
rcfast $001 exactly one 1 in 0000_0000_00xx incompatible with clkfreq/xinfreq
rcslow $002
or
xinput $004 exactly one 1 in 0000_00xx_xx00 and requires clkfreq/xinfreq
xtal1 $008 up to one 1 in 0xxx_xx00_0000
xtal2 $010
xtal3 $020
pll1x $040
pll2x $080
pll4x $100
pll8x $200
pll16x $400
}
b1 := -1 ' b1 is the position of the single 1 in mode[5..0].
repeat i from 0 to 5
if mode & |< i
if b1 <> -1
abort string("invalid _CLKMODE") ' only one 1 allowed
b1 := i
m := lookupz( b1: $00, $01, $22, $2a, $32, $3a )
b2 := -1 ' b2 is the position of single 1 in mode[10..6] (-1 if no 1 bit)
repeat i from 6 to 10
if mode & |< i
if b2 <> -1
abort string("invalid _CLKMODE (multiple PLL)") ' only one 1 allowed
b2 := i
if b1 < 2 ' RCFAST/RCSLOW?
if b2 <> -1 ' b2 better not be set
abort string("invalid _CLKMODE (RC+PLL)")
else ' one of the X-modes
if b2 <> -1
m |= $40 ' PLLENA
m += b2 - 5
dat
ptrs word @s0, @s1, @s2, @s3, @s4
s0 byte "_CLKFREQ", 0
s1 byte "_XINFREQ", 0
s2 byte "_CLKMODE", 0
s3 byte "_FREE", 0
s4 byte "_STACK", 0
dat
long
header byte 0[16]
footer byte $ff, $ff, $f9, $ff
byte $ff, $ff, $f9, $ff
con
MAXFILENAMELENGTH = 8 + 1 + 3 ' 8.3
MAXEXPORTLENGTH = 32
var
byte stringBuffer[MAXEXPORTLENGTH+1]
byte sobName[MAXFILENAMELENGTH+1]
byte outputName[MAXFILENAMELENGTH+1]
con
#0
_pNext[2]
_pNextSorted[2]
_pName[2]
_nImports[2]
_pImports[2]
_pCounts[2]
_startAddress[2]
_objBinSize[2]
_totalVarSize[2]
_varSize[2]
_timestamp[4]
_exportImportSize[2]
_checksum[1]
_[1] ' for alignment: _SIZEOFSOBINFO must be a multiple of 4
_SIZEOFSOBINFO
SOBFILEFORMATVERSION = "S" + "O" << 8 + "B" << 16 + "1" << 24 ' "SOB1"
dat
pSobSpace word 0
SOBSSIZE word 0
pDataSpace word 0
pFirst word 0
pLast word 0
pLastSorted word 0
pri SobInit( p, size )
pFirst := pSobSpace := p
SOBSSIZE := size
pDataSpace := pSobSpace + SOBSSIZE
pLastSorted := pLast := pFirst
word[pFirst +_pNext]~
word[pFirst +_pNextSorted]~
pri ComputeAddressAndTotalVarSize( p, address ) : totalVarSize | pImports, pCounts, interObjectOffset
{{
Go down the priority-sorted list of sobs assigning them hub addresses such that they follow one
another in memory. On the way back up the list, compute each sob's total VAR size (the sob's
own VARs plus its imported sobs' VARs.)
Also updates checksum.
Return value is the current sob's total VAR size. Only the top object's return value is looked at.
}}
ifnot p
return
word[p +_startAddress] := address
address += word[p +_objBinSize]
objBinEndAddress := address ' end address will point just beyond the last obj in hub memory.
' Here we're just overwriting as we go and keeping the last one.
ComputeAddressAndTotalVarSize( word[p +_pNextSorted], address )
checksum += byte[p +_checksum] ' this is the partial checksum of the obj (doesn't count its sub-object table because
' sub-object links are not known until link time (i.e., now))
totalVarSize := word[p +_varSize]
pImports := word[p +_pImports]
pCounts := word[p +_pCounts]
repeat word[p +_nImports] ' for each import, add the import's VAR size multiplied by its count
interObjectOffset := word[ word[pImports] +_startAddress] - word[p +_startAddress]
repeat word[pCounts]
AddToChecksum( @totalVarSize, 2 ) ' checksum needs to include this half of an object table entry
AddToChecksum( @interObjectOffset, 2 ) ' and this other half of an object table entry
totalVarSize += word[ word[pImports] +_totalVarSize]
pImports += 2
pCounts += 2
word[p +_totalVarSize] := totalVarSize
pri AddToChecksum( p, n )
repeat n
checksum += byte[p++]
pri AddSob( pName ) : p | n
{{
Copies name to data area, appends name to the sobs list, returns pointer to new sob entry.
}}
if verbosity => 2
term.str(string("adding "))
term.str(pName)
term.out(13)
p := pSobSpace
pSobSpace += _SIZEOFSOBINFO
n := strsize(pName) + 1
bytemove( Alloc(n), pName, n )
word[pLast +_pNext] := p
word[pLastSorted +_pNextSorted] := p
word[p +_pName] := pDataSpace
word[p +_pNext]~
word[p +_pNextSorted]~
pLast := p
pLastSorted := p
pri Alloc( n )
pDataSpace := (pDataSpace - n) & $fffffffc ' long-aligned
if pDataSpace < pSobSpace
abort string("Insufficient sob table space")
return pDataSpace
pri FindSob( pName ) : p | pPrev
{{
Search the sob list in priority order. If a sob in the list matches name,
update priority-order links to put the sob at the end and return a pointer to it.
Otherwise, return 0.
}}
p := pPrev := pFirst
repeat while p
if strcomp( word[p +_pName], pName )
if p <> pLastSorted
word[pPrev +_pNextSorted] := word[p +_pNextSorted]
word[pLastSorted +_pNextSorted] := p
word[p +_pNextSorted]~
pLastSorted := p
return
pPrev := p
p := word[p +_pNextSorted]
return 0
pri ProcessSob( p, top ) | len, numExports, exportSize, numImports, importSize, objBinSize, hash, pStart, temp, pImports, pCounts, ts0, ts1
{{
Reads the sob file identified by p, appends the sob's imports to the sob list,
then recursively processes the imported sobs.
top is true for the top sob, false for all other sobs.
}}
Str.Copy( @stringBuffer, word[p +_pName] )
Str.Append( @stringBuffer, string(".SOB") )
if verbosity => 2
term.str( string("Reading [") )
term.str( @stringBuffer )
term.out( "]" )
term.out( 13 )
fs.Open( @stringBuffer, "R" )
if fs.Readlong <> SOBFILEFORMATVERSION ' SOB file format version
abort string("Unrecognized SOB file format")
long[p +_timestamp] := fs.Readlong ' timestamp
hash := fs.ReadLong ' hash
numExports := fs.ReadWord ' number of exports
exportSize := fs.ReadWord ' size of exports segment
numImports := fs.ReadWord ' number of imports
importSize := fs.ReadWord ' size of imports segment
word[p +_objBinSize] := fs.ReadWord ' size of bytecode segment
byte[p +_checksum] := fs.ReadByte ' checksum
fs.ReadByte ' padding
word[p +_varSize] := fs.ReadWord ' size of sob's VAR space
ts0 := long[p +_timestamp]
if top
ReadExports( numExports )
else
fs.SkipBytes( exportSize )
word[p +_exportImportSize] := exportSize + importSize
word[p +_nImports] := numImports
word[p +_pImports] := pImports := Alloc( numImports << 1 ) ' points to an array of sob pointers
word[p +_pCounts] := pCounts := Alloc( numImports << 1 ) ' points to an array of sob counts
pStart := pLast
repeat numImports
fs.ReadStringUpperCase( @stringBuffer, 8 )
ts1 := GetTimestamp( @stringBuffer )
if ts0 and ts1 ' Only compare non-zero timestamps
if ts0 =< ts1
term.str( word[p+_pName] )
term.str( string(".SOB is older than ") )
term.str( @stringBuffer )
term.str( string(".SOB", 13) )
outOfDate~~
ifnot temp := FindSob( @stringBuffer )
temp := AddSob( @stringBuffer )
word[pImports] := temp
word[pCounts] := fs.ReadWord
fs.ReadByte ' reserved byte
if verbosity => 3
term.str( word[ word[pImports] +_pName] )
term.out( "*" )
term.dec( word[pCounts] )
term.out( 13 )
pImports += 2
pCounts += 2
if top
fs.SkipBytes( $4 ) ' look into the top sob's object header
firstPubOffset := fs.ReadWord ' and get offset to first pub
firstPubLocalsSize := fs.ReadWord ' and size of first pub's locals
fs.Close
' Process the imported sobs
pStart := word[pStart +_pNext]
repeat while pStart
ProcessSob( pStart, false )
pStart := word[pStart +_pNext]
if verbosity => 2
term.str( string("done with ") )
term.str( word[p +_pName] )
term.out( 13 )
pri GetTimestamp( pFilename )
str.Append( pFilename, string(".SOB") )
fs[1].Open( pFilename, "R" )
fs[1].ReadLong
result := fs[1].ReadLong
fs[1].Close
byte[pFilename][ strsize(pFilename) - 4 ]~ ' remove .SOB tail
{{
Copyright (c) 2009 Michael Park
+------------------------------------------------------------------------------------------------------------------------------+
| TERMS OF USE: MIT License |
+------------------------------------------------------------------------------------------------------------------------------+
|Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation |
|files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, |
|modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software|
|is furnished to do so, subject to the following conditions: |
| |
|The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.|
| |
|THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
|WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
|ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
+------------------------------------------------------------------------------------------------------------------------------+
}}