SPARCManger.mesa
Copyright Ó 1990 by Xerox Corporation. All rights reserved.
Peter B. Kessler, April 2, 1990 12:03 pm PDT
DIRECTORY
SPARCArchitecture USING [SPARCInstruction],
BreakWorldArchitecture USING [Address],
Rope USING [ROPE];
SPARCManger: CEDAR DEFINITIONS ~ {
Mangers come in (for now) five flavors:
normal for ordinary instructions (non-delayed control transfers),
branch for simple delayed control transfers,
call for call instructions,
jmplO7 for jmpl's that use %o7 as their destination (e.g. indirect calls).
jmplG0 for jmpl's that use %g0 as their destination (e.g. returns).
The tag for which kind of manger it is is kept in a ``sethi %lo(tag), %g0'' instruction in the last instruction of the manger. The tag is used to figure out how to put the original instruction back when the breakpoint is removed.
Normal instruction stream:  becomes:  normal manger:
sheep    ba,a patch  sheep
continue:   continue:   ba,a continue
-- This is the easy case, we just jump to the patch to exeucte the instruction.
Branch instruction stream:  becomes:  branch manger:
bicc target   ba,a patch  bicc target
delaySlot   delaySlot  delaySlot
continue:   continue:   ba,a continue
-- With a simple delay slot, we just jump to a copy (relocated) of the instruction and its delay slot. If the branch falls through, we just transfer back to the main code stream.
Call instruction stream:  becomes:  call manger:
call target   call patch  sethi %hi(target), %g1
delaySlot   delaySlot  jmpl %g1+%lo(target), %g0
     noop
-- The trick here is to call the patch, thus setting up the return address because the callee probably uses it (e.g. to return, or return aggregate results, etc.). We allow the instruction in the delay slot to execute (what if it messes with the return address? So be it, that's what it wanted to do.). In the manger, we move the target address to %g1 (which I think is free at this point), and then jmpl to the callee without setting the return address.
JmplO7 instruction stream:  becomes:  jmplO7 manger:
jmpl target, %o7  call patch  jmpl target, %g0
delaySlot   delaySlot  noop
-- Again, the trick here is to call the patch, thus setting up the return address because the callee probably uses it (e.g. to return, or return aggregate results, etc.). We allow the instruction in the delay slot to execute. In the manger, we jmpl to the callee without setting the return address.
JmplG0 instruction stream:  becomes:  jmplG0 manger:
jmpl target, %g0  ba,a patch  jmpl target, %g0
delaySlot   delaySlot  delaySlot
-- ``jmpl target, %g0'' has a standard abbreviation of just ``jmp target'', and there are standard abbreviations of ``ret'' and ``retl'' for ``jmp %i7+8'' and ``jmp %o7+8''.
-- This is just a computed delayed transfer. We branch to a copy of the instruction and it's delay slot, as in the branch instruction case, above, excpet this isn't a conditional branch.
Types
Manger: TYPE ~ MACHINE DEPENDENT RECORD [
variants: SELECT COMPUTED MangerVariant FROM
Manger variants are ``computed'' by examining the ``tag'' field, which is an executable noop (because it gets executed in the call case) that encodes which variant we have. In a better world, we would just waste a word after the header to store the tag and other data about the patch.
There's still some hair to reconstructing the original instruction in the call case, but life is hard.
normal => [
no delay slot
sheep: SPARCArchitecture.SPARCInstruction,
normalContinue: SPARCArchitecture.SPARCInstruction,
normalNoop: SPARCArchitecture.SPARCInstruction,
tag: SPARCArchitecture.SPARCInstruction],
branch => [
has simple delay slot
dcti: SPARCArchitecture.SPARCInstruction,
branchDelay: SPARCArchitecture.SPARCInstruction,
branchContinue: SPARCArchitecture.SPARCInstruction,
tag: SPARCArchitecture.SPARCInstruction],
call => [
has delay slot, and have to get return address pointed to breakpoint address, since callee computes return point.
Use a call to get to the patch, and set up for a ``jmpl %g1,%g0'' from the patch to the target of the original call. (%g1 is free since we are calling a procedure.)
callAddrHi: SPARCArchitecture.SPARCInstruction,
callJmp: SPARCArchitecture.SPARCInstruction,
callNoop: SPARCArchitecture.SPARCInstruction,
tag: SPARCArchitecture.SPARCInstruction],
jmplO7 => [
has delay slot, and have to get return address pointed to breakpoint address, since callee computes return point.
Can only do these when link register is %o7, since we use a call to get to the patch, since we don't have the instruction space to point a register at the patch.
jmplO7Jmp: SPARCArchitecture.SPARCInstruction,
jmplO7Noop: SPARCArchitecture.SPARCInstruction,
jmplO7Pad: SPARCArchitecture.SPARCInstruction,
tag: SPARCArchitecture.SPARCInstruction],
jmplG0 => [
has delay slot, and is an unconditional branch.
jmplG0Jmp: SPARCArchitecture.SPARCInstruction,
jmplG0Delay: SPARCArchitecture.SPARCInstruction,
jmplG0Pad: SPARCArchitecture.SPARCInstruction,
tag: SPARCArchitecture.SPARCInstruction],
ENDCASE
];
MangerVariant: TYPE ~ {
noneOfTheAbove, normal, branch, call, jmplO7, jmplG0
};
NormalManger: TYPE ~ Manger.normal;
BranchManger: TYPE ~ Manger.branch;
CallManger: TYPE ~ Manger.call;
JmplO7Manger: TYPE ~ Manger.jmplO7;
JmplG0Manger: TYPE ~ Manger.jmplG0;
Procedures
Install: PROCEDURE [
address: BreakWorldArchitecture.Address,
manger: BreakWorldArchitecture.Address,
patchCode: BreakWorldArchitecture.Address]
RETURNS [];
Uninstall: PROCEDURE [
address: BreakWorldArchitecture.Address,
manger: BreakWorldArchitecture.Address,
patchCode: BreakWorldArchitecture.Address]
RETURNS [];
Errors
ErrorCode: TYPE ~ ATOM ← nullErrorCode;
nullErrorCode: ErrorCode ~ NIL;
ErrorMessage: TYPE ~ Rope.ROPE ← nullErrorMessage;
nullErrorMessage: ErrorMessage ~ NIL;
CantInstall: ERROR [code: ErrorCode, message: ErrorMessage];
CantUninstall: ERROR [code: ErrorCode, message: ErrorMessage];
}.