All your PCs are belong to us!

PC is short for Program Counter. The PC is a register inside the CPU that always contains the address of the current instruction that is executed (prefetches and caches aside). On Intel machines, it is called the IP (Instruction Pointer), but the idea is the same.

When retargeting, we replace the PC register of the emulator with the native PC of the target processor. This is one of the main speed gains that we get from simulating and retargeting.

Unfortunately this means, that pretty much every assembler instruction that may modify the PC unexpectedly requires some human interaction to be translated. Below is a list of PC related instructons, and how we translate them.


Jump instructions (B Lxxxxxxxx)

Well, obviously, jump instructions modify the PC. SInce NewtonOS was mistly written in C++, simple branches either stay within function boundaries and are translated into C's goto with the target instruction labeled approriately, or they jump to the start of a different function. These jumps were originally return instructions that were optimizes to abuse the target functions return instruction.

NewtonOS has a little surprise for us here. Most function calls within the ROM jump to their destination via a huge jump table that is mapped elsewhere, then jumping back to the originally intended code. This little trick makes it easy to replace ROM code when the need arises (the infamouns Y10K bug was fixed using these tables). These jump tables appear unneccessary when retargeting (I hope I am right about this), so we code around those for simplicity.


Call instructions (BL Lxxxxxxxx)

These instructions call a subroutine. We do the same in retargeted C. At this point, we do not care about caller argument lists or return values. We simply use a global register bank in the same way as it is used in the emulator.


Return instructions (MOV PC, LR)

We simplify greatly by assuming that LR actually still contains the original retun address. This is pretty much always true, but I will consider putting code into the calling instruction that verifies a correct PC.


Special return instructions (MOVS PC, LR)

Here is an evil one! Just one more letter, and the instruction does something entirely different - at least in C. This instruction returns from differen CPU mode, an interrupt, an SWI, etc. . NewtonOS uses this instruction at the end of every SWI to switch tasks if the scheduler is instructed so. The correct translation should use ucontext.h context switching to simulate the cooperative multitasking in NewtonOS.

Solve this and the retargeting is solved!


Pop from stack (ldmdb r11, {..., pc})

Keeping the return address in LR is only useful for short subroutines. The original C compiler stores the LR in the stackframe (push) and later restores the value directly into PC (pop). They could restore the LR and call mov pc, lr, but that is just a waste of time and space. For now, I assume that a pop pc instruction simply does return from a subroutine. But again, the caller should check the PC on return.


Calculate the PC (addls pc, pc, r1, lsl #2)

Ahh, the mind boggeling stuff one can do with a RISC CPU. This instruction is usually preceded by a range chek for R1 and followed by a jump table. In the original code, these were switch-case instructions. In the retargeted code, they will be again simplified switch-case constructs. Luckily there are not many of these in the ROM.

ACHTUNG: there are calculated PC's with other shift values than 2. These are all in the OS core and were most likely not generated by the compiler, but hand-written in ARM code. These things must be transcoded by hand.


Hardware interrupts (FIQ, IRQ)

After every instruction, a FIQ or IRQ can occur, triggered by the hardware, by a timer, some I/O port or whatever else. We will need to handcode context switching here, either usung ucontext again, or with some blocking threading. The NewtonOS was never meant to run on more than one CPU core and is noth safe for preemtive multitasking or true multithreading!


Software interrupts (SWI, unknow instruction)

The SWI again changes context. Transcodeing SWI's is not too bad, except for a thing mentioned earlier: at the end of every SWI, NewtonOS allows for task switching by the cooperativ mutitasking scheduler. This is currently the big roadblock, although I beleive I have understood how it works.

Unknown instruction interrupt OTOH are easy: they are used to emulate the floating point instructtion set of later ARM CPU's. For now, these can be translated into simple function calls since we have no replacement code form the JIT, but should eventually be replaced with C floating point instructions for performance reasons.


Access violations (MMU)

Uuuum, yeah, this is one of these complex things that were used originally to make RAM use minimal in the OS. NewtonOS would not allocate RAM when asked, but instead just mark some area as "signal on access". Marking an area does not take any resources. NewtonOS marks stacks that are 32kB and larger without allocating a single byte. Once the stack grows, access violations are generated which in turn schdule a new task that will allocate true RAM in 1kB steps, then schedule new tasks until the RAM resource is found. Then the previouly failing instrcution is executed again, this time with memory made available. If no RAM was available anymore, an exception is triggered. WHich brings us to...


Exceptions (setjmp, longjmp)

Luckily, setjmp and longjmp use a function call in the ROM code, so it can be relatively easily tranlated into C code. I have not looked at NewtonOS exception handling, but I am positive that it will work in a similar way.


Function pointers and virtual functions

Function pointer must be translated from which ever address they point to in ROM ito the function they should point to in the translated code. We need to find all these possible places where function pointer are used and the use a lookup table for all available functions. This will of course fail if there if unknown code links into the OS form outside. The only solution is to retarget that code as well. Known code would be inside the Network tools and maybe a handful of 3rd party apps.

Virtual functions are pretty much the same as function pointers, using a vtable to find the right function for a give call to a virtual method. The location of all vtables is know, and all target functions can be found and translated.


That's it?

I don't know yet. I will find out if these are all the issues, or if there are additional creative PC amnipulations that need special attention.