A glimpse at the correlation between front-end type and back-end machine code in GCC
intro
There is a quote from Harold Abelson: "Programs must be written for people to read, and only incidentally for machines to execute". But program is supposed to execute on machines eventually, someone needs to fill the gap and transform it to people-friendly to machine-friendly, and that one thing fulfilling it is, as we all know, the compiler.
People usually focus on the sematic translation from front-end high-level language to machine code, mainly the GIMPLE to RTX. But there is another important facet that should not be neglected: the type.
Actually, one the biggest differeces between C and Pythong programming language is that: C is a static, strongly typed programming language, which means type is a key feature of C.
builtin types
As other languages, like Pythong, C also has pre-defined builtin types, such as char, int, etc. They are initialized at the beginning of the conpiler initialization.
create type
The only paramter of make_signed_type is a the number of bits of a type.
///@file:gcc\gcc\tree.cc
/* Create nodes for all integer types (and error_mark_node) using the sizes
of C datatypes. SIGNED_CHAR specifies whether char is signed. */
void
build_common_tree_nodes (bool signed_char)
{
///...
short_integer_type_node = make_signed_type (SHORT_TYPE_SIZE);
short_unsigned_type_node = make_unsigned_type (SHORT_TYPE_SIZE);
integer_type_node = make_signed_type (INT_TYPE_SIZE);
unsigned_type_node = make_unsigned_type (INT_TYPE_SIZE);
long_integer_type_node = make_signed_type (LONG_TYPE_SIZE);
long_unsigned_type_node = make_unsigned_type (LONG_TYPE_SIZE);
long_long_integer_type_node = make_signed_type (LONG_LONG_TYPE_SIZE);
long_long_unsigned_type_node = make_unsigned_type (LONG_LONG_TYPE_SIZE);
///...
}
bind to identifier
Since the type created by make_unsigned_type is just a compiler internal variable, we need to bind it to the language key word unsigned and this is done by record_builtin_type. After this binding, the lexer will recognize the ints in the source as types of int.
///@file:gcc\gcc\c-family\c-common.cc
/* Build tree nodes and builtin functions common to both C and C++ language
frontends. */
void
c_common_nodes_and_builtins (void)
{
///...
///@file: gcc\gcc\c-family\c-common.cc
/* `signed' is the same as `int'. FIXME: the declarations of "signed",
"unsigned long", "long long unsigned" and "unsigned short" were in C++
but not C. Are the conditionals here needed? */
if (c_dialect_cxx ())
record_builtin_type (RID_SIGNED, NULL, integer_type_node);
record_builtin_type (RID_LONG, "long int", long_integer_type_node);
record_builtin_type (RID_UNSIGNED, "unsigned int", unsigned_type_node);
record_builtin_type (RID_MAX, "long unsigned int",
///...
}
types in rtx
Looking closely, you may find that the first field in the definition of rtx_def is not rtx_code but machine_mode which mainly reprents the size of the rtx.
///@file: gcc\gcc\rtl.h
/* RTL expression ("rtx"). */
/* The GTY "desc" and "tag" options below are a kludge: we need a desc
field for gengtype to recognize that inheritance is occurring,
so that all subclasses are redirected to the traversal hook for the
base class.
However, all of the fields are in the base class, and special-casing
is at work. Hence we use desc and tag of 0, generating a switch
statement of the form:
switch (0)
{
case 0: // all the work happens here
}
in order to work with the existing special-casing in gengtype. */
struct GTY((desc("0"), tag("0"),
chain_next ("RTX_NEXT (&%h)"),
chain_prev ("RTX_PREV (&%h)"))) rtx_def {
/* The kind of value the expression has. */
ENUM_BITFIELD(machine_mode) mode : MACHINE_MODE_BITSIZE;
/* The kind of expression this is. */
ENUM_BITFIELD(rtx_code) code: RTX_CODE_BITSIZE;
Another thing worth mentioning is that the first parameter of the key function is expression expansion--machine_mode-- is machine_mode too.
///@file:gcc\gcc\optabs.h
/* Generate code for a simple binary or unary operation. "Simple" in
this case means "can be unambiguously described by a (mode, code)
pair and mapped to a single optab." */
extern rtx expand_simple_binop (machine_mode, enum rtx_code, rtx,
rtx, rtx, int, enum optab_methods);
/* Expand a binary operation given optab and rtx operands. */
extern rtx expand_binop (machine_mode, optab, rtx, rtx, rtx, int,
enum optab_methods);
/* Expand a binary operation with both signed and unsigned forms. */
extern rtx sign_expand_binop (machine_mode, optab, optab, rtx, rtx,
rtx, int, enum optab_methods);
Back to the topic, what i'm trying to address is machine mode is very important in GCC's back-end, which I didn't notice it before.
from type to machine mode: layout_type
In layout_type, we can see one of its main task is to initialize the type's machine mode, by calling SET_TYPE_MODE. This function is called for both builtin types and custom types like structs.
The call stack is make_signed_type==>>fixup_signed_type==>>layout_type for builtin types and finish_struct==>>layout_type for struct types.
///@file:gcc\gcc\stor-layout.cc
/* Calculate the mode, size, and alignment for TYPE.
For an array type, calculate the element separation as well.
Record TYPE on the chain of permanent or temporary types
so that dbxout will find out about it.
TYPE_SIZE of a type is nonzero if the type has been laid out already.
layout_type does nothing on such a type.
If the type is incomplete, its TYPE_SIZE remains zero. */
void
layout_type (tree type)
{
///...
switch (TREE_CODE (type))
{
case LANG_TYPE:
/* This kind of type is the responsibility
of the language-specific code. */
gcc_unreachable ();
case BOOLEAN_TYPE:
case INTEGER_TYPE:
case ENUMERAL_TYPE:
{
scalar_int_mode mode
= smallest_int_mode_for_size (TYPE_PRECISION (type)).require ();
SET_TYPE_MODE (type, mode);
TYPE_SIZE (type) = bitsize_int (GET_MODE_BITSIZE (mode));
/* Don't set TYPE_PRECISION here, as it may be set by a bitfield. */
TYPE_SIZE_UNIT (type) = size_int (GET_MODE_SIZE (mode));
break;
}
case BITINT_TYPE:
{
struct bitint_info info;
int cnt;
bool ok = targetm.c.bitint_type_info (TYPE_PRECISION (type), &info);
gcc_assert (ok);
scalar_int_mode limb_mode
= as_a <scalar_int_mode> (info.abi_limb_mode);
if (TYPE_PRECISION (type) <= GET_MODE_PRECISION (limb_mode))
{
SET_TYPE_MODE (type, limb_mode);
gcc_assert (info.abi_limb_mode == info.limb_mode);
cnt = 1;
}
else
{
SET_TYPE_MODE (type, BLKmode);
cnt = CEIL (TYPE_PRECISION (type), GET_MODE_PRECISION (limb_mode));
gcc_assert (info.abi_limb_mode == info.limb_mode
|| !info.big_endian == !WORDS_BIG_ENDIAN);
}
///..
outro
Machine mode is a key back-end feature which is taken care of even at the beginning of source code parse. This feature could be easily neglected when we only read the front-end process of compiler. Noticing this could help grasp the whole picture of the compiler.
浙公网安备 33010602011771号