{ .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 -- 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. | +------------------------------------------------------------------------------------------------------------------------------+ }}