1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

tests/thread_stack_alignment: new test application

This commit is contained in:
Marian Buschsieweke 2022-09-25 21:22:14 +02:00
parent f57a15131e
commit 0253c478ec
No known key found for this signature in database
GPG Key ID: CB8E3238CE715A94
5 changed files with 190 additions and 0 deletions

View File

@ -0,0 +1,10 @@
include ../Makefile.tests_common
USEMODULE += printf_float
# On ESP* a custom sched_task_exit() is used that does not implement
# test_utils_print_stack_usage yet, which is needed by the test script
# to measure the worst case memory wasting when stacks are unaligned.
FEATURES_BLACKLIST += arch_esp
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,12 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
atmega328p-xplained-mini \
nucleo-f031k6 \
nucleo-l011k4 \
samd10-xmini \
stm32f030f4-demo \
#

View File

@ -0,0 +1,22 @@
Testing for Correct Stack Alignment
===================================
This test application asks the linker to align a stack to 128 B (assuming this
is the worst case alignment requirement). Not that features like the MPU may
result in much higher alignment requirements than the CPU actually has, thus
128 B is not crazy as it may sound. For each offset from 0 to 127 it will
then launch a thread using the aligned stack plus the current offset, thus
iterating over all possible stack alignments.
The test thread run `snprintf()` to format a double, compares the output with
the expected result, and exists to allow the subsequent thread to reuse the
stack. This is a good test for two reasons: Variadic functions (such as
`snprintf()`) on some platforms have different calling conventions that may
more easily trigger alignment issues, and an FPU may have a higher alignment
requirement than the CPU has.
The test is considered as passing if for all tested alignments the call to
`snprintf()` produces the correct result and no crash happens on the way.
Finally, the test script will collect the output of the stack consumptions and
give out the worst case penalty a user has to face

View File

@ -0,0 +1,106 @@
/*
* Copyright (C) 2022 Otto-von-Guericke-Universität Magdeburg
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Thread stack alignment test application
*
* @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
*
* @}
*/
#include <stdalign.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
/* work around bug with LLVM: <stdatomic.h> needs <stdint.h> included first,
* so do not sort this one alphabetically */
#include <stdatomic.h>
#include "irq.h"
#include "sched.h"
#include "thread.h"
#include "thread_arch.h"
#define PI_ROUNDED 3.14159
#define _QUOTE(x) #x
#define QUOTE(x) _QUOTE(x)
#define ALIGNMENT 128
#define STACKSIZE (THREAD_STACKSIZE_DEFAULT + THREAD_EXTRA_STACKSIZE_PRINTF \
+ ALIGNMENT)
static char alignas(ALIGNMENT) stack[STACKSIZE + ALIGNMENT];
static atomic_bool test_failed = ATOMIC_VAR_INIT(false);
static void *thread_func(void *arg)
{
(void)arg;
static const double pi_const = PI_ROUNDED;
static const char pi_str[] = QUOTE(PI_ROUNDED);
/* Force compiler to not optimize out the heavy lifting by loading the
* value of pi with a "volatile" read. The compiler must assume that the
* contents of pi are only known at runtime */
double pi = (volatile const double)pi_const;
char buf[16] = "";
/* Since snprintf() has a variable number of arguments, arguments are
* typically passed via stack even if the calling convention would pass
* arguments via registers most of the time. And typically double has
* the highest alignment requirement, so this is likely to run into
* issues when the stack is not aligned as the target arch requires it */
snprintf(buf, sizeof(buf) - 1, "%1.5f", pi);
if (0 != memcmp(pi_str, buf, sizeof(pi_str))) {
atomic_store(&test_failed, true);
puts("FAILED");
printf("Got \"%s\", expected \"%s\"\n", buf, pi_str);
return NULL;
}
puts("OK");
return NULL;
}
int main(void)
{
bool failed = 0;
printf("Testing with a stack sized %u and an alignment up to %u\n",
(unsigned)STACKSIZE, (unsigned)ALIGNMENT);
for (size_t i = 0; i < ALIGNMENT; i++) {
atomic_store(&test_failed, false);
printf("Testing for alignment %u: ", (unsigned)i);
kernel_pid_t p;
p = thread_create(stack + i, STACKSIZE, THREAD_PRIORITY_MAIN - 1,
THREAD_CREATE_STACKTEST,
thread_func, NULL, "test");
/* we expect that the new thread is scheduled to directly after it is
* created and this will only continue one the thread has terminated.
* But let's better be safe than sorry */
while (thread_get(p) != NULL) {
thread_yield();
}
if (atomic_load(&test_failed)) {
failed = true;
}
}
if (failed) {
puts("TEST FAILED");
}
else {
puts("TEST PASSED");
}
return 0;
}

View File

@ -0,0 +1,40 @@
#!/usr/bin/env python3
# Copyright (C) 2022 Otto-von-Guericke-Universität Magdeburg
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
# @author Marian Buschsieweke <marian.buschsieweke@ovgu.de>
import sys
import json
from testrunner import run
def testfunc(child):
child.expect(r"Testing with a stack sized (\d+) and an alignment up to (\d+)\r\n")
stack_size = int(child.match.group(1))
alignment = int(child.match.group(2))
stack_used_min = stack_size
stack_used_max = 0
for i in range(alignment):
child.expect_exact(f"Testing for alignment {i}: OK")
child.expect(r"(\{[^\n\r]*\})\r\n")
stats = json.loads(child.match.group(1))["threads"][0]
assert stats["name"] == "test"
if stack_used_max < stats["stack_used"]:
stack_used_max = stats["stack_used"]
if stack_used_min > stats["stack_used"]:
stack_used_min = stats["stack_used"]
child.expect_exact("TEST PASSED")
alignment_loss = stack_used_max - stack_used_min
if alignment_loss > 0:
print(f"NOTE: Up to {alignment_loss} B of RAM is lost when thread " +
"stacks are not properly aligned")
if __name__ == "__main__":
sys.exit(run(testfunc))