blob: 1972841fce5ded4a7f7a0a6f68b09568b59de3e7 [file] [log] [blame]
// This file aims to test basic correctness of frame lowering done
// by the compiler.
//
// On at least the AArch64 architecture, the main potential type of
// areas in a stack frame are:
// * incoming arguments.
// * callee-saved registers
// * padding space for aligning local variables more than what's guaranteed
// by the ABI, e.g. by C11's _Alignas keyword, C++11's alignas keyword, or
// gnu's __attribute__((aligned__(x)))
// * local variables/spill slots
// * variable-length arrays
//
// All - or almost all? - combinations of these areas being present or
// not are possible - depending on features used in the function the
// stack frame belongs to.
//
// In this test file, templates and template meta-programming is used
// to create a function instantiation for all combinations of a
// particular area being present/non-empty on the stack frame.
// Look for the template "test_frame" function below.
//
// For each of these instantiated template functions, the following
// things are checked, to ensure the compiler correctly compiled the
// code:
// * an exception can be correctly thrown across the function.
// * check arguments values are as expected.
// * The locals/VLAs that require alignment are indeed aligned as expected,
// and have the expected values.
// * callee-save registers were saved/restored correctly
//
// The checking is implemented by constructing a "trace" of information
// while the function is executing. After the function has executed, the
// trace is checked for expected values. A more direct implementation, e.g.
// checking values during the execution of the function, would not make
// it possible to construct some of the functions in the testing space,
// e.g. leaf functions.
#include <cstring>
#include <cassert>
#include <iostream>
#include <string>
#include <climits>
#include <vector>
/* LotsOfLiveValues is a helper class to produce high register pressure, i.e.
lots of
expensive-to-recompute live values. */
template <typename T> struct LotsOfLiveValues {
__attribute__((always_inline)) LotsOfLiveValues(const T seed)
: d1(seed), d2(d1 + d1), d3(d1 + d2), d4(d1 + d3), d5(d1 + d4),
d6(d1 + d5), d7(d1 + d6), d8(d1 + d7), d9(d1 + d8), d10(d1 + d9),
d11(d1 + d10), d12(d1 + d11), d13(d1 + d12), d14(d1 + d13),
d15(d1 + d14), d16(d1 + d15), d17(d1 + d16), d18(d1 + d17),
d19(d1 + d18), d20(d1 + d19), d21(d1 + d20), d22(d1 + d21),
d23(d1 + d22), d24(d1 + d23), d25(d1 + d24), d26(d1 + d25),
d27(d1 + d26), d28(d1 + d27), d29(d1 + d28), d30(d1 + d29),
d31(d1 + d30), d32(d1 + d31), d33(d1 + d32), d34(d1 + d33),
d35(d1 + d34), d36(d1 + d35), d37(d1 + d36), d38(d1 + d37),
d39(d1 + d38) {}
T d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d14, d15, d16, d17,
d18, d19, d20, d21, d22, d23, d24, d25, d26, d27, d28, d29, d30, d31, d32,
d33, d34, d35, d36, d37, d38, d39;
__attribute__((always_inline)) T makeLive() const {
return d39 / d38 / d37 / d36 / d35 / d34 / d33 / d32 / d31 / d30 / d29 /
d28 / d27 / d26 / d25 / d24 / d23 / d22 / d21 / d20 / d19 / d18 /
d17 / d16 / d15 / d14 / d13 / d12 / d11 / d10 / d9 / d8 / d7 / d6 /
d5 / d4 / d3 / d2 / d1;
}
__attribute__((always_inline)) T consumeLiveValues(const T dep_value) const {
return (d1 / d2 / d3 / d4 / d5 / d6 / d7 / d8 / d9 / d10 / d11 / d12 / d13 /
d14 / d15 / d16 / d17 / d18 / d19 / d20 / d21 / d22 / d23 / d24 /
d25 / d26 / d27 / d28 / d29 / d30 / d31 / d32 / d33 / d34 / d35 /
d36 / d37 / d38 / d39 / dep_value);
}
__attribute__((always_inline)) T produce_register_pressure() const {
return makeLive() * consumeLiveValues(d1);
}
};
static bool is_alignment_as_expected(const void *p,
const unsigned expected_alignment) {
return ((std::ptrdiff_t)p & (((std::ptrdiff_t)expected_alignment) - 1)) == 0;
}
template <typename T>
static bool check_and_print_value_and_alignment(
const char *variant, const char *label, const unsigned expected_alignment,
const void *p, const T value, const T expected_value) {
const bool alignment_ok = is_alignment_as_expected(p, expected_alignment);
const bool value_ok = value == expected_value;
std::cout << variant << ", " << label
<< ", alignment: " << expected_alignment;
if (alignment_ok && value_ok)
std::cout << " OK";
else {
if (!alignment_ok)
std::cout << " ALIGNMENT NOT AS EXPECTED: " << p;
if (!value_ok)
std::cout << " CONTENT NOT AS EXPECTED: " << value << " instead of "
<< expected_value;
}
std::cout << std::endl;
return !(alignment_ok && value_ok);
}
union trace_element {
void *p;
int l;
double d;
};
__attribute__((noinline)) bool check_trace(
const char *variant, const char *input, const trace_element *trace,
const double expected_d_value, const unsigned locals_alignment,
const bool with_vla, const unsigned vla_size, const unsigned vla_alignment,
const bool force_callee_save, const bool throw_exception,
const int exception_value) {
bool problem_found = false;
// see definition of test_frame function below for what to expect & test in
// trace.
// 1. check argument passing
const double d_value = trace++->d;
problem_found = check_and_print_value_and_alignment<double>(
variant, "arg_passing", 0, 0, d_value, expected_d_value);
// 2. check locals
const int aligned_int_value = trace++->l;
const void *aligned_int_address = trace++->p;
const double aligned_double_value = trace++->d;
const void *aligned_double_address = trace++->p;
problem_found |= check_and_print_value_and_alignment<int>(
variant, "int_local", locals_alignment, aligned_int_address,
aligned_int_value, (int)*input++);
problem_found |= check_and_print_value_and_alignment<double>(
variant, "double_local", locals_alignment, aligned_double_address,
aligned_double_value, (double)*input++);
// 3. check vla
if (with_vla) {
// just copy to the first and last byte in the vla to check correct vla
// access
const char vla_content_begin_expected = *input++;
const char vla_content_end_expected = *input++;
const char vla_content_begin = trace++->l;
const void *address_of_vla_begin = trace++->p;
const char vla_content_end = trace++->l;
const void *address_of_vla_end = trace++->p;
problem_found |= check_and_print_value_and_alignment<char>(
variant, "vla_start", vla_alignment, address_of_vla_begin,
vla_content_begin, vla_content_begin_expected);
problem_found |= check_and_print_value_and_alignment<char>(
variant, "vla_end", 1, address_of_vla_end, vla_content_end,
vla_content_end_expected);
}
// 4. callee-save triggering
if (force_callee_save) {
LotsOfLiveValues<int> i(*input++);
LotsOfLiveValues<double> d(*input++);
const int expected_int_result = i.produce_register_pressure();
const double expected_double_result = d.produce_register_pressure();
problem_found |= check_and_print_value_and_alignment<char>(
variant, "callee_save_int", 1, 0, trace++->l, expected_int_result);
problem_found |= check_and_print_value_and_alignment<char>(
variant, "callee_save_double", 1, 0, trace++->d,
expected_double_result);
}
// 6. exception unwinding happened correctly
int expected_exception_value = (throw_exception) ? 42 : -1;
problem_found |= check_and_print_value_and_alignment<int>(
variant, "thrown_value", 1, 0, exception_value, expected_exception_value);
return problem_found;
}
void function_throwing_exception();
// 5. checking correct operation when arguments need to be passed on the stack.
// This is done by the ...ExtraArgs variadice template argument.
template <unsigned locals_alignment, bool with_vla, unsigned vla_alignment,
bool force_callee_save_and_spills, bool throw_exception,
class... ExtraArgs>
__attribute__((noinline)) void test_frame(ExtraArgs..., const unsigned vla_size,
const char *input,
trace_element *trace,
const double d_argument) {
// 1. check argument passing
// check correct passing of the double argument.
// correct passing of integer/pointer arguments is already checked by this
// function
// using the vla_size/input/trace arguments in multiple places.
trace++->d = d_argument;
// 2. checking correct access to potentially aligned locals
volatile int aligned_int __attribute__((__aligned__(locals_alignment)));
volatile double aligned_double __attribute__((__aligned__(locals_alignment)));
aligned_int = (int)*input++;
aligned_double = (double)*input++;
trace++->l = aligned_int;
trace++->p = (void *)&aligned_int;
trace++->d = aligned_double;
trace++->p = (void *)&aligned_double;
// 3. checking correct access to potentially aligned vlas
if (with_vla) {
volatile char aligned_vla[vla_size]
__attribute__((__aligned__(vla_alignment)));
// just copy to the first and last byte in the vla to check correct vla
// access
aligned_vla[0] = *input++;
aligned_vla[vla_size - 1] = *input++;
trace++->l = aligned_vla[0];
trace++->p = (void *)&(aligned_vla[0]);
trace++->l = aligned_vla[vla_size - 1];
trace++->p = (void *)&(aligned_vla[vla_size - 1]);
}
// 4. checking correct operation when callee-save registers are very likely to
// be saved/restored.
// To do that, instantiate code with lots of overlapping live ranges that
// will force the
// register allocator to also use the callee-save registers + force use of
// spill/fill
// slots.
if (force_callee_save_and_spills) {
// to check correct restoring of callee-save registers, the caller of this
// function makes
// sure there are a lot of live ranges crossing the call to this function.
LotsOfLiveValues<int> i(*input++);
LotsOfLiveValues<double> d(*input++);
trace++->l = i.produce_register_pressure();
trace++->d = d.produce_register_pressure();
}
// 6. Check that the frame gets unwound correctly when an exception is thrown
// accros this function.
if (throw_exception) {
function_throwing_exception();
}
}
const std::vector<const char *> testDescriptionHeader(
{// read each column from top to bottom for the below to make sense.
"AVCAE", "LLARX", "IALGC", "G|LSE", "N|E P", "E|EOT", "D| NI", " |S O",
"L|ASN", "O|VT|", "C|EA|", "A||C|", "L||K|", "S||||", "|||||", "VVVVV",
});
const std::string getTestDescriptionString(bool overaligned_locals,
bool with_vla,
bool force_callee_save_and_spills,
bool ensure_args_go_on_stack,
bool throw_exception) {
return std::string("") + (overaligned_locals ? "+" : "-") +
(with_vla ? "+" : "-") + (force_callee_save_and_spills ? "+" : "-") +
(ensure_args_go_on_stack ? "+" : "-") + (throw_exception ? "+" : "-");
}
template <bool overaligned_locals, bool with_vla,
bool force_callee_save_and_spills, bool ensure_args_go_on_stack,
bool throw_exception>
__attribute__((noinline)) bool check_frame_variant(
int seed, volatile int &dumping_ground) {
const char *input = "abcdefghijklmnopqrstuvwxyz";
union trace_element trace[100];
const int vla_size = 100;
int exception_value = -1;
constexpr int locals_alignment = overaligned_locals ? 4096 : 2;
constexpr int vla_alignment = 2048;
const std::string test_description = getTestDescriptionString(
overaligned_locals, with_vla, force_callee_save_and_spills,
ensure_args_go_on_stack, throw_exception);
// Need to check that callee-save registers are restored correctly, by making
// sure all registers are live at point where test_frame is called.
// 4. To check that callee save registers are saved & restored correctly over
// the function call, we're creating lots of live ranges across the call
// to test_frame, in the hope that this will force the register allocator
// to store live information into the callee save registers.
LotsOfLiveValues<double> liveValues_double(seed);
LotsOfLiveValues<int> liveValues_int(seed);
const double d_value =
liveValues_double.makeLive() + liveValues_int.makeLive();
try {
if (ensure_args_go_on_stack) {
// add lots of extra dummy arguments to make the information-passing
// argument go on
// the stack instead of registers.
// Assume that 20 integer and 20 floating point values is enough to
// completely
// fill up the available registers for argument passing
test_frame<locals_alignment, with_vla, vla_alignment,
force_callee_save_and_spills, throw_exception, int, int, int,
int, int, int, int, int, int, int, int, int, int, int, int,
int, int, int, int, int, double, double, double, double,
double, double, double, double, double, double, double, double,
double, double, double, double, double, double, double,
double>(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, vla_size, input, trace, d_value);
} else {
test_frame<locals_alignment, with_vla, vla_alignment,
force_callee_save_and_spills, throw_exception>(vla_size, input,
trace, d_value);
}
dumping_ground += (int)(liveValues_double.consumeLiveValues(trace[0].d) +
liveValues_int.consumeLiveValues(trace[0].l));
} catch (int i) {
exception_value = i;
}
return check_trace(test_description.c_str(), input, trace, d_value,
locals_alignment, with_vla, vla_size, vla_alignment,
force_callee_save_and_spills, throw_exception,
exception_value);
}
int main() {
bool problem_found = false;
volatile int dumping_ground;
const int seed = 42;
for (auto &s : testDescriptionHeader)
std::cout << s << std::endl;
const bool T = true;
const bool F = false;
problem_found |= check_frame_variant<T, F, F, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, F, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, F, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, F, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, F, T, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, T, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, T, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, T, F, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, F, F, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, F, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, F, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, F, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, F, T, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, T, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, T, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, T, T, F>(seed, dumping_ground);
problem_found |= check_frame_variant<T, F, F, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, F, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, F, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, F, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<T, F, T, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, T, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, T, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, T, F, T>(seed, dumping_ground);
problem_found |= check_frame_variant<T, F, F, T, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, F, T, T>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, F, T, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, F, T, T>(seed, dumping_ground);
problem_found |= check_frame_variant<T, F, T, T, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, F, T, T, T>(seed, dumping_ground);
problem_found |= check_frame_variant<T, T, T, T, T>(seed, dumping_ground);
problem_found |= check_frame_variant<F, T, T, T, T>(seed, dumping_ground);
return problem_found ? -1 : 0;
}