» Overview» Code Migration» Low-Level Details» The Question of Data» Wrapping Up
Sun Microsystems sells Solaris platforms that run on two different chip architectures: Sun's own SPARC architecture and x86-based chips from AMD. These processor architectures are distinctly different. SPARC is a RISC architecture, which means that it uses designs that stress simple, equal-sized instructions that can be executed quickly; while x86 is based on CISC, which uses complex instructions of varying sizes. During the 1980's and 90's, there was constant and sometimes heated debate about which computing architecture was better. Since then, both designs have become well accepted, standard components of enterprise computing. Despite the platform differences, Sun has done an excellent job making Solaris look and run the same on both platforms. This similarity derives primarily from the fact that the codebase for both versions of Solaris is much the same. Only in the portions that talk directly to the hardware do the two implementations diverge.
One result of this consistency between Solaris on SPARC and on x86 is that the GUI and utilities run much the same, making the process of migration easier. However, a few key differences exist that can affect migration. These issues can be hard to resolve if you don't know where to look, but can ease the migration process if developers anticipate their occurrence. This article discusses the differences developers should consider when migrating existing software from SPARC to x86 (both 32- and 64-bit versions).
This article focuses primarily on details of code migration to the x64 version of Solaris; that is, the version that runs on AMD64 processors running in 64-bit mode. In many cases, this is the version you're most likely going to choose as the x86 platform, due to its expansive memory capabilities. If you don't have a copy of Solaris 10 for x64, see the links at the end of this article for information on downloading your own copy at no cost.
Java
As you might expect, migrating pure Java programs presents no difficulty. Only when Java programs rely on external resources or native code will there be matters to resolve. In the former case, data from non-Java programs might present some endian difficulties (see section on data migration below). In the latter, (such as using Java Native Interface JNI or other bridging technologies), you need to make sure that the native component you're communicating with has been properly migrated before you bring over the Java code.
If you cannot migrate the component first, consider using a mock object. Mock objects (or "mocks") are a technique used in unit testing to simulate a non-existent object. A simple mock object returns a hard-wired but valid data item when called correctly and a valid error code, when called incorrectly. Such mocks serve as placeholders for native components in JNI until the components can be ported.
C and C++
Unlike Java, C/C++ programs require care when porting. The first thing to do is to perform a static check of the code for any problems that might exist. Use lint or any of the many commercial code-checking tools to detect portability issues. They all point out subtleties, typically related to different sizes in pointers and data types that could disrupt proper operation of the code. The -xarch=amd64 compiler switch is another option that should be used for code being compiled for the new platform. It performs a lint-like scan of the code during compilation. However, it reports typically less than static code checkers, so it should be used as an adjunct rather than the principal checker.
One of the major areas of differences is the alignment of data fields in structures and unions. Whereas Solaris on SPARC defines several schemes for aligning data, the AMD64 application binary interface (ABI) uses a single approach: It repacks structures so that fields are aligned to the size of the largest data type within them. By having only one approach, it makes it possible for developers to compute correctly the size of aggregate data times. The ABI approach means that in many cases data structures will be laid out differently than their SPARC counterparts. If you're reading in binary data that relied on a SPARC-based structure layout, the data will need to be converted. (Beyond the layout, there might be additional conversions that are necessary as described in the section The Question of Data later in this article.)
The question of alignment is somewhat more complex, if programs are being migrated from SPARC v8 systems (which include 32-bit SPARC systems prior to 1998). Those systems aligned long doubles on 64-bit boundaries, whereas AMD64 aligns them on 128-bit boundaries. Similar peculiarities exist between other specific versions of Solaris on SPARC. As a result, it is best to assume that all binary data structures are suspect until verified. Should you decide to change the alignment of structures from the SPARC end (rather than converting it on the AMD end), one way to modify the default alignment of structures is to rely on the compiler using the pragma #pragma pack or the _Pack directive. This approach automates the process in part; although if the data contains numeric items, it still needs to be converted and so the techniques recommended in The Question of Data are probably the best bet.
On RISC systems, such as SPARC platforms, it is comparatively rare for developers to use assembly language. This is in large part due to the complex algorithms needed to sequence SPARC instructions exactly right to gain good performance. As a result, developers are encouraged to do low-level programming in C and let the compiler optimize the generation of assembly-level instructions.
Low-level work on AMD platforms more commonly use assembly language, especially for certain kinds of systems programming and for tuning the optimization of frequently executed loops. For many years, x86 systems have suffered from a reputation of having few general-purpose registers (GPRs). The AMD64 architecture greatly expands the number of GPRs that can be accessed by developers. This greater number-16 in total-makes it easier to write very efficient code. One way the AMD64 ABI makes use of these registers is to employ them (and some special-purpose registers) to pass parameters between functions. This step diminishes the need for stack manipulation every time a function is called.
Multimedia processing is done via a different set of registers. These are a set of sixteen 128-bit SIMD registers, called XMM 0-15. SIMD stands for "single-instruction, multiple-data" and it refers to the ability to load a register with multiple data items and execute a single instruction across all the data items simultaneously. This operation is particularly useful in performing the numerous data transforms required by the graphical and audio components of multimedia processing.
Such transformations are greatly accelerated by a set of extensions to the x86 and x64 instructions sets. These are called SSE, SSE-2, and SSE-3, depending on which generation of extensions is involved. (SSE stands for Streaming SIMD Extensions.) These SIMD instructions can be accessed directly using assembly language or they can be generated automatically by Sun Studio compilers. For the compilers use xarch=sse2 -m64 in lieu of the -xarch=amd64 option (discussed previously) to generate code for SSE-2 extensions, and -xarch=sse3 for the SSE-2 and SSE-3 extensions.
Finally, it's important to note that AMD64 has a separate, robust set of registers for arithmetic operations. It has sixteen 80-bit registers for use in integer and floating-point arithmetic. If more registers are needed, then the 128-bit XMM registers can also be used.
Developers should take into account the different formats used by SPARC and x86 platforms for storing binary data. Fortunately, many tools are available to help with the migration of binary data. The issue arises because the two architectures use a different byte-ordering scheme for numeric data. SPARC (and many RISC architectures) uses a big-endian layout, while x86-based designs use little-endian. Take, for example, a 16-bit integer, with the hexadecimal value ABCDh. On a SPARC system it occupies two bytes with ABh in the first byte (with the lower address in memory) followed by CDh in the second byte (with the higher address). On the x86 architecture, the same value will be stored as CDh in the lower address byte and ABh in the higher-essentially reversing the sequence of bytes. (Note: this problem also exists if numeric data is transferred via network between two systems without use of a protocol that handles the data format issues.)
To correctly move data between systems, the data on the SPARC machine should be converted to either a low-endian format, or preferably to an architecture-neutral format. There are several ways to do this. One way, familiar to SPARC developers who have previously faced this problem before, is to use External Data Representation (XDR), which is a protocol designed by Sun (and now an IETF standard) purposely for just such data transfers.
Today, however, the common format for this type of data conversion is XML. The SPARC system writes the data out to an XML file and the AMD Solaris system reads in the XML and recodes the data in the correct binary representation. The advantage of this approach is that the XML file becomes a document that can be inspected later if any error appears in the data; and the document can be used to validate the correctness of the data transformations.
One reminder: no transformation needs to be done on data that is written and read by Java systems. Java writes out numeric data using the same format regardless of whether a platform is big or little endian.
In most cases, migrating from SPARC to AMD x86 or x64 requires only a few of the steps discussed here. The most common non-Java scenario requires:
- Linting and compiling with the AMD64 switches enabled;
- Handling changes in the size of data types;
- Handling structures and unions in C so that alignment and padding are correct;
- Converting binary data from big-endian to little-endian.
The use of assembly language or of the low-level constructs specified in the AMD64 ABI should be done only after the initial port has been completed satisfactorily and the resulting program passes all tests. Then, to optimize performance—especially of multimedia applications—the low level features of the processor should be exploited. And there, of course, is where you will find a great richness of features in the AMD64 design.
Resources and Useful Links
Solaris 10 for x64 and x86 platforms is available at no charge from:
http://www.sun.com/software/solaris/get.jsp
The AMD64 ABI for Solaris (technically called: System V Application Binary Interface. AMD64 Architecture Processor Supplement)
http://www.x86-64.org/documentation/abi.pdf
Summary of key points of the AMD64 ABI for Solaris
http://docs.sun.com/app/docs/doc/816-5138/6mba6ua5s?a=view
Overview of Sun Studio 11 and Creating 64-bit Applications for AMD-based x86 Systems
http://developer.amd.com/TechnicalArticles/Articles/Pages/629200636.aspx
The compiler switches in Sun's latest release, which shows how to make good use of the AMD64 platform resources:
http://developers.sun.com/sunstudio/downloads/ssx/compilers/readme.html
Sun's own discussion of some of the data issues in migrating:
http://developers.sun.com/solaris/articles/support_for_x86.html
RFC 4506, which describes XDR:
http://www.ietf.org/rfc/rfc4506.txt
AMD Developer Central's Solaris Zone has additional articles and tools for developers migrating to or running Solaris on AMD systems:
http://developer.amd.com/cpu/partners/pages/SolarisZone.aspx
Anderson Bailey is a developer with a longstanding interest in the techniques for using code to exploit processor features. He can be reached at chip.coder@gmail.com.