Skip to content

Some fixes to allow for call instructions to name args, returns, and clobbers with constraints.#74

Merged
cfallin merged 3 commits into
bytecodealliance:mainfrom
cfallin:overlapping-fixed-use-def
Sep 20, 2022
Merged

Some fixes to allow for call instructions to name args, returns, and clobbers with constraints.#74
cfallin merged 3 commits into
bytecodealliance:mainfrom
cfallin:overlapping-fixed-use-def

Conversation

@cfallin
Copy link
Copy Markdown
Member

@cfallin cfallin commented Sep 9, 2022

  • Allow early-pos uses with fixed regs that conflict with clobbers (which happen at late-pos), in addition to the existing logic for conflicts with late-pos defs with fixed regs.

    This is a pretty subtle issue that was uncovered in Impossible constraints when a def and use have same fixed-reg constraint and use is live-out #53 for the def case, and the fix here is the mirror of that fix for clobbers. The root cause for all this complexity is that we can't split in the middle of an instruction (because there's no way to insert a move there!) so if a use is live-downward, we can't let it live in preg A at early-pos and preg B != A at late-pos; instead we need to rewrite the constraints and use a fixup move.

    The earlier change to fix Impossible constraints when a def and use have same fixed-reg constraint and use is live-out #53 was actually a bit too conservative in that it always applied when such conflicts existed, even if the downward arg was not live. This PR fixes that (it's fine for the early-use and late-def to be fixed to the same reg if the use's liverange ends after early-pos) and adapts the same flexibility to the clobbers case as well.

  • Reworks the fixups for the def case mentioned above to not shift the def to the Early point. Doing so causes issues when the def is to a reffy vreg: it can then be falsely included in a stackmap if the instruction containing this operand is a safepoint.

  • Fixes the last-resort split-bundle-into-minimal-pieces logic from Limit split count per original bundle with fallback 1-to-N split. #59 to properly limit a minimal bundle piece to end after the early-pos, rather than cover the entire instruction. This was causing artificial overlaps between args that end after early-pos and defs that start at late-pos when one of the vregs hit the fallback split behavior.

@cfallin
Copy link
Copy Markdown
Member Author

cfallin commented Sep 9, 2022

This one is pretty subtle but perhaps one of @fitzgen, @elliottt, @jameysharp would be willing to review? I'm happy to do a live review / talk through it if that's easier.

@cfallin
Copy link
Copy Markdown
Member Author

cfallin commented Sep 12, 2022

Updated to be a little more generic with respect to safepoint handling (not special-casing early defs) as per suggestion by @jameysharp in offline discussion just now. Thanks! Should be ready to review now.

@cfallin
Copy link
Copy Markdown
Member Author

cfallin commented Sep 15, 2022

Another update pushed after finding a bug in the above changes while fuzzing locally.

…clobbers with constraints.

- Allow early-pos uses with fixed regs that conflict with
  clobbers (which happen at late-pos), in addition to the
  existing logic for conflicts with late-pos defs with fixed
  regs.

  This is a pretty subtle issue that was uncovered in bytecodealliance#53 for the def
  case, and the fix here is the mirror of that fix for clobbers. The
  root cause for all this complexity is that we can't split in the
  middle of an instruction (because there's no way to insert a move
  there!) so if a use is live-downward, we can't let it live in preg A
  at early-pos and preg B != A at late-pos; instead we need to rewrite
  the constraints and use a fixup move.

  The earlier change to fix bytecodealliance#53 was actually a bit too conservative in
  that it always applied when such conflicts existed, even if the
  downward arg was not live. This PR fixes that (it's fine for the
  early-use and late-def to be fixed to the same reg if the use's
  liverange ends after early-pos) and adapts the same flexibility to
  the clobbers case as well.

- Reworks the fixups for the def case mentioned above to not shift the
  def to the Early point. Doing so causes issues when the def is to a
  reffy vreg: it can then be falsely included in a stackmap if the
  instruction containing this operand is a safepoint.

- Fixes the last-resort split-bundle-into-minimal-pieces logic from
  bytecodealliance#59 to properly limit a minimal bundle piece to end after the
  early-pos, rather than cover the entire instruction. This was causing
  artificial overlaps between args that end after early-pos and defs
  that start at late-pos when one of the vregs hit the fallback split
  behavior.
This can create impossible situations: e.g., if a vreg is constrained
to p0 as a late-def, and another, completely different vreg is
constrained to p0 as an early-use on the same instruction, and the
instruction also has a third vreg (early-use), we do not want to merge
the liverange for the third vreg with the first, because it would
result in an unsolveable conflict for p0 at the early-point.
@cfallin cfallin force-pushed the overlapping-fixed-use-def branch from 6061c98 to 98adab7 Compare September 19, 2022 19:47
@cfallin
Copy link
Copy Markdown
Member Author

cfallin commented Sep 19, 2022

Rebased to current main.

Comment thread src/ion/liveranges.rs Outdated
Comment thread src/ion/liveranges.rs Outdated
Comment thread src/ion/merge.rs
self.bundles[bundle.index()].set_cached_fixed();
}
if fixed_def {
self.bundles[bundle.index()].set_cached_fixed_def();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this flag ever get cleared, or does it follow the parts of the bundle if it's split up?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It's recomputed along with the other flags when the bundle is split, if it ever is (it actually is a flag on the whole bundle!).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Comment thread src/ion/merge.rs
Comment on lines +102 to +103
if self.bundles[from.index()].cached_fixed_def()
|| self.bundles[to.index()].cached_fixed_def()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If you knew that the fixed def constraints were for the same register, would that change whether or not you needed to bail out here?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, in fact the fuzzbug was exactly this case! The issue is that the way that we do the constraint rewrite adds an anonymous reservation; so this isn't a case of "different fixed def, can't merge" (that is captured by the incompatible-requirement check) but rather "corner case results in impossible problem".

The interesting case is when we have a fixed-use bound to preg P with liverange up to the early point on an inst, a fixed-def bound to preg P (same preg) at the late point on an inst, and that def'd vreg flows into a blockparam arg and over a backedge to the use's vreg. Then we try to merge the two vregs into one bundle (because we try this for every blockparam arg - blockparam def pair). But we rewrote the use to be an "any" use and put an anonymous reservation on preg P at the early-point only. So rejoining the first vreg (the use-side) to the whole bundle and thus requiring it to have the fixed-preg constraint again causes an impossible-to-solve problem.

There may be an even more complex way to rewrite this but I was several levels deep into "introduce fix, get deeper bug" and wanted to bail out of the rabbithole altogether...

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 check is very conservative and is hurting our compiler quite a bit since it makes heavy use of fixed operands. Removing this check gives a 4% code size reduction (I enabled the checker to ensure the result is still valid).

What are the exact requirements that cause the impossible to resolve constraint? Could this check be made more specific?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I don't really have details paged in anymore, beyond the paragraph describing a fuzzing-discovered example I wrote above, nor the cycles at the moment to think through it deeply. You're welcome to remove it and fuzz to (re)discover it, and propose a better fix, of course!

Copy link
Copy Markdown
Member

@elliottt elliottt left a comment

Choose a reason for hiding this comment

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

This looks good to me! Thanks for answering my questions and for the thorough write-up :)

@cfallin cfallin merged commit 1b38a71 into bytecodealliance:main Sep 20, 2022
@cfallin cfallin deleted the overlapping-fixed-use-def branch September 20, 2022 22:58
@cfallin cfallin mentioned this pull request Sep 20, 2022
cfallin added a commit to cfallin/regalloc2 that referenced this pull request Sep 22, 2022
…f original LR.

When a liverange starts at a *late* point of an instruction, and it
undergoes the fallback "split into all minimal pieces" transform, we end
up creating one minimal bundle that starts at the *early* point of the
instruction at the start of the original LR. This can create
impossible-to-allocate situations where a fixed-constraint LR overlaps
another constrained to the same register (e.g. at calls). We fix this by
ensuring the minimal bundle is trimmed only to the half of the
instruction that overlaps the original LR.

This is analogous to the third fix in bytecodealliance#74, but on the other end (start
of LR rather than end of it).
cfallin added a commit that referenced this pull request Sep 22, 2022
…f original LR. (#85)

When a liverange starts at a *late* point of an instruction, and it
undergoes the fallback "split into all minimal pieces" transform, we end
up creating one minimal bundle that starts at the *early* point of the
instruction at the start of the original LR. This can create
impossible-to-allocate situations where a fixed-constraint LR overlaps
another constrained to the same register (e.g. at calls). We fix this by
ensuring the minimal bundle is trimmed only to the half of the
instruction that overlaps the original LR.

This is analogous to the third fix in #74, but on the other end (start
of LR rather than end of it).
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.

Impossible constraints when a def and use have same fixed-reg constraint and use is live-out

3 participants