Building a Universal Binary

Architectural differences between Macintosh computers that use Intel and PowerPC microprocessors can cause existing PowerPC code to behave differently when built and run natively on a Macintosh computer that uses an Intel microprocessor. The extent to which architectural differences affect your code depends on the level of your source code. Most existing code is high-level source code that is not specific to the processor. If your application falls into this category, you’ll find that creating a universal binary involves adjusting code in a few places. Cocoa developers may need to make fewer adjustments than Carbon developers whose code was ported from Mac OS 9 to Mac OS X.

Most code that uses high-level frameworks and that builds with GCC 4.0 in Mac OS X v10.4 will build with few, if any, changes on an Intel-based Macintosh computer. The best approach for any developer in that situation is to build the existing code as a universal binary, as described in this chapter, and then see how the application runs on an Intel-based Macintosh. Find the places where the code doesn’t behave as expected and consult the sections in this document that cover those issues.

Developers who use AltiVec instructions in their code or who intentionally exploit architectural differences for optimization or other purposes will need to make the most code adjustments. These developers will probably want to consult the rest of this document before building a universal binary. AltiVec programmers should read Preparing Vector-Based Code.

This chapter describes how to use Xcode 2.2 to create a universal binary, provides troubleshooting information, and lists relevant build options. You’ll find that the software development workflow on an Intel-based Macintosh computer is exactly the same as the software development workflow on a PowerPC-based Macintosh.

Build Assumptions

Before you build your code as a universal binary, you must ensure that:

Building Your Code

If you have already been using Xcode to build applications on a PowerPC-based Macintosh, you’ll see that building your code on an Intel-based Macintosh computer is accomplished in the same way. By default, Xcode compiles code to run on the architecture on which you build your Xcode project. Note that your Xcode target must be a native target.

When you are in the process of developing your project, you’ll want to use the following settings for the Default and Debug configurations:

You can set the SDK root for the project by following these steps:

  1. Open your project in Xcode 2.2 or later.

    Make sure that your Xcode target is a native target. If it isn’t, you can choose Project > Upgrade All Targets in Project to Native.

  2. In the Groups & Files list, click the project name.

  3. Click the Info button to open the Info window.

  4. In the General pane, in the Cross-Develop Using Target SDK pop-up menu, choose Mac OS X 10.4 (Universal).

    If you don’t see Mac OS X 10.4 (Universal) as a choice, look in the following directory to make sure that the universal SDK is installed:

    /Developer/SDKs/MacOSX10.4u.sdk

    If it’s not there, you’ll need to install this SDK before you can continue.

  5. Click Change in the sheet that appears.

The Debug build configuration turns on ZeroLink, Fix and Continue, and debug-symbol generation, among other settings, and turns off code optimization.

When you are ready to test your application on both architectures, you’ll want to use the Release configuration. This configuration turns off ZeroLink and Fix and Continue. It also sets the code-optimization level to optimize for size. As with the Default and Debug configurations, you’ll want to set the Mac OS X Deployment Target to Mac OS X 10.4 and the SDK root to MacOSX10.4u.sdk. To build a universal binary, the Architectures setting for the Release configuration must be set to build on Intel and PowerPC.

You can change the Architectures setting by following these steps:

  1. Open your project in Xcode 2.2 or later.

  2. In the Groups & Files list, click the project name.

  3. Click the Info button to open the Info window.

  4. In the Build pane (see Figure 1-1), choose Release from the Configuration pop-up menu.

    Figure 1-1  The Build pane
    The Build pane
  5. Select the Architectures setting and click Edit. In the sheet that appears, select the PowerPC and Intel options, as shown in Figure 1-2.

    Figure 1-2  Architectures settings
    Architectures settings
  6. Close the Info window.

  7. Build and run the project.

If your application doesn’t build, see Debugging.

If your application builds but does not behave as expected when you run it as a native binary on an Intel-based Macintosh computer, see Troubleshooting Your Built Application.

If your application behaves as expected, don’t assume that it also works on the other architecture. You need to test your application on both PowerPC Macintosh computers and Intel-based Macintosh computers. If your application reads data from and writes data to disk, you should make sure that you can save files on one architecture and open them on the other.

For information on default compiler settings, architecture-specific options, and Autoconf macros, see Build Options.

For information on building with version-specific SDKs for PowerPC (Mac OS X v10.3, v10.2, and so forth) while still building a universal binary for both PowerPC and Intel-based Macintosh computers, see the following resources:

Debugging

Xcode uses GDB for debugging, so you’ll want to review the Xcode Debugging Guide document. Xcode provides a powerful user interface to GDB that lets you step through your code, set breakpoints and view variables, stack frames, and threads.

Debugging with GDB—an Open Source document that explains how to use GDB—is another useful resource that you’ll want to look at. It provides a lot of valuable information, including how to get a list of breakpoints for debugging.

If you are moving code to GCC 4.0, you can find remedies for most linking issues and compiler warnings by consulting GCC Porting Guide. You can find additional information on the GCC options you can use to request or suppress warnings in Section 3.8 of the GNU C/C++/Objective-C 4.0.1 Compiler User Guide.

Troubleshooting Your Built Application

Here are the most typical behavior problems you’ll observe when your application runs natively on an Intel-based Macintosh computer:

The first two problems in the list are typically caused by architecture-dependent code. On an Intel-based Macintosh computer, an integer divide-by-zero exception results in a crash, but on PowerPC the same operation returns zero. In these cases, the code must be rewritten in an architecture-independent manner. Architectural Differences discusses the major differences between Macintosh computers that use PowerPC and Intel microprocessors. That chapter can help you determine which code is causing the crash or the unexpected numerical results.

The last four problems in the list are most often caused by byte-ordering differences between architectures. These problems are easily remedied by taking the byte order into account when you read and write data. The strategies available for handling byte ordering, as well as an in-depth discussion of byte-ordering differences, are provided in Swapping Bytes. Keep in mind that Mac OS X ensures that byte-ordering is correct for anything it is responsible for. Apple-defined resources (such as menus) won’t result in problem behavior. Custom resources provided by your application, however, can result in problem behavior. For example, if images in your application seem to have a cyan tint, it’s quite likely that your application is writing alpha channel data to the blue channel. For this specific issue, depending on the APIs that you are using, you’d want to consult the sections GWorlds, Pixel Data , or other graphics-related sections in Guidelines for Specific Scenarios.

Apple engineers prepared a lot of code to run natively on an Intel-based Macintosh computer—including the operating system, most Apple applications, and Apple tools. The guidelines in this book are the result of their work. In addition to the more common issues discussed in Architectural Differences and Swapping Bytes, the engineers identified a number of narrowly focused issues. These are described in Guidelines for Specific Scenarios. You will want to at least glance at this chapter to see if your code can benefit from any of the information.

Determining Whether a Binary Is Universal

You can determine whether an application has a universal binary by looking at the Kind entry in the General section of the Info window for the application (see Figure 1-3). To open the Info window, click the application icon and press Cmd-I.

Figure 1-3  The Chess application has a Universal binary
The Chess application has a Universal binary

On an Intel-based Macintosh computer, when you double-click an application that doesn’t have an executable for the native architecture, it might launch. Whether or not it launches depends on how compatible the application is with Rosetta. For more information, see Rosetta.

Build Options

This section contains information on the build options that you need to be aware of when using Xcode 2.2 and later on an Intel-based Macintosh computer. It lists the default compiler options, discusses how to set architecture-specific options, and provides information on using GNU Autoconf macros.

Default Compiler Options

In Xcode 2.2 and later on an Intel-based Macintosh computer, the defaults for compiler flags that differ from standard GCC distributions are listed in Table 1-1.

Table 1-1  Default values for compiler flags on an Intel-based Macintosh computer

Compiler flag

Default value

Specifies to

-mfpmath

sse

Use SSE instructions for floating-point math.

-msse2

On by default

Enable the MMX, SSE, and SSE2 extensions in the Intel instruction set architecture.

Architecture-Specific Options

Most developers don’t need to use architecture-specific options for their projects.

In Xcode, to set one flag for an Intel-based Macintosh and another for PowerPC, you use the PER_ARCH_CFLAGS_i386 and PER_ARCH_CFLAGS_ppc build settings variables to supply the architecture-specific settings.

For example to set the architecture-specific flags -faltivec and -msse3, you would add the following build settings:

PER_ARCH_CFLAGS_i386 = -msse3
PER_ARCH_CFLAGS_ppc = -faltivec

Similarly, you can supply architecture-specific linker flags using the OTHER_LDFLAGS_i386 and OTHER_LDFLAGS_ppc build settings variables.

You can pass the -arch flag to gcc, ld, and as. The allowable values are i386 and ppc. You can specify both flags as follows:

-arch ppc -arch i386

For more information on architecture-specific options, seeBuilding Universal Binaries.

Autoconf Macros

If you are compiling a project that uses GNU Autoconf and trying to build it for both PowerPC-based and Intel-based Macintosh computers, you need to make sure that when the project configures itself, it doesn't use Autoconf macros to determine the endian type of the runtime system. For example, if your project uses the Autoconf AC_C_BIGENDIAN macro, the program won't work correctly when it is run on the opposite architecture from the one you are targeting when you configure the project. To correctly build for both PowerPC-based and Intel-based Macintosh computers, use the compiler-defined __BIG_ENDIAN__ and __LITTLE_ENDIAN__ macros in your code.

For more information, see Using GNU Autoconf in Porting UNIX/Linux Applications to OS X.

See Also

These resources provide information related to compiling and building applications, and measuring performance: