Mike Abbott - mja@sgi.com
Apache/1.3.7 and beyond can be compiled as either a 32-bit or a 64-bit application, allowing use of a full 64-bit virtual address space and native 64-bit arithmetic. (However, neither 32-bit nor 64-bit Apache can serve files larger than about 2 GB; see below for details.) Currently Apache supports only SGI's 64-bit application binary interface (ABI) but undoubtedly other vendors will port it to their own 64-bit ABIs.
By default Apache compiles as a 32-bit application. To compile Apache as a 64-bit application, just enable configure's IRIX64 rule and run make as usual:
$ configure --enable-rule=IRIX64
$ make
However, there are some complications.
uname
command and see:
$ uname IRIX64
Your system cannot run 64-bit programs if you see:
$ uname IRIX
$ make distclean
Apache can be compiled using any of SGI's three ABIs -- known as old 32-bit or O32, new 32-bit or N32, and 64-bit or 64 (sometimes called new 64-bit or N64) -- on platforms that support them. Two of configure's rules, IRIXN32 and IRIX64, select the ABI for which Apache is compiled. The IRIX64 rule overrides the IRIXN32 rule. By default IRIXN32 is enabled and IRIX64 is disabled so Apache is compiled for the N32 ABI.
Apache ABI | IRIXN32 rule | IRIX64 rule |
---|---|---|
64-bit (64 or N64) | don't care | enabled |
new 32-bit (N32) | enabled (default) | disabled (default) |
old 32-bit (O32) | disabled | disabled (default) |
On non-SGI platforms Apache can be compiled using only a 32-bit ABI and the above rules do not apply.
SGI's 64-bit ABI -- and therefore 64-bit Apache -- uses an LP64
model, wherein long int
s and all pointers are 64 bits
wide (8 bytes) and int
s are 32 bits wide (4 bytes). Other
types remain the same.
Type | 32-bit ABI Size | 64-bit ABI Size |
---|---|---|
char |
8 bits | 8 bits |
short |
16 | 16 |
int |
32 | 32 |
long |
32 | 64 |
pointer | 32 | 64 |
float |
32 | 32 |
double |
64 | 64 |
The discussion and examples below will help you to write code that
works in both 32-bit and 64-bit Apache, but this is not a complete
porting guide. There is no substitute for passing code through a picky
64-bit compiler and cleaning up the warnings and errors. I recommend
always using the compiler option that produces the pickiest warnings
and fixing all the warnings it generates. The option to SGI's compilers
is -fullwarn
, used by default.
Programmers accustomed to 32-bit programming often assume that int
and long
are interchangeable, and that certain types
defined by the development environment are defined in certain ways.
Examples relevant to 64-bit Apache are the standard types size_t
, ssize_t
, off_t
,
and time_t
.
Type Name | Actual Type | Assumed Type |
---|---|---|
size_t |
unsigned long |
unsigned int |
ssize_t |
long |
int |
off_t |
long |
int |
time_t |
int |
long |
All of the following examples compile and run fine under 32-bit ABIs but induce compiler errors or warnings and can produce erroneous results under the 64-bit ABI:
int length = strlen(str); /* oops: strlen() returns size_t */ int r = read(fd, buf, n); /* oops: read() returns ssize_t */ struct stat sb; /* ok */ stat(filename, &sb); /* ok */ printf("%d\n", sb.st_size); /* oops: sb.st_size is off_t */ /* this produces a warning under SGI's N32 ABI too, see below */ long now; time(&now); /* oops: time() takes (time_t *) */
Here are the same examples, corrected for clean 32-bit and 64-bit compilation and operation:
size_t length = strlen(str); /* better */ int length = (int) strlen(str); /* an alternative */ int length = ap_strlen(str); /* another alternative */ int r = (int) read(fd, buf, n); /* better - see below */ struct stat sb; /* ok */ stat(filename, &sb); /* ok */ printf("%ld\n", (long) sb.st_size); /* better - see below */ time_t now; time(&now); /* better */
The general rule is: always use the appropriate type (size_t
,
etc.), not the base type you think that type is defined as. However,
most character strings are shorter than 2 GB so using int
to store a strlen()
result (with a cast to appease the
compiler) is just fine. ap_strlen()
is a convenience macro
that just casts strlen()
's result to int
.
See the sections below for the rule exceptions for file sizes and printfs.
Apache defines four types that are guaranteed to be the right size for
certain uses. These types are defined in the source file src/include/ap_types.h
.
Type Name | Description | Use |
---|---|---|
ap_int32 |
signed 32-bit integer | when at most or exactly 32 bits are required |
ap_uint32 |
unsigned 32-bit integer | when at most or exactly 32 bits are required |
ap_ptr |
unsigned pointer-sized integer (size varies) |
for storing and performing arithmetic on addresses |
ap_atomic |
unsigned atomically-updatable integer (at least as large as ap_ptr ) |
for atomic operations |
Use ap_int32
and ap_uint32
instead of int
/long
and unsigned int
/long
when you need at most
or exactly 32 bits. (You could use int
or unsigned
int
in SGI's 64-bit ABI, but future 64-bit ABIs may define int
as 64 bits.)
struct binary_log_entry { /* on-disk binary log entry format */ ap_uint32 ip_addr; /* IP address of requester */ ap_int32 time; /* time requested */ ap_int32 nbytes; /* number of body bytes in response */ /* ... */ }; #define FLAG0 ((ap_uint32) 0x00000001) #define FLAG1 ((ap_uint32) 0x00000002) /* ... */ #define FLAG30 ((ap_uint32) 0x40000000) #define FLAG31 ((ap_uint32) 0x80000000) /* (ap_uint32) cast avoids accidental 64-bit sign extension on FLAG31 */
That reminds me: Avoid using the L
or UL
suffix on constants; cast the constant to ap_int32
or ap_uint32
instead. Beware constants with bit 31 (0x80000000) set, including -1
and ~0
. Broken examples:
#define FOO 4096UL /* oops: scales */ #define BIG 0xFFFFFFFFL /* oops: sign-extends */ /* oops: in 64-bit Apache INADDR_NONE is 0xFFFFFFFFFFFFFF but * inet_addr() returns 0xFFFFFFFF on failure */ #define INADDR_NONE ((unsigned long) -1) unsigned long my_addr; if ((my_addr = inet_addr(w)) != INADDR_NONE) /* ... */;
Fixed examples:
#define FOO ((ap_uint32) 4096) /* better */ #define BIG ((ap_uint32) 0xFFFFFFFF) /* better */ /* better */ #define INADDR_NONE ((ap_uint32) -1) ap_uint32 my_addr; if ((my_addr = inet_addr(w)) != INADDR_NONE) /* ... */;
Use ap_ptr
for storing and performing arithmetic on
addresses. See below for details.
Use ap_atomic
for atomically-updatable data.
Pointers are the same size as int
s in 32-bit but not
64-bit programming environments. The following common constructs cause
problems:
int length = string2 - string1; /* oops: result is long */ unsigned int address = (unsigned int) &anything; /* oops: pointer is long */
Corrected examples:
size_t length = string2 - string1; /* better */ int length = (int) (string2 - string1); /* an alternative */ unsigned long address = (unsigned long) &anything; /* better */ ap_ptr address = (ap_ptr) &anything; /* an alternative */
Use ap_ptr
, size_t
, or ptrdiff_t
for pointer manipulation unless you're sure the result of pointer
subtraction is a small integer in which case cast and assign the result
to int
.
To store a constant or integer value into a variable having a pointer
type without compiler warnings, you must first cast the data to the
scaling type ap_ptr
and then cast that to the pointer
type, like this:
void *vp = (void *) (ap_ptr) 1; struct myfd *fp = (struct myfd *) (ap_ptr) fd; /* fd is int */
(I think this is yucky but I couldn't find a better way.)
In SGI's 64-bit ABI file sizes (off_t
) are 64 bits wide,
i.e., files can be larger than 4 GB (theoretically up to 16 exabytes
[EB]). (Incidentally, off_t
is 64 bits wide in SGI's N32
ABI too, but not SGI's O32 ABI.) However, 64-bit Apache currently
forbids serving files whose sizes overflow 32 bits. It is possible to
enhance Apache to serve very large files but I chose not to do so. The
function ap_oversized_file()
returns nonzero if a file is
too large to serve. Here is an example, heavily trimmed for clarity, of
how to serve a file in both 32-bit and 64-bit Apache:
struct stat sb; int fd, nbytes; stat(filename, &sb); if (ap_oversized_file(sb.st_size)) { ap_log_rerror(APLOG_MARK, APLOG_ERR | APLOG_NOERRNO, request, "%s: File too large", filename); return HTTP_NOT_IMPLEMENTED; } fd = open(filename, O_RDONLY); while ((nbytes = (int) read(fd, buf, bufsiz)) > 0) ap_rwrite(request, buf, nbytes); close(fd); return OK;
The example checks the validity of the file size first, and safely
truncates the result from read()
to an int
.
At first glance it seems difficult to format output using printf()
, sprintf()
,
and the like without a priori knowledge of type sizes. In a 32-bit ABI %d
(and %o
, %u
, and %x
) and %ld
(and %lo
, %lu
, and %lx
) convert
a 32-bit integer, but in a 64-bit ABI %d
(et al) converts
a 32-bit integer and %ld
(et al) converts a 64-bit
integer. The following example is erroneous under both ABIs:
struct stat sb; /* ok */ stat(filename, &sb); /* ok */ printf("size is %d\n", sb.st_size); /* oops: st_size scales */ printf("mod time is %ld\n", sb.st_mtime); /* oops: st_mtime is int */
There are solutions that I consider messy, such as using #ifdef
to select between different format strings or using #define
to select conversion strings and string concatenation to insert them
into the output string, and one that I think is simple and clean:
Always use the long form conversions (%ld
, %lx
,
etc.) and cast the data to long
or unsigned long
,
no matter what its original type.
struct stat sb; /* ok */ stat(filename, &sb); /* ok */ printf("size is %ld\n", (long) sb.st_size); /* better */ printf("mod time is %ld\n", (long) sb.st_mtime); /* better */ printf("pid is %ld\n", (long) getpid()); printf("address is %#lx\n", (unsigned long) &mumble); printf("sent %lu bytes\n", (unsigned long) nbytes);
SGI's 64-bit compiler checks printf-like format conversions against the
type of the corresponding arguments and generates warnings when they
don't match (%d
with an argument of type long
,
for instance). If you write your own formatting routine, preface its
definition and declarations with the lint-style comment /*PRINTFLIKEn*/
to allow the compiler to compare the conversions with the arguments. n
is the ordinal of the format string.
/*PRINTFLIKE2*/ extern int myprintf(int mystuff, const char *fmt, ...); myprintf(xyz, "this page visited %ld times\n", (long) count);
Beware shifting, anding or oring (<< >> & |
operators) beyond 32 bits and performing arithmetic (+ - * /
operators) that might over- or underflow 32 bits. Truncation to 32
bits is implicit in a 32-bit ABI but not in a 64-bit ABI.
SGI's struct timeval
(defined in <sys/time.h>
)
is defined such that the tv_sec
field is 32 bits and the tv_usec
field is 64 bits. I have no idea why; it seems wrong to me. But
anyway, this causes the compiler to warn about the following construct:
tv.tv_sec = tv.tv_usec = 0; /* oops: assigning 64 bits to 32 */
The simple workaround is to separate this into two lines:
tv.tv_sec = 0; /* better */ tv.tv_usec = 0;
64-bit Apache actually runs slightly slower than 32-bit Apache due to the increased word size but it allows the mmap_static module to map enormous amounts of file data.
As mentioned above, the only 64-bit ABI Apache supports currently is
SGI's. Porting to other 64-bit ABIs that use the LP64 model should be
straightforward, probably consisting mostly of enabling the equivalent
of the -fullwarn
compiler option to identify problem areas
and modifying Apache's source code to handle your system's type quirks (size_t
, off_t
,
etc.) if they differ from SGI's. Porting to an ILP64 model, where int
s
are also 64 bits, will require a larger effort to change all uses of int
.
SGI's MIPSpro 64-Bit Porting and Transition Guide contains excellent detailed information about porting 32-bit applications to 64 bits.
Refer to your system's programming documentation for details about its 64-bit implementation.