After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 783751 - Add G_IN_SET
Add G_IN_SET
Status: RESOLVED INCOMPLETE
Product: glib
Classification: Platform
Component: general
unspecified
Other Linux
: Normal normal
: ---
Assigned To: gtkdev
gtkdev
Depends on:
Blocks:
 
 
Reported: 2017-06-13 14:19 UTC by Colin Walters
Modified: 2017-06-23 08:58 UTC
See Also:
GNOME target: ---
GNOME version: ---



Description Colin Walters 2017-06-13 14:19:48 UTC
Systemd has an "IN_SET" macro that I quite like:
https://github.com/systemd/systemd/blob/42d3bf86bb75842602d3712caa2baccd09a1c795/src/basic/macro.h#L362

Using this Coccinelle spatch:

// Convert to G_IN_SET()
@@
expression V, E1, E2, E3, E4;
@@
- V == E1 || V == E2 || V == E3 || V == E4
+ G_IN_SET (V, E1, E2, E3, E4)
@@
expression V, E1, E2, E3;
@@
- V == E1 || V == E2 || V == E3
+ G_IN_SET (V, E1, E2, E3)
@@
expression V, E1, E2;
@@
- V == E1 || V == E2
+ G_IN_SET (V, E1, E2)

On glib itself yields quite a huge patch, and IMO most (but not all) of the changes are definitely nicer.  Some examples:

@@ -516,10 +512,7 @@ name_validate (GMarkupParseContext  *con
       /* is_name_char */
       if (G_UNLIKELY (!(g_ascii_isalnum (*p) ||
                         (!IS_COMMON_NAME_END_CHAR (*p) &&
-                         (*p == '.' ||
-                          *p == '-' ||
-                          *p == '_' ||
-                          *p == ':')))))
+                         (G_IN_SET(*p, '.', '-', '_', ':'))))))
         goto slow_validate;
     }
@@ -801,7 +793,7 @@ advance_char (GMarkupParseContext *conte
 static inline gboolean
 xml_isspace (char c)
 {
-  return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+  return G_IN_SET(c, ' ', '\t', '\n', '\r');
 }
 
 static void
@@ -1176,8 +1168,7 @@ g_markup_parse_context_parse (GMarkupPar
           /* Possible next states: INSIDE_OPEN_TAG_NAME,
            *  AFTER_CLOSE_TAG_SLASH, INSIDE_PASSTHROUGH
            */
-          if (*context->iter == '?' ||
-              *context->iter == '!')
+          if (G_IN_SET(*context->iter, '?', '!'))
             {
               /* include < in the passthrough */
               const gchar *openangle = "<";

Here's an example where the spatch arity is too low:

--- a/glib-unix.c
+++ b/glib-unix.c
@@ -215,8 +215,7 @@ g_unix_set_fd_nonblocking (gint       fd
 GSource *
 g_unix_signal_source_new (int signum)
 {
-  g_return_val_if_fail (signum == SIGHUP || signum == SIGINT || signum == SIGTERM ||
-                        signum == SIGUSR1 || signum == SIGUSR2 || signum == SIGWINCH,
+  g_return_val_if_fail (G_IN_SET(signum, SIGHUP, SIGINT, SIGTERM, SIGUSR1) || signum == SIGUSR2 || signum == SIGWINCH,
                         NULL);
 
   return _g_main_create_unix_signal_watch (signum);


Here's one though where it feels weird:

--- a/gsequence.c
+++ b/gsequence.c
@@ -604,7 +604,7 @@ g_sequence_move_range (GSequenceIter *de
   g_return_if_fail (src_seq == get_sequence (end));
 
   /* Dest points to begin or end? */
-  if (dest == begin || dest == end)
+  if (G_IN_SET(dest, begin, end))
     return;
 
   /* begin comes after end? */

I think it's because it's pointer values and not integer/characters?  

Anyways, are there any objections to me copying the systemd code into glib and submitting a patch for G_IN_SET?
Comment 1 Philip Withnall 2017-06-13 15:00:59 UTC
(In reply to Colin Walters from comment #0)
> Anyways, are there any objections to me copying the systemd code into glib
> and submitting a patch for G_IN_SET?

Nice, I’m in favour of this. I think the patch to convert GLib would have to be manually tweaked rather than just being a Coccinelle dump, though. I’d like that last (dest == begin || dest == end) change dropped, for example.
Comment 2 Emmanuele Bassi (:ebassi) 2017-06-13 15:05:07 UTC
The implementation of that macro is definitely iffy, though, especially that:

/* If the build breaks in the line below, you need to extend the case macros */

Shouldn't this be a static inline, instead of a pre-processor macro?
Comment 3 Emmanuele Bassi (:ebassi) 2017-06-13 15:26:51 UTC
I mean, this:

```
static inline bool
g_in_set (int value, int arity, ...)
{
  bool res = false;
  va_list args;

  va_start (args, arity);
  while (arity-- > 0)
    if (value == va_arg (args, int))
      {
        res = true;
        break;
      }

  va_end (args);
  return res;
}
```

is effectively the same, with an arity argument as a cost for a clearer implementation that adds some type safety.
Comment 4 Colin Walters 2017-06-13 15:49:30 UTC
Right, that would give an int-conversion warning if the user passed a pointer.  Which...is probably a good thing?  Are there use cases for `long` though?
Comment 5 Philip Withnall 2017-06-13 15:51:32 UTC
(In reply to Emmanuele Bassi (:ebassi) from comment #3)
> is effectively the same, with an arity argument as a cost for a clearer
> implementation that adds some type safety.

What type safety does this add? If anything it drops type safety by passing everything through runtime varargs (and hence everything is treated as an int rather than being automatically promoted to the right size). At least when we use variadic macros, the type checker runs after macro expansion.

(In reply to Emmanuele Bassi (:ebassi) from comment #2)
> The implementation of that macro is definitely iffy, though, especially that:
> 
> /* If the build breaks in the line below, you need to extend the case macros
> */

I wouldn’t say that in particular is iffy. The whole concept of variadic macros is fairly well established, and this failure mode should only occur if more than 20 elements are listed (or if their sizes sum to more than 20*sizeof(int)?). Seems reasonable to me.
Comment 6 Emmanuele Bassi (:ebassi) 2017-06-13 16:22:48 UTC
(In reply to Colin Walters from comment #4)
> Right, that would give an int-conversion warning if the user passed a
> pointer.  Which...is probably a good thing?  Are there use cases for `long`
> though?

A fun bit would be to allow using (on compatible compiler) C11 _Generic macros wrapping typed inlined functions:

static inline gboolean g_int_in_set (int, ...) { ... }
static inline gboolean g_long_in_set (long, ...) { ... }

#if CAN_USE_C11_GENERIC
#define g_in_set(value,arity,...) \
  _Generic((value), \
    int: g_int_in_set,
    long: g_long_in_set,
    default: g_int_in_set) (value,arity,__VA_ARGS__)
#else
#define g_in_set(value,arity,...) g_int_in_set(value,arity,__VA_ARGS__)
#endif


But that's probably a bit overkill.

(In reply to Philip Withnall from comment #5)
> (In reply to Emmanuele Bassi (:ebassi) from comment #3)
> > is effectively the same, with an arity argument as a cost for a clearer
> > implementation that adds some type safety.
> 
> What type safety does this add?

On the argument you're passing.

> If anything it drops type safety by passing
> everything through runtime varargs (and hence everything is treated as an
> int rather than being automatically promoted to the right size). At least
> when we use variadic macros, the type checker runs after macro expansion.

That's true, but it's still pretty gross.

We could still keep the static inline function and have the arity argument be expanded by a variadic macro expansion, but…

> (In reply to Emmanuele Bassi (:ebassi) from comment #2)
> > The implementation of that macro is definitely iffy, though, especially that:
> > 
> > /* If the build breaks in the line below, you need to extend the case macros
> > */
> 
> I wouldn’t say that in particular is iffy. The whole concept of variadic
> macros is fairly well established

… even though variadic macros are well established, they are not evenly supported across toolchains.

GLib currently says, of the variadic macros requirement:

"Not a hard requirement. GLib can work with either C99 or GNU style varargs macros."
-- https://wiki.gnome.org/Projects/GLib/CompilerRequirements

We can make it a hard requirement, though. I would gladly make C99 a hard requirement, and drop the support for GNU style varargs, to be fair.
Comment 7 Colin Walters 2017-06-13 19:40:43 UTC
Here's the commit where Lennart introduced the macro; no surprises here, just adding for reference:
https://github.com/systemd/systemd/commit/cabb78068899232c152f4585f19d023e373aa73d

However, this commit is interesting:
https://github.com/systemd/systemd/commit/d5b26d50fca744d1b19eb8c125b047c18c61ad94

I was thinking more about why they didn't use an inline function, and this may be part of the answer:
https://stackoverflow.com/questions/25482031/inlining-of-vararg-functions

And quickly testing that code with gcc (GCC) 6.3.1 20161221 (Red Hat 6.3.1-1), indeed the vararg function isn't inlined.  

[more time passes]

And in fact looking at the GCC manual, there's __attribute__((always_inline)), which if we use that says explicitly:

inline-varargs.c:5:1: error: function ‘varargtest’ can never be inlined because it uses variable argument lists
 varargtest (const char *format, ...)
 ^~~~~~~~~~

And a bit more research leads to  __builtin_va_arg_pack() here:
https://gcc.gnu.org/onlinedocs/gcc/Constructing-Calls.html
which is *explicitly* for this case.  But I'm not sure I want to get into the game of finding out how portable that is.  

So I plan to go with a more or less straight copy of the systemd code; I guess the ugly wrinkle
Comment 8 Colin Walters 2017-06-13 19:41:26 UTC
...is handling GNUC varargs vs ISO?
Comment 10 Colin Walters 2017-06-13 20:48:45 UTC
I caved for now and just put it in libglnx: https://github.com/GNOME/libglnx/pull/53
Comment 11 Philip Withnall 2017-06-23 08:58:28 UTC
(In reply to Colin Walters from comment #9)
> CLOSED → SCREWED_BY_MSVC ?

For now. :-(