Bug 110743 - Unexpected -ftrivial-auto-var-init=pattern behavior with partial bitfields
Summary: Unexpected -ftrivial-auto-var-init=pattern behavior with partial bitfields
Status: ASSIGNED
Alias: None
Product: gcc
Classification: Unclassified
Component: tree-optimization (show other bugs)
Version: 13.1.0
: P3 normal
Target Milestone: ---
Assignee: Richard Biener
URL:
Keywords: diagnostic
Depends on:
Blocks: Wuninitialized
  Show dependency tree / graph
 
Reported: 2023-07-19 15:59 UTC by Arnd Bergmann
Modified: 2026-06-25 06:54 UTC (History)
7 users (show)

See Also:
Host:
Target:
Build:
Known to work: 17.0
Known to fail:
Last reconfirmed: 2023-07-20 00:00:00


Attachments
patch (1.25 KB, patch)
2026-06-19 09:48 UTC, Richard Biener
Details | Diff
unreduced preprocessed source file (703.75 KB, application/gzip)
2026-06-23 20:56 UTC, Arnd Bergmann
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Arnd Bergmann 2023-07-19 15:59:13 UTC
A warning showed up in Linux kernel builds with code that has a data structure
with sub-byte holes in it, making it appear as though the structure was
uninitialized even though there is no user before the intialization:

struct spi_mem_op {
  struct {
    int a : 1;
  };
  struct {
    char b : 1;
    long c;
  };
};
void spi_nor_read_any_reg(struct spi_mem_op *);
void s25fs256t_post_bfpt_fixup_nor(void) {
  struct spi_mem_op op;
  spi_nor_read_any_reg(&op);
}

$ x86_64-linux-gnu-gcc-12 -O2 -ftrivial-auto-var-init=pattern -Wuninitialized
     x86-64 gcc 12.3 (Editor #1)
x86-64 gcc 12.3 - 905ms (5966B) ~392 lines filtered

    Output of x86-64 gcc 12.3 (Compiler #1)

<source>: In function 's25fs256t_post_bfpt_fixup_nor':
<source>:12:21: warning: 'op' is used uninitialized [-Wuninitialized]
   12 |   struct spi_mem_op op;
      |                     ^~
<source>:12:21: note: 'op' declared here
   12 |   struct spi_mem_op op;
      |                     ^~

See also https://godbolt.org/z/o96GfTaaT

gcc-11 and earlier don't show this behavior because they do not support -ftrivial-auto-var-init=pattern. I notice that in the example above, the first 16 bytes of the structure are intialized to zero with an x86 SSE instruction since they do not contain any actual data bytes, just single bits.

This seems to be the same underlying problem as in bug #104550, but that one is marked fixed and I cannot reproduce it on gcc-12 or gcc-13.
Comment 1 Richard Biener 2023-07-20 09:57:07 UTC
We interestingly see

  <bb 2> :
  op = .DEFERRED_INIT (24, 1, &"op"[0]);
  _1 = MEM <long int> [(struct spi_mem_op *)&op];
  _2 = _1 & 1;
  MEM <long int> [(struct spi_mem_op *)&op] = _2;
  _3 = MEM <long int> [(struct spi_mem_op *)&op + 8B];
  _4 = _3 & 1;
  MEM <long int> [(struct spi_mem_op *)&op + 8B] = _4;
  spi_nor_read_any_reg (&op);
  op ={v} {CLOBBER(eol)};
  return;

which is done by __builtin_clear_padding which we insert during gimplification:

void s25fs256t_post_bfpt_fixup_nor ()
{
  struct spi_mem_op op;

  try
    {
      op = .DEFERRED_INIT (24, 1, &"op"[0]);
      __builtin_clear_padding (&op, 1B);
      spi_nor_read_any_reg (&op);
    }
  finally
    {
      op = {CLOBBER(eol)};
    }
}

I'm not sure why we do it this way.  I wonder why we don't diagnose this at -O0.

Note RTL expansion of .DEFERRED_INIT _does_ write to the bitfield padding
so maybe we can avoid doing that in __builtin_clear_padding (with an extra
op?).  Confirmed.

A workaround is to explicitely name the padding thus add int padding : 31, etc.
Comment 2 Zdenek Sojka 2026-01-19 20:23:12 UTC
Similar happens with _BitInt(<8):

$ cat testcase.c
char
foo ()
{
  _BitInt(7) b;
  if (__builtin_add_overflow (1, 2, &b))
    b = 0;
  return b;
}
$ x86_64-pc-linux-gnu-gcc -Og -ftrivial-auto-var-init=pattern -Werror=uninitialized testcase.c 
testcase.c: In function 'foo':
testcase.c:4:14: error: 'b' is used uninitialized [-Werror=uninitialized]
    4 |   _BitInt(7) b;
      |              ^
cc1: some warnings being treated as errors
Comment 3 Jakub Jelinek 2026-01-19 20:37:42 UTC
The comment says:
             In order to make the paddings as zeroes for pattern init, We
             should add a call to __builtin_clear_padding to clear the
             paddings to zero in compatible with CLANG.
But I wonder if in order to be somehow compatible with clang in making sure padding bits are always zero while other bits are pattern we really need to slow code down.
Just not doing the __builtin_clear_padding would be IMHO better.
Comment 4 qinzhao 2026-01-27 16:27:26 UTC
(In reply to Jakub Jelinek from comment #3)
> The comment says:
>              In order to make the paddings as zeroes for pattern init, We
>              should add a call to __builtin_clear_padding to clear the
>              paddings to zero in compatible with CLANG.
> But I wonder if in order to be somehow compatible with clang in making sure
> padding bits are always zero while other bits are pattern we really need to
> slow code down.
> Just not doing the __builtin_clear_padding would be IMHO better.

Yes, deleting adding the call to __builtin_clear_padding resolved this issue. 
I am happy to do this if "being compatible with CLANG" is not necessary?
Comment 5 Arnd Bergmann 2026-06-19 08:01:31 UTC
I came across another warning of this type in the Linux kernel with gcc-16.1, reduced a test case to

https://godbolt.org/z/K8974sEq9

void security_add_hooks(void *);
struct layer_mask {
  int access : 7;
};
struct layer_masks {
  struct layer_mask layers[5];
};
void hook_unix_find(void)
{
  struct layer_masks layer_masks;
  security_add_hooks(&layer_masks);
}

In kernel builds, this only happens with gcc-16 and not with earlier versions, but the reduced test case still happens with gcc-12, which introduced -ftrivial-auto-var-init=pattern
Comment 6 Richard Biener 2026-06-19 08:44:58 UTC
Seems to be

      __builtin_clear_padding (&layer_masks, 1B);

expansion which does

  layer_masks = .DEFERRED_INIT (20, 1, &"layer_masks"[0]);
  _1 = MEM <long int> [(struct layer_masks *)&layer_masks];
  _2 = _1 & 545460846719;
  MEM <long int> [(struct layer_masks *)&layer_masks] = _2;
  _3 = MEM <long int> [(struct layer_masks *)&layer_masks + 8B];
  _4 = _3 & 545460846719;
  MEM <long int> [(struct layer_masks *)&layer_masks + 8B] = _4;
  _5 = MEM <int> [(struct layer_masks *)&layer_masks + 16B];
  _6 = _5 & 127;
  MEM <int> [(struct layer_masks *)&layer_masks + 16B] = _6;
  security_add_hooks (&layer_masks);

and thus reads from layer_masks.  That's vectorized and from those
reads we warn.  The above possibly has no-warning annotation?
Comment 7 Richard Biener 2026-06-19 09:11:28 UTC
The vectorizer turns this into

  layer_masks = .DEFERRED_INIT (20, 1, &"layer_masks"[0]);
  vect__1.5_14 = MEM <vector(2) long int> [(struct layer_masks *)&layer_masks];
  vect__2.6_15 = vect__1.5_14 & { 545460846719, 545460846719 };
  MEM <vector(2) long int> [(struct layer_masks *)&layer_masks] = vect__2.6_15;
  _5 = MEM <int> [(struct layer_masks *)&layer_masks + 16B];
  _6 = _5 & 127;
  MEM <int> [(struct layer_masks *)&layer_masks + 16B] = _6;
  security_add_hooks (&layer_masks);
Comment 8 Richard Biener 2026-06-19 09:22:28 UTC
So I see some suppress_warning (tmp_dst, OPT_Wuninitialized) in clear-padding lowering.  We might want to have a way to copy suppressed warnings from one location to another (I think that one is on locations?).  The vectorizer
throws away locations because in general locations correspond to different
stmts (not in this case).
Comment 9 Richard Biener 2026-06-19 09:24:06 UTC
Another option might be to pattern match

 _1 = a;
 a = _1;

from uninit and suppress diagnostics about that (allowing | and & modifications).
Comment 10 Richard Biener 2026-06-19 09:39:40 UTC
Testing a patch along that line.
Comment 11 Richard Biener 2026-06-19 09:48:41 UTC
Created attachment 64794 [details]
patch
Comment 12 GCC Commits 2026-06-23 06:55:40 UTC
The master branch has been updated by Richard Biener <rguenth@gcc.gnu.org>:

https://gcc.gnu.org/g:6d842965024cad9f6d3588d300c11c2c2e4a2dba

commit r17-1765-g6d842965024cad9f6d3588d300c11c2c2e4a2dba
Author: Richard Biener <rguenther@suse.de>
Date:   Fri Jun 19 11:45:46 2026 +0200

    tree-optimization/110743 - do not warn for bit sets/clears to uninit memory
    
    The following avoids diagnosing bit sets/clears that involve RMW
    cycles to uninitialized memory.  This is for example exposed by
    __builtin_clear_padding lowering which suppresses diagnostics but
    those can re-surface with vectorization.
    
            PR tree-optimization/110743
            * tree-ssa-uninit.cc (maybe_warn_operand): Suppress diagnostics
            on RMW cycles setting/clearing bits.
    
            * gcc.dg/uninit-pr110743.c: New testcase.
Comment 13 Richard Biener 2026-06-23 07:17:25 UTC
Should be fixed on trunk.  Can you check whether this now works on the kernel itself?  It should be possible to backport this, so I'll keep it open for now.
Comment 14 Arnd Bergmann 2026-06-23 20:56:32 UTC
Created attachment 64837 [details]
unreduced preprocessed source file

Unfortunately, I still see the issue on trunk, using 9579e3eb64ec ("fortran: Fix memory leak for array structure-constructor element [PR121972]", which includes the commit:

$ aarch64-linux-gcc --version
aarch64-linux-gcc (GCC) 17.0.0 20260623 (experimental)
$ aarch64-linux-gcc security/landlock/fs.i -std=gnu11 -fno-strict-aliasing -fms-extensions -mgeneral-regs-only -ftrivial-auto-var-init=pattern -c -O2 -Werror -Wno-address-of-packed-member -Werror=uninitialized

/home/arnd/arm-soc/security/landlock/fs.c: In function 'is_access_to_paths_allowed':
/home/arnd/arm-soc/security/landlock/fs.c:767:28: error: '_layer_masks_child1' is used uninitialized [-Werror=uninitialized]
  767 |         struct layer_masks _layer_masks_child1, _layer_masks_child2;
      |                            ^~~~~~~~~~~~~~~~~~~
Comment 15 Richard Biener 2026-06-24 07:23:55 UTC
(In reply to Arnd Bergmann from comment #14)
> Created attachment 64837 [details]
> unreduced preprocessed source file
> 
> Unfortunately, I still see the issue on trunk, using 9579e3eb64ec ("fortran:
> Fix memory leak for array structure-constructor element [PR121972]", which
> includes the commit:
> 
> $ aarch64-linux-gcc --version
> aarch64-linux-gcc (GCC) 17.0.0 20260623 (experimental)
> $ aarch64-linux-gcc security/landlock/fs.i -std=gnu11 -fno-strict-aliasing
> -fms-extensions -mgeneral-regs-only -ftrivial-auto-var-init=pattern -c -O2
> -Werror -Wno-address-of-packed-member -Werror=uninitialized
> 
> /home/arnd/arm-soc/security/landlock/fs.c: In function
> 'is_access_to_paths_allowed':
> /home/arnd/arm-soc/security/landlock/fs.c:767:28: error:
> '_layer_masks_child1' is used uninitialized [-Werror=uninitialized]
>   767 |         struct layer_masks _layer_masks_child1, _layer_masks_child2;
>       |                            ^~~~~~~~~~~~~~~~~~~

Thanks, this shows

  layer_masks = .DEFERRED_INIT (64, 1, &"layer_masks"[0]);
  vect__22.425_124 = MEM <vector(2) long int> [(struct layer_masks *)&layer_masks];
  vect__22.426_78 = MEM <vector(2) long int> [(struct layer_masks *)&layer_masks + 16B];
  vect__22.427_121 = MEM <vector(2) long int> [(struct layer_masks *)&layer_masks + 32B];
  vect__22.428_123 = MEM <vector(2) long int> [(struct layer_masks *)&layer_masks + 48B];
  _136 = VIEW_CONVERT_EXPR<uint128_t>(vect__22.425_124);
  _138 = _136 & 0x3ffff0003ffff0003ffff0003ffff;
  _139 = VIEW_CONVERT_EXPR<vector(2) long int>(_138);
  _140 = VIEW_CONVERT_EXPR<uint128_t>(vect__22.426_78);
  _142 = _140 & 0x3ffff0003ffff0003ffff0003ffff;
  _143 = VIEW_CONVERT_EXPR<vector(2) long int>(_142);
  _144 = VIEW_CONVERT_EXPR<uint128_t>(vect__22.427_121);
  _146 = _144 & 0x3ffff0003ffff0003ffff0003ffff;
  _147 = VIEW_CONVERT_EXPR<vector(2) long int>(_146);
  _148 = VIEW_CONVERT_EXPR<uint128_t>(vect__22.428_123);
  _150 = _148 & 0x3ffff0003ffff0003ffff0003ffff;
  _151 = VIEW_CONVERT_EXPR<vector(2) long int>(_150);
  MEM <vector(2) long int> [(struct layer_masks *)&layer_masks] = _139;
  MEM <vector(2) long int> [(struct layer_masks *)&layer_masks + 16B] = _143;
  MEM <vector(2) long int> [(struct layer_masks *)&layer_masks + 32B] = _147;
  MEM <vector(2) long int> [(struct layer_masks *)&layer_masks + 48B] = _151;

that's easy to deal with.  This is emitted from word_mode vectorization:

/home/arnd/arm-soc/security/landlock/fs.c:1649:21: missed: op not supported by target.
/home/arnd/arm-soc/security/landlock/fs.c:1649:21: note: proceeding using word mode.
Comment 16 Richard Biener 2026-06-24 07:29:35 UTC
Reduced testcase:

struct layer_mask {
    unsigned int x : 17;
    unsigned int y : 1;
} __attribute__((__packed__)) __attribute__((__aligned__(sizeof (unsigned int))));
struct layer_masks {
  struct layer_mask layers[16];
};

void bar (struct layer_masks *);

void foo ()
{
  struct layer_masks layer_masks;
  bar (&layer_masks);
}
Comment 17 Richard Biener 2026-06-24 07:52:00 UTC
(In reply to Zdenek Sojka from comment #2)
> Similar happens with _BitInt(<8):
> 
> $ cat testcase.c
> char
> foo ()
> {
>   _BitInt(7) b;
>   if (__builtin_add_overflow (1, 2, &b))
>     b = 0;
>   return b;
> }
> $ x86_64-pc-linux-gnu-gcc -Og -ftrivial-auto-var-init=pattern
> -Werror=uninitialized testcase.c 
> testcase.c: In function 'foo':
> testcase.c:4:14: error: 'b' is used uninitialized [-Werror=uninitialized]
>     4 |   _BitInt(7) b;
>       |              ^
> cc1: some warnings being treated as errors

This one shows

  <bb 2> [local count: 1073741824]:
  _1 = .DEFERRED_INIT (1, 1, &"b"[0]);
  b = _1;
  _8 = VIEW_CONVERT_EXPR<char>(_1);
  _2 = _8 & 127;
  MEM[(_BitInt(7) *)&b] = _2;
  b = 3;
  b ={v} {CLOBBER(eos)};
  return 3;

which is not pattern matched.  A better testcase that also diagnoses at -O2 is

void bar(_BitInt(7) *);
void
foo ()
{
  _BitInt(7) b;
  bar (&b);
}

showing

  <bb 2> [local count: 1073741824]:
  _1 = .DEFERRED_INIT (1, 1, &"b"[0]);
  _7 = VIEW_CONVERT_EXPR<char>(_1);
  _2 = _7 & 127;
  MEM[(_BitInt(7) *)&b] = _2;
  bar (&b);
  b ={v} {CLOBBER(eos)};
  return;

it's tempting to match a .DEFERRED_INIT def as "load" of 'b', but we
cannot really distinguish this from

void bar(_BitInt(7));
void baz(_BitInt(7) *);
void
foo ()
{
  _BitInt(7) b;
  bar (b);
  baz (&b);
}

we fail to optimize this btw:

  <bb 2> [local count: 1073741824]:
  _1 = .DEFERRED_INIT (1, 1, &"b"[0]);
  _9 = VIEW_CONVERT_EXPR<char>(_1);
  _3 = _9 & 127;
  MEM[(_BitInt(7) *)&b] = _3;
  b.0_2 = b;
  bar (b.0_2);
  baz (&b);
  b ={v} {CLOBBER(eos)};
  return;

the load to b.0_2 is not elided somehow.

So I'm not adding a match to any .DEFERRED_INIT def.
Comment 18 GCC Commits 2026-06-24 11:46:04 UTC
The master branch has been updated by Richard Biener <rguenth@gcc.gnu.org>:

https://gcc.gnu.org/g:df1eb95b6e915117dc33cf70dbd46be66c23057f

commit r17-1801-gdf1eb95b6e915117dc33cf70dbd46be66c23057f
Author: Richard Biener <rguenther@suse.de>
Date:   Wed Jun 24 09:36:49 2026 +0200

    tree-optimization/110743 - expand RMW uninit detection
    
    The following expands the RMW bit clear/set pattern detection to
    also cover VIEW_CONVERT_EXPR as it appears when generic word_mode
    vectorization is involved.
    
            PR tree-optimization/110743
            * tree-ssa-uninit.cc (maybe_warn_operand): Also cover
            VIEW_CONVERT_EXPR uses for RMW bit clear/set pattern
            detection.
    
            * gcc.dg/uninit-pr110743-2.c: New testcase.
Comment 19 Richard Biener 2026-06-24 12:25:23 UTC
Arnd, please try again (the preprocessed source is now free of uninit diagnostics).
Comment 20 Arnd Bergmann 2026-06-25 06:54:42 UTC
Confirmed, this addresses both instances I'm seeing in the kernel. Thanks a lot for the fix!