Bit Operations with C Macros

John lays the foundation for an "endian engine" that handles a variety of byte orders--from the 68K Big-endian and Intel Little-endian, to the PowerPC's Bi-endian format.


October 01, 1995
URL:http://www.drdobbs.com/cpp/bit-operations-with-c-macros/184409730

SP95: Bit Operations with C Macros

John is a programmer in the Seattle area. He can be contacted on CompuServe at 72634,2402.


Endian refers to a processor addressing model that defines the byte ordering of data and instructions stored in computer memory. The most common addressing models are Big-endian (left-to-right order) and Little-endian (right-to-left). Intel-based processors (80x86, Pentium, and the like) are Little-endian, while others (such as the Motorola 680x0 in the Macintosh) are Big-endian. Still others, particularly the PowerPC, are "Bi-endian," allowing them to run in either Big-endian or Little-endian mode (see the accompanying text box entitled "PowerPC Bi-Endian Capabilities," by James R. Gillig).

As straightforward as this sounds, "endianness" can be confusing for programmers--particularly when developing portable software running on a variety of platforms. To address this confusion, I've developed an "endian engine" which handles every byte order. This engine is presented in my article "Your Own Endian Engine," (Dr. Dobb's Journal, November 1995). The heart of the engine is a powerful set of C macros that perform bitwise operations, which I'll discuss in this article. It's noteworthy that the examples I implement to handle these C macros are designed to handle instructions for MMIX, a hypothetical computer developed by Donald Knuth (see the accompanying text box entitled "MMIX: Knuth's New Computer").

I invented some of the macros myself and modeled others on Fortran functions. Together, they comprise a complete set of macros for manipulating bits. Since ANSI C says the value of a right-shifted negative number is"implementation-defined," I've defined all of the macros for operands with unsigned integral types, just to be on the safe side. Listing One, bitops.h, has all of the C macros discussed in this article. The complete source code to accompany this article is available electronically; see "Availability," page 3.

Except for MVBITS, all of the macros in bitops.h return values rather than updating parameters; see, for instance, the ALL_ZERO_BITS(type) macro in Example 1. The companion macro, ALL_ONE_BITS(type), is analogous.

Bit Numbering

Many of the macros here use bit numbering. By convention, the least significant bit (LSB) is bit 0. Some of the macros indicate one or more contiguous bits in a value, using the convention of a start-bit number and a length in bits. You give the bit number of the lowest bit in the range of bits that you want. For instance, to use bits 0 through 3, give a start-bit number of 0 and a length of 4.

Conveniently, a variety of specifications and standards adhere to the LSB convention of numbering as bit 0. Most Intel processors, the IEEE MUFOM (microprocessor universal format for object modules), and the MIL-STD FORTRAN functions all use this convention. The only major exception is IBM mainframes, which number the most significant bit (MSB) as bit 0.

Fortran-Inspired Macros

Since at least the 1970s, many versions of Fortran have included a standard set of bit functions that include the usual operations: AND, OR, NOT, and exclusive-OR. These Fortran functions also include routines for bit extraction, insertion, shift, and circular shift. Rather than reinvent the wheel, I've used the same function names and operand orders. However, since the Fortran functions were designed for implementations with just one integer type, and C has many sizes of integer types (ranging from char to long), I added, where necessary, an additional parameter (at the end) to indicate the data type to be returned. This must be some unsigned integral type.

As for the Fortran-inspired macros, I'll start with NOT(value,type) (bitwise complement), sometimes called the "flip bits" or "invert" operation. In Fortran, the NOT(value) function has one operand (an integer value) and returns an integer: the inverted value. The C NOT(value,type) macro has an additional operand, which must be an unsigned integral data type. The NOT(value,type) macro converts the given value to the given type and returns the converted value with all of the bits inverted. For instance, in an implementation with 8-bit characters, NOT(0xF0, unsigned char) would be 0x0F.

The IAND(m,n,type) ("integer and") macro simply performs a bitwise-AND of the bits in the first two operands, which are treated as type type, and returns the result. It supports any unsigned integer type for its operands. Kenneth Hamilton used the Fortran version of this macro in his article "Direct Memory Access from PC Fortrans" (Dr. Dobb's Journal, May 1993). Hamilton's code needed to extract the low byte from some integer value. Using the C macros in bitops.h, the equivalent would be:

unsigned int ic1;
ic1=IAND(ic,255,unsigned int);

There is an integer extract bits (IBITS) macro that extracts and right-justifies one or more contiguous bits from a given value. IBITS is called as IBITS(value,bitnum,len,type). For instance, IBITS(0x5678,8,4,unsigned long) returns an unsigned-long value of 0x6.

Another Fortran-inspired macro is the integer shift (ISHFT) macro, a call to which appears as ISHFT(value,shifts,type). ISHFT and its circular-shift companion ISHFTC are unique in that they indicate the direction to shift by positive or negative values of the shifts parameter. A positive value for shifts causes a left shift by that many bits; a negative value causes a right shift by that many bits; a 0 value causes no shift. Make sure the absolute value of shifts is less than or equal to the size of type in bits; otherwise, the result is undefined.

For a Fortran version of the ishft routine, see Ray Duncan's "16-Bit Software Toolbox" column (Dr. Dobb's Journal, August 1985).

Unlike the other macros in bitops.h, MVBITS updates a value (using a pointer passed to it) rather than returning a value. The call MVBITS(src,srcindex,len,destptr,destindex,type) updates the value at *destptr (starting at bit destindex for len bits) with len bits extracted from src starting at bit number srcindex. Example 3 shows an example of using MVBITS.

BitOps Examples Using MMIX

In the examples from here on, I'll use the bitops.h C macros to handle instructions for MMIX. The parts of an MMIX instruction are multiples of 8 bits each, but the bitops.h macros don't depend on this.

A normal MMIX instruction is 32 bits long and broken into four fields of 8 bits each; see Table 1. Three fields generally refer to registers or contain immediate values. Knuth refers to these fields as X, Y, and Z. In some other instructions, Knuth combines the Y and Z fields for 16 bits. In still other instructions, he combines the X, Y, and Z fields into a 24-bit field.

For starters, I'll define a type to hold an instruction, keeping in mind that ANSI C implicitly requires unsigned long to hold 32 bits or more. Remember that ANSI C does not require any particular byte order when storing larger-than-byte objects in memory. Using a trailing _T convention to indicate a type, you can define a type (MMIX_Instr_T) to contain the object code for one instruction, as shown in mmixcom.h (Listing Two).

Taking advantage of the implicit ANSI C requirement that unsigned char be 8 bits or larger, mmixcom.h also defines types for bytes in general and instruction opcodes in particular. I call these types MMIX_Byte_T and MMIX_Opcode_T, respectively.

To use the bitops.h macros to extract the opcode from an instruction, for example, you need to specify start-bit numbers and bit lengths. Listing Two also contains equates called MMIX_INSTR_OPCODE_START and MMIX_INSTR_OPCODE_LEN for this.

Given those bit numbers, you can use the IBITS (integer extract bits) macro to extract the opcode from an instruction; see Example 2. You will recall that IBITS right-justifies its result.

MMIX also stores the X, Y, and Z fields as bytes. Example 3 shows how to create an instruction from scratch using MVBITS. In this case, I am creating an instruction to set r40 (register 40) to the unsigned sum of registers 41 and 42.

Shifting Bits with MMIX and bitops.h

MMIX has an SRU (shift right unsigned) instruction. SRU r3=r4r5 is a shift-right unsigned instruction in Knuth's current assembler syntax that sets register 3 to register 4 shifted right by the number of bits indicated in register 5. If the value in register 5 is greater than or equal to the size of a register in bits, then register 3 will be set to zero. Example 4 shows a short routine that simulates the SRU instruction using the bitops.h RIGHT_SHIFT_BITS macro.

You can readily emulate MMIX's XOR instruction using the bitops.h IEOR (integer exclusive-OR) macro; see Example 5.

MMIX is perhaps unique among instruction sets in having a nor bits (NOR) instruction. You may be familiar with NOR and NAND gates from digital electronics. The corresponding bitops.h macro is NOR_BITS. Example 6 shows how the NOR_BITS macro may be used to simulate MMIX's NOR instruction.

Conclusion

C provides some powerful bitwise operators, although you need to be careful regarding the widening of values between different data types. The macros in bitops.h provide a more complete set of bitwise operations than bare C, with protection against widening problems, although you must avoid macro arguments with side effects. You should also avoid using any of the signed data types with the macros.

References

ANSI X3.159-1989, American National Standard for Information Systems--Programming Language--C. New York, NY: American National Standards Institute (ANSI), 1989.

Knuth, Donald E. MMIX. Private communication, August 20, 1992.

MIL-STD-1753. Military Standard: FORTRAN, DOD Supplement to American National Standard X3.9-1978. November 9, 1978. Available free from the Defense Printing Service at 215-697-2179.

MMIX: Knuth's New Computer

Back in the 1960s, Donald Knuth designed a hypothetical computer called "MIX" for his Art of Computer Programming algorithms books. MIX shows its age in various ways, so Knuth is designing a RISC-like successor called "MMIX" (short for "Meta-MIX" or "Mega-MIX"). He started from scratch to avoid the restrictions of the old architecture. The new computer incorporates Big-endian byte ordering, byte addressing, two's-complement integer arithmetic, and IEEE floating-point arithmetic.

Knuth has not yet published his description of MMIX. His latest draft is dated August 20, 1992. He expects to make many technical changes in his next draft, due sometime in 1995, so details given here may change as well.

Knuth plans to use MMIX for the later volumes of the Art of Computer Programming series. I myself hope to write a cross assembler and simulator for MMIX for publication in Dr. Dobb's Journal. This drives my exploration of "big-integer" (64-bits or more) routines for C, as well as 64-bit, portable object-file formats (like MUFOM and ELF).

In MMIX, Knuth has adopted the common definition of a byte as having 8 bits. He is much more generous with registers; MMIX has 256 general-purpose registers. Knuth has also accounted for other practical issues this time. His description of MMIX floating point acknowledges that on some models, the system may trap floating-point "instructions" and interpret them in software. MMIX is supposed to have virtual memory, although the current draft doesn't seem to have enough detail for an operating system to deal with page faults or page tables.

The 1992 draft defines a 32-bit system, but Knuth is likely to convert to 64 bits before he publishes the final version. He has also had second thoughts about a number of complications the draft introduced: regions, probable branches, and delay slots, for example.

Regions are a simple way to provide multiple address spaces, kind of like segment registers. The delay slot avoids having to refill the prefetch buffer because of the branch. I first saw delay slots being used on the MIPS when I worked at Microsoft. Many of us with previous assembly-language experience kept forgetting that the instruction in the delay slot would execute, too.

Probable branches and delay slots are, in my opinion, architectural warts to improve pipeline performance while driving the assembly-language programmer crazy. In a pipelined system, the instruction right after a branch has already been prefetched, so why not execute it?

It seems, however, that Knuth is reconsidering these additions, and they will probably be dropped from the next draft.

--J.R.

PowerPC Bi-Endian Capabilities

Jim Gillig

James R. is a software engineer on OS/2 and IBM Workplace technologies in Boca Raton, FL. He can be reached through the DDJ offices.<

The PowerPC is a Bi-endian RISC processor that supports both Big- and Little-endian addressing models. The Bi-endian architecture provides hardware and software developers with the flexibility to choose either mode when migrating operating systems and applications from their current BE or LE platforms to the PowerPC. Program instructions are like multibyte-scalar data and are subject to the byte-order effect of Endianness.

Each individual PowerPC machine instruction occupies an aligned word in storage as a 32-bit integer containing that instruction's value. In general, the appearance of instructions in memory is of no concern to the programmer. Program code in memory is inherently either a LE or BE sequence of instructions even if it is an Endian-neutral implementation of an algorithm.

How does the PowerPC handle both LE and BE addressing models? The processor calculates the effective address of data and instructions in the same manner whether in BE mode or LE mode; when in LE mode only, the PowerPC implementation further modifies the effective address to provide the appearance of LE memory to the program for loads and stores.

The operating system is responsible for establishing the Endian mode in which processes execute. Once a mode is selected, all subsequent memory loads and stores will be affected by the memory-addressing model defined for that mode. Byte-alignment and performance issues need to be understood before using an endian mode for a given application. Alignment interrupts may occur in LE mode for the following load and store instructions:

For multibyte-scalar operations, when executing in LE mode, the current PowerPC processors take an alignment interrupt whenever a load or store instruction is issued with a misaligned effective address, regardless of whether such an access could be handled without causing an interrupt in BE mode. For code that is compiled to execute on the PowerPC in LE mode, the compiler should generate as much aligned data and as many aligned instructions as possible to minimize the alignment interrupts. Generally, more alignment interrupts will occur in LE mode than in BE mode. When an alignment interrupt occurs, the operating system should handle the interrupt by software emulation of the load or store.

A very powerful feature of the PowerPC architecture is the set of integer load-and-store instructions with byte reversal that allow applications to interchange or convert data from one Endian type to the other, without performance penalty. These load-and-store instructions are lhbrx/sthbrx, load/store halfword byte-reverse indexed and lwbrx/stwbrx, load/store word byte-reverse indexed. They are ideal for emulation programs that handle LE-type instructions and data, such as the emulation of the Intel instruction set and data. These instructions significantly improve performance in loading and storing LE data while executing PowerPC instructions in BE mode and emulating the Intel instruction behavior; this eliminates the byte-alignment and data-conversion overhead found in architectures that lack byte-reversal instructions. Currently, these instructions can be accessed only through assembly language. Until C compilers provide support to automatically generate the right load and store instructions for this type of data, C programs can rely on masking and concatenating operations or embed the assembly-language byte-reversal instructions.

Table 1: MMIX normal instruction layout.

Contents                   Start-bit #  Len
opcode                         24           8
X (usually target register)    16           8
Y (usually source register)     8           8
Z (usually source register)     0           8

Example 1: Simple C macro.

unsigned short x;
x = ALL_ZERO_BITS(unsigned short);

Example 2: Extracting an opcode using the IBITS macro.

#include "mmixcom.h"  /* MMIX_Opcode_T, etc. */
MMIX_Instr_T  Current_Instruction;
MMIX_Opcode_T Current_Opcode;
   ...
/* Assume Current_Instruction has already been set. */
/* IBITS right-justifies result, so use it to extract opcode. */
Current_Opcode = (MMIX_Opcode_T) IBITS(
      Current_Instruction,           /* value */
      MMIX_INSTR_OPCODE_START,       /* start bit num */
      MMIX_INSTR_OPCODE_LEN,         /* len */
      MMIX_Instr_T);                 /* type */
      
Example 3: Creating an instruction with the MVBITS macro.
MMIX_Instr_T  New_Instr = ALL_ZERO_BITS(MMIX_Instr_T);
/* Set opcode. */
MVBITS(
      0xC2,                    /* ADDU opcode */   /* src */
      0,                       /* src index: src bit 0. */
      MMIX_INSTR_OPCODE_LEN,   /* len */
      &New_Instr,              /* dest ptr */
      MMIX_INSTR_OPCODE_START, /* dest index */
      MMIX_Instr_T);           /* type */
/* Set X (target) field to say r40. */
MVBITS(
      40,                     /* register 40 */  /* src */
      0,                      /* src index: src bit 0. */
      MMIX_INSTR_X_LEN,       /* len */
      &New_Instr,             /* dest ptr */
      MMIX_INSTR_X_START,     /* dest index */
      MMIX_Instr_T);          /* type */
/* Set Y (a source field) to r41. */
MVBITS(
      41,                    /* register 41 */  /* src */
      0,                     /* src index: src bit 0. */
      MMIX_INSTR_Y_LEN,      /* len */
      &New_Instr,            /* dest ptr */
      MMIX_INSTR_Y_START,    /* dest index */
      MMIX_Instr_T);         /* type */
/* Set Z (the other source field) to r42. */
MVBITS(
      42,                   /* register 42 */  /* src */
      0,                    /* src index: src bit 0. */
      MMIX_INSTR_Z_LEN,     /* len */
      &New_Instr,           /* dest ptr */
      MMIX_INSTR_Z_START,   /* dest index */
      MMIX_Instr_T);        /* type */
      
Example 4: Using the RIGHT_SHIFT_BITS macro to simulate an SRU instruction.
#include "mmixcom.h"  /* MMIX_Word_T, MMIX_WORD_LEN, etc. */
MMIX_Word_T
Sim_SRU(  /* Simulate shift right unsigned instr. */
   MMIX_Word_T  Source_Reg,
   MMIX_Word_T  Shift_Count_Reg)
{
   if (Shift_Count_Reg >= MMIX_WORD_LEN)
      return (0);
   return (RIGHT_SHIFT_BITS(
         Source_Reg,            /* value */
         Shift_Count_Reg,       /* shifts */
         MMIX_WORD_LEN,         /* len */
         MMIX_Word_T));         /* type */
}

Example 5: Using the IEOR macro to simulate an XOR instruction.

MMIX_Word_T
Sim_XOR(  /* Simulate exclusive-OR bits instr. */
   MMIX_Word_T  Some_Bits,
   MMIX_Word_T  Other_Bits)
{
   return (IEOR(
         Some_Bits,
         Other_Bits,
         MMIX_Word_T));  /* type */
}

Example 6: Using the NOR_BITS macro to simulate a NOR instruction.

MMIX_Word_T
Sim_NOR(  /* Simulate NOR bits instr. */
   MMIX_Word_T  Some_Bits,
   MMIX_Word_T  Other_Bits)
{
   return (NOR_BITS(
         Some_Bits,
         Other_Bits,
         MMIX_Word_T));  /* type */
}

Listing One

/* BitOps.h - bit operation macros. Copyright (c) 1987-1994 by JR 
 * (John Rogers). All rights reserved. CompuServe: 72634,2402
 * Permission is granted to use these macros in compiled code without payment 
 * of royalties or inclusion of a copyright notice.  This source file
 * may not be sold without written permission from the author.
 *
 * The following macros are inspired by the FORTRAN bit operation routines in 
 * MIL-STD-1753. Except for MVBITS, all return values rather than modifying
 * parameters. MVBITS updates a parameter and does not return anything. The 
 * leading "I" in most of these names means that they return some kind of 
 * integer result.
 *    BTEST(value,bitnum,type)
 *    IAND(m,n,type)
 *    IBCLR(value,bitnum,type)
 *    IBITS(value,bitnum,len,type)
 *    IBSET(value,bitnum,type)
 *    IEOR(m,n,type)
 *    IOR(m,n,type)
 *    ISHFT(value,shifts,type)
 *    ISHFTC(value,shifts,len,type)
 *    MVBITS(src,srcindex,len,destptr,destindex,type)
 *    NOT(value,type)
 * The following C macros were invented by me (JR) or various other C 
 * programmers; all return values rather than modifying parameters:
 *    ALL_ONE_BITS(type)
 *    ALL_ZERO_BITS(type)
 *    BIT_NUM_AND_LEN_TO_MASK(bitnum,len,type)
 *    BIT_NUM_TO_MASK(bitnum,type)
 *    CLEAR_BITS_USING_MASK(value,mask,type)
 *    FLIP_BITS_USING_MASK(value,mask,type)
 *    LEFT_CIRCULAR_SHIFT_BITS(value,shifts,len,type)
 *    LEFT_SHIFT_BITS(value,shifts,len,type)
 *    NAND_BITS(m,n,type)
 *    NOR_BITS(m,n,type)
 *    RIGHT_CIRCULAR_SHIFT_BITS(value,shifts,len,type)
 *    RIGHT_SHIFT_BITS(value,shifts,len,type)
 *    SET_BITS_USING_MASK(value,mask,type)
 *    TEST_BITS_USING_MASK(value,mask,type)
 *    TYPE_SIZE_IN_BITS(type)
 *    XNOR_BITS(m,n,type)
 * Beware of side effects: many macros in this file evaluate their arguments
 * more than once.  These are marked with EVALTWICE comments.
 */
/* Gracefully allow multiple includes of this file. */
#ifndef BITOPS_H
#define BITOPS_H
/******************* I N C L U D E S *****************/
/*lint -efile(766,limits.h) */
#include <limits.h>    /* CHAR_BIT. */
/*********************** M A C R O S  ****************/
/* ALL_ONE_BITS(type): Generate a value of type "type" with all bits set to 
 * 1. "type" must be an unsigned integral type.
 */
#define ALL_ONE_BITS(type)      ( (type) ~((type)0) )
/* ALL_ZERO_BITS(type): Generate a value of type "type" with all bits set to 
 * 0.  "type" must be an unsigned integral type.
 */
#define ALL_ZERO_BITS(type)     ( (type) 0 )
/* BIT_NUM_AND_LEN_TO_MASK(bitnum,len,type): Return a mask of type "type", 
 * with "len" bits on, starting at "bitnum".  Bit 0 is LSB, bits start at 
 * "bitnum" and are turned on in the mask starting at "bitnum" and going to 
 * the left. "type" must be an unsigned integral type.
 */
/*EVALTWICE*/
#define BIT_NUM_AND_LEN_TO_MASK(bitnum,len,type) \
   /*CONSTCOND*/ \
   /*lint -save -e506 -e572 -e778 */ \
   ( (type) \
      ( \
         ( (ALL_ONE_BITS(type)) \
            >> ((TYPE_SIZE_IN_BITS(type)) \
               - ((bitnum)+(type)(len)) ) ) \
         &  ( \
               ((bitnum)>0) \
               ? ~( ALL_ONE_BITS(type) \
                  >> ( (TYPE_SIZE_IN_BITS(type)) \
                     - (bitnum) ) ) \
               : ALL_ONE_BITS(type) ) \
      ) \
   ) \
   /*lint -restore */
/* BIT_NUM_TO_MASK(bitnum,type): Convert bit number "bitnum" to mask of type 
 * "type".  Bits are numbered from right to left, with bit 0 being the least
 * significant bit (LSB). "type" must be an unsigned integral type.
 * This is my (JR's) modification of something posted to Usenet by Bill 
 * Shannon ([email protected]) many years ago.
 */
#define BIT_NUM_TO_MASK(bitnum,type) \
   ( (type) ( ((type)1) << ((type)(bitnum)) ) )
/* BTEST(value,bitnum,type): Test bit numbered "bitnum" in "value", which must
 * be of type "type".  If the tested bit is on, return a Boolean true value 
 * (1); otherwise, return a Boolean false (0). "type" must be an unsigned 
 * integral type.
 */
#define BTEST(value,bitnum,type) \
   ( ( (value) & (BIT_NUM_TO_MASK((bitnum),type)) ) \
     ? 1 : 0 \
   )
/* CLEAR_BITS_USING_MASK(value,mask,type): Return "value", except that any bits
 * which are turned on in "mask" will be turned off in the return value.
 * "type" must be an unsigned integral type.
 */
#define CLEAR_BITS_USING_MASK(value,mask,type) \
   ( (type) ( (value) & ~(mask) ) )
/* FLIP_BITS_USING_MASK(value,mask,type): Return "value", except that any bits
 * which are turned on in "mask" will be flipped (toggled) in the return
 * value.  "type" must be an unsigned integral type.
 */
#define FLIP_BITS_USING_MASK(value,mask,type) \
   ( (type) ( (value) ^ (mask) ) )
/* IAND(m.n,type): Return the bitwise "and" of the integral values "m" and "n".
 * "type" must be an unsigned integral type.
 */
#define IAND(m,n,type) \
   ( (type) ( (m) & (n) ) )
/* IBCLR(value,bitnum,type):  Return "value" with bit at "bitnum" cleared 
 * (zeroed). "type" must be an unsigned integral type.
 */
#define IBCLR(value,bitnum,type) \
   ( \
      (type) CLEAR_BITS_USING_MASK( \
         (value), \
         BIT_NUM_TO_MASK( (bitnum), type ), \
         type) \
   )
/* IBITS(value,bitnum,len,type): Extract bits from "value", starting at bit 
 * "bitnum", for "len" bits. The result will be right justified. "type" must be
 * an unsigned integral type.
 */
/*EVALTWICE*/
#define IBITS(value,bitnum,len,type) \
   /*CONSTCOND*/ \
   /*lint -save */ /* Preserve PC-LINT options. */ \
   /*lint -e572 */ /* Ignore excessive shift val */ \
   /*lint -e778 */ /* Ignore const expr eval to 0 */ \
   ( (type) \
      ( \
         ( (value) & \
            (BIT_NUM_AND_LEN_TO_MASK( \
               (bitnum), (len), type )) ) \
         >> (bitnum) \
      ) \
   ) \
   /*lint -restore */
/* IBSET(value,bitnum,type):  Return "value" with bit at "bitnum" set to true.
 * "type" must be an unsigned integral type.
 */
#define IBSET(value,bitnum,type) \
   ( (type) \
      ( \
         SET_BITS_USING_MASK( \
            (value), \
            BIT_NUM_TO_MASK( (bitnum), type ), \
            type) \
      ) \
   )
/* IEOR(m.n,type): Return the bitwise exclusive-or of the integral values "m" 
 * and "n".  "type" must be an unsigned integral type.
 */
#define IEOR(m,n,type) \
   ( (type) ( (m) ^ (n) ) )
/* IOR(m.n,type): Return the bitwise "or" of the integral values "m" and "n".
 * "type" must be an unsigned integral type.
 */
#define IOR(m,n,type) \
   ( (type) ( (m) | (n) ) )
/* ISHFT(value,shifts,type): Return "value" with bits logically shifted as 
 * specified by "shifts".  Zeros will be shifted-in as applicable. A positive 
 * amount for "shifts" causes a left shift; a negative amount causes a right 
 * shift; a zero amount causes no shift. Note that the absolute value of 
 * "shifts" must be less than or equal to TYPE_SIZE_IN_BITS("type"). Also note
 * that "value" must be of type "type", and "type" must be an unsigned 
 * integral type.
 */
/*EVALTWICE*/
#define ISHFT(value,shifts,type) \
   /*CONSTCOND*/ \
   /*lint -save */ /* Preserve PC-LINT settings. */ \
   /*lint -e504 */ /* Ignore unusual shift value */ \
   /*lint -e778 */ /* Ignore const expr eval to 0 */ \
   ( (type) \
      ( ((shifts)>0) \
         ? ( (value) << (shifts) ) \
         : ( ( (shifts)<0 ) \
            ? ( (value) >> (-(shifts)) ) \
            : (value) \
            ) \
      ) \
   ) \
   /*lint -restore */
/* ISHFTC(value,shifts,len,type): Return "value" with bits circularly shifted
 * (as specified by "shifts") within the lower "len" bits of "value". A 
 * positive amount for "shifts" causes a left shift; a negative amount causes 
 * a right shift; a zero amount causes no shift. Note that the absolute value 
 * of "shifts" must be less than or equal to "len".  Also note that "value"
 * must be of type "type", and "type" must be an unsigned integral type. "len"
 * must be greater than 0 and less than or equal to TYPE_SIZE_IN_BITS("type").
 */
/*EVALTWICE*/
#define ISHFTC(value,shifts,len,type) \
   /*lint -save -e501 */ \
   ( (type) ( \
      ( ((shifts) == 0) \
         || ((len) == (type) (shifts)) \
         || ((len) == - (type) (shifts)) ) \
      ? ((type)(value)) \
      : ( \
        ( (shifts) > 0 ) \
        ? (RIGHT_CIRCULAR_SHIFT_BITS( \
           (value), (shifts), (len), type) ) \
        : (LEFT_CIRCULAR_SHIFT_BITS( \
           (value), (type) (- (shifts)), \
           (len), type) ) ) \
      ) \
   ) /*lint -restore */
/* LEFT_CIRCULAR_SHIFT_BITS(value,shifts,len,type): Return "value" with bits 
 * circularly shifted left "shifts" bits within the lower "len" bits of
 * "value". A zero amount for "shifts" causes no shift. Note that "shifts" 
 * must be less than or equal to  "len". Also note that "value" must be of type
 * "type", and "type" must be an unsigned integral type. "len" must also be 
 * greater than zero and less than or equal to TYPE_SIZE_IN_BITS("type").
 */
/*EVALTWICE*/
#define LEFT_CIRCULAR_SHIFT_BITS( \
  value,shifts,len,type) \
  /*lint -save -e504 */ \
  ( (type) ( \
     ((shifts)==0) || ((len)==(type) (shifts)) \
     ? (value) \
     : ( ( (value) & \
           ~BIT_NUM_AND_LEN_TO_MASK(0,(len),type) ) \
       | ( ( (value) & (BIT_NUM_AND_LEN_TO_MASK( \
           0, (len), type )) ) \
           >> (shifts) ) \
       | ( ( (value) & (BIT_NUM_AND_LEN_TO_MASK( \
           0, (shifts), type )) ) \
           << ((len)-(type)(shifts)) ) ) ) \
   ) /*lint -restore */
/* LEFT_SHIFT_BITS(value,shifts,len,type): Return "value" with bits logically 
 * shifted left "shifts" bits within the lower "len" bits of "value".  If
 * necessary, zero bits are added on the right.  A zero amount for "shifts" 
 * causes no shift. Note that "shifts" must be less than or equal to "len".  
 * Also note that "value" must be of type "type", and "type" must be an 
 * unsigned integral type. "len" must also be greater than zero and less than 
 * or equal to TYPE_SIZE_IN_BITS("type").
 */
/*EVALTWICE*/
#define LEFT_SHIFT_BITS(value,shifts,len,type) \
   /*lint -save -e504 */ \
   ( (type) ( \
      ( ((shifts)==0) || ((len)==(type) (shifts)) ) \
      ? (value) \
      : ( ( (value) & \
            ~BIT_NUM_AND_LEN_TO_MASK(0,(len),type) ) \
          | ( ( (value) << (shifts) ) \
             & (BIT_NUM_AND_LEN_TO_MASK( \
                0, (len), type )) ) ) \
      ) \
   ) /*lint -restore */
/* MVBITS(src,srcindex,len,destptr,destindex,type): Update the value that 
 * "destptr" points to, using bits extracted from "src" starting at bit 
 * "srcindex" for "len" bits.  "destindex" indicates the bit number in the 
 * destination to begin updates.  "type" must be an unsigned integral type.
 */
/*EVALTWICE*/
#define MVBITS( \
   src,srcindex,len,destptr,destindex,type) \
   /*CONSTCOND*/ /*lint -save -e506 */ \
   { \
       type srcbits = \
          (src) & BIT_NUM_AND_LEN_TO_MASK( \
                (srcindex), (len), type ); \
       type destmask = BIT_NUM_AND_LEN_TO_MASK( \
                (destindex), (len), type ); \
       *(destptr) &= ~destmask; \
       *(destptr) |= ISHFT( \
                srcbits, \
                (int) ((destindex)-(srcindex)), \
                type ); \
   } /*lint -restore */
/* NAND_BITS(m.n,type): Return the bitwise "nand" of the integral values 
 * "m" and "n".  "type" must be an unsigned integral type.
 */
#define NAND_BITS(m,n,type) \
   ( (type) ~ ( IAND((m),(n),type) ) )
/* NOR_BITS(m.n,type): Return the bitwise "nor" of the integral values "m" and 
 * "n". "type" must be an unsigned integral type.
 */
#define NOR_BITS(m,n,type) \
   ( (type) ~ ( IOR((m),(n),type) ) )
/* NOT(value,type): Return all bits of "value" flipped. Note that "value" must
 * be of type "type", which must be an unsigned integral type.
 */
#define NOT(value,type)  ( (type) ~((type)(value)) )
/* RIGHT_CIRCULAR_SHIFT_BITS(value,shifts,len,type). Return "value" with bits 
 * circularly shifted right "shifts" bits within the lower "len" bits of
 * "value".  A zero amount for "shifts" causes no shift. Note that "shifts" 
 * must be less than or equal to "len". Also note that "value" must be of type
 * "type", and "type" must be an unsigned integral type. "len" must also be 
 * greater than zero and less than or equal to TYPE_SIZE_IN_BITS("type").
 */
/*EVALTWICE*/
#define RIGHT_CIRCULAR_SHIFT_BITS( \
   value,shifts,len,type) \
  /*lint -save -e504 */ \
  ( (type) ( \
  ((shifts)==0) || ((len)==(type) (shifts)) \
  ? (value) \
  : ( ( (value) & \
        ~BIT_NUM_AND_LEN_TO_MASK(0,(len),type) ) \
    | ( ( (value) & (BIT_NUM_AND_LEN_TO_MASK( \
      0, ((len)-(type)(shifts)),type)) ) \
      <<(shifts)) \
    | ( ((value)&(BIT_NUM_AND_LEN_TO_MASK( \
      ((len)-(type)(shifts)),(shifts),type))) \
      >> ((len)-(type)(shifts)) ) ) ) \
   ) /*lint -restore*/
/* RIGHT_SHIFT_BITS(value,shifts,len,type): Return "value" with bits logically
 * shifted right "shifts" bits within the lower "len" bits of "value".  If
 * necessary, zero bits are added on the left. A zero amount for "shifts" 
 * causes no shift. Note that "shifts" must be less than or equal to "len". 
 * Also note that "value" must be of type "type", and "type" must be an 
 * unsigned integral type. "len" must also be greater than zero and
 * less than or equal to TYPE_SIZE_IN_BITS("type").
 */
/*EVALTWICE*/
#define RIGHT_SHIFT_BITS(value,shifts,len,type) \
   /*lint -save -e504 */ \
   ( (type) ( \
      ( ((shifts)==0) || ((len)==(type) (shifts)) ) \
      ? (value) \
      : ( ( (value) & \
            ~BIT_NUM_AND_LEN_TO_MASK(0,(len),type) ) \
          | ( ( (value) & (BIT_NUM_AND_LEN_TO_MASK( \
            0, (len), type )) ) >> (shifts) ) ) \
      ) \
   ) /*lint -restore */
/* SET_BITS_USING_MASK(value,mask,type): Return "value", except that any bits 
 * which are turned on in "mask" will also be turned on in the return
 * value. "type" must be an unsigned integral type.
 */
#define SET_BITS_USING_MASK(value,mask,type) \
   ( (type) ( (value) | (mask) ) )
/* TEST_BITS_USING_MASK(value,mask,type): Return "value", except that only bits
 * which are turned on in "mask" will be returned. "type" must be an
 * unsigned integral type.
 */
#define TEST_BITS_USING_MASK(value,mask,type) \
   ( (type) ( (value) & (mask) ) )
/* TYPE_SIZE_IN_BITS(type): Return the number of bits required for type "type".
 */
#define TYPE_SIZE_IN_BITS(type) \
   ( (type) ( sizeof(type) * CHAR_BIT ) )
/* XNOR_BITS(m.n,type): Return the bitwise exclusive "nor" of the integral 
 * values "m" and "n".  "type" must be an unsigned integral type. 
 */
#define XNOR_BITS(m,n,type) \
   ( (type) ~ ( IEOR((m),(n),type) ) )
#endif /* BITOPS_H */

Listing Two

/* mmixcom.h--  MMIX common defns. Copyright (c) 1994 by JR (John Rogers).
 * All rights reserved. CompuServe: 72634,2402
 * FUNCTION - mmixcom.h contains types and equates used for defining MMIX 
 *      instructions in object code format.
 * We take advantage of the implicit ANSI C requirement that unsigned char be 8
 * bits or larger. Similarly, we can assume unsigned long is 32 bits or larger.
 */
#ifndef MMIXCOM_H
#define MMIXCOM_H
/* Define a type for one instruction. Note that this will be at least 32 bits,
 * depending on the compiler.
 */
typedef unsigned long  MMIX_Instr_T;
/* We also need to deal with single words in MMIX. These are currently 32 bits 
 * wide, although Knuth is likely to change them to 64 bits soon.
 */
typedef unsigned long  MMIX_Word_T;
#define MMIX_WORD_LEN  32
/* Many parts of MMIX words are in bytes. In MMIX, a byte is 8 bits long. In C,
 * this might be larger.
 */
typedef unsigned char  MMIX_Byte_T;
/* Even if "char" is more than 8 bits, leave this. */
#define MMIX_BYTE_BIT_LEN  8
/* Define a type for an opcode. */
typedef MMIX_Byte_T  MMIX_Opcode_T;
/* Define equates for each part of MMIX_Instr_T. Use bit numbering convention 
 * of 0=least significant bit (LSB).
 */
#define MMIX_INSTR_OPCODE_START 24
#define MMIX_INSTR_OPCODE_LEN   MMIX_BYTE_BIT_LEN
#define MMIX_INSTR_X_START  16
#define MMIX_INSTR_X_LEN    MMIX_BYTE_BIT_LEN
#define MMIX_INSTR_Y_START  8
#define MMIX_INSTR_Y_LEN    MMIX_BYTE_BIT_LEN
#define MMIX_INSTR_Z_START  0
#define MMIX_INSTR_Z_LEN    MMIX_BYTE_BIT_LEN
#endif /* MMIXCOM_H */
End Listings


Copyright © 1995, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.