diff --git a/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py b/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py deleted file mode 100644 index 861f50e61..000000000 --- a/ports/risc-v64/gnu/example_build/qemu_virt/test/threadx_test_tx_gnu_riscv64_qemu.py +++ /dev/null @@ -1,286 +0,0 @@ -############################################################################## -# Copyright (c) 2024 Microsoft Corporation -# 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 -############################################################################## - -import subprocess -import sys -import os -import argparse -import socket -import select - -def print_content(content): - """Prints content using os.write to handle non-blocking stdout robustly.""" - try: - msg = f"{content}\n".encode('utf-8') - total_len = len(msg) - written = 0 - fd = sys.stdout.fileno() - while written < total_len: - try: - n = os.write(fd, msg[written:]) - written += n - except BlockingIOError: - select.select([], [fd], []) - except Exception: - pass - -def get_free_port(): - """Finds a free TCP port.""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(('', 0)) - return s.getsockname()[1] - -def run_qemu_test(elf_path, qemu_bin, gdb_bin): - """ - Runs a test cycle using QEMU and GDB. - """ - print(f"Testing ELF: {elf_path}") - print(f"QEMU: {qemu_bin}") - print(f"GDB: {gdb_bin}") - - # Find a free port for GDB connection - gdb_port = get_free_port() - print(f"Using GDB port: {gdb_port}") - - # 1. Start QEMU in the background - qemu_cmd = [ - qemu_bin, - "-M", "virt", - "-nographic", - "-bios", "none", # Disable default OpenSBI - "-kernel", elf_path, - "-gdb", f"tcp::{gdb_port}", "-S", - "-monitor", "none", # Disable monitor - "-serial", "stdio" # Redirect serial output to stdio - ] - - print(f"Starting QEMU: {' '.join(qemu_cmd)}") - qemu_process = subprocess.Popen( - qemu_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True - ) - - if qemu_process.poll() is not None: - print("QEMU failed to start.") - print(qemu_process.stderr.read()) - return False - - # 2. Create a GDB command file - gdb_cmds = """ -file {elf} -target remote :{port} -set pagination off -set confirm off - -# Setup Breakpoints -break tx_application_define -break thread_0_entry -break thread_6_and_7_entry -break _tx_timer_interrupt - -# Execute to Application Definition -continue - -# Inspect mstatus once thread_0 has started -continue -print/x $mstatus - -# Verify FPU Logic and Register State exercised by thread_6/7 -continue -finish -step -step -step -print/x $mstatus -info registers float -print fpu_test_val - -# Await Timer Interrupt -continue -print "Hit Timer Interrupt" - -# Verify MEPC Integrity - Save State -print/x $mepc -set $saved_pc = $mepc - -# Verify System Timer Before ISR -set $clock_before = _tx_timer_system_clock -print $clock_before - -# Configure Time-Slice Test Conditions -set _tx_timer_time_slice = 1 -set _tx_timer_expired_time_slice = 0 -set $ts_handler_called = 0 - -# Set Breakpoint at Time-Slice Handler with Auto-Continue -tbreak _tx_thread_time_slice -commands - set $ts_handler_called = 1 - continue -end - -# Set Breakpoint at ISR Return Address -set $ret_addr = $ra -tbreak *$ret_addr -continue - -# Verify Time-Slice Handler Was Called -if $ts_handler_called == 1 - print "SUCCESS: Time-slice handler called." -else - print "FAILURE: Time-slice handler NOT called." -end - -# Verify System Timer Increment (Monotonicity) -set $clock_after = _tx_timer_system_clock -print $clock_after - -if $clock_after > $clock_before - print "SUCCESS: System timer incremented." -else - print "FAILURE: System timer did not increment." -end - -# Verify Preemption Logic (Thread Priority) -set $curr_ptr = _tx_thread_current_ptr -set $exec_ptr = _tx_thread_execute_ptr -if $curr_ptr != 0 && $exec_ptr != 0 - set $curr_prio = $curr_ptr->tx_thread_priority - set $exec_prio = $exec_ptr->tx_thread_priority - printf "PREEMPT_CHECK current_prio=%d execute_prio=%d\\n", $curr_prio, $exec_prio - if $exec_prio < $curr_prio - printf "PREEMPT_VERIFIED_OK\\n" - else - printf "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED\\n" - end -else - printf "PREEMPT_VERIFIED_FAIL_NULL\\n" -end - -quit -""".format(port=gdb_port, elf=elf_path) - - gdb_cmd_file = "test_cmds.gdb" - with open(gdb_cmd_file, "w") as f: - f.write(gdb_cmds) - - # 3. Run GDB - gdb_cmd = [ - gdb_bin, - "--batch", - "-x", gdb_cmd_file - ] - - print_content(f"Starting GDB: {' '.join(gdb_cmd)}") - - GDB_TIMEOUT_S = 30 - - try: - gdb_process = subprocess.run( - gdb_cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - timeout=GDB_TIMEOUT_S, - ) - - print_content("GDB Output:") - print_content(gdb_process.stdout) - if gdb_process.stderr: - print_content("GDB Error Output:") - print_content(gdb_process.stderr) - - except subprocess.TimeoutExpired as e: - print_content( - f"FAILURE: GDB session exceeded {GDB_TIMEOUT_S}s timeout; " - "likely stuck on a `continue` that never matched a breakpoint." - ) - if e.stdout: - print_content("GDB Output (partial):") - print_content(e.stdout if isinstance(e.stdout, str) - else e.stdout.decode(errors='replace')) - if e.stderr: - print_content("GDB Error Output (partial):") - print_content(e.stderr if isinstance(e.stderr, str) - else e.stderr.decode(errors='replace')) - return False - - except Exception as e: - print_content(f"An error occurred during test execution: {e}") - return False - - finally: - # 4. Clean up - print_content("Stopping QEMU...") - qemu_process.terminate() - try: - qemu_process.wait(timeout=2) - except subprocess.TimeoutExpired: - print_content("QEMU did not terminate gracefully, killing it forcefully.") - qemu_process.kill() - - # Verify results - stdout = gdb_process.stdout - timer_hit = "Breakpoint 4, _tx_timer_interrupt" in stdout - fpu_verified = False - preemption_verified = "PREEMPT_VERIFIED_OK" in stdout - - if "Breakpoint 3, thread_6_and_7_entry" in stdout: - if "1.10" in stdout or "fpu_test_val" in stdout: - print_content("SUCCESS: FPU instructions executed and registers inspected.") - fpu_verified = True - else: - print_content("FAILURE: Hit thread, but failed to inspect FPU. " - "Output does not contain expected value.") - - if timer_hit: - print_content("SUCCESS: Timer Interrupt verified! Hit _tx_timer_interrupt.") - else: - print_content("FAILURE: Did not hit timer interrupt.") - - if preemption_verified: - print_content("SUCCESS: Preemption verified (higher-priority thread " - "preempted a lower-priority one).") - else: - if "PREEMPT_VERIFIED_FAIL_INVERTED" in stdout: - print_content("FAILURE: Preemption inverted -- lower priority " - "thread scheduled over higher priority one.") - elif "PREEMPT_VERIFIED_FAIL_NULL" in stdout: - print_content("FAILURE: Preemption check saw NULL thread pointers.") - elif "PREEMPT_VERIFIED_FAIL_NOT_OBSERVED" in stdout: - print_content("FAILURE: Preemption was not observed within the " - "loop budget.") - else: - print_content("FAILURE: Preemption check did not run to completion.") - - if timer_hit and fpu_verified and preemption_verified: - return True - else: - return False - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Run ThreadX RV64 QEMU/GDB Test") - parser.add_argument("--elf", required=True, help="Path to the kernel ELF file") - parser.add_argument("--qemu", default="qemu-system-riscv64", - help="Path to QEMU binary") - parser.add_argument("--gdb", default="riscv64-unknown-elf-gdb", - help="Path to GDB binary") - - args = parser.parse_args() - - success = run_qemu_test(args.elf, args.qemu, args.gdb) - - if success: - sys.exit(0) - else: - sys.exit(1) diff --git a/ports/risc-v64/gnu/src/tx_thread_context_restore.S b/ports/risc-v64/gnu/src/tx_thread_context_restore.S index cebc8a062..d8190fb67 100644 --- a/ports/risc-v64/gnu/src/tx_thread_context_restore.S +++ b/ports/risc-v64/gnu/src/tx_thread_context_restore.S @@ -31,6 +31,7 @@ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Wei-Chen Lai, National Cheng Kung University */ /* */ /* DESCRIPTION */ /* */ @@ -88,7 +89,13 @@ _tx_thread_context_restore: /* Just recover the saved registers and return to the point of interrupt. */ - /* Recover floating point registers. */ + /* Recover floating point registers only if saved mstatus.FS was not Off. */ +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + ld t1, 29*8(sp) // Pickup saved mstatus + srli t1, t1, 13 + andi t1, t1, 0x3 + beqz t1, _tx_thread_skip_fp_restore // Skip if FS was Off +#endif #if defined(__riscv_float_abi_single) flw f0, 31*8(sp) // Recover ft0 flw f1, 32*8(sp) // Recover ft1 @@ -136,6 +143,7 @@ _tx_thread_context_restore: ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 #endif +_tx_thread_skip_fp_restore: #if defined(__riscv_vector) /* Recover vector registers v0-v31 */ @@ -285,7 +293,13 @@ _tx_thread_no_preempt_restore: ld sp, 8(t1) // Switch back to thread's stack - /* Recover floating point registers. */ + /* Recover floating point registers only if saved mstatus.FS was not Off. */ +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + ld t3, 29*8(sp) // Pickup saved mstatus + srli t3, t3, 13 + andi t3, t3, 0x3 + beqz t3, _tx_thread_no_preempt_skip_fp_restore // Skip if FS was Off +#endif #if defined(__riscv_float_abi_single) flw f0, 31*8(sp) // Recover ft0 flw f1, 32*8(sp) // Recover ft1 @@ -333,6 +347,7 @@ _tx_thread_no_preempt_restore: ld t0, 63*8(sp) // Recover fcsr csrw fcsr, t0 // Restore fcsr #endif +_tx_thread_no_preempt_skip_fp_restore: #if defined(__riscv_vector) /* Recover vector registers v0-v31 */ @@ -464,7 +479,13 @@ _tx_thread_preempt_restore: ori t3, zero, 1 // Build interrupt stack type sd t3, 0(t0) // Store stack type - /* Store floating point preserved registers. */ + /* Store floating point preserved registers only if saved mstatus.FS was not Off. */ +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + ld t3, 29*8(t0) // Pickup saved mstatus + srli t3, t3, 13 + andi t3, t3, 0x3 + beqz t3, _tx_thread_preempt_skip_fp_restore // Skip if FS was Off +#endif #ifdef __riscv_float_abi_single fsw f8, 39*8(t0) // Store fs0 fsw f9, 40*8(t0) // Store fs1 @@ -492,6 +513,7 @@ _tx_thread_preempt_restore: fsd f26, 57*8(t0) // Store fs10 fsd f27, 58*8(t0) // Store fs11 #endif +_tx_thread_preempt_skip_fp_restore: #if defined(__riscv_vector) /* Store vector registers and CSRs */ diff --git a/ports/risc-v64/gnu/src/tx_thread_context_save.S b/ports/risc-v64/gnu/src/tx_thread_context_save.S index 7935bfee2..dcc6301a1 100644 --- a/ports/risc-v64/gnu/src/tx_thread_context_save.S +++ b/ports/risc-v64/gnu/src/tx_thread_context_save.S @@ -30,6 +30,7 @@ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Wei-Chen Lai, National Cheng Kung University */ /* */ /* DESCRIPTION */ /* */ @@ -101,6 +102,15 @@ _tx_thread_context_save: #endif sd t0, 30*8(sp) // Save it on the stack + /* Save mstatus and skip FP state if FS is Off. */ + csrr t0, mstatus + sd t0, 29*8(sp) // Save mstatus +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + srli t1, t0, 13 + andi t1, t1, 0x3 + beqz t1, _tx_thread_skip_nested_fpu_save // Skip if FS was Off +#endif + /* Save floating point scratch registers if floating point is enabled. */ #ifdef __riscv_float_abi_single fsw f0, 31*8(sp) // Store ft0 @@ -149,6 +159,7 @@ _tx_thread_context_save: csrr t0, fcsr sd t0, 63*8(sp) // Store fcsr #endif +_tx_thread_skip_nested_fpu_save: #if defined(__riscv_vector) /* Store vector registers and CSRs */ @@ -225,6 +236,15 @@ _tx_thread_not_nested_save: #endif sd t1, 30*8(sp) // Save it on the stack + /* Save mstatus and skip FP state if FS is Off. */ + csrr t1, mstatus + sd t1, 29*8(sp) // Save mstatus +#if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) + srli t2, t1, 13 + andi t2, t2, 0x3 + beqz t2, _tx_thread_skip_fpu_save // Skip if FS was Off +#endif + /* Save floating point scratch registers if floating point is enabled. */ #ifdef __riscv_float_abi_single fsw f0, 31*8(sp) // Store ft0 @@ -273,6 +293,7 @@ _tx_thread_not_nested_save: csrr t0, fcsr sd t0, 63*8(sp) // Store fcsr #endif +_tx_thread_skip_fpu_save: #if defined(__riscv_vector) /* Store vector registers and CSRs */ diff --git a/ports/risc-v64/gnu/src/tx_thread_stack_build.S b/ports/risc-v64/gnu/src/tx_thread_stack_build.S index 86cab4f09..443f9ede3 100644 --- a/ports/risc-v64/gnu/src/tx_thread_stack_build.S +++ b/ports/risc-v64/gnu/src/tx_thread_stack_build.S @@ -30,6 +30,7 @@ /* AUTHOR */ /* */ /* Scott Larson, Microsoft Corporation */ +/* Wei-Chen Lai, National Cheng Kung University */ /* */ /* DESCRIPTION */ /* */ @@ -92,7 +93,7 @@ _tx_thread_stack_build: x11 26 Initial a1 x10 27 Initial a0 x1 28 Initial ra - -- 29 reserved + mstatus 29 Initial mstatus mepc 30 Initial mepc If floating point support: f0 31 Inital ft0 @@ -192,6 +193,8 @@ If vector extension support: sd zero, 26*8(t0) // Initial a1 sd zero, 27*8(t0) // Initial a0 sd zero, 28*8(t0) // Initial ra + li t1, 0x2000 // mstatus.FS = Initial + sd t1, 29*8(t0) // Initial mstatus sd a1, 30*8(t0) // Initial mepc/sepc (thread entry point) #if defined(__riscv_float_abi_single) || defined(__riscv_float_abi_double) sd zero, 31*8(t0) // Initial ft0 diff --git a/test/tx/cmake/riscv/regression/CMakeLists.txt b/test/tx/cmake/riscv/regression/CMakeLists.txt index 80b6e3bdb..3db2eeec5 100644 --- a/test/tx/cmake/riscv/regression/CMakeLists.txt +++ b/test/tx/cmake/riscv/regression/CMakeLists.txt @@ -115,6 +115,14 @@ set(regression_test_cases # interrupt_save on RISC-V. ) +# Architecture specific tests living next to this file. The RV32 stack +# builder still leaves the mstatus slot of the frame unwritten, so the new +# thread FPU state test only applies to RV64 for now. +if(THREADX_ARCH STREQUAL "risc-v64") + list(APPEND regression_test_cases + ${CMAKE_CURRENT_LIST_DIR}/threadx_riscv_new_thread_fpu_state_test.c) +endif() + # Tests that provide their own main() and must NOT link with testcontrol. set(standalone_test_cases ${SOURCE_DIR}/threadx_initialize_kernel_setup_test.c diff --git a/test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c b/test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c new file mode 100644 index 000000000..b388ad621 --- /dev/null +++ b/test/tx/cmake/riscv/regression/threadx_riscv_new_thread_fpu_state_test.c @@ -0,0 +1,278 @@ +/***************************************************************************/ +/* 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 Claude Opus 5. */ +/* 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 */ +/***************************************************************************/ + +/* This test is designed to verify that a thread created after another + thread has used the floating point registers starts with a clean + floating point state. + + The interrupt stack frame reserves slot 29 for mstatus, and the context + restore path uses the FS field of that slot to decide whether the saved + floating point registers have to be recovered. When the stack builder + leaves the slot untouched, the decision is made on whatever the thread + stack happened to contain, so the new thread can silently inherit the + floating point registers of the thread that ran before it. + + Thread 0 fills every floating point register with a non-zero value and + then creates thread 1. The test checks that the stack builder wrote the + mstatus slot at all, that the slot describes a live floating point state, + and that thread 1 observes all floating point registers as zero once it + preempts thread 0. + + An unwritten slot still holds the stack fill pattern, so it is detected + by comparing the slot against a stack word the builder never touches. */ + +#include +#include "tx_api.h" + + +/* The mstatus slot of the interrupt stack frame. */ + +#define FRAME_MSTATUS_INDEX 29 + + +#if defined(__riscv_float_abi_double) + +#define FP_SUPPORTED 1 +#define FP_LOAD "fld" +#define FP_STORE "fsd" +#define FP_WORD_SIZE "8" +typedef double FP_WORD; + +#elif defined(__riscv_float_abi_single) + +#define FP_SUPPORTED 1 +#define FP_LOAD "flw" +#define FP_STORE "fsw" +#define FP_WORD_SIZE "4" +typedef float FP_WORD; + +#else + +#define FP_SUPPORTED 0 + +#endif + + +#if FP_SUPPORTED + +/* Walk f0-f31 in register number order so that the index is both the + register number and the slot in the state buffers. */ + +#define FP_REG_LIST(op) \ + op(ft0, 0) op(ft1, 1) op(ft2, 2) op(ft3, 3) \ + op(ft4, 4) op(ft5, 5) op(ft6, 6) op(ft7, 7) \ + op(fs0, 8) op(fs1, 9) op(fa0, 10) op(fa1, 11) \ + op(fa2, 12) op(fa3, 13) op(fa4, 14) op(fa5, 15) \ + op(fa6, 16) op(fa7, 17) op(fs2, 18) op(fs3, 19) \ + op(fs4, 20) op(fs5, 21) op(fs6, 22) op(fs7, 23) \ + op(fs8, 24) op(fs9, 25) op(fs10, 26) op(fs11, 27) \ + op(ft8, 28) op(ft9, 29) op(ft10, 30) op(ft11, 31) + +#define FP_LOAD_ONE(reg, index) FP_LOAD " " #reg ", " #index "*" FP_WORD_SIZE "(%0)\n" +#define FP_STORE_ONE(reg, index) FP_STORE " " #reg ", " #index "*" FP_WORD_SIZE "(%0)\n" +#define FP_CLOBBER_ONE(reg, index) #reg, + +#define FP_REGISTERS 32 + +static FP_WORD dirty_pattern[FP_REGISTERS]; +static FP_WORD thread_1_fp_state[FP_REGISTERS]; +static unsigned long thread_1_fp_dirty = 0; + +#endif + + +static TX_THREAD thread_0; +static TX_THREAD thread_1; + +/* Give thread 1 a private stack so it can be cleared before the thread is + created. */ + +static unsigned long thread_1_stack[TEST_STACK_SIZE_PRINTF/sizeof(unsigned long)]; + +static unsigned long thread_1_executed = 0; + + +/* Define thread prototypes. */ + +static void thread_0_entry(ULONG thread_input); +static void thread_1_entry(ULONG thread_input); + + +/* Prototype for test control return. */ +void test_control_return(UINT status); + + +/* Define what the initial system looks like. */ + +#ifdef CTEST +void test_application_define(void *first_unused_memory) +#else +void threadx_riscv_new_thread_fpu_state_application_define(void *first_unused_memory) +#endif +{ + +UINT status; +CHAR *pointer; + + + /* Put first available memory address into a character pointer. */ + pointer = (CHAR *) first_unused_memory; + + status = tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0, + pointer, TEST_STACK_SIZE_PRINTF, + 16, 16, TX_NO_TIME_SLICE, TX_AUTO_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("Running RISC-V New Thread FPU State Test............................ ERROR #1\n"); + test_control_return(1); + } +} + + + +/* Define the test threads. */ + +static void thread_0_entry(ULONG thread_input) +{ + +UINT status; +unsigned long *frame; +unsigned long stack_fill; +unsigned long frame_fs; +#if FP_SUPPORTED +UINT i; +#endif + + + /* Inform user. */ + printf("Running RISC-V New Thread FPU State Test............................ "); + +#if FP_SUPPORTED + + /* Build a pattern that is non-zero in every register. */ + for (i = 0; i < FP_REGISTERS; i++) + { + + dirty_pattern[i] = (FP_WORD) (i + 1); + } + + /* Make the floating point state of this thread dirty. */ + __asm__ volatile (FP_REG_LIST(FP_LOAD_ONE) + : + : "r" (dirty_pattern) + : FP_REG_LIST(FP_CLOBBER_ONE) "memory"); +#endif + + /* Create thread 1 without starting it, so the built frame can be + inspected before the thread is scheduled. */ + status = tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0, + thread_1_stack, sizeof(thread_1_stack), + 15, 15, TX_NO_TIME_SLICE, TX_DONT_START); + + /* Check for status. */ + if (status != TX_SUCCESS) + { + + printf("ERROR #2\n"); + test_control_return(1); + } + + /* The bottom of the stack is never part of the frame, so it still holds + the fill pattern that thread creation left behind. */ + frame = (unsigned long *) thread_1.tx_thread_stack_ptr; + stack_fill = thread_1_stack[0]; + + /* The stack builder must have written the mstatus slot. */ + if (frame[FRAME_MSTATUS_INDEX] == stack_fill) + { + + printf("ERROR #3\n"); + test_control_return(1); + } + + /* The built frame must describe a live floating point state. */ + frame_fs = (frame[FRAME_MSTATUS_INDEX] >> 13) & 0x3UL; + + if (frame_fs == 0) + { + + printf("ERROR #4\n"); + test_control_return(1); + } + + /* Let thread 1 preempt and run to completion. */ + status = tx_thread_resume(&thread_1); + + /* Check for status. */ + if ((status != TX_SUCCESS) || (thread_1_executed != 1)) + { + + printf("ERROR #5\n"); + test_control_return(1); + } + +#if FP_SUPPORTED + + /* Thread 1 must not have inherited the floating point registers of + thread 0. */ + if (thread_1_fp_dirty) + { + + printf("ERROR #6\n"); + test_control_return(1); + } +#endif + + /* Successful test. */ + printf("SUCCESS!\n"); + test_control_return(0); +} + + +static void thread_1_entry(ULONG thread_input) +{ + +#if FP_SUPPORTED + +const unsigned char *state_bytes; +UINT i; + + + /* Capture the floating point registers before anything else can use + them. */ + __asm__ volatile (FP_REG_LIST(FP_STORE_ONE) + : + : "r" (thread_1_fp_state) + : "memory"); + + state_bytes = (const unsigned char *) thread_1_fp_state; + + for (i = 0; i < (UINT) sizeof(thread_1_fp_state); i++) + { + + if (state_bytes[i] != 0) + { + + thread_1_fp_dirty = 1; + break; + } + } +#endif + + thread_1_executed = 1; +}