Skip to content

Implement Typed IR#504

Draft
Derppening wants to merge 86 commits into
hkust-taco:hkmc2from
Derppening:enhance/typed-ir
Draft

Implement Typed IR#504
Derppening wants to merge 86 commits into
hkust-taco:hkmc2from
Derppening:enhance/typed-ir

Conversation

@Derppening

Copy link
Copy Markdown
Contributor

No description provided.

@LPTK LPTK 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.

Some possible next immediate steps:

  • Update printer to show the erased types at variable and member declaration/definition sites.
  • Update Lowering so it generates erased types from parameter type annotations, to be used to annotate the corresponding VarSymbol.
  • Make sure we are never overriding an existing erased type in a given symbol by using softAssert, as a sanity check.

A subtlety we should get right: the erasure of annotated class parameter types should successfully propagate to their defining fields. Param has a ``fldSym` which can be used for this.

Comment thread hkmc2/shared/src/main/scala/hkmc2/codegen/js/JSBuilder.scala Outdated
new Rewriter(instId).applyBlock(ogBody),
mkReturnCall(restFunSym, restFunArgs))
val refreshedFvSymbols = dtorBranchFnFvs(branchId._1).map(s => s -> new VarSymbol(Tree.Ident(s"fv_${s.nme}")))
val refreshedFvSymbols = dtorBranchFnFvs(branchId._1).map(s => s -> new VarSymbol(Tree.Ident(s"fv_${s.nme}"), erasedType = N))

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.

It seems many cases like this one should carry over the previous erasedType somehow.

Comment thread hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala Outdated
@Derppening

Derppening commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

The current task list (work items created by me, organized by AI):

  • Phase A — erasedType on Result (keystone infra) — ✅ committed

    • Enum redesign — PrimitiveType.Array + totalized sym; three-case ErasedType; ErasedType.sym; erasedType_!; Normalization → ObjectRef
    • Infra — Result extends HasErasedType; lazy val erasedType; abstract def on trait; Value's override val removed; this match over Tuple/Record/Instantiate/Value
    • Materialization sweep (Cat 1 + 2 + 3) — all applied, compiles clean, committed
    • Core sites (subTerm, ReflectionInstrumenter.assign, ValDefn.mk); join-point traps left N
  • Phase B — printer baseline (was C1) — captures post-A erasedType so later tightening shows as a diff

    • showErasedType toggle in Printer.scala (mirror showPurity, default OFF); render at variable + member declaration/definition sites
    • Commit one curated baseline test file (tuple/instantiate/lit/record/call/select/val-def, ≥1 case through Lifter) with post-A types as-is
  • Phase C — Annotation-driven erasedType + invariant guard (was Phase B; lands right after the B baseline)

    • Param-annotation → VarSymbol (Lowering erases param type annotation onto the VarSymbol)
    • Class-param erasure → defining field via Param.fldSym
    • softAssert no-clobber invariant (never override an existing symbol erasedType)
  • Phase D — WatBuilder consumes ErasedType (was C2; correctness harness)

    • Drive anyref cast targets from operand erasedType (additive + N-graceful); WASM goldens shift here
    • Optional explicit asserts when a known erasedType contradicts the required use-site type
  • Phase E — FuncRef + AnyFuncRef (was Phase D)

    • Add AnyFuncRef coarse constant, then FuncRef(params, result)
    • Fill Lambda → FuncRef
  • Phase F — refine residual inference (was Phase E)

    • Call return types (easy win: builtin-op result-type table, survey §6)
    • Select/DynSelect field/member types
    • rest params; Lifter capture symbols; function results
    • revisit resSym/l sites left N in Phase A

@LPTK

LPTK commented Jun 1, 2026

Copy link
Copy Markdown
Contributor
  • join-point traps left N

What does that mean?

  • C1 — showErasedType toggle in Printer.scala

This should be moved to phase B. In fact, it's the first thing yoiu should do, just so you can actually see what you're doing!

@Derppening

Copy link
Copy Markdown
Contributor Author
  • join-point traps left N

What does that mean?

erasedType = N, will be left for Phase D.

What does that mean?

  • C1 — showErasedType toggle in Printer.scala

This should be moved to phase B. In fact, it's the first thijng yoiu should do, just so you can actually see what you're doing!

Good point, I have updated to task list.

Comment thread hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala Outdated
Comment thread hkmc2/shared/src/main/scala/hkmc2/codegen/Block.scala
Comment thread hkmc2/shared/src/main/scala/hkmc2/semantics/Symbol.scala Outdated
Comment thread hkmc2/shared/src/test/mlscript/codegen/ErasedType.mls Outdated
@Derppening

Copy link
Copy Markdown
Contributor Author

Implemented this week:

  • Use the erasedType in as many places in the WAT backend as possible, reflected in the tightening of Wasm types in the diff tests

Remaining Items as of this week:

  • Tightening the Wasm type of return values - Only free functions completed for now
  • Implement isSubtype for Wasm types and merge cast helpers (castConserve and downcastConserve)

Additional good-to-have refactoring:

  • Implement global unreachable-type propagation: The WAT backend currently generates code that is unreachable, e.g. (local.get $matchRes) at the end of a match block even if all arms are control transfer instructions. This forces the type of the block to fallback to anyref.

All of these will be worked on the week after, and I'll go through the entire patchset and clean it up before marking this as ready for review.

blockImpl(stats, res)
case DefineVar(sym, rhs) :: stats =>
term(rhs): r =>
// Seed the binding's erased type before lowering the continuation, so that a later use of

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.

What does it mean by "seed"??

Comment on lines +197 to +199
// A local consumed by a *later* statement in the same (non-hoisted) block is
// seeded before that statement is lowered, so its erased type survives: both `n`
// and the forward use `m` stay `Int`, and the inferred return is `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.

@Derppening

This is all bs and not what I asked you to do at all. It's not the right design. I've already told you the types of variables are to be resolved by a resolver pass before Lowering. I never asked you to reimplement a broken and incomplete form of type inference...

You need to slow down with Claude, take a step back, and actually think about the design and about what your actual task is. And ask me before you make such choices in the design of the compiler.

@LPTK

LPTK commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

TODO:

  • Remove all the type inference bs
  • Add a cast form to the IR – for now try to make it a Result
  • Make Lowering use casts when the erased types don't match

/** The types are the same. */
case Identity
/** The value type is a subtype of the target type. */
case Upcast

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.

👎

/** How a value's erased type relates to the erased type of the slot it flows into. */
enum CastKind:
/** The types are the same. */
case Identity

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.

👎

/** The value type is a supertype of the target type. */
case Downcast
/** The types are provably incompatible. */
case Unrelated

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.

👎

*
* - `rsc` is true if this reference is a resource class.
*/
case class AnyRef(rsc: Bool, csym: ClassLikeSymbol | NoSymbol.type) extends ErasedType, ErasedValueType:

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.

Tip: make ErasedValueType an abstract base class and you will only need extends ErasedValueType.

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.

3 participants