ARM code for beginners

Line up in line



 

    Since I've been having /so/ much fun writing interesting patches over the last few months, I thought I'd finish off with one I've been meaning to write for a while.  Try putting !LineUp into your !Boot.Choices.PreDesk folder; you may get no effect, but load a few applications (Impression / ArtWorks / Newsbase) to see what it is doing.

    One of the great underused features of RISC OS 3 is the facility to arrange icons on the icon bar by a 'priority'.  RISC OS 2 only allowed you to specify 'left' or 'right', and it created the icon on the inside of all the others.  Since RISC OS 3, we can also specify a priority (from 0-&7FFFFFFF), so that it will create the icon to the left or right of any icons with a lower priority.

    The two important values that are fed to Wimp_CreateIcon are the priority in R0, and the window handle in which to create the icon, which is passed in the first word of the block pointed to by R1.  The window handle for the icon bar is any one of eight values:

-1 on the left
-2 on the right
-3 on the left of the icon handle specified in R0
-4 on the right of the icon handle specified in R0
-5 high priority on the left
-6 low priority on the left
-7 high priority right
-8 low priority right

    The problem is that most applications create an icon with -2 which means in machines where memory is abundant, the icons pile up on the right and finding the right one becomes hard.

Bridge over troubled waters

    So what our patch needs to be able to do is to trap all calls to Wimp_CreateIcon and change the position and priority depending on which task has created it.  Already we're into potentially hazardous territory, but there is a module written by Andrew Clover called WimpSWIVe which allows programmers to claim Wimp SWIs easily-- it claims the same SWI chunk as the Wimp and provides a claim / release to the programmer through Wimp_RegisterFilter; the documentation and module are on the disc inside the !LineUp application which I'll assume you'll read.

    The other problem to think about is how to configure the module; we need to feed it a list of tasks that create icons, and the appropriate positons and priorities for these.  I decided to put this information into a tab-separated text file (which I wrote before any code); take a look at the format of the !LineUp.Preferences file.  This needs to be read in during the initialisation code.

    Trapping Wimp_CreateIcon is a simple SWI call to WimpSWIVe; this is the easy part.  However, our trapping code needs to find out the name of the task that is calling it.  This is harder to find out than it seems: firstly, the task's handle isn't stored anywhere obvious.  Because we're intercepting one of the caller's SWIs, our code is assumed to know the task's handle since it pretends to be part of the caller's code.  The solution to finding the task handle is to use Wimp_SendMessage in an odd fashion: if we send reason code 19, UserMessageAcknowledge, a code which is normally used either to stop a broadcast message being passed to any further tasks, or to stop a message that required acknowledgement from 'bouncing' back to its sender.  Either way, it has the neat properties of filling in the sender's handle (i.e. the task we're intercepting) in the message block (at offset 4), and not generating any Wimp events.  If we fill in a dummy message code (doesn't matter which) and send one of these messages, it will return immediately with the sender's handle at R1+4.

    Our next problem is finding the task name from this handle: TaskManager_TaskNameFromHandle seems like an obvious one, and I was puzzled why it didn't work.  The problem is that the task manager relies on Wimp messages to keep its list of tasks up to date; usually when a task creates its icon bar icon, it has called Wimp_initialise to register itself, but the message which this SWI sends to register itself with the task manager hasn't reached the task manager because the task hasn't yet called Wimp_Poll which would deliver the message.  The only solution is to keep our own list of task names and handles by post-trapping Wimp_Initialise (where the name and handle are in R2 and R1 respectively, and pre-trapping Wimp_Closedown where the dying task's handle is passed in R0.  See the box-out if you're confused about the differences between pre-trapping and post-trapping.

    The initialisation code, then, claims 2048 bytes of workspace, traps Wimp_CreateIcon, _Initialise and _CloseDown and loads the configuration file into the module workspace, and the finalisation code merely releases the relevant SWIs and workspace.
 
 

CRCs for fun and profit

    This month's module contains a trick which will save some considerable fiddling concerning string matching: it involves a mathematical process called a CRC (cyclic redundancy check).  You don't need to know how it works (and I don't), but the idea is that a CRC value is a short 'checksum' value for any area of memory.  The algorithm for producing the checksum is such that two areas of memory are highly unlikely to have the same one, though this is not guaranteed.

    OS_CRC is used to produce 16-bit CRC values; this is not ideal, but quicker than writing a CRC routine from scratch or some boring string matching code.  It hasn't caused problems so far... anyhow, to use it, you pass the CRC continuation value in R0 (0 for all our purposes), the start and end memory addresses in R1 and R2 respectively, and the increment (STEP value) in R3, which should be 1 for comparing strings.  The CRC is then output to R0, which we can use as a quick check instead of comparing every byte of both strings.  It also saves on memory, since for every task name we read in, we can manipulate and compare a single word, which can be stored in a register, rather than the whole task name.

    The only loop we used when employing OS_CRC (loops being irritating things to manage under ARM code, as you maybe have noticed) is to count the length of the string to CRC; OS_CRC has a more general purpose and won't work on a zero-terminated string.
 
 
Our module's workspace

2K is claimed and released in the initialisation and finalisation code with OS_Module 6 and 7 respectively (see article 7), and the pointer stored in the module's private wort (R12).  Its format is like this:

List of icons to rearrange

R0 + 0    -- CRC of task name to match
R0 + 4    -- position on icon bar, negated (i.e. 5, 6 instead of -5, -6)
R0 + 8    -- priority value
   ... repeat ...
          -- -1 terminates list

List of tasks running

R0 + 1024 -- CRC of task name
R0 + 1028 -- task handle
   ... repeat, but no terminator this time ...

R0 + 1968 -- scratch space for Wimp_SendMessage and OS_GBPB reads
 


 
Errata

Since the program was written a few days before this article, a few problems have come to light:
 

  • there is no bounds checking on how many items should be in each list, so the list of trappable icons could overwrite the list of running tasks if it grew beyond 85;
  • due to the second list not being properly managed (no terminator), if a task dies near the start of the list, the hole it leaves will stop the search routine going any further once it hits the hole; any tasks registered after this will not have their icons trapped;

The show must go on

    Don't let your ARM code stop with these articles; I've tried over the last few months to fire your imagination with what you can do with small amounts of code written by hand.  I'd never advise using any more ARM code than you have to, however; if there's a lot of data processing to be done, do it in a high-level language and call pieces of ARM code for the speed-critical parts.  But if you /are/ feeling masochistic, the 'pebble-by-pebble' approach can pay dividends: see Sibelius for a staggering example of a lightning-fast, entirely hand-coded WIMP application.  A quote to bear in mind, from Larry Wall, the author of the PERL language: 'The three principle virtues of a programmer are Laziness, Impatience and Hubris'.  Don't ever do more than you have to.