Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions SingleSource/Regression/C/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 15 additions & 0 deletions SingleSource/Regression/C/musttail/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
60 changes: 60 additions & 0 deletions SingleSource/Regression/C/musttail/alias-distinct-addresses.c
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit 0
34 changes: 34 additions & 0 deletions SingleSource/Regression/C/musttail/computed-indirect.c
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit 0
42 changes: 42 additions & 0 deletions SingleSource/Regression/C/musttail/cross-bb-musttail.c
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit 0
28 changes: 28 additions & 0 deletions SingleSource/Regression/C/musttail/forward-indirect.c
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit 0
58 changes: 58 additions & 0 deletions SingleSource/Regression/C/musttail/local-source.c
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit 0
47 changes: 47 additions & 0 deletions SingleSource/Regression/C/musttail/mixed-indirect-spill.c
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit 0
56 changes: 56 additions & 0 deletions SingleSource/Regression/C/musttail/mixed-sources.c
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
exit 0
22 changes: 22 additions & 0 deletions SingleSource/Regression/C/musttail/musttail-common.h
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>

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
Loading