Skip to content

Share UCS splits using new constructs#457

Open
chengluyu wants to merge 4 commits intohkust-taco:hkmc2from
chengluyu:ucs/let-split
Open

Share UCS splits using new constructs#457
chengluyu wants to merge 4 commits intohkust-taco:hkmc2from
chengluyu:ucs/let-split

Conversation

@chengluyu
Copy link
Copy Markdown
Member

@chengluyu chengluyu commented Apr 13, 2026

During UCS normalization, when the same alternative split appears on both the positively (+) and negatively (-) specialized splits of a match, the old implementation duplicated the code. This PR introduces a way to let-bind splits to share the common splits instead.

New Split constructs

Two new Split variants were introduced:

  • LetSplit(sym, tail): declares a named split. The symbol's body holds the shared split.
  • UseSplit(sym): references a previously declared named split.

A new symbol class SplitSymbol holds the shared body and an optional LabelSymbol for lowering.

Changes to Normalization

Method normalize

normalizeImpl returns (Split, Set[SplitSymbol]): the normalized split plus any SplitSymbols whose UseSplit references survive but whose LetSplit hasn't been created yet. This lets LetSplit be placed at the lowest common ancestor of all UseSplit references.

A LetSplit is created only when:

  • The alternative is not trivial (End or UseSplit), and
  • The alternative doesn't reference the same scrutinee (which would make positive/negative specialization produce different results on each side).

Method specialize

specialize returns Opt[Split] (N = unchanged, S(...) = changed), so UseSplit references are preserved when specialization doesn't change the split.

Changes to lowering

lowerSplit translates LetSplit to a Label block and UseSplit to Break. When the continuation is Ret/Thrw, the label is used directly to preserve tail-call position. Otherwise, an exit label with a temp variable is used.

What's removed?

  • createLabelsForDuplicatedBranches and the Labels class (old label-based deduplication) are gone!
  • patMatConsequentSharingThreshold config option and its :patMatConsequentSharingThreshold test command are also removed. I'm not sure about this step, we can also introduce them back.

# Conflicts:
#	hkmc2/shared/src/test/mlscript/block-staging/Functions.mls
#	hkmc2/shared/src/test/mlscript/codegen/MergeMatchArms.mls
#	hkmc2/shared/src/test/mlscript/ucs/general/LogicalConnectives.mls
#	hkmc2/shared/src/test/mlscript/ucs/normalization/Deduplication.mls
Copy link
Copy Markdown
Contributor

@LPTK LPTK left a comment

Choose a reason for hiding this comment

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

Nice changes, thanks!

Please avoid duplicating specialized splits when they're used several times with the same specialization. (Later, we might also try to preserve more sharing between specializations.)

Also, please add tests for these cases (sharing of specialized splits and lack thereof).

//│ set arg$Some$0$ = s⁰.value⁰;
//│ match arg$Some$0$
//│ 1 =>
//│ begin
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

FIXME why was there a begin left?

case Split.Cons(head, tail) => Split.Cons(head, tail ++ those)
case Split.Let(name, term, tail) => Split.Let(name, term, tail ++ those)
case Split.Else(_) /* impossible */ | Split.End => those)
case Split.Else(_) /* impossible */ | Split.End => those
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
case Split.Else(_) /* impossible */ | Split.End => those
case Split.Else(_) => softAssert(false); those
case Split.End => those

* placement is deferred to the lowest common ancestor of their UseSplit references. */
case class JoinPointCtx(pending: Set[SplitSymbol]):
def +(sym: SplitSymbol): JoinPointCtx = JoinPointCtx(pending + sym)
case class JoinPointCtx(pending: Set[SplitSymbol], sharingThreshold: Opt[Int]):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason to put that field in here, when the Config is already available everywhere?

//│ > x = 42
//│ > x

:fixme // Label/Break blocks from LetSplit lowering not yet supported in ReflectionInstrumenter
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please reintroduce this fixme and the conditions to make it break.

case _ => lhs

inline def apply(split: Split): Split = normalize(split)(using VarSet())
inline def apply(split: Split): Split = normalize(split)(using VarSet(), JoinPointCtx.empty)._1
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Note: bug. Should be fixed by moving out the needless field.

//│ set scrut = true;
//│ let scrut, scrut1, tmp, tmp1;
//│ set scrut = true;
//│ block σ$x:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This label seems unusedd. Is this on purpose?

//│ if (scrut1 === true) {
//│ Predef.print(1);
//│ continue lbl
//│ σ$x: {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why do we ahve a label here? It seems there is no sharing.

// > $j


// * Contemplate this absolutely horrendous code (before optimization):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

TODO: Update comment.

@LPTK LPTK mentioned this pull request Apr 15, 2026
9 tasks
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