Lock free routines are not guaranteed to work if they are not aligned properly. Until now, the __atmoic_is_lock_free property has simply relied on the size of an object to determine lockfreeness. This works when you assume a certain alignment.
The library takes generic pointers and with structure packing and such, alignment is not guaranteed. This means its possible to have a 4 byte object which is lock free, and another which is not. Up until now I've simply punted on it and claimed it was undefined if not properly aligned. By adding the address of the object to the function, it easy enough to check alignment at runtime, as well as at compile time, usually. The library will need this in the future so might as well take care of it now.
Simply adds an optional parameter to __atomic_{is_always}_lock_free which is used to check alignment against the size parameter. NULL as the object pointer produces the same behaviour as today, lockfreeness based on proper alignment for an integral value of the specified size.
Bootstrapped and tested on x86_64-unknown-linux-gnu. Andrew
gcc * builtins.c (fold_builtin_atomic_always_lock_free): Add object param and check alignment if present. (expand_builtin_atomic_always_lock_free): Expect 2nd argument. (fold_builtin_atomic_is_lock_free): Expect 2nd argument. (expand_builtin_atomic_is_lock_free): Expect 2nd argument. (fold_builtin_1): Remove LOCK_FREE builtins. (fold_builtin_2): Add LOCK_FREE builtins. * sync-builtins.def (BUILT_IN_ATOMIC_{IS,ALWAYS}_LOCK_FREE): Add param. * builtin-types.def (BT_FN_BOOL_SIZE): Remove. (BT_FN_BOOL_SIZE_CONST_VPTR): Add. fortran * types.def (BT_FN_BOOL_SIZE): Remove. (BT_FN_BOOL_SIZE_CONST_VPTR): Add. libstdc++-v3 * include/std/atomic (is_lock_free): Add object pointer to __atomic_is_lock_free. * include/bits/atomic_base.h (LOCKFREE_PROP): Add 0 for object ptr. (is_lock_free): Add object pointer to __atomic_is_lock_free. testsuite * gcc.dg/atomic-lockfree-aux.c: Add extra lock-free parameter. * gcc.dg/atomic-invalid.c: Add extra lock-free parameter. * gcc.dg/atomic-lockfree.c: Add extra lock-free parameter. Index: gcc/builtins.c =================================================================== *** gcc/builtins.c (revision 180699) --- gcc/builtins.c (working copy) *************** expand_builtin_atomic_fetch_op (enum mac *** 5448,5502 **** return ret; } ! /* Return true if size ARG is always lock free on this architecture. */ static tree ! fold_builtin_atomic_always_lock_free (tree arg) { int size; enum machine_mode mode; ! if (TREE_CODE (arg) != INTEGER_CST) return NULL_TREE; /* Check if a compare_and_swap pattern exists for the mode which represents the required size. The pattern is not allowed to fail, so the existence of the pattern indicates support is present. */ - size = INTVAL (expand_normal (arg)) * BITS_PER_UNIT; - mode = mode_for_size (size, MODE_INT, 0); - if (can_compare_and_swap_p (mode)) return integer_one_node; else return integer_zero_node; } ! /* Return true if the first argument to call EXP represents a size of ! object than will always generate lock-free instructions on this target. ! Otherwise return false. */ static rtx expand_builtin_atomic_always_lock_free (tree exp) { tree size; ! tree arg = CALL_EXPR_ARG (exp, 0); ! if (TREE_CODE (arg) != INTEGER_CST) { ! error ("non-constant argument to __atomic_always_lock_free"); return const0_rtx; } ! size = fold_builtin_atomic_always_lock_free (arg); if (size == integer_one_node) return const1_rtx; return const0_rtx; } ! /* Return a one or zero if it can be determined that size ARG is lock free on ! this architecture. */ static tree ! fold_builtin_atomic_is_lock_free (tree arg) { ! tree always = fold_builtin_atomic_always_lock_free (arg); /* If it isnt always lock free, don't generate a result. */ if (always == integer_one_node) --- 5448,5539 ---- return ret; } ! /* Return true if (optional) argument ARG1 of size ARG0 is always lock free on ! this architecture. If ARG1 is NULL, use typical alignment for size ARG0. */ ! static tree ! fold_builtin_atomic_always_lock_free (tree arg0, tree arg1) { int size; enum machine_mode mode; + unsigned int mode_align, type_align; ! if (TREE_CODE (arg0) != INTEGER_CST) return NULL_TREE; + size = INTVAL (expand_normal (arg0)) * BITS_PER_UNIT; + mode = mode_for_size (size, MODE_INT, 0); + mode_align = GET_MODE_ALIGNMENT (mode); + + /* If a pointer is provided, check that alignment matches OK. Otherwise a + zero must be provided to indicate normal alignment for an object size. */ + if (TREE_CODE (arg1) == INTEGER_CST && INTVAL (expand_normal (arg1)) == 0) + type_align = mode_align; + else + { + tree ttype = TREE_TYPE (arg1); + + /* Parameters at this point are usually cast to void *, so check for that + and look past the cast. */ + if (TREE_CODE (arg1) == NOP_EXPR && POINTER_TYPE_P (ttype) + && VOID_TYPE_P (TREE_TYPE (ttype))) + arg1 = TREE_OPERAND (arg1, 0); + + ttype = TREE_TYPE (arg1); + gcc_assert (POINTER_TYPE_P (ttype)); + + /* Get the underlying type of the object. */ + ttype = TREE_TYPE (ttype); + type_align = TYPE_ALIGN (ttype); + } + + /* If the object has smaller alignment, the the lock free routines cannot + be used. */ + if (type_align < mode_align) + return integer_zero_node; + /* Check if a compare_and_swap pattern exists for the mode which represents the required size. The pattern is not allowed to fail, so the existence of the pattern indicates support is present. */ if (can_compare_and_swap_p (mode)) return integer_one_node; else return integer_zero_node; } ! /* Return true if the parameters to call EXP represent an object which will ! always generate lock free instructions. The first argument represents the ! size of the object, and the second parameter is a pointer to the object ! itself. If NULL is passed for the object, then the result is based on ! typical alignment for an object of the specified size. Otherwise return ! false. */ ! static rtx expand_builtin_atomic_always_lock_free (tree exp) { tree size; ! tree arg0 = CALL_EXPR_ARG (exp, 0); ! tree arg1 = CALL_EXPR_ARG (exp, 1); ! if (TREE_CODE (arg0) != INTEGER_CST) { ! error ("non-constant argument 1 to __atomic_always_lock_free"); return const0_rtx; } ! size = fold_builtin_atomic_always_lock_free (arg0, arg1); if (size == integer_one_node) return const1_rtx; return const0_rtx; } ! /* Return a one or zero if it can be determined that object ARG1 of size ARG ! is lock free on this architecture. */ ! static tree ! fold_builtin_atomic_is_lock_free (tree arg0, tree arg1) { ! tree always = fold_builtin_atomic_always_lock_free (arg0, arg1); /* If it isnt always lock free, don't generate a result. */ if (always == integer_one_node) *************** fold_builtin_atomic_is_lock_free (tree a *** 5505,5526 **** return NULL_TREE; } ! /* Return one or zero if the first argument to call EXP represents a size of ! object than can generate lock-free instructions on this target. */ static rtx expand_builtin_atomic_is_lock_free (tree exp) { tree size; ! tree arg = CALL_EXPR_ARG (exp, 0); ! if (!INTEGRAL_TYPE_P (TREE_TYPE (arg))) { ! error ("non-integer argument to __atomic_is_lock_free"); return NULL_RTX; } /* If the value is known at compile time, return the RTX for it. */ ! size = fold_builtin_atomic_is_lock_free (arg); if (size == integer_one_node) return const1_rtx; --- 5542,5568 ---- return NULL_TREE; } ! /* Return true if the parameters to call EXP represent an object which will ! always generate lock free instructions. The first argument represents the ! size of the object, and the second parameter is a pointer to the object ! itself. If NULL is passed for the object, then the result is based on ! typical alignment for an object of the specified size. Otherwise return ! NULL*/ static rtx expand_builtin_atomic_is_lock_free (tree exp) { tree size; ! tree arg0 = CALL_EXPR_ARG (exp, 0); ! tree arg1 = CALL_EXPR_ARG (exp, 1); ! if (!INTEGRAL_TYPE_P (TREE_TYPE (arg0))) { ! error ("non-integer argument 1 to __atomic_is_lock_free"); return NULL_RTX; } /* If the value is known at compile time, return the RTX for it. */ ! size = fold_builtin_atomic_is_lock_free (arg0, arg1); if (size == integer_one_node) return const1_rtx; *************** fold_builtin_1 (location_t loc, tree fnd *** 10459,10470 **** return build_empty_stmt (loc); break; - case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE: - return fold_builtin_atomic_always_lock_free (arg0); - - case BUILT_IN_ATOMIC_IS_LOCK_FREE: - return fold_builtin_atomic_is_lock_free (arg0); - default: break; } --- 10501,10506 ---- *************** fold_builtin_2 (location_t loc, tree fnd *** 10668,10673 **** --- 10704,10715 ---- return fold_builtin_fprintf (loc, fndecl, arg0, arg1, NULL_TREE, ignore, fcode); + case BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE: + return fold_builtin_atomic_always_lock_free (arg0, arg1); + + case BUILT_IN_ATOMIC_IS_LOCK_FREE: + return fold_builtin_atomic_is_lock_free (arg0, arg1); + default: break; } Index: gcc/sync-builtins.def =================================================================== *** gcc/sync-builtins.def (revision 180699) --- gcc/sync-builtins.def (working copy) *************** DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_FETCH_ *** 579,589 **** DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE, "__atomic_always_lock_free", ! BT_FN_BOOL_SIZE, ATTR_CONST_NOTHROW_LEAF_LIST) DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_IS_LOCK_FREE, "__atomic_is_lock_free", ! BT_FN_BOOL_SIZE, ATTR_CONST_NOTHROW_LEAF_LIST) DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_THREAD_FENCE, --- 579,589 ---- DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_ALWAYS_LOCK_FREE, "__atomic_always_lock_free", ! BT_FN_BOOL_SIZE_CONST_VPTR, ATTR_CONST_NOTHROW_LEAF_LIST) DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_IS_LOCK_FREE, "__atomic_is_lock_free", ! BT_FN_BOOL_SIZE_CONST_VPTR, ATTR_CONST_NOTHROW_LEAF_LIST) DEF_SYNC_BUILTIN (BUILT_IN_ATOMIC_THREAD_FENCE, Index: gcc/builtin-types.def =================================================================== *** gcc/builtin-types.def (revision 180699) --- gcc/builtin-types.def (working copy) *************** DEF_FUNCTION_TYPE_1 (BT_FN_ULONG_ULONG, *** 224,230 **** DEF_FUNCTION_TYPE_1 (BT_FN_ULONGLONG_ULONGLONG, BT_ULONGLONG, BT_ULONGLONG) DEF_FUNCTION_TYPE_1 (BT_FN_UINT32_UINT32, BT_UINT32, BT_UINT32) DEF_FUNCTION_TYPE_1 (BT_FN_UINT64_UINT64, BT_UINT64, BT_UINT64) - DEF_FUNCTION_TYPE_1 (BT_FN_BOOL_SIZE, BT_BOOL, BT_SIZE) DEF_POINTER_TYPE (BT_PTR_FN_VOID_PTR, BT_FN_VOID_PTR) --- 224,229 ---- *************** DEF_FUNCTION_TYPE_2 (BT_FN_I16_CONST_VPT *** 332,337 **** --- 331,338 ---- BT_INT) DEF_FUNCTION_TYPE_2 (BT_FN_VOID_VPTR_INT, BT_VOID, BT_VOLATILE_PTR, BT_INT) DEF_FUNCTION_TYPE_2 (BT_FN_BOOL_VPTR_INT, BT_BOOL, BT_VOLATILE_PTR, BT_INT) + DEF_FUNCTION_TYPE_2 (BT_FN_BOOL_SIZE_CONST_VPTR, BT_BOOL, BT_SIZE, + BT_CONST_VOLATILE_PTR) DEF_POINTER_TYPE (BT_PTR_FN_VOID_PTR_PTR, BT_FN_VOID_PTR_PTR) Index: gcc/fortran/types.def =================================================================== *** gcc/fortran/types.def (revision 180699) --- gcc/fortran/types.def (working copy) *************** DEF_FUNCTION_TYPE_1 (BT_FN_VOID_VPTR, BT *** 90,96 **** DEF_FUNCTION_TYPE_1 (BT_FN_UINT_UINT, BT_UINT, BT_UINT) DEF_FUNCTION_TYPE_1 (BT_FN_PTR_PTR, BT_PTR, BT_PTR) DEF_FUNCTION_TYPE_1 (BT_FN_VOID_INT, BT_VOID, BT_INT) - DEF_FUNCTION_TYPE_1 (BT_FN_BOOL_SIZE, BT_BOOL, BT_SIZE) DEF_POINTER_TYPE (BT_PTR_FN_VOID_PTR, BT_FN_VOID_PTR) --- 90,95 ---- *************** DEF_FUNCTION_TYPE_2 (BT_FN_I16_CONST_VPT *** 117,122 **** --- 116,123 ---- BT_INT) DEF_FUNCTION_TYPE_2 (BT_FN_VOID_VPTR_INT, BT_VOID, BT_VOLATILE_PTR, BT_INT) DEF_FUNCTION_TYPE_2 (BT_FN_BOOL_VPTR_INT, BT_BOOL, BT_VOLATILE_PTR, BT_INT) + DEF_FUNCTION_TYPE_2 (BT_FN_BOOL_SIZE_CONST_VPTR, BT_BOOL, BT_SIZE, + BT_CONST_VOLATILE_PTR) DEF_POINTER_TYPE (BT_PTR_FN_VOID_PTR_PTR, BT_FN_VOID_PTR_PTR) Index: libstdc++-v3/include/std/atomic =================================================================== *** libstdc++-v3/include/std/atomic (revision 180699) --- libstdc++-v3/include/std/atomic (working copy) *************** _GLIBCXX_BEGIN_NAMESPACE_VERSION *** 181,191 **** bool is_lock_free() const noexcept ! { return __atomic_is_lock_free(sizeof(_M_i)); } bool is_lock_free() const volatile noexcept ! { return __atomic_is_lock_free(sizeof(_M_i)); } void store(_Tp __i, memory_order _m = memory_order_seq_cst) noexcept --- 181,191 ---- bool is_lock_free() const noexcept ! { return __atomic_is_lock_free(sizeof(_M_i), &_M_i); } bool is_lock_free() const volatile noexcept ! { return __atomic_is_lock_free(sizeof(_M_i), &_M_i); } void store(_Tp __i, memory_order _m = memory_order_seq_cst) noexcept Index: libstdc++-v3/include/bits/atomic_base.h =================================================================== *** libstdc++-v3/include/bits/atomic_base.h (revision 180699) --- libstdc++-v3/include/bits/atomic_base.h (working copy) *************** _GLIBCXX_BEGIN_NAMESPACE_VERSION *** 85,91 **** /// Lock-free Property ! #define LOCKFREE_PROP(T) (__atomic_always_lock_free (sizeof (T)) ? 2 : 1) #define ATOMIC_CHAR_LOCK_FREE LOCKFREE_PROP (char) #define ATOMIC_CHAR16_T_LOCK_FREE LOCKFREE_PROP (char16_t) --- 85,91 ---- /// Lock-free Property ! #define LOCKFREE_PROP(T) (__atomic_always_lock_free (sizeof (T), 0) ? 2 : 1) #define ATOMIC_CHAR_LOCK_FREE LOCKFREE_PROP (char) #define ATOMIC_CHAR16_T_LOCK_FREE LOCKFREE_PROP (char16_t) *************** _GLIBCXX_BEGIN_NAMESPACE_VERSION *** 427,437 **** bool is_lock_free() const noexcept ! { return __atomic_is_lock_free (sizeof (_M_i)); } bool is_lock_free() const volatile noexcept ! { return __atomic_is_lock_free (sizeof (_M_i)); } void store(__int_type __i, memory_order __m = memory_order_seq_cst) noexcept --- 427,437 ---- bool is_lock_free() const noexcept ! { return __atomic_is_lock_free (sizeof (_M_i), &_M_i); } bool is_lock_free() const volatile noexcept ! { return __atomic_is_lock_free (sizeof (_M_i), &_M_i); } void store(__int_type __i, memory_order __m = memory_order_seq_cst) noexcept *************** _GLIBCXX_BEGIN_NAMESPACE_VERSION *** 706,716 **** bool is_lock_free() const noexcept ! { return __atomic_is_lock_free (sizeof (_M_p)); } bool is_lock_free() const volatile noexcept ! { return __atomic_is_lock_free (sizeof (_M_p)); } void store(__pointer_type __p, --- 706,716 ---- bool is_lock_free() const noexcept ! { return __atomic_is_lock_free (sizeof (_M_p), &_M_p); } bool is_lock_free() const volatile noexcept ! { return __atomic_is_lock_free (sizeof (_M_p), &_M_p); } void store(__pointer_type __p, Index: gcc/testsuite/gcc.dg/atomic-lockfree-aux.c =================================================================== *** gcc/testsuite/gcc.dg/atomic-lockfree-aux.c (revision 180699) --- gcc/testsuite/gcc.dg/atomic-lockfree-aux.c (working copy) *************** *** 10,16 **** /* Supply a builtin external function which returns a non-standard value so it can be detected that it was called. */ int ! __atomic_is_lock_free (size_t s) { return 2; } --- 10,16 ---- /* Supply a builtin external function which returns a non-standard value so it can be detected that it was called. */ int ! __atomic_is_lock_free (size_t s, void *p) { return 2; } Index: gcc/testsuite/gcc.dg/atomic-invalid.c =================================================================== *** gcc/testsuite/gcc.dg/atomic-invalid.c (revision 180699) --- gcc/testsuite/gcc.dg/atomic-invalid.c (working copy) *************** main () *** 23,27 **** __atomic_store_n (&i, 1, __ATOMIC_CONSUME); /* { dg-error "invalid memory model" } */ __atomic_store_n (&i, 1, __ATOMIC_ACQ_REL); /* { dg-error "invalid memory model" } */ ! i = __atomic_always_lock_free (s); /* { dg-error "non-constant argument" } */ } --- 23,27 ---- __atomic_store_n (&i, 1, __ATOMIC_CONSUME); /* { dg-error "invalid memory model" } */ __atomic_store_n (&i, 1, __ATOMIC_ACQ_REL); /* { dg-error "invalid memory model" } */ ! i = __atomic_always_lock_free (s, NULL); /* { dg-error "non-constant argument" } */ } Index: gcc/testsuite/gcc.dg/atomic-lockfree.c =================================================================== *** gcc/testsuite/gcc.dg/atomic-lockfree.c (revision 180699) --- gcc/testsuite/gcc.dg/atomic-lockfree.c (working copy) *************** int r1, r2; *** 20,27 **** main () { ! r1 = __atomic_always_lock_free (sizeof(char)); ! r2 = __atomic_is_lock_free (sizeof(char)); /* If always lock free, then is_lock_free must also be true. */ if (r1) { --- 20,27 ---- main () { ! r1 = __atomic_always_lock_free (sizeof(char), 0); ! r2 = __atomic_is_lock_free (sizeof(char), 0); /* If always lock free, then is_lock_free must also be true. */ if (r1) { *************** main () *** 35,42 **** abort (); } ! r1 = __atomic_always_lock_free (2); ! r2 = __atomic_is_lock_free (2); /* If always lock free, then is_lock_free must also be true. */ if (r1) { --- 35,42 ---- abort (); } ! r1 = __atomic_always_lock_free (2, 0); ! r2 = __atomic_is_lock_free (2, 0); /* If always lock free, then is_lock_free must also be true. */ if (r1) { *************** main () *** 51,58 **** } ! r1 = __atomic_always_lock_free (4); ! r2 = __atomic_is_lock_free (4); /* Try passing in a variable. */ /* If always lock free, then is_lock_free must also be true. */ if (r1) { --- 51,58 ---- } ! r1 = __atomic_always_lock_free (4, 0); ! r2 = __atomic_is_lock_free (4, 0); /* Try passing in a variable. */ /* If always lock free, then is_lock_free must also be true. */ if (r1) { *************** main () *** 67,74 **** } ! r1 = __atomic_always_lock_free (8); ! r2 = __atomic_is_lock_free (8); /* If always lock free, then is_lock_free must also be true. */ if (r1) { --- 67,74 ---- } ! r1 = __atomic_always_lock_free (8, 0); ! r2 = __atomic_is_lock_free (8, 0); /* If always lock free, then is_lock_free must also be true. */ if (r1) { *************** main () *** 83,90 **** } ! r1 = __atomic_always_lock_free (16); ! r2 = __atomic_is_lock_free (16); /* If always lock free, then is_lock_free must also be true. */ if (r1) { --- 83,90 ---- } ! r1 = __atomic_always_lock_free (16, 0); ! r2 = __atomic_is_lock_free (16, 0); /* If always lock free, then is_lock_free must also be true. */ if (r1) { *************** main () *** 99,106 **** } ! r1 = __atomic_always_lock_free (32); ! r2 = __atomic_is_lock_free (32); /* If always lock free, then is_lock_free must also be true. */ if (r1) { --- 99,106 ---- } ! r1 = __atomic_always_lock_free (32, 0); ! r2 = __atomic_is_lock_free (32, 0); /* If always lock free, then is_lock_free must also be true. */ if (r1) {