Channels ▼
RSS

Parallel

Mac OS X Tiger & 64 Bits

Source Code Accompanies This Article. Download It Now.


February, 2006: Mac OS X Tiger & 64 Bits

Rodney Mach is HPC Technical Director for Absoft. He can be contacted at rwm@absoft.com.


Mac OS X Tiger is the first version of the Macintosh operating system that supports 64-bit computing, thereby letting you fully exploit the 64-bit PowerPC G5 processor. However, this does not necessarily mean that you should migrate every application to the 64-bit platform. Most OS X apps don't need to be ported to 64-bit, and in fact will execute faster as 32-bit applications. The main reason you might want to make an application 64-bit is if it needs to access more than 4 GB of memory. Applications in this category include scientific and engineering programs, rendering applications, and database apps. So before looking at what's necessary to port your applications to 64-bit, it is a good idea to examine the circumstances that don't require applications to be ported to 64-bit:

  • 64-bit math. You don't need to port to 64-bit to do 64-bit arithmetic with OS X on 64-bit PowerPC G5 hardware. The PowerPC supports 64-bit arithmetic instructions in 32-bit mode. You can use the GCC options -mcpu=G5 to enable G5-specific optimizations, as well as -mpowerpc64 to allow 64-bit instructions. Using these two options enables performance gains in 32-bit applications.
  • Apple has announced that the Mac platform will be transitioning to Intel. Intel processors, such as the 64-bit Intel Xeon, require applications to be 64-bit to take advantage of the additional 64-bit general-purpose registers (unlike the PowerPC). Therefore, you may need to reevaluate decisions to port to 64-bit once more details about the Intel on Mac architecture become available—especially if your code is integer intensive.
  • 64-bit data types. You don't need to port to 64-bit to gain access to 64-bit data types. For example, long long and int64_t are 64 bit and can be used by 32-bit applications.
  • Faster code. You should not port to 64-bit if your code is performance sensitive and highly tuned for 32-bit. The increased size of 64-bit pointers and long can cause increased cache pressure, as well as increased disk, memory, and network usage, which can lead to application performance degradation.

64-Bit Clean

Once you determine that an application does need to be 64 bit, then you should make your code "64-bit clean." The 64-bit C data model used by Mac OS X (and all modern UNIX derivatives) is commonly referred to as "LP64." In the LP64 data model, ints are 32 bit, while longs and pointers are 64 bit. The 32-bit data model is referred to as "ILP32," and ints, longs, and pointers are all 32 bit.

This difference in the size of long and pointer between ILP32 and LP64 can cause truncation issues in code that assumes the same width as int. Many of these 64-bit porting bugs can be detected by using the -Wall -Wformat -Wmissing-prototypes -Wconversion -Wsign-compare -Wpointer options with GCC. (For more information on general 64-bit porting issues, refer to my article "Moving to 64-Bits," C/C++ Users Journal, June 2005; http://www.cuj.com/documents/s=9796/cuj0506mach/0506mach.html.)

However, there is a 64-bit caveat: Support for 64-bit programming is not available throughout the entire OS X API for 64-bit computing on OS X Tiger. For example, application frameworks such as Cocoa and Carbon are not yet available for 64-bit development. This means you cannot simply recompile 32-bit GUI apps as 64 bit on OS X—only command-line apps can be recompiled as 64 bit. However, this doesn't mean GUI applications cannot take advantage of 64-bit computing. In the rest of this article, I examine how you work around this issue by porting an example 32-bit OS X GUI application to 64-bit.

The Demo Application

The 32-bit demo application that I 64-bit enable here is a simple "array lookup" application. Users enter an index of the array, and the application returns the array value at that index; see Figure 1. I want to migrate this application to 64 bit to take advantage of arrays greater than 4 GB.

The GUI in this example is written in Qt 4 (http://www.trolltech.com/), an open-source C++ application framework that makes it straightforward to write cross-platform native GUIs (Carbon on OS X). At Absoft (where I work), all of our cross-platform developer tools are written in Qt for easy maintenance, and native speed on all of our supported platforms (Windows, Linux, and OS X). If your application is not Qt based and uses native OS X APIs, the strategy I present here still applies.

The Methodology

To convert the 32-bit demo application to 64 bit, I split the 32-bit application into two parts to work around the limitation that only command-line apps can be 64 bit on OS X:

  • A 64-bit command-line server that does the necessary 64-bit operations such as array allocation and management.
  • A 32-bit GUI that displays result and interfaces with users. The existing GUI is refactored to launch and communicate with the server.

This is the same strategy we used at Absoft with our 64-bit Fx2 debugger on OS X Tiger. The debugger is a 32-bit UI that communicates with a 64-bit back end. Refactoring the application into a 64-bit executable and 32-bit GUI is the most difficult task for most GUI applications.

Once you have identified a strategy for 64-bit enabling of the application, you must decide on the communication method between the 64-bit server and 32-bit GUI client. There are several mechanisms you can use for communication:

  • Communicate using message passing between STDIN and STDOUT of the 64-bit application.
  • Use UNIX Domain sockets for same host communication.
  • Use TCP/IP client/server mechanisms.
  • Use shared memory or other IPC mechanism.

The method you select depends on the application. The implementation I present here is based on UNIX Domain sockets.

UNIX Domain sockets are lightweight, high-performance sockets that enable communication between processes on the same host. If you are familiar with standard TCP sockets, you will find UNIX domain sockets easy to master. UNIX Domain sockets also assist in future proofing your code by enabling an easy upgrade path to more heavyweight TCP sockets. For example, a future version of your application could have the server run on a PowerPC-based Mac, and the GUI client on the Intel-based Mac.

Creating the Server

The server handles allocating the array so you can access more than 4 GB of memory. It also provides an interface that a client can use to look up values from the array. This server can be tested independently of the GUI, letting you hammer out the client-server interaction before refactoring the GUI.

Use fixed-width datatypes for sharing between ILP32 and LP64. Listing One (server.c) is the server source code. In lines 16-18 of Listing One, the code uses fixed-width datatypes such as uint64_t instead of unsigned long long. It is a good practice to use fixed-width datatypes when sharing data over a socket, or sharing data on disk between ILP32 and LP64. This guarantees that the size of the data does not change while communicating between the two different data models. It also future proofs your code against changes in the width of fundamental datatypes and saves you headaches in the future. These fixed-width datatypes were introduced by C99, and are located in the header file <stdint.h>. While this C99 feature is not technically part of the C++ Standard, it is a feature supported by most C++ compilers (such as Absoft 10.0 a++ and GNU g++).

Use the _LP64_ macro to conditionally compile 64-bit-specific code. When maintaining a single code base for 32- and 64-bit code, you may want to conditionally compile the code depending on whether it is 64 bit or 32 bit. In this case, I want the defined ARRAY_SIZE on line 18 to be larger when compiled as 64-bit to take advantage of larger memory. Listing Two (__LP64__) is the macro to use on OS X.

In UNIX Domain sockets, a pathname in the filesystem ("/tmp/foo," for instance) is used as the address for the client and server to communicate. This filename is not a regular filename that you can read from or write to—your program must associate this filename with a socket in order to perform communication. You can identify this special socket using the UNIX command ls -laF on the file; you will see a "=" appended to the filename indicating it is a socket:

% ls -laF /tmp/sock
srwxr-xr-x 1 rwm wheel 0 Oct 29 21:51
/tmp/sock=

Returning to the server code in Listing One, the server must be prepared to accept connections, which is done via the socket, bind, and listen calls. On line 26 of Listing One, the socket call creates an endpoint for communication, returning an unnamed socket. The socket call takes three arguments:

  • The first argument is the family type. In this case, I use AF_LOCAL to specify UNIX Domain family.
  • The second argument of SOCK_STREAM type provides sequenced, reliable, two-way connection-based bytestreams for this socket.
  • The final argument selects the protocol for the family. In this case, zero is the default.

In lines 30-33 of Listing One, I set up the sockaddr_un structure with the filename to use. Note that the SOCK_ADDR filename is defined in the absoft.h header file (Listing Two) as a UNIX pathame "/tmp/sock." The filename is arbitrary, but must be defined the same in both the client and server, and must be an absolute pathname. Be sure to delete this file as it may have been left over from a previous instance on line 35 and ensure that the bind call succeeds.

Next, on line 37, I bind the unnamed socket previously created with the name I just configured. Finally, on line 42, I use the listen call to begin accepting connections on this connection.

On line 46, I sit in a loop and wait to accept connections from the client. Once you have received a connection, you read in the array index the user selected on line 54, and return the array value on line 64. Note the use of readn and written functions. Regular read/write do not guarantee that all the bytes requested will be read/written in one call. Wrapper functions are used to ensure all bytes are read/written as expected (see util.c, available electronically, "Resource Center," page 6).

Creating the Client

To test the server, create a C client that connects to the server, requests an array index, and fetches the result. You can use this client to test the server interaction before having to refactor the GUI. The client uses the socket and connect calls to talk to the server; see Listing Three for the implementation of the client lookUp function. The client code should be easy to follow because it is similar to the server but uses the connect system call to connect to the already existing server socket.

You may wonder why the server and client were not written in C++. The main reason is portability. C socket implementations are portable to a variety of platforms without the need for third-party libraries or a roll-your-own implementation. If you do need to code the client/server in C++, Qt provides a QSocket class that you can extend to support UNIX Domain sockets.

Refactoring the GUI

At this point, you have a server that allocates the array, and a client that can call the server and fetch values from the server. It is now time to tackle the messy part—refactoring the GUI. You must identify everywhere the GUI currently manipulates or queries the array directly, and direct it to use the client function call instead. Luckily, only one method, Viewer::lookupArray() in line 52 of Viewer.cpp (available electronically), is used to look up values in the array. This method is modified on line 54 to call the client lookupUp function in a thread.

To leave the original behavior intact, wrap the new functionality in a DIVORCE_UI define statement so you can conditionally compile-in changes.

To simplify the code, I made all network calls blocking. You can't issue a blocking call from the UI thread in Qt (and most GUI frameworks) without making the UI unresponsive to users. Therefore, I issue the blocking call to the server inside a thread, and have the thread alert the UI when the blocking network communication has completed.

See the FetchDataThread.cpp class (Listing Four) for the implementation of my thread wrapper to the fetchData function.

The run() method in Listing Four calls the blocking lookupValue function call defined in Listing Three. The method locks a mutex around critical data to ensure thread safety.

In line 27 of Viewer.cpp, I use the Qt emit keyword to emit a signal containing the result received from the server. The GUI receives this method by connecting a "slot" in Qt parlance to the "signal" from the FetchDataThread thread (see lines 40-43 in Viewer.cpp). The end result is the showResult method in Viewer.cpp. It is called to display the results from the server and enable the Lookup button in the application.

Starting and Stopping the Server

The final piece of the puzzle is to have the GUI automatically start the 64-bit server to make the split appear transparent. The main() function in Viewer.cpp uses the Qt class QProcess to launch the server executable on lines 83-88, and shuts the server down on lines 93-97 before the applications exits.

Creating a Universal Binary

You may want to ship 32-bit and 64-bit servers so your application can run on a wide variety of Macintosh hardware. Instead of shipping multiple versions of the application, you can create a Universal Binary (also called a "Fat Binary") that lets you ship one server binary that is both 32 bit and 64 bit. A Universal Binary automatically selects the correct code, depending on the user's system without additional coding or user intervention.

It is straightforward to create a Universal Binary using Xcode, or using the lipo tool shipped with OS X. Lipo "glues" your 32-bit and 64-bit applications into one binary. Listing Five is an example makefile that creates a Universal Binary for the server presented here. Use the UNIX file command to examine the resulting binary:

% file server
server: Mach-O fat file with 2 architectures
server (for architecture ppc):
Mach-O executable ppc
server (for architecture ppc64):
Mach-O 64-bit executable ppc64

Building and Running the Application

To build the application after you have installed Qt, enter:

% qmake ; make ; make -f Makefile.server

at the command line. The qmake utility (included with Qt) creates a Makefile for building the GUI from the Viewer.pro file in Listing Six. The Makefile.server builds the server as a Universal Binary. Once the build has completed, you can execute the 64-bit enabled Viewer application by running it from the command line:

%./Viewer.app/Contents/MacOS/Viewer

Conclusion

With its UNIX heritage and innovative features such as Universal Binaries, OS X is a great 64-bit platform to develop 64-bit applications on. Migrating command-line applications to 64-bit is straightforward, and the strategy I've outlined here will help you in 64-bit enabling your GUI applications to harness the full power of Mac OS X Tiger.

DDJ



Listing One

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <inttypes.h>
#include <unistd.h>
#include "absoft.h"

int main(int argc, char *argv[])
{
    int listenfd,/* listen socket descriptor        */
    clientfd,    /* socket descriptor from connect  */
    i;
    int32_t x;   /* array index from the client */
    uint64_t result;  /* result sent to client */
    static uint64_t  bigarray_[ARRAY_SIZE];
    socklen_t clientlen;
    struct sockaddr_un server, client;
    /* Initialize array with random values */
    for ( i = 0 ; i < ARRAY_SIZE ; i++ ) {
        bigarray_[i] = 10000000000000000000ULL + i;
    }
    /* AF_LOCAL is Unix Domain Socket */
    if ((listenfd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0) {
        perror("socket");
        exit(1);
    }
    /* Setup socket info */
    bzero((char *) &server, sizeof(server));
    server.sun_family = AF_LOCAL;
    strncpy(server.sun_path, SOCK_ADDR, sizeof(server.sun_path));

    /* Unlink file to make sure bind succeeds. Ignore error */
    unlink(SOCK_ADDR);

    /* Bind to socket */
    if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) < 0 ) {
        perror("bind");
        exit(2);
    }
    /* Listen on socket */
    if (listen(listenfd, LISTENQ) < 0 ) {
        perror("listen");
        exit(3);
    }
    for(;;) {
        printf("Waiting for a connection...\n");
        clientlen = sizeof(client);
        if ((clientfd = 
            accept(listenfd, (struct sockaddr *)&client, &clientlen)) < 0) {
            perror("accept");
            exit(4);
        }
        /* Read the array index UI has requested */
        readn(clientfd, &x, sizeof(x));
        printf("Read in request for array element %d\n", x);
        if ( x > ARRAY_SIZE || x < 0 ) {
            /* Error */
            result = 0;
        } else {
            result = bigarray_[x];
        }
        /* Print specifier for unsigned 64-bit integer*/
        printf ("Server sending back to client: %llu\n", result);
        if (writen(clientfd, &result, sizeof(result))  < 0 ) {
            exit(5);
        }
        close(clientfd);
    }
    exit(0);
}
Back to article


Listing Two
 1 #ifndef ABSOFT_H
 2 #define ABSOFT_H
 3 #include <stdint.h>
 4 #include <stdlib.h>
 5 #define SOCK_ADDR "/tmp/sock"
 6 #define LISTENQ 5
 7 /* When compiled as 64-bit, use larger array
 8  * (for demo the size is just 1 larger then 32-bit
 9  */
10 #ifdef __LP64__
11 #define ARRAY_SIZE 1001
12 #else
13 #define ARRAY_SIZE 1000
14 #endif /* __LP64__ */
15 /* Protos */
16 ssize_t readn(int fd, void *vptr, size_t n);
17 ssize_t writen(int fd, const void *vptr, size_t n);
18 uint64_t lookupValue(int32_t x);
19 #endif
Back to article


Listing Three
1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <errno.h>
 4 #include <string.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <sys/un.h>
 8 #include <sys/uio.h>
 9 #include <sys/fcntl.h>
10 #include <inttypes.h>
11 #include <stdint.h>
12 #include <unistd.h>
13 #include "absoft.h"
14 /* Lookup array value at index x
15  * by connecting to unix domain socket
16  */
17 uint64_t lookupValue(int32_t x)
18 {
19     int s;
20     struct sockaddr_un remote;
21     uint64_t result;
22     if ((s = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0 ) {
23         perror("socket");
24         return(0);
25     }
26     bzero(&remote, sizeof(remote));
27     printf("Trying to connect...\n");
28     remote.sun_family = AF_LOCAL;
29     strcpy(remote.sun_path, SOCK_ADDR);
30     if (connect(s, (struct sockaddr *)&remote, sizeof(remote)) < 0) {
31         perror("connect");
32         return(0);
33     }
34     printf("Connected and sending %d\n", x);
35     if (writen(s, &x, sizeof(x)) < 0 ) {
36         perror("send");
37         return(0);
38     }
39     readn(s, &result, sizeof(result));
40     printf ("Client received result from server = %llu\n", result);
41     close(s);
42     return  result;
43 }
Back to article


Listing Four
 1 #include "FetchDataThread.h"
 2 FetchDataThread::FetchDataThread(QObject *parent)
 3         : QThread(parent)
 4 {
 5 }
 6 FetchDataThread::~FetchDataThread()
 7 {
 8     cond.wakeOne();
 9     wait();
10 }
11 void FetchDataThread::fetchData(const int32_t x)
12 {
13     // Hold mutex until function exits
14     QMutexLocker locker(&mutex);
15     this->x = x;
16     if (!isRunning())
17         start();
18     else
19         cond.wakeOne();
20 }
21 void FetchDataThread::run()
22 {
23     QMutexLocker locker(&mutex);
24     int32_t xv = x;
25     // This is the call that blocks
26     uint64_t result = lookupValue(xv);
27     /* Minimal error checking. Returns 0 if error */
28     if ( result == 0  ) {
29         emit errorOccured("Error looking up value");
30         return;
31     } else {
32         QString str;
33         emit fetchedData( str.setNum(result) );
34     }
35 }
Back to article


Listing Five
CFLAGS= -Wall -Wformat -Wmissing-prototypes -Wconversion 
                                    -Wsign-compare -Wpointer-arith
      all: server
      server32: util.c server.c
          gcc $(CFLAGS) -m32 util.c server.c -o server32
      server64: util.c server.c
          gcc $(CFLAGS) -m64 util.c server.c -o server64
      server: server32 server64
          lipo -create server32 server64 -output server
      clean:
          rm -rf server32 server64 server 
Back to article


Listing Six
# Use The Qt utility "qmake" to build
# a Makefile from this file
TEMPLATE = app 
CONFIG += qt  release 
TARGET += 
DEPENDPATH += .
INCLUDEPATH += .
DEFINES += DIVORCE_UI
HEADERS += Viewer.h
HEADERS += absoft.h 
HEADERS += FetchDataThread.h
SOURCES += client.c
SOURCES += util.c
SOURCES += Viewer.cpp
SOURCES += FetchDataThread.cpp 
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video