Handling platform-specific issues in a program is a complex and demanding task. Most of the issues to do with the architecture end up affecting the code in three ways:
· CPU word size and arithmetic capabilities affects the type selection,
· System memory map and CPU speed affects the program architecture, especially the choice of algorithm at various points, and
· Interlocking and synchronisation of critical code segments requires precise control over CPU capabilities, particularly interrupts.
The art of design is slicing, and one of the criteria that feeds into the decision of where to slice a system into components is the desire to isolate platform-dependent code into drivers so that most components of the program can we written in a more generic and reusable fashion. This results in "boots-and-all" decisions: either a module is generic and must be written without any platform-dependent code, or the module is by design platform-dependent (e.g. a device driver) and may use nonportable code in its implementation (but not its interface).
The CPU arithmetic characteristics and register model mostly affect the types offered by the compiler. In this case, the family of types built in CompDef provide speed/space trade-offs without requiring platform-specific details.
The memory map (memory available) and CPU speed direct what algorithms are used in the application. Resource trade-offs may also affect the size of data structures such as queues. In both cases, these choices would best be centralised into a single module that operated very early in the program initialisation and directs how the program is instantiated. Platform fills this role -- it does not implement the algorithms itself, but does understand the environment and is able to make reasonable choices, and plug in the appropriate module.
The program main entry point is in Platform, so that the environment can be configured without needing to intercept the operation of the control logic. Platform also participates in the Modula-2 discipline applied to C: Almost all modules publish an Init function, and Platform executes these calls on behalf of each module. This Init function is a direct translation of the BEGIN..END block that may be included in Modula-2 modules. Incidentally, this is also why most modules publish both an Init function and a Start function: Init is constrained to have no parameters and no return value, whereas Start isn't restricted. Also, Start calls may assume that all modules have had time to prepare via Init , and hence inter-module links such as registration functions may be called. Referencing outside modules is prohibited during execution of Init (with the exception of Platform-supplied facilities?)
One major example of Platform in action was in the original DOS release of Grouse Grep: during development, the match engine was implemented both in C and in handcrafted assembly. The C version was used to prototype and to debug the state tables and actions, and the assembly version was updated to shadow (but also to substantially outperform) the C version once the details were settled. The nature of the interface published by MatchEng was such that the rest of the program did not know or care which version was used; only Platform needed to be changed (and this could be done by a command-line switch, if necessary) -- good program structure did not need to be sacrificed to gain performance, if the system was sliced up into modules the right way.
Platform contains routines to display almost all the output generated by the rest of the program, except for error and debugging output. This is done so that the code can quickly move to other environments as required, and so that some of the GNU/Linux-specific items in the output -- particularly highlighting the matching text -- can be easily isolated and managed as the program is ported. (Use the -H switch to highlight matching text.)
In the GNU/Linux release, Platform also provides a couple of miscellaneous services. One is an interface to obtain the program name specified on the command line, including editing the name to remove the path if necessary. Another is a "small malloc" service: Since malloc(2) runs relatively slowly, a module-based approach can run slowly if it results in many calls to malloc from disparate places. The small malloc service overcomes this by using a single largish call to malloc to obtain some memory, and then parcelling out this memory in pieces to callers in a light and fast fashion as required. Much of the speed comes from abandoning the ability to free the memory when it's no longer in use: This property is dangerous if abused, as it can lead to damaging memory leaks. In Grouse Grep this is an acceptable risk, mainly as grep runs in a transient instead of a permanent program, and the increase in speed is worthwhile, especially where the remainder of the program strives so hard to be light and fast.
ProgramName -- Report the name used to invoke us SmallMalloc -- Optimised malloc for small areas SmallFree -- Free space issued by SmallMalloc
Display -- Display matching line DisplayHighlighted -- Display line, highlighting matching text DisplayFilename -- Display file name and match details main -- Set up platform-specific things before invoking program