Introduce hardbool attribute for C

Message ID orlet4lmn8.fsf@lxoliva.fsfla.org
State New
Headers
Series Introduce hardbool attribute for C |

Commit Message

Alexandre Oliva July 7, 2022, 7:59 p.m. UTC
  This patch introduces hardened booleans in C.  The hardbool attribute,
when attached to an integral type, turns it into an enumerate type
with boolean semantics, using the named or implied constants as
representations for false and true.

Expressions of such types decay to _Bool, trapping if the value is
neither true nor false, and _Bool can convert implicitly back to them.
Other conversions go through _Bool first.

Regstrapped on x86_64-linux-gnu.  Ok to install?


for  gcc/c-family/ChangeLog

	* c-attribs.cc (c_common_attribute_table): Add hardbool.
	(handle_hardbool_attribute): New.
	(type_valid_for_vector_size): Reject hardbool.
	* c-common.cc (convert_and_check): Skip warnings for convert
	and check for hardbool.
	(c_hardbool_type_attr_1): New.
	* c-common.h (c_hardbool_type_attr): New.

for  gcc/c/ChangeLog

	* c-typeck.cc (convert_lvalue_to_rvalue): Decay hardbools.
	* c-convert.cc (convert): Convert to hardbool through
	truthvalue.
	* c-decl.cc (check_bitfield_type_and_width): Skip enumeral
	truncation warnings for hardbool.
	(finish_struct): Propagate hardbool attribute to bitfield
	types.
	(digest_init): Convert to hardbool.

for  gcc/ChangeLog

	* doc/extend.texi (hardbool): New type attribute.

for  gcc/testsuite/ChangeLog

	* gcc.dg/hardbool-err.c: New.
	* gcc.dg/hardbool-trap.c: New.
	* gcc.dg/hardbool.c: New.
	* gcc.dg/hardbool-s.c: New.
	* gcc.dg/hardbool-us.c: New.
	* gcc.dg/hardbool-i.c: New.
	* gcc.dg/hardbool-ul.c: New.
	* gcc.dg/hardbool-ll.c: New.
	* gcc.dg/hardbool-5a.c: New.
	* gcc.dg/hardbool-s-5a.c: New.
	* gcc.dg/hardbool-us-5a.c: New.
	* gcc.dg/hardbool-i-5a.c: New.
	* gcc.dg/hardbool-ul-5a.c: New.
	* gcc.dg/hardbool-ll-5a.c: New.
---
 gcc/c-family/c-attribs.cc                     |   97 ++++++++++++++++++++-
 gcc/c-family/c-common.cc                      |   21 ++++
 gcc/c-family/c-common.h                       |   18 ++++
 gcc/c/c-convert.cc                            |   14 +++
 gcc/c/c-decl.cc                               |   10 ++
 gcc/c/c-typeck.cc                             |   31 ++++++-
 gcc/doc/extend.texi                           |   37 ++++++++
 gcc/testsuite/gcc.dg/hardbool-err.c           |   28 ++++++
 gcc/testsuite/gcc.dg/hardbool-trap.c          |   13 +++
 gcc/testsuite/gcc.dg/torture/hardbool-5a.c    |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c  |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-i.c     |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-ll.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c  |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-s.c     |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-ul.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-us.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool.c       |  118 +++++++++++++++++++++++++
 21 files changed, 444 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/hardbool-err.c
 create mode 100644 gcc/testsuite/gcc.dg/hardbool-trap.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool.c
  

Comments

Richard Biener July 8, 2022, 6:58 a.m. UTC | #1
On Thu, Jul 7, 2022 at 10:00 PM Alexandre Oliva via Gcc-patches
<gcc-patches@gcc.gnu.org> wrote:
>
>
> This patch introduces hardened booleans in C.  The hardbool attribute,
> when attached to an integral type, turns it into an enumerate type
> with boolean semantics, using the named or implied constants as
> representations for false and true.
>
> Expressions of such types decay to _Bool, trapping if the value is
> neither true nor false, and _Bool can convert implicitly back to them.
> Other conversions go through _Bool first.
>
> Regstrapped on x86_64-linux-gnu.  Ok to install?

Does this follow some other compilers / language?  Is such feature used
in existing code?  Why is it useful to allow arbitrary values for true/false?
Why is the default 0 and ~0 rather than 0 and 1 as for _Bool?

When writing

> +@smallexample
> +typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
> +hbool first = 0;       /* False, stored as (char)0x5a.  */
> +hbool second = !first; /* True, stored as ~(char)0x5a.  */
hbool thrid;

what's the initial value of 'third'?  Does "zero-initialization" mean
initialization to the corresponding False value (0x5a) or does it
initialize to a zero representation and thus have indeterminate state?
The documentation should probably be explicit about this case.

If the underlying representation is an Enum, why not have hardened_enum
instead?  A hardened _Bool might want to have a special NaT value as
well I guess?

Richard.

> for  gcc/c-family/ChangeLog
>
>         * c-attribs.cc (c_common_attribute_table): Add hardbool.
>         (handle_hardbool_attribute): New.
>         (type_valid_for_vector_size): Reject hardbool.
>         * c-common.cc (convert_and_check): Skip warnings for convert
>         and check for hardbool.
>         (c_hardbool_type_attr_1): New.
>         * c-common.h (c_hardbool_type_attr): New.
>
> for  gcc/c/ChangeLog
>
>         * c-typeck.cc (convert_lvalue_to_rvalue): Decay hardbools.
>         * c-convert.cc (convert): Convert to hardbool through
>         truthvalue.
>         * c-decl.cc (check_bitfield_type_and_width): Skip enumeral
>         truncation warnings for hardbool.
>         (finish_struct): Propagate hardbool attribute to bitfield
>         types.
>         (digest_init): Convert to hardbool.
>
> for  gcc/ChangeLog
>
>         * doc/extend.texi (hardbool): New type attribute.
>
> for  gcc/testsuite/ChangeLog
>
>         * gcc.dg/hardbool-err.c: New.
>         * gcc.dg/hardbool-trap.c: New.
>         * gcc.dg/hardbool.c: New.
>         * gcc.dg/hardbool-s.c: New.
>         * gcc.dg/hardbool-us.c: New.
>         * gcc.dg/hardbool-i.c: New.
>         * gcc.dg/hardbool-ul.c: New.
>         * gcc.dg/hardbool-ll.c: New.
>         * gcc.dg/hardbool-5a.c: New.
>         * gcc.dg/hardbool-s-5a.c: New.
>         * gcc.dg/hardbool-us-5a.c: New.
>         * gcc.dg/hardbool-i-5a.c: New.
>         * gcc.dg/hardbool-ul-5a.c: New.
>         * gcc.dg/hardbool-ll-5a.c: New.
> ---
>  gcc/c-family/c-attribs.cc                     |   97 ++++++++++++++++++++-
>  gcc/c-family/c-common.cc                      |   21 ++++
>  gcc/c-family/c-common.h                       |   18 ++++
>  gcc/c/c-convert.cc                            |   14 +++
>  gcc/c/c-decl.cc                               |   10 ++
>  gcc/c/c-typeck.cc                             |   31 ++++++-
>  gcc/doc/extend.texi                           |   37 ++++++++
>  gcc/testsuite/gcc.dg/hardbool-err.c           |   28 ++++++
>  gcc/testsuite/gcc.dg/hardbool-trap.c          |   13 +++
>  gcc/testsuite/gcc.dg/torture/hardbool-5a.c    |    6 +
>  gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c  |    6 +
>  gcc/testsuite/gcc.dg/torture/hardbool-i.c     |    5 +
>  gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c |    6 +
>  gcc/testsuite/gcc.dg/torture/hardbool-ll.c    |    5 +
>  gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c  |    6 +
>  gcc/testsuite/gcc.dg/torture/hardbool-s.c     |    5 +
>  gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c |    6 +
>  gcc/testsuite/gcc.dg/torture/hardbool-ul.c    |    5 +
>  gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c |    6 +
>  gcc/testsuite/gcc.dg/torture/hardbool-us.c    |    5 +
>  gcc/testsuite/gcc.dg/torture/hardbool.c       |  118 +++++++++++++++++++++++++
>  21 files changed, 444 insertions(+), 4 deletions(-)
>  create mode 100644 gcc/testsuite/gcc.dg/hardbool-err.c
>  create mode 100644 gcc/testsuite/gcc.dg/hardbool-trap.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-5a.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us.c
>  create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool.c
>
> diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
> index c8d96723f4c30..e385d780c49ce 100644
> --- a/gcc/c-family/c-attribs.cc
> +++ b/gcc/c-family/c-attribs.cc
> @@ -172,6 +172,7 @@ static tree handle_objc_root_class_attribute (tree *, tree, tree, int, bool *);
>  static tree handle_objc_nullability_attribute (tree *, tree, tree, int, bool *);
>  static tree handle_signed_bool_precision_attribute (tree *, tree, tree, int,
>                                                     bool *);
> +static tree handle_hardbool_attribute (tree *, tree, tree, int, bool *);
>  static tree handle_retain_attribute (tree *, tree, tree, int, bool *);
>
>  /* Helper to define attribute exclusions.  */
> @@ -288,6 +289,8 @@ const struct attribute_spec c_common_attribute_table[] =
>         affects_type_identity, handler, exclude } */
>    { "signed_bool_precision",  1, 1, false, true, false, true,
>                               handle_signed_bool_precision_attribute, NULL },
> +  { "hardbool",               0, 2, false, true, false, true,
> +                             handle_hardbool_attribute, NULL },
>    { "packed",                 0, 0, false, false, false, false,
>                               handle_packed_attribute,
>                               attr_aligned_exclusions },
> @@ -975,6 +978,95 @@ handle_signed_bool_precision_attribute (tree *node, tree name, tree args,
>    return NULL_TREE;
>  }
>
> +/* Handle a "hardbool" attribute; arguments as in struct
> +   attribute_spec.handler.  */
> +
> +static tree
> +handle_hardbool_attribute (tree *node, tree name, tree args,
> +                          int /* flags */, bool *no_add_attrs)
> +{
> +  if (c_language != clk_c)
> +    {
> +      error ("%qE attribute only supported in C", name);
> +      *no_add_attrs = TRUE;
> +      return NULL_TREE;
> +    }
> +
> +  if (!TYPE_P (*node) || TREE_CODE (*node) != INTEGER_TYPE)
> +    {
> +      error ("%qE attribute only supported on "
> +            "integral types", name);
> +      *no_add_attrs = TRUE;
> +      return NULL_TREE;
> +    }
> +
> +  tree orig = *node;
> +  *node = build_duplicate_type (orig);
> +
> +  TREE_SET_CODE (*node, ENUMERAL_TYPE);
> +
> +  tree false_value;
> +  if (args)
> +    false_value = fold_convert (*node, TREE_VALUE (args));
> +  else
> +    false_value = fold_convert (*node, integer_zero_node);
> +
> +  if (TREE_OVERFLOW_P (false_value))
> +    {
> +      warning (OPT_Wattributes,
> +              "overflows in conversion from %qT to %qT "
> +              "changes value from %qE to %qE",
> +              TREE_TYPE (TREE_VALUE (args)), *node,
> +              TREE_VALUE (args), false_value);
> +      TREE_OVERFLOW (false_value) = false;
> +    }
> +
> +  tree true_value;
> +  if (args && TREE_CHAIN (args))
> +    true_value = fold_convert (*node, TREE_VALUE (TREE_CHAIN (args)));
> +  else
> +    true_value = fold_build1 (BIT_NOT_EXPR, *node, false_value);
> +
> +  if (TREE_OVERFLOW_P (true_value))
> +    {
> +      warning (OPT_Wattributes,
> +              "overflows in conversion from %qT to %qT "
> +              "changes value from %qE to %qE",
> +              TREE_TYPE (TREE_VALUE (TREE_CHAIN (args))), *node,
> +              TREE_VALUE (TREE_CHAIN (args)), true_value);
> +      TREE_OVERFLOW (true_value) = false;
> +    }
> +
> +  if (tree_int_cst_compare (false_value, true_value) == 0)
> +    {
> +      error ("%qE attribute requires different values for"
> +            " %<false%> and %<true%> for type %qT",
> +            name, *node);
> +      *no_add_attrs = TRUE;
> +      return NULL_TREE;
> +    }
> +
> +  tree values = build_tree_list (get_identifier ("false"),
> +                                false_value);
> +  TREE_CHAIN (values) = build_tree_list (get_identifier ("true"),
> +                                        true_value);
> +
> +  /* Do *not* set TYPE_MIN_VALUE, TYPE_MAX_VALUE, nor TYPE_PRECISION according
> +     to the false and true values.  That might cause the constants to be the
> +     only acceptable values, which would drop the very hardening checks this
> +     attribute is supposed to add.  */
> +
> +  TYPE_ATTRIBUTES (*node) = tree_cons (name, args,
> +                                      TYPE_ATTRIBUTES (*node));
> +  *no_add_attrs = TRUE;
> +
> +  gcc_checking_assert (!TYPE_CACHED_VALUES_P (*node));
> +  TYPE_VALUES (*node) = values;
> +  TYPE_NAME (*node) = orig;
> +
> +  return NULL_TREE;
> +}
> +
>  /* Handle a "packed" attribute; arguments as in
>     struct attribute_spec.handler.  */
>
> @@ -4289,7 +4381,8 @@ static tree
>  type_valid_for_vector_size (tree type, tree atname, tree args,
>                             unsigned HOST_WIDE_INT *ptrnunits)
>  {
> -  bool error_p = ptrnunits != NULL;
> +  bool hardbool_p = c_hardbool_type_attr (type);
> +  bool error_p = ptrnunits != NULL || hardbool_p;
>
>    /* Get the mode of the type being modified.  */
>    machine_mode orig_mode = TYPE_MODE (type);
> @@ -4301,7 +4394,7 @@ type_valid_for_vector_size (tree type, tree atname, tree args,
>           && GET_MODE_CLASS (orig_mode) != MODE_INT
>           && !ALL_SCALAR_FIXED_POINT_MODE_P (orig_mode))
>        || !tree_fits_uhwi_p (TYPE_SIZE_UNIT (type))
> -      || TREE_CODE (type) == BOOLEAN_TYPE)
> +      || TREE_CODE (type) == BOOLEAN_TYPE || hardbool_p)
>      {
>        if (error_p)
>         error ("invalid vector type for attribute %qE", atname);
> diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
> index 1b8e73f7bc5d8..9a8584a5fe095 100644
> --- a/gcc/c-family/c-common.cc
> +++ b/gcc/c-family/c-common.cc
> @@ -1780,7 +1780,8 @@ convert_and_check (location_t loc, tree type, tree expr, bool init_const)
>
>    if (c_inhibit_evaluation_warnings == 0
>        && !TREE_OVERFLOW_P (expr)
> -      && result != error_mark_node)
> +      && result != error_mark_node
> +      && !c_hardbool_type_attr (type))
>      warnings_for_convert_and_check (loc, type, expr_for_warning, result);
>
>    return result;
> @@ -9488,4 +9489,22 @@ c_common_finalize_early_debug (void)
>        (*debug_hooks->early_global_decl) (cnode->decl);
>  }
>
> +/* This is the slow path of c-common.h's c_hardbool_type_attr.  */
> +
> +tree
> +c_hardbool_type_attr_1 (tree type, tree *false_value, tree *true_value)
> +{
> +  tree attr = lookup_attribute ("hardbool", TYPE_ATTRIBUTES (type));
> +  if (!attr)
> +    return attr;
> +
> +  if (false_value)
> +    *false_value = TREE_VALUE (TYPE_VALUES (type));
> +
> +  if (true_value)
> +    *true_value = TREE_VALUE (TREE_CHAIN (TYPE_VALUES (type)));
> +
> +  return attr;
> +}
> +
>  #include "gt-c-family-c-common.h"
> diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
> index c0900848965a3..a8d6691d0813d 100644
> --- a/gcc/c-family/c-common.h
> +++ b/gcc/c-family/c-common.h
> @@ -912,6 +912,7 @@ extern tree c_common_get_narrower (tree, int *);
>  extern bool get_attribute_operand (tree, unsigned HOST_WIDE_INT *);
>  extern void c_common_finalize_early_debug (void);
>  extern bool c_option_is_from_cpp_diagnostics (int);
> +extern tree c_hardbool_type_attr_1 (tree, tree *, tree *);
>
>  /* Used by convert_and_check; in front ends.  */
>  extern tree convert_init (tree, tree);
> @@ -1291,6 +1292,23 @@ c_tree_chain_next (tree t)
>    return NULL;
>  }
>
> +/* Return the hardbool attribute associated with TYPE, if there is one, provided
> +   that TYPE looks like an enumeral type that might have been set up by
> +   handle_hardbool_attribute.  Return NULL otherwise.
> +
> +   If FALSE_VALUE or TRUE_VALUE are non-NULL and TYPE is a hardened boolean
> +   type, store the corresponding representation values.  */
> +static inline tree
> +c_hardbool_type_attr (tree type,
> +                     tree *false_value = NULL, tree *true_value = NULL)
> +{
> +  if (TREE_CODE (type) != ENUMERAL_TYPE
> +      || TYPE_LANG_SPECIFIC (type))
> +    return NULL_TREE;
> +
> +  return c_hardbool_type_attr_1 (type, false_value, true_value);
> +}
> +
>  /* Mask used by tm_stmt_attr.  */
>  #define TM_STMT_ATTR_OUTER     2
>  #define TM_STMT_ATTR_ATOMIC    4
> diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
> index 18083d5961826..1399cbf9d8d37 100644
> --- a/gcc/c/c-convert.cc
> +++ b/gcc/c/c-convert.cc
> @@ -105,6 +105,20 @@ c_convert (tree type, tree expr, bool init_const)
>        return error_mark_node;
>      }
>
> +  {
> +    tree false_value, true_value;
> +    if (c_hardbool_type_attr (type, &false_value, &true_value))
> +      {
> +       bool save = in_late_binary_op;
> +       in_late_binary_op = true;
> +       expr = c_objc_common_truthvalue_conversion (input_location, expr);
> +       in_late_binary_op = save;
> +
> +       return fold_build3_loc (loc, COND_EXPR, type,
> +                               expr, true_value, false_value);
> +      }
> +  }
> +
>    switch (code)
>      {
>      case VOID_TYPE:
> diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
> index ae8990c138fd2..e103582d677cd 100644
> --- a/gcc/c/c-decl.cc
> +++ b/gcc/c/c-decl.cc
> @@ -6163,6 +6163,12 @@ check_bitfield_type_and_width (location_t loc, tree *type, tree *width,
>    else
>      w = tree_to_uhwi (*width);
>
> +  /* Truncation of hardbool false and true representation values is always safe:
> +     either the values remain different, or we'll report a problem when creating
> +     the narrower type.  */
> +  if (c_hardbool_type_attr (*type))
> +    return;
> +
>    if (TREE_CODE (*type) == ENUMERAL_TYPE)
>      {
>        struct lang_type *lt = TYPE_LANG_SPECIFIC (*type);
> @@ -8887,6 +8893,10 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
>             {
>               TREE_TYPE (field)
>                 = c_build_bitfield_integer_type (width, TYPE_UNSIGNED (type));
> +             if (tree attr = c_hardbool_type_attr (type))
> +               decl_attributes (&TREE_TYPE (field),
> +                                copy_list (attr),
> +                                0, NULL_TREE);
>               SET_DECL_MODE (field, TYPE_MODE (TREE_TYPE (field)));
>             }
>           DECL_INITIAL (field) = NULL_TREE;
> diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
> index fd0a7f81a7a92..1244241dd295b 100644
> --- a/gcc/c/c-typeck.cc
> +++ b/gcc/c/c-typeck.cc
> @@ -2172,6 +2172,35 @@ convert_lvalue_to_rvalue (location_t loc, struct c_expr exp,
>    if (convert_p && !error_operand_p (exp.value)
>        && (TREE_CODE (TREE_TYPE (exp.value)) != ARRAY_TYPE))
>      exp.value = convert (build_qualified_type (TREE_TYPE (exp.value), TYPE_UNQUALIFIED), exp.value);
> +
> +  {
> +    tree false_value, true_value;
> +    if (convert_p && !error_operand_p (exp.value)
> +       && c_hardbool_type_attr (TREE_TYPE (exp.value),
> +                                &false_value, &true_value))
> +      {
> +       tree t = save_expr (exp.value);
> +
> +       mark_exp_read (exp.value);
> +
> +       tree trapfn = builtin_decl_explicit (BUILT_IN_TRAP);
> +       tree expr = build_call_expr_loc (loc, trapfn, 0);
> +       expr = build_compound_expr (loc, expr, boolean_true_node);
> +       expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
> +                               fold_build2_loc (loc, NE_EXPR,
> +                                                boolean_type_node,
> +                                                t, true_value),
> +                               expr, boolean_true_node);
> +       expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
> +                               fold_build2_loc (loc, NE_EXPR,
> +                                                boolean_type_node,
> +                                                t, false_value),
> +                               expr, boolean_false_node);
> +
> +       exp.value = expr;
> +      }
> +  }
> +
>    return exp;
>  }
>
> @@ -8167,7 +8196,7 @@ digest_init (location_t init_loc, tree type, tree init, tree origtype,
>             }
>         }
>
> -      if (code == VECTOR_TYPE)
> +      if (code == VECTOR_TYPE || c_hardbool_type_attr (type))
>         /* Although the types are compatible, we may require a
>            conversion.  */
>         inside_init = convert (type, inside_init);
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index dfbe33ac652f8..7b514354263f4 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -8654,6 +8654,43 @@ initialization will result in future breakage.
>  GCC emits warnings based on this attribute by default; use
>  @option{-Wno-designated-init} to suppress them.
>
> +@item hardbool
> +@itemx hardbool (@var{false_value})
> +@itemx hardbool (@var{false_value}, @var{true_value})
> +@cindex @code{hardbool} type attribute
> +This attribute may only be applied to integral types in C, to introduce
> +hardened boolean types.  It turns the integral type into a boolean-like
> +type with the same size and precision, that uses the specified values as
> +representations for @code{false} and @code{true}.  Underneath, it is
> +actually an enumerate type, but its observable behavior is like that of
> +@code{_Bool}, except for the strict internal representations, verified
> +by runtime checks.
> +
> +If @var{true_value} is omitted, the bitwise negation of
> +@var{false_value} is used.  If @var{false_value} is omitted, zero is
> +used.  The named representation values must be different when converted
> +to the original integral type.  Narrower bitfields are rejected if the
> +representations become indistinguishable.
> +
> +Values of such types automatically decay to @code{_Bool}, at which
> +point, the selected representation values are mapped to the
> +corresponding @code{_Bool} values.  When the represented value is not
> +determined, at compile time, to be either @var{false_value} or
> +@var{true_value}, runtime verification calls @code{__builtin_trap} if it
> +is neither.  This is what makes them hardened boolean types.
> +
> +When converting scalar types to such hardened boolean types, implicitly
> +or explicitly, behavior corresponds to a conversion to @code{_Bool},
> +followed by a mapping from @code{false} and @code{true} to
> +@var{false_value} and @var{true_value}, respectively.
> +
> +@smallexample
> +typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
> +hbool first = 0;       /* False, stored as (char)0x5a.  */
> +hbool second = !first; /* True, stored as ~(char)0x5a.  */
> +@end smallexample
> +
> +
>  @item may_alias
>  @cindex @code{may_alias} type attribute
>  Accesses through pointers to types with this attribute are not subject
> diff --git a/gcc/testsuite/gcc.dg/hardbool-err.c b/gcc/testsuite/gcc.dg/hardbool-err.c
> new file mode 100644
> index 0000000000000..634feaed4deef
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/hardbool-err.c
> @@ -0,0 +1,28 @@
> +/* { dg-do compile } */
> +/* { dg-options "" } */
> +
> +typedef _Bool __attribute__ ((__hardbool__))
> +hbbl; /* { dg-error "integral types" } */
> +
> +typedef double __attribute__ ((__hardbool__))
> +hbdbl; /* { dg-error "integral types" } */
> +
> +enum x;
> +typedef enum x __attribute__ ((__hardbool__))
> +hbenum; /* { dg-error "integral types" } */
> +
> +struct s;
> +typedef struct s __attribute__ ((__hardbool__))
> +hbstruct; /* { dg-error "integral types" } */
> +
> +typedef int __attribute__ ((__hardbool__ (0, 0)))
> +hb00; /* { dg-error "different values" } */
> +
> +typedef int __attribute__ ((__hardbool__ (4, 16))) hb4x;
> +struct s {
> +  hb4x m:2;
> +}; /* { dg-error "is a GCC extension|different values" } */
> +/* { dg-warning "changes value" "warning" { target *-*-* } .-1 } */
> +
> +hb4x __attribute__ ((vector_size (4 * sizeof (hb4x))))
> +vvar; /* { dg-error "invalid vector type" } */
> diff --git a/gcc/testsuite/gcc.dg/hardbool-trap.c b/gcc/testsuite/gcc.dg/hardbool-trap.c
> new file mode 100644
> index 0000000000000..2eebd0ef64fff
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/hardbool-trap.c
> @@ -0,0 +1,13 @@
> +/* { dg-do compile } */
> +/* { dg-options "-fdump-tree-optimized" } */
> +
> +typedef char __attribute__ ((__hardbool__ (1))) hbool;
> +
> +hbool var;
> +
> +int main () {
> +  __builtin_memset (&var, 0, sizeof (var));
> +  (void)var;
> +}
> +
> +/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "optimized" } } */
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
> new file mode 100644
> index 0000000000000..a03887cfbecc5
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
> @@ -0,0 +1,6 @@
> +/* { dg-do run } */
> +/* { dg-options "-w" } */
> +
> +#define falseval 0x5a
> +
> +#include "hardbool.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
> new file mode 100644
> index 0000000000000..c0ba2a8b9148e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
> @@ -0,0 +1,6 @@
> +/* { dg-do run } */
> +/* { dg-options "-w" } */
> +
> +#define falseval 0xa53cc35a
> +
> +#include "hardbool-i.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i.c b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
> new file mode 100644
> index 0000000000000..39214d28c5627
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
> @@ -0,0 +1,5 @@
> +/* { dg-do run } */
> +
> +#define basetype int
> +
> +#include "hardbool.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
> new file mode 100644
> index 0000000000000..14438c5104f07
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
> @@ -0,0 +1,6 @@
> +/* { dg-do run } */
> +/* { dg-options "-w" } */
> +
> +#define falseval 0x781ee187a53cc35all
> +
> +#include "hardbool-ll.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
> new file mode 100644
> index 0000000000000..d4d498c6f2af1
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
> @@ -0,0 +1,5 @@
> +/* { dg-do run } */
> +
> +#define basetype long long
> +
> +#include "hardbool.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
> new file mode 100644
> index 0000000000000..e38a56b5deb05
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
> @@ -0,0 +1,6 @@
> +/* { dg-do run } */
> +/* { dg-options "-w" } */
> +
> +#define falseval 0x5aa5
> +
> +#include "hardbool-s.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s.c b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
> new file mode 100644
> index 0000000000000..942300be2072a
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
> @@ -0,0 +1,5 @@
> +/* { dg-do run } */
> +
> +#define basetype short
> +
> +#include "hardbool.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
> new file mode 100644
> index 0000000000000..7beec578ff89c
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
> @@ -0,0 +1,6 @@
> +/* { dg-do run } */
> +/* { dg-options "-w" } */
> +
> +#define falseval 0xa53cc35a
> +
> +#include "hardbool-ul.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
> new file mode 100644
> index 0000000000000..841c1d4bc2ec8
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
> @@ -0,0 +1,5 @@
> +/* { dg-do run } */
> +
> +#define basetype unsigned long
> +
> +#include "hardbool.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
> new file mode 100644
> index 0000000000000..5bfc922795d3d
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
> @@ -0,0 +1,6 @@
> +/* { dg-do run } */
> +/* { dg-options "-w" } */
> +
> +#define falseval 0xa55a
> +
> +#include "hardbool-us.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us.c b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
> new file mode 100644
> index 0000000000000..e9feec681c41e
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
> @@ -0,0 +1,5 @@
> +/* { dg-do run } */
> +
> +#define basetype unsigned short
> +
> +#include "hardbool.c"
> diff --git a/gcc/testsuite/gcc.dg/torture/hardbool.c b/gcc/testsuite/gcc.dg/torture/hardbool.c
> new file mode 100644
> index 0000000000000..01684952a2a9f
> --- /dev/null
> +++ b/gcc/testsuite/gcc.dg/torture/hardbool.c
> @@ -0,0 +1,118 @@
> +/* { dg-do run } */
> +
> +#include <assert.h>
> +
> +#ifndef basetype
> +#define basetype char
> +#endif
> +
> +#ifndef falseval
> +#define falseval 0
> +#endif
> +
> +#ifndef trueval
> +#define trueval ~falseval
> +#endif
> +
> +/* hardbool may be #defined so as to drop parms in other tests.  */
> +typedef basetype __attribute__ ((hardbool (falseval, trueval))) hbool;
> +
> +typedef unsigned char __attribute__ ((__hardbool__ (1, 0))) zbool;
> +
> +struct hs {
> +  hbool a[2];
> +  hbool x:2;
> +  hbool y:5;
> +  zbool z:1;
> +};
> +
> +hbool var = 0;
> +
> +struct hs x = { { 1, 0 }, 2, 0, 2 };
> +
> +int f(hbool v) {
> +  return !v;
> +}
> +
> +int g(int i) {
> +  return f(i);
> +}
> +
> +hbool h(hbool x) {
> +  return x;
> +}
> +
> +hbool h2(hbool x) {
> +  return h(x);
> +}
> +
> +int hsx(struct hs v) {
> +  return v.x;
> +}
> +
> +int ghs(hbool s) {
> +  struct hs v = { {s, !s}, s, !s, s };
> +  return hsx (v);
> +}
> +
> +int t = (hbool)2;
> +
> +void check_pfalse (hbool *p)
> +{
> +  assert (!*p);
> +  assert (*(basetype*)p == (basetype)falseval);
> +  assert (!(int)(hbool)*p);
> +}
> +
> +void check_ptrue (hbool *p)
> +{
> +  assert (*p);
> +  assert (*(basetype*)p == (basetype)trueval);
> +  assert ((int)(hbool)*p);
> +}
> +
> +void check_vfalse (hbool v)
> +{
> +  check_pfalse (&v);
> +}
> +
> +void check_vtrue (hbool v)
> +{
> +  check_ptrue (&v);
> +}
> +
> +int main () {
> +  check_pfalse (&var);
> +  var = !(int)(hbool)(_Bool)var;
> +  check_ptrue (&var);
> +  var = (zbool)var;
> +  check_ptrue (&var);
> +
> +  check_ptrue (&x.a[0]);
> +  check_pfalse (&x.a[1]);
> +  check_vtrue (x.x);
> +  check_vfalse (x.y);
> +  check_vtrue (x.z);
> +
> +  check_vtrue (t);
> +
> +  check_vtrue (var && t);
> +  check_vfalse (!var || x.y);
> +
> +  check_vfalse (f (2));
> +  check_vfalse (f (1));
> +  check_vtrue (f (0));
> +
> +  check_vfalse (g (2));
> +  check_vfalse (g (1));
> +  check_vtrue (g (0));
> +
> +  check_vtrue (h (2));
> +  check_vtrue (h (1));
> +  check_vfalse (h (0));
> +
> +  check_vtrue (h2 (2));
> +  check_vtrue (h2 (1));
> +  check_vfalse (h2 (0));
> +}
> +
>
>
> --
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>    Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>
  
Sebastian Huber July 8, 2022, 1:39 p.m. UTC | #2
On 08.07.22 08:58, Richard Biener via Gcc-patches wrote:
> On Thu, Jul 7, 2022 at 10:00 PM Alexandre Oliva via Gcc-patches
> <gcc-patches@gcc.gnu.org>  wrote:
>>
>> This patch introduces hardened booleans in C.  The hardbool attribute,
>> when attached to an integral type, turns it into an enumerate type
>> with boolean semantics, using the named or implied constants as
>> representations for false and true.
>>
>> Expressions of such types decay to _Bool, trapping if the value is
>> neither true nor false, and _Bool can convert implicitly back to them.
>> Other conversions go through _Bool first.
>>
>> Regstrapped on x86_64-linux-gnu.  Ok to install?
> Does this follow some other compilers / language?  Is such feature used
> in existing code?  Why is it useful to allow arbitrary values for true/false?
> Why is the default 0 and ~0 rather than 0 and 1 as for _Bool?

Maybe this helps to catch errors caused by radiation which resulted in a 
bit flip in a processor register or other memory. If you use a single 
bit for true/false you can't detect such an error without special hardware.
  
Alexandre Oliva July 8, 2022, 3:28 p.m. UTC | #3
On Jul  8, 2022, Richard Biener <richard.guenther@gmail.com> wrote:

> The documentation should probably be explicit about this case.

Please consider, for purposes of review, the following incremental
patchlet as if integrated with yesterday's submission.

diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index a63a94158341a..a1dcd581dd8ad 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8483,7 +8483,22 @@ followed by a mapping from @code{false} and @code{true} to
 typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
 hbool first = 0;       /* False, stored as (char)0x5a.  */
 hbool second = !first; /* True, stored as ~(char)0x5a.  */
-@end smallexample
+
+static hbool zeroinit; /* False, stored as (char)0x5a.  */
+auto hbool uninit;     /* Undefined, may trap.  */
+@end smallexample
+
+When zero-initializing a variable or field of hardened boolean type
+(presumably held in static storage) the implied zero initializer gets
+converted to @code{_Bool}, and then to the hardened boolean type, so
+that the initial value is the hardened representation for @code{false}.
+Using that value is well defined.  This is @emph{not} the case when
+variables and fields of such types are uninitialized (presumably held in
+automatic or dynamic storage): their values are indeterminate, and using
+them invokes undefined behavior.  Using them may trap or not, depending
+on the bits held in the storage (re)used for the variable, if any, and
+on optimizations the compiler may perform on the grounds that using
+uninitialized values invokes undefined behavior.
 
 
 @item may_alias
  
Alexandre Oliva July 8, 2022, 3:28 p.m. UTC | #4
On Jul  8, 2022, Richard Biener <richard.guenther@gmail.com> wrote:

> Does this follow some other compilers / language?

It is analogous to Ada's booleans with representation clauses and
runtime validation checking at use points.

> Is such feature used in existing code?

Not that I know.  The attribute name was my choice.

That said, we have already delivered the experimental implementation to
the customer who requested it (GCC was in stage3, thus the delayed
submission), so by now they may already have some code using it.

> Why is it useful to allow arbitrary values for true/false?

Increasing the hamming distance between legitimate values is desirable
to catch hardware-based attacks, but booleans are probably the only
builtin type that has room for that.

> Why is the default 0 and ~0 rather than 0 and 1 as for _Bool?

My understanding is that the goal is to maximize the hamming distance
between the legitimate values, so as to increase the sensibility to
errors.

There was no requirement for defaults to be these values, however.  The
examples often used 0x5a and 0xa5, but those seemed too arbitrary to be
defaults.  I found ~1 and 1 to be too nasty, so I went for 0 and ~0,
that are still recognizable as false and true values, respectively,
though I'm not sure whether this is advantageous.

>> +@smallexample
>> +typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
>> +hbool first = 0;       /* False, stored as (char)0x5a.  */
>> +hbool second = !first; /* True, stored as ~(char)0x5a.  */
> hbool thrid;

> what's the initial value of 'third'?

If it's an automatic variable, it's uninitialized, as expected for C.
It might by chance happen to hold one of the legitimate values, but odds
are it doesn't, and if so, accessing it will trap.

If it's a static-storage variable, it will be zero-initialized as
expected, but the zero will be mapped to the representation for false.

> The documentation should probably be explicit about this case.

Agreed, thanks, will do.

> If the underlying representation is an Enum, why not have
> hardened_enum instead?

In Ada, Booleans are enumerator types with various conventions and
builtin operations, with or without a representation clause, that sets
the representation values for the enumerators.  Other enumerations
aren't subject to such conventions as automatic conversions between
Boolean types with different representations, that enable all of them to
be used interchangeably (source-wise) in logical expressions.

It would nevertheless be feasible to implement hardened enumerator
types, that, like Ada, perform runtime validation checking that the
stored value corresponds to one of the enumerators.  This would not fit
some common use cases of enumerator types, e.g. those in which
enumerators define bits or masks, and different enumerators are combined
into a single variable.  This was not the feature that we were asked to
implement, though.

> A hardened _Bool might want to have a special NaT value as well I
> guess?

That might sound appealing, but ISTM that it would instead break the
symmetry of the maximal hamming distance between the representation
values for true and false.  OTOH, NaB, if so desired, would be just any
other value; the challenge would be to get such a value stored in a
variable, given that actual booleans can only hold true (nonzero) or
false (zero), and neither would convert to NaB.
  
Richard Biener July 11, 2022, 9:59 a.m. UTC | #5
On Fri, Jul 8, 2022 at 5:29 PM Alexandre Oliva <oliva@adacore.com> wrote:
>
> On Jul  8, 2022, Richard Biener <richard.guenther@gmail.com> wrote:
>
> > Does this follow some other compilers / language?
>
> It is analogous to Ada's booleans with representation clauses and
> runtime validation checking at use points.
>
> > Is such feature used in existing code?
>
> Not that I know.  The attribute name was my choice.
>
> That said, we have already delivered the experimental implementation to
> the customer who requested it (GCC was in stage3, thus the delayed
> submission), so by now they may already have some code using it.
>
> > Why is it useful to allow arbitrary values for true/false?
>
> Increasing the hamming distance between legitimate values is desirable
> to catch hardware-based attacks, but booleans are probably the only
> builtin type that has room for that.
>
> > Why is the default 0 and ~0 rather than 0 and 1 as for _Bool?
>
> My understanding is that the goal is to maximize the hamming distance
> between the legitimate values, so as to increase the sensibility to
> errors.
>
> There was no requirement for defaults to be these values, however.  The
> examples often used 0x5a and 0xa5, but those seemed too arbitrary to be
> defaults.  I found ~1 and 1 to be too nasty, so I went for 0 and ~0,
> that are still recognizable as false and true values, respectively,
> though I'm not sure whether this is advantageous.
>
> >> +@smallexample
> >> +typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
> >> +hbool first = 0;       /* False, stored as (char)0x5a.  */
> >> +hbool second = !first; /* True, stored as ~(char)0x5a.  */
> > hbool thrid;
>
> > what's the initial value of 'third'?
>
> If it's an automatic variable, it's uninitialized, as expected for C.
> It might by chance happen to hold one of the legitimate values, but odds
> are it doesn't, and if so, accessing it will trap.
>
> If it's a static-storage variable, it will be zero-initialized as
> expected, but the zero will be mapped to the representation for false.
>
> > The documentation should probably be explicit about this case.
>
> Agreed, thanks, will do.
>
> > If the underlying representation is an Enum, why not have
> > hardened_enum instead?
>
> In Ada, Booleans are enumerator types with various conventions and
> builtin operations, with or without a representation clause, that sets
> the representation values for the enumerators.  Other enumerations
> aren't subject to such conventions as automatic conversions between
> Boolean types with different representations, that enable all of them to
> be used interchangeably (source-wise) in logical expressions.
>
> It would nevertheless be feasible to implement hardened enumerator
> types, that, like Ada, perform runtime validation checking that the
> stored value corresponds to one of the enumerators.  This would not fit
> some common use cases of enumerator types, e.g. those in which
> enumerators define bits or masks, and different enumerators are combined
> into a single variable.  This was not the feature that we were asked to
> implement, though.
>
> > A hardened _Bool might want to have a special NaT value as well I
> > guess?
>
> That might sound appealing, but ISTM that it would instead break the
> symmetry of the maximal hamming distance between the representation
> values for true and false.  OTOH, NaB, if so desired, would be just any
> other value; the challenge would be to get such a value stored in a
> variable, given that actual booleans can only hold true (nonzero) or
> false (zero), and neither would convert to NaB.

Thanks for all the detailed answers - I do see limited use of this
feature (the method and robustness only works for _Bool, not other
types which is rather limiting), but it's sound.

Note the actual change should be reviewed by frontend maintainers.

I suppose a C++ hardbool would be implemented as a (standard library)
class instead.

Thanks,
Richard.

>
> --
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>    Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>
  
Alexandre Oliva Aug. 9, 2022, 1:53 p.m. UTC | #6
Ping? (sorry, Joseph, I failed to Cc: you last time)

https://gcc.gnu.org/pipermail/gcc-patches/2022-July/598034.html
https://gcc.gnu.org/pipermail/gcc-patches/2022-July/598084.html

On Jul  7, 2022, Alexandre Oliva <oliva@adacore.com> wrote:

> for  gcc/c-family/ChangeLog

> 	* c-attribs.cc (c_common_attribute_table): Add hardbool.
> 	(handle_hardbool_attribute): New.
> 	(type_valid_for_vector_size): Reject hardbool.
> 	* c-common.cc (convert_and_check): Skip warnings for convert
> 	and check for hardbool.
> 	(c_hardbool_type_attr_1): New.
> 	* c-common.h (c_hardbool_type_attr): New.

> for  gcc/c/ChangeLog

> 	* c-typeck.cc (convert_lvalue_to_rvalue): Decay hardbools.
> 	* c-convert.cc (convert): Convert to hardbool through
> 	truthvalue.
> 	* c-decl.cc (check_bitfield_type_and_width): Skip enumeral
> 	truncation warnings for hardbool.
> 	(finish_struct): Propagate hardbool attribute to bitfield
> 	types.
> 	(digest_init): Convert to hardbool.

> for  gcc/ChangeLog

> 	* doc/extend.texi (hardbool): New type attribute.

> for  gcc/testsuite/ChangeLog

> 	* gcc.dg/hardbool-err.c: New.
> 	* gcc.dg/hardbool-trap.c: New.
> 	* gcc.dg/hardbool.c: New.
> 	* gcc.dg/hardbool-s.c: New.
> 	* gcc.dg/hardbool-us.c: New.
> 	* gcc.dg/hardbool-i.c: New.
> 	* gcc.dg/hardbool-ul.c: New.
> 	* gcc.dg/hardbool-ll.c: New.
> 	* gcc.dg/hardbool-5a.c: New.
> 	* gcc.dg/hardbool-s-5a.c: New.
> 	* gcc.dg/hardbool-us-5a.c: New.
> 	* gcc.dg/hardbool-i-5a.c: New.
> 	* gcc.dg/hardbool-ul-5a.c: New.
> 	* gcc.dg/hardbool-ll-5a.c: New.
  
Alexandre Oliva June 16, 2023, 5:35 a.m. UTC | #7
On Aug  9, 2022, Alexandre Oliva <oliva@adacore.com> wrote:

> Ping?

Ping?  Refreshed, added setting of ENUM_UNDERLYING_TYPE, retested on
x86_64-linux-gnu (also on gcc-13).


Introduce hardbool attribute for C

This patch introduces hardened booleans in C.  The hardbool attribute,
when attached to an integral type, turns it into an enumerate type
with boolean semantics, using the named or implied constants as
representations for false and true.

Expressions of such types decay to _Bool, trapping if the value is
neither true nor false, and _Bool can convert implicitly back to them.
Other conversions go through _Bool first.


for  gcc/c-family/ChangeLog

	* c-attribs.cc (c_common_attribute_table): Add hardbool.
	(handle_hardbool_attribute): New.
	(type_valid_for_vector_size): Reject hardbool.
	* c-common.cc (convert_and_check): Skip warnings for convert
	and check for hardbool.
	(c_hardbool_type_attr_1): New.
	* c-common.h (c_hardbool_type_attr): New.

for  gcc/c/ChangeLog

	* c-typeck.cc (convert_lvalue_to_rvalue): Decay hardbools.
	* c-convert.cc (convert): Convert to hardbool through
	truthvalue.
	* c-decl.cc (check_bitfield_type_and_width): Skip enumeral
	truncation warnings for hardbool.
	(finish_struct): Propagate hardbool attribute to bitfield
	types.
	(digest_init): Convert to hardbool.

for  gcc/ChangeLog

	* doc/extend.texi (hardbool): New type attribute.

for  gcc/testsuite/ChangeLog

	* gcc.dg/hardbool-err.c: New.
	* gcc.dg/hardbool-trap.c: New.
	* gcc.dg/hardbool.c: New.
	* gcc.dg/hardbool-s.c: New.
	* gcc.dg/hardbool-us.c: New.
	* gcc.dg/hardbool-i.c: New.
	* gcc.dg/hardbool-ul.c: New.
	* gcc.dg/hardbool-ll.c: New.
	* gcc.dg/hardbool-5a.c: New.
	* gcc.dg/hardbool-s-5a.c: New.
	* gcc.dg/hardbool-us-5a.c: New.
	* gcc.dg/hardbool-i-5a.c: New.
	* gcc.dg/hardbool-ul-5a.c: New.
	* gcc.dg/hardbool-ll-5a.c: New.
---
 gcc/c-family/c-attribs.cc                     |   98 ++++++++++++++++++++-
 gcc/c-family/c-common.cc                      |   21 ++++
 gcc/c-family/c-common.h                       |   18 ++++
 gcc/c/c-convert.cc                            |   14 +++
 gcc/c/c-decl.cc                               |   10 ++
 gcc/c/c-typeck.cc                             |   31 ++++++-
 gcc/doc/extend.texi                           |   52 +++++++++++
 gcc/testsuite/gcc.dg/hardbool-err.c           |   28 ++++++
 gcc/testsuite/gcc.dg/hardbool-trap.c          |   13 +++
 gcc/testsuite/gcc.dg/torture/hardbool-5a.c    |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c  |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-i.c     |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-ll.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c  |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-s.c     |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-ul.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-us.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool.c       |  118 +++++++++++++++++++++++++
 21 files changed, 460 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/hardbool-err.c
 create mode 100644 gcc/testsuite/gcc.dg/hardbool-trap.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool.c

diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index c12211cb4d499..365319e642b1a 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -176,6 +176,7 @@ static tree handle_objc_root_class_attribute (tree *, tree, tree, int, bool *);
 static tree handle_objc_nullability_attribute (tree *, tree, tree, int, bool *);
 static tree handle_signed_bool_precision_attribute (tree *, tree, tree, int,
 						    bool *);
+static tree handle_hardbool_attribute (tree *, tree, tree, int, bool *);
 static tree handle_retain_attribute (tree *, tree, tree, int, bool *);
 static tree handle_fd_arg_attribute (tree *, tree, tree, int, bool *);
 
@@ -293,6 +294,8 @@ const struct attribute_spec c_common_attribute_table[] =
        affects_type_identity, handler, exclude } */
   { "signed_bool_precision",  1, 1, false, true, false, true,
 			      handle_signed_bool_precision_attribute, NULL },
+  { "hardbool",               0, 2, false, true, false, true,
+			      handle_hardbool_attribute, NULL },
   { "packed",                 0, 0, false, false, false, false,
 			      handle_packed_attribute,
 	                      attr_aligned_exclusions },
@@ -992,6 +995,96 @@ handle_signed_bool_precision_attribute (tree *node, tree name, tree args,
   return NULL_TREE;
 }
 
+/* Handle a "hardbool" attribute; arguments as in struct
+   attribute_spec.handler.  */
+
+static tree
+handle_hardbool_attribute (tree *node, tree name, tree args,
+			   int /* flags */, bool *no_add_attrs)
+{
+  if (c_language != clk_c)
+    {
+      error ("%qE attribute only supported in C", name);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  if (!TYPE_P (*node) || TREE_CODE (*node) != INTEGER_TYPE)
+    {
+      error ("%qE attribute only supported on "
+	     "integral types", name);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  tree orig = *node;
+  *node = build_duplicate_type (orig);
+
+  TREE_SET_CODE (*node, ENUMERAL_TYPE);
+  ENUM_UNDERLYING_TYPE (*node) = orig;
+
+  tree false_value;
+  if (args)
+    false_value = fold_convert (*node, TREE_VALUE (args));
+  else
+    false_value = fold_convert (*node, integer_zero_node);
+
+  if (TREE_OVERFLOW_P (false_value))
+    {
+      warning (OPT_Wattributes,
+	       "overflows in conversion from %qT to %qT "
+	       "changes value from %qE to %qE",
+	       TREE_TYPE (TREE_VALUE (args)), *node,
+	       TREE_VALUE (args), false_value);
+      TREE_OVERFLOW (false_value) = false;
+    }
+
+  tree true_value;
+  if (args && TREE_CHAIN (args))
+    true_value = fold_convert (*node, TREE_VALUE (TREE_CHAIN (args)));
+  else
+    true_value = fold_build1 (BIT_NOT_EXPR, *node, false_value);
+
+  if (TREE_OVERFLOW_P (true_value))
+    {
+      warning (OPT_Wattributes,
+	       "overflows in conversion from %qT to %qT "
+	       "changes value from %qE to %qE",
+	       TREE_TYPE (TREE_VALUE (TREE_CHAIN (args))), *node,
+	       TREE_VALUE (TREE_CHAIN (args)), true_value);
+      TREE_OVERFLOW (true_value) = false;
+    }
+
+  if (tree_int_cst_compare (false_value, true_value) == 0)
+    {
+      error ("%qE attribute requires different values for"
+	     " %<false%> and %<true%> for type %qT",
+	     name, *node);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  tree values = build_tree_list (get_identifier ("false"),
+				 false_value);
+  TREE_CHAIN (values) = build_tree_list (get_identifier ("true"),
+					 true_value);
+
+  /* Do *not* set TYPE_MIN_VALUE, TYPE_MAX_VALUE, nor TYPE_PRECISION according
+     to the false and true values.  That might cause the constants to be the
+     only acceptable values, which would drop the very hardening checks this
+     attribute is supposed to add.  */
+
+  TYPE_ATTRIBUTES (*node) = tree_cons (name, args,
+				       TYPE_ATTRIBUTES (*node));
+  *no_add_attrs = TRUE;
+
+  gcc_checking_assert (!TYPE_CACHED_VALUES_P (*node));
+  TYPE_VALUES (*node) = values;
+  TYPE_NAME (*node) = orig;
+
+  return NULL_TREE;
+}
+
 /* Handle a "packed" attribute; arguments as in
    struct attribute_spec.handler.  */
 
@@ -4357,7 +4450,8 @@ static tree
 type_valid_for_vector_size (tree type, tree atname, tree args,
 			    unsigned HOST_WIDE_INT *ptrnunits)
 {
-  bool error_p = ptrnunits != NULL;
+  bool hardbool_p = c_hardbool_type_attr (type);
+  bool error_p = ptrnunits != NULL || hardbool_p;
 
   /* Get the mode of the type being modified.  */
   machine_mode orig_mode = TYPE_MODE (type);
@@ -4369,7 +4463,7 @@ type_valid_for_vector_size (tree type, tree atname, tree args,
 	  && GET_MODE_CLASS (orig_mode) != MODE_INT
 	  && !ALL_SCALAR_FIXED_POINT_MODE_P (orig_mode))
       || !tree_fits_uhwi_p (TYPE_SIZE_UNIT (type))
-      || TREE_CODE (type) == BOOLEAN_TYPE)
+      || TREE_CODE (type) == BOOLEAN_TYPE || hardbool_p)
     {
       if (error_p)
 	error ("invalid vector type for attribute %qE", atname);
diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 9c8eed5442a45..1257760c426ac 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -1757,7 +1757,8 @@ convert_and_check (location_t loc, tree type, tree expr, bool init_const)
 
   if (c_inhibit_evaluation_warnings == 0
       && !TREE_OVERFLOW_P (expr)
-      && result != error_mark_node)
+      && result != error_mark_node
+      && !c_hardbool_type_attr (type))
     warnings_for_convert_and_check (loc, type, expr_for_warning, result);
 
   return result;
@@ -9545,4 +9546,22 @@ c_strict_flex_array_level_of (tree array_field)
   return strict_flex_array_level;
 }
 
+/* This is the slow path of c-common.h's c_hardbool_type_attr.  */
+
+tree
+c_hardbool_type_attr_1 (tree type, tree *false_value, tree *true_value)
+{
+  tree attr = lookup_attribute ("hardbool", TYPE_ATTRIBUTES (type));
+  if (!attr)
+    return attr;
+
+  if (false_value)
+    *false_value = TREE_VALUE (TYPE_VALUES (type));
+
+  if (true_value)
+    *true_value = TREE_VALUE (TREE_CHAIN (TYPE_VALUES (type)));
+
+  return attr;
+}
+
 #include "gt-c-family-c-common.h"
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 336a09f4a407d..a5d3e77f121f8 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -909,6 +909,7 @@ extern bool get_attribute_operand (tree, unsigned HOST_WIDE_INT *);
 extern void c_common_finalize_early_debug (void);
 extern unsigned int c_strict_flex_array_level_of (tree);
 extern bool c_option_is_from_cpp_diagnostics (int);
+extern tree c_hardbool_type_attr_1 (tree, tree *, tree *);
 
 /* Used by convert_and_check; in front ends.  */
 extern tree convert_init (tree, tree);
@@ -1342,6 +1343,23 @@ c_tree_chain_next (tree t)
   return NULL;
 }
 
+/* Return the hardbool attribute associated with TYPE, if there is one, provided
+   that TYPE looks like an enumeral type that might have been set up by
+   handle_hardbool_attribute.  Return NULL otherwise.
+
+   If FALSE_VALUE or TRUE_VALUE are non-NULL and TYPE is a hardened boolean
+   type, store the corresponding representation values.  */
+static inline tree
+c_hardbool_type_attr (tree type,
+		      tree *false_value = NULL, tree *true_value = NULL)
+{
+  if (TREE_CODE (type) != ENUMERAL_TYPE
+      || TYPE_LANG_SPECIFIC (type))
+    return NULL_TREE;
+
+  return c_hardbool_type_attr_1 (type, false_value, true_value);
+}
+
 /* Mask used by tm_stmt_attr.  */
 #define TM_STMT_ATTR_OUTER	2
 #define TM_STMT_ATTR_ATOMIC	4
diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
index 5754d00b30a67..e73103c188434 100644
--- a/gcc/c/c-convert.cc
+++ b/gcc/c/c-convert.cc
@@ -105,6 +105,20 @@ c_convert (tree type, tree expr, bool init_const)
       return error_mark_node;
     }
 
+  {
+    tree false_value, true_value;
+    if (c_hardbool_type_attr (type, &false_value, &true_value))
+      {
+	bool save = in_late_binary_op;
+	in_late_binary_op = true;
+	expr = c_objc_common_truthvalue_conversion (input_location, expr);
+	in_late_binary_op = save;
+
+	return fold_build3_loc (loc, COND_EXPR, type,
+				expr, true_value, false_value);
+      }
+  }
+
   switch (code)
     {
     case VOID_TYPE:
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 1af51c4acfc5e..cc3ec90053994 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -6414,6 +6414,12 @@ check_bitfield_type_and_width (location_t loc, tree *type, tree *width,
   else
     w = tree_to_uhwi (*width);
 
+  /* Truncation of hardbool false and true representation values is always safe:
+     either the values remain different, or we'll report a problem when creating
+     the narrower type.  */
+  if (c_hardbool_type_attr (*type))
+    return;
+
   if (TREE_CODE (*type) == ENUMERAL_TYPE)
     {
       struct lang_type *lt = TYPE_LANG_SPECIFIC (*type);
@@ -9304,6 +9310,10 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
 	    {
 	      TREE_TYPE (field)
 		= c_build_bitfield_integer_type (width, TYPE_UNSIGNED (type));
+	      if (tree attr = c_hardbool_type_attr (type))
+		decl_attributes (&TREE_TYPE (field),
+				 copy_list (attr),
+				 0, NULL_TREE);
 	      SET_DECL_MODE (field, TYPE_MODE (TREE_TYPE (field)));
 	    }
 	  DECL_INITIAL (field) = NULL_TREE;
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 22e240a3c2a55..f9cc609b54d94 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -2226,6 +2226,35 @@ convert_lvalue_to_rvalue (location_t loc, struct c_expr exp,
     exp.value = convert (build_qualified_type (TREE_TYPE (exp.value), TYPE_UNQUALIFIED), exp.value);
   if (force_non_npc)
     exp.value = build1 (NOP_EXPR, TREE_TYPE (exp.value), exp.value);
+
+  {
+    tree false_value, true_value;
+    if (convert_p && !error_operand_p (exp.value)
+	&& c_hardbool_type_attr (TREE_TYPE (exp.value),
+				 &false_value, &true_value))
+      {
+	tree t = save_expr (exp.value);
+
+	mark_exp_read (exp.value);
+
+	tree trapfn = builtin_decl_explicit (BUILT_IN_TRAP);
+	tree expr = build_call_expr_loc (loc, trapfn, 0);
+	expr = build_compound_expr (loc, expr, boolean_true_node);
+	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
+				fold_build2_loc (loc, NE_EXPR,
+						 boolean_type_node,
+						 t, true_value),
+				expr, boolean_true_node);
+	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
+				fold_build2_loc (loc, NE_EXPR,
+						 boolean_type_node,
+						 t, false_value),
+				expr, boolean_false_node);
+
+	exp.value = expr;
+      }
+  }
+
   return exp;
 }
 
@@ -8488,7 +8517,7 @@ digest_init (location_t init_loc, tree type, tree init, tree origtype,
 	    }
 	}
 
-      if (code == VECTOR_TYPE)
+      if (code == VECTOR_TYPE || c_hardbool_type_attr (type))
 	/* Although the types are compatible, we may require a
 	   conversion.  */
 	inside_init = convert (type, inside_init);
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 2de212c8c2d84..7b5592502734e 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8681,6 +8681,58 @@ initialization will result in future breakage.
 GCC emits warnings based on this attribute by default; use
 @option{-Wno-designated-init} to suppress them.
 
+@cindex @code{hardbool} type attribute
+@item hardbool
+@itemx hardbool (@var{false_value})
+@itemx hardbool (@var{false_value}, @var{true_value})
+This attribute may only be applied to integral types in C, to introduce
+hardened boolean types.  It turns the integral type into a boolean-like
+type with the same size and precision, that uses the specified values as
+representations for @code{false} and @code{true}.  Underneath, it is
+actually an enumerate type, but its observable behavior is like that of
+@code{_Bool}, except for the strict internal representations, verified
+by runtime checks.
+
+If @var{true_value} is omitted, the bitwise negation of
+@var{false_value} is used.  If @var{false_value} is omitted, zero is
+used.  The named representation values must be different when converted
+to the original integral type.  Narrower bitfields are rejected if the
+representations become indistinguishable.
+
+Values of such types automatically decay to @code{_Bool}, at which
+point, the selected representation values are mapped to the
+corresponding @code{_Bool} values.  When the represented value is not
+determined, at compile time, to be either @var{false_value} or
+@var{true_value}, runtime verification calls @code{__builtin_trap} if it
+is neither.  This is what makes them hardened boolean types.
+
+When converting scalar types to such hardened boolean types, implicitly
+or explicitly, behavior corresponds to a conversion to @code{_Bool},
+followed by a mapping from @code{false} and @code{true} to
+@var{false_value} and @var{true_value}, respectively.
+
+@smallexample
+typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
+hbool first = 0;       /* False, stored as (char)0x5a.  */
+hbool second = !first; /* True, stored as ~(char)0x5a.  */
+
+static hbool zeroinit; /* False, stored as (char)0x5a.  */
+auto hbool uninit;     /* Undefined, may trap.  */
+@end smallexample
+
+When zero-initializing a variable or field of hardened boolean type
+(presumably held in static storage) the implied zero initializer gets
+converted to @code{_Bool}, and then to the hardened boolean type, so
+that the initial value is the hardened representation for @code{false}.
+Using that value is well defined.  This is @emph{not} the case when
+variables and fields of such types are uninitialized (presumably held in
+automatic or dynamic storage): their values are indeterminate, and using
+them invokes undefined behavior.  Using them may trap or not, depending
+on the bits held in the storage (re)used for the variable, if any, and
+on optimizations the compiler may perform on the grounds that using
+uninitialized values invokes undefined behavior.
+
+
 @cindex @code{may_alias} type attribute
 @item may_alias
 Accesses through pointers to types with this attribute are not subject
diff --git a/gcc/testsuite/gcc.dg/hardbool-err.c b/gcc/testsuite/gcc.dg/hardbool-err.c
new file mode 100644
index 0000000000000..634feaed4deef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/hardbool-err.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+typedef _Bool __attribute__ ((__hardbool__))
+hbbl; /* { dg-error "integral types" } */
+
+typedef double __attribute__ ((__hardbool__))
+hbdbl; /* { dg-error "integral types" } */
+
+enum x;
+typedef enum x __attribute__ ((__hardbool__))
+hbenum; /* { dg-error "integral types" } */
+
+struct s;
+typedef struct s __attribute__ ((__hardbool__))
+hbstruct; /* { dg-error "integral types" } */
+
+typedef int __attribute__ ((__hardbool__ (0, 0)))
+hb00; /* { dg-error "different values" } */
+
+typedef int __attribute__ ((__hardbool__ (4, 16))) hb4x;
+struct s {
+  hb4x m:2;
+}; /* { dg-error "is a GCC extension|different values" } */
+/* { dg-warning "changes value" "warning" { target *-*-* } .-1 } */
+
+hb4x __attribute__ ((vector_size (4 * sizeof (hb4x))))
+vvar; /* { dg-error "invalid vector type" } */
diff --git a/gcc/testsuite/gcc.dg/hardbool-trap.c b/gcc/testsuite/gcc.dg/hardbool-trap.c
new file mode 100644
index 0000000000000..2eebd0ef64fff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/hardbool-trap.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fdump-tree-optimized" } */
+
+typedef char __attribute__ ((__hardbool__ (1))) hbool;
+
+hbool var;
+
+int main () {
+  __builtin_memset (&var, 0, sizeof (var));
+  (void)var;
+}
+
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
new file mode 100644
index 0000000000000..a03887cfbecc5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5a
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
new file mode 100644
index 0000000000000..c0ba2a8b9148e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-i.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i.c b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
new file mode 100644
index 0000000000000..39214d28c5627
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype int
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
new file mode 100644
index 0000000000000..14438c5104f07
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x781ee187a53cc35all
+
+#include "hardbool-ll.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
new file mode 100644
index 0000000000000..d4d498c6f2af1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype long long
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
new file mode 100644
index 0000000000000..e38a56b5deb05
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5aa5
+
+#include "hardbool-s.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s.c b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
new file mode 100644
index 0000000000000..942300be2072a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype short
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
new file mode 100644
index 0000000000000..7beec578ff89c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-ul.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
new file mode 100644
index 0000000000000..841c1d4bc2ec8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype unsigned long
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
new file mode 100644
index 0000000000000..5bfc922795d3d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa55a
+
+#include "hardbool-us.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us.c b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
new file mode 100644
index 0000000000000..e9feec681c41e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype unsigned short
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool.c b/gcc/testsuite/gcc.dg/torture/hardbool.c
new file mode 100644
index 0000000000000..01684952a2a9f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool.c
@@ -0,0 +1,118 @@
+/* { dg-do run } */
+
+#include <assert.h>
+
+#ifndef basetype
+#define basetype char
+#endif
+
+#ifndef falseval
+#define falseval 0
+#endif
+
+#ifndef trueval
+#define trueval ~falseval
+#endif
+
+/* hardbool may be #defined so as to drop parms in other tests.  */
+typedef basetype __attribute__ ((hardbool (falseval, trueval))) hbool;
+
+typedef unsigned char __attribute__ ((__hardbool__ (1, 0))) zbool;
+
+struct hs {
+  hbool a[2];
+  hbool x:2;
+  hbool y:5;
+  zbool z:1;
+};
+
+hbool var = 0;
+
+struct hs x = { { 1, 0 }, 2, 0, 2 };
+
+int f(hbool v) {
+  return !v;
+}
+
+int g(int i) {
+  return f(i);
+}
+
+hbool h(hbool x) {
+  return x;
+}
+
+hbool h2(hbool x) {
+  return h(x);
+}
+
+int hsx(struct hs v) {
+  return v.x;
+}
+
+int ghs(hbool s) {
+  struct hs v = { {s, !s}, s, !s, s };
+  return hsx (v);
+}
+
+int t = (hbool)2;
+
+void check_pfalse (hbool *p)
+{
+  assert (!*p);
+  assert (*(basetype*)p == (basetype)falseval);
+  assert (!(int)(hbool)*p);
+}
+
+void check_ptrue (hbool *p)
+{
+  assert (*p);
+  assert (*(basetype*)p == (basetype)trueval);
+  assert ((int)(hbool)*p);
+}
+
+void check_vfalse (hbool v)
+{
+  check_pfalse (&v);
+}
+
+void check_vtrue (hbool v)
+{
+  check_ptrue (&v);
+}
+
+int main () {
+  check_pfalse (&var);
+  var = !(int)(hbool)(_Bool)var;
+  check_ptrue (&var);
+  var = (zbool)var;
+  check_ptrue (&var);
+
+  check_ptrue (&x.a[0]);
+  check_pfalse (&x.a[1]);
+  check_vtrue (x.x);
+  check_vfalse (x.y);
+  check_vtrue (x.z);
+
+  check_vtrue (t);
+
+  check_vtrue (var && t);
+  check_vfalse (!var || x.y);
+
+  check_vfalse (f (2));
+  check_vfalse (f (1));
+  check_vtrue (f (0));
+
+  check_vfalse (g (2));
+  check_vfalse (g (1));
+  check_vtrue (g (0));
+
+  check_vtrue (h (2));
+  check_vtrue (h (1));
+  check_vfalse (h (0));
+
+  check_vtrue (h2 (2));
+  check_vtrue (h2 (1));
+  check_vfalse (h2 (0));
+}
+
  
Thomas Koenig June 16, 2023, 6:52 a.m. UTC | #8
Hi Alexandre,

> On Apr  6, 2023, Bernhard Reutner-Fischer <rep.dot.nop@gmail.com> wrote:
> 
>> 29 For C_BOOL, the internal representation of .TRUE._C_BOOL and
>> .FALSE._C_BOOL shall be the same as those of
>> 30 the C values (_Bool)1 and (_Bool)0 respectively.
> 
> I'm not changing any of the standard types, FWIW.  The proposed
> extension enables alternate boolean types to be introduced, with
> extra checking for hardening.
> 
>> I personally like your proposed 0 and ~0, that's probably pretty robust.
> 
> That is only a default.  Certain applications may benefit from other
> values.  0xa5 and 0x5a seem to make a good combination too.
In gfortran, we depend on the representation of 0/1 for several things
(among them avoiding having separate library versions for logical
kind=1,2,4,8,16 for MASK).

So, such a type would be incompatible with vanilla LOGICAL variables
and with C interop logical variables.

If anybody should pass such a hardbool variable to Fortran, they
will get unpredictable results, and deserve to lose.  The opposite
effect of what would be intended :-)

Best regards

	Thomas
  
Alexandre Oliva June 16, 2023, 8:03 a.m. UTC | #9
On Jun 16, 2023, Thomas Koenig <tkoenig@netcologne.de> wrote:

> So, such a type would be incompatible with vanilla LOGICAL variables
> and with C interop logical variables.

Yeah, it would.  It's something else, and if you choose to use such a
type in an interface, it would need to be handled as such.  Presumably,
absent direct support in the desired language, using the underlying type
and the explicitly chosen constants would work.

This is nothing to call home about.  It's not unusual for languages to
support features that are not directly representable in other languages.
And this is one that isn't even hard to work around.

But I'd first doubt the wisdom of whoever adds such a type to a
cross-language interface.
  
Bernhard Reutner-Fischer June 19, 2023, 6:48 p.m. UTC | #10
On 16 June 2023 07:35:27 CEST, Alexandre Oliva via Gcc-patches <gcc-patches@gcc.gnu.org> wrote:

index 0000000000000..634feaed4deef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/hardbool-err.c
@@ -0,0 +1,28 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+typedef _Bool __attribute__ ((__hardbool__))
+hbbl; /* { dg-error "integral types" } */
+
+typedef double __attribute__ ((__hardbool__))
+hbdbl; /* { dg-error "integral types" } */
+
+enum x;
+typedef enum x __attribute__ ((__hardbool__))
+hbenum; /* { dg-error "integral types" } */
+
+struct s;
+typedef struct s __attribute__ ((__hardbool__))
+hbstruct; /* { dg-error "integral types" } */
+
+typedef int __attribute__ ((__hardbool__ (0, 0)))
+hb00; /* { dg-error "different values" } */
+
+typedef int __attribute__ ((__hardbool__ (4, 16))) hb4x;
+struct s {
+ hb4x m:2;
+}; /* { dg-error "is a GCC extension|different values" } */
+/* { dg-warning "changes value" "warning" { target *-*-* } .-1 } */
+
+hb4x __attribute__ ((vector_size (4 * sizeof (hb4x))))
+vvar; /* { dg-error "invalid vector type" } */

Arm-chair, tinfoil hat still on, didn't look closely, hence:

I don't see explicit tests with _Complex nor __complex__. Would we want to check these here, or are they handled thought the "underlying" tests above?

I'd welcome a fortran interop note in the docs as hinted previously to cover out of the box behavior. It's probably reasonably unlikely but better be safe than sorry?
cheers,
  
Qing Zhao June 21, 2023, 3:57 p.m. UTC | #11
Hi, Alexandre,

> 
> diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
> index 22e240a3c2a55..f9cc609b54d94 100644
> --- a/gcc/c/c-typeck.cc
> +++ b/gcc/c/c-typeck.cc
> @@ -2226,6 +2226,35 @@ convert_lvalue_to_rvalue (location_t loc, struct c_expr exp,
>     exp.value = convert (build_qualified_type (TREE_TYPE (exp.value), TYPE_UNQUALIFIED), exp.value);
>   if (force_non_npc)
>     exp.value = build1 (NOP_EXPR, TREE_TYPE (exp.value), exp.value);
> +
> +  {
> +    tree false_value, true_value;
> +    if (convert_p && !error_operand_p (exp.value)
> +	&& c_hardbool_type_attr (TREE_TYPE (exp.value),
> +				 &false_value, &true_value))
> +      {
> +	tree t = save_expr (exp.value);
> +
> +	mark_exp_read (exp.value);
> +
> +	tree trapfn = builtin_decl_explicit (BUILT_IN_TRAP);
> +	tree expr = build_call_expr_loc (loc, trapfn, 0);
> +	expr = build_compound_expr (loc, expr, boolean_true_node);
> +	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
> +				fold_build2_loc (loc, NE_EXPR,
> +						 boolean_type_node,
> +						 t, true_value),
> +				expr, boolean_true_node);
> +	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
> +				fold_build2_loc (loc, NE_EXPR,
> +						 boolean_type_node,
> +						 t, false_value),
> +				expr, boolean_false_node);
> +
> +	exp.value = expr;
> +      }
> +  }

I see that you have testing case to check the above built_in_trap call is generated by FE. 
Do you have a testing case to check the trap is happening at runtime? 
> +
>   return exp;
> }
> 
> @@ -8488,7 +8517,7 @@ digest_init (location_t init_loc, tree type, tree init, tree origtype,
> 	    }
> 	}
> 
> -      if (code == VECTOR_TYPE)
> +      if (code == VECTOR_TYPE || c_hardbool_type_attr (type))
> 	/* Although the types are compatible, we may require a
> 	   conversion.  */
> 	inside_init = convert (type, inside_init);
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 2de212c8c2d84..7b5592502734e 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -8681,6 +8681,58 @@ initialization will result in future breakage.
> GCC emits warnings based on this attribute by default; use
> @option{-Wno-designated-init} to suppress them.
> 
> +@cindex @code{hardbool} type attribute
> +@item hardbool
> +@itemx hardbool (@var{false_value})
> +@itemx hardbool (@var{false_value}, @var{true_value})
> +This attribute may only be applied to integral types in C, to introduce
> +hardened boolean types.  It turns the integral type into a boolean-like
> +type with the same size and precision, that uses the specified values as
> +representations for @code{false} and @code{true}.  Underneath, it is
> +actually an enumerate type, but its observable behavior is like that of
> +@code{_Bool}, except for the strict internal representations, verified
> +by runtime checks.
> +
> +If @var{true_value} is omitted, the bitwise negation of
> +@var{false_value} is used.  If @var{false_value} is omitted, zero is
> +used.  The named representation values must be different when converted
> +to the original integral type.  Narrower bitfields are rejected if the
> +representations become indistinguishable.
> +
> +Values of such types automatically decay to @code{_Bool}, at which
> +point, the selected representation values are mapped to the
> +corresponding @code{_Bool} values.  When the represented value is not
> +determined, at compile time, to be either @var{false_value} or
> +@var{true_value}, runtime verification calls @code{__builtin_trap} if it
> +is neither.  This is what makes them hardened boolean types.
> +
> +When converting scalar types to such hardened boolean types, implicitly
> +or explicitly, behavior corresponds to a conversion to @code{_Bool},
> +followed by a mapping from @code{false} and @code{true} to
> +@var{false_value} and @var{true_value}, respectively.
> +
> +@smallexample
> +typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
> +hbool first = 0;       /* False, stored as (char)0x5a.  */
> +hbool second = !first; /* True, stored as ~(char)0x5a.  */
> +
> +static hbool zeroinit; /* False, stored as (char)0x5a.  */
> +auto hbool uninit;     /* Undefined, may trap.  */
> +@end smallexample
> +
> +When zero-initializing a variable or field of hardened boolean type
> +(presumably held in static storage) the implied zero initializer gets
> +converted to @code{_Bool}, and then to the hardened boolean type, so
> +that the initial value is the hardened representation for @code{false}.
> +Using that value is well defined.  This is @emph{not} the case when
> +variables and fields of such types are uninitialized (presumably held in
> +automatic or dynamic storage): their values are indeterminate, and using
> +them invokes undefined behavior.  Using them may trap or not, depending
> +on the bits held in the storage (re)used for the variable, if any, and
> +on optimizations the compiler may perform on the grounds that using
> +uninitialized values invokes undefined behavior.

So, when -ftrivial-auto-var-init presents, what’s the behavior for the hardened Boolean auto variables? 
This might need to be documented and also handled correctly. 

thanks.

Qing
  
Alexandre Oliva June 22, 2023, 1:08 a.m. UTC | #12
Thanks for the test.

Did you mean for me to incorporate it into the patch, or do you mean to
contribute it separately, if the feature happens to be accepted?

On Jun 19, 2023, Bernhard Reutner-Fischer <rep.dot.nop@gmail.com> wrote:

> I don't see explicit tests with _Complex nor __complex__. Would we
> want to check these here, or are they handled thought the "underlying"
> tests above?

Good question.  The notion of using complex types to hold booleans
hadn't even crossed my mind.

On the one hand, there doesn't seem to be reason to rule them out, and
that could go for literally any other type.

On the other, there doesn't seem to be any useful case for them.  Can
anyone think of one?

> I'd welcome a fortran interop note in the docs

Is there any good place for such interop notes?  I'm not sure I'm
best-suited to write them up, since Fortran is not a language I'm
very familiar with, but I suppose I could give it a try.
  
Alexandre Oliva June 22, 2023, 2:35 a.m. UTC | #13
On Jun 21, 2023, Qing Zhao <qing.zhao@oracle.com> wrote:

> I see that you have testing case to check the above built_in_trap call
> is generated by FE.
> Do you have a testing case to check the trap is happening at runtime? 

I have written such tests, using type-punning, but I don't think our
testing infrastructure could take trapping as success and other results
as failure.

> So, when -ftrivial-auto-var-init presents, what’s the behavior for the
> hardened Boolean auto variables?

Good question.  This option was not even available when hardbool was
designed and implemented.  (tests) The deferred_init internal function
initializes with bit-patterns 0x00 or 0xfe, regardless of type, when the
data lives in memory, and otherwise forces the 0x00 bit pattern for
booleans, variable-sized types, types that cannot be accessed with a
single mode or for modes that don't have a set pattern.

It's hard for me to tell what "correct" or "expected" would be here.
Enumerators don't seem to be given special treatment.  Checked
enumerators, constrained integral subtypes, none of these would get
well-formed values or even checking at the assignments.

If I were to design this option myself, I'd probably arrange for it to
handle booleans (including hardened booleans) by zero-initializing as
false and pattern-initializing as true, though I realize that this could
be very confusing if one chose to use 0xfe as the value for false and/or
0x00 as the value for true.

I'd probably have arranged for the front-end to create the initializer
value, because expansion time is too late to figure it out: we may not
even have the front-end at hand any more, in case of lto compilation.


But with the current description and implementation, I guess the
behavior is correct, if not ideal: the bit patterns refer to the
representation, rather than to the language interpretation of the value.
When it comes ot integral types, they may match, but floating-point,
fixed fractional types, offsets and multipliers, even boolean member of
larger structs...  not so much: the effect is that of a memset, rather
than that of an assignment of zero or of the pattern to a variable.

Now, I acknowledge that the decision to make implicit
zero-initialization of boolean types set them to the value for false,
rather than to all-bits-zero representation, is a departure from common
practice of zero-initialization yielding logical zero.  That was unusual
enough that I thought it worth mentioning in the docs.


> This might need to be documented and also handled correctly. 

I suppose the place to document this distinction between logical values
and representation would be under -ftrivial-auto-var-init.  That's
likely where someone using that option would look for guidance on how it
interacts with unusual types, and where exceptions to general
expectations WRT initialization would go.  Do you concur?

That said, it probalby makes sense to refer to / mention that
-ftrivial-auto-var-init does not special-case hardened booleans in the
hardened booleans documentation.  I wonder if there are other
conflicting options I'm not even aware of.
  
Bernhard Reutner-Fischer June 22, 2023, 9:15 p.m. UTC | #14
On Wed, 21 Jun 2023 22:08:55 -0300
Alexandre Oliva <oliva@adacore.com> wrote:

> Thanks for the test.
> 
> Did you mean for me to incorporate it into the patch, or do you mean to
> contribute it separately, if the feature happens to be accepted?

These were your tests that i quoted but i or my MUA botched to add one
level of quotes -- sorry for that.

> 
> On Jun 19, 2023, Bernhard Reutner-Fischer <rep.dot.nop@gmail.com> wrote:
> 
> > I don't see explicit tests with _Complex nor __complex__. Would we
> > want to check these here, or are they handled thought the "underlying"
> > tests above?  
> 
> Good question.  The notion of using complex types to hold booleans
> hadn't even crossed my mind.

Maybe it is not real, it just sparkled through somehow.

> On the one hand, there doesn't seem to be reason to rule them out, and
> that could go for literally any other type.
> 
> On the other, there doesn't seem to be any useful case for them.  Can
> anyone think of one?

We could either not reject other such uses and wait or we could reject
them and equally wait for complaints. I would not dare to bet who pops
up first, fuzzers or users, though arguments of the latter would
probably be interesting.. I don't have an opinion (nor a use-case),
really, it was just a thought (i mentioned tinfoil hat, did i ;).

> 
> > I'd welcome a fortran interop note in the docs  
> 
> Is there any good place for such interop notes?  I'm not sure I'm
> best-suited to write them up, since Fortran is not a language I'm
> very familiar with, but I suppose I could give it a try.
> 

I'd append to your extend.texi hunk below the para about uninitialized a
note to the effect of:
Note: Types annotated with this attribute may not be Fortran
interoperable.

I would not go into too much detail about C_BOOL nor LOGICAL for i
reckon anybody sensibilised to either two of that attribute, C and
Fortran will draw her conclusions.
Didn't really think how easy it would be to handle this on the user
side, but i fear the modern iso_c_binding way would need help from the
compiler for the lazy. I'd expect a user to be able to trivially
translate this in wrappers done the old way though, which is a pity
from an educational and modernisation POV. Didn't look closely, so this
guesstimate might be all wrong, of course.

thanks,
  
Qing Zhao June 23, 2023, 9:40 p.m. UTC | #15
> On Jun 21, 2023, at 10:35 PM, Alexandre Oliva <oliva@adacore.com> wrote:
> 
> On Jun 21, 2023, Qing Zhao <qing.zhao@oracle.com> wrote:
> 
>> I see that you have testing case to check the above built_in_trap call
>> is generated by FE.
>> Do you have a testing case to check the trap is happening at runtime? 
> 
> I have written such tests, using type-punning, but I don't think our
> testing infrastructure could take trapping as success and other results
> as failure.
Okay, I see. 
> 
>> So, when -ftrivial-auto-var-init presents, what’s the behavior for the
>> hardened Boolean auto variables?
> 
> Good question.  This option was not even available when hardbool was
> designed and implemented.  (tests) The deferred_init internal function
> initializes with bit-patterns 0x00 or 0xfe, regardless of type, when the
> data lives in memory, and otherwise forces the 0x00 bit pattern for
> booleans, variable-sized types, types that cannot be accessed with a
> single mode or for modes that don't have a set pattern.

-ftrivial-auto-var-init has been in GCC since GCC12.  
The decision to use 0x00 for zero-initiation, 0xfe for pattern-initiation
has been discussed thoroughly during the design phase. -:)

These initial values (0x00 and 0xfe) are not perfect choices, but they the 
best choices based on functionality and performance tradeoff. 

Since this hardbool attribute will also  be a security feature, users that seek
security features of GCC will likely ask the same question for these two
features.

So, their interaction is better to be at least documented. That’s my main point.

> 
> It's hard for me to tell what "correct" or "expected" would be here.
> Enumerators don't seem to be given special treatment.  Checked
> enumerators, constrained integral subtypes, none of these would get
> well-formed values or even checking at the assignments.

> 
> If I were to design this option myself, I'd probably arrange for it to
> handle booleans (including hardened booleans) by zero-initializing as
> false and pattern-initializing as true, though I realize that this could
> be very confusing if one chose to use 0xfe as the value for false and/or
> 0x00 as the value for true.

For normal Boolean variables, 0x00 is false, this is a reasonable init value with zero-initialization.
For hardbool variables, what 0x00 represents if it’s not false or true value?  A garbage value for this hardbool variable?
> 
> I'd probably have arranged for the front-end to create the initializer
> value, because expansion time is too late to figure it out: we may not
> even have the front-end at hand any more, in case of lto compilation.

Is the hardbool attribute information available during the rtl expansion phase?
> 
> 
> But with the current description and implementation, I guess the
> behavior is correct, if not ideal: the bit patterns refer to the
> representation, rather than to the language interpretation of the value.
> When it comes ot integral types, they may match, but floating-point,
> fixed fractional types, offsets and multipliers, even boolean member of
> larger structs...  not so much: the effect is that of a memset, rather
> than that of an assignment of zero or of the pattern to a variable.
> 
> Now, I acknowledge that the decision to make implicit
> zero-initialization of boolean types set them to the value for false,
> rather than to all-bits-zero representation, is a departure from common
> practice of zero-initialization yielding logical zero.

Dont’s quite understand the above, for normal boolean variables, zero-initialization set them to false, 
and also set them to all-bits-zero.(i.e, all-bits-zero is equal to logical false for normal boolean variable, right?)
And this is a very reasonable initial value for Boolean variables,


>  That was unusual
> enough that I thought it worth mentioning in the docs.
> 
I don’t see why this is unusual for the normal Boolean variables? Could you please explain a little bit?

From my understanding, only with the introduction of “hardbool” attribute, all-bits-zero will not be equal to the
logical false anymore. 

> 
>> This might need to be documented and also handled correctly. 
> 
> I suppose the place to document this distinction between logical values
> and representation would be under -ftrivial-auto-var-init.

Yes, the documentation of -ftrivial-auto-var-init could be updated with this clarification, mainly for
The new “hardbool” attribute. 

>  That's
> likely where someone using that option would look for guidance on how it
> interacts with unusual types, and where exceptions to general
> expectations WRT initialization would go.  Do you concur?
And at the same time, the doc for the new “hardbool” attribute might be better to add such warning too?

> 
> That said, it probalby makes sense to refer to / mention that
> -ftrivial-auto-var-init does not special-case hardened booleans in the
> hardened booleans documentation.

Agreed.

thanks.

Qing

>  I wonder if there are other
> conflicting options I'm not even aware of.

> 
> -- 
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>   Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>
  
Alexandre Oliva June 24, 2023, 2:38 a.m. UTC | #16
On Jun 23, 2023, Qing Zhao via Gcc-patches <gcc-patches@gcc.gnu.org> wrote:

> -ftrivial-auto-var-init has been in GCC since GCC12.  

*nod*.  IIRC I started designing hardbool in GCC10, but the first
complete implementation was for GCC11.

> The decision to use 0x00 for zero-initiation, 0xfe for pattern-initiation
> has been discussed thoroughly during the design phase. -:)

*nod*, and it's a good one

> Since this hardbool attribute will also  be a security feature, users that seek
> security features of GCC will likely ask the same question for these two
> features.

> So, their interaction is better to be at least documented. That’s my main point.

*nod*.  At first, I thought the ideal place to clarify the issue was in
the documentation for the option, because there's nothing exceptional
about the option's behavior when it comes to hardbool specifically.  But
it doesn't hurt to mention it in both places, so I did.  How about the
incremental patchlet below (at the end)?

> For normal Boolean variables, 0x00 is false, this is a reasonable init
> value with zero-initialization.

*nod*.  I was surprised by zero initialization of (non-hardened)
booleans even when pattern is requested, but not consistently
(e.g. boolean fields of a larger struct would still get
pattern-initialized IIUC).  I'd have expected pattern would translate to
nonzero and thus to true, rather than false.

> For hardbool variables, what 0x00 represents if it’s not false or true
> value?

It depends on how hardbool is parameterized.  One may pick 0x00 or 0xFE
as the representations for true or false, or neither, in which case the
trivial initializer will end up as a trapping value.

>> I'd probably have arranged for the front-end to create the initializer
>> value, because expansion time is too late to figure it out: we may not
>> even have the front-end at hand any more, in case of lto compilation.

> Is the hardbool attribute information available during the rtl expansion phase?

It is in the sense that the attribute lives on, but c_hardbool_type_attr
is a frontend function, it cannot be called from e.g. lto1.

The hardbool attribute is also implemented in Ada, but there it only
affects validity checking in the front end: Boolean types in Ada are
Enumeration types, and there is standard syntax to specify the
representations for true and false.  AFAICT, once we translate GNAT IR
to GNU IR, hardened booleans would not be recognizable as boolean types.
Even non-hardened booleans with representation clauses would.  So
handling these differently from other enumeration types, to make them
closer to booleans, would be a bit of a challenge, and a
backwards-compatibility issue (because such booleans have already been
handled in the present way since the introduction of -ftrivial-* back in
GCC12)

>> Now, I acknowledge that the decision to make implicit
>> zero-initialization of boolean types set them to the value for false,
>> rather than to all-bits-zero representation, is a departure from common
>> practice of zero-initialization yielding logical zero.

> Dont’s quite understand the above, for normal boolean variables,

Sorry, I meant hardened boolean types.  This was WRT to the design
decision that led to this bit in the documentation:

typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
[...]
static hbool zeroinit; /* False, stored as (char)0x5a.  */
auto hbool uninit;     /* Undefined, may trap.  */
  
> And this is a very reasonable initial value for Boolean variables,

Agreed.  The all-zeros bit pattern is not so great for booleans that use
alternate representations, though, such as the following standard Ada:

   type MyBool is new Boolean;
   for MyBool use (16#5a#, 16#a5#);
   for MyBool'Size use 8;

or for biased variables such as:

  X : Integer range 254 .. 507;
  for X'Size use 8; -- bits, so a biased representation is required.

Just to make things more interesting, I chose a range for X that causes
the compiler to represent 0xfe as 0x00 in in the byte that holds X, but
that places the 0xfe pattern just out of the range :-) So with
-ftrivial-auto-var-init=zero, X = 254, whereas with
-ftrivial-auto-var-init=pattern, it fails validity checking, and might
come out as 508 if that's disabled.

> From my understanding, only with the introduction of “hardbool”
> attribute, all-bits-zero will not be equal to the
> logical false anymore. 

Ada booleans have long allowed nonzero representations for false.


diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 772209c1793e8..ae7867bb35696 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8774,6 +8774,12 @@ on the bits held in the storage (re)used for the variable, if any, and
 on optimizations the compiler may perform on the grounds that using
 uninitialized values invokes undefined behavior.
 
+Users of @option{-ftrivial-auto-var-init} should be aware that the bit
+patterns used as its trivial initializers are @emph{not} converted to
+@code{hardbool} types, so using variables implicitly initialized by it
+may trap if the representations values chosen for @code{false} and
+@code{true} do not match the initializer.
+
 
 @cindex @code{may_alias} type attribute
 @item may_alias
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 296a9c178b195..e21f468a9c8f3 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -13510,6 +13510,23 @@ The values used for pattern initialization might be changed in the future.
 
 The default is @samp{uninitialized}.
 
+Note that the initializer values, whether @samp{zero} or @samp{pattern},
+refer to data representation (in memory or machine registers), rather
+than to their interpretation as numerical values.  This distinction may
+be important in languages that support types with biases or implicit
+multipliers, and with such extensions as @samp{hardbool} (@pxref{Type
+Attributes}).  For example, a variable that uses 8 bits to represent
+(biased) quantities in the @code{range 160..400} will be initialized
+with the bit patterns @code{0x00} or @code{0xFE}, depending on
+@var{choice}, whether or not these representations stand for values in
+that range, and even if they do, the interpretation of the value held by
+the variable will depend on the bias.  A @samp{hardbool} variable that
+uses say @code{0X5A} and @code{0xA5} for @code{false} and @code{true},
+respectively, will trap with either @samp{choice} of trivial
+initializer, i.e., @samp{zero} initialization will not convert to the
+representation for @code{false}, even if it would for a @code{static}
+variable of the same type.
+
 You can control this behavior for a specific variable by using the variable
 attribute @code{uninitialized} (@pxref{Variable Attributes}).
  
Alexandre Oliva June 24, 2023, 2:57 a.m. UTC | #17
On Jun 22, 2023, Bernhard Reutner-Fischer <rep.dot.nop@gmail.com> wrote:

> On Wed, 21 Jun 2023 22:08:55 -0300
> Alexandre Oliva <oliva@adacore.com> wrote:

>> Thanks for the test.
>> 
>> Did you mean for me to incorporate it into the patch, or do you mean to
>> contribute it separately, if the feature happens to be accepted?

> These were your tests that i quoted

Aaah.  I didn't look too closely, I just assumed you'd tweaked something
in there.

>> > I don't see explicit tests with _Complex nor __complex__. Would we
>> > want to check these here, or are they handled thought the "underlying"
>> > tests above?  
>> 
>> Good question.  The notion of using complex types to hold booleans
>> hadn't even crossed my mind.

> Maybe it is not real, it just sparkled through somehow.

Is it imaginary, then? :-D

>> On the other, there doesn't seem to be any useful case for them.  Can
>> anyone think of one?

> We could either not reject other such uses and wait or we could reject
> them and equally wait for complaints. I would not dare to bet who pops
> up first, fuzzers or users

I bet on fuzzers :-)

> it was just a thought (i mentioned tinfoil hat, did i ;).

indeed ;-)

Having verified that it gets rejected (phew :-) I'm inclined to add it
to the test you quoted and be done with it.  If a reason ever comes up
to support it, the test can be adjusted accordingly.

>> 
>> > I'd welcome a fortran interop note in the docs  
>> 
>> Is there any good place for such interop notes?  I'm not sure I'm
>> best-suited to write them up, since Fortran is not a language I'm
>> very familiar with, but I suppose I could give it a try.

> I'd append to your extend.texi hunk below the para about uninitialized a
> note to the effect of:
> Note: Types annotated with this attribute may not be Fortran
> interoperable.

I'm not comfortable single-casing Fortran like that.  I expect other
languages could face similar interop issues when relying on
single-language extensions.  How about:

  Since this is a language extension only available in C, interoperation
  with other languages may pose difficulties.  It should interoperate
  with Ada Booleans defined with the same size and equivalent
  representation clauses, and with enumerations or other languages'
  integral types that correspond to C's chosen integral type.
  
Alexandre Oliva June 24, 2023, 4:42 a.m. UTC | #18
On Jun 16, 2023, Alexandre Oliva <oliva@adacore.com> wrote:

> On Aug  9, 2022, Alexandre Oliva <oliva@adacore.com> wrote:
>> Ping?

> Ping?  Refreshed, added setting of ENUM_UNDERLYING_TYPE, retested on
> x86_64-linux-gnu (also on gcc-13).

Here's a consolidated patch incorporating the doc and test patchlets
sent out in response to Qing's and Berhard's suggestions.

Regstrapped on x86_64-linux-gnu.  Ok to install?


Introduce hardbool attribute for C

This patch introduces hardened booleans in C.  The hardbool attribute,
when attached to an integral type, turns it into an enumerate type
with boolean semantics, using the named or implied constants as
representations for false and true.

Expressions of such types decay to _Bool, trapping if the value is
neither true nor false, and _Bool can convert implicitly back to them.
Other conversions go through _Bool first.


for  gcc/c-family/ChangeLog

	* c-attribs.cc (c_common_attribute_table): Add hardbool.
	(handle_hardbool_attribute): New.
	(type_valid_for_vector_size): Reject hardbool.
	* c-common.cc (convert_and_check): Skip warnings for convert
	and check for hardbool.
	(c_hardbool_type_attr_1): New.
	* c-common.h (c_hardbool_type_attr): New.

for  gcc/c/ChangeLog

	* c-typeck.cc (convert_lvalue_to_rvalue): Decay hardbools.
	* c-convert.cc (convert): Convert to hardbool through
	truthvalue.
	* c-decl.cc (check_bitfield_type_and_width): Skip enumeral
	truncation warnings for hardbool.
	(finish_struct): Propagate hardbool attribute to bitfield
	types.
	(digest_init): Convert to hardbool.

for  gcc/ChangeLog

	* doc/extend.texi (hardbool): New type attribute.
	* doc/invoke.texi (-ftrivial-auto-var-init): Document
	representation vs values.

for  gcc/testsuite/ChangeLog

	* gcc.dg/hardbool-err.c: New.
	* gcc.dg/hardbool-trap.c: New.
	* gcc.dg/hardbool.c: New.
	* gcc.dg/hardbool-s.c: New.
	* gcc.dg/hardbool-us.c: New.
	* gcc.dg/hardbool-i.c: New.
	* gcc.dg/hardbool-ul.c: New.
	* gcc.dg/hardbool-ll.c: New.
	* gcc.dg/hardbool-5a.c: New.
	* gcc.dg/hardbool-s-5a.c: New.
	* gcc.dg/hardbool-us-5a.c: New.
	* gcc.dg/hardbool-i-5a.c: New.
	* gcc.dg/hardbool-ul-5a.c: New.
	* gcc.dg/hardbool-ll-5a.c: New.
---
 gcc/c-family/c-attribs.cc                     |   98 ++++++++++++++++++++-
 gcc/c-family/c-common.cc                      |   21 ++++
 gcc/c-family/c-common.h                       |   18 ++++
 gcc/c/c-convert.cc                            |   14 +++
 gcc/c/c-decl.cc                               |   10 ++
 gcc/c/c-typeck.cc                             |   31 ++++++-
 gcc/doc/extend.texi                           |   64 ++++++++++++++
 gcc/doc/invoke.texi                           |   21 ++++
 gcc/testsuite/gcc.dg/hardbool-err.c           |   31 +++++++
 gcc/testsuite/gcc.dg/hardbool-trap.c          |   13 +++
 gcc/testsuite/gcc.dg/torture/hardbool-5a.c    |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c  |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-i.c     |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-ll.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c  |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-s.c     |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-ul.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c |    6 +
 gcc/testsuite/gcc.dg/torture/hardbool-us.c    |    5 +
 gcc/testsuite/gcc.dg/torture/hardbool.c       |  118 +++++++++++++++++++++++++
 22 files changed, 496 insertions(+), 4 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/hardbool-err.c
 create mode 100644 gcc/testsuite/gcc.dg/hardbool-trap.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-i.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ll.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-s.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-ul.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool-us.c
 create mode 100644 gcc/testsuite/gcc.dg/torture/hardbool.c

diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index c12211cb4d499..365319e642b1a 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -176,6 +176,7 @@ static tree handle_objc_root_class_attribute (tree *, tree, tree, int, bool *);
 static tree handle_objc_nullability_attribute (tree *, tree, tree, int, bool *);
 static tree handle_signed_bool_precision_attribute (tree *, tree, tree, int,
 						    bool *);
+static tree handle_hardbool_attribute (tree *, tree, tree, int, bool *);
 static tree handle_retain_attribute (tree *, tree, tree, int, bool *);
 static tree handle_fd_arg_attribute (tree *, tree, tree, int, bool *);
 
@@ -293,6 +294,8 @@ const struct attribute_spec c_common_attribute_table[] =
        affects_type_identity, handler, exclude } */
   { "signed_bool_precision",  1, 1, false, true, false, true,
 			      handle_signed_bool_precision_attribute, NULL },
+  { "hardbool",               0, 2, false, true, false, true,
+			      handle_hardbool_attribute, NULL },
   { "packed",                 0, 0, false, false, false, false,
 			      handle_packed_attribute,
 	                      attr_aligned_exclusions },
@@ -992,6 +995,96 @@ handle_signed_bool_precision_attribute (tree *node, tree name, tree args,
   return NULL_TREE;
 }
 
+/* Handle a "hardbool" attribute; arguments as in struct
+   attribute_spec.handler.  */
+
+static tree
+handle_hardbool_attribute (tree *node, tree name, tree args,
+			   int /* flags */, bool *no_add_attrs)
+{
+  if (c_language != clk_c)
+    {
+      error ("%qE attribute only supported in C", name);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  if (!TYPE_P (*node) || TREE_CODE (*node) != INTEGER_TYPE)
+    {
+      error ("%qE attribute only supported on "
+	     "integral types", name);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  tree orig = *node;
+  *node = build_duplicate_type (orig);
+
+  TREE_SET_CODE (*node, ENUMERAL_TYPE);
+  ENUM_UNDERLYING_TYPE (*node) = orig;
+
+  tree false_value;
+  if (args)
+    false_value = fold_convert (*node, TREE_VALUE (args));
+  else
+    false_value = fold_convert (*node, integer_zero_node);
+
+  if (TREE_OVERFLOW_P (false_value))
+    {
+      warning (OPT_Wattributes,
+	       "overflows in conversion from %qT to %qT "
+	       "changes value from %qE to %qE",
+	       TREE_TYPE (TREE_VALUE (args)), *node,
+	       TREE_VALUE (args), false_value);
+      TREE_OVERFLOW (false_value) = false;
+    }
+
+  tree true_value;
+  if (args && TREE_CHAIN (args))
+    true_value = fold_convert (*node, TREE_VALUE (TREE_CHAIN (args)));
+  else
+    true_value = fold_build1 (BIT_NOT_EXPR, *node, false_value);
+
+  if (TREE_OVERFLOW_P (true_value))
+    {
+      warning (OPT_Wattributes,
+	       "overflows in conversion from %qT to %qT "
+	       "changes value from %qE to %qE",
+	       TREE_TYPE (TREE_VALUE (TREE_CHAIN (args))), *node,
+	       TREE_VALUE (TREE_CHAIN (args)), true_value);
+      TREE_OVERFLOW (true_value) = false;
+    }
+
+  if (tree_int_cst_compare (false_value, true_value) == 0)
+    {
+      error ("%qE attribute requires different values for"
+	     " %<false%> and %<true%> for type %qT",
+	     name, *node);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  tree values = build_tree_list (get_identifier ("false"),
+				 false_value);
+  TREE_CHAIN (values) = build_tree_list (get_identifier ("true"),
+					 true_value);
+
+  /* Do *not* set TYPE_MIN_VALUE, TYPE_MAX_VALUE, nor TYPE_PRECISION according
+     to the false and true values.  That might cause the constants to be the
+     only acceptable values, which would drop the very hardening checks this
+     attribute is supposed to add.  */
+
+  TYPE_ATTRIBUTES (*node) = tree_cons (name, args,
+				       TYPE_ATTRIBUTES (*node));
+  *no_add_attrs = TRUE;
+
+  gcc_checking_assert (!TYPE_CACHED_VALUES_P (*node));
+  TYPE_VALUES (*node) = values;
+  TYPE_NAME (*node) = orig;
+
+  return NULL_TREE;
+}
+
 /* Handle a "packed" attribute; arguments as in
    struct attribute_spec.handler.  */
 
@@ -4357,7 +4450,8 @@ static tree
 type_valid_for_vector_size (tree type, tree atname, tree args,
 			    unsigned HOST_WIDE_INT *ptrnunits)
 {
-  bool error_p = ptrnunits != NULL;
+  bool hardbool_p = c_hardbool_type_attr (type);
+  bool error_p = ptrnunits != NULL || hardbool_p;
 
   /* Get the mode of the type being modified.  */
   machine_mode orig_mode = TYPE_MODE (type);
@@ -4369,7 +4463,7 @@ type_valid_for_vector_size (tree type, tree atname, tree args,
 	  && GET_MODE_CLASS (orig_mode) != MODE_INT
 	  && !ALL_SCALAR_FIXED_POINT_MODE_P (orig_mode))
       || !tree_fits_uhwi_p (TYPE_SIZE_UNIT (type))
-      || TREE_CODE (type) == BOOLEAN_TYPE)
+      || TREE_CODE (type) == BOOLEAN_TYPE || hardbool_p)
     {
       if (error_p)
 	error ("invalid vector type for attribute %qE", atname);
diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 9c8eed5442a45..1257760c426ac 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -1757,7 +1757,8 @@ convert_and_check (location_t loc, tree type, tree expr, bool init_const)
 
   if (c_inhibit_evaluation_warnings == 0
       && !TREE_OVERFLOW_P (expr)
-      && result != error_mark_node)
+      && result != error_mark_node
+      && !c_hardbool_type_attr (type))
     warnings_for_convert_and_check (loc, type, expr_for_warning, result);
 
   return result;
@@ -9545,4 +9546,22 @@ c_strict_flex_array_level_of (tree array_field)
   return strict_flex_array_level;
 }
 
+/* This is the slow path of c-common.h's c_hardbool_type_attr.  */
+
+tree
+c_hardbool_type_attr_1 (tree type, tree *false_value, tree *true_value)
+{
+  tree attr = lookup_attribute ("hardbool", TYPE_ATTRIBUTES (type));
+  if (!attr)
+    return attr;
+
+  if (false_value)
+    *false_value = TREE_VALUE (TYPE_VALUES (type));
+
+  if (true_value)
+    *true_value = TREE_VALUE (TREE_CHAIN (TYPE_VALUES (type)));
+
+  return attr;
+}
+
 #include "gt-c-family-c-common.h"
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 336a09f4a407d..a5d3e77f121f8 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -909,6 +909,7 @@ extern bool get_attribute_operand (tree, unsigned HOST_WIDE_INT *);
 extern void c_common_finalize_early_debug (void);
 extern unsigned int c_strict_flex_array_level_of (tree);
 extern bool c_option_is_from_cpp_diagnostics (int);
+extern tree c_hardbool_type_attr_1 (tree, tree *, tree *);
 
 /* Used by convert_and_check; in front ends.  */
 extern tree convert_init (tree, tree);
@@ -1342,6 +1343,23 @@ c_tree_chain_next (tree t)
   return NULL;
 }
 
+/* Return the hardbool attribute associated with TYPE, if there is one, provided
+   that TYPE looks like an enumeral type that might have been set up by
+   handle_hardbool_attribute.  Return NULL otherwise.
+
+   If FALSE_VALUE or TRUE_VALUE are non-NULL and TYPE is a hardened boolean
+   type, store the corresponding representation values.  */
+static inline tree
+c_hardbool_type_attr (tree type,
+		      tree *false_value = NULL, tree *true_value = NULL)
+{
+  if (TREE_CODE (type) != ENUMERAL_TYPE
+      || TYPE_LANG_SPECIFIC (type))
+    return NULL_TREE;
+
+  return c_hardbool_type_attr_1 (type, false_value, true_value);
+}
+
 /* Mask used by tm_stmt_attr.  */
 #define TM_STMT_ATTR_OUTER	2
 #define TM_STMT_ATTR_ATOMIC	4
diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
index 5754d00b30a67..e73103c188434 100644
--- a/gcc/c/c-convert.cc
+++ b/gcc/c/c-convert.cc
@@ -105,6 +105,20 @@ c_convert (tree type, tree expr, bool init_const)
       return error_mark_node;
     }
 
+  {
+    tree false_value, true_value;
+    if (c_hardbool_type_attr (type, &false_value, &true_value))
+      {
+	bool save = in_late_binary_op;
+	in_late_binary_op = true;
+	expr = c_objc_common_truthvalue_conversion (input_location, expr);
+	in_late_binary_op = save;
+
+	return fold_build3_loc (loc, COND_EXPR, type,
+				expr, true_value, false_value);
+      }
+  }
+
   switch (code)
     {
     case VOID_TYPE:
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index 1af51c4acfc5e..cc3ec90053994 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -6414,6 +6414,12 @@ check_bitfield_type_and_width (location_t loc, tree *type, tree *width,
   else
     w = tree_to_uhwi (*width);
 
+  /* Truncation of hardbool false and true representation values is always safe:
+     either the values remain different, or we'll report a problem when creating
+     the narrower type.  */
+  if (c_hardbool_type_attr (*type))
+    return;
+
   if (TREE_CODE (*type) == ENUMERAL_TYPE)
     {
       struct lang_type *lt = TYPE_LANG_SPECIFIC (*type);
@@ -9304,6 +9310,10 @@ finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
 	    {
 	      TREE_TYPE (field)
 		= c_build_bitfield_integer_type (width, TYPE_UNSIGNED (type));
+	      if (tree attr = c_hardbool_type_attr (type))
+		decl_attributes (&TREE_TYPE (field),
+				 copy_list (attr),
+				 0, NULL_TREE);
 	      SET_DECL_MODE (field, TYPE_MODE (TREE_TYPE (field)));
 	    }
 	  DECL_INITIAL (field) = NULL_TREE;
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index 22e240a3c2a55..f9cc609b54d94 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -2226,6 +2226,35 @@ convert_lvalue_to_rvalue (location_t loc, struct c_expr exp,
     exp.value = convert (build_qualified_type (TREE_TYPE (exp.value), TYPE_UNQUALIFIED), exp.value);
   if (force_non_npc)
     exp.value = build1 (NOP_EXPR, TREE_TYPE (exp.value), exp.value);
+
+  {
+    tree false_value, true_value;
+    if (convert_p && !error_operand_p (exp.value)
+	&& c_hardbool_type_attr (TREE_TYPE (exp.value),
+				 &false_value, &true_value))
+      {
+	tree t = save_expr (exp.value);
+
+	mark_exp_read (exp.value);
+
+	tree trapfn = builtin_decl_explicit (BUILT_IN_TRAP);
+	tree expr = build_call_expr_loc (loc, trapfn, 0);
+	expr = build_compound_expr (loc, expr, boolean_true_node);
+	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
+				fold_build2_loc (loc, NE_EXPR,
+						 boolean_type_node,
+						 t, true_value),
+				expr, boolean_true_node);
+	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
+				fold_build2_loc (loc, NE_EXPR,
+						 boolean_type_node,
+						 t, false_value),
+				expr, boolean_false_node);
+
+	exp.value = expr;
+      }
+  }
+
   return exp;
 }
 
@@ -8488,7 +8517,7 @@ digest_init (location_t init_loc, tree type, tree init, tree origtype,
 	    }
 	}
 
-      if (code == VECTOR_TYPE)
+      if (code == VECTOR_TYPE || c_hardbool_type_attr (type))
 	/* Although the types are compatible, we may require a
 	   conversion.  */
 	inside_init = convert (type, inside_init);
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index a3b37337bd3d3..dd405ba04f9ef 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8698,6 +8698,70 @@ initialization will result in future breakage.
 GCC emits warnings based on this attribute by default; use
 @option{-Wno-designated-init} to suppress them.
 
+@cindex @code{hardbool} type attribute
+@item hardbool
+@itemx hardbool (@var{false_value})
+@itemx hardbool (@var{false_value}, @var{true_value})
+This attribute may only be applied to integral types in C, to introduce
+hardened boolean types.  It turns the integral type into a boolean-like
+type with the same size and precision, that uses the specified values as
+representations for @code{false} and @code{true}.  Underneath, it is
+actually an enumerate type, but its observable behavior is like that of
+@code{_Bool}, except for the strict internal representations, verified
+by runtime checks.
+
+If @var{true_value} is omitted, the bitwise negation of
+@var{false_value} is used.  If @var{false_value} is omitted, zero is
+used.  The named representation values must be different when converted
+to the original integral type.  Narrower bitfields are rejected if the
+representations become indistinguishable.
+
+Values of such types automatically decay to @code{_Bool}, at which
+point, the selected representation values are mapped to the
+corresponding @code{_Bool} values.  When the represented value is not
+determined, at compile time, to be either @var{false_value} or
+@var{true_value}, runtime verification calls @code{__builtin_trap} if it
+is neither.  This is what makes them hardened boolean types.
+
+When converting scalar types to such hardened boolean types, implicitly
+or explicitly, behavior corresponds to a conversion to @code{_Bool},
+followed by a mapping from @code{false} and @code{true} to
+@var{false_value} and @var{true_value}, respectively.
+
+@smallexample
+typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
+hbool first = 0;       /* False, stored as (char)0x5a.  */
+hbool second = !first; /* True, stored as ~(char)0x5a.  */
+
+static hbool zeroinit; /* False, stored as (char)0x5a.  */
+auto hbool uninit;     /* Undefined, may trap.  */
+@end smallexample
+
+When zero-initializing a variable or field of hardened boolean type
+(presumably held in static storage) the implied zero initializer gets
+converted to @code{_Bool}, and then to the hardened boolean type, so
+that the initial value is the hardened representation for @code{false}.
+Using that value is well defined.  This is @emph{not} the case when
+variables and fields of such types are uninitialized (presumably held in
+automatic or dynamic storage): their values are indeterminate, and using
+them invokes undefined behavior.  Using them may trap or not, depending
+on the bits held in the storage (re)used for the variable, if any, and
+on optimizations the compiler may perform on the grounds that using
+uninitialized values invokes undefined behavior.
+
+Users of @option{-ftrivial-auto-var-init} should be aware that the bit
+patterns used as its trivial initializers are @emph{not} converted to
+@code{hardbool} types, so using variables implicitly initialized by it
+may trap if the representations values chosen for @code{false} and
+@code{true} do not match the initializer.
+
+Since this is a language extension only available in C, interoperation
+with other languages may pose difficulties.  It should interoperate with
+Ada Booleans defined with the same size and equivalent representation
+clauses, and with enumerations or other languages' integral types that
+correspond to C's chosen integral type.
+
+
 @cindex @code{may_alias} type attribute
 @item may_alias
 Accesses through pointers to types with this attribute are not subject
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 23528b4cfaac2..a4dc4e3ed9f8a 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -13508,6 +13508,27 @@ The values used for pattern initialization might be changed in the future.
 
 The default is @samp{uninitialized}.
 
+Note that the initializer values, whether @samp{zero} or @samp{pattern},
+refer to data representation (in memory or machine registers), rather
+than to their interpretation as numerical values.  This distinction may
+be important in languages that support types with biases or implicit
+multipliers, and with such extensions as @samp{hardbool} (@pxref{Type
+Attributes}).  For example, a variable that uses 8 bits to represent
+(biased) quantities in the @code{range 160..400} will be initialized
+with the bit patterns @code{0x00} or @code{0xFE}, depending on
+@var{choice}, whether or not these representations stand for values in
+that range, and even if they do, the interpretation of the value held by
+the variable will depend on the bias.  A @samp{hardbool} variable that
+uses say @code{0X5A} and @code{0xA5} for @code{false} and @code{true},
+respectively, will trap with either @samp{choice} of trivial
+initializer, i.e., @samp{zero} initialization will not convert to the
+representation for @code{false}, even if it would for a @code{static}
+variable of the same type.  This means the initializer pattern doesn't
+generally depend on the type of the initialized variable.  One notable
+exception is that (non-hardened) boolean variables that fit in registers
+are initialized with @code{false} (zero), even when @samp{pattern} is
+requested.
+
 You can control this behavior for a specific variable by using the variable
 attribute @code{uninitialized} (@pxref{Variable Attributes}).
 
diff --git a/gcc/testsuite/gcc.dg/hardbool-err.c b/gcc/testsuite/gcc.dg/hardbool-err.c
new file mode 100644
index 0000000000000..e5bf58e55de62
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/hardbool-err.c
@@ -0,0 +1,31 @@
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+typedef _Bool __attribute__ ((__hardbool__))
+hbbl; /* { dg-error "integral types" } */
+
+typedef double __attribute__ ((__hardbool__))
+hbdbl; /* { dg-error "integral types" } */
+
+typedef _Complex int __attribute__ ((__hardbool__))
+hbcplx; /* { dg-error "integral types" } */
+
+enum x;
+typedef enum x __attribute__ ((__hardbool__))
+hbenum; /* { dg-error "integral types" } */
+
+struct s;
+typedef struct s __attribute__ ((__hardbool__))
+hbstruct; /* { dg-error "integral types" } */
+
+typedef int __attribute__ ((__hardbool__ (0, 0)))
+hb00; /* { dg-error "different values" } */
+
+typedef int __attribute__ ((__hardbool__ (4, 16))) hb4x;
+struct s {
+  hb4x m:2;
+}; /* { dg-error "is a GCC extension|different values" } */
+/* { dg-warning "changes value" "warning" { target *-*-* } .-1 } */
+
+hb4x __attribute__ ((vector_size (4 * sizeof (hb4x))))
+vvar; /* { dg-error "invalid vector type" } */
diff --git a/gcc/testsuite/gcc.dg/hardbool-trap.c b/gcc/testsuite/gcc.dg/hardbool-trap.c
new file mode 100644
index 0000000000000..2eebd0ef64fff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/hardbool-trap.c
@@ -0,0 +1,13 @@
+/* { dg-do compile } */
+/* { dg-options "-fdump-tree-optimized" } */
+
+typedef char __attribute__ ((__hardbool__ (1))) hbool;
+
+hbool var;
+
+int main () {
+  __builtin_memset (&var, 0, sizeof (var));
+  (void)var;
+}
+
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
new file mode 100644
index 0000000000000..a03887cfbecc5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5a
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
new file mode 100644
index 0000000000000..c0ba2a8b9148e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-i.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i.c b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
new file mode 100644
index 0000000000000..39214d28c5627
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype int
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
new file mode 100644
index 0000000000000..14438c5104f07
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x781ee187a53cc35all
+
+#include "hardbool-ll.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
new file mode 100644
index 0000000000000..d4d498c6f2af1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype long long
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
new file mode 100644
index 0000000000000..e38a56b5deb05
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5aa5
+
+#include "hardbool-s.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s.c b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
new file mode 100644
index 0000000000000..942300be2072a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype short
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
new file mode 100644
index 0000000000000..7beec578ff89c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-ul.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
new file mode 100644
index 0000000000000..841c1d4bc2ec8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype unsigned long
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
new file mode 100644
index 0000000000000..5bfc922795d3d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
@@ -0,0 +1,6 @@
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa55a
+
+#include "hardbool-us.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us.c b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
new file mode 100644
index 0000000000000..e9feec681c41e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
@@ -0,0 +1,5 @@
+/* { dg-do run } */
+
+#define basetype unsigned short
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool.c b/gcc/testsuite/gcc.dg/torture/hardbool.c
new file mode 100644
index 0000000000000..01684952a2a9f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool.c
@@ -0,0 +1,118 @@
+/* { dg-do run } */
+
+#include <assert.h>
+
+#ifndef basetype
+#define basetype char
+#endif
+
+#ifndef falseval
+#define falseval 0
+#endif
+
+#ifndef trueval
+#define trueval ~falseval
+#endif
+
+/* hardbool may be #defined so as to drop parms in other tests.  */
+typedef basetype __attribute__ ((hardbool (falseval, trueval))) hbool;
+
+typedef unsigned char __attribute__ ((__hardbool__ (1, 0))) zbool;
+
+struct hs {
+  hbool a[2];
+  hbool x:2;
+  hbool y:5;
+  zbool z:1;
+};
+
+hbool var = 0;
+
+struct hs x = { { 1, 0 }, 2, 0, 2 };
+
+int f(hbool v) {
+  return !v;
+}
+
+int g(int i) {
+  return f(i);
+}
+
+hbool h(hbool x) {
+  return x;
+}
+
+hbool h2(hbool x) {
+  return h(x);
+}
+
+int hsx(struct hs v) {
+  return v.x;
+}
+
+int ghs(hbool s) {
+  struct hs v = { {s, !s}, s, !s, s };
+  return hsx (v);
+}
+
+int t = (hbool)2;
+
+void check_pfalse (hbool *p)
+{
+  assert (!*p);
+  assert (*(basetype*)p == (basetype)falseval);
+  assert (!(int)(hbool)*p);
+}
+
+void check_ptrue (hbool *p)
+{
+  assert (*p);
+  assert (*(basetype*)p == (basetype)trueval);
+  assert ((int)(hbool)*p);
+}
+
+void check_vfalse (hbool v)
+{
+  check_pfalse (&v);
+}
+
+void check_vtrue (hbool v)
+{
+  check_ptrue (&v);
+}
+
+int main () {
+  check_pfalse (&var);
+  var = !(int)(hbool)(_Bool)var;
+  check_ptrue (&var);
+  var = (zbool)var;
+  check_ptrue (&var);
+
+  check_ptrue (&x.a[0]);
+  check_pfalse (&x.a[1]);
+  check_vtrue (x.x);
+  check_vfalse (x.y);
+  check_vtrue (x.z);
+
+  check_vtrue (t);
+
+  check_vtrue (var && t);
+  check_vfalse (!var || x.y);
+
+  check_vfalse (f (2));
+  check_vfalse (f (1));
+  check_vtrue (f (0));
+
+  check_vfalse (g (2));
+  check_vfalse (g (1));
+  check_vtrue (g (0));
+
+  check_vtrue (h (2));
+  check_vtrue (h (1));
+  check_vfalse (h (0));
+
+  check_vtrue (h2 (2));
+  check_vtrue (h2 (1));
+  check_vfalse (h2 (0));
+}
+
  
Qing Zhao June 26, 2023, 7:05 p.m. UTC | #19
Hi, Alexandre,

> On Jun 23, 2023, at 10:38 PM, Alexandre Oliva <oliva@adacore.com> wrote:
> 
>> For normal Boolean variables, 0x00 is false, this is a reasonable init
>> value with zero-initialization.
> 
> *nod*.  I was surprised by zero initialization of (non-hardened)
> booleans even when pattern is requested, but not consistently
> (e.g. boolean fields of a larger struct would still get
> pattern-initialized IIUC).  I'd have expected pattern would translate to
> nonzero and thus to true, rather than false.

Such inconsistent behavior was introduced by the following commit:

From c081d0a3b0291297f04a05c833d2ffa8de3a7a1a Mon Sep 17 00:00:00 2001
Subject: [PATCH] middle-end/103033 - drop native_interpret_expr with
 .DEFERRED_INIT expansion

> 
>> For hardbool variables, what 0x00 represents if it’s not false or true
>> value?
> 
> It depends on how hardbool is parameterized.  One may pick 0x00 or 0xFE
> as the representations for true or false, or neither, in which case the
> trivial initializer will end up as a trapping value.

Okay, then, this looks like a  good behavior (trapping with -ftrival-auto-var-init  most of the time,
 i.e, when neither 0x00 or 0xFE was chosen as the representations for true or false), 
it will urge the user to fix the uninitialized hardbool variables.
Do I miss anything here?
> 
>>> I'd probably have arranged for the front-end to create the initializer
>>> value, because expansion time is too late to figure it out: we may not
>>> even have the front-end at hand any more, in case of lto compilation.
> 
>> Is the hardbool attribute information available during the rtl expansion phase?
> 
> It is in the sense that the attribute lives on, but c_hardbool_type_attr
> is a frontend function, it cannot be called from e.g. lto1.
does lookup_attribute work for this attribute during rtl expansion? (Still a little confusing here)
> 
> The hardbool attribute is also implemented in Ada, but there it only
> affects validity checking in the front end: Boolean types in Ada are
> Enumeration types, and there is standard syntax to specify the
> representations for true and false.  AFAICT, once we translate GNAT IR
> to GNU IR, hardened booleans would not be recognizable as boolean types.
> Even non-hardened booleans with representation clauses would.

So, right now, the GNU IR represents Ada’s boolean type as enumeration type? 

>  So
> handling these differently from other enumeration types, to make them
> closer to booleans, would be a bit of a challenge,

is there any special handling in GNU IR when representing Ada’s boolean type as enumeration type?
Any issue right now?

> and a
> backwards-compatibility issue (because such booleans have already been
> handled in the present way since the introduction of -ftrivial-* back in
> GCC12)

With the new hardbool attribute added for C, an original bool type becomes an enumeration type logically, 
But such information is not passed to middle end through GNU IR, So, in GCC middle-end, 
We still treat such type as boolean, not an enumeration type.
Is this understanding correct?

> 
>>> Now, I acknowledge that the decision to make implicit
>>> zero-initialization of boolean types set them to the value for false,
>>> rather than to all-bits-zero representation, is a departure from common
>>> practice of zero-initialization yielding logical zero.
> 
>> Dont’s quite understand the above, for normal boolean variables,
> 
> Sorry, I meant hardened boolean types.  This was WRT to the design
> decision that led to this bit in the documentation:
> 
> typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
> [...]
> static hbool zeroinit; /* False, stored as (char)0x5a.  */
> auto hbool uninit;     /* Undefined, may trap.  */
For the hardbool variable "uninit",  -ftrivial-auto-var-init=zero will initialize it to zero, and it will trap during runtime.
And at the same time, -ftrivial-auto-var-init=pattern will initialize it to 0xfe, and it will trap during runtime, too.

I think these are good behaviors, just need to be documented. 
> 
>> And this is a very reasonable initial value for Boolean variables,
> 
> Agreed.  The all-zeros bit pattern is not so great for booleans that use
> alternate representations, though, such as the following standard Ada:
> 
>   type MyBool is new Boolean;
>   for MyBool use (16#5a#, 16#a5#);
>   for MyBool'Size use 8;
> 
> or for biased variables such as:
> 
>  X : Integer range 254 .. 507;
>  for X'Size use 8; -- bits, so a biased representation is required.
> 
> Just to make things more interesting, I chose a range for X that causes
> the compiler to represent 0xfe as 0x00 in in the byte that holds X, but
> that places the 0xfe pattern just out of the range :-) So with
> -ftrivial-auto-var-init=zero, X = 254, whereas with
> -ftrivial-auto-var-init=pattern, it fails validity checking, and might
> come out as 508 if that's disabled.

 for the biased variable X,  it was initialized to 254 (the smallest valid value in the range) when -ftrivial-auto-var-init=zero, and
fails validity checking when -ftrivial-auto-var-init=pattern, both are GOOD and reasonable behaviors with -ftrivial-auto-var-init. 
> 
>> From my understanding, only with the introduction of “hardbool”
>> attribute, all-bits-zero will not be equal to the
>> logical false anymore. 
> 
> Ada booleans have long allowed nonzero representations for false.

Okay, I see. 
> 
> 
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 772209c1793e8..ae7867bb35696 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -8774,6 +8774,12 @@ on the bits held in the storage (re)used for the variable, if any, and
> on optimizations the compiler may perform on the grounds that using
> uninitialized values invokes undefined behavior.
> 
> +Users of @option{-ftrivial-auto-var-init} should be aware that the bit
> +patterns used as its trivial initializers are @emph{not} converted to
> +@code{hardbool} types, so using variables implicitly initialized by it
> +may trap if the representations values chosen for @code{false} and
> +@code{true} do not match the initializer.

How about the following:

> +Users of the @option{-ftrivial-auto-var-init} should be aware that the bit
> +patterns used as initializers are @emph{not} converted to
> +@code{hardbool} types, so using a @code{hardbool} variable  that is implicitly initialized
> +by  the @option{-ftrivial-auto-var-init} may trap if the representations values
> +chosen for @code{false} and @code{true} do not match the initializer.
> +
> 


> @cindex @code{may_alias} type attribute
> @item may_alias
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index 296a9c178b195..e21f468a9c8f3 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -13510,6 +13510,23 @@ The values used for pattern initialization might be changed in the future.
> 
> The default is @samp{uninitialized}.
> 
> +Note that the initializer values, whether @samp{zero} or @samp{pattern},
> +refer to data representation (in memory or machine registers), rather
> +than to their interpretation as numerical values.  This distinction may
> +be important in languages that support types with biases or implicit
> +multipliers, and with such extensions as @samp{hardbool} (@pxref{Type
> +Attributes}).  For example, a variable that uses 8 bits to represent
> +(biased) quantities in the @code{range 160..400} will be initialized
> +with the bit patterns @code{0x00} or @code{0xFE}, depending on
> +@var{choice}, whether or not these representations stand for values in
> +that range, and even if they do, the interpretation of the value held by
> +the variable will depend on the bias.  A @samp{hardbool} variable that
> +uses say @code{0X5A} and @code{0xA5} for @code{false} and @code{true},
> +respectively, will trap with either @samp{choice} of trivial
> +initializer, i.e., @samp{zero} initialization will not convert to the
> +representation for @code{false},


> even if it would for a @code{static}
> +variable of the same type.

A little confused about the above sentence: do you mean, if a hardbool variable is static (not auto), then the implicit initizlation to it by the compiler will be false? (Because static variable initialization is done by the FE)

But when a hardbool variable is auto, then the implicit initialization to it by the compiler (with -ftrivial-auto-var-init) will not be false?

Thanks.

Qing

> +
> You can control this behavior for a specific variable by using the variable
> attribute @code{uninitialized} (@pxref{Variable Attributes}).
> 
> 
> 
> -- 
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>   Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>
  
Alexandre Oliva June 28, 2023, 7:26 a.m. UTC | #20
On Jun 26, 2023, Qing Zhao <qing.zhao@oracle.com> wrote:

>>> For hardbool variables, what 0x00 represents if it’s not false or true
>>> value?
>> 
>> It depends on how hardbool is parameterized.  One may pick 0x00 or 0xFE
>> as the representations for true or false, or neither, in which case the
>> trivial initializer will end up as a trapping value.

> Okay, then, this looks like a good behavior (trapping with
> -ftrival-auto-var-init most of the time,
>  i.e, when neither 0x00 or 0xFE was chosen as the representations for true or false), 
> it will urge the user to fix the uninitialized hardbool variables.

Agreed

>>>> I'd probably have arranged for the front-end to create the initializer
>>>> value, because expansion time is too late to figure it out: we may not
>>>> even have the front-end at hand any more, in case of lto compilation.
>> 
>>> Is the hardbool attribute information available during the rtl expansion phase?
>> 
>> It is in the sense that the attribute lives on, but c_hardbool_type_attr
>> is a frontend function, it cannot be called from e.g. lto1.
> does lookup_attribute work for this attribute during rtl expansion?
> (Still a little confusing here)

Yes, the hardbool attribute would be there in C.
But not in Ada.

And that should be fine, because Ada hardbool is handled entirely in the
frontend, as are non-hardened booleans with representation clauses, that
become enumeration types without any distinguishing feature.

>> The hardbool attribute is also implemented in Ada, but there it only
>> affects validity checking in the front end: Boolean types in Ada are
>> Enumeration types, and there is standard syntax to specify the
>> representations for true and false.  AFAICT, once we translate GNAT IR
>> to GNU IR, hardened booleans would not be recognizable as boolean types.
>> Even non-hardened booleans with representation clauses would.

> So, right now, the GNU IR represents Ada’s boolean type as enumeration type? 

All Ada boolean types are defined by the language as enumeration types:

  There is a predefined enumeration type named Boolean, [declared in the
  visible part of package Standard].  It has the two enumeration
  literals False and True ordered with the relation False < True.  Any
  descendant of the predefined type Boolean is called a boolean type.

However, boolean types without representation clauses are mapped to the
language-independent boolean_type_node.  Those that do are mapped to
enumeration types.

>> So
>> handling these differently from other enumeration types, to make them
>> closer to booleans, would be a bit of a challenge,

> is there any special handling in GNU IR when representing Ada’s
> boolean type as enumeration type?
> Any issue right now?

Not that I'm aware of.  The front end takes care of converting between
non-boolean_type_node enumeration types and boolean_type_node as needed,
so that the GNU IR needs no extra information.

>> and a
>> backwards-compatibility issue (because such booleans have already been
>> handled in the present way since the introduction of -ftrivial-* back in
>> GCC12)

> With the new hardbool attribute added for C, an original bool type
> becomes an enumeration type logically,

There's no change to the original bool type.

Only hardbool types are represented as enumeration types in C.

In Ada, boolean types with representation clauses are still represented
as enumeration types, whether or not they're hardbool.

> But such information is not passed to middle end through GNU IR, So,
> in GCC middle-end, We still treat such type as boolean, not an
> enumeration type.

The middle-end doesn't know (and ATM cannot know) that those represented
as enumeration types are conceptually booleans, so they are treated as
enumeration types, not as booleans.

>> static hbool zeroinit; /* False, stored as (char)0x5a.  */
>> auto hbool uninit;     /* Undefined, may trap.  */

> For the hardbool variable "uninit", -ftrivial-auto-var-init=zero will
> initialize it to zero, and it will trap during runtime.
> And at the same time, -ftrivial-auto-var-init=pattern will initialize
> it to 0xfe, and it will trap during runtime, too.

> I think these are good behaviors, just need to be documented. 

You mean more than what's in the patch posted last week?

>> 
>>> And this is a very reasonable initial value for Boolean variables,
>> 
>> Agreed.  The all-zeros bit pattern is not so great for booleans that use
>> alternate representations, though, such as the following standard Ada:
>> 
>> type MyBool is new Boolean;
>> for MyBool use (16#5a#, 16#a5#);
>> for MyBool'Size use 8;
>> 
>> or for biased variables such as:
>> 
>> X : Integer range 254 .. 507;
>> for X'Size use 8; -- bits, so a biased representation is required.
>> 
>> Just to make things more interesting, I chose a range for X that causes
>> the compiler to represent 0xfe as 0x00 in in the byte that holds X, but
>> that places the 0xfe pattern just out of the range :-) So with
>> -ftrivial-auto-var-init=zero, X = 254, whereas with
>> -ftrivial-auto-var-init=pattern, it fails validity checking, and might
>> come out as 508 if that's disabled.

>  for the biased variable X, it was initialized to 254 (the smallest
> valid value in the range) when -ftrivial-auto-var-init=zero, and
> fails validity checking when -ftrivial-auto-var-init=pattern, both are
> GOOD and reasonable behaviors with -ftrivial-auto-var-init.

*nod*.  However, I don't think this specific biased representation is
mandated by the language (AFAICT it doesn't even mandate support for
biased representations, but it explicitly allows them).  This means it
could conceivably map the all-zeros pattern to a trapping value, and
0xfe could stand for 507, or 506, or even other values, depending on the
bias.

> How about the following:

>> +Users of the @option{-ftrivial-auto-var-init} should be aware that the bit
>> +patterns used as initializers are @emph{not} converted to
>> +@code{hardbool} types, so using a @code{hardbool} variable that is
>> implicitly initialized
>> +by  the @option{-ftrivial-auto-var-init} may trap if the representations values
>> +chosen for @code{false} and @code{true} do not match the initializer.

That works for me.

>> even if it would for a @code{static}
>> +variable of the same type.

> A little confused about the above sentence: do you mean, if a hardbool
> variable is static (not auto), then the implicit initizlation to it by
> the compiler will be false? (Because static variable initialization is
> done by the FE)

Yes, exactly, that's what's implemented and documented with the example
"static hbool zeroinit" above.

> But when a hardbool variable is auto, then the implicit initialization
> to it by the compiler (with -ftrivial-auto-var-init) will not be
> false?

Yeah, it can't be counted on being false.  It can be a malformed value.

This is also the case of e.g. Integer ranges:

  X : Integer range 17..42;

  type E is (Red, Green, Blue);
  for E use (16#00f#, 16#0f0#, 16#f00#);  
  Y : E;

-ftrivial-auto-var-init will initialize X and Y with values that are not
acceptable for those variables.  And, as you say, that's a good thing.
  
Qing Zhao June 28, 2023, 3:07 p.m. UTC | #21
> On Jun 28, 2023, at 3:26 AM, Alexandre Oliva <oliva@adacore.com> wrote:
> 
>>>>> I'd probably have arranged for the front-end to create the initializer
>>>>> value, because expansion time is too late to figure it out: we may not
>>>>> even have the front-end at hand any more, in case of lto compilation.
>>> 
>>>> Is the hardbool attribute information available during the rtl expansion phase?
>>> 
>>> It is in the sense that the attribute lives on, but c_hardbool_type_attr
>>> is a frontend function, it cannot be called from e.g. lto1.
>> does lookup_attribute work for this attribute during rtl expansion?
>> (Still a little confusing here)
> 
> Yes, the hardbool attribute would be there in C.
> But not in Ada.
> 
> And that should be fine, because Ada hardbool is handled entirely in the
> frontend, as are non-hardened booleans with representation clauses, that
> become enumeration types without any distinguishing feature.
> 
>>> The hardbool attribute is also implemented in Ada, but there it only
>>> affects validity checking in the front end: Boolean types in Ada are
>>> Enumeration types, and there is standard syntax to specify the
>>> representations for true and false.  AFAICT, once we translate GNAT IR
>>> to GNU IR, hardened booleans would not be recognizable as boolean types.
>>> Even non-hardened booleans with representation clauses would.
> 
>> So, right now, the GNU IR represents Ada’s boolean type as enumeration type? 
> 
> All Ada boolean types are defined by the language as enumeration types:
> 
>  There is a predefined enumeration type named Boolean, [declared in the
>  visible part of package Standard].  It has the two enumeration
>  literals False and True ordered with the relation False < True.  Any
>  descendant of the predefined type Boolean is called a boolean type.
> 
> However, boolean types without representation clauses are mapped to the
> language-independent boolean_type_node.  Those that do are mapped to
> enumeration types.

>>> So
>>> handling these differently from other enumeration types, to make them
>>> closer to booleans, would be a bit of a challenge,
> 
>> is there any special handling in GNU IR when representing Ada’s
>> boolean type as enumeration type?
>> Any issue right now?
> 
> Not that I'm aware of.  The front end takes care of converting between
> non-boolean_type_node enumeration types and boolean_type_node as needed,
> so that the GNU IR needs no extra information.

In summary, Ada’s Boolean variables (whether it’s hardened or not) are represented as
enumeration types in GNU IR. FE takes care of the converting between 
non-boolean_type_node enumeration types and boolean_type_node as needed, 
no special handling in Middle end. 

So, is this exactly the same situation as the new hardbool attribute for C being implemented in 
this patch?

(Another question, for Ada’s Boolean variables, does the ada FE also insert BUILT_IN_TRAP when
  The value is neither true_value nor false_value?)

>>> and a
>>> backwards-compatibility issue (because such booleans have already been
>>> handled in the present way since the introduction of -ftrivial-* back in
>>> GCC12)
> 
>> With the new hardbool attribute added for C, an original bool type
>> becomes an enumeration type logically,
> 
> There's no change to the original bool type.
> 
> Only hardbool types are represented as enumeration types in C.
> 
> In Ada, boolean types with representation clauses are still represented
> as enumeration types, whether or not they're hardbool.
> 
>> But such information is not passed to middle end through GNU IR, So,
>> in GCC middle-end, We still treat such type as boolean, not an
>> enumeration type.
> 
> The middle-end doesn't know (and ATM cannot know) that those represented
> as enumeration types are conceptually booleans, so they are treated as
> enumeration types, not as booleans.
They should know it’s a boolean if using the lookup_attribute to get the attribute info -:)
> 
>>> static hbool zeroinit; /* False, stored as (char)0x5a.  */
>>> auto hbool uninit;     /* Undefined, may trap.  */
> 
>> For the hardbool variable "uninit", -ftrivial-auto-var-init=zero will
>> initialize it to zero, and it will trap during runtime.
>> And at the same time, -ftrivial-auto-var-init=pattern will initialize
>> it to 0xfe, and it will trap during runtime, too.
> 
>> I think these are good behaviors, just need to be documented. 
> 
> You mean more than what's in the patch posted last week?
No, the updated doc is good I think.

> 
>>> 
>>>> And this is a very reasonable initial value for Boolean variables,
>>> 
>>> Agreed.  The all-zeros bit pattern is not so great for booleans that use
>>> alternate representations, though, such as the following standard Ada:
>>> 
>>> type MyBool is new Boolean;
>>> for MyBool use (16#5a#, 16#a5#);
>>> for MyBool'Size use 8;
>>> 
>>> or for biased variables such as:
>>> 
>>> X : Integer range 254 .. 507;
>>> for X'Size use 8; -- bits, so a biased representation is required.
>>> 
>>> Just to make things more interesting, I chose a range for X that causes
>>> the compiler to represent 0xfe as 0x00 in in the byte that holds X, but
>>> that places the 0xfe pattern just out of the range :-) So with
>>> -ftrivial-auto-var-init=zero, X = 254, whereas with
>>> -ftrivial-auto-var-init=pattern, it fails validity checking, and might
>>> come out as 508 if that's disabled.
> 
>> for the biased variable X, it was initialized to 254 (the smallest
>> valid value in the range) when -ftrivial-auto-var-init=zero, and
>> fails validity checking when -ftrivial-auto-var-init=pattern, both are
>> GOOD and reasonable behaviors with -ftrivial-auto-var-init.
> 
> *nod*.  However, I don't think this specific biased representation is
> mandated by the language (AFAICT it doesn't even mandate support for
> biased representations, but it explicitly allows them).  This means it
> could conceivably map the all-zeros pattern to a trapping value, and
> 0xfe could stand for 507, or 506, or even other values, depending on the
> bias.
Yes.
> 
>> How about the following:
> 
>>> +Users of the @option{-ftrivial-auto-var-init} should be aware that the bit
>>> +patterns used as initializers are @emph{not} converted to
>>> +@code{hardbool} types, so using a @code{hardbool} variable that is
>>> implicitly initialized
>>> +by  the @option{-ftrivial-auto-var-init} may trap if the representations values
>>> +chosen for @code{false} and @code{true} do not match the initializer.
> 
> That works for me.
> 
>>> even if it would for a @code{static}
>>> +variable of the same type.
> 
>> A little confused about the above sentence: do you mean, if a hardbool
>> variable is static (not auto), then the implicit initizlation to it by
>> the compiler will be false? (Because static variable initialization is
>> done by the FE)
> 
> Yes, exactly, that's what's implemented and documented with the example
> "static hbool zeroinit" above.
> 
>> But when a hardbool variable is auto, then the implicit initialization
>> to it by the compiler (with -ftrivial-auto-var-init) will not be
>> false?
> 
> Yeah, it can't be counted on being false.  It can be a malformed value.
> 
> This is also the case of e.g. Integer ranges:
> 
>  X : Integer range 17..42;
> 
>  type E is (Red, Green, Blue);
>  for E use (16#00f#, 16#0f0#, 16#f00#);  
>  Y : E;
> 
> -ftrivial-auto-var-init will initialize X and Y with values that are not
> acceptable for those variables.  And, as you say, that's a good thing.

So, from my current understanding, a summary on my major concern and the possible solution to this concern:

1.  There is one inconsistent issue for the new hardbool attribute for C:

     If the hardbool variable is implicitly initialized by  FE, i.e, when it is a static variable, the initialized value is the logical false value;
At the same time,
     If the hardbool variable is implicitly initialized by middle end, i.e, when it is an auto variable without initialization + -ftrivial-auto-var-init,  the initialized value is a bitwise zero or 0xFE;

2.   Such inconsistency has been there for Ada’s Boolean variables and also  types with biases already, not a new issue. 

3.  Possible solution to this existing inconsistency issue:

     A. Document such inconsistency as we discussed;
     B. Fixing such inconsistency. 

Currently, we are taking the solution A. I am okay with it. 
Is it necessary to fix such inconsistency?  I am not the right person to make the suggestion at this moment.

Is the above understanding correct?

Thanks.

Qing
> 
> -- 
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>   Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>
  
Alexandre Oliva June 29, 2023, 10:30 a.m. UTC | #22
On Jun 28, 2023, Qing Zhao <qing.zhao@oracle.com> wrote:

> In summary, Ada’s Boolean variables (whether it’s hardened or not) are
> represented as
> enumeration types in GNU IR.

Not quite.  Only boolean types with representation clauses are.  Those
without (such as Standard.Boolean) are BOOLEAN_TYPEs.  But those without
a representation clause are not so relevant and could be disregarded,
for purposes of this conversation.

> FE takes care of the converting between non-boolean_type_node
> enumeration types and boolean_type_node as needed, no special handling
> in Middle end.

> So, is this exactly the same situation as the new hardbool attribute
> for C being implemented in this patch?

That's correct.

> (Another question, for Ada’s Boolean variables, does the ada FE also
> insert BUILT_IN_TRAP when
>   The value is neither true_value nor false_value?)

Ada raises exceptions when validity checking fails; such as upon using a
boolean variable with a representation clause holds a value that is
neither true nor false.

>> The middle-end doesn't know (and ATM cannot know) that those represented
>> as enumeration types are conceptually booleans, so they are treated as
>> enumeration types, not as booleans.

> They should know it’s a boolean if using the lookup_attribute to get
> the attribute info -:)

I meant boolean types that have a representation clause but are not
hardbools.  Those don't have any attribute whatsoever.

>> You mean more than what's in the patch posted last week?
> No, the updated doc is good I think.

Great, thanks

> So, from my current understanding, a summary on my major concern and
> the possible solution to this concern:

That was a good summary.

> Is it necessary to fix such inconsistency?

I don't think it is even desirable.

Initialization of static variables is well-defined, one is allowed to
count on a specific value after initialization, and we have that
covered.

Automatic variables, OTOH, when not explicitly initialized, may hold
undefined, random, even malformed values.  Picking an initializer to
make them predictable needs not follow the semantics of zero
initialization for static variables.  =pattern makes it clear that using
something other than zero initialization is useful to catch errors.  The
Ada language standard even suggests that compilers may set uninitialized
variables to out-of-range values so as to catch this sort of error.  So,
though it might seem desirable, for symmetry, to have automatic
variables implicitly initialized similarly to static variables, it's not
clear that doing so serves a useful purpose, at least for such types as
hardened booleans, that are *intended* to catch malformed values.
  
Qing Zhao June 29, 2023, 3:21 p.m. UTC | #23
Hi, ALexandre,

Thank you for the explanation.
I am now clear with the interaction between hardbool and -ftrivial-auto-var-init, and also agree
that clarifying the documentation on their interaction is good enough. 

Qing

> On Jun 29, 2023, at 6:30 AM, Alexandre Oliva <oliva@adacore.com> wrote:
> 
> On Jun 28, 2023, Qing Zhao <qing.zhao@oracle.com> wrote:
> 
>> In summary, Ada’s Boolean variables (whether it’s hardened or not) are
>> represented as
>> enumeration types in GNU IR.
> 
> Not quite.  Only boolean types with representation clauses are.  Those
> without (such as Standard.Boolean) are BOOLEAN_TYPEs.  But those without
> a representation clause are not so relevant and could be disregarded,
> for purposes of this conversation.
> 
>> FE takes care of the converting between non-boolean_type_node
>> enumeration types and boolean_type_node as needed, no special handling
>> in Middle end.
> 
>> So, is this exactly the same situation as the new hardbool attribute
>> for C being implemented in this patch?
> 
> That's correct.
> 
>> (Another question, for Ada’s Boolean variables, does the ada FE also
>> insert BUILT_IN_TRAP when
>>  The value is neither true_value nor false_value?)
> 
> Ada raises exceptions when validity checking fails; such as upon using a
> boolean variable with a representation clause holds a value that is
> neither true nor false.
> 
>>> The middle-end doesn't know (and ATM cannot know) that those represented
>>> as enumeration types are conceptually booleans, so they are treated as
>>> enumeration types, not as booleans.
> 
>> They should know it’s a boolean if using the lookup_attribute to get
>> the attribute info -:)
> 
> I meant boolean types that have a representation clause but are not
> hardbools.  Those don't have any attribute whatsoever.
> 
>>> You mean more than what's in the patch posted last week?
>> No, the updated doc is good I think.
> 
> Great, thanks
> 
>> So, from my current understanding, a summary on my major concern and
>> the possible solution to this concern:
> 
> That was a good summary.
> 
>> Is it necessary to fix such inconsistency?
> 
> I don't think it is even desirable.
> 
> Initialization of static variables is well-defined, one is allowed to
> count on a specific value after initialization, and we have that
> covered.
> 
> Automatic variables, OTOH, when not explicitly initialized, may hold
> undefined, random, even malformed values.  Picking an initializer to
> make them predictable needs not follow the semantics of zero
> initialization for static variables.  =pattern makes it clear that using
> something other than zero initialization is useful to catch errors.  The
> Ada language standard even suggests that compilers may set uninitialized
> variables to out-of-range values so as to catch this sort of error.  So,
> though it might seem desirable, for symmetry, to have automatic
> variables implicitly initialized similarly to static variables, it's not
> clear that doing so serves a useful purpose, at least for such types as
> hardened booleans, that are *intended* to catch malformed values.
> 
> -- 
> Alexandre Oliva, happy hacker                https://FSFLA.org/blogs/lxo/
>   Free Software Activist                       GNU Toolchain Engineer
> Disinformation flourishes because many people care deeply about injustice
> but very few check the facts.  Ask me about <https://stallmansupport.org>
  

Patch

diff --git a/gcc/c-family/c-attribs.cc b/gcc/c-family/c-attribs.cc
index c8d96723f4c30..e385d780c49ce 100644
--- a/gcc/c-family/c-attribs.cc
+++ b/gcc/c-family/c-attribs.cc
@@ -172,6 +172,7 @@  static tree handle_objc_root_class_attribute (tree *, tree, tree, int, bool *);
 static tree handle_objc_nullability_attribute (tree *, tree, tree, int, bool *);
 static tree handle_signed_bool_precision_attribute (tree *, tree, tree, int,
 						    bool *);
+static tree handle_hardbool_attribute (tree *, tree, tree, int, bool *);
 static tree handle_retain_attribute (tree *, tree, tree, int, bool *);
 
 /* Helper to define attribute exclusions.  */
@@ -288,6 +289,8 @@  const struct attribute_spec c_common_attribute_table[] =
        affects_type_identity, handler, exclude } */
   { "signed_bool_precision",  1, 1, false, true, false, true,
 			      handle_signed_bool_precision_attribute, NULL },
+  { "hardbool",               0, 2, false, true, false, true,
+			      handle_hardbool_attribute, NULL },
   { "packed",                 0, 0, false, false, false, false,
 			      handle_packed_attribute,
 	                      attr_aligned_exclusions },
@@ -975,6 +978,95 @@  handle_signed_bool_precision_attribute (tree *node, tree name, tree args,
   return NULL_TREE;
 }
 
+/* Handle a "hardbool" attribute; arguments as in struct
+   attribute_spec.handler.  */
+
+static tree
+handle_hardbool_attribute (tree *node, tree name, tree args,
+			   int /* flags */, bool *no_add_attrs)
+{
+  if (c_language != clk_c)
+    {
+      error ("%qE attribute only supported in C", name);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  if (!TYPE_P (*node) || TREE_CODE (*node) != INTEGER_TYPE)
+    {
+      error ("%qE attribute only supported on "
+	     "integral types", name);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  tree orig = *node;
+  *node = build_duplicate_type (orig);
+
+  TREE_SET_CODE (*node, ENUMERAL_TYPE);
+
+  tree false_value;
+  if (args)
+    false_value = fold_convert (*node, TREE_VALUE (args));
+  else
+    false_value = fold_convert (*node, integer_zero_node);
+
+  if (TREE_OVERFLOW_P (false_value))
+    {
+      warning (OPT_Wattributes,
+	       "overflows in conversion from %qT to %qT "
+	       "changes value from %qE to %qE",
+	       TREE_TYPE (TREE_VALUE (args)), *node,
+	       TREE_VALUE (args), false_value);
+      TREE_OVERFLOW (false_value) = false;
+    }
+
+  tree true_value;
+  if (args && TREE_CHAIN (args))
+    true_value = fold_convert (*node, TREE_VALUE (TREE_CHAIN (args)));
+  else
+    true_value = fold_build1 (BIT_NOT_EXPR, *node, false_value);
+
+  if (TREE_OVERFLOW_P (true_value))
+    {
+      warning (OPT_Wattributes,
+	       "overflows in conversion from %qT to %qT "
+	       "changes value from %qE to %qE",
+	       TREE_TYPE (TREE_VALUE (TREE_CHAIN (args))), *node,
+	       TREE_VALUE (TREE_CHAIN (args)), true_value);
+      TREE_OVERFLOW (true_value) = false;
+    }
+
+  if (tree_int_cst_compare (false_value, true_value) == 0)
+    {
+      error ("%qE attribute requires different values for"
+	     " %<false%> and %<true%> for type %qT",
+	     name, *node);
+      *no_add_attrs = TRUE;
+      return NULL_TREE;
+    }
+
+  tree values = build_tree_list (get_identifier ("false"),
+				 false_value);
+  TREE_CHAIN (values) = build_tree_list (get_identifier ("true"),
+					 true_value);
+
+  /* Do *not* set TYPE_MIN_VALUE, TYPE_MAX_VALUE, nor TYPE_PRECISION according
+     to the false and true values.  That might cause the constants to be the
+     only acceptable values, which would drop the very hardening checks this
+     attribute is supposed to add.  */
+
+  TYPE_ATTRIBUTES (*node) = tree_cons (name, args,
+				       TYPE_ATTRIBUTES (*node));
+  *no_add_attrs = TRUE;
+
+  gcc_checking_assert (!TYPE_CACHED_VALUES_P (*node));
+  TYPE_VALUES (*node) = values;
+  TYPE_NAME (*node) = orig;
+
+  return NULL_TREE;
+}
+
 /* Handle a "packed" attribute; arguments as in
    struct attribute_spec.handler.  */
 
@@ -4289,7 +4381,8 @@  static tree
 type_valid_for_vector_size (tree type, tree atname, tree args,
 			    unsigned HOST_WIDE_INT *ptrnunits)
 {
-  bool error_p = ptrnunits != NULL;
+  bool hardbool_p = c_hardbool_type_attr (type);
+  bool error_p = ptrnunits != NULL || hardbool_p;
 
   /* Get the mode of the type being modified.  */
   machine_mode orig_mode = TYPE_MODE (type);
@@ -4301,7 +4394,7 @@  type_valid_for_vector_size (tree type, tree atname, tree args,
 	  && GET_MODE_CLASS (orig_mode) != MODE_INT
 	  && !ALL_SCALAR_FIXED_POINT_MODE_P (orig_mode))
       || !tree_fits_uhwi_p (TYPE_SIZE_UNIT (type))
-      || TREE_CODE (type) == BOOLEAN_TYPE)
+      || TREE_CODE (type) == BOOLEAN_TYPE || hardbool_p)
     {
       if (error_p)
 	error ("invalid vector type for attribute %qE", atname);
diff --git a/gcc/c-family/c-common.cc b/gcc/c-family/c-common.cc
index 1b8e73f7bc5d8..9a8584a5fe095 100644
--- a/gcc/c-family/c-common.cc
+++ b/gcc/c-family/c-common.cc
@@ -1780,7 +1780,8 @@  convert_and_check (location_t loc, tree type, tree expr, bool init_const)
 
   if (c_inhibit_evaluation_warnings == 0
       && !TREE_OVERFLOW_P (expr)
-      && result != error_mark_node)
+      && result != error_mark_node
+      && !c_hardbool_type_attr (type))
     warnings_for_convert_and_check (loc, type, expr_for_warning, result);
 
   return result;
@@ -9488,4 +9489,22 @@  c_common_finalize_early_debug (void)
       (*debug_hooks->early_global_decl) (cnode->decl);
 }
 
+/* This is the slow path of c-common.h's c_hardbool_type_attr.  */
+
+tree
+c_hardbool_type_attr_1 (tree type, tree *false_value, tree *true_value)
+{
+  tree attr = lookup_attribute ("hardbool", TYPE_ATTRIBUTES (type));
+  if (!attr)
+    return attr;
+
+  if (false_value)
+    *false_value = TREE_VALUE (TYPE_VALUES (type));
+
+  if (true_value)
+    *true_value = TREE_VALUE (TREE_CHAIN (TYPE_VALUES (type)));
+
+  return attr;
+}
+
 #include "gt-c-family-c-common.h"
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index c0900848965a3..a8d6691d0813d 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -912,6 +912,7 @@  extern tree c_common_get_narrower (tree, int *);
 extern bool get_attribute_operand (tree, unsigned HOST_WIDE_INT *);
 extern void c_common_finalize_early_debug (void);
 extern bool c_option_is_from_cpp_diagnostics (int);
+extern tree c_hardbool_type_attr_1 (tree, tree *, tree *);
 
 /* Used by convert_and_check; in front ends.  */
 extern tree convert_init (tree, tree);
@@ -1291,6 +1292,23 @@  c_tree_chain_next (tree t)
   return NULL;
 }
 
+/* Return the hardbool attribute associated with TYPE, if there is one, provided
+   that TYPE looks like an enumeral type that might have been set up by
+   handle_hardbool_attribute.  Return NULL otherwise.
+
+   If FALSE_VALUE or TRUE_VALUE are non-NULL and TYPE is a hardened boolean
+   type, store the corresponding representation values.  */
+static inline tree
+c_hardbool_type_attr (tree type,
+		      tree *false_value = NULL, tree *true_value = NULL)
+{
+  if (TREE_CODE (type) != ENUMERAL_TYPE
+      || TYPE_LANG_SPECIFIC (type))
+    return NULL_TREE;
+
+  return c_hardbool_type_attr_1 (type, false_value, true_value);
+}
+
 /* Mask used by tm_stmt_attr.  */
 #define TM_STMT_ATTR_OUTER	2
 #define TM_STMT_ATTR_ATOMIC	4
diff --git a/gcc/c/c-convert.cc b/gcc/c/c-convert.cc
index 18083d5961826..1399cbf9d8d37 100644
--- a/gcc/c/c-convert.cc
+++ b/gcc/c/c-convert.cc
@@ -105,6 +105,20 @@  c_convert (tree type, tree expr, bool init_const)
       return error_mark_node;
     }
 
+  {
+    tree false_value, true_value;
+    if (c_hardbool_type_attr (type, &false_value, &true_value))
+      {
+	bool save = in_late_binary_op;
+	in_late_binary_op = true;
+	expr = c_objc_common_truthvalue_conversion (input_location, expr);
+	in_late_binary_op = save;
+
+	return fold_build3_loc (loc, COND_EXPR, type,
+				expr, true_value, false_value);
+      }
+  }
+
   switch (code)
     {
     case VOID_TYPE:
diff --git a/gcc/c/c-decl.cc b/gcc/c/c-decl.cc
index ae8990c138fd2..e103582d677cd 100644
--- a/gcc/c/c-decl.cc
+++ b/gcc/c/c-decl.cc
@@ -6163,6 +6163,12 @@  check_bitfield_type_and_width (location_t loc, tree *type, tree *width,
   else
     w = tree_to_uhwi (*width);
 
+  /* Truncation of hardbool false and true representation values is always safe:
+     either the values remain different, or we'll report a problem when creating
+     the narrower type.  */
+  if (c_hardbool_type_attr (*type))
+    return;
+
   if (TREE_CODE (*type) == ENUMERAL_TYPE)
     {
       struct lang_type *lt = TYPE_LANG_SPECIFIC (*type);
@@ -8887,6 +8893,10 @@  finish_struct (location_t loc, tree t, tree fieldlist, tree attributes,
 	    {
 	      TREE_TYPE (field)
 		= c_build_bitfield_integer_type (width, TYPE_UNSIGNED (type));
+	      if (tree attr = c_hardbool_type_attr (type))
+		decl_attributes (&TREE_TYPE (field),
+				 copy_list (attr),
+				 0, NULL_TREE);
 	      SET_DECL_MODE (field, TYPE_MODE (TREE_TYPE (field)));
 	    }
 	  DECL_INITIAL (field) = NULL_TREE;
diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc
index fd0a7f81a7a92..1244241dd295b 100644
--- a/gcc/c/c-typeck.cc
+++ b/gcc/c/c-typeck.cc
@@ -2172,6 +2172,35 @@  convert_lvalue_to_rvalue (location_t loc, struct c_expr exp,
   if (convert_p && !error_operand_p (exp.value)
       && (TREE_CODE (TREE_TYPE (exp.value)) != ARRAY_TYPE))
     exp.value = convert (build_qualified_type (TREE_TYPE (exp.value), TYPE_UNQUALIFIED), exp.value);
+
+  {
+    tree false_value, true_value;
+    if (convert_p && !error_operand_p (exp.value)
+	&& c_hardbool_type_attr (TREE_TYPE (exp.value),
+				 &false_value, &true_value))
+      {
+	tree t = save_expr (exp.value);
+
+	mark_exp_read (exp.value);
+
+	tree trapfn = builtin_decl_explicit (BUILT_IN_TRAP);
+	tree expr = build_call_expr_loc (loc, trapfn, 0);
+	expr = build_compound_expr (loc, expr, boolean_true_node);
+	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
+				fold_build2_loc (loc, NE_EXPR,
+						 boolean_type_node,
+						 t, true_value),
+				expr, boolean_true_node);
+	expr = fold_build3_loc (loc, COND_EXPR, boolean_type_node,
+				fold_build2_loc (loc, NE_EXPR,
+						 boolean_type_node,
+						 t, false_value),
+				expr, boolean_false_node);
+
+	exp.value = expr;
+      }
+  }
+
   return exp;
 }
 
@@ -8167,7 +8196,7 @@  digest_init (location_t init_loc, tree type, tree init, tree origtype,
 	    }
 	}
 
-      if (code == VECTOR_TYPE)
+      if (code == VECTOR_TYPE || c_hardbool_type_attr (type))
 	/* Although the types are compatible, we may require a
 	   conversion.  */
 	inside_init = convert (type, inside_init);
diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index dfbe33ac652f8..7b514354263f4 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -8654,6 +8654,43 @@  initialization will result in future breakage.
 GCC emits warnings based on this attribute by default; use
 @option{-Wno-designated-init} to suppress them.
 
+@item hardbool
+@itemx hardbool (@var{false_value})
+@itemx hardbool (@var{false_value}, @var{true_value})
+@cindex @code{hardbool} type attribute
+This attribute may only be applied to integral types in C, to introduce
+hardened boolean types.  It turns the integral type into a boolean-like
+type with the same size and precision, that uses the specified values as
+representations for @code{false} and @code{true}.  Underneath, it is
+actually an enumerate type, but its observable behavior is like that of
+@code{_Bool}, except for the strict internal representations, verified
+by runtime checks.
+
+If @var{true_value} is omitted, the bitwise negation of
+@var{false_value} is used.  If @var{false_value} is omitted, zero is
+used.  The named representation values must be different when converted
+to the original integral type.  Narrower bitfields are rejected if the
+representations become indistinguishable.
+
+Values of such types automatically decay to @code{_Bool}, at which
+point, the selected representation values are mapped to the
+corresponding @code{_Bool} values.  When the represented value is not
+determined, at compile time, to be either @var{false_value} or
+@var{true_value}, runtime verification calls @code{__builtin_trap} if it
+is neither.  This is what makes them hardened boolean types.
+
+When converting scalar types to such hardened boolean types, implicitly
+or explicitly, behavior corresponds to a conversion to @code{_Bool},
+followed by a mapping from @code{false} and @code{true} to
+@var{false_value} and @var{true_value}, respectively.
+
+@smallexample
+typedef char __attribute__ ((__hardbool__ (0x5a))) hbool;
+hbool first = 0;       /* False, stored as (char)0x5a.  */
+hbool second = !first; /* True, stored as ~(char)0x5a.  */
+@end smallexample
+
+
 @item may_alias
 @cindex @code{may_alias} type attribute
 Accesses through pointers to types with this attribute are not subject
diff --git a/gcc/testsuite/gcc.dg/hardbool-err.c b/gcc/testsuite/gcc.dg/hardbool-err.c
new file mode 100644
index 0000000000000..634feaed4deef
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/hardbool-err.c
@@ -0,0 +1,28 @@ 
+/* { dg-do compile } */
+/* { dg-options "" } */
+
+typedef _Bool __attribute__ ((__hardbool__))
+hbbl; /* { dg-error "integral types" } */
+
+typedef double __attribute__ ((__hardbool__))
+hbdbl; /* { dg-error "integral types" } */
+
+enum x;
+typedef enum x __attribute__ ((__hardbool__))
+hbenum; /* { dg-error "integral types" } */
+
+struct s;
+typedef struct s __attribute__ ((__hardbool__))
+hbstruct; /* { dg-error "integral types" } */
+
+typedef int __attribute__ ((__hardbool__ (0, 0)))
+hb00; /* { dg-error "different values" } */
+
+typedef int __attribute__ ((__hardbool__ (4, 16))) hb4x;
+struct s {
+  hb4x m:2;
+}; /* { dg-error "is a GCC extension|different values" } */
+/* { dg-warning "changes value" "warning" { target *-*-* } .-1 } */
+
+hb4x __attribute__ ((vector_size (4 * sizeof (hb4x))))
+vvar; /* { dg-error "invalid vector type" } */
diff --git a/gcc/testsuite/gcc.dg/hardbool-trap.c b/gcc/testsuite/gcc.dg/hardbool-trap.c
new file mode 100644
index 0000000000000..2eebd0ef64fff
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/hardbool-trap.c
@@ -0,0 +1,13 @@ 
+/* { dg-do compile } */
+/* { dg-options "-fdump-tree-optimized" } */
+
+typedef char __attribute__ ((__hardbool__ (1))) hbool;
+
+hbool var;
+
+int main () {
+  __builtin_memset (&var, 0, sizeof (var));
+  (void)var;
+}
+
+/* { dg-final { scan-tree-dump-times "__builtin_trap" 1 "optimized" } } */
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
new file mode 100644
index 0000000000000..a03887cfbecc5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-5a.c
@@ -0,0 +1,6 @@ 
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5a
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
new file mode 100644
index 0000000000000..c0ba2a8b9148e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-i-5a.c
@@ -0,0 +1,6 @@ 
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-i.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-i.c b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
new file mode 100644
index 0000000000000..39214d28c5627
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-i.c
@@ -0,0 +1,5 @@ 
+/* { dg-do run } */
+
+#define basetype int
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
new file mode 100644
index 0000000000000..14438c5104f07
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll-5a.c
@@ -0,0 +1,6 @@ 
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x781ee187a53cc35all
+
+#include "hardbool-ll.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ll.c b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
new file mode 100644
index 0000000000000..d4d498c6f2af1
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ll.c
@@ -0,0 +1,5 @@ 
+/* { dg-do run } */
+
+#define basetype long long
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
new file mode 100644
index 0000000000000..e38a56b5deb05
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-s-5a.c
@@ -0,0 +1,6 @@ 
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5aa5
+
+#include "hardbool-s.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-s.c b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
new file mode 100644
index 0000000000000..942300be2072a
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-s.c
@@ -0,0 +1,5 @@ 
+/* { dg-do run } */
+
+#define basetype short
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
new file mode 100644
index 0000000000000..7beec578ff89c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul-5a.c
@@ -0,0 +1,6 @@ 
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-ul.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-ul.c b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
new file mode 100644
index 0000000000000..841c1d4bc2ec8
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-ul.c
@@ -0,0 +1,5 @@ 
+/* { dg-do run } */
+
+#define basetype unsigned long
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
new file mode 100644
index 0000000000000..5bfc922795d3d
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-us-5a.c
@@ -0,0 +1,6 @@ 
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa55a
+
+#include "hardbool-us.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool-us.c b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
new file mode 100644
index 0000000000000..e9feec681c41e
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool-us.c
@@ -0,0 +1,5 @@ 
+/* { dg-do run } */
+
+#define basetype unsigned short
+
+#include "hardbool.c"
diff --git a/gcc/testsuite/gcc.dg/torture/hardbool.c b/gcc/testsuite/gcc.dg/torture/hardbool.c
new file mode 100644
index 0000000000000..01684952a2a9f
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/torture/hardbool.c
@@ -0,0 +1,118 @@ 
+/* { dg-do run } */
+
+#include <assert.h>
+
+#ifndef basetype
+#define basetype char
+#endif
+
+#ifndef falseval
+#define falseval 0
+#endif
+
+#ifndef trueval
+#define trueval ~falseval
+#endif
+
+/* hardbool may be #defined so as to drop parms in other tests.  */
+typedef basetype __attribute__ ((hardbool (falseval, trueval))) hbool;
+
+typedef unsigned char __attribute__ ((__hardbool__ (1, 0))) zbool;
+
+struct hs {
+  hbool a[2];
+  hbool x:2;
+  hbool y:5;
+  zbool z:1;
+};
+
+hbool var = 0;
+
+struct hs x = { { 1, 0 }, 2, 0, 2 };
+
+int f(hbool v) {
+  return !v;
+}
+
+int g(int i) {
+  return f(i);
+}
+
+hbool h(hbool x) {
+  return x;
+}
+
+hbool h2(hbool x) {
+  return h(x);
+}
+
+int hsx(struct hs v) {
+  return v.x;
+}
+
+int ghs(hbool s) {
+  struct hs v = { {s, !s}, s, !s, s };
+  return hsx (v);
+}
+
+int t = (hbool)2;
+
+void check_pfalse (hbool *p)
+{
+  assert (!*p);
+  assert (*(basetype*)p == (basetype)falseval);
+  assert (!(int)(hbool)*p);
+}
+
+void check_ptrue (hbool *p)
+{
+  assert (*p);
+  assert (*(basetype*)p == (basetype)trueval);
+  assert ((int)(hbool)*p);
+}
+
+void check_vfalse (hbool v)
+{
+  check_pfalse (&v);
+}
+
+void check_vtrue (hbool v)
+{
+  check_ptrue (&v);
+}
+
+int main () {
+  check_pfalse (&var);
+  var = !(int)(hbool)(_Bool)var;
+  check_ptrue (&var);
+  var = (zbool)var;
+  check_ptrue (&var);
+
+  check_ptrue (&x.a[0]);
+  check_pfalse (&x.a[1]);
+  check_vtrue (x.x);
+  check_vfalse (x.y);
+  check_vtrue (x.z);
+
+  check_vtrue (t);
+
+  check_vtrue (var && t);
+  check_vfalse (!var || x.y);
+
+  check_vfalse (f (2));
+  check_vfalse (f (1));
+  check_vtrue (f (0));
+
+  check_vfalse (g (2));
+  check_vfalse (g (1));
+  check_vtrue (g (0));
+
+  check_vtrue (h (2));
+  check_vtrue (h (1));
+  check_vfalse (h (0));
+
+  check_vtrue (h2 (2));
+  check_vtrue (h2 (1));
+  check_vfalse (h2 (0));
+}
+