Legacy Documentclose button

Important: The information in this document is obsolete and should not be used for new development.

Previous Book Contents Book Index Next

Inside Macintosh: PowerPC System Software /
Chapter 1 - Introduction to PowerPC System Software


Mixed Mode

An instruction set architecture is the set of instructions recognized by a particular processor or family of processors. The Mixed Mode Manager is the part of the Macintosh system software that manages mode switches between code in different instruction set architectures, switching the execution context between the CPU's native PowerPC context and the 68LC040 Emulator. The 68LC040 Emulator is responsible for handling all code in the 680x0 instruction set. This includes existing 680x0 applications, device drivers, system extensions, and even parts of the system software itself that have not yet been rewritten to use the PowerPC instruction set.

Mode switches are required not only when the user switches from an emulated to a native application (or vice versa), but also when any application calls a system software routine or any other code that exists in a different instruction set. For example, the Memory Manager has been reimplemented in the first version of system software for PowerPC processor-based Macintosh computers as native PowerPC code. When an existing 680x0 application running under the 68LC040 Emulator calls a Memory Manager routine such as NewHandle, a mode switch is required to move out of the emulator and into the native PowerPC environment. Then, once the Memory Manager routine completes, another mode switch is required to return to the 68LC040 Emulator and to allow the 680x0 application to continue executing.

Similarly, PowerPC applications cause mode switches whenever they invoke routines that exist only as 680x0 code. For example, if a PowerPC application calls a part of
the Macintosh Toolbox or Operating System that has not been ported native, a mode
switch is required to move from the native environment to the environment of the 68LC040 Emulator.

The Mixed Mode Manager exists solely to manage these kinds of mode switches. It makes it possible for the execution environment of PowerPC processor-based Macintosh computers to accommodate a mixture of 680x0 applications, PowerPC applications, 680x0 system software, PowerPC system software, 680x0 executable resources, PowerPC executable resources, 680x0 device drivers, PowerPC device drivers, and so forth. The 68LC040 Emulator and the Mixed Mode Manager together allow both 680x0 code and PowerPC code to execute on the PowerPC microprocessor.

The Mixed Mode Manager is designed to hide, as much as possible, the hybrid nature of the mixed environment supported on PowerPC processor-based Macintosh computers. Occasionally, however, some executable code needs to interact directly with the Mixed Mode Manager to ensure that a mode switch occurs at the correct time. Because the 68LC040 Emulator is designed to allow existing 680x0 applications and system software to execute without modification, it's always the responsibility of native applications
and system software to implement any changes necessary to interact with the Mixed Mode Manager.

This section describes the basic operation of the Mixed Mode Manager. It shows you how, if you're writing a native application, you might need to modify your application to make it compatible with the mixed environment of the system software for PowerPC processor-based Macintosh computers. If you use fairly simple techniques for calling code external to your application and use only the standard types of callback routines, the information in this section might be sufficient for your needs. If not, see the chapter "Mixed Mode Manager" in this book for complete information about the Mixed
Mode Manager.

Cross-Mode Calls

The Mixed Mode Manager is intended to operate transparently to most applications and other kinds of software. This means, in particular, that most cross-mode calls (calls
to code in a different instruction set from the caller's instruction set) are detected automatically by the Mixed Mode Manager and handled without explicit intervention by the calling software. For instance, when a 680x0 application calls a Memory Manager routine--which, as you have already learned, exists as PowerPC code in the system software for PowerPC processor-based Macintosh computers--the Trap Manager dispatches to the code pointed to by the appropriate entry in the trap dispatch table. For routines that are implemented as native code, the entry in the trap dispatch table is a pointer to a routine descriptor, a data structure used by the Mixed Mode Manager to encapsulate information about a routine. The first field in a routine descriptor is an executable 680x0 instruction that invokes the Mixed Mode Manager. The Mixed Mode Manager handles all the details of switching to the native mode, calling the native code, and then returning to the 68LC040 Emulator. The calling application is completely unaware that any mode switches have occurred.

The operation of the Mixed Mode Manager is also completely transparent when a PowerPC application calls a system software routine that exists as 680x0 code, although the exact details are slightly different. When a native application calls a system soft-
ware routine, the Operating System executes some glue code in an import library of executable code. The glue code inspects the trap dispatch table for the address of the called routine. If the called routine exists only as 680x0 code, the Mixed Mode Manager switches modes and calls the 680x0 routine. When the 680x0 code returns, the Mixed Mode Manager switches back to the native PowerPC environment and the execution of the PowerPC application continues.

Note
See "The PowerPC Native Environment" beginning on page 1-19
for information about the native execution environment, including import libraries.
When writing PowerPC code, you need to explicitly intervene in the mode-switching process only when you execute code (or have code executed on your behalf) whose instruction set architecture might be different from that of the calling code. For example, whenever you pass the address of a callback routine to the Operating System or Toolbox, it's possible that the instruction set architecture of the code whose address you are passing is different from the instruction set architecture of the routine you're passing
it to. In such cases, you need to explicitly signal the type of code you're passing and its calling conventions. Otherwise, the Mixed Mode Manager might not be called to make a required mode switch.

To see this a bit more clearly, suppose that you are writing a native PowerPC application that calls the Control Manager procedure TrackControl. TrackControl accepts as one of its parameters the address of an action procedure that is called repeatedly while the user holds down the mouse button in a control. TrackControl has no way of determining in advance the instruction set architecture of the code whose address you will pass it. Moreover, you have no way of determining in advance the instruction set architecture of the TrackControl procedure, so you cannot know whether your action procedure and the TrackControl procedure are of the same instruction set architecture. As a result, you must explicitly indicate the instruction set architecture of any callback routines whose addresses you pass to the system software.

Routine Descriptors

You indicate the instruction set architecture of a particular routine by creating a routine descriptor for that routine. Here is the structure of a routine descriptor.

struct RoutineDescriptor {
   unsigned short       goMixedModeTrap;  /*mixed-mode A-trap*/
   char                 version;          /*routine descriptor version*/
   RDFlagsType          routineDescriptorFlags;
                                          /*routine descriptor flags*/
   unsigned long        reserved1;        /*reserved*/
   unsigned char        reserved2;        /*reserved*/
   unsigned char        selectorInfo;     /*selector information*/
   short                routineCount;     /*index of last RR in this RD*/
   RoutineRecord        routineRecords[1];/*the individual routines*/
};
typedef struct RoutineDescriptor RoutineDescriptor;
As you can see, the first field of a routine descriptor is an executable 680x0 instruction that invokes the Mixed Mode Manager. When the Mixed Mode Manager is called, it inspects the remaining fields of the routine descriptor--in particular the routineRecords field--to determine whether a mode switch is required. The routineRecords field is an array of routine records, each element of which describes a single routine. In the simplest case, the array of routine records contains a single element. Here is the structure of a routine record.

struct RoutineRecord {
   ProcInfoType         procInfo;         /*calling conventions*/
   unsigned char        reserved1;        /*reserved*/
   ISAType              ISA;              /*instruction set architecture*/
   RoutineFlagsType     routineFlags;     /*flags for each routine*/
   ProcPtr              procDescriptor;   /*the thing we're calling*/
   unsigned long        reserved2;        /*reserved*/
   unsigned long        selector;         /*selector for dispatched calls*/
};
typedef struct RoutineRecord RoutineRecord;
typedef RoutineRecord *RoutineRecordPtr, **RoutineRecordHandle;
The most important fields in a routine record are the procInfo field and the ISA field. The ISA field encodes the instruction set architecture of the routine being described. It must always contain one of these two constants:

enum {
   kM68kISA          = (ISAType)0,     /*MC680x0 architecture*/
   kPowerPCISA       = (ISAType)1      /*PowerPC architecture*/};
The procInfo field contains the routine's procedure information, which encodes
the routine's calling conventions and information about the number and location of the routine's parameters. For the standard kinds of callback procedures and other
types of "detached" code, the universal interface files include definitions of procedure information. For example, the C language interface file Controls.h includes
this definition:

enum {
   uppControlActionProcInfo = kPascalStackBased
       | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(ControlHandle)))
       | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(short)))
};
This procedure information specification indicates that a control action procedure follows standard Pascal calling conventions and takes two stack-based parameters, a control handle and a part code; the action procedure returns no result. Similarly, the file Controls.h defines the procedure information for a control definition function as follows:

enum {
   uppControlDefProcInfo = kPascalStackBased
       | RESULT_SIZE(SIZE_CODE(sizeof(long)))
       | STACK_ROUTINE_PARAMETER(1, SIZE_CODE(sizeof(short)))
       | STACK_ROUTINE_PARAMETER(2, SIZE_CODE(sizeof(ControlHandle)))
       | STACK_ROUTINE_PARAMETER(3, SIZE_CODE(sizeof(short)))
       | STACK_ROUTINE_PARAMETER(4, SIZE_CODE(sizeof(long)))
};
You can create a routine descriptor by calling the Mixed Mode Manager function NewRoutineDescriptor, as shown in Listing 1-1.

Listing 1-1 Creating a routine descriptor

UniversalProcPtr myActionProc;
myActionProc = NewRoutineDescriptor((ProcPtr)MyAction, 
                                    uppControlActionProcInfo, 
                                    GetCurrentISA());
Here, MyAction is the address of your control action procedure and GetCurrentISA
is a C language macro that returns the current instruction set architecture. When executed in the PowerPC environment, the NewRoutineDescriptor function creates
a routine descriptor in your application heap and returns the address of that routine descriptor. When executed in the 680x0 environment, the NewRoutineDescriptor function simply returns its first parameter. Notice that the result returned by
the NewRoutineDescriptor function is of type UniversalProcPtr. A universal procedure pointer is defined to be either a 680x0 procedure pointer or a pointer to a routine descriptor, essentially as follows:

#if !USESROUTINEDESCRIPTORS
typedef ProcPtr UniversalProcPtr, *UniversalProcHandle;
#else
typedef RoutineDescriptor *UniversalProcPtr, **UniversalProcHandle;
#endif
Once you've executed the code in Listing 1-1 (probably at application launch time), you can later call TrackControl like this:

TrackControl(myControl, myPoint, myActionProc);
If your application is a PowerPC application, the value passed in the gActionProc parameter is not the address of your action procedure itself, but the address of the routine descriptor created in Listing 1-1. When a 680x0 version of TrackControl executes your action procedure, it begins by executing the instruction contained in the first field of the routine descriptor. That instruction invokes the Mixed Mode Manager, which inspects the instruction set architecture of the action routine (contained in the ISA field of the routine record contained in the routine descriptor). If that instruction set architecture differs from the instruction set architecture of the TrackControl routine, the Mixed Mode Manager causes a mode switch. Otherwise, if the two instruction set architectures are identical, the Mixed Mode Manager simply executes the action procedure without switching modes.

In short, you solve the general problem of indicating a routine's instruction set archi-
tecture by creating routine descriptors and by using the addresses of those routine descriptors where you would have used procedure pointers in the 680x0 programming environment. You have to do this, however, only when you need to pass the address of a routine to some external piece of code (such as the Toolbox or Operating System or some other application) that might be in a different instruction set architecture from that of the routine. There are quite a number of cases in which you pass procedure pointers to the system software and which therefore require you to use the techniques illustrated above for Control Manager action procedures. Some of the typical routines you need to create routine descriptors for include

The interface files for the PowerPC system software have been revised to change
all references to parameters or fields of type ProcPtr to references of type UniversalProcPtr. In addition, these new universal interface files contain procedure information definitions for all the standard kinds of callback routines. Moreover, the universal interface files define new routines that you can use in place of the more general code shown in Listing 1-1 on page 1-17. For example, the interface file Controls.h contains the definition shown in Listing 1-2 for the NewControlActionProc function.

Listing 1-2 The definition of the NewControlActionProc routine

typedef UniversalProcPtr ControlActionUPP;
#define NewControlActionProc(userRoutine) \
   (ControlActionUPP) NewRoutineDescriptor((ProcPtr)userRoutine, \
   uppControlActionProcInfo, GetCurrentISA())
Because this routine is defined in the universal header files, you can replace the code in Listing 1-1 with the simpler code shown in Listing 1-3.

Listing 1-3 Creating a routine descriptor for a control action procedure

ControlActionUPP myActionProc;
myActionProc = NewControlActionProc((ProcPtr)MyAction);
In general, you should use the specific routines defined throughout the universal header files instead of the general technique illustrated in Listing 1-1.

IMPORTANT
You do not need to create routine descriptors for routines that are called only by your application. More generally, if you know for certain that a routine is always called by code of the same instruction set architecture, you can and should continue to use procedure pointers instead of universal procedure pointers. If, however, the address of one of your application's routines might be passed to a Toolbox or Operating System routine, you should make sure to use a routine descriptor.

Memory Considerations

The technique described in the previous section for using routine descriptors is by far the simplest and easiest to implement: any routine descriptors needed by an application are allocated in the application heap at application launch time. The descriptors remain allocated until the application terminates, at which time the entire application heap is reclaimed by the Process Manager. As a result, you don't have to dispose of any routine descriptors created in this way.

If, in some case, you know that you won't be needing a routine descriptor any more during the execution of your application, you can explicitly dispose of it by calling
the DisposeRoutineDescriptor function. This is most useful when you allocate a routine descriptor for temporary use only. For example, you might call some code that uses a callback procedure only very infrequently. In that case you can allocate the routine descriptor when the code is called and then release it when the code is done.

Finally, you can create a routine descriptor on the stack if you intend to use it only within a single procedure. The Mixed Mode Manager interface file MixedMode.h defines the C language macro BUILD_ROUTINE_DESCRIPTOR that you can use for this purpose, as well as for initializing static routine descriptors. For details, see "Using Static Routine Descriptors" on page 2-22 in the chapter "Mixed Mode Manager" in this book.


Previous Book Contents Book Index Next

© Apple Computer, Inc.
3 JUL 1996