The SUIF Machine Library

Michael D. Smith
smith@eecs.harvard.edu
Division of Engineering and Applied Sciences
Harvard University
Compatible with SUIF Release 1.1.2
Revised March 29, 1998


Introduction

The SUIF compiler provides an excellent set of flexible libraries for parallel and machine-independent optimizations. This document describes the SUIF machine library, a library which extends the base SUIF library with abstractions necessary for machine-specific optimizations. At the core of this library is the machine_instr class, derived from the base SUIF in_gen class, that allows you to manipulate machine instructions within SUIF. The library provides numerous abstractions for a machine instruction so that you can easily write compiler passes that perform machine-specific optimizations over a wide range of machine architectures. We have designed the machine library so that it is relatively straightforward to add support for new instructions or instruction set architectures.

The machine library consists of files in the machine subdirectory of the machsuif distribution package. The makefile in machine creates and installs the machine library. Further information about the machsuif package can be found in the machsuif/doc directory. It is assumed that the reader is familiar with the SUIF system and both the SUIF and machsuif overview documents.

This documentation was created using the noweb system by Norman Ramsey. This literate programming tool lets you combine documentation and code in the same source file. It is our convention to use noweb to document only the most important header files---the files that describe the library interface. We use simple C comments to document the interesting portions of the library implementation files, the parts that should be ``black boxes'' to the users of the machine library.

We organize the code for the SUIF machine library into two distinct pieces: code that is target-architecture independent and code that is target-architecture dependent. Except for machineUtil.cc, the code in the files machine*.{h,cc}, annoteHelper.{h,cc}, eaHelper.{h,cc}, and archInfo.{h,cc} is architecture independent. The file machineUtil.h lists all of the helper routines that we use to encapsulate machine-specific information for the target-architecture-independent passes. Definitions of these routines in the file machineUtil.cc dispatch to machine-specific actions. You can think of this as a manual implementation of the virtual method dispatch that would occur if we simply derived a new set of instruction classes for each target architecture. Sections [->] through [->] describe the classes and helper functions in machine*.h, while Sections [->] through [->] cover the code in annoteHelper.h, eaHelper.h, and archInfo.h.

Architecture-specific code is contained in files with names of the form <archname>.data, <archname>Ops.{h,cc}, and <archname>Instr.{h,cc}. We put machine-specific opcode information in the *Ops.* files and any architecture-specific routines in the *Instr.* files. We place all of the rest of the architecture and machine-specific data required during compilation in the src/machsuif/impl directory. Please see the machsuif overview document and the README.* files in the impl directory for more information. Sections [->] and [->] describe how you can build and access the machine-specific data contained in the impl directory during compilation. Finally, Section [->] presents the use of the architecture-specific routines in the <archname>Instr.{h,cc} files.

All of the code is protected by the following copyright notice.

<SUIF copyright>= (U-> U-> U-> U-> U-> U-> U-> U-> U-> U->)
/*  Copyright (c) 1994 Stanford University

    All rights reserved.

    Copyright (c) 1995,1996 The President and Fellows of Harvard University

    All rights reserved.

    This software is provided under the terms described in
    the "suif_copyright.h" include file. */

#include <suif_copyright.h>

Header file for the machine library

[*]

For the most part, the file machine.h is a typical SUIF library header file. It contains #include references so that the library can be compiled or simply included in another pass's compilation. The include files are indented to show the dependences between the header files within the machine library. It also determines the appropriate definition for EXPORTED_BY_MACHINE so that we can compile on both Unix and Win32 platforms.

<machine.h>=
/* file "machine.h" */

<SUIF copyright>

/*  Header for SUIF library of machine-specific definitions and routines */

#ifndef MACHINE_H
#define MACHINE_H

// Mark exported symbols as DLL imports for Win32. (jsimmons)
#if defined(_WIN32) && !defined(__CYGWIN32__) && !defined(MACHINELIB)
#define EXPORTED_BY_MACHINE _declspec(dllimport) extern
#else
#define EXPORTED_BY_MACHINE extern
#endif

/* 
 *  Use a macro to include files so that they can be treated differently
 *  when compiling the library than when compiling an application.
 */

#ifdef MACHINELIB
#define MACHINEINCLFILE(F) #F
#else
#define MACHINEINCLFILE(F) <machine/ ## F ## >
#endif

/*
 *  The files are listed below in groups.  Each group generally depends
 *  on the groups included before it.  Within each group, indentation is
 *  used to show the dependences between files.
 */

/* this should always be defined first */
#include <suif1.h>

/* annotation, target-architecture, and effective-address helper routines */
#include MACHINEINCLFILE(annoteHelper.h)
#include MACHINEINCLFILE(archInfo.h)
#include MACHINEINCLFILE(eaHelper.h)

/* machine instruction base classes */
#include MACHINEINCLFILE(machineDefs.h)
#include   MACHINEINCLFILE(machineInstr.h)

#ifdef M_ALPHA
#include   MACHINEINCLFILE(alphaOps.h)
#include     MACHINEINCLFILE(alphaInstr.h)
#endif

#ifdef M_MIPS
#include   MACHINEINCLFILE(mipsOps.h)
#include     MACHINEINCLFILE(mipsInstr.h)
#endif

#ifdef M_PPC
#include   MACHINEINCLFILE(ppcOps.h)
#include     MACHINEINCLFILE(ppcInstr.h)
#endif

#ifdef M_X86
#include   MACHINEINCLFILE(x86Ops.h)
#include     MACHINEINCLFILE(x86Instr.h)
#endif

/* code gen/query helper -- depends upon archInfo.h and machineDefs.h */
#include MACHINEINCLFILE(machineUtil.h)

#endif /* MACHINE_H */

To create a machine library with support for a particular target architecture, you must set the appropriate MACHSUIF_TARGET_* environment variable. The machine library makefile checks these environment variables, and it will not include the library routines for a particular architecture if the environment variable is not set. Notice that the top-level machsuif makefile also tests these environment variables to determine what architecture-specific passes need to be built. For example, if you want to be able to produce Digital Alpha code using the machine library, you would set the environment variable MACHSUIF_TARGET_ALPHA. A single copy of the machine library will support multiple architectures if you set multiple MACHSUIF_TARGET_* environment variables.

The following header file just creates a convenient place to put information that is global to the Machine library files, but private to the outside world. It is this file that is included in every Machine library source file.

<machine_internal.h>=
/* file "machine_internal.h" */

<SUIF copyright>

#ifndef MACHINE_INTERNAL_H
#define MACHINE_INTERNAL_H

#ifndef MACHINELIB
#error "for the Machine SUIF library only"
#endif

#include "machine.h"

/* This file is for information that is global to different parts
 * of the Machine SUIF library, but private to the library.  It
 * should be the first header included by each source file in
 * the Machine SUIF library.
 *
 * There is no such information currently.
 */

#endif /* MACHINE_INTERNAL_H */

Enum definitions

[*]

In the machineDefs.h header file, we have defined a number of constants to make it easier to create a compiler pass that is independent of the target architecture.

<machineDefs.h>=
/* file "machineDefs.h" */

<SUIF copyright>

#ifndef MACHINE_DEFS_H
#define MACHINE_DEFS_H

<machine constants>

<machine opcode base definitions>

<machine instruction formats>

#endif

The size of a data object is measured in bits. Please note that we define a WORD-sized data object to be 32-bits in size, independent of the definition of a WORD in the target architecture.

<machine constants>= (<-U)
/**** Global definitions and declarations for machine constants ****/
const int SIZE_OF_BYTE = 8;             /* everything defined in bits */
const int SIZE_OF_HALFWORD = 16;
const int SIZE_OF_WORD = 32;
const int SIZE_OF_SINGLE = 32;
const int SIZE_OF_DOUBLE = 64;
const int SIZE_OF_QUAD = 128;

const int SHIFT_BITS_TO_BYTES = 3;
const int SHIFT_BYTES_TO_WORDS = 2;
const int DWORD_ALIGNMENT_MASK = 15;  /* off 16-byte boundary? */
const int WORD_ALIGNMENT_MASK = 7;    /* off 8-byte boundary? */
const int BYTE_MASK = 3;              /* off 4-byte boundary? */

const int BYTES_IN_WORD = SIZE_OF_WORD >> SHIFT_BITS_TO_BYTES;
const int BYTES_IN_DOUBLE = SIZE_OF_DOUBLE >> SHIFT_BITS_TO_BYTES;
const int BYTES_IN_QUAD = SIZE_OF_QUAD >> SHIFT_BITS_TO_BYTES;

To define the opcodes for a particular instruction set architecture, we do not modify the if_ops enumeration in opcodes.h in basesuif. Instead, we have defined constants that represent the bases of the machine opcode enumerations. The opcode enumeration for each particular machine architecture is defined as an integer enumeration to allow for future expansion. For programming clarity, proper type names for the opcode and format enumeration types are typedef-ed to int.

We reserve the integers between 0 and 999 for SUIF opcodes. It is recommended that you leave at least 1000 opcode numbers between opcode starting bases. This will allow for experiments in such areas as non-excepting architectures where the entire opcode space is duplicated. The actual base value for each target architecture is defined in that architecture's *Ops.h file.

<machine opcode base definitions>= (<-U)
/**** Opcode definitions ****/
typedef int mi_ops;
typedef int mi_op_exts;

const int io_null = -1;         /* null opcode extension to SUIF op space */

/* Definitions for the SUIF instruction set architecture. */
const int OP_BASE_SUIF = 0;     /* start of if_ops enumeration */

The machine instruction format enumeration is listed below. Each opcode refers to an instruction of a certain format. For example, the MIPS mo_add has format mif_rr. The function which_mformat(mi_ops) provides this mapping. In the current implementation of the machine library, there is a unique machine instruction format for each opcode. If you want to add a new instruction that can be instantiated in one of several machine instruction formats, you should create a unique mi_ops identifier for each instance. All of these unique identifiers can use the same ASCII string value.

<machine instruction formats>= (<-U)
/**** Format tags ****/
typedef int mi_formats;
extern int which_mformat(mi_ops o);

enum /* mi_formats */ { /* machine instruction formats */
    mif_xx,             /* pseudo-op and other free formats */
    mif_lab,            /* labels */
    mif_bj,             /* branch/jump to label or through register */
    mif_rr,             /* general format */
    mif_LAST_FMT
};

Machine instructions

[*]

As in the SUIF instruction class, all machine instructions share some basic features. This section describes the fields in the base machine_instr class defined in the file machineInstr.h. Again as in the SUIF instruction class, the machine_instr class is an abstract class from which classes for specific instruction formats are derived. The derived classes are organized hierarchically according to whether the instruction modifies the PC and/or writes to memory. After Section [->] describes our approach for handling effective address calculations, Sections [->] through [->] provide the details for the derived classes.

<machineInstr.h>=
/* file "machineInstr.h" */

<SUIF copyright>

#ifndef MACHINE_INSTR_H
#define MACHINE_INSTR_H

<machine string constants>

<machine annotation definitions>

/*
 *  Helper functions related to the generic machine instruction class.
 */
<machine library initialization>
<machine file I/O>
<machine print helpers>
<machine operand helpers>

/*
 *  Machine instruction classes.
 */
<class machine_instr>
<list class for machine_instr>

<class mi_lab>
<class mi_rr>
<class mi_bj>
<class mi_xx>

#endif

We derive a machine_instr from an in_gen. The machine_instr class is an abstract class and cannot be directly instantiated. We use the name field in the in_gen class to store the architecture string for this particular machine instruction. (See Section [->] for a more detailed discussion of the target-architecture-specific information used in the machine library.) Though we implement our own clone and print methods, the methods for machine_instr look very much like the methods for any other SUIF instruction. This is not by chance; machine_instr and its derived classes contain only methods that are useful to routines that are independent of the target machine architecture. We designed the machine instruction classes to handle both RISC-like and CISC-like architectures, though the constructors given assume a more RISC-like architecture.

<class machine_instr>= (U->)
class machine_instr: public in_gen {
    int m_op;                           /* architecture-specific opcode */
    /* opcode extensions are included as annotations */

  protected:
    /* The base SUIF instruction class defaults to: io_nop, type_void,
     * inum = 0, par = NULL, and dst = operand(). */
    machine_instr():in_gen(k_null_string, type_void, operand(), 0)
        { m_op = io_null; set_result_type(type_void); }
    machine_instr(char *a, int op):in_gen(a, type_void, operand(), 0)
        { m_op = op; set_result_type(type_void); }

    void clone_base(machine_instr *i, replacements *r, boolean no_copy); 

  public:
    ~machine_instr() {}

    /* normal assembly instruction information */
    char *architecture()                { return name(); }
    virtual inst_format format()        { return inf_none; }

    /* opcode stuff including ugly hack to override opcode return type */
    if_ops opcode()                     { return (if_ops)m_op; }
    char *op_string();
    void set_opcode(if_ops o);
    void set_opcode(int o)              { set_opcode((if_ops)o); }

    /* comment field operations */
    boolean has_comment() { return (peek_annote(k_comment) != NULL); }
    void append_comment(immed_list *);
    void delete_comment() { get_annote(k_comment); }

    /* clone helper methods */
    virtual instruction *clone_helper(replacements *r, 
                                        boolean no_copy = FALSE)=0; 
    virtual void find_exposed_refs(base_symtab *dst_scope, replacements *r)=0; 

    /* print methods */
    virtual void print(FILE *o_fd=stdout);
    void print_comment(FILE *o_fd, char *comment_char);
};

The machine_instr class has an opcode() method that overrides the instruction class opcode() method. The machine library stores machine opcodes separately from the SUIF opcodes so that the SUIF I/O stream routines and other routines that do not know about machine instructions simply see them as io_gen's. Currently, we perform an ugly cast to handle the return type problem on the opcode() and set_opcode(int) methods. We expect this ``extensible enumeration'' problem to be solved in later releases of SUIF.

In addition to SUIF's usual concept of opcodes, the machine library also allows for opcode extensions. For example, we use opcode extensions to capture the VAX/IEEE rounding mode qualifiers on Alpha floating-point instructions. Any opcode (or instruction) may have an extension. Opcode extensions are represented by integer values and are architecture specific. They consume the integers following the integers consumed by the instruction set architecture's opcodes. Opcode extensions are associated with an instruction through the use of the k_instr_op_exts annotation. An instruction can have more than one opcode extension, and the order of the annotation list containing the opcode extensions is unimportant or architecture specific.

The machine_instr class includes methods to simplify the placement of an assembly-language comment on a machine instruction. A comment is stored internally as a k_comment annotation.

Please note that the print routines for the machine instruction classes default to a SUIF-style ASCII output. Print routines are considered to be machine-specific routines and are kept separate from the base classes. Section [->] illustrates how these generic print routines are massaged to yield Alpha-specific assembly-language output.

The machine library declares the milist class for working with lists of pointers to machine instructions. Like the instruction_list class, this class exists only for convenience. The machine library and the SUIF system store machine instructions in tree_instr nodes since they are simply instruction pointers. We have also supplied a helper function for printing lists of machine instructions.

<list class for machine_instr>= (U->)
DECLARE_DLIST_CLASS(milist, machine_instr *);
extern void Print_milist(FILE *, milist *);

Effective address (EA) operands

[*]

As described in our ``basesuif-changes'' document, an operand of a machine_instr may have any of the kinds in operand_kinds, but a machine operand that is an OPER_INSTR has a special meaning. The SUIF system uses operands that point to other instructions as single-use, single-def compiler temporaries. Since the machine library extensions add registers as a first-class operand kind, we can define a set of compiler temporary registers using the register operand kind. The SUIF system also uses instruction-pointer operands to construct expression trees. Since machine-specific optimizations such as global instruction scheduling fix an instruction ordering, the usual representation for a list of instructions in the back-end of a compiler is a flat list. Thus, instruction-pointer operands may not seem very useful. On the contrary, we have found them to be quite useful as placeholders for operands that are immediate values or that live in memory.

In the machine library, instruction-pointer operands represent either an effective address (EA) calculation or a simple immediate value. EA calculations are io_ldc instructions that always have a pointer type as the destination type. Simple immediate values then are any immediate value whose type is not a pointer type. The proper sequence of checks to perform to differentiate a simple immediate operand from an EA calculation in the machine library is as follows: if the operand in question is an OPER_INSTR kind, then use the machsuif helper function Is_ea_operand(operand) to differentiate operands representing EA calculations from simple immediate operands. You may not need the extra check (i.e. the is_immed() operand method is sufficient) in some contexts. For example, the second operand of a base+displacement form of an EA calculation is a simple immediate.

Memory operands are accessed through the generation of an EA, and the generation of an EA can be quite complicated in some instruction set architectures. For example, the x86 instruction set architecture can specify the address of a memory operand through a single base register, or through a base register plus a signed 16-bit immediate, or through a base register plus a scaled index register plus a signed immediate, etc. Let us assume that we have an x86 add operation that has two source operands and a single destination operand. Either of the source operands can be an operand that lives in memory. If we were to make each of the components of the EA calculation a SUIF operand in the machine instruction, we would not be able to tell easily where one x86 add operand ended and the next started. Alternatively, we would have to define a special add opcode for every combination of EA modes and operands.

The machine library uses SUIF instruction-pointer operand's to compartmentalize the arbitrary complexity of EA calculations. In other words, the operand that causes the helper function Is_ea_operand(operand) to return TRUE is a memory operand. The operand is in fact an expression tree of SUIF instructions that define the EA calculation for this memory operand. The instructions within this expression tree rooted at the memory operand are true SUIF instructions. For example, the representation of the EA for a memory operand at the top of the stack is represented by a SUIF io_add operation whose first operand is a register representing the stack pointer and whose second operand is an immediate with a value of zero (in actuality, another instruction-pointer pointing to a SUIF in_ldc instruction). The EA representation for an operand located at label ``foo'' in memory is represented by a SUIF io_ldc operation whose immediate value is the sym_addr ``foo''. Recall that the result type of the in_ldc instruction differentiates in_ldc's that represent EA calculations (result type must be a pointer type) from those that represent immediate values (result type cannot be a pointer type). Section [->] describes a set of helper routines that we provide in Machine SUIF to simplify the process of creating, querying about, and accessing parts of EA calculations.

Representing memory operands with SUIF instructions implies that machine instruction files are actually a collection of SUIF and machine instructions. These files are read as expression trees however, so that only machine instructions are directly connected to tree_instr nodes (assuming a fully translated machine file). Please see <machine file I/O> for more information on reading and writing SUIF machine instruction files.

The machine library assumes that memory operands contain only the EA calculation for that memory operand and no other side effects. If the addressing mode is post-increment, that information is not encoded in the expression tree. [Note that a pre-increment operation would be included in the expression tree, but the information that the result of the pre-increment modifies the user-visible state (i.e. is written back) is not.] We currently encode information like post-increment in an opcode extension. Similarly, one should not use instruction-pointer operands to express the operations in a complex operation. That information should be specified entirely by the opcode.

Should we use opcode extensions to indicate a post-increment operation on an index register in an EA calculation? It seems that it would be better if the post-increment indicator was on the EA operand.

Non-control-transfer instructions

[*]

Like the SUIF in_rrr instruction class, the vast majority of machine instructions are represented by the mi_rr class. This class includes all ALU, load-constant, and memory operations. Because of our extensions to the SUIF operand class, we do not need a separate class to handle load constant operations. An operation is not part of this class if it is a control transfer instruction, i.e. it explicitly changes the value of the program counter.

<class mi_rr>= (U->)
/* Class for almost all operations.  Called 'rr' for historical reasons. */
class mi_rr: public machine_instr {

  public:
    mi_rr();
    /* constructor for instructions that do NOT write memory */
    mi_rr(mi_ops o,
          operand d = operand(),        /* destination */
          operand s1 = operand(),       /* source 1 */
          operand s2 = operand());      /* source 2 */
    /* constructor for instructions that DO write memory */
    mi_rr(mi_ops o,
          instruction *ea,              /* store effective address */
          operand s1 = operand(),       /* source 1 */
          operand s2 = operand());      /* source 2 */

    virtual inst_format format()        { return (inst_format)mif_rr; }

    /* by convention, we keep the store EA, if it exists, in srcs[0] */
    operand store_addr_op(unsigned n);
    void set_store_addr_op(unsigned n, operand r);
    void remove_store_addr_op(unsigned n);

    virtual instruction *clone_helper(replacements *r, boolean no_copy=FALSE);
    virtual void find_exposed_refs(base_symtab *dst_scope, replacements *r); 

    virtual void print(FILE *o_fd=stdout);
};

We define two constructors for the class mi_rr: one for instructions that do and one for instructions that do not write memory. A mi_rr operation may have any number of source and destination operands, though the machine library currently defines constructors only for the typical two-source/one-destination operation. You must incrementally build an instruction with a larger number of sources and/or destinations by using the methods set_num_dsts(unsigned), set_dst(unsigned, operand), set_num_srcs(unsigned), and set_src_op(unsigned, operand).

Like the source operands, a destination operand may be a memory (rather than a register) operand. Since the operands of the EA calculation for a destination memory operand are effectively source operands of the operation, we place a store's EA calculation in the source operand list. Although it might seem intuitive to have store EA operands on the destination side of an instruction that writes to memory, machsuif puts them on the source side because they describe some of the instruction's inputs (such as base registers). Thus an analyzer never looks for instruction sources in a destination operand.

We use the k_is_store_ea annotation to distinguish EA calculations for loads from those for stores. The three methods store_addr_op(int), set_store_addr_op(int,operand), and remove_store_addr_op(int) handle the bookkeeping for the k_is_store_ea annotation. The remove_store_addr_op(int) method will work even if you remove the operand (i.e. set it to NULL) before calling this method. By convention, we use src_op(0) to hold the store EA in our code. This is only a convention; helper functions like Write_memory(instruction *) described in Section [->] work correctly in the general case. In summary, a destination operand is either a register, a symbol, or null, but never an EA calculation.

Control-transfer instructions

The mi_bj class is a superset of the functionality of the mi_rr class. The additional functionality being that operations in the mi_bj class modify the program counter and those in the mi_rr class do not. This classification includes conditional and unconditional branches, calls and returns, etc. This class handles both jumps to symbolic targets and jumps through register sources.

<class mi_bj>= (U->)
/* Branch/jump class.  Instructions in this class may also perform
 * alu operations and/or read/write memory.  */
class mi_bj: public mi_rr {
    sym_node    *targ;          /* may be NULL */

  public:
    mi_bj();
    mi_bj(mi_ops o,
          sym_node *t,                  /* target symbol */
          operand d = operand(),        /* destination */
          operand s1 = operand(),       /* src1 or tgt-reg */
          operand s2 = operand());      /* source 2 */
    mi_bj(mi_ops o,
          instruction *ea,              /* store ea */
          sym_node *t,                  /* target symbol */
          operand d = operand(),        /* destination */
          operand s1 = operand(),       /* src1 or tgt-reg */
          operand s2 = operand());      /* source 2 */

    inst_format format()                { return (inst_format)mif_bj; }

    sym_node *target()                  { return targ; }
    void set_target(sym_node *t)        { targ = t; }
    boolean is_indirect()               { return (targ == NULL); }

    instruction *clone_helper(replacements *r, boolean no_copy = FALSE);
    void find_exposed_refs(base_symtab *dst_scope, replacements *r); 

    void print(FILE *o_fd=stdout);
};

If the control transfer operation specifies a symbolic target, the target() and set_target(sym_node *) methods are used to access and define the target symbol. Unlike the SUIF in_bj class, the symbolic target of a mi_bj is a sym_addr since this target can be a procedure symbol (e.g. in a call operation).

If the control transfer operation modifies the contents of the program counter with the contents of a general-purpose register, target is set to NULL. The location in the source operand array for the register that updates the program counter can be unique to each architecture, though we usually use src_op(0).

Our code generators attach a k_instr_mbr_tgts annotation on each mi_bj instruction that represents a SUIF in_mbr instruction. This annotation maintains the array of target symbols from the in_mbr operation. The k_instr_mbr_tgts annotation is a flat annotation where the first list entry is the number of labels in the multi-way branch and the rest of the list entries are the labels. Our code generators also attach a k_instr_ret annotation on each mi_bj instruction that represents a SUIF io_ret operation. The k_instr_ret is a flat annotation with at most one list entry: a ``type_node *'' representing the return value type if any. This annotation is necessary since some instruction set architectures do not have an opcode that differentiates procedure return instructions from other register-indirect jump instructions.

Label instructions

The mi_lab class is equivalent to the SUIF in_lab class. This re-implementation of the label class is necessary so that we can associate an architecture identifier with a label pseudo-op. The printing methods use the architecture identifier to determine the appropriate syntax for the printing of labels.

The label class contains only text labels. Assembly-language data label statements are never stored internally. These are generated only when printing the ASCII assembly language program. Until that point, the information about data labels is contained in the SUIF symbol table. To support the printing of data labels, we have included a short-cut routine in the helper functions for printing out data labels in the same syntax as a text label (see Print_data_label() in Section [->], <machine print helpers>).

<class mi_lab>= (U->)
/* Label instructions. */
class mi_lab: public machine_instr {
    label_sym   *lab;

  public:
    mi_lab():machine_instr(k_null_string,io_lab) { lab = NULL; }
    mi_lab(mi_ops o, label_sym *s);
    mi_lab(mi_ops o, in_lab *i);

    inst_format format()                { return (inst_format)mif_lab; }

    label_sym *label()                  { return lab; }
    void set_label(label_sym *l)        { lab = l; }

    unsigned num_dsts()                 { return 0; }
    operand dst_op(unsigned n=0)        { return operand(); }
    void set_num_dsts(unsigned)         { return; }     /* dummy method */
    void set_dst(operand)               { no_dst_error(); }
    void set_dst(unsigned, operand)     { no_dst_error(); }

    instruction *clone_helper(replacements *r, boolean no_copy = FALSE);
    void find_exposed_refs(base_symtab *dst_scope, replacements *r); 

    void print(FILE *o_fd=stdout);
};

Pseudo-op instructions

[*]

The mi_xx class is the catch-all class for all other architecture-specific pseudo-op or assembler directive statements. No machine-level operation is performed by these instructions. The operands of a mi_xx operation are kept on an immed_list, like the values of a flat annotation. The library provides append_operand(immed) and pop_operand() list methods to manipulate the immediate list. The destination operand is unused and trying to set it will cause an error.

<class mi_xx>= (U->)
/* Pseudo-op class. */
class mi_xx: public machine_instr {
    immed_list  il;                     /* list of pseudo-op operands */

  public:
    mi_xx():machine_instr(k_null_string, io_null) {}
    mi_xx(mi_ops o);
    mi_xx(mi_ops o, immed i);

    inst_format format()                { return (inst_format)mif_xx; }

    unsigned num_dsts()                 { return 0; }
    operand dst_op(unsigned n=0)        { return operand(); }
    void set_num_dsts(unsigned)         { return; }     /* dummy method */
    void set_dst(operand)               { no_dst_error(); }
    void set_dst(unsigned, operand)     { no_dst_error(); }

    /* pseudo-op instruction operations */
    boolean has_operands()              { return !il.is_empty(); }
    void append_operand(immed i)        { il.append(i); }
    immed pop_operand()                 { return il.pop(); }

    instruction *clone_helper(replacements *r, boolean no_copy = FALSE);
    void find_exposed_refs(base_symtab *dst_scope, replacements *r); 

    void print(FILE *o_fd=stdout);
};

Library initialization

If you use a standard SUIF makefile, a SUIF program needs only to link with the machine library to take advantage of the machine library functionality. In this scenario, the machine library code is registered automatically with the SUIF library and its initialization functions are called automatically by init_suif(). The routine exit_machine() is called during exit_suif(). Please note that, though these routines are declared in machineInstr.h, their implementations are kept in machineUtil.h since they include architecture-specific code.

<machine library initialization>= (U->)
extern void init_machine(int &argc, char *argv[]);
extern void exit_machine(void);

We also added one new SUIF string constant, k_null_string, whose value is initialized automatically by the machine library.

<machine string constants>= (U->)
EXPORTED_BY_MACHINE char *k_null_string;

Annotations

We define several new annotations to aid in file I/O and the communication of information between machsuif passes. All of these are flat annotations. We begin with an explanation of the annotations used by all machine instructions and move toward those annotations defined for only specific instructions or particular purposes.

<machine annotation definitions>= (U->)
/*
 *  Flat annotations used by machsuif passes.
 */
EXPORTED_BY_MACHINE char *k_comment;
EXPORTED_BY_MACHINE char *k_instr_op_exts;
EXPORTED_BY_MACHINE char *k_hint;
EXPORTED_BY_MACHINE char *k_reloc;

/* ... following are used during file I/O (attached to instructions) */
EXPORTED_BY_MACHINE char *k_machine_instr;
EXPORTED_BY_MACHINE char *k_instr_xx_sources;
EXPORTED_BY_MACHINE char *k_instr_bj_target;

/* ... following keep extra high-level info with instruction */
EXPORTED_BY_MACHINE char *k_instr_ret;
EXPORTED_BY_MACHINE char *k_incomplete_proc_exit;
EXPORTED_BY_MACHINE char *k_instr_mbr_tgts;
EXPORTED_BY_MACHINE char *k_mbr_index_def;
EXPORTED_BY_MACHINE char *k_regs_defd;
EXPORTED_BY_MACHINE char *k_regs_used;
EXPORTED_BY_MACHINE char *k_is_store_ea;

/* ... following are markers in text list (attached to *o_null instrs) */
EXPORTED_BY_MACHINE char *k_proc_entry;         /* marks entry point to proc */

/* ... following are attached to file_set_entry's */
EXPORTED_BY_MACHINE char *k_target_arch;        /* arch and machine info */
EXPORTED_BY_MACHINE char *k_next_free_reloc_num;

/* ... following are attached to tree_proc's */
EXPORTED_BY_MACHINE char *k_vreg_manager;       /* should be in proc_symtab */
EXPORTED_BY_MACHINE char *k_stack_frame_info;/* used by *gen, ra*, and *fin */

/* ... following are attached to sym_node's */
EXPORTED_BY_MACHINE char *k_vsym_info;  /* extra info for this var_sym */

/* ... following identify instructions added by *fin */
EXPORTED_BY_MACHINE char *k_header_trailer; /* header/trailer code/pseudo-op */

Annotations attached to any instruction's

If a machine instruction has a comment in the output assembly language file, this comment string is stored in a k_comment annotation. The machine_instr class methods has_comment(), append_comment(immed_list *), and delete_comment() manipulate this annotation. If the instruction uses opcode extensions, the k_instr_op_exts annotation holds this extension information. The annotation is an unordered list of integers representing the opcode extensions associated with the instruction. If the instruction uses hint information (currently only supported under Alpha), the k_hint annotation holds this information.

If the instruction requires relocation processing, the k_reloc annotation holds this information. The contents and ordering of this annotation's fields are specific to the target machine. Typically, the first field corresponds to the relocation type, and the second field indicates the relocation sequence number (used to match relocation pairs if necessary. If the annotation is attached to an instruction, the last field corresponds to the number of the source operand to be relocated. Remeber that we cannot annotate operands.

Annotations attached to particular instruction's

We use the next set of annotations to store machine-specific information during file I/O. Recall that machsuif instructions are read and written as SUIF io_gen instructions, and thus there is no place for machsuif-specific information such as the machine opcode. The following annotations appear on io_gen instructions only. The k_machine_instr annotation records first the architecture string and then the machine opcode information. The machine opcode information consists of the integer representation of that opcode followed by its ASCII equivalent. The ASCII equivalent is redundant information that simply makes it easier to read the printsuif output of a machsuif binary file. A mi_bj instruction records its target symbol in the k_instr_bj_target annotation. A mi_xx instruction records its immed source list in a k_instr_xx_sources annotation. No other special annotations are necessary for file I/O as SUIF io_gen instructions can carry all of the rest of the machsuif information.

We define several annotations to maintain high-level information associated with control-transfer instructions. Attaching a k_instr_ret annotation to a control-transfer instruction indicates that this instruction is a procedure return instruction. The annotation lists the type of the return value. The k_incomplete_proc_exit annotation is attached to return operations to indicate that the procedure exit code, e.g. saved register restores, have not been generated yet. This annotation is removed by the finishing passes. The k_instr_mbr_tgts annotation lists the label_sym pointers of a multiway branch instruction. The immed_list for this annotation first contains an integer, the number of targets, and then the label list in order. The k_mbr_index_def annotation marks the index generation instruction for the next multiway branch in the instruction stream. This annotation enables an instrumentation pass to quickly and easily find the jump table associated with the multiway branch instruction.

By design, all machsuif instructions are explicit about their register definitions and uses. This makes it straightforward to analyze the dependences between instructions, independent of their actual machine-specific encoding. Unfortunately, there is somewhat of an exception to this rule (heck, what good rule doesn't have some sort of exception). We'd like to know what argument registers are used and what result registers are written by a called procedure. Notice that the call instruction itself does not actually read any parameter registers nor does it actually write any result registers, and therefore it doesn't make sense to put this information explicitly in the source and destination operand arrays. However, this information is useful during register allocation, for example. To solve this problem, we have defined two annotations: k_regs_used and k_regs_defd. Both are attached only to calls---these annotations are not to be used to circumvent the rule that all machsuif instructions explicitly list their source and destination operands. The immed_list of the first annotation records the argument registers (if any) used in the call operation; the immed_list of the second annotation records the result registers (if any) written during the call operation. The register values are our abstract register names, not the machine-specific numbers.

Is agen the only code generator to include the k_regs_* annotations at this time?

The annotation k_is_store_ea is placed on SUIF instructions that write memory. The annotation differentiates EA calculations for loads from those for stores, since store EA calculations are placed in the source operand list. The immed_list of this annotation contains the indices of those source operands that contain an EA calculation that results in a write to memory.

To support a code generation environment that is split across many separate machsuif passes, we have defined several markers that are placed in the instruction list to remember specific points of interest in that list. All of the following annotations are attached to *o_null instructions.

The annotation k_proc_entry marks the entry point into the procedure. Normally, SUIF implicitly assumes that the first instruction in the instruction list is the procedure entry point. Our procedure layout optimizations invalidate this assumption. The k_proc_entry annotation has an empty immed_list. It is created by the *gen passes, maintained by all intermediate machsuif passes, and then consumed by printmachine (which translates this instruction into the appropriate procedure-entry pseudo-ops).

Annotations attached to file_set_entry's

[*]

The annotation k_target_arch records the machine-specific target information for this particular back-end compilation. We explain the full details of this annotation and the associated machine library classes in Sections [->] and [->]. We attach this annotation to the file_set_entry with the assumption that all of the procedures and instructions in a single file_set_entry have the same target architecture.

The annotation k_next_free_reloc_num has a single value in its immediate list corresponding to the next free relocation number for this file. If this annotation does not exist, the file does not contain any relocations.

Annotations attached to tree_proc's

The annotation k_vreg_manager records the information needed by the virtual register manager. This information should be kept in the proc_symtab with the other numbering managers for this procedure. It will eventually move there when basesuif includes basic support for machsuif. Currently, the only information recorded in this annotation is a single integer. The value of this integer is the next unused virtual register number. Currently, this annotation is read by Read_machine_proc() and written by Write_machine_proc(), which are defined in <machine file I/O>.

The annotation k_stack_frame_info records the stack frame information needed to create the stack frame and its associated instructions. We require this annotation because the generation of the stack frame is spread across several machsuif passes. The *gen code generator passes create the initial version of this annotation. [Actually, swighnflew generates a partial k_stack_frame_info annote to mark procedures that use varargs. Code generators should pop off this annote before creating their own.] The ra* register allocation passes modify this annotation to include, for example, information about which callee-save registers were used. Other passes, such as an instruction scheduling pass that reallocates registers, may also modify this annotation. The *fin finishing passes read the information on this annotation and create the actual stack frame instructions in the procedure preludes and postludes. Currently, the *fin passes delete the annote after reading its information.

The k_stack_frame_info annotation is a list of the following fields:

immed #0 int is_leaf
immed #1 int is_varargs
immed #2 int framesize
immed #3 int frameoffset
immed #4 int max_arg_area
immed #5 int 1st_saved_reg
immed #6 int 2nd_saved_reg
...

The boolean is_leaf is TRUE if this procedure does not contain any call instructions. If this boolean is TRUE, the immed's numbered greater than 3 may not appear. The boolean is_varargs is TRUE if this procedure is a VARARGS procedure. The field framesize records the total size of the stack frame in bytes, if known; 0 otherwise. The field frameoffset records the frame offset, also in bytes. It's interpretation is architecture dependent, though it is often negative. The value of this field is garbage if framesize is 0. The field max_arg_area records the maximum size of the call argument area in bytes for procedures that are not leaves. Finally, the remaining fields list the saved registers used in this procedure. Typically, they are listed in the order required by the stack frame rules, though this is not necessary. The register values are our abstract register names, not the machine-specific numbers.

Annotations attached to sym_node's

[*]

We place the annotation k_vsym_info on sym_node's that are automatic variables. This annotation records several pieces of information that are not currently recorded in the sym_node structure. For example, we use this annotation to record the offset from the stack pointer of this variable's stack home. As described in Section [->], the file annoteHelper.h defines several helper routines that manipulate and access the information in this annotation. This file also provides helper routines to access the other related information kept in the sym_node structure. For more details, please see Section [->], <annoteHelper.h>.

The following defines the three fields of the k_vsym_info annotation:

immed #0 int sp_offset
immed #1 int usage_count
immed #2 int param_reg

The field sp_offset records the offset in bytes from the stack pointer to the stack home of this sym_node. The field usage_count is a scratch-pad field. We sometimes use it during register allocation to record the static number of references to this sym_node during the procedure. The final field, param_reg, exists only if the sym_node is a parameter that is passed in a parameter register. The value of this field indicates which parameter register is used. Again, the register values are our abstract register names, not the machine-specific numbers. The k_vsym_info annotation has only two fields if the sym_node is not a parameter variable passed in a register.

This organization of this information is not very pretty. This whole issue needs to be revisited.

Helper functions

[*]

The machine library defines several kinds of helper functions to make it easier to write machsuif passes, especially passes that perform machine-specific optimizations in a machine-independent manner. This section describes each set of helper functions in turn.

Please note that these functions are distributed across four different machine files: machineInstr.h, machineUtil.h, annoteHelper.h, and archInfo.h. Basically, machineUtil.h contains all of the helper routines that check a machsuif instruction in a machine-specific way or return a machine-specific opcode to perform a generic action. The files annoteHelper.h and archInfo.h contain helper routines for the machine library annotations, while machineInstr.h is the catch-all for the rest of the low-level helper routines.

I/O helpers (and the virtual-register manager)

[*]

Because we have extended the basic SUIF instruction class but have not changed the underlying SUIF I/O methods, we have created wrappers for the SUIF proc_sym::read_proc() and proc_sym::write_proc() methods. Our wrappers are declared below. Both routines are ``robust'' in that they can read and write tree_proc bodies that do not contain any machine instructions.

<machine file I/O>= (U->)

class mproc_symtab {
  private:
    proc_symtab *st;    /* where we'd like this info */
    int next_vrnum;     /* next unused virtual register number */

  public:
    mproc_symtab(proc_symtab *t)        { st = t; next_vrnum = -1; }
    ~mproc_symtab()                     {}

    proc_symtab *pst()                  { return st; }

    int vreg_num()                      { return next_vrnum; }
    int next_vreg_num()                 { return next_vrnum--; }
    void set_vreg_num(int n)            { assert(n < 0); next_vrnum = n; }

    void renumber_vregs();      /* renumbers vr's uses in tree_proc body */
};
 
extern mproc_symtab *Read_machine_proc(proc_sym *, boolean /* exp_trees */,
                                       boolean /* use_fortran_form */);
extern void Write_machine_proc(proc_sym *, file_set_entry *);

The Read_machine_proc() routine reads a list of in_gen instructions and reconstructs the appropriate machine instructions from the machine library annotations on the in_gen instructions. This routine returns a mproc_symtab pointer which is our hack to make a sensible place to put the virtual register manager without actually hacking any more of basesuif. We make the returned pointer into a global variable, cur_psymtab, that can be accessed anywhere in our passes. This allows us to get at the virtual register manager easily.

The virtual register manager is simply a few methods in the mproc_symtab class. Notice that this class just wraps the real proc_symtab; mproc_symtab is not derived from proc_symtab. We have included the mproc_symtab::pst() method for accessing the methods in the real proc_symtab class. However, it is our convention to have a global variable proc_sym *cur_psym, and we always generate a pointer to the procedure symbol table off this variable. This convention will (hopefully) make it easier to transition our code when the virtual register manager moves into basesuif.

To hide the details of virtual-register-number generation, we use a macro called NEW_VREG. For all passes but those that translate low-suif into machsuif, we define this macro to be

#define NEW_VREG cur_psymtab->next_vreg_num()

so that we invoke the virtual register manager to give us a new virtual register number. This macro relies on the fact that we declared our mproc_symtab variable returned by Read_machine_proc() using the name cur_psymtab. Note that you put this #define into your pass's code; it is not part of the library.

On the other hand, if the pass in question is a *gen pass, then we define the macro to be

#define NEW_VREG (-(int)cur_psym->block()->proc_syms->next_instr_num())

so that we invoke the instruction number manager in basesuif. We use the instruction numbering system to generate virtual register numbers in *gen passes because instruction-pointer operands are turned into virtual registers by negating the instruction number of the defining instruction. Why do we do this? Because we don't have to do any extra work to maintain the mapping between this virtual register's definition and its use. Notice that you cannot use the virtual register manager in a *gen pass because the numbering spaces could potentially overlap. Since we build our *gen passes so that they can add low-suif code during the generation process, we cannot guarantee that the number spaces would never overlap even with some weird initialization of the virtual register numbers based on the next_instr_num. If you want to remove this lazy approach from the system, remember that the k_vreg_manager annotation does not exist before the *gen passes.

Also, please remember that the machine library's virtual register numbers are negative. Unlike the SUIF instruction number manager, we allow you to set the next unused virtual register number. This functionality allows one to build passes, like a procedure inliner, that reallocate virtual register numbers. Again, please remember that virtual register numbers are unique only within a procedure.

The Write_machine_proc() routine creates a list of SUIF in_gen instructions with machine library annotations from a list of machine instructions. Normally, you would call mproc_symtab::renumber_vregs() before Write_machine_proc(). The routine renumber_vregs() compacts the virtual register numbering space. You can call this routine at any time. You can delete your mproc_symtab variable after Write_machine_proc() has completed. Write_machine_proc() calculates the next_vreg_num value for the k_vreg_manager annotation without use of the virtual register manager, so you don't have to allocate a mproc_symtab pointer variable if your pass doesn't create new virtual register operands.

Print helpers

[*]

The following routines exist simply to make it easier to print a SUIF object in a machine-specific manner. They all require a file pointer as the first argument.

<machine print helpers>= (U->)

extern void Print_data_label(FILE *, sym_node *, char);
extern void Print_raw_immed(FILE *, immed);
extern void Print_symbol(FILE *, sym_node *);

EXPORTED_BY_MACHINE boolean skip_printing_of_unwanted_annotes;
DECLARE_LIST_CLASS(nonprinting_annotes_list, char *);
EXPORTED_BY_MACHINE nonprinting_annotes_list *nonprinting_annotes;

The routine Print_data_label() prints a sym_node name with the appropriate trailing character, specified by the third parameter, so that the string is interpreted as a data label in the output assembly language file.

The routine Print_raw_immed() prints an immed in the ``rawest'' manner possible. For example, we don't want any double quotes around strings or other such SUIF-specific printing rules being applied. Also, the offset fields of symbols are printed in bytes, not bits.

The routine Print_symbol() is a slight modification to the sym_node::print() method. Here, we don't want the leading ``.'' printed by the default SUIF print routine. This is typically an undesirable feature in an assembly language label.

These routines are used by the machine-specific print helper routines invoked by the mi_*::print() methods. See Section [->] for examples.

The global variable skip_printing_of_unwanted_annotes provides a convenient mechanism to disable the printing of certain annotations. We use this mechanism in printmachine to remove clutter from the resulting assembly listing files. This variable is set to FALSE by the machine library (i.e., enable printing of all annotations). The list nonprinting_annotes contains the annotations that should not be printed when skip_printing_of_unwanted_annotes is TRUE.

Operand helpers

[*]

In the machine library, instruction-pointer operands represent either an effective address (EA) calculation or a simple immediate value. Section [<-] provides a detailed discussion of EA calculation operands and the Is_ea_operand(operand) helper routine. [Earlier prototype versions of the machine library contained an immediate operand kind. The OPERAND_IMMED kind of operand has been removed. Instead, the machine library now represents immediate operands in the same way as the rest of SUIF. Immediate operands are stored in SUIF in_ldc instructions, and then this in_ldc instruction is attached to an immediate instruction as the immediate operand.]

<machine operand helpers>= (U->) [D->]
extern boolean Is_ea_operand(operand);
extern operand Immed_operand(immed &, type_node *);

Along with the immediate operand kind in the earlier versions of the machine library, there also existed a constructor for immediate operands. To replace this constructor, we have created a helper routine called Immed_operand(immed &, type_node *). This routine takes an immed and a type_node pointer as input parameters and returns an instruction pointer to an in_ldc instruction with the specified immediate value.

Please note that Immed_operand(immed &, type_node *) is meant to be used to create simple immediates, not effective address calculations. One should never call Immed_operand() with a type_node that is a ptr_type.

An additional helper for operands is:

<machine operand helpers>+= (U->) [<-D]
extern void
Map_operand(instruction *in, operand (*fun)(operand, boolean, void *), void *x);

This utility maps function fun over the operands of instruction in, passing three arguments: the operand, a flag that is true for source operands and false for destinations, and the pointer x. Each leaf operand to which fun is applied is replaced by the result of the call. Non-leaf operands (EA's and immediates) are expected to be returned unchanged.

machineUtil.h

[*]

This file contains helper functions that allow one to write machine-independent code for machine-specific optimizations.

<machineUtil.h>=
/* file "machineUtil.h" */

<SUIF copyright>

#ifndef MACHINE_UTIL_H
#define MACHINE_UTIL_H

<architecture and format distinguishers>

<instruction kind distinguishers>

<uniform access methods>

<architecture-specific opcode generators>

<other print helpers>

#endif

There are five kinds of routines in this file. The first kind takes a SUIF opcode and returns the architecture or instruction format for this opcode. Please note that both these routines work with the SUIF opcodes in addition to the Machine SUIF opcodes.

<architecture and format distinguishers>= (<-U)
extern char *which_architecture(mi_ops o);
extern int which_mformat(mi_ops o);

The next kind of routine takes a SUIF instruction pointer as the only parameter and then tells you something about the instruction (e.g. that it's an unconditional jump instruction). These routines are useful if your analysis or optimization pass is looking for a particular kind of instruction, but it does not care about the actual opcode.

<instruction kind distinguishers>= (<-U)
extern boolean Is_null(instruction *);
extern boolean Is_label(instruction *);
extern boolean Is_ldc(instruction *);
extern boolean Is_move(instruction *);
extern boolean Is_cmove(instruction *);         // conditional move
extern boolean Is_line(instruction *);          // line number marker

extern boolean Is_cti(instruction *);           // One of below is true
extern boolean Is_ubr(instruction *);           // unconditional jump/branch
extern boolean Is_cbr(instruction *);           // conditional (2 targets)
extern boolean Is_mbr(instruction *);           // multiway (n targets)
extern boolean Is_call(instruction *);          // includes coroutines calls
extern boolean Is_return(instruction *);

extern boolean Reads_memory(instruction *);
extern boolean Writes_memory(instruction *);

Hopefully, most of the routines are self-explanatory from their names; only a few deserve some explanation. The routine Is_null() returns TRUE if the opcode of the instruction is the null opcode. This is not the same as a NOP. Null opcodes are used as placeholders for annotations or assembly-language comments. The routine Is_line() returns TRUE if the instruction is a directive indicating the source line number at this point in the program.

The branching methods also deserve some explanation. The routine Is_cti() returns TRUE if the instruction is a control-transfer instruction. This also means that at least one of the next five helper routines are TRUE. The abbreviations are defined as follows: UBR stands for unconditional branch (single target); CBR stands for conditional branch (two targets, one implicit); and MBR stands for multi-way branch (N targets where N may be unknown). Please note that an instruction is a CBR whenever it has two targets, one of which is implicit (the fall-through target typically). This is independent of whether the branch evaluation is constant or conditional.

The third kind of routine provides us with a uniform way to access particular parts of instructions that require different methods depending upon whether the instruction is a simple SUIF instruction or a machsuif machine_instr. For example, code that creates a control-flow graph doesn't care about the actual opcode of an instruction, it just wants the target sym_node if the instruction is an unconditional jump.

<uniform access methods>= (<-U)
extern label_sym *Get_label(instruction *);     /* of label instr */
extern sym_node *Get_target(instruction *);     /* of branch/jump instr */
extern proc_sym *Get_proc(instruction *);       /* of call instr */

The fourth kind of routine is one that takes a pointer to an architectural description of the target machine and possibly a type_node pointer, and returns a machine-specific opcode that fulfills the requested action. For example, the helper routine Ubr_op(archInfo *) returns the opcode corresponding to an unconditional branch in the indicated architecture.

<architecture-specific opcode generators>= (<-U)
extern mi_ops Ubr_op(archinfo *);
extern mi_ops Label_op(archinfo *);
extern mi_ops Null_op(archinfo *);
extern mi_ops Load_op(archinfo *, type_node *);
extern mi_ops Store_op(archinfo *, type_node *);
extern mi_ops Move_op(archinfo *, type_node *);
extern mi_ops Invert_cbr_op(archinfo *, mi_ops);

The final set of routines help with printing. In particular, they are used by printmachine to create an ASCII assembly file. Again, most of these are straightforward, though a few deserve a few extra words of documentation. The Print_global_directives routine allows you to insert assembler directives at the top of your output assembly-language file that are valid during the entire file. For example, we use this feature to disable code reordering by the assembler. The Print_file_op routine takes a file number and a file string name. The Print_var_def routine requires, for some architectures, an integer that indicates the maximum size of a data item that is placed in the global pointer area. If the architecture does not require this parameter, the int value is ignored. The Print_proc_begin routine takes a file number that is then used for the initial line directive. This information aids in debugging. Again, some architectures do not need this information, and in those cases, this int value is ignored.

<other print helpers>= (<-U)
extern void Print_global_directives(archinfo *, file_set_entry *, FILE *);
extern void Print_extern_op(archinfo *, var_sym *, FILE *);
extern void Print_file_op(archinfo *, int, char *, FILE *);
extern void Print_var_def(archinfo *, var_sym *, int, FILE *);
extern void Print_proc_def(archinfo *, proc_sym *, FILE *);
extern void Print_proc_begin(archinfo *, proc_sym *,  FILE *);
extern void Print_proc_entry(archinfo *, proc_sym *, int, FILE *);
extern void Print_proc_end(archinfo *, proc_sym *, FILE *);

annoteHelper.h

[*]

This file contains the helper routines created for the machine library annotations. Currently, we have stand-alone helper routines for only one annotation: k_vsym_info.

<annoteHelper.h>=
/* file "annoteHelper.h" */

<SUIF copyright>

#ifndef ANNOTEHELPER_H
#define ANNOTEHELPER_H

/*              vsym_in_reg(var_sym *);         #* use SUIF is_reg() method */
extern void     vsym_clear_hreg(var_sym *);     /* hard register methods */
extern void     vsym_set_hreg(var_sym *, int);
extern int      vsym_get_hreg(var_sym *);
extern void     vsym_set_sp_offset(var_sym *, int);
extern int      vsym_get_sp_offset(var_sym *);
extern void     vsym_update_usage(var_sym *);   /* usage count methods */
extern int      vsym_used(var_sym *);           /* returns usage count */
extern void     vsym_set_preg(var_sym *, int);  /* parameter reg methods */
extern boolean  vsym_passed_in_preg(var_sym *);
extern int      vsym_get_preg(var_sym *);
extern sym_addr vsym_get_auto_sym(base_symtab *, int, int);

#endif /* ANNOTEHELPER_H */

As explained in Section [<-], the k_vsym_info annotation records several pieces of information related to automatic variables. These helper routines supply mechanisms to set, access, and query this information. In addition, we supply routines that set, clear, access, and query the SUIF register information attached to sym_node's. Please remember that we interpret a TRUE result from the sym_node::is_reg() method to mean that the sym_node is stored in a register, not that the sym_node is a register. By creating these hard register helper routines that manipulate the SUIF structures, we can have a uniform interface for var_sym information.

Currently, the parameter register methods are used to pass parameter register information between the *gen and ra* passes. The stack pointer methods are used inside the *fin passes. The usage methods are used by our stupid register allocator, ra0, which is no longer distributed. We do not consistently set the is_reg() flag when we allocate a var_sym to a register. Notice that this current mechanism for mapping a var_sym to a register is not powerful enough for register allocators that implement live-range splitting.

This documentation is out of date! The vsym_get_auto_sym scans the local symbol list from the indicated symbol table and returns the symbol that lives at the stack pointer offset specified in the argument. This routine returns NULL if the offset does not correspond to an auto variable in the specified symbol table. It requires the current size of the stack frame; passed as the second parameter.

As stated in Section [<-], this organization of this information is not very pretty, and this whole issue needs to be revisited.

eaHelper.h

[*]

This file contains the helper routines that aid in the creation and manipulation of effective-address (EA) calculations in Machine SUIF. Recall that we encode EA calculations as instruction-pointer operands that encapsulate expression trees. These trees contain SUIF operations. Please refer back to Section [<-] for a more-complete discussion.

<eaHelper.h>=
/* file "eaHelper.h" */

<SUIF copyright>

#ifndef EAHELPER_H
#define EAHELPER_H

/* Routines that help to create EA operands */
extern instruction *New_ea_base_p_disp(operand b, long d);
extern instruction *New_ea_base_p_disp(operand b, immed di);

extern instruction *New_ea_symaddr(sym_node *s, unsigned d = 0);
extern instruction *New_ea_symaddr(immed si);

extern instruction *New_ea_indexed_symaddr(operand i, sym_node *s, long d);

extern instruction *New_ea_base_p_indexS_p_disp(operand b, operand i,
                                                unsigned s, long d);

/* Routines that answer common queries about an EA expression tree */
extern boolean Is_ea_base_p_disp(instruction *);
extern boolean Is_ea_symaddr(instruction *);
extern boolean Is_ea_indexed_symaddr(instruction *);
extern boolean Is_ea_indexS_p_disp(instruction *);
extern boolean Is_ea_base_p_index_p_disp(instruction *);
extern boolean Is_ea_base_p_indexS_p_disp(instruction *);

/* Routines that help access parts of an EA calculation */
extern sym_node *Get_ea_symaddr_sym(instruction *);
extern int Get_ea_symaddr_off(instruction *);

// Unfinished code not yet needed.
// typedef void (*ea_map_f)(instruction *ea, void *x);
// extern void Map_ea(instruction *ea, ea_map_f f, void *x);

#endif /* EAHELPER_H */

In general, we have named our EA calculations after the names used in x86 basic programming model. The New_ea_* routines return pointers to SUIF expression trees corresponding to different types of EA modes. The letter ``p'' in a routine name stands for ``plus''. These routines are not meant to be an exhaustive list of helper routines; they are simply the ones that we have found useful to this point. Feel free to add more.

Here is a list of our supported effective-addressing modes:

The routine Get_ea_symaddr_off(instruction *) returns the optional byte displacment on a symaddr effective address.

Most of the query kind helper routines are (hopefully) obvious. The two routines Is_ea_indexS_p_disp and Is_ea_base_p_index_p_disp do not have their own unique constructors. They use the New_ea_base_p_indexS_p_disp constructor with the appropriate NULL operand or constant one input parameters. The underlying expression tree is more complicated than necessary for these forms, and these routines help to pinpoint the fluff.

Access to machine-specific data

[*]

To perform machine-specific optimizations, we must have some information concerning the target machine. The current version of machsuif maintains pointers to machine-specific information. This pointer information is recorded in a k_target_arch annotation which is attached to each file_set_entry. Using this pointer information, we build separate classes to manage the different kinds of architecture and machine-specific information. The rest of this section reviews how we access machine-specific data. The next section describes a particular class that encapsulates a piece of machine-specific data.

k_target_arch annotation

The k_target_arch annotation is created during code generation by the *gen machsuif passes, since these passes are machine specific. Please see the machsuif overview document for additional information on how the values for this annotation are set.

The k_target_arch annotation is a structured annotation with the following fields:

immed #0 char* architecture_family_name
immed #1 char* arch_version_number
immed #2 char* vendor_and_OS_designation
immed #3 char* machine_implementation

The architecture_family_name is a string and is the same as the string stored in the architecture() method of each instruction in a procedure tree_node_list. It quickly distinguishes, for example, between MIPS and Alpha instructions, but it does not distinguish between a MIPS-I and a MIPS-IV architecture. Distinguishing versions within an architecture family is handled by the arch_version_number value. Please note that this value is encoded as a string. By convention, we start version numbers with a number. For example, the MIPS-I architecture would be recorded as version 1 while MIPS-IV would be recorded as 4. Following this convention, we can use strcmp() to perform ordering tests, e.g. the code strcmp("1.3a", version()) <= 0 will verify that a feature implemented in version 1.3a will work in the current target.

The third value in this annotation contains the vendor and operating system information for the target machine. This item completes the <architecture>-<vendor>-<os> string kept in the SUIF MACHINE environment variable.

The final value in this annotation is a string describing the target hardware implementation. We use the value of this string, along with the architectural family and version strings, to construct a basename for the files containing the architecture and machine-specific information to be used during compilation. The machsuif overview document provides more information on the structure and location of these machine-specific data files.

archinfo class

We have built a common interface to manage the architecture- and machine-specific data used during compilation. The key to this common interface is the <class archinfo> described below. From this class, we can build other classes that manage particular pieces of machine-specific information. The <class reginfo> is one example of these associated classes. The machsuif scheduling library provides other examples of hardware-resource-management classes that rely on the <class archinfo>.

<archInfo.h>=
/* file "archInfo.h */

<SUIF copyright>

#ifndef ARCHINFO_H
#define ARCHINFO_H

<class archinfo>

<reginfo enums>

<class reginfo>

<register defines>

#endif /* ARCHINFO_H */

The archinfo class is defined as follows:

<class archinfo>= (<-U)
class archinfo {
  private:
    char *fam;          /* architectural family name */
    char *ver;          /* architectural revision/version number */
    char *vos;          /* vendor and os designation for target */
    char *impl;         /* chip name and variant */

    if_ops first;       /* range of valid opcodes for this arch. */
    if_ops last;        /* range is [first_op, last_op) */

  protected:
    void init_from_annote(file_set_entry *fse);

  public:
    archinfo(file_set_entry *fse)       { init_from_annote(fse); }
    archinfo(proc_sym *p)               { init_from_annote(p->file()); }
    ~archinfo()                         {}

    char *family()              { return fam; }
    char *version()             { return ver; }
    char *vendor_os()           { return vos; }
    char *implementation()      { return impl; }

    if_ops first_op()           { return first; }
    if_ops last_op()            { return last; }
    char *op_string(if_ops);

    FILE *fopen_mdfile(char *ext, char *mode="r"); 

    void print(FILE *fd = stdout);
};

EXPORTED_BY_MACHINE archinfo *target_arch;      /* pointers to target data */

Most of the methods in this class are obvious. The only interesting method is archinfo::fopen_mdfile(char *, char *). This method takes a filename extension and some access mode information, and then searches the impl directory for the specified machine-specific data file. It returns a handle to the file containing the machine-specific data. This handle is passed to a class capable of reading and managing access to the data. The <class reginfo> in the next section is an example of such a class.

The machine library provides access to the target architecture and implementation data through the global variable target_arch. When building a machsuif pass, you would typically define this variable within your file_set iterator loop. The following code illustrates this concept.

<example code that reads some machine-specific data>=
    /* Process each input file */
    file_set_entry *fse;
    fileset->reset_iter();
    while ((fse = fileset->next_file())) {
        ifile = fse->name();

        /* get machine-specific data needed by this optimization */
        target_arch = new archinfo(fse);
        target_regs = new reginfo(target_arch->fopen_mdfile("reg"));

        /* perform optimization */
        Process_file(fse);

        delete target_regs;
        delete target_arch;
    }

The example code indicates that this optimization pass requires information about the registers in the target architecture. This information is read from the appropriate file in the impl directory. The register information is kept in files with the file extension ``.reg'' as explained in the next section. A pass might require multiple target_* managers for each type of machine-specific data that it requires. Notice that you only need to read the data you require.

Registers and the reginfo class

[*]

One piece of architecture information that is commonly needed is the specifics of the register set of the target machine. The <class reginfo> manages this information.

We use abstract register names (actually an integer) when referring to hard registers in the machine library. The actual string value expected by the assembler for a specific architectural register is available from the name(int) method in the <class reginfo>. With abstract register names, routines that do not care, for example, about the specific number for the stack pointer register in the current target architecture can still reference the stack pointer using the abstract name REG_sp. The architecture-specific print routines automatically translate these abstract register names into the appropriate target-specific register number.

We organize an architecture's hard registers into a two-level hierarchy. The first level of this hierarchy splits an architecture's registers into register banks. Currently, the machine library defines four register banks, as shown below. The second level of this hierarchy splits the hard registers into one of several register conventions. Within a register convention, an architecture will have zero or more hard registers. In a machsuif/impl/*.reg file, you assign each hard register within a register bank and convention a unique id (i.e. a number from 0 to one less than the number of registers in that bank's convention). Given this assignment, we can now refer to a hard register abstractly by specifying its bank, convention, and index number.

<reginfo enums>= (<-U)
enum {  /* register banks */
    GPR = 0,            /* general-purpose (e.g. integer) registers */
    FPR,                /* floating-point registers */
    SEG,                /* segment registers */
    CTL,                /* control (e.g. PC, condition code) registers */
    LAST_REG_BANK       /* must be the last entry in the enum */
}; 

extern char *reg_bank_string(int);

enum {  /* register conventions */
    CONST0 = 0,         /* constantly zero */
    RA,                 /* return address register */
    SP,                 /* stack pointer */
    GP,                 /* global pointer */
    FP,                 /* frame pointer */
    ARG,                /* argument registers */
    RET,                /* function return registers */
    SAV,                /* callee saved registers */
    TMP,                /* caller saved registers */
    ASM_TMP,            /* assembler temporary registers (caller saved) */
    GEN,                /* generic -- catch all for other registers in bank */
    LAST_REG_CONV       /* must be the last entry in the enum */
}; 

extern char *reg_conv_string(int);

We also provide two routines, reg_bank_string(int) and reg_conv_string(int), to help with the human-readable printing of the reginfo enum's.

We place every architecture's hard register into this hierarchy in exactly one place, even though a hard register can belong theoretically to multiple conventions within a single register bank. For example, parameter registers are often considered to be temporary (caller-saved) registers too. This is not a problem as long as each hard register appears only once in the hierarchy. The small, ugly aspect of this situation is that the abstract reference may not always be the best name for a particular context. To continue the example, it would be nice to refer to a parameter register with a temporary register reference when the register allocator assigns it as a temporary register, but you cannot.

We define a register bank to be any set of registers that have the same access cost with respect to a particular instruction. This means that architectures with multiple GPR banks (e.g. clustered VLIW register files) should contain multiple ``GPR'' register banks (e.g. GPR1, GPR2, etc.). Our current system does not provide a register allocator capable of bank assignment and inter-bank register allocation for these types of architectures.

As the file impl/alpha.reg illustrates, an architecture defines only those banks and conventions that it uses. You can easily change the number of registers in a bank, the number of banks in an architecture, or the conventions used by a bank by simply changing the appropriate data file for that architecture. The only time that you need to modify the machine library and recompile the machsuif system is when you add new values to the register banks and conventions enum's. Remember to fix the archData.l and archData.y files too.

To deal with architectures that gang registers together to handle operations on larger datatypes (e.g. the use of two FP registers in the MIPS-I architecture to hold double-precision FP numbers) and architectures that grew in the size of their datapath as they grew older (e.g. the x86 architecture permits the addressing of two separate 8-bit chunks of the A register), the machine library maintains information about the natural width and the addressable grain size of the registers in each register bank. This information is specified in the *.reg file. In the simplest case, a register is always accessed in its natural width. For example, the Alpha architecture has 64-bit registers (the natural width), and you modify all 64 bits whenever you write one of these registers. Thus, the grain size of the GPR and FPR banks in the Alpha architecture is 64 bits. In the latest x86 architecture, the A register can be accessed as an 8-bit, 16-bit, or 32-bit quantity. The natural size of this register is 32 bits, but its grain size is 8 bits (not all 8-bit grains are addressable in the architecture though an abstract name exists for each grain).

To see how the machine library uses this information, we consider a simple example from the MIPS-I architecture. In this architecture, the FPR bank has a natural width of 32 bits and a grain size of 32 bits. When a DP FP value is written to an even FP register, the size of the operand data is 64 bits (indicated by the operand type information). The machine library uses the grain size information to realize that a write of this operand data requires two FPR registers. By convention, the library uses the specified abstract register and the next sequential grains in the machine description file until the total grain size is equal to the operand data size. The implication here is that the MIPS-I machsuif instruction that writes a DP FP value specifies only a single abstract register name as the destination. The machine library (including the register allocation and dependence analysis passes) realizes that multiple registers are written. Another implication is that your passes had better maintain the correct operand type information if you expect the machine library to work correctly.

As an aside, virtual registers have no predefined width. They are as wide as they need to be in order to hold the operand data. When assigning hard registers to virtual registers, even if the value in a virtual register requires multiple hard registers, we place only the name for the starting hard register grain in the destination operand corresponding to the virtual register. The type information conveys the extra information indicating the other occupied grains. You create multiple destination operands only when an instruction produces multiple distinct results.

Looking at things for the other direction, consider the x86 architecture. We define that the A, B, C, and D registers each comprise four 8-bit grains for a total natural width of 32 bits. How many of these grains are used depends upon the size of the operand. An 8-bit operand that wants to write only AL, for example, would write only the first 8-bit grain. An 8-bit operand that wants to write AH would write the grain following the one for AL. A 16-bit operand that wants to write only AX would write the first two 8-bit grains for hard register A (i.e. it would write AL and AH); the operand uses the same abstract register name used by the write to AL. A 32-bit operand would write all four grains. Notice that if we wanted to write an 8-bit value into EAX, we would specify the operand size as 32-bits.

Make sure that the passes know about the size of a memory container, i.e. make sure that the correct code is generated when we want to write only the lower 8 bits of EAX into an 8-bit wide memory location.

To manage this register information, the machine library defines the <class reginfo>. You define the information in this class in your impl/*.reg file. You access this information through the target_regs variable with help from the target_arch variable. Please see the <example code that reads some machine-specific data>.

<class reginfo>= (<-U)
struct reg_desc {
    int bank;           /* register bank */
    int conv;           /* primary convention */
    int encoding;       /* hardware encoding */
    char *name;         /* assembler name */
}; 

class reginfo {
    friend int yyparse(void); 
    friend void yy_rpair(int, char *);

  private:
    int n;              /* number of grains contained in all register banks */
    int *num_in_b;      /* size n_banks */
    int *num_in_bc;     /* size n_banks * n_conventions */
    int *start_of_b;    /* size n_banks */
    int *start_of_bc;   /* size n_banks * n_conventions */
    int *width_of_b;    /* size n_banks */
    int *gsize_of_b;    /* size n_banks */
    reg_desc *desc;     /* size n */
    int *m2a_map;       /* machine encoding to abstract number map; size n */

  public:       
    int total_grains() const { return n; }
    int num_in(int b) const { 
        assert(b < LAST_REG_BANK); 
        return num_in_b[b]; 
    }
    int num_in(int b, int c) const {
        assert(b < LAST_REG_BANK && c < LAST_REG_CONV); 
        return num_in_bc[b * LAST_REG_CONV + c]; 
    }
    int start_of(int b) const {
        assert(b < LAST_REG_BANK); 
        return start_of_b[b];
    }
    int start_of(int b, int c) const {
        assert(b < LAST_REG_BANK && c < LAST_REG_CONV); 
        return start_of_bc[b * LAST_REG_CONV + c]; 
    }
    int width_of(int b) const {
        assert(num_in(b) > 0);
        return width_of_b[b];
    }
    int grain_size_of(int b) const {
        assert(num_in(b) > 0);
        return gsize_of_b[b];
    }
    int lookup(int b, int c, int i) const {
        assert(i < num_in(b, c)); 
        return start_of(b, c) + i; 
    }

    reg_desc describe(int ar) const {
        assert(ar < n);
        return desc[ar];
    }
    char *name(int ar) const { return describe(ar).name; }

    /* mapping functions between abstract reg numbers and machine encodings */
    int a2m(int ar);
    int m2a(int b, int mr);

    void print(FILE *fd = stdout);

    reginfo(FILE *fd); 
    ~reginfo(); 
};

EXPORTED_BY_MACHINE reginfo *target_regs;    /* target register information */

The total_grains() method returns the total number of register grains in the machine. This number is the same as the number of registers in the machine ONLY IF the grain size equals the natural width in each register bank. There is a simple calculation that you can perform to determine the total number of architecturally-visible hard registers in the machine (we never need it so we don't have a method for it). The methods num_in(int b) and num_in(int b, int c) return the number of grains in a register bank b and in a register bank b's convention c, respectively. If a num_in method returns 0, then the bank and/or its convention queried do not exist. The start_of methods return the number of the first abstract register in the queried bank and queried bank's convention respectively. The width_of(int b) method returns the natural width of the bank b in bits. The grain_size_of(int b) method returns the grain size of the bank b in bits. All registers within a bank have the same natural width and grain size.

The lookup(int b, int c, int i) method returns the abstract register number for the hard register assigned to index i of bank b in convention c. The index starts at 0 and goes to num_in(b,c) - 1. Since the registers in each bank's convention are numbered sequentially in the space of abstract register numbers, one can build a bit_set for a bank b and convention c knowing the value returned by lookup(b,c,0) and num_in(b,c).

The method describe(int ar) provides the reverse mapping from an abstract register number ar to its bank, convention, machine encoding, and assembler string. The data is returned in a reg_desc structure. To get the index, you subtract the abstract register number of the zeroth element of ar's bank and convention from ar. The machine encoding is the value used in a machine instruction to specify this register. We need this information, for example, when setting the saved register bit masks in Alpha. Notice that the encoding is unique only within a bank. We provide the method name(int ar) as a short cut to get to the assembler name for the abstract register number ar.

The methods a2m(int) and m2a(int,int) perform the mappings between an abstract register name and the machine encoding. As stated above, in order to obtain the abstract register name from a machine encoding, you must also specify the register bank for this hard register.

We use the print method for debugging purposes only.

Finally, it is inconvenient to type target_regs->lookup(b,c,i) every time that you want to refer to a register. Hence, we created some macro's to make our lives easier.

<register defines>= (<-U)
#define REG(b, c, i) (target_regs->lookup(b, c, i))
#define REG_const0      (REG(GPR, CONST0, 0))
#define REG_ra          (REG(GPR, RA, 0))
#define REG_sp          (REG(GPR, SP, 0))
#define REG_gp          (REG(GPR, GP, 0))
#define REG_fp          (REG(GPR, FP, 0))

Currently we have an implicit mapping between an operand type and its home register bank. We may want to make this mapping explicit in the machine description file someday.

Example of architecture-specific library files

[*]

In this section, we present the header files for one target architecture, Digital Alpha. Each architecture has two header files and two data files in the machsuif/machine directory. For the Alpha architecture, the file alphaInstr.h contains the Alpha-specific annotations and helper routines that are invoked by the machine library functions. The file alphaOps.h simply contains the opcode and opcode-extension enumerations along with a few extra helper routines directly related to the determination of the machine instruction format for each opcode and to the printing of opcodes and opcode extensions. The alpha.data file contains the actual complete listing of Alpha assembly opcodes and pseudo-ops. This data file is read by the alphaOps.{h,cc} files using a lcc-like, table-driven macro. This table collects all the data related to each opcode and ensures that we maintain alignment between the opcode enumeration value and its specific attributes.

The alpha*.* files in the machsuif/impl directory are machine description files. Please refer to the machsuif overview document and the README.* files in the impl directory for information on the format of these machine description files.

alphaInstr.h

We start with a presentation of alphaInstr.h. It declares the architecture family string, k_alpha, and the library initialization routine, init_alpha(). The initialization routine is invoked by init_machine(), which is invoked by init_suif(). The file also declares the Alpha-specific helper functions needed to implement the generic library helper functions described in Section [<-].

<alphaInstr.h>=
/* file "alphaInstr.h" */

<SUIF copyright>

#ifndef ALPHA_INSTR_H
#define ALPHA_INSTR_H

class immed_list;
class type_node;
class instruction;
class machine_instr;
class mi_lab;
class mi_bj;
class mi_rr;
class mi_xx;

EXPORTED_BY_MACHINE char *k_alpha;

<alpha-specific annotations>

extern void init_alpha();

<alpha_is_* helper functions>
<alpha_*_op helper functions>
<alpha_*_print helper functions>
<alpha_print_* helper functions>

#endif

We define one Alpha-specific annotation that extends the SUIF set of intial data annotations. The k_gprel_init annotation indicates that a variable should be initialized with the (possibly truncated) signed displacement between the global pointer value and the address of a symbol V specified in the immed_list. The first entry in the immed_list is an integer N representing the size of the displacement in bits. Currently, this value must be 32. The second entry in the immed_list is the symbol V.

<alpha-specific annotations>= (<-U)
EXPORTED_BY_MACHINE char *k_gprel_init;

The following three sets of helper routines are needed by the machine library helper routines. One should never need to invoke them directly.

<alpha_is_* helper functions>= (<-U)
extern boolean alpha_is_ldc(instruction *);
extern boolean alpha_is_move(instruction *);
extern boolean alpha_is_cmove(instruction *);
extern boolean alpha_is_line(instruction *);
extern boolean alpha_is_ubr(instruction *);
extern boolean alpha_is_cbr(instruction *);
extern boolean alpha_is_call(instruction *);
extern boolean alpha_is_return(instruction *);
extern boolean alpha_reads_memory(instruction *);

<alpha_*_op helper functions>= (<-U)
extern mi_ops alpha_load_op(type_node *);
extern mi_ops alpha_store_op(type_node *);
extern mi_ops alpha_move_op(type_node *);

<alpha_*_print helper functions>= (<-U)
extern void alpha_lab_print(mi_lab *, FILE *);
extern void alpha_bj_print(mi_bj *, FILE *);
extern void alpha_rr_print(mi_rr *, FILE *);
extern void alpha_xx_print(mi_xx *, immed_list *, FILE *);

<alpha_print_* helper functions>= (<-U)
extern void alpha_print_global_directives(file_set_entry *, FILE *);
extern void alpha_print_extern_op(var_sym *, FILE *);
extern void alpha_print_file_op(int, char *, FILE *);
extern void alpha_print_var_def(var_sym *, int, FILE *);
extern void alpha_print_proc_def(proc_sym *, FILE *);
extern void alpha_print_proc_begin(proc_sym *, FILE *);
extern void alpha_print_proc_entry(proc_sym *, int, FILE *);
extern void alpha_print_proc_end(proc_sym *, FILE *);
extern void alpha_print_operand(operand *, FILE *);

alphaOps.h

The file alphaOps.h contains the enumerations for the complete listing of the ALPHA assembly opcodes, pseudo-ops, opcode extensions, and the code generator/scheduler pseudo-ops. To modify the contents of the opcode enumeration, you should modify alpha.data. Currently, the opcode extensions are enumerated directly in this file.

We have defined several special values to bracket the entire set of Alpha opcodes and opcode extensions. You must make sure that any new values reside between these special values.

<alphaOps.h>=
/* file "alphaOps.h" */

<SUIF copyright>

#ifndef ALPHA_OPS_H
#define ALPHA_OPS_H

const int OP_BASE_ALPHA = 2000; /* start of ai_ops enumeration */

enum /* mi_ops */ {
    ao_FIRST_OP = OP_BASE_ALPHA,        /* start of ALPHA enumeration */

    #undef xx
    #define xx(opcode, string, format) opcode,
    #include "alpha.data"

    ao_LAST_OP
} ;

enum /* mi_op_exts */ {
    aoe_FIRST_OP_EXT = ao_LAST_OP,

    /** VAX/IEEE rounding mode qualifiers */
    aoe_round_normal,           /* normal rounding mode controlled by FPCR */
    aoe_round_chopped,          /* chopped, i.e. truncate */
    aoe_round_p_inf,            /* round toward plus infinity */
    aoe_round_m_inf,            /* round toward minus infinity */

    /** VAX/IEEE trap modes */
    aoe_trap_none,              /* imprecise, underflow disabled, (and, for
                                 * IEEE, inexact disabled) */
    aoe_trap_u,                 /* imprecise, underflow enabled, (and, for
                                 * IEEE, inexact disabled) */
    aoe_trap_s,                 /* software and underflow disabled (not valid
                                 * option for IEEE FP) */
    aoe_trap_su,                /* software, underflow enabled, (and, for
                                 * IEEE, inexact disabled) */
    aoe_trap_sui,               /* software, underflow enabled, and inexact
                                 * enabled (not valid option for VAX) */

    /** VAX/IEEE convert-to-integer trap modes */
    aoe_itrap_none,             /* imprecise, int overflow disabled, (and,
                                 * for IEEE, inexact disabled) */
    aoe_itrap_v,                /* imprecise, int overflow enabled, (and,
                                 * for IEEE, inexact disabled) */
    aoe_itrap_s,                /* software, int overflow disabled, (not
                                 * valid option for IEEE) */
    aoe_itrap_sv,               /* software, int overflow enabled, (and,
                                 * for IEEE, inexact disabled) */
    aoe_itrap_svi,              /* software, int overflow ensabled, and inexact
                                 * enabled (not valid option for VAX) */

    aoe_LAST_OP_EXT
} ;

extern char *alpha_op_string(mi_ops o);
extern mi_formats alpha_which_mformat(mi_ops o);
extern char *alpha_op_ext_string(mi_op_exts e);

<opcode helper tables>

#endif /* ALPHA_OPS_H */

As we saw in alphaInstr.h, the helper functions in this file are also used only by the machine library class methods and helper routines. The alpha_op_string(mi_ops) function returns the string name of an Alpha opcode value. The alpha_which_mformat(mi_ops) function returns the machine instruction format used by each Alpha opcode. The alpha_op_ext_string(mi_op_exts) function returns the string name of the Alpha opcode extension value.

We have also created the table alpha_invert_table to help with the mapping of a conditional branch opcode to its inversion. Only a single entry is required for any opcode pair. This table is used by the helper routine Invert_cbr_op.

<opcode helper tables>= (<-U)
extern int alpha_invert_table[];

Acknowledgments

This library benefited from the time and patience of many people in the HUBE research group at Harvard and the SUIF research group at Stanford. In particular, I would like to acknowledge the help of Chris Wilson, Cliff Young, and Glenn Holloway.

This work is supported in part by an DARPA/NSF infrastructure grant (NDA904-97-C-0225) and a NSF Young Investigator award (CCR-9457779). We also gratefully acknowledge the generous support of this research by Advanced Micro Devices, Digital Equipment, Hewlett-Packard, International Business Machines, Intel, and Microsoft.