

|
Version 2.3 © 2000 K. J. Bricknell
IntroductionThe demonstration programs at Chapter 7 - Introduction to Controls are the first in which specific measures have had to be taken to ensuring that the source code will compile satisfactorily as both 680x0 code and PowerPC code. Accordingly, as a prelude for what is to come, this chapter addresses the differing run time environments of the 680x0 and PowerPC microprocessors and the measures required to ensure that source code that will compile successfully as both 680x0 and PowerPC code.
An important aspect of the emulator is that it makes it possible for parts of the system software to remain as 680x0 code while other parts of the system software are re-implemented, primarily for reasons of speed, as native PowerPC code. (The Memory Manager and QuickDraw, for example, were re-implemented as native PowerPC in the first system software release for Power Macintoshes.)
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 emulator is designed to allow existing 680x0 applications to execute without modification, it is always the responsibility of native applications to implement any changes necessary to interact with the Mixed Mode Manager.
Because of all this, you must explicitly indicate the ISA of any action functions whose addresses you pass to system software functions such as TrackControl.
The most important fields in a routine structure are the procInfo field and the ISA field:
Creating a Routine Descriptor For a Control Action FunctionThe Universal Headers file Controls.h contains this definition for the NewControlActionProc function:
typedef UniversalProcPtr ControlActionUPP;
#define NewControlActionProc(userRoutine) \
(ControlActionUPP) NewRoutineDescriptor((ProcPtr)(userRoutine), \
uppControlActionProcInfo,GetCurrentISA())
Using NewControlActionProc, you can create a routine descriptor for a control action function as follows, in which myControlAction is your application-defined control action function:
ControlActionUPP myControlActionUPP;
myControlActionUPP = NewControlActionProc((ProcPtr) myControlAction);
Notice that the result returned by NewControlActionProc is of type ControlActionUPP. The UPP stands for a universal procedure pointer, which is defined in the Universal Headers to be either a pointer to a routine descriptor or a simple 680x0 function pointer (hence the term "universal"). Thus the effect of the call to NewControlActionProc depends on whether it is executed in the 680x0 environment or the PowerPC environment:
Effect of the Routine DescriptorOnce you have created the routine descriptor, you can later call TrackControl like this:TrackControl(myControl,myPoint,myControlActionUPP);If your application is a PowerPC application, the value passed in the myControlActionUPP parameter is not the address of your action function itself, but the address of the routine descriptor. When a 680x0 version of TrackControl executes your action function, it begins by executing the instruction in the first field of the routine descriptor. That instruction invokes the Mixed Mode Manager, which inspects the ISA of the action function (contained in the ISA field of the routine structure). If that ISA differs from the instruction set architecture of the TrackControl function, the Mixed Mode Manager causes a mode switch. Otherwise, if the ISAs are identical, the Mixed Mode Manager simply executes the action function without switching modes. Disposing of Routine DescriptorsRoutine descriptors may be disposed of using DisposeRoutineDescriptor, although this is only necessary or advisable if you know that you will not be using the descriptor any more during the execution of your application or if you allocate a routine descriptor for temporary use only.Functions Requiring Routine DescriptorsThus you satisfy the requirement to explicitly indicate a function's ISA by creating routine descriptors and by using the address of those routine descriptors where you would have used simple function pointers in the purely 680x0 programming environment.Remember, however, that you only need to do this when you need to pass the address of a function to some external piece of code (such as a system software function or some other application) that might be in a different ISA from that of the function. You do not need to create routine descriptors for functions that are called only by your application. More generally, if you know for certain that a function is always called by code of the same ISA, you can and should continue to use function pointers instead of universal procedure pointers. Some of the typical functions for which you need to create routine descriptors are:
The PowerPC EnvironmentIn the emulation environment provided by the 68LC040 emulator, the organisation of an application partition, and the run-time behaviour of emulated software are identical to that provided on 680x0-based Macintoshes.
However, the run-time environment for native PowerPC software is significantly different from that of standard 680x0 run-time environment. The PowerPC environment provides a much simpler run-time model made possible by the use of fragments as the standard way of organising executable code and data in memory
Fragment Storage and LoadingThe physical storage for a fragment is a container, which can be any kind of object accessible by the operating system. The system software import library, for example, is stored in ROM. The fragment containing an application's executable code is stored in the application's data fork, which is a file of type 'APPL'. A container can also be a resource.The process of loading a fragment into memory and preparing it for execution is handled by the Code Fragment Manager. The code and data sections of a loaded fragment are loaded into separate sections of memory, which are generally not contiguous. Regardless of where it is loaded, however, there is no segmentation within the code section of a fragment. Even though a fragment's code and data sections can be loaded anywhere in memory, those sections cannot be moved in memory once they have been loaded.
Effect of the Code Fragment ResourceTypically, the code and data for a PowerPC application are contained in your application's data fork, as shown at Fig 2. If your application does not contain a code fragment resource, the Process Manager assumes that your application is a 680x0 application and calls the Segment Manager to load your application's executable code from resources of type 'CODE' in your application's resource fork (see Fig 2).
For example, you might re-compile an existing custom menu definition function (stored in a resource of type 'MDEF') into PowerPC code. Note that, because the Menu Manager code that calls your menu definition function might be 680x0 code, a mode switch to the PowerPC environment might be required before your definition function can be executed. Accordingly, as shown at the left at Fig 4, you need to prepend a routine descriptor onto the beginning of the resource.
These kinds of resources are called accelerated resources because they are faster implementations of existing kinds of resources. An accelerated resource is any resource containing PowerPC code that has a single entry point at the top (the routine descriptor) and that models the traditional behaviour of a 680x0 stand-alone code resource, for example, a control definition function (stored in a resource of type 'CDEF').
The CodeWarrior Linker adds the start-up code to your resource that allows a 680x0 application, running under the emulator on a PowerPC, to use the resource. The resource runs only on a PowerPC computer.
One significant feature of the calling convention in the PowerPC environment is that most parameters are passed in registers dedicated for that purpose. The large number of general-purpose and floating-point registers in the PowerPC makes this goal quite easy to achieve. Parameters are passed on the stack only when they cannot be put into registers.
Because VBL tasks are interrupt routines, they could well execute when the value in the 680x0 microprocessor's A5 register does not point to your application's A5 world. As a result, if you needed to access your application's global variables within a VBL task, you would need to set the A5 register to its correct value when your VBL task begins executing and restore the previous value upon exit. To achieve this, your application would save its A5 using SetCurrentA5. Then, at interrupt time, the VBL task would begin by calling SetA5 to, firstly, set the A5 register to this saved value and, secondly, save the value that was in the A5 register immediately prior to the call. The VBL task would then end with another call to SetA5, this time to restore the initial value. All this is necessary in 680x0 code. However, because a PowerPC application does not have an A5 world (see Chapter 1 - System Software, Memory, and Resources), you never need to explicitly set up and restore your application's A5 world. (Put another way, your application's global variables are transparently available to any code compiled into your application.) Accordingly your VBL task source code would need to be modified for compilation as PowerPC code. To maintain a single source code base for both the 680x0 and PowerPC environment, you would use conditional compilation. For example, consider the following simple 680x0 VBL task:
VBLRecPtr GetVBLRec(void) = 0x2E88; // MOVE.L A0,D0
...
void theVBLTask(void)
{
SInt32 curA5; // For stored value of A5.
VBLRecPtr vblRecPtr; // Pointer to task structure.
taskRecPtr = GetVBLRec(); // Get address of task structure.
currentA5 = SetA5(vblRecPtr->thisAppsA5); // Set app's A5 and store old A5.
gCounter++; // MODIFY A GLOBAL VARIABLE.
taskRecPtr->vblTaskRec.vblCount = kInterval; // Reset so function executes again.
(void) SetA5(currentA5); // Restore the old A5 value.
}
At the first call to SetA5, the application's A5 is set and the current value in the A5 register is saved. This saved value is re-installed at the second call to SetA5. For VBL tasks written as PowerPC code, both of these steps are unnecessary. Furthermore, in the 680x0 environment, the address of the VBL task structure is passed in register A0. If you need that address in a high-level language, you must retrieve it immediately upon entry into your VBL task (as is done in the above listing). In the PowerPC environment, however, the address of the VBL task structure can be passed to the task as an explicit parameter.
The following shows how the above source code would be modified for conditional compilation so as to be capable of being compiled as both 680x0 code and PowerPC code:
#if TARGET_CPU_68K
VBLRecPtr GetVBLRec(void) = 0x2E88; // MOVE.L A0,D0
#endif
...
#if TARGET_CPU_68K
void theVBLTask(void)
#else
void theVBLTask(VBLTaskPtr vblRecPtr)
#endif
{
#if TARGET_CPU_68K
SInt32 curA5; // For stored value of A5.
VBLRecPtr vblRecPtr // Pointer to task structure.
vblRecPtr = GetVBLRec(); // Get address of task structure.
currentA5 = SetA5(vblRecPtr->thisAppsA5); // Set app's A5 and store old A5.
#endif
gCounter++; // MODIFY A GLOBAL VARIABLE.
taskRecPtr->vblTaskRec.vblCount = kInterval // Reset so function executes again.
#if TARGET_CPU_68K
(void) SetA5(currentA5); // Restore the old A5 value.
#endif
}
TARGET_CPU_68K (Compiler is generating 680x0 instructions) is defined in the Universal Headers file ConditionalMacros.h.
Accessing Global Variables - Code ResourcesCode resources (for example, custom menu definition functions) often need to access global variables. To avoid any conflict with the running application, 680x0 versions of code resources access their globals referenced from the 680x0 A4 register, instead of the A5 register. Accordingly, the value in the A4 register must be saved on entry to the main function and restored on exit. Using CodeWarrior, you would ensure this by calling EnterCodeResource and ExitCodeResource as follows:
#includeThe calls to EnterCodeResource and ExitCodeResource are not required when the code is being compiled as PowerPC code. Accordingly the above code would need to be modified for conditional compilation so as to be capable of being compiled as both 680x0 code and PowerPC code:
#include Data AlignmentUnless told to do otherwise, a compiler arranges a data structure in memory so as to minimise the amount of time required to access the fields of the structure, which is generally what you would like to have happen. PowerPC and 680x0 compilers follow different conventions concerning the alignment of data in memory. Those conventions are as follows
Floating Point ArithmeticThe PowerPC-based Macintosh follows the IEEE 754 standard for floating-point arithmetic. In this standard, float is 32 bits, and double is 64 bits. (Apple has added a 128 bit long double type.) However, the PowerPC Floating-Point Unit does not support Motorola's 80/96-bit extended type, and neither do the PowerPC numerics. To accommodate this, you must use Apple-supplied conversion utilities to move to and from extended. Once again, conditional compilation may be used to make your code 680x0/PowerPC compatible. The following is an example:
Sint32 quantity;
extended80 value80bit;
#if TARGET_CPU_PPC
double valueDouble;
#endif
#if TARGET_CPU_68K
value80bit = value80bit * quantity;
#endif
#if TARGET_CPU_PPC
valueDouble = x80tod(&value80bit);
valueDouble = valueDouble * quantity;
dtox80(&valueDouble,&value80bit);
#endif
Making Code Suitable For Compilation As Either 680x0 Code or PowerPC code - SummaryIn general, it is relatively easy to ensure that your code will compile successfully as either 680x0 code or PowerPC code. The areas requiring attention are summarised as follows:
kM68kISA = (ISAType)0 680x0 architecture. kPowerPCISA = (ISAType)1 PowerPC architecture. Procedure InformationkPascalStackBased = (CallingConventionType)0 kCStackBased = (CallingConventionType)1 kRegisterBased = (CallingConventionType)2 Data Typestypedef SInt8 ISAType; typedef unsigned short CallingConventionType; typedef unsigned long ProcInfoType; Routine Descriptor
struct RoutineDescriptor
{
unsigned short goMixedModeTrap; // Our A-Trap.
SInt8 version;
RDFlagsType routineDescriptorFlags;
unsigned long reserved1;
UInt8 reserved2;
UInt8 selectorInfo;
short routineCount;
RoutineRecord routineRecords[1]; // The individual routines.
};
typedef struct RoutineDescriptor RoutineDescriptor;
typedef RoutineDescriptor *RoutineDescriptorPtr, **RoutineDescriptorHandle;
Routine Structure
struct RoutineRecord
{
ProcInfoType procInfo; // Calling conventions.
SInt8 reserved1; // Must be 0.
ISAType ISA; // Instruction Set Architecture.
RoutineFlagsType routineFlags; // Flags for each routine.
ProcPtr procDescriptor; // Where is the thing we're calling?
unsigned long reserved2; // Must be 0.
unsigned long selector; // For dispatched routines, the selector.
};
typedef struct RoutineRecord RoutineRecord;
typedef RoutineRecord *RoutineRecordPtr, **RoutineRecordHandle;
FunctionsDetermining Instruction Set ArchitecturesISAType GetCurrentISA(void); #define GetCurrentArchitecture() (GetCurrentISA() | GetCurrentRTA()) Creating and Disposing of Routine DescriptorsUniversalProcPtr NewRoutineDescriptor(ProcPtr theProc,ProcInfoType theProcInfo,ISAType
theISA);
UniversalProcPtr NewFatRoutineDescriptor(ProcPtr theM68kProc,ProcPtr thePowerPCProc,
ProcInfoType theProcInfo);
void DisposeRoutineDescriptor (UniversalProcPtr theProcPtr);
#define NewControlActionProc(userRoutine)
(ControlActionUPP)NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlActionProcInfo,GetCurrentArchitecture())
#define NewControlDefProc(userRoutine)
(ControlDefUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlDefProcInfo,GetCurrentArchitecture())
#define NewUserItemProc(userRoutine)
(UserItemUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppUserItemProcInfo,GetCurrentArchitecture())
#define NewListDefProc(userRoutine)
(ListDefUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppListDefProcInfo,GetCurrentArchitecture())
#define NewTEClickLoopProc(userRoutine)
(TEClickLoopUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppTEClickLoopProcInfo,GetCurrentArchitecture())
#define NewAEEventHandlerProc(userRoutine)
(AEEventHandlerUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppAEEventHandlerProcInfo,GetCurrentArchitecture())
#define NewModalFilterProc(userRoutine)
(ModalFilterUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppModalFilterProcInfo,GetCurrentArchitecture())
#define NewListSearchProc(userRoutine)
(ListSearchUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppListSearchProcInfo,GetCurrentArchitecture())
#define NewDeviceLoopDrawingProc(userRoutine)
(DeviceLoopDrawingUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppDeviceLoopDrawingProcInfo,GetCurrentArchitecture())
#define NewVBLProc(userRoutine)
(VBLUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),uppVBLProcInfo,
GetCurrentArchitecture())
#define NewSoundProc(userRoutine)
(SoundUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),uppSoundProcInfo,
GetCurrentArchitecture())
#define NewControlKeyFilterProc(userRoutine)
(ControlKeyFilterUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlKeyFilterProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneDrawProc(userRoutine)
(ControlUserPaneDrawUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneDrawProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneHitTestProc(userRoutine)
(ControlUserPaneHitTestUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneHitTestProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneTrackingProc(userRoutine)
(ControlUserPaneTrackingUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneTrackingProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneIdleProc(userRoutine)
(ControlUserPaneIdleUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneIdleProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneKeyDownProc(userRoutine)
(ControlUserPaneKeyDownUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneKeyDownProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneActivateProc(userRoutine)
(ControlUserPaneActivateUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneActivateProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneFocusProc(userRoutine)
(ControlUserPaneFocusUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneFocusProcInfo,GetCurrentArchitecture())
#define NewControlUserPaneBackgroundProc(userRoutine)
(ControlUserPaneBackgroundUPP) NewRoutineDescriptor((ProcPtr)(userRoutine),
uppControlUserPaneBackgroundProcInfo,GetCurrentArchitecture())
|



- SPREAD THE WORD:

- Slashdot

- Digg

- Del.icio.us


- Newsvine



