-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move CLIF legalization into ISLE #7321
base: main
Are you sure you want to change the base?
Conversation
Adding myself as a reviewer in addition to Trevor as I definitely want to look this over... this is really exciting! |
One of the more recent test failures highlights a weakness of this strategy. One of the concerns brought up was that if legalization removes a construct there's nothing preventing optimizations from recreating the construct. That in fact just happened. For example a This also reminds me that I have not done requisite performance work for this. I don't think it will have much of an impact on compile time since it's not much different than the previous legalization pass. That being said I should still confirm before landing of course. |
One high-level thought: if legalization rewrites were egraph rules, that would allow us to use the cost mechanism to push away from "illegal" outputs for a given ISA: the op we want to legalize away has infinite cost. That would also address one concern I had skimming this just now, namely the |
Ah, and I guess using the egraph pass in this way would also force the question of side-effecting ops -- we need to be able to create new ones, and replace/delete existing ones. If we keep this as a separate pass for that reason then one possible tweak I'd suggest to the API is to have ctors the same as in the egraph ISLE environment (so |
Subscribe to Label Action
This issue or pull request has been labeled: "cranelift", "cranelift:area:aarch64", "cranelift:area:x64", "cranelift:meta", "isle"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
Yeah the I originally wanted to reuse the I do wish there was something better than |
CFG modification would be difficult at best in the egraph rewrite infra, because that's not really the paradigm the thing is built around -- the skeleton remains fixed and we deal in value representations. I think I'm settling on "edits to the skeleton are a fundamentally different pass"; that's a much clearer factoring, and points toward this basic design of three passes total -- legalize, opt, lower -- each with different possibilities, namely:
which is basically what you have in this PR (I just wanted to write it out explicitly here). So the main question IMHO, if this is OK with everyone, is the design around
then to sketch some more
and given the rule that emitting any inst deletes the original, and its results are aliased to the return values, this should work. Thoughts? |
(And on a little further thought, with some more autoconversions, the |
I'm slightly concerned about this, I think we are going to run into a lot of these situations. Not for i128, those are fairly hard operations to recognize. But here's an example: So in this case we would also have to disable that rule for the RISC-V backend, if the Which worries me because, we now either have to have a lot of checks in the midend for X arch with Y extension. Or avoid adding rules. RISC-V is particularly bad here since we don't support a lot without extensions, so it might be that we end up adding a lot of legalization rules. |
@afonso360 I agree, but I think that's pretty fundamental here: if we start making backends require a specific subset of total CLIF after opts, then we'll have to be aware of that when rewriting CLIF; the alternative I guess is doing legalization after optimization, which is effectively what we do today (in the sense that the legalization -- expansion of i128 ops etc -- is folded into the lowering) but loses optimization opportunities. Said another way I think there are two separate choices here: what we legalize, and when we legalize. This discussion is about when we legalize (and carries the observation that if we legalize before rewrites, those rewrites need to preserve the legalized subset); but we haven't expanded what we legalize yet, and that's what causes the real issue. If we were to legalize rotates into something else in today's (pre-optimization) legalization phase we'd hit the same issues that you're describing, I think -- we're OK mainly because we legalize away fairly little (the |
One option would perhaps be to start running legalization twice? Once before egraphs and once after. Legalization might not expand |
Conclusions from today's meeting (please correct me if I'm wrong):
|
Pattern-match an `iconcat` with a constant lower value in addition to `maybe_uextend`. This is in preparation for the next commit where `uextend`-to-i128 always appears as an `iconcat`.
This commit creates the scaffolding necessary to start writing legalizations in ISLE. This intention here is to start out simple, replacing sign/zero-extend instructions to `i128` with an appropriate `iconcat` node. The goal is to eventually rewrite all current legalization into ISLE. Additionally all current legalization would still be shared across backends but each backend would be able to easily define its own legalization rules as well. The intention is to help move some of this legalization specifically before the egraph optimization phase so backends have an easier time of generating optimal code without having to do so much locally.
Now it's done in ISLE!
Update the `legalize` constructor to provide more control over where to resume legalization.
This additionally enables deleting all existing Rust-base simple legalization now that nothing depends on it any more.
Ensure that instructions aren't skipped
This should hopefully be a bit more easy to read and more understandable because it's done at a higher level than before. Additionally this fixes minor issues such as using AVX instructions when available and additionally breaking false dependencies with a leading `xorps` of the destination register.
Use Cargo features to disable ISLE compiles if they're not needed.
Use the last-inserted instruction as the one to replace all the original values of the original instruction with.
f672347
to
c8ed85e
Compare
Ok I've come back to this and I've implemented the first piece which is to unify the ctors/etors into one. The downside of that commit, however, is that there's quite a few changes in the test suite, not all of which are positive I think. For example I saw a number of x64 Additionally as I've dug in more, I'm not actually sure how to implement the cost function. The nuances of what can and cannot be codegen'd cannot be represented currently in the cost model and what the inputs are. For example given the legalization implemented in this PR the x64 backend cannot codegen Now one answer could perhaps be "well we just can't legalize that and the backend must support it". I'm not sure how to best handle that. |
We can't pass both of those to the cost function? |
Ah sorry I should speak a bit more precisely. Yes both types can be passed in by I gave the wrong impression that this would be sufficient. Instead what actually needs to happen is that a |
I forgot this PR existed and got excited again while reading over the discussion history. I think that, with some work, there's a clean solution to the problem of the egraph pass undoing target-specific legalizations: Delay elaboration until lowering. I've written that up in #8529 because I think it's useful independently. There's still a problem if the legalized version gets eliminated by The above is all stuff we could do separately from this PR. I think it would be worthwhile to remove the parts of this PR that change any lowering rules, and just convert the existing legalization pass to ISLE. |
A bit belated getting back to this, but that sounds plausible to me! I'll probably leave this be though for now since we're not really changing that much related to legalization nowadays and the main motivation here was to open up the door to simplifying various rules in backends (e.g. backend-specific legalization). Once there's a clearer direction on #8529 I think it'd make sense to start moving in that direction. |
FYI: we now take But I also kind of feel like we shouldn't have to do any legalization in the mid-end. Instead, I think we should remove the instructions that get legalized and replace them with |
The reason I'm using stack_load and stack_store in cg_clif is to simplify the printed clif ir to make it easier to read. As well as to make future optimizations that depend on stack pointers not leaking easier. stack_addr would leak stack pointers as Cranelift doesn't have pointer provenance. |
One day I'd like to have a property where backends don't have to implement lowering for |
+1, that's the "good" usage of legalization IMHO. Historically it was used for (i) "unconditional on all backends" rewrites (stack loads/stores as a good example), (ii) actual instruction lowering until it reached CLIF insts that corresponded one-to-one to machine insts on the target; (iii) "reduce to a previously solved problem" rewrites (narrowing and polyfills). Options (i) and (ii) are "inefficient" in the sense that we can do them better by (i) emitting the final sequence right away, as suggested above, and (ii) using our one-pass lowering scheme that we introduced with MachInsts and VCode. But Option (iii) is eminently reasonable; I too think that there's unnecessary duplication in our 128-bit ops handling, for example. We've talked about "architecture-specific mid-end rules" and this is a use-case for that IMHO -- a backend can opt into the "narrow 128 to 64 bits" ruleset, or selectively opt in but gradually implement more efficient lowerings over time, etc. |
Agreed that use case (iii) continues to make sense. We just don't currently do it at all. (i) and (ii) I think should be removed.
If you only use the proposed It is true that the clif text dumps will have two operations where it used to have one (pre-legalization and optimization, at least). I think that is ultimately acceptable, and a small price to pay for simplifying the IR and compiler internals, but it is a downside. |
For a little more on escape analysis and stack addresses, re: @bjorn3's concerns: it's true that Cranelift doesn't have a notion of pointer provenance, so an arbitrary integer computation that happens to produce a correct stack address could technically access the stack legally; but that's already the case today, under the hood, in the sense that we do translate Short of full provenance though I think we can still have a notion of "leakage" that is formally definable: an address becomes visible (an arbitrary integer computation can produce a pointer that can access it) once used/exposed by any operation other than as the address of a load/store. So |
This PR is the result of some discussion in the Cranelift weekly meeting a few weeks ago at this point. Currently backends are required to cope with any shape of IR thrown at them, modulo some minor platform-agnostic legalization. This can be difficult at times in cases such as:
uextend
as well.As a broad solution to the above issues as well as being in the spirit of "let's write more stuff in ISLE as it's much easier to extend", this PR migrates all legalization into ISLE. The general setup of this PR is that there's a shared set of legalizations which represent the prior backend-agnostic legalizations (e.g. replacing
iadd_imm x, y
withiadd x, (iconst y)
). Each backend then has its own set of legalizations which are added to this set of legalizations to create one largelegalize
function. Additionally the NaN canonicalization pass is now moved into ISLE as well to happen as part of the legalization step (ok so maybe "legalize" isn't the best term for it any more at that point, please bikeshed this or tell me it's a bad idea)Existing legalizations are ported for examples, in addition to two target-specific legalizations:
uextend
-to-i128 no longer needs to be handled in the backend as it's handled in the legalization phase instead. Other backends can in theory include these rules as well, but that's left for a future PR.fcvt_from_uint
instruction's lowering for 64-bit types is no longer a pseudo-instruction in the backend. Instead the pseudo-instruction is deleted and replaced with a legalization. This brings a few benefits such as being able to use AVX instructions in the lowering in addition to breaking false dependencies, neither of which previously happened in the lowering.My main goal with this is to not necessarily solve all the preexisting issues with the backends but instead provide a framework to move forward. When CFG edits are required for lowerings or when constructs would be good to throw at egraph-based optimization it's now easy to add a rule to
legalize.isle
and have it thrown in. This can help shift some complexity around and avoid consolidating it all in one pass.A downside of this approach, however, is that it doesn't provide a means to remove all pseudo-instructions. For example the x64 backend has pseudo-instructions for converting floats to integers, and they can't be legalized with this technique. The reason for this is that the native instruction has no corresponding CLIF instruction which can express it. While it's possible to add
x86_*
instructions to CLIF that seems best left for later only if truly necessary. In the meantime my hope is that backends, as maintenance continues over time, can start sharing more logic in these legalizations related to 128-bit integers for example or otherwise legalizing larger lowerings through this technique rather than all via ISLE lowerings.