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>
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 */
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
};
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 *);
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.
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.
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.
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);
};
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);
};
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;
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 */
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.
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.
Isagenthe only code generator to include thek_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).
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.
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 |
| ... |
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.
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 |
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.
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.
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.
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.
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.
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 *);
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.
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:
base_p_disp -- base plus displacement. The base
is either a var_sym operand or a register operand. The
displacement is a signed integer value with a unit of bytes.
symaddr -- address of a simple relocatable symbol
plus a optional displacement. You simply specify the symbol
that you want to address. Due to the underlying representation,
the displacement is an unsigned integer. The displacement is
specified in bytes, though internally it is stored as a displacement
in bits.
indexed_symaddr -- indexed, relocatable address.
The index operand must be a register operand, the sym_node
is the symbol whose address you want taken, and the displacement
is a signed value in units of bytes. The address is generated
by adding the contents of the index register to the relocatable
symbol's address, plus the optional signed byte displacment.
base_p_indexS_p_disp -- base register plus (index register
times an unsigned scale factor) plus a signed literal displacement.
The scale factor and the displacement are specified in bytes. This
is our most general form. If you wish to create an ``(index times scale)
plus displacement'' form or a ``base plus index plus displacement''
form, you should use this form with a NULL base operand or a constant
one in the appropriate argument locations. Since these forms are used
infrequently, we are not concerned with the wasteful encoding.
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.
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 |
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.
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.
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.
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 *);
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[];
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.