llvm / llvm/llvm-project

[Clang][Sema] Clang miscompiles parenthesized aggregate member initialization in templates

Open
#213,284 3 comments 0 reactions 0 assignees View on GitHub
clang:frontend miscompilation
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

Clang seems to miscompile [parenthesized aggregate member initialization](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0960r3.html) when instantiated from a templated class/struct.

repro.cpp:
```cpp
struct Ref {
unsigned long long bits;
};

template
struct Result {
Result() : thing(0) {}
Ref thing;
};

Result construct()
{
return Result();
}
```
repro.ll (with clang 22.1.3):
```ll
; ModuleID = 'repro.cpp'
source_filename = "repro.cpp"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc19.51.36248"

%struct.Result = type { %struct.Ref }
%struct.Ref = type { i64 }

$"??0?$Result@X@@QEAA@XZ" = comdat any

; Function Attrs: mustprogress noinline optnone uwtable
define dso_local void @"?construct@@YA?AU?$Result@X@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.Result) align 8 %0) #0 {
%2 = alloca ptr, align 8
store ptr %0, ptr %2, align 8
%3 = call noundef ptr @"??0?$Result@X@@QEAA@XZ"(ptr noundef nonnull align 8 dereferenceable(8) %0)
ret void
}

; Function Attrs: mustprogress noinline nounwind optnone uwtable
define linkonce_odr dso_local noundef ptr @"??0?$Result@X@@QEAA@XZ"(ptr noundef nonnull returned align 8 dereferenceable(8) %0) unnamed_addr #1 comdat align 2 {
%2 = alloca ptr, align 8
store ptr %0, ptr %2, align 8
%3 = load ptr, ptr %2, align 8
%4 = getelementptr inbounds nuw %struct.Result, ptr %3, i32 0, i32 0
%5 = getelementptr inbounds nuw %struct.Ref, ptr %4, i32 0, i32 0
store i32 0, ptr %5, align 8
ret ptr %3
}

attributes #0 = { mustprogress noinline optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { mustprogress noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3, !4, !5, !6}
!llvm.ident = !{!7}

!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 22.1.3 (https://github.com/llvm/llvm-project e9846648fd6183ee6d8cbdb4502213fcf902a211)", isOptimized: false, runtimeVersion: 0, emissionKind: NoDebug, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "repro.cpp", directory: ".")
!2 = !{i32 2, !"Debug Info Version", i32 3}
!3 = !{i32 1, !"wchar_size", i32 2}
!4 = !{i32 8, !"PIC Level", i32 2}
!5 = !{i32 7, !"uwtable", i32 2}
!6 = !{i32 1, !"MaxTLSAlign", i32 65536}
!7 = !{!"clang version 22.1.3 (https://github.com/llvm/llvm-project e9846648fd6183ee6d8cbdb4502213fcf902a211)"}
```

I expected the `store i32 0, ptr %5, align 8` in the Result constructor to be a `store i64` instead.

Godbolt demo: [clang 20.1.0 (correct)](https://godbolt.org/z/TMhea4rfW) [clang 21.1.0 (regression)](https://godbolt.org/z/PjYcjn9z3) [clang trunk (also miscompiles)](https://godbolt.org/z/KMYWvxT5f) [gcc 16.1 (correct)](https://godbolt.org/z/vKWYcY1P8) [msvc v19.51 VS18.6 (correct)](https://godbolt.org/z/x6cjEEaqq)

I don't know much at all about the Clang or LLVM codebase so I am not confident about submitting a pull request, but I found this PR (#142988) which fixes the same kind of issue in new expressions and pattern-matched the same [fix](https://github.com/llvm/llvm-project/pull/142988/changes/c7154b0b2bd6457d908bb7fbedc028219f8b5e6d) on the member initializer code and it fixed the miscompilation for the minimized repro. Here is the patch in case it is useful:
```patch
@@ -4657,6 +4657,9 @@ Sema::BuildMemberInitializer(ValueDecl *Member, Expr *Init,
Args = MultiExprArg(ParenList->getExprs(), ParenList->getNumExprs());
} else if (InitListExpr *InitList = dyn_cast(Init)) {
Args = MultiExprArg(InitList->getInits(), InitList->getNumInits());
+ } else if (CXXParenListInitExpr *CXXList =
+ dyn_cast(Init)) {
+ Args = CXXList->getInitExprs();
} else {
// Template instantiation doesn't reconstruct ParenListExprs for us.
Args = Init;
```

The fix commit can be found here: https://github.com/hatgfx/llvm-project/commit/ae0adeb6ae762276ce7f1dc22b65b68e953410a5

repro.ll with clang 24.0.0git ae0adeb6ae762276ce7f1dc22b65b68e953410a5

> type repro.cpp
```cpp
struct Ref {
unsigned long long bits;
};

template
struct Result {
Result() : thing(0) {}
Ref thing;
};

Result construct()
{
return Result();
}
```
> .\clang.exe -std=c++20 -S -emit-llvm "-fdebug-prefix-map=$((Get-Location).Path)=." repro.cpp
> type repro.ll
```ll
; ModuleID = 'repro.cpp'
source_filename = "repro.cpp"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc19.51.36248"

%struct.Result = type { %struct.Ref }
%struct.Ref = type { i64 }

$"??0?$Result@X@@QEAA@XZ" = comdat any

; Function Attrs: mustprogress noinline optnone uwtable
define dso_local void @"?construct@@YA?AU?$Result@X@@XZ"(ptr dead_on_unwind noalias writable sret(%struct.Result) align 8 %agg.result) #0 {
entry:
%result.ptr = alloca ptr, align 8
store ptr %agg.result, ptr %result.ptr, align 8
%call = call noundef ptr @"??0?$Result@X@@QEAA@XZ"(ptr noundef nonnull align 8 dereferenceable(8) %agg.result)
ret void
}

; Function Attrs: mustprogress noinline nounwind optnone uwtable
define linkonce_odr dso_local noundef ptr @"??0?$Result@X@@QEAA@XZ"(ptr noundef nonnull returned align 8 dereferenceable(8) %this) unnamed_addr #1 comdat align 2 {
entry:
%this.addr = alloca ptr, align 8
store ptr %this, ptr %this.addr, align 8
%this1 = load ptr, ptr %this.addr, align 8
%thing = getelementptr inbounds nuw %struct.Result, ptr %this1, i32 0, i32 0
%bits = getelementptr inbounds nuw %struct.Ref, ptr %thing, i32 0, i32 0
store i64 0, ptr %bits, align 8
ret ptr %this1
}

attributes #0 = { mustprogress noinline optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { mustprogress noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cmov,+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3, !4, !5}
!llvm.ident = !{!6}

!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 24.0.0git (https://github.com/hatgfx/llvm-project ae0adeb6ae762276ce7f1dc22b65b68e953410a5)", isOptimized: false, runtimeVersion: 0, emissionKind: NoDebug, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "repro.cpp", directory: ".")
!2 = !{i32 2, !"Debug Info Version", i32 3}
!3 = !{i32 8, !"PIC Level", i32 2}
!4 = !{i32 7, !"uwtable", i32 2}
!5 = !{i32 1, !"MaxTLSAlign", i32 65536}
!6 = !{!"clang version 24.0.0git (https://github.com/hatgfx/llvm-project ae0adeb6ae762276ce7f1dc22b65b68e953410a5)"}
```

**AI disclosure:** After finding the miscompilation in my code while compiling it with clang-cl, I used GPT 5.6 Sol in Codex to verify that it was indeed a miscompilation, cloned the llvm-project, and asked it to help me explore the codebase and narrow down what could be the issue.

Contributor guide

Open the contributing guide

Research direction

Start at Sema::BuildMemberInitializer and compare its handling of InitListExpr, ParenListExpr, and CXXParenListInitExpr with the related new-expression fix in PR #142988. Use the repro.cpp example and emit LLVM IR with clang-cl; done means the templated constructor stores an i64 value for Ref::bits rather than i32, with a regression test covering the case.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.