Thursday, March 24, 2011

g++ behind the static_cast

Assume that we have a simple C++ code

 const int i = static_cast<const int>(123.123f);

What is actually behind the static_cast?


The magic of static_cast starts in

tree
build_static_cast (tree type, tree expr, tsubst_flags_t complain)
{
  tree result;
  bool valid_p;

  if (type == error_mark_node || expr == error_mark_node)
    return error_mark_node;

  if (processing_template_decl)
    {
      expr = build_min (STATIC_CAST_EXPR, type, expr);
      /* We don't know if it will or will not have side effects.  */
      TREE_SIDE_EFFECTS (expr) = 1;
      return convert_from_reference (expr);
    }

  /* build_c_cast puts on a NOP_EXPR to make the result not an lvalue.
     Strip such NOP_EXPRs if VALUE is being used in non-lvalue context.  */
  if (TREE_CODE (type) != REFERENCE_TYPE
      && TREE_CODE (expr) == NOP_EXPR
      && TREE_TYPE (expr) == TREE_TYPE (TREE_OPERAND (expr, 0)))
    expr = TREE_OPERAND (expr, 0);

  result = build_static_cast_1 (type, expr, /*c_cast_p=*/false, &valid_p,
                                complain);
  if (valid_p)
    return result;

  if (complain & tf_error)
    error ("invalid static_cast from type %qT to type %qT",
           TREE_TYPE (expr), type);
  return error_mark_node;
}


There, for example, we can see g++'s "invalid static_cast ..." error message.

build_static_cast is a wrapper around build_static_cast_1, that, in turn,
is a big "switch", since it should deal with all kind of derived to
base casts, user defined casts, type instantiation, etc

static tree
build_static_cast_1 (tree type, tree expr, bool c_cast_p,
                     bool *valid_p, tsubst_flags_t complain)


For example:

   /* "An lvalue of type cv1 T1 can be cast to type rvalue reference to
      cv2 T2 if cv2 T2 is reference-compatible with cv1 T1 (8.5.3)."  */
   if (TREE_CODE (type) == REFERENCE_TYPE
       && TYPE_REF_IS_RVALUE (type)
       && real_lvalue_p (expr)
       && reference_related_p (TREE_TYPE (type), intype)
       && (c_cast_p || at_least_as_qualified_p (TREE_TYPE (type), intype)))
     {
       expr = build_typed_address (expr, type);
       return convert_from_reference (expr);
     }


From here we're getting to gcc/fold-const.c
Place where tons of interesting stuff are, for example, fold_convert_loc.
Which is, once again, a number of "switches" (convert from INTEGER to
CONST INTEGER
, etc.)
:

Convert expression ARG to type TYPE.  Used by the middle-end for
simple conversions in preference to calling the front-end's convert.

 fold_convert_loc (location_t loc, tree type, tree arg)
[..]
   switch (TREE_CODE (type))
     {
     case POINTER_TYPE:
     case REFERENCE_TYPE:
       /* Handle conversions between pointers to different address spaces.  */
       if (POINTER_TYPE_P (orig)
       && (TYPE_ADDR_SPACE (TREE_TYPE (type))
           != TYPE_ADDR_SPACE (TREE_TYPE (orig))))
     return fold_build1_loc (loc, ADDR_SPACE_CONVERT_EXPR, type, arg);

or
     case INTEGER_TYPE: case ENUMERAL_TYPE: case BOOLEAN_TYPE:
     case OFFSET_TYPE:
       if (TREE_CODE (arg) == INTEGER_CST)
     {                                                             
       tem = fold_convert_const (NOP_EXPR, type, arg);
       if (tem != NULL_TREE)
         return tem;
     }



Since we're requesting cast to const int -- the right function to call
is fold_convert_const, which is getting called from

 Fold a unary expression of code CODE and type TYPE with operand
 OP0.  Return the folded expression if folding is successful.
 Otherwise, return NULL_TREE.

 fold_unary_loc (location_t loc, enum tree_code code, tree type, tree op0)
[..]
   switch (code)
     {
     case PAREN_EXPR:
       /* Re-association barriers around constants and other re-association
      barriers can be removed.  */
       if (CONSTANT_CLASS_P (op0)
       || TREE_CODE (op0) == PAREN_EXPR)
     return fold_convert_loc (loc, type, op0);
       return NULL_TREE;


     CASE_CONVERT:
     case FLOAT_EXPR:
     case FIX_TRUNC_EXPR:
[..]
       tem = fold_convert_const (code, type, op0);
       return tem ? tem : NULL_TREE;



fold_convert_const decides what conversion exactly should be
performed (if any):

fold_convert_const (enum tree_code code, tree type, tree arg1)
[..]
   if (POINTER_TYPE_P (type) || INTEGRAL_TYPE_P (type)
       || TREE_CODE (type) == OFFSET_TYPE)
     {
       if (TREE_CODE (arg1) == INTEGER_CST)
     return fold_convert_const_int_from_int (type, arg1);
       else if (TREE_CODE (arg1) == REAL_CST)
     return fold_convert_const_int_from_real (code, type, arg1);                                                             
       else if (TREE_CODE (arg1) == FIXED_CST)
     return fold_convert_const_int_from_fixed (type, arg1);
     }
[..]


which is finally getting closer to a cast itself by

real_to_integer2 (HOST_WIDE_INT *plow, HOST_WIDE_INT *phigh,
                  const REAL_VALUE_TYPE *r)


call in gcc/real.c

   The floating point model used internally is not exactly IEEE 754
   compliant, and close to the description in the ISO C99 standard,
   section 5.2.4.2.2 Characteristics of floating types.

   Specifically

        x = s * b^e * \sum_{k=1}^p f_k * b^{-k}

        where
                s = sign (+- 1)
                b = base or radix, here always 2
                e = exponent
                p = precision (the number of base-b digits in the significand)
                f_k = the digits of the significand.

   We differ from typical IEEE 754 encodings in that the entire
   significand is fractional.  Normalized significands are in the
   range [0.5, 1.0).

   A requirement of the model is that P be larger than the largest
   supported target floating-point type by at least 2 bits.  This gives
   us proper rounding when we truncate to the target type.  In addition,
   E must be large enough to hold the smallest supported denormal number
   in a normalized form.

   Both of these requirements are easily satisfied.  The largest target
   significand is 113 bits; we store at least 160.  The smallest
   denormal number fits in 17 exponent bits; we store 26.

   Note that the decimal string conversion routines are sensitive to
   rounding errors.  Since the raw arithmetic routines do not themselves
   have guard digits or rounding, the computation of 10**exp can
   accumulate more than a few digits of error.  The previous incarnation
   of real.c successfully used a 144-bit fraction; given the current
   layout of REAL_VALUE_TYPE we're forced to expand to at least 160 bits.




There is a bunch of fold_convert_* functions, e.g.:
fold_convert_const_int_from_int
fold_convert_const_int_from_real
fold_convert_const_int_from_fixed
fold_convert_const_real_from_real


and so on.

No comments:

Post a Comment