diff --git a/SingleSource/Regression/C/CMakeLists.txt b/SingleSource/Regression/C/CMakeLists.txt index d8a6dd825437..624d54eed537 100644 --- a/SingleSource/Regression/C/CMakeLists.txt +++ b/SingleSource/Regression/C/CMakeLists.txt @@ -5,6 +5,8 @@ if(ARCH MATCHES "x86" OR ARCH MATCHES "riscv" OR add_subdirectory(gcc-c-torture) endif() +add_subdirectory(musttail) + list(APPEND LDFLAGS -lm) list(APPEND CFLAGS -Wno-implicit-int) list(APPEND CFLAGS -Wno-error=incompatible-pointer-types) diff --git a/SingleSource/Regression/C/musttail/CMakeLists.txt b/SingleSource/Regression/C/musttail/CMakeLists.txt new file mode 100644 index 000000000000..f06b1e9a07ef --- /dev/null +++ b/SingleSource/Regression/C/musttail/CMakeLists.txt @@ -0,0 +1,15 @@ +# Regression tests for `__attribute__((musttail))` argument forwarding. +# +# Triggered by LLVM PR #185094, which fixed a RISC-V miscompile where +# indirectly-passed arguments were re-spilled into the caller's frame +# before a musttail call, leaving the forwarded pointer dangling after +# the tail call deallocated the frame. +# +# The tests cover paths shared with AArch64 (SVE-disabled) and x86_64: +# 256-bit struct args are too large for the by-value paths on all three, +# so they all flow through the indirect-arg / sret machinery. +# +# Gated on Clang because GCC does not support __attribute__((musttail)). +if(CMAKE_C_COMPILER_ID STREQUAL "Clang") + llvm_singlesource(PREFIX "Regression-C-musttail-") +endif() diff --git a/SingleSource/Regression/C/musttail/alias-distinct-addresses.c b/SingleSource/Regression/C/musttail/alias-distinct-addresses.c new file mode 100644 index 000000000000..92fcd5cffc51 --- /dev/null +++ b/SingleSource/Regression/C/musttail/alias-distinct-addresses.c @@ -0,0 +1,60 @@ +// Same argument forwarded to two call slots: `C(a, a)`. The C ABI +// gives each by-value parameter its own storage; the callee can observe +// aliasing by writing through one slot and reading the other. +// +// `&x == &y` would also expose the bug but the optimizer folds that +// check at -O1+ (it treats indirect params as noalias by ABI). The +// write-then-read-via-volatile probe survives optimization. +// +// Before llvm/llvm-project#199351, fork-clang's forwarding path passed +// the same incoming pointer to both slots for non-restrict struct args. +// Writing to `x` then reading `y` exposed the alias. +// +// REQUIRES: clang (for __attribute__((musttail))). + +#include "musttail-common.h" + +static const bigint kA = {{0xA1A1A1A1A1A1A1A1ULL, 0xA2A2A2A2A2A2A2A2ULL, + 0xA3A3A3A3A3A3A3A3ULL, 0xA4A4A4A4A4A4A4A4ULL}}; + +#define POISON 0xDEADBEEFCAFEBABEULL + +__attribute__((noinline)) static int callee(bigint x, bigint y) { + // Write a known poison value through x.parts[0]. Read y.parts[0] + // via a volatile pointer so the optimizer cannot reuse x's prior + // value or assume the load is independent. + x.parts[0] = POISON; + volatile unsigned long long *vy = (volatile unsigned long long *)&y; + unsigned long long via_y = vy[0]; + if (via_y == POISON) + return 1; + if (via_y != kA.parts[0]) + return 2; + if (x.parts[1] != kA.parts[1] || x.parts[2] != kA.parts[2] || + x.parts[3] != kA.parts[3]) + return 3; + return 0; +} + +__attribute__((noinline)) static int caller(bigint a, bigint b) { + (void)b; + __attribute__((musttail)) return callee(a, a); +} + +int main(void) { + bigint b = {{0, 0, 0, 0}}; + int r = caller(kA, b); + switch (r) { + case 1: + fprintf(stderr, "alias-distinct-addresses: writing to x changed y " + "(ABI violation)\n"); + break; + case 2: + fprintf(stderr, "alias-distinct-addresses: y corrupted\n"); + break; + case 3: + fprintf(stderr, "alias-distinct-addresses: x corrupted\n"); + break; + } + return r; +} diff --git a/SingleSource/Regression/C/musttail/alias-distinct-addresses.reference_output b/SingleSource/Regression/C/musttail/alias-distinct-addresses.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/alias-distinct-addresses.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/computed-indirect.c b/SingleSource/Regression/C/musttail/computed-indirect.c new file mode 100644 index 000000000000..ad29c0d89edf --- /dev/null +++ b/SingleSource/Regression/C/musttail/computed-indirect.c @@ -0,0 +1,34 @@ +// Computed indirect arg: caller modifies the bigint then musttails. This +// is the case folkertdev caught during LLVM PR #185094 review -- before +// the fix, the RISC-V backend silently dropped the modified value because +// it forwarded the original incoming pointer instead of storing the new +// value into it. + +#include "musttail-common.h" + +static const bigint kInput = {{0x0101010101010101ULL, 0x0202020202020202ULL, + 0x0303030303030303ULL, 0x0404040404040404ULL}}; + +static const bigint kExpected = {{0x0101010101010102ULL, 0x0202020202020203ULL, + 0x0303030303030304ULL, + 0x0404040404040405ULL}}; + +__attribute__((noinline)) static int callee(bigint a) { + return bigint_eq(&a, &kExpected) ? 0 : 1; +} + +__attribute__((noinline)) static int caller(bigint a) { + bigint b; + b.parts[0] = a.parts[0] + 1; + b.parts[1] = a.parts[1] + 1; + b.parts[2] = a.parts[2] + 1; + b.parts[3] = a.parts[3] + 1; + __attribute__((musttail)) return callee(b); +} + +int main(void) { + int r = caller(kInput); + if (r != 0) + fprintf(stderr, "computed-indirect: computed value lost or wrong\n"); + return r; +} diff --git a/SingleSource/Regression/C/musttail/computed-indirect.reference_output b/SingleSource/Regression/C/musttail/computed-indirect.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/computed-indirect.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/cross-bb-musttail.c b/SingleSource/Regression/C/musttail/cross-bb-musttail.c new file mode 100644 index 000000000000..b29cb50796dc --- /dev/null +++ b/SingleSource/Regression/C/musttail/cross-bb-musttail.c @@ -0,0 +1,42 @@ +// musttail behind a branch: the SDValue-lifetime regression in LLVM PR +// #185094 review. LowerFormalArguments runs in the entry BB and the +// musttail lives in a successor BB. Storing the indirect-arg pointer as +// an SDValue across the BB boundary was invalidated by CurDAG->clear(); +// the vreg-based fix keeps the pointer alive across BBs. + +#include "musttail-common.h" + +static const bigint kInput = {{0xC0DEC0DEC0DEC0DEULL, 0xDEADBEEFDEADBEEFULL, + 0xFEEDFACEFEEDFACEULL, 0xCAFEBABECAFEBABEULL}}; + +__attribute__((noinline)) static int callee(bigint a, int cond) { + (void)cond; + return bigint_eq(&a, &kInput) ? 0 : 1; +} + +static volatile int gCond = 1; + +__attribute__((noinline)) static int caller(bigint a, int cond) { + if (cond) { + __attribute__((musttail)) return callee(a, cond); + } + return 99; +} + +static volatile int gFalse = 0; + +int main(void) { + int r = caller(kInput, gCond); + if (r != 0) { + fprintf(stderr, "cross-bb-musttail: forwarded ptr corrupted across BBs\n"); + return r; + } + // Exercise the false branch too so the entry-block CopyToReg is not the + // only path validated. + int s = caller(kInput, gFalse); + if (s != 99) { + fprintf(stderr, "cross-bb-musttail: false branch returned %d, expected 99\n", s); + return 2; + } + return 0; +} diff --git a/SingleSource/Regression/C/musttail/cross-bb-musttail.reference_output b/SingleSource/Regression/C/musttail/cross-bb-musttail.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/cross-bb-musttail.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/forward-indirect.c b/SingleSource/Regression/C/musttail/forward-indirect.c new file mode 100644 index 000000000000..bb9e021f66f1 --- /dev/null +++ b/SingleSource/Regression/C/musttail/forward-indirect.c @@ -0,0 +1,28 @@ +// Forwarded indirect arg: caller receives a 256-bit bigint and musttails +// to a callee that takes the same value. The arg is passed indirect by +// the RISC-V and AArch64 ABIs (struct > 2*XLEN). Before LLVM PR #185094, +// the RISC-V backend allocated a fresh stack temporary in the caller and +// passed a pointer that became dangling after the tail call deallocated +// the caller's frame. +// +// REQUIRES: clang (for __attribute__((musttail))). + +#include "musttail-common.h" + +static const bigint kInput = {{0x1111111111111111ULL, 0x2222222222222222ULL, + 0x3333333333333333ULL, 0x4444444444444444ULL}}; + +__attribute__((noinline)) static int callee(bigint a) { + return bigint_eq(&a, &kInput) ? 0 : 1; +} + +__attribute__((noinline)) static int caller(bigint a) { + __attribute__((musttail)) return callee(a); +} + +int main(void) { + int r = caller(kInput); + if (r != 0) + fprintf(stderr, "forward-indirect: arg corrupted across musttail\n"); + return r; +} diff --git a/SingleSource/Regression/C/musttail/forward-indirect.reference_output b/SingleSource/Regression/C/musttail/forward-indirect.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/forward-indirect.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/local-source.c b/SingleSource/Regression/C/musttail/local-source.c new file mode 100644 index 000000000000..417fa15735d6 --- /dev/null +++ b/SingleSource/Regression/C/musttail/local-source.c @@ -0,0 +1,58 @@ +// musttail with a local-variable source: `C(local)`. The value lives +// in the caller's frame, which the tail call deallocates. The frontend +// must route the value through the incoming-parameter storage (which +// survives) rather than forwarding a pointer to the local. +// +// The local is computed at runtime from a `volatile` seed so the +// optimizer cannot fold it into a constant and skip the byval-temp +// pattern this test is designed to expose. +// +// REQUIRES: clang (for __attribute__((musttail))). + +#include "musttail-common.h" + +static volatile unsigned long long gSeed = 0x4242424242424242ULL; + +__attribute__((noinline)) static int callee(bigint a) { + unsigned long long seed = gSeed; + if (a.parts[0] != seed) + return 1; + if (a.parts[1] != (seed ^ 0xFFULL)) + return 2; + if (a.parts[2] != (seed + 1)) + return 3; + if (a.parts[3] != ~seed) + return 4; + return 0; +} + +__attribute__((noinline)) static int caller(bigint incoming) { + (void)incoming; + unsigned long long seed = gSeed; + bigint local; + local.parts[0] = seed; + local.parts[1] = seed ^ 0xFFULL; + local.parts[2] = seed + 1; + local.parts[3] = ~seed; + __attribute__((musttail)) return callee(local); +} + +int main(void) { + bigint dummy = {{0, 0, 0, 0}}; + int r = caller(dummy); + switch (r) { + case 1: + fprintf(stderr, "local-source: parts[0] corrupted\n"); + break; + case 2: + fprintf(stderr, "local-source: parts[1] corrupted\n"); + break; + case 3: + fprintf(stderr, "local-source: parts[2] corrupted\n"); + break; + case 4: + fprintf(stderr, "local-source: parts[3] corrupted\n"); + break; + } + return r; +} diff --git a/SingleSource/Regression/C/musttail/local-source.reference_output b/SingleSource/Regression/C/musttail/local-source.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/local-source.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/mixed-indirect-spill.c b/SingleSource/Regression/C/musttail/mixed-indirect-spill.c new file mode 100644 index 000000000000..58b1c8d20672 --- /dev/null +++ b/SingleSource/Regression/C/musttail/mixed-indirect-spill.c @@ -0,0 +1,47 @@ +// Indirect bigint + 9 i32 args on the same musttail call. Exercises both +// the indirect-arg forwarding path (LLVM PR #185094) and the stack-spill +// bypass on the same call site. + +#include "musttail-common.h" + +static const bigint kBig = {{0x1234567890ABCDEFULL, 0xFEDCBA0987654321ULL, + 0xAAAAAAAAAAAAAAAAULL, 0x5555555555555555ULL}}; + +__attribute__((noinline)) static int callee(bigint big, int a, int b, int c, + int d, int e, int f, int g, int h, + int i) { + if (!bigint_eq(&big, &kBig)) + return 100; + if (a != 1) + return 1; + if (b != 2) + return 2; + if (c != 3) + return 3; + if (d != 4) + return 4; + if (e != 5) + return 5; + if (f != 6) + return 6; + if (g != 7) + return 7; + if (h != 8) + return 8; + if (i != 9) + return 9; + return 0; +} + +__attribute__((noinline)) static int caller(bigint big, int a, int b, int c, + int d, int e, int f, int g, int h, + int i) { + __attribute__((musttail)) return callee(big, a, b, c, d, e, f, g, h, i); +} + +int main(void) { + int r = caller(kBig, 1, 2, 3, 4, 5, 6, 7, 8, 9); + if (r != 0) + fprintf(stderr, "mixed-indirect-spill: arg corrupted across musttail (got %d)\n", r); + return r; +} diff --git a/SingleSource/Regression/C/musttail/mixed-indirect-spill.reference_output b/SingleSource/Regression/C/musttail/mixed-indirect-spill.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/mixed-indirect-spill.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/mixed-sources.c b/SingleSource/Regression/C/musttail/mixed-sources.c new file mode 100644 index 000000000000..2f2d12c5e3ad --- /dev/null +++ b/SingleSource/Regression/C/musttail/mixed-sources.c @@ -0,0 +1,56 @@ +// musttail with one local source and one forwarded-incoming source: +// `C(local, a)`. The two slots take different code paths inside the +// frontend's two-phase emit: slot 0 copies the runtime-computed local +// into the i=0 incoming pointer; slot 1 forwards the incoming arg. +// +// The callee receives two values; slot 0 holds the local, slot 1 holds +// the original incoming. The local is computed from a volatile seed to +// defeat constant folding. +// +// REQUIRES: clang (for __attribute__((musttail))). + +#include "musttail-common.h" + +static volatile unsigned long long gSeed = 0x1234567812345678ULL; + +static const bigint kIncoming = {{0xC0FFEEC0FFEEC0FFULL, 0xEEC0FFEEC0FFEEC0ULL, + 0xFFEEC0FFEEC0FFEEULL, 0xC0FFEEC0FFEEC0FFULL}}; + +__attribute__((noinline)) static int callee(bigint x, bigint y) { + unsigned long long seed = gSeed; + if (x.parts[0] != seed) + return 1; + if (x.parts[1] != (seed + 1)) + return 2; + if (!bigint_eq(&y, &kIncoming)) + return 3; + return 0; +} + +__attribute__((noinline)) static int caller(bigint a, bigint b) { + (void)b; + unsigned long long seed = gSeed; + bigint local; + local.parts[0] = seed; + local.parts[1] = seed + 1; + local.parts[2] = seed + 2; + local.parts[3] = seed + 3; + __attribute__((musttail)) return callee(local, a); +} + +int main(void) { + bigint b = {{0, 0, 0, 0}}; + int r = caller(kIncoming, b); + switch (r) { + case 1: + fprintf(stderr, "mixed-sources: local slot parts[0] corrupted\n"); + break; + case 2: + fprintf(stderr, "mixed-sources: local slot parts[1] corrupted\n"); + break; + case 3: + fprintf(stderr, "mixed-sources: incoming slot corrupted\n"); + break; + } + return r; +} diff --git a/SingleSource/Regression/C/musttail/mixed-sources.reference_output b/SingleSource/Regression/C/musttail/mixed-sources.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/mixed-sources.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/musttail-common.h b/SingleSource/Regression/C/musttail/musttail-common.h new file mode 100644 index 000000000000..1cc8a65eb105 --- /dev/null +++ b/SingleSource/Regression/C/musttail/musttail-common.h @@ -0,0 +1,22 @@ +// Helpers shared by the musttail regression tests in this directory. +// +// The 256-bit `bigint` is the smallest type that is passed indirect by the +// RISC-V (RV32 and RV64) and AArch64 (SVE-disabled) ABIs. fp128 and i128 +// would also exercise the indirect path on RV32 specifically, but are not +// portable across the three targets we care about (RV, AArch64, x86_64). + +#ifndef MUSTTAIL_COMMON_H +#define MUSTTAIL_COMMON_H + +#include + +typedef struct { + unsigned long long parts[4]; +} bigint; + +static inline int bigint_eq(const bigint *a, const bigint *b) { + return a->parts[0] == b->parts[0] && a->parts[1] == b->parts[1] && + a->parts[2] == b->parts[2] && a->parts[3] == b->parts[3]; +} + +#endif diff --git a/SingleSource/Regression/C/musttail/sret-musttail.c b/SingleSource/Regression/C/musttail/sret-musttail.c new file mode 100644 index 000000000000..4d0aee56fb29 --- /dev/null +++ b/SingleSource/Regression/C/musttail/sret-musttail.c @@ -0,0 +1,26 @@ +// sret + musttail: caller returns a 256-bit struct, which is too large to +// return in registers on RISC-V and AArch64. Both caller and callee get an +// implicit sret pointer; the pointer is forwarded unchanged through the +// musttail. Confirms that the sret bypass in +// isEligibleForTailCallOptimization (added in LLVM PR #185094) is correct. + +#include "musttail-common.h" + +static const bigint kExpected = {{0xDEADBEEF00000001ULL, 0xDEADBEEF00000002ULL, + 0xDEADBEEF00000003ULL, + 0xDEADBEEF00000004ULL}}; + +__attribute__((noinline)) static bigint callee(void) { return kExpected; } + +__attribute__((noinline)) static bigint caller(void) { + __attribute__((musttail)) return callee(); +} + +int main(void) { + bigint got = caller(); + if (!bigint_eq(&got, &kExpected)) { + fprintf(stderr, "sret-musttail: returned bigint corrupted\n"); + return 1; + } + return 0; +} diff --git a/SingleSource/Regression/C/musttail/sret-musttail.reference_output b/SingleSource/Regression/C/musttail/sret-musttail.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/sret-musttail.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/stack-spill-10-i32.c b/SingleSource/Regression/C/musttail/stack-spill-10-i32.c new file mode 100644 index 000000000000..8392fcee30e3 --- /dev/null +++ b/SingleSource/Regression/C/musttail/stack-spill-10-i32.c @@ -0,0 +1,48 @@ +// 10 x i32 args. RISC-V and AArch64 pass the first 8 in registers and +// spill the rest to the caller's outgoing stack region. musttail reuses +// that incoming layout. This exercises the getStackSize() != 0 bypass +// in isEligibleForTailCallOptimization that LLVM PR #185094 reworked +// (the bypass already existed in some form on these backends; this +// test guards against future regressions of musttail with stack-passed +// args independently of the indirect-arg fix). + +#include "musttail-common.h" + +__attribute__((noinline)) static int callee(int a, int b, int c, int d, int e, + int f, int g, int h, int i, + int j) { + if (a != 1) + return 1; + if (b != 2) + return 2; + if (c != 3) + return 3; + if (d != 4) + return 4; + if (e != 5) + return 5; + if (f != 6) + return 6; + if (g != 7) + return 7; + if (h != 8) + return 8; + if (i != 9) + return 9; + if (j != 10) + return 10; + return 0; +} + +__attribute__((noinline)) static int caller(int a, int b, int c, int d, int e, + int f, int g, int h, int i, + int j) { + __attribute__((musttail)) return callee(a, b, c, d, e, f, g, h, i, j); +} + +int main(void) { + int r = caller(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + if (r != 0) + fprintf(stderr, "stack-spill-10-i32: arg slot mismatch (got %d)\n", r); + return r; +} diff --git a/SingleSource/Regression/C/musttail/stack-spill-10-i32.reference_output b/SingleSource/Regression/C/musttail/stack-spill-10-i32.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/stack-spill-10-i32.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/swap-alias.c b/SingleSource/Regression/C/musttail/swap-alias.c new file mode 100644 index 000000000000..f81ed7d4988f --- /dev/null +++ b/SingleSource/Regression/C/musttail/swap-alias.c @@ -0,0 +1,68 @@ +// Swap combined with aliasing: `C(b, a, a)`. Slot 0 takes the second +// incoming (`b`); slots 1 and 2 both take the first incoming (`a`). +// A two-phase emit that handles aliasing for a pure repeat `C(a, a, a)` +// can still fail this case if the swap-phase ordering leaves slot 2's +// read clobbered by slot 0's write to slot 1's incoming pointer. +// +// REQUIRES: clang (for __attribute__((musttail))). + +#include "musttail-common.h" + +static const bigint kA = {{0xA1A1A1A1A1A1A1A1ULL, 0xA2A2A2A2A2A2A2A2ULL, + 0xA3A3A3A3A3A3A3A3ULL, 0xA4A4A4A4A4A4A4A4ULL}}; + +static const bigint kB = {{0xB1B1B1B1B1B1B1B1ULL, 0xB2B2B2B2B2B2B2B2ULL, + 0xB3B3B3B3B3B3B3B3ULL, 0xB4B4B4B4B4B4B4B4ULL}}; + +#define POISON 0xDEADBEEFCAFEBABEULL + +__attribute__((noinline)) static int callee(bigint x, bigint y, bigint z) { + // Slot 0 should hold kB; slots 1 and 2 should both hold kA. + // Address-distinctness via write-then-read probe. + x.parts[0] = POISON; + volatile unsigned long long *vy = (volatile unsigned long long *)&y; + volatile unsigned long long *vz = (volatile unsigned long long *)&z; + unsigned long long via_y = vy[0]; + unsigned long long via_z = vz[0]; + if (via_y == POISON) + return 1; + if (via_z == POISON) + return 2; + if (via_y != kA.parts[0]) + return 3; + if (via_z != kA.parts[0]) + return 4; + // Sanity: x's other parts should still be kB. + if (x.parts[1] != kB.parts[1] || x.parts[2] != kB.parts[2] || + x.parts[3] != kB.parts[3]) + return 5; + return 0; +} + +__attribute__((noinline)) static int caller(bigint a, bigint b, bigint c) { + (void)c; + __attribute__((musttail)) return callee(b, a, a); +} + +int main(void) { + bigint zero = {{0, 0, 0, 0}}; + int r = caller(kA, kB, zero); + switch (r) { + case 1: + fprintf(stderr, "swap-alias: write to x changed y (slot 0/1 alias)\n"); + break; + case 2: + fprintf(stderr, "swap-alias: write to x changed z (slot 0/2 alias)\n"); + break; + case 3: + fprintf(stderr, "swap-alias: y is not kA\n"); + break; + case 4: + fprintf(stderr, "swap-alias: z is not kA\n"); + break; + case 5: + fprintf(stderr, "swap-alias: x (kB) corrupted\n"); + break; + } + return r; +} diff --git a/SingleSource/Regression/C/musttail/swap-alias.reference_output b/SingleSource/Regression/C/musttail/swap-alias.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/swap-alias.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/swapped-args.c b/SingleSource/Regression/C/musttail/swapped-args.c new file mode 100644 index 000000000000..802ea53a5be5 --- /dev/null +++ b/SingleSource/Regression/C/musttail/swapped-args.c @@ -0,0 +1,34 @@ +// Swapped indirect args: caller receives (a, b) and musttails callee(b, a). +// Before the OrigArgIndex fix in LLVM PR #185094, the RISC-V backend +// forwarded the wrong incoming pointers because InputArg::OrigArgIndex +// (unfiltered) and OutputArg::OrigArgIndex (filtered into CallBase::args) +// have different semantics, and the LowerCall side keyed off the wrong one. + +#include "musttail-common.h" + +static const bigint kA = {{0xAAAA000000000001ULL, 0xAAAA000000000002ULL, + 0xAAAA000000000003ULL, 0xAAAA000000000004ULL}}; + +static const bigint kB = {{0xBBBB000000000001ULL, 0xBBBB000000000002ULL, + 0xBBBB000000000003ULL, 0xBBBB000000000004ULL}}; + +__attribute__((noinline)) static int callee(bigint x, bigint y) { + if (!bigint_eq(&x, &kB)) + return 1; + if (!bigint_eq(&y, &kA)) + return 2; + return 0; +} + +__attribute__((noinline)) static int caller(bigint a, bigint b) { + __attribute__((musttail)) return callee(b, a); +} + +int main(void) { + int r = caller(kA, kB); + if (r == 1) + fprintf(stderr, "swapped-args: first arg not swapped\n"); + else if (r == 2) + fprintf(stderr, "swapped-args: second arg not swapped\n"); + return r; +} diff --git a/SingleSource/Regression/C/musttail/swapped-args.reference_output b/SingleSource/Regression/C/musttail/swapped-args.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/swapped-args.reference_output @@ -0,0 +1 @@ +exit 0 diff --git a/SingleSource/Regression/C/musttail/triple-alias.c b/SingleSource/Regression/C/musttail/triple-alias.c new file mode 100644 index 000000000000..0c704e9f3b44 --- /dev/null +++ b/SingleSource/Regression/C/musttail/triple-alias.c @@ -0,0 +1,57 @@ +// Same argument forwarded to three call slots: `C(a, a, a)`. +// Generalizes alias-distinct-addresses to three slots: the callee +// writes through one and observes the others via volatile reads. +// Two of the three observations must remain unaffected for the C ABI +// rule (distinct storage per by-value param) to hold. +// +// REQUIRES: clang (for __attribute__((musttail))). + +#include "musttail-common.h" + +static const bigint kA = {{0x1A1A1A1A1A1A1A1AULL, 0x2A2A2A2A2A2A2A2AULL, + 0x3A3A3A3A3A3A3A3AULL, 0x4A4A4A4A4A4A4A4AULL}}; + +#define POISON 0xDEADBEEFCAFEBABEULL + +__attribute__((noinline)) static int callee(bigint x, bigint y, bigint z) { + x.parts[0] = POISON; + volatile unsigned long long *vy = (volatile unsigned long long *)&y; + volatile unsigned long long *vz = (volatile unsigned long long *)&z; + unsigned long long via_y = vy[0]; + unsigned long long via_z = vz[0]; + if (via_y == POISON) + return 1; + if (via_z == POISON) + return 2; + if (via_y != kA.parts[0]) + return 3; + if (via_z != kA.parts[0]) + return 4; + return 0; +} + +__attribute__((noinline)) static int caller(bigint a, bigint b, bigint c) { + (void)b; + (void)c; + __attribute__((musttail)) return callee(a, a, a); +} + +int main(void) { + bigint zero = {{0, 0, 0, 0}}; + int r = caller(kA, zero, zero); + switch (r) { + case 1: + fprintf(stderr, "triple-alias: writing to x changed y (slot 0-1 alias)\n"); + break; + case 2: + fprintf(stderr, "triple-alias: writing to x changed z (slot 0-2 alias)\n"); + break; + case 3: + fprintf(stderr, "triple-alias: y corrupted\n"); + break; + case 4: + fprintf(stderr, "triple-alias: z corrupted\n"); + break; + } + return r; +} diff --git a/SingleSource/Regression/C/musttail/triple-alias.reference_output b/SingleSource/Regression/C/musttail/triple-alias.reference_output new file mode 100644 index 000000000000..ca916d098dab --- /dev/null +++ b/SingleSource/Regression/C/musttail/triple-alias.reference_output @@ -0,0 +1 @@ +exit 0