Skip to content
Merged
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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ All notable changes to CableDyn are recorded in this file. The format follows
the displaced mass) no longer depends on the sign of the round-off weight: a line whose
total weight is below 1e-12 of its axial stiffness is seeded as a straight, uniformly
stretched line. Previously the catenary seed could fail to close on some platforms.
- Windows builds with gfortran: the `modal` test could stop with a stack overflow on some
CPUs, because it kept 1.7 MB of dense matrices on the stack. Large test arrays and the
initialisation workspaces of the coupled aggregate and the C API are now allocated on the
heap, every executable of such a build reserves a 64 MiB stack, and the new `stack` tests
run the library on a 1 MiB thread stack. Results are unchanged.
- The static Windows `openfast.exe` compiles the CableDyn adapter with `/heap-arrays:1024`,
like the CableDyn core, so its run-time-sized arrays no longer use the
`openfast.exe` stack.
- Builds with gfortran 16 are free of `-Wuninitialized` warnings; the reported values (the
bounds of unallocated components) were never read.

## [0.1.0] - 2026-10-01

Expand Down
82 changes: 82 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,22 @@ if(CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
$<$<COMPILE_LANGUAGE:C>:-Wall>
$<$<COMPILE_LANGUAGE:C>:-Wextra>)
endif()
# GNU builds on Windows (MinGW-w64): every executable gets a 64 MiB stack reserve instead of
# the 2 MiB MinGW default. With OpenMP, gfortran places fixed-size local arrays on the stack
# (-fopenmp implies -frecursive), and the stack depth of the OpenBLAS kernels varies with the
# CPU, so a small reserve can overflow on one machine and pass on another. A reserve is address
# space, not committed memory. The library itself must run within a small stack, since a host
# such as python.exe sets the stack of the threads that call it: the stack_* tests check that.
# The static IFX release sets its own reserve on the driver.
set(CABLEDYN_GNU_WINDOWS_STACK OFF)
if(MINGW AND CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
set(CABLEDYN_GNU_WINDOWS_STACK ON)
set(CABLEDYN_EXE_STACK_RESERVE 67108864)
set(CABLEDYN_SMALL_STACK_RESERVE 1048576)
add_link_options(
"$<$<STREQUAL:$<TARGET_PROPERTY:TYPE>,EXECUTABLE>:-Wl,--stack,${CABLEDYN_EXE_STACK_RESERVE}>")
endif()

if(CABLEDYN_ENABLE_COVERAGE AND NOT CMAKE_Fortran_COMPILER_ID STREQUAL "GNU")
message(FATAL_ERROR "CABLEDYN_ENABLE_COVERAGE requires GNU Fortran/gcov instrumentation")
endif()
Expand Down Expand Up @@ -1074,6 +1090,25 @@ else()
"the portable C API lifecycle gate remains enabled")
endif()

# Portable (Win32 or POSIX threads) stack bound of the shared library: each deck is
# initialised, stepped, and evaluated on a thread with a 1 MiB stack (a host program, not
# CableDyn, chooses the stack of the threads that call the library). The decks are a
# 2048-element finite-EI cable (12294 DOFs), an EI=0 + finite-EI aggregate, and an EI=0
# mooring; a stack need that grew with the model would overflow here.
add_executable(test_c_api_stack tests/test_c_api_stack.c)
target_include_directories(test_c_api_stack PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_link_libraries(test_c_api_stack PRIVATE $<TARGET_LINKER_FILE:cabledyn_shared>)
add_dependencies(test_c_api_stack cabledyn_shared)
if(NOT WIN32)
target_link_libraries(test_c_api_stack PRIVATE Threads::Threads m)
endif()
add_test(NAME stack_c_api
COMMAND test_c_api_stack 1024
0.05 ${CMAKE_CURRENT_SOURCE_DIR}/tests/data/stack_humboldt800_2048_elements.dat
0.025 ${CMAKE_CURRENT_SOURCE_DIR}/examples/iea15mw_umaine_mixed_cabledyn.dat
0.1 ${CMAKE_CURRENT_SOURCE_DIR}/examples/iea15mw_volturnus_mooring.dat)
set_tests_properties(stack_c_api PROPERTIES LABELS "c;unit;capi;stack" TIMEOUT 900)

# Portable (Win32 or POSIX threads) gate: concurrent InitDeck of one deck and of decks
# sharing a Syrope OWC table in the user's locale, concurrent Step on distinct handles,
# the missing-file diagnostic, and the BLAS thread/memory policy of CableDyn_Create.
Expand Down Expand Up @@ -1630,6 +1665,53 @@ target_link_libraries(test_modal PRIVATE cabledyn_core)
add_test(NAME modal COMMAND test_modal)
set_tests_properties(modal PROPERTIES LABELS "fortran;unit;driver;static;validation")

# Stack bound (GNU Windows builds): the modal test and the driver relinked with a 1 MiB stack
# reserve, half the MinGW default, instead of the 64 MiB reserve of the other executables. A
# large local array (as the dense matrices test_modal once kept on the stack) or a library
# frame that grows with the model overflows here on every machine. The driver runs a
# 2048-element finite-EI cable. Linux and macOS executables take their main-thread stack
# from the environment (ulimit -s), so these links are Windows-only; test_c_api_stack covers
# the library on every platform.
if(CABLEDYN_GNU_WINDOWS_STACK)
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/stack_small")
add_executable(test_modal_small_stack tests/test_modal.f90)
target_link_libraries(test_modal_small_stack PRIVATE cabledyn_core)
add_executable(cabledyn_small_stack app/cabledyn.f90)
target_link_libraries(cabledyn_small_stack PRIVATE cabledyn_core)
foreach(_cabledyn_small IN ITEMS test_modal_small_stack cabledyn_small_stack)
# Appended after the 64 MiB directory-wide option; the last --stack wins (checked below).
target_link_options(${_cabledyn_small} PRIVATE "-Wl,--stack,${CABLEDYN_SMALL_STACK_RESERVE}")
endforeach()
add_test(NAME stack_modal COMMAND test_modal_small_stack
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/stack_small")
set_tests_properties(stack_modal PROPERTIES LABELS "fortran;unit;driver;static;stack")
add_test(NAME stack_driver_large_deck
COMMAND cabledyn_small_stack
"${CMAKE_CURRENT_SOURCE_DIR}/tests/data/stack_humboldt800_2048_elements.dat"
"${CMAKE_BINARY_DIR}/stack_small/stack_driver_large_deck")
set_tests_properties(stack_driver_large_deck PROPERTIES
LABELS "fortran;driver;integration;dynamic;stack" TIMEOUT 900)
# The reserve each link actually received, read from the PE headers: <binary>=<bytes>.
string(JOIN "|" _cabledyn_stack_checks
"$<TARGET_FILE:cabledyn>=${CABLEDYN_EXE_STACK_RESERVE}"
"$<TARGET_FILE:test_modal>=${CABLEDYN_EXE_STACK_RESERVE}"
"$<TARGET_FILE:test_c_api_stack>=${CABLEDYN_EXE_STACK_RESERVE}"
"$<TARGET_FILE:test_modal_small_stack>=${CABLEDYN_SMALL_STACK_RESERVE}"
"$<TARGET_FILE:cabledyn_small_stack>=${CABLEDYN_SMALL_STACK_RESERVE}")
add_test(NAME stack_reserve
COMMAND ${CMAKE_COMMAND} "-DCHECKS=${_cabledyn_stack_checks}"
-P ${CMAKE_CURRENT_SOURCE_DIR}/tests/check_stack_reserve.cmake)
set_tests_properties(stack_reserve PROPERTIES LABELS "unit;driver;stack")
# The check itself must see a reserve that differs from the expected one.
add_test(NAME stack_reserve_mismatch
COMMAND ${CMAKE_COMMAND}
"-DCHECKS=$<TARGET_FILE:test_modal_small_stack>=${CABLEDYN_EXE_STACK_RESERVE}"
-P ${CMAKE_CURRENT_SOURCE_DIR}/tests/check_stack_reserve.cmake)
set_tests_properties(stack_reserve_mismatch PROPERTIES LABELS "unit;driver;stack"
PASS_REGULAR_EXPRESSION
"stack reserve ${CABLEDYN_SMALL_STACK_RESERVE} bytes, expected ${CABLEDYN_EXE_STACK_RESERVE}")
endif()

add_executable(test_seabed_friction_aniso tests/test_seabed_friction_aniso.f90)
target_link_libraries(test_seabed_friction_aniso PRIVATE cabledyn_core)
add_test(NAME seabed_friction_aniso COMMAND test_seabed_friction_aniso)
Expand Down
20 changes: 19 additions & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ The Debug configuration (`-DCMAKE_BUILD_TYPE=Debug`) adds `-fcheck=all
conda OpenBLAS through `CONDA_PREFIX`. `cmake --install build --prefix stage`
installs the driver, the shared library, and `CableDyn_CAPI.h`.

A Windows build with gfortran links every executable with a 64 MiB stack reserve
(the MinGW default is 2 MiB). With OpenMP enabled, gfortran keeps fixed-size local
arrays on the stack, and the stack depth of the OpenBLAS kernels differs between
CPUs, so a small reserve can pass on one machine and overflow on another. The
shared library cannot rely on this reserve: the host program (for example
`python.exe`, which reserves about 2 MB) sets the stack of the threads that call
it. The `-L stack` tests check the library on a thread with a 1 MiB stack and, in
a Windows gfortran build, the driver and the modal test relinked with a 1 MiB
reserve and the reserve written into each executable.

| Selection | Tests |
|---|---|
| `-L fortran` | All Fortran tests |
Expand All @@ -56,6 +66,7 @@ installs the driver, the shared library, and `CableDyn_CAPI.h`.
| `-L external_ref` | IEA-15MW VolturnUS-S mooring comparisons |
| `-L cosserat` | Secondary Cosserat path |
| `-L python` | Python package, deck-style, and documentation checks |
| `-L stack` | Library, driver, and modal test on a 1 MiB stack |

A test program can also be run directly from the build tree (`build/`, or
`build\bin\` with the conda toolchain on Windows); it prints `PASS:` or
Expand Down Expand Up @@ -124,7 +135,14 @@ Fortran lines longer than 120 columns must be split with `&` before formatting.
`ErrMsg (CHARACTER(*), INTENT(OUT))`.
- LAPACK: banded solvers (`DGBSV`, and `DPBTRF`/`DPBTRS` for symmetric positive
definite bands); a dense assembly is packed into band storage before the solve.
- Warning-clean under `-std=f2018 -Wall -Wextra -fimplicit-none`.
- Warning-clean under `-std=f2018 -Wall -Wextra -fimplicit-none` with gfortran 15
and 16. gfortran 16 can report `-Wuninitialized` for a default-initialised local
derived-type variable with allocatable components: the optimiser copies the
unset bounds of its unallocated arrays, which the program never reads. Such a
local is `ALLOCATABLE` where this occurs.
- Keep large data off the stack: a local array or derived-type variable that can
exceed about 64 KB is `ALLOCATABLE`, except on the per-step path, which uses the
preallocated workspaces and does not allocate.

### SPDX headers

Expand Down
10 changes: 8 additions & 2 deletions app/cabledyn.f90
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ SUBROUTINE run_mixed_held_deck(deck, root, dt, has_dt, run_time, has_run_time, h
INTEGER, INTENT(OUT) :: run_stat
CHARACTER(*), INTENT(OUT) :: run_msg

TYPE(CD_AGG_ModuleType) :: aggregate
TYPE(CD_AGG_ModuleType), ALLOCATABLE :: aggregate ! about 37 KB: on the heap, not the stack
REAL(wp), ALLOCATABLE :: position(:, :), velocity(:, :), acceleration(:, :), load(:, :)
REAL(wp) :: time, first_miss_time, last_miss_time, step_dt
INTEGER :: es, n_moving, nstep, step, n_iter, miss_count, consecutive_miss, max_consecutive_miss
Expand Down Expand Up @@ -447,6 +447,12 @@ SUBROUTINE run_mixed_held_deck(deck, root, dt, has_dt, run_time, has_run_time, h
END IF

mixed_run: BLOCK
ALLOCATE (aggregate, STAT=ios)
IF (ios /= 0) THEN
run_stat = CD_DECKDRV_SOLVEFAIL
run_msg = 'CableDyn_driver: cannot allocate the mixed aggregate'
EXIT mixed_run
END IF
! The aggregate is caller-driven and cannot refresh standalone wave/current
! OPTIONS. Reject such decks by name instead of silently holding zero fluid.
CALL CD_AGG_Init_From_Deck(aggregate, deck, step_dt, es, em, forbid_deck_ambient=.TRUE., &
Expand Down Expand Up @@ -574,7 +580,7 @@ SUBROUTINE run_mixed_held_deck(deck, root, dt, has_dt, run_time, has_run_time, h
END BLOCK mixed_run

IF (file_open) CLOSE (unit)
CALL CD_AGG_End(aggregate, es, em)
IF (ALLOCATED(aggregate)) CALL CD_AGG_End(aggregate, es, em)
END SUBROUTINE run_mixed_held_deck

LOGICAL FUNCTION deck_requests_modes(deck) RESULT(requested)
Expand Down
4 changes: 3 additions & 1 deletion integration/openfast/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ the system code page cannot spell; the static release build embeds it.

For the static Windows release, pass a clean, unpatched checkout to
`release/build_static_windows.ps1`; it applies the series itself (see
[Installation](../../doc/installation.rst)).
[Installation](../../doc/installation.rst)). Its Visual Studio project compiles the CableDyn
adapter with `/heap-arrays:1024`, as the static CMake build compiles the CableDyn core, so arrays
whose size is known only at run time are placed on the heap rather than on the `openfast.exe` stack.

## License of modified files

Expand Down
16 changes: 8 additions & 8 deletions integration/openfast/vs-build/CableDyn.vfproj.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,35 +5,35 @@
</Platforms>
<Configurations>
<Configuration Name="Debug|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" DebugInformationFormat="debugEnabled" MultiProcessorCompilation="true" Optimization="optimizeDisabled" Preprocess="preprocessYes" WarnInterfaces="true" Traceback="true" BoundsCheck="true" StackFrameCheck="true" RuntimeLibrary="rtMultiThreadedDebug" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" DebugInformationFormat="debugEnabled" MultiProcessorCompilation="true" Optimization="optimizeDisabled" Preprocess="preprocessYes" WarnInterfaces="true" Traceback="true" BoundsCheck="true" StackFrameCheck="true" RuntimeLibrary="rtMultiThreadedDebug" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
<Configuration Name="Double_Debug|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" DebugInformationFormat="debugEnabled" MultiProcessorCompilation="true" Optimization="optimizeDisabled" Preprocess="preprocessYes" WarnInterfaces="true" RealKIND="realKIND8" Traceback="true" BoundsCheck="true" StackFrameCheck="true" RuntimeLibrary="rtMultiThreadedDebug" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" DebugInformationFormat="debugEnabled" MultiProcessorCompilation="true" Optimization="optimizeDisabled" Preprocess="preprocessYes" WarnInterfaces="true" RealKIND="realKIND8" Traceback="true" BoundsCheck="true" StackFrameCheck="true" RuntimeLibrary="rtMultiThreadedDebug" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
<Configuration Name="Release|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
<Configuration Name="Double_Release|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" RealKIND="realKIND8" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" RealKIND="realKIND8" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
<Configuration Name="OpenMP_Release|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" OpenMP="OpenMPParallelCode" EnableOpenMPSupport="OpenMPParallelCodeIFX" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" OpenMP="OpenMPParallelCode" EnableOpenMPSupport="OpenMPParallelCodeIFX" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
<Configuration Name="Double_OpenMP_Release|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" OpenMP="OpenMPParallelCode" EnableOpenMPSupport="OpenMPParallelCodeIFX" RealKIND="realKIND8" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" MultiProcessorCompilation="true" Preprocess="preprocessYes" OpenMP="OpenMPParallelCode" EnableOpenMPSupport="OpenMPParallelCodeIFX" RealKIND="realKIND8" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
<Configuration Name="Matlab_Debug|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" DebugInformationFormat="debugEnabled" Optimization="optimizeDisabled" Preprocess="preprocessYes" WarnInterfaces="true" Traceback="true" BoundsCheck="true" StackFrameCheck="true" RuntimeLibrary="rtMultiThreadedDebug" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" DebugInformationFormat="debugEnabled" Optimization="optimizeDisabled" Preprocess="preprocessYes" WarnInterfaces="true" Traceback="true" BoundsCheck="true" StackFrameCheck="true" RuntimeLibrary="rtMultiThreadedDebug" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
<Configuration Name="Matlab_Release|x64" UseCompiler="ifxCompiler" OutputDirectory="..\..\build\lib" IntermediateDirectory="..\..\build\$(Configuration)_$(Platform)\$(ProjectName)\" ConfigurationType="typeStaticLibrary">
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" Preprocess="preprocessYes" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFFortranCompilerTool" SuppressStartupBanner="true" Preprocess="preprocessYes" HeapArrays="1024" UseMkl="mklSequential" AdditionalIncludeDirectories="@CABLEDYN_MODULE_DIR@"/>
<Tool Name="VFLibrarianTool"/>
</Configuration>
</Configurations>
Expand Down
Loading
Loading