Severity: medium/low · Category: gpu-parallel, memory-ub, physics-numerics · Fix order: 11 of 21 — fix this 11th.
Filenames are numbered in reverse fix order: 001 = fix last, 021 = fix first. This file is 011.
Locations: Source/MacProj.cpp:778, Source/MacProj.cpp:562, Source/MacProj.cpp:1068
Based on commit 9bf664bf (line numbers refer to that tree).
The three defects are independent and can be applied separately; only the first also touches NavierStokesBase::ComputeAofs.
The defect
Source/MacProj.cpp:778 — The known-edgestate mac_sync_compute overload passes a default-constructed temporary MultiFab() as the state, but NavierStokesBase::ComputeAofs unconditionally evaluates S.const_array(mfi, S_comp) (NavierStokesBase.cpp:4665) for every grid, indexing the empty FabArray's internal m_fabs_v vector out of bounds.
Source/MacProj.cpp:562 — mac_sync_compute builds the divu constraint at prev_time only, omitting the 0.5dtdsdt half-time correction that both velocity_advection (NavierStokesBase.cpp:3423-3424) and scalar_advection (NavierStokes.cpp:732-733) apply before Godunov/BDS edge-state prediction, so the sync reconstructs edge states with a different divu than the advance used.
Source/MacProj.cpp:1068 — test_umac_periodic performs host-side FArrayBox operations (mfcd.FillFab into 'diff', diff.minusRunOn::Host, diff.normRunOn::Host) on data allocated in device memory ('diff' uses the default arena, u_mac fabs are device-resident), the #147/#109 host-touches-device class.
Why it matters
F024: Any invocation of this public API (kept and documented in MacProj.cpp:733-739 for derived physics codes that supply precomputed edge states, e.g. div(rho U h) syncs) fails on the first grid: AMREX_ASSERT(mfi.LocalIndex() < indexArray.size()) fires in debug builds; in release builds m_fabs_v[li] on an empty vector yields a garbage/null FAB pointer that is immediately dereferenced, segfaulting. The overload cannot work in any configuration.
F065: Multilevel (max_level>=1, do_reflux) run with advection_scheme Godunov or BDS and nonzero, time-varying divu (e.g. do_temp=1 with diffusive temperature so have_divu and have_dsdt are set): during mac_sync the recomputed edge states differ from the advance's by the O(0.5dtdsdt) source-term difference, so Vsync/Ssync and the sync flux-register increments are systematically inconsistent with the advance at coarse-fine boundaries, violating the design requirement stated in fix 82d3106 (#118) that the sync rebuild edge states exactly as the advance did.
F077: GPU build with a periodic direction and mac_proj.check_umac_periodicity=1 in the inputs (the option is user-settable; only its default is 0 on GPU): after each mac projection, FillFab writes and minusRunOn::Host reads device pointers on the host, segfaulting since the_arena_is_managed=false by default.
Suggested fix
F024: keep the documented overload, but stop materializing an undefined state. AMReX-Hydro's ComputeFluxesOnBoxFromState never reads q when knownFaceState is true (an empty Array4 is the accepted sentinel there, exactly as for divu/fq), so ComputeAofs should build S_arr only where it is used, and shape the EB StateRedist rstate_tmp from advc rather than S.boxArray()/S.nGrow() — both must change together or the EB sync still dereferences the empty state. Having MacProj pass a defined alias instead is the maintainer's call.
F065: mirror the 0.5dtdsdt Saxpy the advance applies, so the sync rebuilds edge states with the advance's divu as #118 requires. The pre-port Fortran had the same asymmetry, so this is a long-standing gap, not a migration regression; getDsdt returns a zeroed MultiFab when have_divu/have_dsdt are unset, so runs without divu are unchanged.
F077: follow 92f0e72 (#147) — pinned FAB plus dtoh_memcpy_async/streamSynchronize before minus/norm; hard-disabling the option on GPU is an acceptable alternative.
For Source/MacProj.cpp:778 (F024):
--- a/Source/MacProj.cpp
+++ b/Source/MacProj.cpp
@@ -770,12 +770,20 @@
bool do_crse_add = false;
bool do_fine_add = update_fluxreg;
+ //
+ // ComputeAofs always builds an Array4 from the state MultiFab, even though
+ // it does not use the values when the edge states are known. So it must be
+ // given a defined MultiFab; an alias of Sync has the correct BoxArray and
+ // DistributionMap.
+ //
+ MultiFab Smf(Sync, amrex::make_alias, Sync_indx, ncomp);
+
//
// Compute the mac sync correction.
//
ns_level.ComputeAofs(Sync, /*Ssync_comp*/ Sync_indx, state_comp,
ncomp,
- /*State*/ MultiFab(), /*S_comp*/ int(),//not used when known_edgestates
+ /*State*/ Smf, /*S_comp*/ 0, //not used when known_edgestates
/*forcing*/ nullptr, /*f_comp*/ int(), //not used when known_edgestates
/*constraint divU*/ nullptr, //not used when known_edgestates
fluxes, /*flux_comp*/ 0,
Aliases Sync (AMReX MultiFab alias ctor) so ComputeAofs's unconditional S.const_array(mfi,S_comp) is valid; values are unused once knownFaceState is true (hydro_compute_edgestate_and_flux.cpp:428). In EB builds this also gives the non-StateRedist rstate=&S path the sync data, matching the StateRedist branch's documented choice. Alternative: guard S_arr inside ComputeAofs.
For Source/MacProj.cpp:562 (F065):
--- a/Source/MacProj.cpp
+++ b/Source/MacProj.cpp
@@ -561,6 +561,13 @@
forcing_term = std::make_unique<MultiFab>(grids, dmap, num_state_comps, NavierStokesBase::nghost_force());
divu_fp.reset(ns_level.getDivCond(NavierStokesBase::nghost_force(),prev_time));
+ // Get divu to time n+1/2, exactly as velocity_advection and
+ // scalar_advection do before predicting the edge states.
+ {
+ std::unique_ptr<MultiFab> dsdt(ns_level.getDsdt(NavierStokesBase::nghost_force(),prev_time));
+ MultiFab::Saxpy(*divu_fp, 0.5*dt, *dsdt, 0, 0, 1, NavierStokesBase::nghost_force());
+ }
+
MultiFab& Gp = ns_level.get_old_data(Gradp_Type);
visc_terms.setVal(0.0); // Initialize to make calls below safe
Copies the half-time divu update from the twins at NavierStokesBase.cpp:3423 and NavierStokes.cpp:732. getDsdt (public, non-const ns_level) returns a zeroed MF unless have_dsdt&&have_divu, so runs without dsdt are unchanged. dt here is parent->dtLevel(level) (NavierStokes.cpp:1454), the same dt the advance used.
For Source/MacProj.cpp:1068 (F077):
--- a/Source/MacProj.cpp
+++ b/Source/MacProj.cpp
@@ -989,13 +989,25 @@
std::vector< std::pair<int,Box> > isects;
+ //
+ // MultiFabCopyDescriptor and the FArrayBox comparison below both work on
+ // the host, so they must be given host-accessible copies of u_mac.
+ //
+ Array<MultiFab,AMREX_SPACEDIM> h_umac;
+
for (int dim = 0; dim < AMREX_SPACEDIM; dim++)
{
if (geom.isPeriodic(dim))
{
Box eDomain = amrex::surroundingNodes(geom.Domain(),dim);
- mfid[dim] = mfcd.RegisterMultiFab(&u_mac[dim]);
+ h_umac[dim].define(u_mac[dim].boxArray(), u_mac[dim].DistributionMap(),
+ u_mac[dim].nComp(), u_mac[dim].nGrowVect(),
+ MFInfo().SetArena(The_Pinned_Arena()));
+ amrex::dtoh_memcpy(h_umac[dim], u_mac[dim]);
+ Gpu::streamSynchronize();
+
+ mfid[dim] = mfcd.RegisterMultiFab(&h_umac[dim]);
// How to combine pirm into one global pirm?
// don't think std::vector::push_back() is thread safe
@@ -1061,10 +1073,10 @@
AMREX_ASSERT(pirm_i.m_srcBox.sameSize(pirm_i.m_dstBox));
AMREX_ASSERT(u_mac[dim].DistributionMap()[pirm_i.m_idx] == ParallelDescriptor::MyProc());
- diff.resize(pirm_i.m_srcBox, 1);
+ diff.resize(pirm_i.m_srcBox, 1, The_Pinned_Arena());
mfcd.FillFab(mfid[dim], pirm_i.m_fbid, diff);
- diff.minus<RunOn::Host>(u_mac[dim][pirm_i.m_idx],pirm_i.m_dstBox,diff.box(),0,0,1);
+ diff.minus<RunOn::Host>(h_umac[dim][pirm_i.m_idx],pirm_i.m_dstBox,diff.box(),0,0,1);
const Real max_norm = diff.norm<RunOn::Host>(0);
Registers pinned host copies with the descriptor (so FillFab/copyToMem read host memory) and puts diff in The_Pinned_Arena. amrex::dtoh_memcpy (AMReX_FabArrayUtility.H:1722) falls back to Copy on CPU builds. Caveat: for remote fills AMReX's FabArrayCopyDescriptor still allocates its cache FAB and MPI buffers from The_Arena, so GPU+MPI needs an AMReX-side fix too.
Diff(s) are against 9bf664bf, written from the current source and verified only with git apply --check — never compiled, never run, never applied to the tree. Treat them as precise intent, not tested patches.
Verification evidence
F024 — confirmed (one verifier lens)
Lens 1 (refutation attempt): MacProj.cpp:778 passes /*State*/ MultiFab() to ComputeAofs; NavierStokesBase.cpp:4665 runs const auto& S_arr = S.const_array(mfi, S_comp); unconditionally per grid before known_edge_state is consulted. AMReX fabPtr: AMREX_ASSERT(mfi.LocalIndex() < indexArray.size()); return m_fabs_v[li]; — both empty for MultiFab(), then ->const_array() dereferences the garbage pointer. No in-repo caller exists, so it is latent within stock IAMR, but any invocation of this documented public API crashes as claimed.
F065 — confirmed (one verifier lens)
Lens 1 (refutation attempt): MacProj.cpp:562 divu_fp.reset(ns_level.getDivCond(nghost_force(),prev_time)); with no dsdt Saxpy, vs NSB.cpp:3423-24 and NS.cpp:732-33 MultiFab::Saxpy(*divu_fp, 0.5*dt, *dsdt,...) before edge prediction; divu feeds Godunov::/BDS::ComputeEdgeState via ComputeFluxesOnBoxFromState. Commit 82d3106: "we need to reconstruct the edge states exactly as we made them during the advection step". Reachable: do_temp=1 sets have_divu/have_dsdt (NS_setup.cpp:372-391); mac_sync gated only by do_reflux (default 1) && level<finest_level.
F077 — confirmed (one verifier lens)
Lens 1 (refutation attempt): MacProj.cpp:1064-1070: diff.resize(...) uses default DataAllocator -> The_Arena() (device; AMReX_Arena.cpp:59 the_arena_is_managed = false); FACopyDescriptor.H:852 FillFab does destFab.template copy<RunOn::Host>(*fcdp->localFabSource,...) where localFabSource aliases the device u_mac FAB; then diff.minus<RunOn::Host>(u_mac[dim][idx],...). Reachable on GPU: default 0 (lines 56-60) but pp.query("check_umac_periodicity",...) (line 69) allows opt-in, and mac_project calls it when any direction is periodic.
Based on commit 9bf664bf, which is also the tree the audit verified against. From an automated audit of Source/, Tutorials/ and Util/. Audit finding ids: F024, F065, F077. Reviewer unit(s): MacProj, theme:gpu-capture. Nothing here was compiled or run — the failure scenarios are code reasoning, so the reaching configuration above is the cheapest way to confirm or refute it.
Severity: medium/low · Category: gpu-parallel, memory-ub, physics-numerics · Fix order: 11 of 21 — fix this 11th.
Filenames are numbered in reverse fix order:
001= fix last,021= fix first. This file is011.Locations:
Source/MacProj.cpp:778,Source/MacProj.cpp:562,Source/MacProj.cpp:1068Based on commit
9bf664bf(line numbers refer to that tree).The three defects are independent and can be applied separately; only the first also touches NavierStokesBase::ComputeAofs.
The defect
Source/MacProj.cpp:778— The known-edgestate mac_sync_compute overload passes a default-constructed temporary MultiFab() as the state, but NavierStokesBase::ComputeAofs unconditionally evaluates S.const_array(mfi, S_comp) (NavierStokesBase.cpp:4665) for every grid, indexing the empty FabArray's internal m_fabs_v vector out of bounds.Source/MacProj.cpp:562— mac_sync_compute builds the divu constraint at prev_time only, omitting the 0.5dtdsdt half-time correction that both velocity_advection (NavierStokesBase.cpp:3423-3424) and scalar_advection (NavierStokes.cpp:732-733) apply before Godunov/BDS edge-state prediction, so the sync reconstructs edge states with a different divu than the advance used.Source/MacProj.cpp:1068— test_umac_periodic performs host-side FArrayBox operations (mfcd.FillFab into 'diff', diff.minusRunOn::Host, diff.normRunOn::Host) on data allocated in device memory ('diff' uses the default arena, u_mac fabs are device-resident), the #147/#109 host-touches-device class.Why it matters
F024: Any invocation of this public API (kept and documented in MacProj.cpp:733-739 for derived physics codes that supply precomputed edge states, e.g. div(rho U h) syncs) fails on the first grid: AMREX_ASSERT(mfi.LocalIndex() < indexArray.size()) fires in debug builds; in release builds m_fabs_v[li] on an empty vector yields a garbage/null FAB pointer that is immediately dereferenced, segfaulting. The overload cannot work in any configuration.
F065: Multilevel (max_level>=1, do_reflux) run with advection_scheme Godunov or BDS and nonzero, time-varying divu (e.g. do_temp=1 with diffusive temperature so have_divu and have_dsdt are set): during mac_sync the recomputed edge states differ from the advance's by the O(0.5dtdsdt) source-term difference, so Vsync/Ssync and the sync flux-register increments are systematically inconsistent with the advance at coarse-fine boundaries, violating the design requirement stated in fix 82d3106 (#118) that the sync rebuild edge states exactly as the advance did.
F077: GPU build with a periodic direction and mac_proj.check_umac_periodicity=1 in the inputs (the option is user-settable; only its default is 0 on GPU): after each mac projection, FillFab writes and minusRunOn::Host reads device pointers on the host, segfaulting since the_arena_is_managed=false by default.
Suggested fix
F024: keep the documented overload, but stop materializing an undefined state. AMReX-Hydro's ComputeFluxesOnBoxFromState never reads
qwhen knownFaceState is true (an empty Array4 is the accepted sentinel there, exactly as for divu/fq), so ComputeAofs should build S_arr only where it is used, and shape the EB StateRedist rstate_tmp from advc rather than S.boxArray()/S.nGrow() — both must change together or the EB sync still dereferences the empty state. Having MacProj pass a defined alias instead is the maintainer's call.F065: mirror the 0.5dtdsdt Saxpy the advance applies, so the sync rebuilds edge states with the advance's divu as #118 requires. The pre-port Fortran had the same asymmetry, so this is a long-standing gap, not a migration regression; getDsdt returns a zeroed MultiFab when have_divu/have_dsdt are unset, so runs without divu are unchanged.
F077: follow 92f0e72 (#147) — pinned FAB plus dtoh_memcpy_async/streamSynchronize before minus/norm; hard-disabling the option on GPU is an acceptable alternative.
For
Source/MacProj.cpp:778(F024):Aliases Sync (AMReX MultiFab alias ctor) so ComputeAofs's unconditional S.const_array(mfi,S_comp) is valid; values are unused once knownFaceState is true (hydro_compute_edgestate_and_flux.cpp:428). In EB builds this also gives the non-StateRedist rstate=&S path the sync data, matching the StateRedist branch's documented choice. Alternative: guard S_arr inside ComputeAofs.
For
Source/MacProj.cpp:562(F065):Copies the half-time divu update from the twins at NavierStokesBase.cpp:3423 and NavierStokes.cpp:732. getDsdt (public, non-const ns_level) returns a zeroed MF unless have_dsdt&&have_divu, so runs without dsdt are unchanged. dt here is parent->dtLevel(level) (NavierStokes.cpp:1454), the same dt the advance used.
For
Source/MacProj.cpp:1068(F077):Registers pinned host copies with the descriptor (so FillFab/copyToMem read host memory) and puts diff in The_Pinned_Arena. amrex::dtoh_memcpy (AMReX_FabArrayUtility.H:1722) falls back to Copy on CPU builds. Caveat: for remote fills AMReX's FabArrayCopyDescriptor still allocates its cache FAB and MPI buffers from The_Arena, so GPU+MPI needs an AMReX-side fix too.
Diff(s) are against
9bf664bf, written from the current source and verified only withgit apply --check— never compiled, never run, never applied to the tree. Treat them as precise intent, not tested patches.Verification evidence
F024— confirmed (one verifier lens)Lens 1 (refutation attempt): MacProj.cpp:778 passes
/*State*/ MultiFab()to ComputeAofs; NavierStokesBase.cpp:4665 runsconst auto& S_arr = S.const_array(mfi, S_comp);unconditionally per grid before known_edge_state is consulted. AMReX fabPtr:AMREX_ASSERT(mfi.LocalIndex() < indexArray.size()); return m_fabs_v[li];— both empty for MultiFab(), then->const_array()dereferences the garbage pointer. No in-repo caller exists, so it is latent within stock IAMR, but any invocation of this documented public API crashes as claimed.F065— confirmed (one verifier lens)Lens 1 (refutation attempt): MacProj.cpp:562
divu_fp.reset(ns_level.getDivCond(nghost_force(),prev_time));with no dsdt Saxpy, vs NSB.cpp:3423-24 and NS.cpp:732-33MultiFab::Saxpy(*divu_fp, 0.5*dt, *dsdt,...)before edge prediction; divu feeds Godunov::/BDS::ComputeEdgeState via ComputeFluxesOnBoxFromState. Commit 82d3106: "we need to reconstruct the edge states exactly as we made them during the advection step". Reachable: do_temp=1 sets have_divu/have_dsdt (NS_setup.cpp:372-391); mac_sync gated only by do_reflux (default 1) && level<finest_level.F077— confirmed (one verifier lens)Lens 1 (refutation attempt): MacProj.cpp:1064-1070:
diff.resize(...)uses default DataAllocator -> The_Arena() (device; AMReX_Arena.cpp:59the_arena_is_managed = false); FACopyDescriptor.H:852 FillFab doesdestFab.template copy<RunOn::Host>(*fcdp->localFabSource,...)where localFabSource aliases the device u_mac FAB; thendiff.minus<RunOn::Host>(u_mac[dim][idx],...). Reachable on GPU: default 0 (lines 56-60) butpp.query("check_umac_periodicity",...)(line 69) allows opt-in, and mac_project calls it when any direction is periodic.Based on commit
9bf664bf, which is also the tree the audit verified against. From an automated audit ofSource/,Tutorials/andUtil/. Audit finding ids: F024, F065, F077. Reviewer unit(s): MacProj, theme:gpu-capture. Nothing here was compiled or run — the failure scenarios are code reasoning, so the reaching configuration above is the cheapest way to confirm or refute it.