Codelet reentrancy
Posted: Sun Apr 13, 2014
To fix Chuck Rock, Cannon Fodder and a few other games, codelets need to either be reentrant or execute with IRQ's disabled.
The core problem is that most codelets require working registers and to preserve the existing register they need to be stored somewhere. They can't be stacked as a stack may not exist, so they're currently stored locally within the codelet. This presents a problem if an IRQ occurs whilst a codelet is executing, which subsequently calls the codelet interrupted. As execution is likely to be in User, IRQ's can't easily be disabled which leaves few options.
Options are:
1. Use an undefined instruction to enable / disable IRQ's
2. Use an undefined instruction to stack / unstack working registers
3. Have the codelet check to see if it's already stored working registers and use a secondary working area
4. Intercept all IRQ's and allow any executing codelets to complete before serving the IRQ
5. Hypervise the IRQ and FIQ hardware vectors
(1) is the obvious choice, as it mirrors what the ARM3 would do anyway. The drawback however is that IRQ's could end up potentially disabled most of the time - something we wouldn't want when you're being prompted to insert a floppy.
(2) seems the better option, using a private stack within the JIT. At most four registers are going to be stacked at any one time, two from the codelet that was interrupted and two for any subsequent codelets.
The implication however is that the majority of codelets will cause two undefined instructions, so performance will take a big hit. This won't be noticeable on the Pi, but will impact StrongARM quite heavily. Each undefined instruction will trigger a jump to the Und vector, two CPU mode changes and X instructions to both decode and execute the instruction.
(3) although this sounds simple, it vastly complicates the codelets. One potential workaround to simplify things, would be to duplicate each codelet and have the first handoff to the second if it's already executing.
EDIT: Added option 4. I attempted to code option 2 and soon realised it wont work for some codelets. LDM/STM for example have to repeatedly stack and unstack working registers, to preserve all registers around the original instruction.
EDIT2: Added option 5. To implement, claim the IRQ/FIQ hardware vectors and check R14 on entry, if it's within JIT codelet space, stack the codelets local variables, let the IRQ/FIQ proceed but return to us, unstack the codelets local variables and then exit as normal. If R14 is outside of JIT codelet space pass the call on. I'm not certain if FIQ would need to be hypervised, I suspect not as events that trigger codelet reentrancy tend to be VSync, T1, SSBC, EventV which all hang off IRQ.
This will require some method of 1) ascertaining if the codelet has local variables and 2) knowing where they are in the codelet.
EDIT3: A variant of 5. Claim IRQ/FIQ as above, but only store R14 and immediately pass the call on. At all interrupt driven entry points (OS_Claim, OS_ClaimDeviceDriver, OS_CallBack, OS_CallAfter, IRQ/IRQv, IOC VSync/T1/SSBC) cache the variables of the codelet being interrupted.
Suggest altering the codelet header structure to include 4 variable slots and then walk the codelet tree to find the codelet interrupted. This could be sped up by walking up/down depending on the R14 address being below or above the halfway mark or allocated codelet space.
The core problem is that most codelets require working registers and to preserve the existing register they need to be stored somewhere. They can't be stacked as a stack may not exist, so they're currently stored locally within the codelet. This presents a problem if an IRQ occurs whilst a codelet is executing, which subsequently calls the codelet interrupted. As execution is likely to be in User, IRQ's can't easily be disabled which leaves few options.
Options are:
1. Use an undefined instruction to enable / disable IRQ's
2. Use an undefined instruction to stack / unstack working registers
3. Have the codelet check to see if it's already stored working registers and use a secondary working area
4. Intercept all IRQ's and allow any executing codelets to complete before serving the IRQ
5. Hypervise the IRQ and FIQ hardware vectors
(1) is the obvious choice, as it mirrors what the ARM3 would do anyway. The drawback however is that IRQ's could end up potentially disabled most of the time - something we wouldn't want when you're being prompted to insert a floppy.
(2) seems the better option, using a private stack within the JIT. At most four registers are going to be stacked at any one time, two from the codelet that was interrupted and two for any subsequent codelets.
The implication however is that the majority of codelets will cause two undefined instructions, so performance will take a big hit. This won't be noticeable on the Pi, but will impact StrongARM quite heavily. Each undefined instruction will trigger a jump to the Und vector, two CPU mode changes and X instructions to both decode and execute the instruction.
(3) although this sounds simple, it vastly complicates the codelets. One potential workaround to simplify things, would be to duplicate each codelet and have the first handoff to the second if it's already executing.
EDIT: Added option 4. I attempted to code option 2 and soon realised it wont work for some codelets. LDM/STM for example have to repeatedly stack and unstack working registers, to preserve all registers around the original instruction.
EDIT2: Added option 5. To implement, claim the IRQ/FIQ hardware vectors and check R14 on entry, if it's within JIT codelet space, stack the codelets local variables, let the IRQ/FIQ proceed but return to us, unstack the codelets local variables and then exit as normal. If R14 is outside of JIT codelet space pass the call on. I'm not certain if FIQ would need to be hypervised, I suspect not as events that trigger codelet reentrancy tend to be VSync, T1, SSBC, EventV which all hang off IRQ.
This will require some method of 1) ascertaining if the codelet has local variables and 2) knowing where they are in the codelet.
EDIT3: A variant of 5. Claim IRQ/FIQ as above, but only store R14 and immediately pass the call on. At all interrupt driven entry points (OS_Claim, OS_ClaimDeviceDriver, OS_CallBack, OS_CallAfter, IRQ/IRQv, IOC VSync/T1/SSBC) cache the variables of the codelet being interrupted.
Suggest altering the codelet header structure to include 4 variable slots and then walk the codelet tree to find the codelet interrupted. This could be sped up by walking up/down depending on the R14 address being below or above the halfway mark or allocated codelet space.