Traps that live in no other doc, collected because their failure modes are silent —
wrong answers and wrong indices, not error messages. General development pitfalls are
covered in docs/documentation/contributing.md.
- Grid dimensions
m,n,p(cells in x, y, z); 1D: n=p=0, 2D: p=0. Interior0:m; ghost region-buff_size:m+buff_size; bounds structsidwint(1:3)(interior) andidwbuff(1:3)(with ghosts); cell boundariesx_cb(-1-buff_size:m+buff_size). buff_sizeis not a single formula: it's set per reconstruction scheme ins_configure_coordinate_bounds(m_helper_basic.fpp) and floored higher for Lagrange bubbles and IB. Read that routine for the current value rather than assuming one.- Riemann solvers: left state at
j, right state atj+1. - All equation indices live in the
eqn_idxstruct (eqn_idx_infoinm_derived_types.fpp, populated bys_initialize_eqn_idxinm_global_parameters_common.fpp):%cont,%mom,%E,%adv, plus optional ranges (%bub,%stress,%species,%B, ...). The oldcontxb/momxbshorthands are gone. Index positions depend onmodel_eqnsand enabled features — changing either moves ALL indices; never hard-code one.
- WARNING: do NOT wrap
GPU_LOOPinGPU_PARALLELfor spatial loops —GPU_LOOPemits empty directives on Cray and AMD, causing silent serial execution. Spatial loops always useGPU_PARALLEL_LOOP/END_GPU_PARALLEL_LOOP. Macro API:docs/documentation/gpuParallelization.md; signatures:src/common/include/parallel_macros.fpp. Never call theACC_*/OMP_*implementation layers directly. - Only
src/simulation/is GPU-accelerated. Backends: OpenACC (nvfortran primary, Cray) and OpenMP offload (Cray primary, AMD flang, nvfortran). The CPU-only build must always work — every#ifdefneeds a path for all configurations (CPU, ACC, OMP, with/without MPI). Gates:MFC_GPU,MFC_OpenACC,MFC_OpenMP,MFC_MPI,MFC_DEBUG,MFC_SINGLE_PRECISION/MFC_MIXED_PRECISION,MFC_PRE_PROCESS/MFC_SIMULATION/MFC_POST_PROCESS, and compiler macros (_CRAYFTN,__PGI, ...). @:ACC_SETUP_VFs(...)/@:ACC_SETUP_SFs(...)GPU pointer setup compiles only under Cray. Around MPI:GPU_UPDATE(host=...)before send,GPU_UPDATE(device=...)after receive.- An array whose bound is a device global (
dimension(num_fluids),dimension(num_species)) may be passed to a device routine from a parallel-loop body, but not from inside anotherGPU_ROUTINE(parallelism='[seq]'). CCE OpenACC rejects the second form withftn-7066 ... Global in accelerator routine without declare -- num_fluids, and reports it at whatever line it gave up on: remove one trigger and the message walks forward to the next call, so the reported line is not the cause. Only the plain lanes fail - under--case-optimizationthose bounds areparameters, so a green Case Opt lane beside a failing plain one is the signature. Every accepted call site in the tree already obeys this (m_cbc,m_ibm,m_bubbles_EL,s_compute_cell_state): form such a call in the loop body and pass scalars deeper. Neithercray_inlinenor anum_fluids_maxbound nor dropping optional dummies helps - all three were measured. - nvfortran 23.11/24.1 segfault (
fort2 TERMINATED by signal 11) on a caller that passes aparameterarray fromm_thermochem(e.g.molecular_weights) into a declare-target routine. Read such arrays directly in the kernel, or pass a plain local computed from them. - The
USING_AMDfypp guards (86 sites,#:setinsrc/common/include/shared_parallel_macros.fpp) are load-bearing, not a stale workaround - do not "modernize" them away. They swap a device-global array bound for a literal:dimension(3)fornum_dims/num_fluidswhen case optimization is off (64 sites), anddimension(20)forsys_sizeinm_compute_cbc(21 sites, with a matching@:PROHIBITinm_start_upcappingsys_size <= 20under AMD+CBC). SettingUSING_AMD = Falseand rebuilding amdflang--gpu mpwithout case optimization compiles CLEAN - 728 s, zero diagnostics - and then NaNs at step 50 in CBC, riemannwave_speeds=2, IBM, surface tension, QBMM/viscous and MHD HLLD, while both Lagrange bubble cases complete with out-of-tolerance answers. Measured 2026-08-29 on MI210. A compile-only check returns green, so any future attempt to drop these must run the tests, not just build. - CCE OpenACC (19.0.0 through 21.0.2,
-O2;-O0/-O1correct; OpenMP offload unaffected): a device routine that contains anyGPU_LOOP(itself or in anything it calls) must be called with scalars, never with an array element as an actual argument. Everyroutinelevel is affected, including the conformingloop vectorinsideroutine vector. With both ingredients present the element is misaddressed: anintent(in)element reads as garbage, anintent(out)element is never written. Either ingredient alone is fine, which is why master'ss_compute_pressure(q%sf(j,k,l),...)works (no loop) ands_compute_mixture_coefficientsworks (scalar actuals). PR #1811 added the Newton and RK4 loops to the EOS helpers and every call that passed%sf(j,k,l)orblkmod1(k,l,q)ended inNaN(s) in timestep outputon the Frontier CCE OpenACC lanes only, bit-identical on every other backend. Fix: copy elements to locals before the call, receive into a local. 37-line reproducer and the bisection: sbryngelson/compiler-bugscce/acc-routine-element-by-reference, MFC #1815. Do not "fix" it by deleting theseqdirectives instead: they are the idiom master uses in every device routine. - The same "call it from the loop body" rule covers
m_thermochem: callingget_species_*from inside aGPU_ROUTINErather than from the kernel gave CCE OpenMP a runtimeMemory access fault by GPU node-N ... Reason: Unknownon the first step (exit 134), while every other backend ran. Evaluate them at the call site and pass the arrays in. Note this one only shows at runtime, and only on a case that reaches the path - the build is clean.
- Adding one:
_r()definition +_nv()NAMELIST_VARSregistration intoolchain/mfc/params/definitions.py;case_validator.pyonly if physics-constrained (with aPHYSICS_DOCSentry). Fortran declarations and namelist bindings are auto-generated at build time (ninja-tracked custom command) — re-run cmake (or./mfc.sh build) after editing. - Still manual: derived-type
TYPEmember definitions insrc/common/m_derived_types.fpp; default-value assignments ins_assign_default_values_to_user_inputs; theCASE_OPT_EXTRA_LINESliteral intoolchain/mfc/params/generators/fortran_gen.py(coversnum_dims,num_vels,weno_polyn,muscl_polyn,weno_num_stencils,wenojs); multi-variable declaration lines (bc_x/y/z,x/y/z_domain,x/y/z_output, post'sG); and the MPI broadcast residue inm_mpi_proxy(computed variables that are not namelist-bound:m_glb/n_glb/p_glb,cfl_dt,bc_io, and complex struct-member array loops — these cannot be auto-generated and stay hand-listed). Everything else — scalar declarations, plain arrays (FORTRAN_ARRAY_DIMStable indefinitions.py), derived-type namelist declarations includingGPU_DECLARElines and Doxygen descs (TYPED_DECLStable indefinitions.py), the simulation case-optimization declaration block, and the per-target MPI broadcast lists for all namelist-registry scalars (generated_bcast.fpp) — is regenerated at build time by a ninja-tracked custom command (editingparams/*.pytriggers regeneration automatically). Gotcha: ADDING a new file undertoolchain/mfc/params/needs one reconfigure (the custom command's DEPENDS list is globbed at configure time). Under--case-optimizationthe baked-in constants are dropped from the namelist, so changing one needs a rebuild, not a case edit. - Derived-type params (
chem_params,lag_params,rburn) are NOT auto-broadcast:generated_bcast.fppcovers namelist scalars only. Each type needs a hand-written_emit_<name>intoolchain/mfc/params/generators/fortran_gen.pyplus its call site in thetarget == "sim"block, and — if it is read on device — an explicit$:GPU_UPDATE(device='[name]')in BOTHm_global_parameters.fppandm_start_up.fpp(GPU_DECLAREalone does not make it device-resident). Regrouping scalars into a derived type silently drops their broadcast, so every non-root rank keeps thedflt_realsentinel; single-rank goldens cannot see this, so pair such a change with appn=2test and confirm it fails without the emitter. - A
patch_ibmember that anym_ibmghost-point code reads must ALSO be set ins_add_cloud_particle(src/simulation/m_particle_cloud.fpp):particle_cloud_ibsisallocated without default initialization, ands_reduce_ib_patch_arraycopies the whole struct intopatch_ib, overwriting the defaults froms_assign_default_values_to_user_inputs. Anything left unset reaches the solver as uninitialized memory, and only where the allocation is not already zero-filled — a garbagev_blowfailed Frontier AMD withICFL is NaNwhile every NVIDIA lane and all local CPU/GPU runs passed. A platform-only NaN is the signature of this class. - Shared-state pattern: namelist declarations (
#:include 'generated_decls.fpp'), theeqn_idx/sys_sizestate variables, and the common defaults core all live insrc/common/m_global_parameters_common.fpp. Each per-targetm_global_parameters.fppdoesuse m_global_parameters_common(default-public), souse m_global_parameterscontinues to work for all downstream modules without change.src/common/carries noMFC_PRE_PROCESS/MFC_SIMULATION/MFC_POST_PROCESSguards: stage-varying behavior is passed in as an explicit argument or initialization policy, and device residency for generated simulation scalars is emitted fromSIM_GPU_DECL_VARS(toolchain/mfc/params/generators/fortran_gen.py). Generated includes (generated_decls.fpp,generated_bcast.fpp,generated_case_opt_decls.fpp) must exist for every target — pre/post get the common computed scalars (num_dims,num_vels,weno_polyn,muscl_polyn), so a common file that includes one will compile for pre/post too. - Runtime checks (
@:PROHIBIT) go where they run: shared →src/common/m_checker_common.fpp; simulation-only →src/simulation/m_checker.fpp; pre/post-only →src/{pre,post}_process/m_checker.fpp(theirs_check_inputsare currently empty — that IS the right place, not m_checker_common). - Analytic ICs are compiled into the binary. Expressions are AST-validated at case load
(syntax errors and unknown variables are immediate, named errors; bare
eis not a variable — writeexp(1.0)). Each IC variable maps to aneqn_idx%…expression inQPVF_IDX_VARS(toolchain/mfc/case.py); a new patch-settable conserved variable means updating that map AND the Fortraneqn_idxbuilder to agree — a mismatch is a silent wrong index. Variables available in expressions:docs/documentation/case.md.
- Tests are generated programmatically in
toolchain/mfc/test/cases.py(parameter modifications onBASE_CFGvia theCaseGeneratorStackpush/pop pattern); test UUID = CRC32 of the trace string;./mfc.sh test -llists all. --onlymatches whole trace elements, not substrings, and_filter_only(toolchain/mfc/test/test.py) ANDs labels while ORing UUIDs. So--only bubblesmatches nothing (the element isBubbles), and--only low_Mach=1 low_Mach=2asks for cases carrying both and also matches nothing. It then exits 143, which reads like an external kill rather than an empty filter. Pass UUIDs whenever you want the union of several groups.- Sibling
define_case_dcalls off the same stack level are never combined. Two switches that only matter together (avg_state=1needswave_speeds=2to be read at all) therefore get zero effective coverage unless something pushes one and defines the other beneath it. Check reachability before trusting that a flag is tested. --no-buildsilently runs whatever binary is on disk for a configuration it did not build. Chemistry has its own config (gpu-mp-chem-*) that a plain./mfc.sh buildnever produces, so a--no-buildrun reports failures from stale binaries and hides real compile breaks. Run chemistry-touching sets without it.- Pick the newest binary by the binary's mtime (
ls -t build/install/*/bin/simulation), not the install directory's - a stale config's directory can be newer than a fresh build's. - The pre-commit hook lives in the main repo's
.git/hooks/and git exportsGIT_DIRthere during a commit, so from a worktree the toolchain lint enumerates the other checkout and fails. Reproduce withGIT_DIR=<main>/.git ./mfc.sh precheck. Run precheck by hand and commit with--no-verify. /tmpis node-local: scratch does not survive a compute-node change, and its absence is silence, not an error. Keep patches and resource baselines on a shared filesystem.- Golden files are tolerance-compared. Regenerate only the affected tests
(
./mfc.sh test --generate --only <tests>) — an unexplained golden-file diff is a bug report, not noise to be regenerated away.