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)
{