@@ -466,15 +466,16 @@ def _register_lines(
466466 if has_dyn_consts :
467467 lines .append ('' )
468468 if has_consts :
469+ # Each constituent scheme's _register returns its array into this
470+ # temp; it is appended to the buffer and reused for the next
471+ # scheme, so _register is called EXACTLY ONCE per scheme.
469472 lines .append (
470473 '{}type({}), allocatable :: scheme_consts(:)' .format (
471474 i2 , _CONST_PROP_TYPE
472475 )
473476 )
474- lines .append ('{}integer :: num_consts, i' .format (i2 ))
475- else :
476- # auto-clone-only path: no scheme-returned temp, no copy
477- # loop, so we don't need ``scheme_consts`` or ``i``.
477+ if has_auto_cloned :
478+ # Counter used only by the auto-clone-constituents buffer growth.
478479 lines .append ('{}integer :: num_consts' .format (i2 ))
479480
480481 # Trace block: dummies referenced inside the gated write so strict
@@ -522,15 +523,18 @@ def _register_lines(
522523 # ``ccpp_register_constituents`` can ``set_const_index`` on each
523524 # without conflicting with other instances. The outer wrapper
524525 # array is allocated once on first call (any instance); each
525- # instance then runs its own two-pass count+pack into its slot.
526- # The state-machine guard above this block ensures each instance
527- # runs the fill at most once.
526+ # instance then appends each scheme's constituents into its slot,
527+ # calling every scheme's ``_register`` EXACTLY ONCE (register may
528+ # allocate persistent module state, so the earlier two-pass
529+ # count+copy that called it twice broke non-idempotent schemes such
530+ # as ``prescribed_aerosols_register``). The state-machine guard
531+ # above this block ensures each instance runs the fill at most once.
528532 #
529533 # auto-clone-constituents: the legacy shim contributes one
530534 # additional ``%instantiate`` per consumer-side
531535 # ``is_constituent`` arg with no register-phase source. Those
532- # synthesised entries participate in the same two-pass
533- # count+pack against the same per-instance buffer slot .
536+ # synthesised entries are appended to the same per-instance buffer
537+ # slot after the scheme-registered entries .
534538 const_scheme_names = {scheme_name for scheme_name , _ in suite_res .constituent_register_calls }
535539 buf = '{}_dynamic_constituents' .format (suite_name )
536540 n_auto_clone = len (suite_res .auto_cloned_constituents )
@@ -545,59 +549,63 @@ def _register_lines(
545549 lines .append ('{}end if' .format (i2 ))
546550 lines .append ('' )
547551
548- # Per-instance two-pass count+pack into this instance's slot.
549- lines .append ('{}num_consts = 0' .format (i2 ))
550- lines .append ('{}! First pass: count constituents' .format (i2 ))
552+ # Single pass: call each constituent scheme's _register EXACTLY ONCE
553+ # and append its returned array to this instance's slot. Start from
554+ # an empty slot and grow it; intrinsic assignment of the constituent
555+ # array constructor deep-copies each entry (same assignment the old
556+ # copy loop used element-wise).
557+ lines .append (
558+ "{}! Pack each scheme's constituents (register run once each)." .format (i2 )
559+ )
560+ lines .append ('{}allocate({}({})%items(0))' .format (i2 , buf , inst_idx ))
551561 for _gname , resolved_call in _register_calls (suite_res ):
552562 if resolved_call .scheme_name in const_scheme_names :
553563 _emit_register_call (resolved_call , i2 , errflg_local , lines )
554564 lines .append (
555- '{}num_consts = num_consts + size(scheme_consts, 1) ' .format (
556- i2 ,
565+ '{0}{1}({2})%items = [{1}({2})%items, scheme_consts] ' .format (
566+ i2 , buf , inst_idx ,
557567 )
558568 )
559569 lines .append ('{}deallocate(scheme_consts)' .format (i2 ))
560- # auto-clone-constituents: synthesised entries contribute a
561- # static count; add a literal at the end of the count pass .
570+ # auto-clone-constituents: reserve n_auto_clone trailing slots after
571+ # the scheme-registered entries, then instantiate into them .
562572 if n_auto_clone > 0 :
573+ lines .append ('' )
563574 lines .append (
564- '{}! auto-clone-constituents: legacy-shim synthesised entries' .format (
575+ '{}! auto-clone-constituents: reserve + instantiate synthesised entries' .format (
565576 i2 ,
566577 )
567578 )
568- lines .append ('{}num_consts = num_consts + {}' .format (i2 , n_auto_clone ))
569- lines .append ('' )
570- lines .append ('{}allocate({}({})%items(num_consts))' .format (
571- i2 , buf , inst_idx ,
572- ))
573- lines .append ('{}num_consts = 0' .format (i2 ))
574- lines .append ('' )
575- lines .append ('{}! Second pass: copy into per-instance buffer' .format (i2 ))
576- for _gname , resolved_call in _register_calls (suite_res ):
577- if resolved_call .scheme_name in const_scheme_names :
578- _emit_register_call (resolved_call , i2 , errflg_local , lines )
579- lines .append ('{}do i = 1, size(scheme_consts, 1)' .format (i2 ))
579+ lines .append (
580+ '{}num_consts = size({}({})%items, 1)' .format (i2 , buf , inst_idx )
581+ )
582+ if has_consts :
583+ # Grow the existing scheme-registered slot by n_auto_clone.
580584 lines .append (
581- '{}{}({})%items(num_consts + i) = scheme_consts(i )' .format (
582- i2 + _INDENT , buf , inst_idx ,
585+ '{}call move_alloc( {}({})%items, scheme_consts)' .format (
586+ i2 , buf , inst_idx ,
583587 )
584588 )
585- lines .append ('{}end do' .format (i2 ))
586589 lines .append (
587- '{}num_consts = num_consts + size(scheme_consts, 1 )' .format (
588- i2 ,
590+ '{}allocate({}({})%items( num_consts + {}) )' .format (
591+ i2 , buf , inst_idx , n_auto_clone ,
589592 )
590593 )
591- lines .append ('{}deallocate(scheme_consts)' .format (i2 ))
592- # auto-clone-constituents: emit one synthesised %instantiate
593- # call per entry into the per-instance buffer slot.
594- if n_auto_clone > 0 :
595- lines .append ('' )
596- lines .append (
597- '{}! auto-clone-constituents: legacy-shim synthesised %instantiate calls' .format (
598- i2 ,
594+ lines .append (
595+ '{0}if (num_consts > 0) {1}({2})%items(1:num_consts) = '
596+ 'scheme_consts(1:num_consts)' .format (i2 , buf , inst_idx )
597+ )
598+ lines .append (
599+ '{}if (allocated(scheme_consts)) deallocate(scheme_consts)' .format (i2 )
600+ )
601+ else :
602+ # No scheme-registered entries: just size the slot directly.
603+ lines .append ('{}deallocate({}({})%items)' .format (i2 , buf , inst_idx ))
604+ lines .append (
605+ '{}allocate({}({})%items({}))' .format (
606+ i2 , buf , inst_idx , n_auto_clone ,
607+ )
599608 )
600- )
601609 for entry in suite_res .auto_cloned_constituents :
602610 _emit_auto_clone_instantiate (
603611 entry , buf , inst_idx , i2 , errflg_local , errmsg_local , lines ,
0 commit comments