diff --git a/common/src/tx_thread_create.c b/common/src/tx_thread_create.c index cff4db406..256e2f17b 100644 --- a/common/src/tx_thread_create.c +++ b/common/src/tx_thread_create.c @@ -46,7 +46,7 @@ /* DESCRIPTION */ /* */ /* This function creates a thread and places it on the list of created */ -/* threads. */ +/* threads. Stack alignment preserves the full port-defined address. */ /* */ /* INPUT */ /* */ @@ -130,12 +130,11 @@ ULONG stack_fill_value; overflow conditions during run-time. */ stack_size = ((stack_size/(sizeof(ULONG))) * (sizeof(ULONG))) - (sizeof(ULONG)); - /* Ensure the starting stack address is evenly aligned. */ -#ifdef TX_MISRA_ENABLE - new_stack_start = TX_POINTER_TO_ULONG_CONVERT(stack_start); -#else - new_stack_start = TX_POINTER_TO_ALIGN_TYPE_CONVERT(stack_start); -#endif /* TX_MISRA_ENABLE */ + /* Ensure the starting stack address is evenly aligned. The port-defined + ALIGN_TYPE preserves all pointer bits, even when ULONG is narrower. + MISRA C:2012/2023 Rule 11.6 (C:2004 Rule 11.3) deviation: these + pointer/integer conversions are required for address alignment. */ + new_stack_start = (ALIGN_TYPE) ((VOID *) stack_start); updated_stack_start = (((new_stack_start) + ((sizeof(ULONG)) - ((ULONG) 1)) ) & (~((sizeof(ULONG)) - ((ULONG) 1)))); /* Determine if the starting stack address is different. */ @@ -147,11 +146,7 @@ ULONG stack_fill_value; } /* Update the starting stack pointer. */ -#ifdef TX_MISRA_ENABLE - stack_start = TX_ULONG_TO_POINTER_CONVERT(updated_stack_start); -#else - stack_start = TX_ALIGN_TYPE_TO_POINTER_CONVERT(updated_stack_start); -#endif /* TX_MISRA_ENABLE */ + stack_start = (VOID *) ((ALIGN_TYPE) updated_stack_start); #endif /* Prepare the thread control block prior to placing it on the created diff --git a/test/tx/cmake/CMakeLists.txt b/test/tx/cmake/CMakeLists.txt index 2f9c9b9f4..7ed620d9c 100644 --- a/test/tx/cmake/CMakeLists.txt +++ b/test/tx/cmake/CMakeLists.txt @@ -1,3 +1,15 @@ +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# SPDX-License-Identifier: MIT +############################################################################## + +# Portions of this file were generated with AI assistance. + cmake_minimum_required(VERSION 3.13 FATAL_ERROR) cmake_policy(SET CMP0054 NEW) cmake_policy(SET CMP0057 NEW) @@ -147,3 +159,11 @@ else() -Waggregate-return -Wfloat-equal) endif() + +# Preserve native32 host coverage and also exercise the Linux pointer-width +# mismatch with a native64 kernel, including MISRA plus stack checking. +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND THREADX_ARCH STREQUAL "linux" AND + THREADX_TOOLCHAIN STREQUAL "gnu" AND + CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$") + add_subdirectory(thread_stack_alignment) +endif() diff --git a/test/tx/cmake/thread_stack_alignment/CMakeLists.txt b/test/tx/cmake/thread_stack_alignment/CMakeLists.txt new file mode 100644 index 000000000..4040a0935 --- /dev/null +++ b/test/tx/cmake/thread_stack_alignment/CMakeLists.txt @@ -0,0 +1,89 @@ +############################################################################## +# Copyright (c) 2026 Eclipse ThreadX contributors +# +# This program and the accompanying materials are made available under the +# terms of the MIT License which is available at +# https://opensource.org/licenses/MIT. +# +# AI Disclosure: This file was largely AI-generated by Codex (gpt-6.1-sol). +# The AI-generated portions may be considered public domain (CC0-1.0) +# and not subject to the project's licence. The human contributor has +# reviewed and verified that the code is correct. +# +# SPDX-License-Identifier: MIT AND CC0-1.0 +############################################################################## + +cmake_minimum_required(VERSION 3.13 FATAL_ERROR) +project(thread_stack_alignment_test LANGUAGES C) + +set(CMAKE_C_STANDARD 99) +set(CMAKE_C_STANDARD_REQUIRED ON) +set(CMAKE_C_EXTENSIONS OFF) + +if(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux" OR + NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86_64|amd64|AMD64)$" OR + NOT CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang)$") + message(FATAL_ERROR "Stack alignment regression requires native x86_64 Linux/GNU port widths") +endif() + +# The host suite normally builds with -m32 and configuration-specific TX_ macros. +# Keep that coverage intact, but compile this directory independently at native64 +# width: the defect requires ALIGN_TYPE to be wider than ULONG. Clear only this +# directory's inherited options/definitions, then select the tested features. +set_property(DIRECTORY PROPERTY COMPILE_OPTIONS "") +set_property(DIRECTORY PROPERTY LINK_OPTIONS "") +set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS "") +add_compile_options(-m64 -Wall -Wextra -ffunction-sections -fdata-sections) +add_link_options(-m64 -Wl,--gc-sections) + +enable_testing() +get_filename_component(REPO_ROOT "${CMAKE_CURRENT_LIST_DIR}/../../../.." ABSOLUTE) +set(TEST_SOURCE_DIR ${REPO_ROOT}/test/tx/thread_stack_alignment) + +# Enforce warning-free compilation for the new tests and changed service. +# Unchanged MISRA/kernel sources keep their existing native64 diagnostics. +set_source_files_properties( + ${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_test.c + ${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_schedule_test.c + ${REPO_ROOT}/common/src/tx_thread_create.c + PROPERTIES COMPILE_OPTIONS -Werror) + +foreach(mode misra control) + set(test_target tx_stack_alignment_${mode}) + add_executable(${test_target} + ${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_test.c + ${REPO_ROOT}/common/src/tx_thread_create.c + ${REPO_ROOT}/common/src/tx_misra.c) + target_include_directories(${test_target} PRIVATE + ${REPO_ROOT}/common/inc ${REPO_ROOT}/ports/linux/gnu/inc) + target_compile_definitions(${test_target} PRIVATE TX_ENABLE_STACK_CHECKING) + if(mode STREQUAL "misra") + target_compile_definitions(${test_target} PRIVATE TX_MISRA_ENABLE) + endif() + add_test(NAME thread_stack_alignment_native64_${mode} COMMAND ${test_target}) + set_tests_properties(thread_stack_alignment_native64_${mode} + PROPERTIES TIMEOUT 20 LABELS "native64;stack_alignment") +endforeach() + +# Use the existing source lists to build a separate real Linux kernel. This +# target also works when the parent already owns the normal native32 threadx. +set(PROJECT_NAME tx_stack_alignment_linux) +add_library(${PROJECT_NAME} STATIC) +add_subdirectory(${REPO_ROOT}/common common_native64) +add_subdirectory(${REPO_ROOT}/ports/linux/gnu linux_native64) +target_compile_definitions(${PROJECT_NAME} PUBLIC + TX_MISRA_ENABLE TX_ENABLE_STACK_CHECKING TX_TIMER_PROCESS_IN_ISR) +target_link_libraries(${PROJECT_NAME} PUBLIC pthread) + +add_executable(tx_stack_alignment_schedule + ${TEST_SOURCE_DIR}/threadx_thread_stack_alignment_schedule_test.c) +target_link_libraries(tx_stack_alignment_schedule PRIVATE tx_stack_alignment_linux) +target_link_options(tx_stack_alignment_schedule PRIVATE -Wl,--wrap=_tx_thread_stack_build) + +# Each process owns one kernel instance, with a fresh worker and observer. +foreach(offset RANGE 0 3) + add_test(NAME thread_stack_alignment_native64_schedule_${offset} + COMMAND tx_stack_alignment_schedule ${offset}) + set_tests_properties(thread_stack_alignment_native64_schedule_${offset} + PROPERTIES TIMEOUT 20 RUN_SERIAL TRUE LABELS "native64;stack_alignment") +endforeach() diff --git a/test/tx/thread_stack_alignment/threadx_thread_stack_alignment_schedule_test.c b/test/tx/thread_stack_alignment/threadx_thread_stack_alignment_schedule_test.c new file mode 100644 index 000000000..676424495 --- /dev/null +++ b/test/tx/thread_stack_alignment/threadx_thread_stack_alignment_schedule_test.c @@ -0,0 +1,154 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (gpt-6.1-sol). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT AND CC0-1.0 + **************************************************************************/ + +/* Exercise actual Linux thread creation and scheduling across sleep/wake. + Check metadata before the real port builder can touch an invalid address. */ +#include "tx_api.h" +#include + +#define STACK_BYTES 8192U +#define PADDING_BYTES 32U +#define SENTINEL 0xA5U + +static TX_THREAD worker; +static TX_THREAD observer; +static ALIGN_TYPE worker_storage[(STACK_BYTES + 2U * PADDING_BYTES) / sizeof(ALIGN_TYPE)]; +static ALIGN_TYPE observer_storage[(STACK_BYTES + 2U * PADDING_BYTES) / sizeof(ALIGN_TYPE)]; +static UCHAR *worker_input; +static UCHAR *observer_input; +static UINT offset; +static volatile UINT visits; + +VOID __real__tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*entry)(VOID)); + +/* An assertion exits cleanly on the original tree, before stack dereference or + creation of either application pthread. The CTest timeout catches hangs. */ +static VOID require(UINT condition, const CHAR *message) +{ + if (condition == 0U) + { + printf("FAIL: %s\n", message); + exit(1); + } +} + +/* Validate full-width metadata before delegating to the genuine Linux builder. */ +VOID __wrap__tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*entry)(VOID)) +{ +UCHAR *input = (thread_ptr == &worker) ? worker_input : observer_input; +ULONG remainder = (ULONG) ((ALIGN_TYPE) input % sizeof(ULONG)); +ULONG adjustment = (remainder == 0U) ? 0U : (ULONG) sizeof(ULONG) - remainder; +UCHAR *expected_start = input + adjustment; +ULONG expected_size = STACK_BYTES - (ULONG) sizeof(ULONG); + + require((thread_ptr == &worker) || (thread_ptr == &observer), "unexpected thread build"); + if (adjustment != 0U) + { + expected_size -= (ULONG) sizeof(ULONG); + } + printf("port build: input=%p start=%p end=%p size=%u\n", (VOID *) input, + thread_ptr -> tx_thread_stack_start, thread_ptr -> tx_thread_stack_end, + (UINT) thread_ptr -> tx_thread_stack_size); + require(thread_ptr -> tx_thread_stack_start == expected_start, "stack start retains high bits"); + require(thread_ptr -> tx_thread_stack_size == expected_size, "stack guard reservations"); + require(thread_ptr -> tx_thread_stack_end == expected_start + expected_size - 1U, + "stack end remains in supplied storage"); + __real__tx_thread_stack_build(thread_ptr, entry); + require((ALIGN_TYPE) thread_ptr -> tx_thread_stack_ptr >= (ALIGN_TYPE) expected_start, + "initial stack pointer lower bound"); + require((ALIGN_TYPE) thread_ptr -> tx_thread_stack_ptr <= (ALIGN_TYPE) (expected_start + expected_size - 1U), + "initial stack pointer upper bound"); +} + +/* Check padding after the real builder and after the worker has run. */ +static VOID check_padding(UCHAR *bytes, UINT start_offset) +{ +UINT i; + + for (i = 0U; i < sizeof(worker_storage); i++) + { + if ((i < PADDING_BYTES + start_offset) || (i >= PADDING_BYTES + start_offset + STACK_BYTES)) + { + require(bytes[i] == SENTINEL, "caller padding survives creation/scheduling"); + } + } +} + +/* The worker must run twice, separated by an actual timer suspension. */ +static VOID worker_entry(ULONG input) +{ + require(input == 17U, "scalar worker input"); + require(tx_thread_identify() == &worker, "worker was scheduled"); + visits++; + require(tx_thread_sleep(1U) == TX_SUCCESS, "worker sleep/wake"); + visits++; +} + +/* A lower-priority observer gives the worker time to complete after waking. */ +static VOID observer_entry(ULONG input) +{ + require(input == 23U, "scalar observer input"); + require(tx_thread_sleep(3U) == TX_SUCCESS, "observer sleep/wake"); + require(visits == 2U, "worker ran across sleep/wake"); + require(worker.tx_thread_state == TX_COMPLETED, "worker completed"); + check_padding((UCHAR *) worker_storage, offset); + check_padding((UCHAR *) observer_storage, 0U); + puts("PASS: high-address stack survived real creation and scheduling"); + exit(0); +} + +/* Timer processing in ISR mode avoids an unrelated timer-thread stack. */ +VOID tx_application_define(VOID *unused) +{ +UINT status; + + (void) unused; + status = tx_thread_create(&worker, "alignment worker", worker_entry, 17U, + worker_input, STACK_BYTES, 15U, 15U, TX_NO_TIME_SLICE, TX_DONT_START); + require(status == TX_SUCCESS, "worker create"); + require(worker.tx_thread_stack_highest_ptr == worker.tx_thread_stack_ptr, "worker highest pointer"); + status = tx_thread_create(&observer, "alignment observer", observer_entry, 23U, + observer_input, STACK_BYTES, 16U, 16U, TX_NO_TIME_SLICE, TX_DONT_START); + require(status == TX_SUCCESS, "observer create"); + require(observer.tx_thread_stack_highest_ptr == observer.tx_thread_stack_ptr, "observer highest pointer"); + require((worker.tx_thread_state == TX_SUSPENDED) && (observer.tx_thread_state == TX_SUSPENDED), + "created threads suspended before resume"); + check_padding((UCHAR *) worker_storage, offset); + check_padding((UCHAR *) observer_storage, 0U); + require(tx_thread_resume(&worker) == TX_SUCCESS, "worker resume"); + require(tx_thread_resume(&observer) == TX_SUCCESS, "observer resume"); +} + +/* Require the genuine 4/8/8 Linux mismatch and actual high-address storage. */ +INT main(INT argc, CHAR **argv) +{ + setvbuf(stdout, TX_NULL, _IONBF, 0); + require(argc == 2, "one alignment offset required"); + require((argv[1][0] >= '0') && (argv[1][0] <= '3') && (argv[1][1] == '\0'), "valid offset"); + offset = (UINT) (argv[1][0] - '0'); + worker_input = (UCHAR *) worker_storage + PADDING_BYTES + offset; + observer_input = (UCHAR *) observer_storage + PADDING_BYTES; + printf("sizeof(ULONG)=%zu sizeof(ALIGN_TYPE)=%zu sizeof(void*)=%zu offset=%u\n", + sizeof(ULONG), sizeof(ALIGN_TYPE), sizeof(VOID *), offset); + require((sizeof(ULONG) == 4U) && (sizeof(ALIGN_TYPE) == 8U) && (sizeof(VOID *) == 8U), + "native64 Linux port widths"); + require(((ALIGN_TYPE) worker_input > (ALIGN_TYPE) ((ULONG) ~0U)) && + ((ALIGN_TYPE) observer_input > (ALIGN_TYPE) ((ULONG) ~0U)), "high-address stacks"); + memset(worker_storage, SENTINEL, sizeof(worker_storage)); + memset(observer_storage, SENTINEL, sizeof(observer_storage)); + tx_kernel_enter(); + require(0U, "kernel unexpectedly returned"); + return(1); +} diff --git a/test/tx/thread_stack_alignment/threadx_thread_stack_alignment_test.c b/test/tx/thread_stack_alignment/threadx_thread_stack_alignment_test.c new file mode 100644 index 000000000..f3ecbac70 --- /dev/null +++ b/test/tx/thread_stack_alignment/threadx_thread_stack_alignment_test.c @@ -0,0 +1,228 @@ +/*************************************************************************** + * Copyright (c) 2026 Eclipse ThreadX contributors + * + * This program and the accompanying materials are made available under the + * terms of the MIT License which is available at + * https://opensource.org/licenses/MIT. + * + * AI Disclosure: This file was largely AI-generated by Codex (gpt-6.1-sol). + * The AI-generated portions may be considered public domain (CC0-1.0) + * and not subject to the project's licence. The human contributor has + * reviewed and verified that the code is correct. + * + * SPDX-License-Identifier: MIT AND CC0-1.0 + **************************************************************************/ + +/* Execute the real create service and MISRA shim with Linux port types. + Only interrupt, stack-builder and scheduler boundaries are recorders. */ +#define TX_SOURCE_CODE +#include "tx_api.h" +#include "tx_thread.h" +#include + +#define STACK_BYTES 8192U +#define PADDING_BYTES 32U +#define SENTINEL 0xA5U +#define CASE_COUNT (sizeof(ULONG) * sizeof(ULONG)) + +/* Kernel state owned by this harness; the real shim declares it extern. */ +TX_THREAD *_tx_thread_created_ptr; +TX_THREAD *_tx_thread_execute_ptr; +ULONG _tx_thread_created_count; +volatile ULONG _tx_thread_system_state; +volatile UINT _tx_thread_preempt_disable; + +static TX_THREAD threads[CASE_COUNT]; +static ALIGN_TYPE storage[(STACK_BYTES + 2U * PADDING_BYTES + 16U) / sizeof(ALIGN_TYPE)]; +static VOID *recorded_start; +static VOID *recorded_end; +static ULONG recorded_size; +static VOID *safe_stack_ptr; +static UINT interrupt_depth; +static UINT interrupt_errors; +static UINT build_calls; +static UINT preempt_calls; +static UINT unexpected_calls; +static UINT failures; + +/* Report all cases, including on the old tree, without using corrupt metadata + as an address to read or write. */ +static VOID check(UINT condition, const CHAR *message) +{ + if (condition == 0U) + { + printf("FAIL: %s\n", message); + failures++; + } +} + +/* Record the stack handed to the port. The supplied pointer belongs to the + original allocation even when the create service truncates its metadata. */ +VOID _tx_thread_stack_build(TX_THREAD *thread_ptr, VOID (*entry)(VOID)) +{ + recorded_start = thread_ptr -> tx_thread_stack_start; + recorded_end = thread_ptr -> tx_thread_stack_end; + recorded_size = thread_ptr -> tx_thread_stack_size; + thread_ptr -> tx_thread_stack_ptr = safe_stack_ptr; + build_calls++; + check(entry == _tx_thread_shell_entry, "stack builder shell entry"); +} + +/* Preserve and restore the port's interrupt posture contract. */ +UINT _tx_thread_interrupt_disable(VOID) +{ +UINT previous = interrupt_depth; + + interrupt_depth++; + return(previous); +} + +VOID _tx_thread_interrupt_restore(UINT previous) +{ + if ((interrupt_depth == 0U) || (previous + 1U != interrupt_depth)) + { + interrupt_errors++; + } + interrupt_depth = previous; +} + +/* DONT_START reaches preemption checking, but must never resume or execute. */ +VOID _tx_thread_system_preempt_check(VOID) +{ + preempt_calls++; + check(_tx_thread_preempt_disable == 0U, "balanced preemption posture"); +} + +VOID _tx_thread_system_resume(TX_THREAD *thread_ptr) +{ + (void) thread_ptr; + unexpected_calls++; +} + +VOID _tx_thread_shell_entry(VOID) +{ + unexpected_calls++; +} + +VOID _tx_thread_timeout(ULONG input) +{ + (void) input; + unexpected_calls++; +} + +static VOID thread_entry(ULONG input) +{ + (void) input; + unexpected_calls++; +} + +/* Check the complete fill region, caller padding and both reserved stack + boundaries. Expectations use remainder arithmetic, independently of the + create service's alignment mask. */ +static VOID run_case(UINT offset, ULONG size, UINT index) +{ +TX_THREAD *thread_ptr = &threads[index]; +UCHAR *bytes = (UCHAR *) storage; +UCHAR *input = bytes + PADDING_BYTES + offset; +ULONG remainder = (ULONG) ((ALIGN_TYPE) input % sizeof(ULONG)); +ULONG adjustment = (remainder == 0U) ? 0U : (ULONG) sizeof(ULONG) - remainder; +UCHAR *expected_start = input + adjustment; +ULONG expected_size = size - size % (ULONG) sizeof(ULONG) - (ULONG) sizeof(ULONG); +UCHAR *expected_end; +UINT status; +UINT i; +UINT calls_before = build_calls; +TX_THREAD *next; +TX_THREAD *previous; + + if (adjustment != 0U) + { + expected_size -= (ULONG) sizeof(ULONG); + } + expected_end = expected_start + expected_size - 1U; + safe_stack_ptr = expected_start + expected_size - 2U * sizeof(ULONG); + memset(storage, SENTINEL, sizeof(storage)); + status = _tx_thread_create(thread_ptr, "alignment", thread_entry, 17U, + input, size, 15U, 15U, TX_NO_TIME_SLICE, TX_DONT_START); + printf("offset=%u size=%u input=%p start=%p end=%p usable=%u\n", + offset, (UINT) size, (VOID *) input, recorded_start, recorded_end, (UINT) recorded_size); + check(status == TX_SUCCESS, "create status"); + check(build_calls == calls_before + 1U, "one stack build per create"); + check(recorded_start == expected_start, "port start retains high bits and alignment"); + check(thread_ptr -> tx_thread_stack_start == expected_start, "stored stack start"); + check(recorded_size == expected_size, "port stack size and guards"); + check(thread_ptr -> tx_thread_stack_size == expected_size, "stored stack size"); + check(recorded_end == expected_end, "port stack end within caller storage"); + check(thread_ptr -> tx_thread_stack_end == expected_end, "stored stack end"); + check(thread_ptr -> tx_thread_stack_highest_ptr == safe_stack_ptr, "highest stack pointer"); + check(thread_ptr -> tx_thread_stack_ptr == safe_stack_ptr, "initial stack pointer"); + check(thread_ptr -> tx_thread_state == TX_SUSPENDED, "DONT_START suspended state"); + check(thread_ptr -> tx_thread_id == TX_THREAD_ID, "created thread ID"); + check(thread_ptr -> tx_thread_entry == thread_entry, "entry function"); + check(thread_ptr -> tx_thread_entry_parameter == 17U, "scalar entry parameter"); + check(thread_ptr -> tx_thread_priority == 15U, "priority"); + check(thread_ptr -> tx_thread_preempt_threshold == 15U, "preemption threshold"); + check(thread_ptr -> tx_thread_time_slice == TX_NO_TIME_SLICE, "time slice"); + check(thread_ptr -> tx_thread_timer.tx_timer_internal_timeout_function == _tx_thread_timeout, + "timeout callback"); + check(thread_ptr -> tx_thread_timer.tx_timer_internal_extension_ptr == thread_ptr, + "timeout thread pointer retains high bits"); + + for (i = 0U; i < sizeof(storage); i++) + { + UCHAR expected = ((i >= PADDING_BYTES + offset) && + (i < PADDING_BYTES + offset + size)) ? (UCHAR) TX_STACK_FILL : SENTINEL; + if (bytes[i] != expected) + { + check(0U, "fill region or caller sentinel corrupted"); + break; + } + } + for (i = 0U; i < sizeof(ULONG); i++) + { + check(expected_start[i] == (UCHAR) TX_STACK_FILL, "bottom stack guard fill"); + check(expected_end[1U + i] == (UCHAR) TX_STACK_FILL, "top reserved guard fill"); + } + check(_tx_thread_created_count == index + 1U, "created list count"); + check(_tx_thread_created_ptr == &threads[0], "created list head"); + next = _tx_thread_created_ptr; + previous = thread_ptr; + for (i = 0U; i <= index; i++) + { + check(next == &threads[i], "created list order"); + check(next -> tx_thread_created_previous == previous, "created list back link"); + previous = next; + next = next -> tx_thread_created_next; + } + check(next == _tx_thread_created_ptr, "created list closes"); + check((interrupt_depth == 0U) && (interrupt_errors == 0U), "balanced interrupt posture"); + check(preempt_calls == index + 1U, "preemption check count"); + check(unexpected_calls == 0U, "no scheduling at recorder boundary"); +} + +/* Native64 is a required precondition, never a successful no-op test. */ +INT main(VOID) +{ +UINT offset; +UINT tail; +UINT index = 0U; + + printf("sizeof(ULONG)=%zu sizeof(ALIGN_TYPE)=%zu sizeof(void*)=%zu\n", + sizeof(ULONG), sizeof(ALIGN_TYPE), sizeof(VOID *)); + if ((sizeof(ULONG) != 4U) || (sizeof(ALIGN_TYPE) != 8U) || (sizeof(VOID *) != 8U) || + ((ALIGN_TYPE) storage <= (ALIGN_TYPE) ((ULONG) ~0U))) + { + puts("FAIL: native64 high-address preconditions absent"); + return(1); + } + for (offset = 0U; offset < sizeof(ULONG); offset++) + { + for (tail = 0U; tail < sizeof(ULONG); tail++) + { + run_case(offset, STACK_BYTES + tail, index++); + } + } + printf("%s: %u alignment/size cases, %u failures\n", (failures == 0U) ? "PASS" : "FAIL", + index, failures); + return((failures == 0U) ? 0 : 1); +}