Tuesday, August 29, 2017

GCC target description macros and functions

This is part four of a series “Writing a GCC back end”.

The functionality that cannot be handled by the machine description in machine.md is implemented using target macros and functions in machine.h and machine.c. Starting from an existing back end that is similar to you target will give you a reasonable implementation for most of these, but you will need to update the implementation related to the register file and addressing modes to correctly describe your architecture. This blog post describes the relevant macros and functions, with examples from the Moxie back end.

Inspecting the RTL

Some of the macros and functions are given RTL expressions that they need to inspect in order to do what is expected. RTL expressions are represented by a type rtx that is accessed using macros. Assume we have a variable
rtx x;
containing the expression
(plus:SI (reg:SI 100) (const_int 42))
We can now access the different parts of it as
  • GET_CODE(x) returns the operation PLUS
  • GET_MODE(x) returns the operation’s machine mode SImode
  • XEXP(x,0) returns the first operand – an rtx corresponding to (reg:SI 100)
  • XEXP(x,1) returns the second operand – an rtx corresponding to (const_int 42)
XEXP() returns an rtx which is not right for accessing the value of a leaf expression (such as reg) – those need to be accessed using a type-specific macro. For example, the integer value from (const_int 42) is accessed using INTVAL(), and the register number from (reg:SI 100) is accessed using REGNO().

For an example of how this is used in the back end, suppose our machine have load and store instructions that accept an address that is a constant, a register, or a register plus a constant in the range [-32768, 32767], and we need a function that given an address expression tells if it is a valid address expression for the target. We could write that function as
bool
valid_address_p (rtx x)
{
  if (GET_CODE (x) == SYMBOL_REF
      || GET_CODE (x) == LABEL_REF
      || GET_CODE (x) == CONST)
    return true;
  if (REG_P (x))
    return true;
  if (GET_CODE(x) == PLUS
      && REG_P (XEXP (x, 0))
      && CONST_INT_P (XEXP (x, 1))
      && IN_RANGE (INTVAL (XEXP (x, 1)), -32768, 32767))
    return true;
  return false;
}

Registers

The register names are specified as
#deefine REGISTER_NAMES             \
  {                                 \
    "$fp", "$sp", "$r0", "$r1",     \
    "$r2", "$r3", "$r4", "$r5",     \
    "$r6", "$r7", "$r8", "$r9",     \
    "$r10", "$r11", "$r12", "$r13", \
    "?fp", "?ap", "$pc", "?cc"      \
  }
The names are used for generating the assembly files, and for the GCC extension letting programmers place variables in specified registers
register int *foo asm ("$r12");
The Moxie architecture does only have 18 registers, but it adds two fake registers ?fp and ?ap for the frame pointer and argument pointer (used to access the function’s argument lists). This is a common strategy in GCC back ends to simplify code generation and elimination of unneeded frame pointer and argument pointers, and there there is a mechanism (ELIMINABLE_REGS) that rewrites these fake registers to real registers (such as the stack pointer).

The GCC RTL contains virtual registers (which are called pseudo registers in GCC) before the real registers (called hard registers) are allocated. The registers are represented as an integer, where 0 is the first hard register, 1 is the second hard register, etc., and the pseudo registers follows after the last hard register. The back end specifies the number of hard registers by specifying the first pseudo register
#define FIRST_PSEUDO_REGISTER 20

Some of the registers, such as the program counter $pc, cannot be used by the register allocator, so we need to specify which registers the register allocator must avoid
#define FIXED_REGISTERS             \
  {                                 \
    1, 1, 0, 0,                     \
    0, 0, 0, 0,                     \
    0, 0, 0, 0,                     \
    0, 0, 0, 1,                     \
    1, 1, 1, 1                      \
  }
where 1 means that it cannot be used by the register allocator. Similarly, the register allocator needs to know which registers may be changed by calling a function
#define CALL_USED_REGISTERS         \
  {                                 \
    1, 1, 1, 1,                     \
    1, 1, 1, 1,                     \
    0, 0, 0, 0,                     \
    0, 0, 1, 1,                     \
    1, 1, 1, 1                      \
  }
where 1 means that the register is clobbered by function calls.

It is possible to specify the order in which the register allocator allocates the registers. The Moxie back end does not do this, but it could have done it with something like the code below that would use register 2 ($r0) for the first allocated register, register 3 ($r1) for the second allocated register, etc.
#define REG_ALLOC_ORDER                     \
  {                                         \
    /* Call-clobbered registers */          \
    2, 3, 4, 5, 6, 7, 14                    \
    /* Call-saved registers */              \
    8, 9, 10, 11, 12, 13,                   \
    /* Registers not for general use.  */   \
    0, 1, 15, 16, 17, 18, 19                \
  }
For an example of where this can be used, consider an architecture with 16 registers and “push multiple” and “pop multiple” instructions that push/pop the last \(n\) registers. The instructions are designed to store call-saved registers when calling a function, and it makes sense to allocate the call-saved registers in reverse order so the push/pop instructions save as few registers as possible
#define REG_ALLOC_ORDER                         \
  {                                             \
    /* Call-clobbered registers */              \
    0, 1, 2, 3,                                 \
    /* Call-saved registers */                  \
    15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4    \
  }

There are usually restrictions on how registers can be used (e.g. the Moxie $pc register cannot be used in arithmetic instructions), and these restrictions are described by register classes. Each register class defines a set of registers that can be used in the same way (for example, that can be used in arithmetic instructions). There are three standard register classes that must be defined
  • ALL_REGS – containing all of the registers.
  • NO_REGS – containing no registers.
  • GENERAL_REGS – used for the ‘r’ and ‘g’ constraints.
and the back end can add its own as needed. The register classes are implemented as
enum reg_class
{
  NO_REGS,
  GENERAL_REGS,
  SPECIAL_REGS,
  CC_REGS,
  ALL_REGS,
  LIM_REG_CLASSES
};

#define N_REG_CLASSES LIM_REG_CLASSES

#define REG_CLASS_NAMES             \
  {                                 \
    "NO_REGS",                      \
    "GENERAL_REGS",                 \
    "SPECIAL_REGS",                 \
    "CC_REGS",                      \
    "ALL_REGS"                      \
  }

#define REG_CLASS_CONTENTS                           \
  {                                                  \
    { 0x00000000 }, /* Empty */                      \
    { 0x0003FFFF }, /* $fp, $sp, $r0 to $r13, ?fp */ \
    { 0x00040000 }, /* $pc */                        \
    { 0x00080000 }, /* ?cc */                        \
    { 0x000FFFFF }  /* All registers */              \
  }
REG_CLASS_CONTENTS consists of a bit mask for each register class, telling with registers are in the set. Architectures having more than 32 registers use several values for each register class, such as
    { 0xFFFFFFFF, 0x000FFFFF }  /* All registers */  \

The back end also need to implement the REGNO_REG_CLASS macro that returns the register class for a register. There are in general several register classes containing the registers, and the macro should return a minimal class (i.e. one for which no smaller class contains the register)
#define REGNO_REG_CLASS(REGNO) moxie_regno_to_class[ (REGNO) ]

const enum reg_class riscv_regno_to_class[FIRST_PSEUDO_REGISTER] = {
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, GENERAL_REGS, GENERAL_REGS,
  GENERAL_REGS, GENERAL_REGS, SPECIAL_REGS, CC_REGS
};

The back end may also need to set a few target macros detailing how values fit in registers, such as
/* A C expression that is nonzero if it is permissible to store a
   value of mode MODE in hard register number REGNO (or in several
   registers starting with that one).  */ 
#define HARD_REGNO_MODE_OK(REGNO,MODE) 1
See section 17.7.2 in “GNU Compiler Collection Internals” for a list of those macros.

Addressing modes

Addressing modes varies a bit between architectures – most can do “base + index” addressing, but there are often restrictions on the format of the index etc. The general functionality is described by five macros:
/* Specify the machine mode that pointers have.
   After generation of rtl, the compiler makes no further distinction
   between pointers and any other objects of this machine mode.  */
#define Pmode SImode

/* Maximum number of registers that can appear in a valid memory
   address.  */
#define MAX_REGS_PER_ADDRESS 1

/* A macro whose definition is the name of the class to which a
   valid base register must belong.  A base register is one used in
   an address which is the register value plus a displacement.  */
#define BASE_REG_CLASS GENERAL_REGS

/* A macro whose definition is the name of the class to which a
   valid index register must belong.  An index register is one used
   in an address where its value is either multiplied by a scale
   factor or added to another register (as well as added to a
   displacement).  */
#define INDEX_REG_CLASS NO_REGS

/* A C expression which is nonzero if register number NUM is suitable
   for use as a base register in operand addresses.  */
#ifdef REG_OK_STRICT
#define REGNO_OK_FOR_BASE_P(NUM)   \
  (HARD_REGNO_OK_FOR_BASE_P(NUM)    \
   || HARD_REGNO_OK_FOR_BASE_P(reg_renumber[(NUM)]))
#else
#define REGNO_OK_FOR_BASE_P(NUM)   \
  ((NUM) >= FIRST_PSEUDO_REGISTER || HARD_REGNO_OK_FOR_BASE_P(NUM))
#endif

/* A C expression which is nonzero if register number NUM is suitable
   for use as an index register in operand addresses.  */
#define REGNO_OK_FOR_INDEX_P(NUM) 0
where
#define HARD_REGNO_OK_FOR_BASE_P(NUM) \
  ((unsigned) (NUM) < FIRST_PSEUDO_REGISTER \
   && (REGNO_REG_CLASS(NUM) == GENERAL_REGS \
       || (NUM) == HARD_FRAME_POINTER_REGNUM))
is a helper macro the Moxie back end has introduced.

The macros REGNO_OK_FOR_BASE_P and REGNO_OK_FOR_INDEX_P have two versions – the normal version that accepts all pseudo registers as well as the valid hard registers, and one “strict” version that only accepts the valid hard registers. The strict version is used from within the register allocator, so it needs to check pseudo registers against the reg_number array to accept cases where the pseudo register is in the process of being allocated to a hard register.

The back end also needs to implement the TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P hook that is used by the compiler to check that the address expression is valid (this is needed as the hardware may have more detailed constraints that cannot be expressed by the macros above). This is done similarly to the valid_address_p example in “inspecting the IR” above, but it needs to handle the “strict” case too. The Moxie back end implements this as
static bool
moxie_reg_ok_for_base_p (const_rtx reg, bool strict_p)
{
  int regno = REGNO (reg);

  if (strict_p)
    return HARD_REGNO_OK_FOR_BASE_P (regno)
    || HARD_REGNO_OK_FOR_BASE_P (reg_renumber[regno]);
  else    
    return !HARD_REGISTER_NUM_P (regno)
    || HARD_REGNO_OK_FOR_BASE_P (regno);
}

static bool
moxie_legitimate_address_p (machine_mode mode ATTRIBUTE_UNUSED,
       rtx x, bool strict_p,
       addr_space_t as)
{
  gcc_assert (ADDR_SPACE_GENERIC_P (as));

  if (GET_CODE(x) == PLUS
      && REG_P (XEXP (x, 0))
      && moxie_reg_ok_for_base_p (XEXP (x, 0), strict_p)
      && CONST_INT_P (XEXP (x, 1))
      && IN_RANGE (INTVAL (XEXP (x, 1)), -32768, 32767))
    return true;
  if (REG_P (x) && moxie_reg_ok_for_base_p (x, strict_p))
    return true;
  if (GET_CODE (x) == SYMBOL_REF
      || GET_CODE (x) == LABEL_REF
      || GET_CODE (x) == CONST)
    return true;
  return false;
}

#undef  TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P
#define TARGET_ADDR_SPACE_LEGITIMATE_ADDRESS_P moxie_legitimate_address_p

Finally, TARGET_PRINT_OPERAND_ADDRESS must be implemented to output the correct syntax for the address expressions in the assembly file generated by the compiler:
static void
moxie_print_operand_address (FILE *file, machine_mode, rtx x)
{
  switch (GET_CODE (x))
    {
    case REG:
      fprintf (file, "(%s)", reg_names[REGNO (x)]);
      break;
      
    case PLUS:
      switch (GET_CODE (XEXP (x, 1)))
 {
 case CONST_INT:
   fprintf (file, "%ld(%s)", 
     INTVAL(XEXP (x, 1)), reg_names[REGNO (XEXP (x, 0))]);
   break;
 case SYMBOL_REF:
   output_addr_const (file, XEXP (x, 1));
   fprintf (file, "(%s)", reg_names[REGNO (XEXP (x, 0))]);
   break;
 case CONST:
   {
     rtx plus = XEXP (XEXP (x, 1), 0);
     if (GET_CODE (XEXP (plus, 0)) == SYMBOL_REF 
  && CONST_INT_P (XEXP (plus, 1)))
       {
  output_addr_const(file, XEXP (plus, 0));
  fprintf (file,"+%ld(%s)", INTVAL (XEXP (plus, 1)),
    reg_names[REGNO (XEXP (x, 0))]);
       }
     else
       abort();
   }
   break;
 default:
   abort();
 }
      break;

    default:
      output_addr_const (file, x);
      break;
    }
}

#undef  TARGET_PRINT_OPERAND_ADDRESS
#define TARGET_PRINT_OPERAND_ADDRESS moxie_print_operand_address

Further reading

All functionality is described in “GNU Compiler Collection Internals”:
  • Chapter 13 describes the RTL representation, including macros and functions for accessing it.
  • Chapter 17 describes all target description macros and functions.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.