Skip to content

codegen: type-directed specialization — emit native ops where the type is statically known (#5334 Tier 3 / Lever E) #5497

Open

Description

@proggeramlug

Follow-on to #5334. That issue tracked making perry's IR efficient for large/untyped modules; its Tier 1 (outline the dynamic machinery) and Tier 2 (emit less scaffolding) levers all shipped:

This issue carries the one remaining lever, Tier 3 / Lever E: type-directed specialization — the strategic, long-term piece that #5334 explicitly deferred.

Goal

Where a value is statically a number / string / known-shape (real .ts, tsgo, or local inference), emit native ops instead of the dynamic form:

  • fadd / fmul / fcmp instead of js_add / js_rel_lt / IC guards
  • direct slot load/store instead of the inline-cache diamond
  • skip the nan-box round-trip entirely when the value never leaves a typed register

This attacks the two largest line-counts from #5334's measurement at the source — the 4.4M call @js_* and 365K js_typed_feedback_*_guard sites — rather than outlining them after the fact.

Why separate from #5334

Sketch

  1. Thread a static-type signal to codegen op-emit sites (extend the existing static_type_of / class_field_declared_type machinery).
  2. Number/boolean arithmetic + comparisons → native f64 ops, guarded only where the static type is number | undefined-ish.
  3. Known-shape field access → direct slot, bypassing the IC diamond.
  4. String concat / comparison on statically-string operands → direct runtime calls without the dynamic dispatch.
  5. Measure on a typed hot-loop benchmark (IR-line reduction + wall-clock) and on a real .ts app; hard constraint carried over from codegen: IR is ~96x bloated for large/untyped modules (1.25GB / ~15GB clang RSS for a 13MB bundle) — outline dynamic machinery + specialize #5334: no runtime perf regression on real code.

Context: surfaced taking the 13 MB @anthropic-ai/claude-code cli.js through perry. Tiers 1–2 made untyped bundles compilable; this is the typed-code performance frontier.

Activity

  1. added
    performanceRuntime, compile-time, build-size, or memory performance
    on Jun 21, 2026
  2. added a commit that references this issue on Jun 23, 2026
  3. proggeramlug commented on Aug 13, 2026

    @proggeramlug
    ContributorAuthor

    I’m working on a focused first slice of this tracker: threading existing static type information into a native-op specialization path, with IR/codegen regression coverage. I’ll keep the PR scoped and leave release/version metadata to maintainers.

  4. proggeramlug commented on Aug 13, 2026

    @proggeramlug
    ContributorAuthor

    Opened #8031 with the focused Boolean/native-numeric Lever E slice. Proven i1 booleans now feed native arithmetic/fcmp; annotation-only and invalidated facts retain the dynamic fallback.

  5. proggeramlug commented on Aug 15, 2026

    @proggeramlug
    ContributorAuthor

    Status: this is the surviving umbrella for Lever E. Keeping it open, with the current slice map.

    Worth stating explicitly because #7294 (the engine plan tracker) is closed, so if this were closed too, type-directed specialization would have no strategic tracker — only scattered slices.

    The architectural direction now lives in two merged documents: docs/representation-selection-rfc.md (416 lines) and docs/engine-plan.md (690 lines, canonical).

    What has shipped against Lever E

    What is still open under it

    issue
    #8180 array-callback invocation, ~368 instructions/call — 77% of the forEach gap
    #8103 element-shape fact for array-callback parameters — measured at only a ~23% ceiling, so #8180 should lead
    #8099 class-typed parameters refused by guarded specialization
    #8108 / #6812 the static write PIC still rejects any safepoint
    #8171 #8167's residual 22% — a range test provably unnecessary where a branch already narrows the parameter
    #8175 fib40 IPC collapsed 6.30 → 1.92; instruction wins are not converting to time
    #8079 the #8033 cliff — 8 of 9 regressed corpus rows still unrecovered

    Two measured results that should shape the sequencing

    1. Instruction count has stopped predicting time on this axis. perf(codegen): let a specialized entry re-enter itself #8167 cut instructions 45.5x and wall time only 13.9x. Any Lever E claim quoted in instructions alone can overstate the delivered win by ~3x — quote cycles or IPC alongside.
    2. Dynamic-arith site count has stopped predicting cost. interp and iso_miss each lost 22 sites in the last round and got no faster (0.8528 → 0.8524, 0.9896 → 0.9901). The residual is concentrated — parseApply alone holds 30 of ~43 sites in both — so site-count sweeps are the wrong instrument here.

    This issue's original framing still holds: minified bundles are Any almost everywhere so Lever E barely moves them, and the win is for typed programs. The corpus rows above are the measurable proxy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    performanceRuntime, compile-time, build-size, or memory performance

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions