DPDK patches and discussions
 help / color / mirror / Atom feed
From: Daniel Gregory <daniel.gregory@bytedance.com>
To: Stephen Hemminger <stephen@networkplumber.org>
Cc: Ruifeng Wang <ruifeng.wang@arm.com>,
	dev@dpdk.org, Punit Agrawal <punit.agrawal@bytedance.com>,
	Liang Ma <liangma@bytedance.com>
Subject: Re: [PATCH] eal/arm: replace RTE_BUILD_BUG on non-constant
Date: Thu, 9 May 2024 12:02:51 +0100	[thread overview]
Message-ID: <20240509110251.GA3795959@ste-uk-lab-gw> (raw)
In-Reply-To: <20240503175624.54dd0a72@hermes.local>

On Fri, May 03, 2024 at 05:56:24PM -0700, Stephen Hemminger wrote:
> On Fri, 3 May 2024 10:46:05 +0100
> Daniel Gregory <daniel.gregory@bytedance.com> wrote:
> 
> > On Thu, May 02, 2024 at 02:48:26PM -0700, Stephen Hemminger wrote:
> > > There are already constant checks like this elsewhere in the file.  
> > 
> > Yes, but they're in macros, rather than inlined functions, so my
> > understanding was that at compile time, macro expansion has put the
> > memorder constant in the _Static_assert call as opposed to still being
> > a function parameter in the inline definition.
> 
> Gcc and clang are smart enough that it is possible to use the internal
> __builtin_constant_p() in the function. Some examples in DPDK:
> 
> static __rte_always_inline int
> rte_mempool_do_generic_get(struct rte_mempool *mp, void **obj_table,
> 			   unsigned int n, struct rte_mempool_cache *cache)
> {
> 	int ret;
> 	unsigned int remaining;
> 	uint32_t index, len;
> 	void **cache_objs;
> 
> 	/* No cache provided */
> 	if (unlikely(cache == NULL)) {
> 		remaining = n;
> 		goto driver_dequeue;
> 	}
> 
> 	/* The cache is a stack, so copy will be in reverse order. */
> 	cache_objs = &cache->objs[cache->len];
> 
> 	if (__extension__(__builtin_constant_p(n)) && n <= cache->len) {
> 
> It should be possible to use RTE_BUILD_BUG_ON() or static_assert here.

Yes, it's possible to use RTE_BUILD_BUG_ON(!__builtin_constant_p(n)) on
Clang, I am simply not seeing it succeed. In fact, the opposite check,
that the memorder is not constant, builds just fine with Clang 16.


diff --git a/lib/eal/arm/include/rte_pause_64.h b/lib/eal/arm/include/rte_pause_64.h
index 5cb8b59056..d0646320e6 100644
--- a/lib/eal/arm/include/rte_pause_64.h
+++ b/lib/eal/arm/include/rte_pause_64.h
@@ -153,8 +153,7 @@ rte_wait_until_equal_16(volatile uint16_t *addr, uint16_t expected,
 {
 	uint16_t value;
 
-	RTE_BUILD_BUG_ON(memorder != rte_memory_order_acquire &&
-		memorder != rte_memory_order_relaxed);
+	RTE_BUILD_BUG_ON(__builtin_constant_p(memorder));
 
 	__RTE_ARM_LOAD_EXC_16(addr, value, memorder)
 	if (value != expected) {
@@ -172,8 +171,7 @@ rte_wait_until_equal_32(volatile uint32_t *addr, uint32_t expected,
 {
 	uint32_t value;
 
-	RTE_BUILD_BUG_ON(memorder != rte_memory_order_acquire &&
-		memorder != rte_memory_order_relaxed);
+	RTE_BUILD_BUG_ON(__builtin_constant_p(memorder));
 
 	__RTE_ARM_LOAD_EXC_32(addr, value, memorder)
 	if (value != expected) {
@@ -191,8 +189,7 @@ rte_wait_until_equal_64(volatile uint64_t *addr, uint64_t expected,
 {
 	uint64_t value;
 
-	RTE_BUILD_BUG_ON(memorder != rte_memory_order_acquire &&
-		memorder != rte_memory_order_relaxed);
+	RTE_BUILD_BUG_ON(__builtin_constant_p(memorder));
 
 	__RTE_ARM_LOAD_EXC_64(addr, value, memorder)
 	if (value != expected) {
diff --git a/lib/eal/include/generic/rte_pause.h b/lib/eal/include/generic/rte_pause.h
index f2a1eadcbd..3973488865 100644
--- a/lib/eal/include/generic/rte_pause.h
+++ b/lib/eal/include/generic/rte_pause.h
@@ -80,6 +80,7 @@ static __rte_always_inline void
 rte_wait_until_equal_16(volatile uint16_t *addr, uint16_t expected,
 		rte_memory_order memorder)
 {
+	RTE_BUILD_BUG_ON(__builtin_constant_p(memorder));
 	assert(memorder == rte_memory_order_acquire || memorder == rte_memory_order_relaxed);
 
 	while (rte_atomic_load_explicit((volatile __rte_atomic uint16_t *)addr, memorder)
@@ -91,6 +92,7 @@ static __rte_always_inline void
 rte_wait_until_equal_32(volatile uint32_t *addr, uint32_t expected,
 		rte_memory_order memorder)
 {
+	RTE_BUILD_BUG_ON(__builtin_constant_p(memorder));
 	assert(memorder == rte_memory_order_acquire || memorder == rte_memory_order_relaxed);
 
 	while (rte_atomic_load_explicit((volatile __rte_atomic uint32_t *)addr, memorder)
@@ -102,6 +104,7 @@ static __rte_always_inline void
 rte_wait_until_equal_64(volatile uint64_t *addr, uint64_t expected,
 		rte_memory_order memorder)
 {
+	RTE_BUILD_BUG_ON(__builtin_constant_p(memorder));
 	assert(memorder == rte_memory_order_acquire || memorder == rte_memory_order_relaxed);
 
 	while (rte_atomic_load_explicit((volatile __rte_atomic uint64_t *)addr, memorder)


This seemed odd, and it doesn't line up with what the GCC documentation
says about __builtin_constant_p:

> [__builtin_constant_p] does not return 1 when you pass a constant
> numeric value to the inline function unless you specify the -O option.
https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html

So I did some more looking and the behaviour I've seen is that both
Clang and GCC don't allow for static checks on inline function
parameters:

static inline __attribute__((always_inline)) int
fn(int val)
{
	_Static_assert(val == 0, "val nonzero");
	return 0;
}

int main(void) {
	return fn(0);
}

( https://godbolt.org/z/TrfWqYoGo )

Both GCC and Clang's __builtin_constant_p return 1 at runtime when
called on an inline function parameter

static inline __attribute__((always_inline)) int
fn(int val)
{
	return __builtin_constant_p(val);
}

int main(void) {
	return fn(0);
}

( https://godbolt.org/z/hTGqvj91K )

But GCC doesn't allow for static assertions on the return value of
__builtin_constant_p, error-ing that the expression is not constant:

static inline __attribute__((always_inline)) int
fn(int val)
{
	_Static_assert(__builtin_constant_p(val), "v isn't constant");
	return 0;
}

int main(void) {
	return fn(0);
}

( https://godbolt.org/z/4799nEK1T )

And on Clang these static assertions are allowed, but always fail
( https://godbolt.org/z/6j16c5MbT ).

I don't really have the experience with Clang or GCC to assess whether
this is intended behaviour or a bug (I've not seen evidence this
behaviour is new or different from previous versions), it's just what
I've observed.
 
> Changing a compile time check into a runtime check means that buggy programs
> blow up much later and in a confusing manner. And it impacts all code that
> is doing a spin lock, certainly one of the hottest paths in DPDK.

True, buggy programs will error later, but other than converting the
rte_wait_until_equal_* functions to macros, I haven't seen an approach
that will let us use a static assert on the passed in memory order
value right now.

However, new enough GCC and Clang versions will optimise out runtime
checks that are guaranteed to pass, such as in this case when an inline
functions is called with a constant value. To give a reduced example:

#include <assert.h>

enum E {
	X,
	Y,
	Z,
};

static inline __attribute__((always_inline)) void
add(int *dst, enum E e)
{
	assert(e == X || e == Y);
	if (e == X)
		(*dst)++;
	else
		(*dst) += 4;
}
}

int
fn(int num)
{
	add(&num, Y);
	return num;
}

(GCC https://godbolt.org/z/GKGEa7Wdd )
(Clang https://godbolt.org/z/1vYv8MYcs )

With optimisations they both produce a fn that doesn't perform any check
on the value of e, and simply return the input+4.

Therefore, this patch shouldn't have an impact on the performance of
locks when built in release mode.

  reply	other threads:[~2024-05-09 11:02 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-05-02 14:21 Daniel Gregory
2024-05-02 16:20 ` Stephen Hemminger
2024-05-02 17:44   ` Daniel Gregory
2024-05-02 18:27     ` Stephen Hemminger
2024-05-02 21:48     ` Stephen Hemminger
2024-05-03  9:46       ` Daniel Gregory
2024-05-04  0:56         ` Stephen Hemminger
2024-05-09 11:02           ` Daniel Gregory [this message]
2024-05-03 13:32 ` David Marchand
2024-05-03 14:21   ` Daniel Gregory
2024-05-03 18:27 ` [PATCH v2] " Daniel Gregory
2024-05-03 18:30   ` Daniel Gregory
2024-05-04  0:59   ` Stephen Hemminger
2024-06-27 15:08     ` Thomas Monjalon
2024-06-28 10:05       ` Daniel Gregory
2024-06-28 15:19         ` Stephen Hemminger
2024-05-06  9:30   ` Ruifeng Wang
2024-05-11 17:00   ` Wathsala Wathawana Vithanage
2024-10-04 17:47   ` Stephen Hemminger
2024-10-08  9:47     ` Morten Brørup
2024-05-04  1:02 ` [PATCH] " Stephen Hemminger
2024-05-09 11:11   ` Daniel Gregory
2024-05-09 16:47     ` Tyler Retzlaff
2024-05-11 16:48 ` Wathsala Wathawana Vithanage

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20240509110251.GA3795959@ste-uk-lab-gw \
    --to=daniel.gregory@bytedance.com \
    --cc=dev@dpdk.org \
    --cc=liangma@bytedance.com \
    --cc=punit.agrawal@bytedance.com \
    --cc=ruifeng.wang@arm.com \
    --cc=stephen@networkplumber.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).