FutureBasic Logo

<<    Index    >> FutureBasic 5

Appendix M - Endian Issues   appendix



Endian Issues

The original FutureBasic Compiler, specifically the compiler prior to FBtoC, always produced traditional Mac big-endian PPC code. On an Intel Mac, such apps are run in Rosetta and thus keep their big-endian property. FBtoC can produce either big-endian or little-endian or both, depending on the Architecture setting (PPC, Intel, Universal).

Many FutureBasic programs, when run in little-endian mode for the first time, turn out to have bugs. Endian bugs affect multibyte numeric variables: short, long, pointer, single and double (along with synonyms: SInt16, SInt32, ptr...). Strings and 1-byte numeric vars (char, unsigned byte, UInt8...) are not affected directly by endianness.

Byte order in memory

As an illustrative example, consider how we might display the most- and least-significant byes of a short variable.
dim as short myShortVar
dim as byte lsByte, msByte
myShortVar = 1
print , "msByte", "lsByte"
... rest of program follows later...

Output (on Intel Mac):
                  msByte    lsByte
buggy method         1         0
fix 1                0         1
fix 2                0         1
fix 3                0         1
fix 4                0         1
Old code that assumes big-endian format :-(
print "buggy method",
msByte = peek( @myShortVar )     // endian bug on Intel
lsByte = peek( @myShortVar + 1 ) // endian bug on Intel
print msByte, lsByte
Patch a copy of the byte-peeking code to work little-endian, then set things up so the patch gets used on Intel (only).
The magic constant _LITTLEENDIAN can be used.
Con: conditional compilation makes code hard to read, understand and maintain.
print "fix 1",
#if def _LITTLEENDIAN // Intel
msByte = peek( @myShortVar + 1 ) // byte 1
lsByte = peek( @myShortVar ) // byte 0
#else // PPC
msByte = peek( @myShortVar ) // byte 0
lsByte = peek( @myShortVar + 1 ) // byte 1
#endif /* def _LITTLEENDIAN */
print msByte, lsByte
Byte-swap the data, do our calculation, then swap it back again.
CFSwapXxxxHostToBig() and CFSwapXxxxBigToHost() swap on Intel but not on PPC.
Pro: doesn't use conditional compilation
Con: remember to swap back again
print "fix 2",
include "Tlbx CFByteOrder.incl"
myShortVar = fn CFSwapInt16HostToBig( myShortVar )
// myShortVar is now certainly big-endian
// so we can safely use "buggy method"'s code
msByte = peek( @myShortVar ) // rescued from endian bug on Intel
lsByte = peek( @myShortVar + 1 ) // rescued from endian bug on Intel
myShortVar = fn CFSwapInt16BigToHost( myShortVar ) // restore previous byte order
print msByte, lsByte
Convert the data into a string, character sequence, text stream etc, which are inherently endian safe
Pro: doesn't use conditional compilation or byte-swapping
Con: slow, obfuscated
print "fix 3",
dim as Str255 tempString
defstr long
tempString = hex$( myShortVar )
msByte = val&( mid$( tempString, tempString[0] - 4, 2 ) )
lsByte = val&( right$( tempString, 2 ) )
print msByte, lsByte
Write endian-safe code in the first place.
Pro: shifting and masking are endian safe and very fast, so for this problem, fixes 1-3 above are redundant.
Con: not always possible
print "fix 4",
msByte = myShortVar >> 8
lsByte = myShortVar and 0x00FF
print msByte, lsByte
Byte order on disk: data
// string format on disk; endian safe
print #fileNum, anything
input #fileNum, anything

// written via the FBtoC runtime as big-endian on disk
// automatically swapped by the runtime to little-endian read on Intel
write #fileNum, shortVar, longVar, singleVar, doubleVar, int64Var // endian safe
read #fileNum, shortVar, longVar, singleVar, doubleVar, int64Var // endian safe

write #fileNum, anyRecordVar // on disk, has endianness of writer host; potential endian bug
read #fileNum, anyRecordVar // byte order on disk preserved in reader host memory; potential endian bug

write file #fileNum, address, numBytes // on disk, has endianness of writer host; potential endian bug
read file #fileNum, address, numBytes // byte order on disk preserved in reader host memory; potential endian bug
Byte order on disk: resources

Standard system-defined resource types (e.g. STR#, moov, MENU, etc) are big-endian on disk. System-supplied 'resource flippers' automatically byte-swap on an Intel Mac, in both read and write directions, during any call to any call to the relevant Resource Manager functions. Standard resource types are thereby made to take on the endianness of the host, and all your resource management code should just work, unchanged, on Intel. (Inept coding on your part, though, could get the ResType wrong, so that you ask for a 'voom' resource).

Custom resources have a potential endian bug.

Swapping floating point values in an array
include "Subs FloatByteSwapping.incl"
dim as single value(9)
dim as long j, n

// read big-endian array in one chunk from disk
n = 10
read file #1, @value(0), n*sizeof( single )

// make it host-endian
for j = 0 to n - 1
value(j) = fn SwapSingleBigToHost!( value(j) )
next
// value array is now host-endian, ready for use
//...

// make it big-endian again
for j = 0 to n - 1
value(j) = fn SwapSingleHostToBig!( value(j) )
next

// write big-endian array in one chunk to disk
write file #1, @value(0), n*sizeof( single )
SwapDoubleBigToHost#() and SwapDoubleHostToBig#() are available for swapping doubles similarly. This code works in PPC and Intel, FutureBasic versions 4 or 5. If your FutureBasic project is FBtoC-only, you can remove the ugly #! suffices from the swapping functions.

Core Foundation/Foundation Framework

Use of Core Foundation both in memory and to read/write to files handles any endian issues automatically. The XML-style files ( key/value coding, aka: KVC and commonly referred to as property list files ) created are by definition endian safe because they are architecture-independent.

Reference

Xcode User Guide: Universal Binary Programming Guidelines
Xcode User Guide: Swapping Bytes
Xcode User Guide: Byte-Order Utilities Reference