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.
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.
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
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.
(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?
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
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?
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);
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).
Another option might be to pattern match _1 = a; a = _1; from uninit and suppress diagnostics about that (allowing | and & modifications).
Testing a patch along that line.
Created attachment 64794 [details] patch
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.
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.
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; | ^~~~~~~~~~~~~~~~~~~
(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.
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); }
(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.
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.
Arnd, please try again (the preprocessed source is now free of uninit diagnostics).
Confirmed, this addresses both instances I'm seeing in the kernel. Thanks a lot for the fix!