Skip to content

[vm] Prepare mixins for zero-cost use cases. #64235

Open

Description

@modulovalue

This issue proposes two new pragmas so we can more reliably rely on mixin monomorphization.

Vectorization use cases suffer from a lot of duplication, mainly:

  • Iteration over a particular block size (e.g., do we unroll the SIMD loop by 1, 4, 8 or 16?)
  • The strategy that is used over a particular block size (e.g., do we test for equality or non-equality? Do we use &, | or ^?)

This seems like a small issue, but consider that we can, at the very least, very easily vectorize the following (once all the APIs exist, see: #64170):

  • {Int32, Uint32, Int64, Int16, Uint16, Int8, Uint8, Uint8Clamped}List.indexOf
  • {Int32, Uint32, Int64, Int16, Uint16, Int8, Uint8, Uint8Clamped}List.contains
  • {Int32, Uint32, Int64, Int16, Uint16, Int8, Uint8, Uint8Clamped}List.lastIndexOf

A correct 16x unrolled iteration is difficult to understand at first glance, so being able to reuse it across dozens of implementations would be very helpful.

Traditional abstraction approaches (e.g., abstract classes with an interface, passing closures around) don't reliably guarantee monomorphization and prevent inlining from running, which is critical here as we can't afford to pay for the abstraction. Furthermore, they don't support composition, that is, we can't easily implement and decouple the strategy from the block size. This is where we can use mixins.

Mixins are already implemented in a monomorph fashion where their application copies the contents of the mixin over. However this is not happening reliably. Also, implemented abstract mixin members are not inlined by default. Implementations need to provide a prefer inline pragma for them to be inlined.

To solve those problems I propose the following two pragmas:

  • vm:prefer-monomorph-mixin
  • vm:prefer-inline-overrides

vm:prefer-monomorph-mixin

While mixins are already implemented in a way where they are monomorphized, this has apparently lead to code-size issues where a kernel transformation was introduced that deduplicates mixin applications. The purpose of this pragma is to opt out of that mixin deduplication process.

vm:prefer-inline was not chosen because it is already heavily overloaded. Note that this new pragma explicitly doesn't mention the mixin deduplication, because the intent is that future kernel transformers or whatever should attempt to honor this mixin if they could prevent monomorphization from happening.

vm:prefer-inline-overrides

Strategy specializations would still need to annotate their abstract implementations with @pragma("vm:prefer-inline"). This is tedious and an interface-level policy that doesn't belong in an implementation. To remove that, an abstract member annotated with the vm:prefer-inline-overrides pragma opts that member in to a new kernel transformation that propagates vm:prefer-inline to all its implementations.

vm:prefer-inline was explicitly rejected for this purpose because existing code might already use that pragma on abstract members, and we don't want to mess with those.

Concretely, here's what it would look like:

@pragma('vm:prefer-monomorph-mixin')
mixin K {
  @pragma('vm:prefer-inline-overrides')
  int probe(int x);

  int use(int x) => probe(x) + probe(x + 1);
}

class A with K {
  @override
  int probe(int x) => x + 1;
}

class B with K {
  @override
  int probe(int x) => x * 2;
}

@mkustermann raised some concerns in reviews where I introduced mixins to the typed_data library. It turned out not to be an issue today, because the mixin_deduplication transformer uses simple heuristics for deduplication, but it is fragile and future changes to that transformer or the typed_data library could lead to regressions. These pragmas would help there as well.

cc @mraleph @lrhn @mkustermann @rakudrama thoughts?

Activity

  1. added
    area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.
    on Sep 17, 2026
  2. jrdevadattan commented on Sep 17, 2026

    @jrdevadattan

    I can help with a small first slice of this VM/compiler design work. The issue is really about making reusable vectorization strategies monomorphic enough for inlining, so I would begin by documenting the exact abstraction pattern needed for the typed-data indexOf / contains / lastIndexOf cases and then prototype one pragma path against a minimal benchmark. I would keep the first change focused on proving the compiler can specialize the mixin-style structure without paying closure/interface dispatch costs.

  3. modulovalue commented on Sep 17, 2026

    @modulovalue
    ContributorAuthor

    Thanks @jrdevadattan! I'm already on it. No need for help here, but I appreciate it!

  4. jrdevadattan commented on Sep 18, 2026

    @jrdevadattan

    Thanks for the heads-up, @modulovalue. I’ll stay out of your way on this one, and I appreciate you clarifying.

  5. lrhn commented on Oct 14, 2026

    @lrhn
    Member

    This annotation would be better placed on the mixin application, not the declaration.
    The same mixin may be used in different places with different requirements.

    Also, I'd prefer that the annotation is only respected if it occurs in platform code, otherwise I fear users will start sprinkling it around like fairy-dust "for speed", and end up costing themselves more in size increase.

    If there are multiple mixin applications in the same class declaration, and they don't all need to be specialized, then
    you can always introduce private mixin applications:

    class Something extends Super with SpecMixin, GeneralMixin, Spec2Mixin { ... }

    would be

    @pragma('vm:prefer-monomorph-mixin')
    class _SuperSpec = Super with SpecMixin;
    // No annotation
    class _SuperSpecGeneral = _SuperSpec with General;
    @pragma('vm:prefer-monomorph-mixin')
    class Something extends _SuperSpecGeneral with Spec2Mixin { ... {
  6. modulovalue commented on Oct 14, 2026

    @modulovalue
    ContributorAuthor

    This annotation would be better placed on the mixin application, not the declaration.

    I suspect you come from the typed_data mixin CL where your point (to prefer a use site annotation) definitely can make sense.

    Personally I feel like inlining/expanding/monomorphization should be able to be an interface level decision because it has a big effect on performance (and the main purpose of those mixins is performance), in other words, I think users should not be able to accidentally forget "enabling" an essential property of the abstraction they are trying to use.

    Thinking of the more distant future, maybe we should support multiple with clauses and annotations on them:

    class Something extends Super
      @pragma('vm:prefer-monomorph-mixin')
      with SpecMixin, 
      with GeneralMixin,
      @pragma('vm:prefer-monomorph-mixin')
      with Spec2Mixin { ... }

    Also, I'd prefer that the annotation is only respected if it occurs in platform code, otherwise I fear users will start sprinkling it around like fairy-dust "for speed", and end up costing themselves more in size increase.

    I see your fear, but given the fact that mixins are not that widespread, and from what I've seen, prefer-inline did not turn into fairy-dust, I feel like we should be safe (Also, since agentic workflows are so widespread today, from my experience, we can safely assume that this is not going to turn into fairy dust. Modern models appear to be smart enough to not do that, especially if we provide documentation with clear guidelines).

    That being said, I've put this on the back burner for now until we have more places to use these features in.

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

Metadata

Metadata

Assignees

Labels

area-vmUse area-vm for VM related issues, including code coverage, and the AOT and JIT backends.triagedIssue has been triaged by sub team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions