Skip to content

fix(rt): closure aggregate + #setTupleArgs fallback#952

Merged
Stevengre merged 5 commits intomasterfrom
jh/fix-closure-aggregate-tuple-arg
Mar 6, 2026
Merged

fix(rt): closure aggregate + #setTupleArgs fallback#952
Stevengre merged 5 commits intomasterfrom
jh/fix-closure-aggregate-tuple-arg

Conversation

@Stevengre
Copy link
Copy Markdown
Contributor

@Stevengre Stevengre commented Feb 28, 2026

Summary

Add three semantics fixes needed by the closure-call path, plus a regression test:

  • Add aggregateKindClosure reduction:
    • ARGS:List ~> #mkAggregate(aggregateKindClosure(...)) => Aggregate(variantIdx(0), ARGS)
  • Add #setTupleArgs(IDX, VAL:Value) [owise] fallback for unwrapped single-value arguments
  • Add typed-closure callee setup rule setupCalleeClosure3 (with isFunType predicate)
  • Add closure-no-capture.rs regression test

Context

Stable MIR represents closure construction via Rvalue::Aggregate(AggregateKind::Closure(...)). The K semantics had rules for tuple/ADT aggregates, but no rule for aggregateKindClosure.

For closure calls, argument placement goes through #setTupleArgs. Existing rules only handled tuple/list-shaped payloads.

Also, once closure type metadata is present, closure env locals can be typed as Ref<FunType(...)>. Before this PR, closure setup rules only matched VoidType-based closure typing paths, so the typed closure path needed an explicit setup rule.

Red vs Green for aggregateKindClosure

RED:

ListItem(...) ~> #mkAggregate(aggregateKindClosure(...))

No matching rule, execution is stuck.

GREEN:
aggregateKindClosure reduces to Aggregate(variantIdx(0), ARGS), so closure aggregate construction proceeds.

Red vs Green for #setTupleArgs [owise]

After closure aggregate reduction is added, execution advances to the next blocker:

RED (next blocker):

#setTupleArgs(2, Integer(41,8,false))

No existing #setTupleArgs rule matches this plain Value shape.

GREEN:
The fallback

#setTupleArgs(IDX, VAL:Value) => #setLocalValue(..., #incrementRef(VAL)) [owise]

handles the unwrapped single-value case and allows argument setup to continue.

Red vs Green for setupCalleeClosure3

With typed closure metadata, closure env can appear as Ref<FunType(...)>. Existing closure setup rules were guarded for VoidType closure typing paths, so typed closure setup could miss those rules.

RED:
Typed closure env path does not satisfy the existing VoidType guard shape for closure setup, so closure-call setup does not reliably take the typed closure path.

GREEN:
setupCalleeClosure3 adds the typed path explicitly:

  • guard uses isFunType(#lookupMaybeTy(pointeeTy(...)))
  • still performs the same tuple-arg unpacking via #setTupleArgs(2, getValue(LOCALS, TUPLE))

This keeps closure-call argument placement consistent when closure env typing is known.

Proof evidence

Without fix (RED): closure-no-capture.rs gets stuck on closure aggregate, and after adding only the closure aggregate rule it gets stuck at #setTupleArgs(2, Integer(41,8,false)).

With fix (GREEN):

test_prove_rs[closure-no-capture] PASSED

Test plan

  • test_prove_rs[closure-no-capture]
  • make test-integration regression

@Stevengre Stevengre marked this pull request as ready for review March 2, 2026 13:04
@Stevengre Stevengre requested a review from dkcumming March 2, 2026 13:04
@Stevengre Stevengre self-assigned this Mar 2, 2026
@Stevengre Stevengre requested a review from mariaKt March 2, 2026 13:09
Copy link
Copy Markdown
Collaborator

@dkcumming dkcumming left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just commenting for now sorry, I need to look closer tomorrow. But just mentioning that the link you sent was from current rust MIR (fine in this case) but for clarity the AggregateKind we are representing is this Stable MIR one. Also I think the assert would be good in the test to see that the data is correctly manipulated

Comment thread kmir/src/tests/integration/data/prove-rs/closure-no-capture.rs Outdated
Copy link
Copy Markdown
Collaborator

@dkcumming dkcumming left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hesitant to approve without knowing if the change to data.md is mistakenly included or what it relates to. Happy to approve otherwise

fn repro() -> u8 {
let f = |x: u8| x + 1;
apply(f, 41u8)
}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great example. If you would be open to it in the future it would be nice to use git rebase -i to have the inclusion of the test as the first commit with the expected output failing clearly, then the inclusion of the fix in a following commit, then the update of the test from failing to to passing. It isn't a bit deal but it makes it nice to see the commit history as a story solving the problem. I think #919 is a good example of this.

andBool isTypedValue(LOCALS[CLOSURE])
andBool isRefType(lookupTy(tyOfLocal({LOCALS[CLOSURE]}:>TypedLocal)))
andBool isFunType(#lookupMaybeTy(pointeeTy(lookupTy(tyOfLocal({LOCALS[CLOSURE]}:>TypedLocal)))))
[priority(46), preserves-definedness]
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like we are getting some interesting priorities 40, 45 ,46. Maybe not part of this PR but we could do a write up above the rules that explains in relation to the Rust source code and Stable MIR the how the precedence the rules apply

// Closure-call setup for typed closure environments.
// When the closure pointee type is known (`typeInfoFunType`), we still need to unpack
// the second tuple argument into `_2 .. _n` locals.
rule [setupCalleeClosure3]: <k> #setUpCalleeData(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there is a way to refactor the 3 rules since they seem very similar. Not worth thinking about right now as this unblocks these closures but we should do some clean up at some point

Comment on lines +1054 to +1059
rule <k> ARGS:List ~> #mkAggregate(aggregateKindClosure(_CLOSUREDEF, _GENERICARGS))
=>
Aggregate(variantIdx(0), ARGS)
...
</k>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary? It seems unrelated to the test you provided. I removed it and the test passed.

daniel@daniel-MS-7E06 mir-semantics$ git diff
diff --git a/kmir/src/kmir/kdist/mir-semantics/rt/data.md b/kmir/src/kmir/kdist/mir-semantics/rt/data.md
index b47c6fe9..89982d1f 100644
--- a/kmir/src/kmir/kdist/mir-semantics/rt/data.md
+++ b/kmir/src/kmir/kdist/mir-semantics/rt/data.md
@@ -1051,13 +1051,6 @@ Literal arrays are also built using this RValue.
         ...
        </k>

-  rule <k> ARGS:List ~> #mkAggregate(aggregateKindClosure(_CLOSUREDEF, _GENERICARGS))
-        =>
-            Aggregate(variantIdx(0), ARGS)
-        ...
-       </k>
-
-
   // #readOperands accumulates a list of `TypedLocal` values from operands
   syntax KItem ::= #readOperands ( Operands )
                  | #readOperandsAux( List , Operands )
daniel@daniel-MS-7E06 mir-semantics$ make test-integration TEST_ARGS='-k closure-no-capture'
//-snip-
kmir/src/tests/integration/test_integration.py::test_prove_rs[closure-no-capture]
[gw3] [100%] PASSED kmir/src/tests/integration/test_integration.py::test_prove_rs[closure-no-capture]

================================= slowest durations ===================================
39.33s call     src/tests/integration/test_integration.py::test_prove_rs[closure-no-capture]

(2 durations < 0.005s hidden.  Use -vv to show these durations.)
================================== 1 passed in 40.09s ==================================
daniel@daniel-MS-7E06 mir-semantics$

@Stevengre Stevengre force-pushed the jh/fix-closure-aggregate-tuple-arg branch 3 times, most recently from 4e83d3b to 085024d Compare March 6, 2026 03:20
@Stevengre Stevengre requested a review from dkcumming March 6, 2026 03:35
@Stevengre
Copy link
Copy Markdown
Contributor Author

@dkcumming I refactored the commit history to show each change's meaning. And it shows that there is no need for closure3. (Interesting) But the changes in data.md is indeed needed.

@Stevengre Stevengre force-pushed the jh/fix-closure-aggregate-tuple-arg branch from 085024d to a167b02 Compare March 6, 2026 05:03
Copy link
Copy Markdown
Collaborator

@dkcumming dkcumming left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Stevengre Stevengre merged commit 022dafb into master Mar 6, 2026
7 checks passed
@Stevengre Stevengre deleted the jh/fix-closure-aggregate-tuple-arg branch March 6, 2026 06:06
Stevengre added a commit that referenced this pull request Mar 10, 2026
- fix(kompile): map linked NormalSym functions to monoItemFn(noBody)
[953](#953)
- fix(rt): generalize direct-tag enum decoding to any variant count and
discriminant
[955](#955)
- fix(rt): closure aggregate + #setTupleArgs fallback
[952](#952)
- fix(rt): bug fix for castKindPtrToPtr
[974](#974)
- fix(rt): repair closure callee setup for iter-eq repro
[957](#957)
dkcumming added a commit to runtimeverification/solana-token that referenced this pull request Mar 10, 2026
- fix(rt): handle fun-type closure env refs in callee setup
[956](runtimeverification/mir-semantics#956)
- fix(decode): accept signed enum tag scalars in _extract_tag
[954](runtimeverification/mir-semantics#954)
- Added check for FunType in setupCalleeClosure
[969](runtimeverification/mir-semantics#969)
- Merge in latest master
[973](runtimeverification/mir-semantics#973)
- fix(kompile): map linked NormalSym functions to monoItemFn(noBody)
[953](runtimeverification/mir-semantics#953)
- fix(rt): generalize direct-tag enum decoding to any variant count and
discriminant
[955](runtimeverification/mir-semantics#955)
- fix(spl-token): encode multisig signers as pubkey aggregates
[958](runtimeverification/mir-semantics#958)
- fix(rt): closure aggregate + #setTupleArgs fallback
[952](runtimeverification/mir-semantics#952)
- fix(rt): bug fix for castKindPtrToPtr
[974](runtimeverification/mir-semantics#974)
- fix(rt): repair closure callee setup for iter-eq repro
[957](runtimeverification/mir-semantics#957)
- Change the number of signers from 11 to 3
[982](runtimeverification/mir-semantics#982)
- Merging latest master into feature/p-token
[981](runtimeverification/mir-semantics#981)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants