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 *);
static tree handle_null_terminated_string_arg_attribute (tree *, tree, tree, int, bool *);
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 },
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. */
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);
&& !ALL_SCALAR_FIXED_POINT_MODE_P (orig_mode))
|| !tree_fits_uhwi_p (TYPE_SIZE_UNIT (type))
|| TREE_CODE (type) == BOOLEAN_TYPE
+ || hardbool_p
|| TREE_CODE (type) == BITINT_TYPE)
{
if (error_p)
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;
return !strict_p || *feat_p;
}
+/* 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"
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);
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
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:
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);
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;
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;
}
}
}
- 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);
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 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.
+
+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
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}).
--- /dev/null
+/* { 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" } */
--- /dev/null
+/* { 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" } } */
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5a
+
+#include "hardbool.c"
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-i.c"
--- /dev/null
+/* { dg-do run } */
+
+#define basetype int
+
+#include "hardbool.c"
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x781ee187a53cc35all
+
+#include "hardbool-ll.c"
--- /dev/null
+/* { dg-do run } */
+
+#define basetype long long
+
+#include "hardbool.c"
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0x5aa5
+
+#include "hardbool-s.c"
--- /dev/null
+/* { dg-do run } */
+
+#define basetype short
+
+#include "hardbool.c"
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa53cc35a
+
+#include "hardbool-ul.c"
--- /dev/null
+/* { dg-do run } */
+
+#define basetype unsigned long
+
+#include "hardbool.c"
--- /dev/null
+/* { dg-do run } */
+/* { dg-options "-w" } */
+
+#define falseval 0xa55a
+
+#include "hardbool-us.c"
--- /dev/null
+/* { dg-do run } */
+
+#define basetype unsigned short
+
+#include "hardbool.c"
--- /dev/null
+/* { 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));
+}
+