Files
Henry Baba-Weiss 6adef02db5 [X86][regcall] Rework struct classification for non-Windows x86-64 targets (#187134)
Currently, when `X86_64ABIInfo::classifyRegCallStructTypeImpl`
classifies a struct argument or return value as direct, it leaves the
LLVM IR coerce type unspecified, implicitly relying on
`CodeGenTypes::ConvertType` to eventually construct a default IR type
based on the struct's layout. This conversion is neither stable nor
guaranteed to adhere to the ABI's classification rules.

Instead, rewrite `classifyRegCallStructTypeImpl` to construct an
explicit sequence of coerce types, using the existing field
classification to obtain a coerce type for each member of the struct.
Also, rename the function to `passRegCallStructTypeDirectly` and return
a boolean instead, so that now `classifyRegCallStructType` is the only
place that computes `ABIArgInfo`.

This rewrite also fixes several other issues with the `X86_64ABIInfo`
implementation of `__regcall`:

* Empty structs are now ignored instead of being misclassified as
direct.
* Arrays are now classified specially based on the element type, since
`X86_64ABIInfo::classifyArgumentType` ignores standalone array types.
* SSE registers used for return values are now correctly reused for
arguments, matching the 64-bit Windows behavior.

Since this is an ABI change, it has the potential to cause
incompatibilities with `__regcall` code compiled by earlier versions of
Clang. Specifically:

* Because SSE return registers can now be reused as argument registers,
functions will now pass more floating point arguments in SSE registers.
* `_Complex float` struct fields are now passed in one SSE register
instead of two.

Fixes #62999
Fixes #98635
2026-04-13 13:03:39 -07:00

145 lines
8.2 KiB
C++

// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -std=c++11 %s -o - | FileCheck -allow-deprecated-dag-overlap -check-prefix=CHECK-LIN -check-prefix=CHECK-LIN64 %s
// RUN: %clang_cc1 -triple i386-linux-gnu -emit-llvm -std=c++11 %s -o - | FileCheck -allow-deprecated-dag-overlap -check-prefix=CHECK-LIN -check-prefix=CHECK-LIN32 %s
// RUN: %clang_cc1 -triple x86_64-windows-msvc -emit-llvm -std=c++11 %s -o - -DWIN_TEST | FileCheck -allow-deprecated-dag-overlap -check-prefix=CHECK-WIN64 %s
// RUN: %clang_cc1 -triple i386-windows-msvc -emit-llvm -std=c++11 %s -o - -DWIN_TEST | FileCheck -allow-deprecated-dag-overlap -check-prefix=CHECK-WIN32 %s
int __regcall foo(int i);
int main()
{
int p = 0, _data;
auto lambda = [&](int parameter) -> int {
_data = foo(parameter);
return _data;
};
return lambda(p);
}
// CHECK-LIN: call x86_regcallcc {{.+}} @_Z15__regcall3__foo
// CHECK-WIN64: call x86_regcallcc {{.+}} @"?foo@@YwHH@Z"
// CHECK-WIN32: call x86_regcallcc {{.+}} @"?foo@@YwHH@Z"
int __regcall foo (int i){
return i;
}
// CHECK-LIN: define{{.*}} x86_regcallcc noundef {{.+}}@_Z15__regcall3__foo
// CHECK-WIN64: define dso_local x86_regcallcc noundef {{.+}}@"?foo@@YwHH@Z"
// CHECK-WIN32: define dso_local x86_regcallcc noundef {{.+}}@"?foo@@YwHH@Z"
struct DynBase { int x; virtual void v() = 0; };
struct Dyn : public DynBase { double a; void v() {} };
void __regcall f1(Dyn a) { a.x = 42; }
// CHECK-LIN64: define dso_local x86_regcallcc void @_Z14__regcall3__f13Dyn(ptr noundef byval(%struct.Dyn) align 8 %a)
// CHECK-LIN32: define dso_local x86_regcallcc void @_Z14__regcall3__f13Dyn(ptr inreg noundef dead_on_return %a)
// CHECK-WIN64: define dso_local x86_regcallcc void @"?f1@@YwXUDyn@@@Z"(ptr noundef dead_on_return %a)
// CHECK-WIN32: define dso_local x86_regcallcc void @"?f1@@YwXUDyn@@@Z"(ptr inalloca(<{ %struct.Dyn }>) %0)
struct Base { int x; };
struct Derived : public Base { double a; };
void __regcall f2(Derived a) { a.x = 42; }
// CHECK-LIN64: define dso_local x86_regcallcc void @_Z14__regcall3__f27Derived(i32 %a.coerce0, double %a.coerce1)
// CHECK-LIN32: define dso_local x86_regcallcc void @_Z14__regcall3__f27Derived(ptr noundef byval(%struct.Derived) align 4 %a)
// CHECK-WIN64: define dso_local x86_regcallcc void @"?f2@@YwXUDerived@@@Z"(ptr noundef dead_on_return %a)
// CHECK-WIN32: define dso_local x86_regcallcc void @"?f2@@YwXUDerived@@@Z"(ptr noundef byval(%struct.Derived) align 4 %0)
struct Empty {};
struct NestedEmpty : public Empty { int x; };
void __regcall f3(NestedEmpty a) { a.x = 42; }
// CHECK-LIN64: define dso_local x86_regcallcc void @_Z14__regcall3__f311NestedEmpty(i32 %a.coerce)
// CHECK-LIN32: define dso_local x86_regcallcc void @_Z14__regcall3__f311NestedEmpty(ptr noundef byval(%struct.NestedEmpty) align 4 %a)
// CHECK-WIN64: define dso_local x86_regcallcc void @"?f3@@YwXUNestedEmpty@@@Z"(i32 %a.coerce)
// CHECK-WIN32: define dso_local x86_regcallcc void @"?f3@@YwXUNestedEmpty@@@Z"(i32 %a.0)
// used to give a body to test_class functions
static int x = 0;
class test_class {
int a;
public:
#ifndef WIN_TEST
__regcall
#endif
test_class(){++x;}
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc void @_ZN10test_classC1Ev
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc void @_ZN10test_classC2Ev
// Windows ignores calling convention on constructor/destructors.
// CHECK-WIN64-DAG: define linkonce_odr dso_local noundef ptr @"??0test_class@@QEAA@XZ"
// CHECK-WIN32-DAG: define linkonce_odr dso_local x86_thiscallcc noundef ptr @"??0test_class@@QAE@XZ"
#ifndef WIN_TEST
__regcall
#endif
~test_class(){--x;}
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc void @_ZN10test_classD2Ev
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc void @_ZN10test_classD1Ev
// Windows ignores calling convention on constructor/destructors.
// CHECK-WIN64-DAG: define linkonce_odr dso_local void @"??1test_class@@QEAA@XZ"
// CHECK-WIN32-DAG: define linkonce_odr dso_local x86_thiscallcc void @"??1test_class@@QAE@XZ"
test_class& __regcall operator+=(const test_class&){
return *this;
}
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc noundef nonnull align 4 dereferenceable(4) ptr @_ZN10test_classpLERKS_
// CHECK-WIN64-DAG: define linkonce_odr dso_local x86_regcallcc noundef nonnull align 4 dereferenceable(4) ptr @"??Ytest_class@@QEAwAEAV0@AEBV0@@Z"
// CHECK-WIN32-DAG: define linkonce_odr dso_local x86_regcallcc noundef nonnull align 4 dereferenceable(4) ptr @"??Ytest_class@@QAwAAV0@ABV0@@Z"
void __regcall do_thing(){}
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc void @_ZN10test_class20__regcall3__do_thingEv
// CHECK-WIN64-DAG: define linkonce_odr dso_local x86_regcallcc void @"?do_thing@test_class@@QEAwXXZ"
// CHECK-WIN32-DAG: define linkonce_odr dso_local x86_regcallcc void @"?do_thing@test_class@@QAwXXZ"
template<typename T>
void __regcall tempFunc(T i){}
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc void @_ZN10test_class20__regcall3__tempFuncIiEEvT_
// CHECK-WIN64-DAG: define linkonce_odr dso_local x86_regcallcc void @"??$freeTempFunc@H@@YwXH@Z"
// CHECK-WIN32-DAG: define linkonce_odr dso_local x86_regcallcc void @"??$freeTempFunc@H@@YwXH@Z"
};
bool __regcall operator ==(const test_class&, const test_class&){ --x; return false;}
// CHECK-LIN-DAG: define{{.*}} x86_regcallcc noundef zeroext i1 @_ZeqRK10test_classS1_
// CHECK-WIN64-DAG: define dso_local x86_regcallcc noundef zeroext i1 @"??8@Yw_NAEBVtest_class@@0@Z"
// CHECK-WIN32-DAG: define dso_local x86_regcallcc noundef zeroext i1 @"??8@Yw_NABVtest_class@@0@Z"
test_class __regcall operator""_test_class (unsigned long long) { ++x; return test_class{};}
// CHECK-LIN64-DAG: define{{.*}} x86_regcallcc void @_Zli11_test_classy(ptr dead_on_unwind noalias writable sret(%class.test_class) align 4 %agg.result, i64 noundef %0)
// CHECK-LIN32-DAG: define{{.*}} x86_regcallcc void @_Zli11_test_classy(ptr dead_on_unwind inreg noalias writable sret(%class.test_class) align 4 %agg.result, i64 noundef %0)
// CHECK-WIN64-DAG: ??__K_test_class@@Yw?AVtest_class@@_K@Z"
// CHECK-WIN32-DAG: ??__K_test_class@@Yw?AVtest_class@@_K@Z"
template<typename T>
void __regcall freeTempFunc(T i){}
// CHECK-LIN-DAG: define linkonce_odr x86_regcallcc void @_Z24__regcall3__freeTempFuncIiEvT_
// CHECK-WIN64-DAG: define linkonce_odr dso_local x86_regcallcc void @"??$freeTempFunc@H@@YwXH@Z"
// CHECK-WIN32-DAG: define linkonce_odr dso_local x86_regcallcc void @"??$freeTempFunc@H@@YwXH@Z"
// class to force generation of functions
void force_gen() {
test_class t;
test_class t2 = 12_test_class;
t += t2;
auto t3 = 100_test_class;
t3.tempFunc(1);
freeTempFunc(1);
t3.do_thing();
}
long double _Complex __regcall foo(long double _Complex f) {
return f;
}
// CHECK-LIN64-DAG: define{{.*}} x86_regcallcc void @_Z15__regcall3__fooCe(ptr dead_on_unwind noalias writable sret({ x86_fp80, x86_fp80 }) align 16 %agg.result, ptr noundef byval({ x86_fp80, x86_fp80 }) align 16 %f)
// CHECK-LIN32-DAG: define{{.*}} x86_regcallcc void @_Z15__regcall3__fooCe(ptr dead_on_unwind inreg noalias writable sret({ x86_fp80, x86_fp80 }) align 4 %agg.result, ptr noundef byval({ x86_fp80, x86_fp80 }) align 4 %f)
// CHECK-WIN64-DAG: define dso_local x86_regcallcc noundef { double, double } @"?foo@@YwU?$_Complex@O@__clang@@U12@@Z"(double noundef %f.0, double noundef %f.1)
// CHECK-WIN32-DAG: define dso_local x86_regcallcc noundef { double, double } @"?foo@@YwU?$_Complex@O@__clang@@U12@@Z"(double noundef %f.0, double noundef %f.1)
// The following caused us to dereference uninitialized memory. The long name
// seems necessary, as does the return types.
float _Complex __regcall callee(float _Complex f);
// CHECK-LIN64-DAG: declare x86_regcallcc noundef <2 x float> @_Z18__regcall3__calleeCf(<2 x float> noundef)
// CHECK-LIN32-DAG: declare x86_regcallcc noundef { float, float } @_Z18__regcall3__calleeCf(float noundef, float noundef)
// CHECK-WIN64-DAG: declare dso_local x86_regcallcc noundef { float, float } @"?callee@@YwU?$_Complex@M@__clang@@U12@@Z"(float noundef, float noundef)
// CHECK-WIN32-DAG: declare dso_local x86_regcallcc noundef { float, float } @"?callee@@YwU?$_Complex@M@__clang@@U12@@Z"(float noundef, float noundef)
__regcall int
some_really_long_name_that_manages_to_hit_the_right_spot_of_mem(int a) {
float _Complex x[2];
x[0] = callee(x[0]);
return a;
}