Skip to content

Merge HandlerLowering into a single file#533

Merged
LPTK merged 9 commits into
hkust-taco:hkmc2from
AnsonYeung:handlers-with-safety
Jun 25, 2026
Merged

Merge HandlerLowering into a single file#533
LPTK merged 9 commits into
hkust-taco:hkmc2from
AnsonYeung:handlers-with-safety

Conversation

@AnsonYeung

Copy link
Copy Markdown
Contributor

Splitting into several files caused difficulty when the code of HandlerLowering changes, stack safety have a lot of assumption about the inner workings of HandlerLowering already and we can avoid a retraversal on the tree since stack safety and handler lowering uses the same analysis information. The HandlerLowering already have several special cases because of stack safety. This also make both pass more consistent as evident by improvement of at least one test case where StackSafetyTransform did not check EffectfulResult before, and now it share the information with HandlerLowering directly.

This also fixes a bug: if the stack safety effect is raised right when the function is resumed by runtime, unwind will wipe out the pc and local variable state due to stack safety always unwind the function to the initial state. Since the pass is now merged, moving the check to after the restoreVars section is easy.

As an added bonus, I removed the forced while loop rewriting for handlers. Since lifting is always done when handler is turned on, loop variables can be safely floated out to the top. This will not cause any semantic change as no nested lambda exist and thus no code can access inner loop variables from the previous iteration.

Comment thread hkmc2/shared/src/test/mlscript/lifter/StackSafetyLift.mls Outdated
@LPTK

LPTK commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Nice simplification!

@CAG2Mark do you think you coudl leave a review (approving or requesting changes) one of these days?

@CAG2Mark

Copy link
Copy Markdown
Contributor

Nice simplification!

@CAG2Mark do you think you coudl leave a review (approving or requesting changes) one of these days?

I can try

@CAG2Mark CAG2Mark left a comment

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.

LGTM otherwise.


lazy val scopedVars: collection.Set[ScopedSymbol] = this match
case Scoped(syms, body) => syms ++ body.scopedVars
case _ => this.subBlocks.foldLeft(Set.empty)((vars, blk) => vars ++ blk.scopedVars)

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 not flatMap?

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.

And use this.subBlocks.iterator to avoid the unnecessary creation of intermediate sets.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought flatMap require convert the list to a set first (this.subBlocks is a list). I guess I can use iterator + flatMap + toSet

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.

If you use .toSet instead of .iterator/.toSet, you create an intermediate set. With toSet, a (probably) more efficient builder will be used to build the result set in one go. Not entirely sure this really makes a real difference one way or the other, though.

Comment thread hkmc2/shared/src/main/scala/hkmc2/codegen/HandlerLowering.scala Outdated

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.

I wonder if we should have tail-call optimization be run after lifting, but before the effect handler transformation.

@AnsonYeung AnsonYeung Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In fact I want to move effect handler transformation to be right before JSBuilder to enjoy all the optimization. Currently effect handler benchmark have a big disadvantage compared to non-instrumented code as the instrumentation completely obfuscate the control flow, and elimination of certain variable that is live at the effectful result become impossible since they will be saved for unwinding.

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.

Good point. We just have to make sure none of the optimization passes assume the absence of funny semantics due to effect handler. But it may be that this is already the case. Not sure.

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.

I guess we can do this in a follow-up PR – IIRC we already talked with @AnsonYeung about how to refactor the compilation pipeline into a cleaner CompilationPipeline class, moving things out of Lowering.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

My intuition is that they should just work. For example, abortive effects are essentially Throw except they behave even nicer due to the absence of finally and that the body is always a separate function. This means that as long as exceptions are handled properly they should be handled as well. The transformation of resumption will put the resumed value into the correct variable as well

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.

Actually, we should probably add support for finally for effect handlers at some point 🤓

This is actually important if we implement exceptions as effects and handlers, for compatibility reasons.

AnsonYeung and others added 2 commits June 25, 2026 22:07
Co-authored-by: Mark Ng <55091936+CAG2Mark@users.noreply.github.com>
@LPTK LPTK merged commit 2224c93 into hkust-taco:hkmc2 Jun 25, 2026
1 check passed
@LPTK LPTK deleted the handlers-with-safety branch June 25, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants