The EightThirty Two ISA – Part 20 – 2020-04-03
I’m continuing to make gradual improvements to the EightThirtyTwo toolchain, and over the last few days I’ve given my attention to linker libraries, and endian issues.
While I initially intended EightThirtyTwo to be a big endian CPU, it occurred to me early on that I could make endian-ness configurable with a generic parameter. This I did, and having set that “littleendian” parameter to “true” for testing, I never actually changed it back, so all my testing over the last few months has actually been in little endian mode!
I recently had cause to try the CPU in a big endian context, and naturally found some bugs and issues with the toolchain – which are now fixed.
The assembler, 832a, now has a -e switch, which allows you to select between big (“b”) and little endian (“l”) modes when assembling a file. Because the vast majority of literals are built up using a chain of “li” instructions, and because references are resolved at link time, the only place where endian-ness matters in the assembler is when including constants using the .int or .short directives.
This means that if you don’t use those directives, object files are endian-agnostic – which means that I don’t need two separate versions of the startup and premain code.
The linker, too, now has a -e parameter for setting the endian-ness of the linked executable. And once again, the situations where it matters aren’t all that frequent – which, unfortunately means they can lurk undetected! The most subtle bug I uncovered was to do with char and short parameters in function calls; accessing them using the wrong storage size usually works in little-endian mode because the first byte is always the least significant. In big-endian mode this is no longer true.
Speaking of linking, creating a linker library from a bunch of object files is as simple as concatenating them. I now have a lib832 linker library containing some support functions, including division, string manipulation functions and a simple printf implementation. This uses nominal puts() and putchar() functions which write to a UART defined in my simulation testbench, and also in the EightThirtyTwoDemos projects. Symbols are resolved from left to right on the linker command line, so simply providing an alternative implementation of those two functions in an object file listed before the library is enough to re-target printf.
Functions are only included in the final executable if they’re referenced from somewhere else, starting with the first function in the first section of the first object file. Also supported are constructor functions or initialisers (marked in C source files with a __constructor(pri.name) attribute). These are sorted at link time and called in order by the startup code – but once again, are only included in the final executable if another symbol in the same object file has been referenced. This means that, for example, the malloc() function in EightThirtyTwoDemos automatically sets up the memory pool if a project uses it, but doesn’t do so needlessly for projects that don’t.