Skip to content

Commit cda7b9a

Browse files
committed
Bug fix for double allocation of constituents
1 parent 44eb383 commit cda7b9a

2 files changed

Lines changed: 64 additions & 56 deletions

File tree

capgen-ng/generator/suite_cap.py

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -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,

unit-tests/test_suite_resolver.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3544,33 +3544,33 @@ def test_no_host_object_referenced(self):
35443544
self.assertNotIn('%lock_table', self.text)
35453545
self.assertNotIn('%new_field', self.text)
35463546

3547-
def test_two_pass_packs_into_buffer(self):
3547+
def test_register_called_once_per_scheme(self):
35483548
register_body = self.text.split('subroutine reg_consts_register')[1].split(
35493549
'end subroutine reg_consts_register'
35503550
)[0]
3551-
self.assertIn('First pass: count', register_body)
3552-
self.assertIn('Second pass: copy into per-instance buffer', register_body)
3553-
# The constituent-providing scheme is called twice (one per pass).
3551+
# Single-pass append: each constituent scheme's _register is called
3552+
# EXACTLY ONCE (the old count+copy two-pass called it twice and broke
3553+
# non-idempotent schemes such as prescribed_aerosols_register).
35543554
self.assertEqual(
3555-
register_body.count('call register_constituents_register'), 2,
3555+
register_body.count('call register_constituents_register'), 1,
35563556
)
3557+
self.assertNotIn('First pass', register_body)
3558+
self.assertNotIn('Second pass', register_body)
35573559

35583560
def test_buffer_allocate(self):
35593561
# Outer wrapper-DDT array is sized to number_of_instances on first
3560-
# call; each instance allocates its own ``%items(num_consts)`` slot.
3562+
# call; each instance starts its own slot empty (``%items(0)``) and
3563+
# appends each scheme's constituents.
35613564
self.assertIn(
35623565
'allocate(reg_consts_dynamic_constituents(',
35633566
self.text,
35643567
)
3565-
self.assertIn(
3566-
'allocate(reg_consts_dynamic_constituents(',
3567-
self.text,
3568-
)
3569-
self.assertIn('%items(num_consts))', self.text)
3568+
self.assertIn('%items(0))', self.text)
35703569

3571-
def test_buffer_populate_loop(self):
3570+
def test_buffer_append(self):
3571+
# Each scheme's returned array is appended to the per-instance slot.
35723572
self.assertIn(
3573-
'%items(num_consts + i) = scheme_consts(i)',
3573+
'%items = [reg_consts_dynamic_constituents(inst_num)%items, scheme_consts]',
35743574
self.text,
35753575
)
35763576

0 commit comments

Comments
 (0)