-
Notifications
You must be signed in to change notification settings - Fork 1.5k
cranelift: ISLE wrapper for constructing constants for integers, floating-points and vectors #11942
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
base: main
Are you sure you want to change the base?
cranelift: ISLE wrapper for constructing constants for integers, floating-points and vectors #11942
Conversation
Subscribe to Label Action
This issue or pull request has been labeled: "cranelift", "isle"
Thus the following users have been cc'd because of the following labels:
To subscribe or unsubscribe from this label, edit the |
|
I don't feel the best to review this so I'm going to move over to @fitzgen, but thanks for the PR @efJerryYang! (sorry for the delay, we were all at the wasm CG meeting last week) |
fitzgen
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for sending this PR!
After looking at these changes, I'm not sure that using the same constructor for all of {ints, floats, vectors} is worth it. Some of the updated usage sites don't really seem like an improvement. It is also strange to have to create float and vector consts from Imm64s and such.
I think it would be better if we had a unified constructor for just ints and didn't try to also unify floats and vector types under that as well. See my comments below.
| #[inline] | ||
| fn ieee16_from_imm64(&mut self, val: Imm64) -> Ieee16 { | ||
| Ieee16::with_bits(val.bits() as u16) | ||
| } | ||
|
|
||
| #[inline] | ||
| fn ieee32_from_imm64(&mut self, val: Imm64) -> Ieee32 { | ||
| Ieee32::with_bits(val.bits() as u32) | ||
| } | ||
|
|
||
| #[inline] | ||
| fn ieee64_from_imm64(&mut self, val: Imm64) -> Ieee64 { | ||
| Ieee64::with_bits(val.bits() as u64) | ||
| } | ||
|
|
||
| #[inline] | ||
| fn imm64_from_ieee16(&mut self, val: Ieee16) -> Imm64 { | ||
| Imm64::new(val.bits() as i64) | ||
| } | ||
|
|
||
| #[inline] | ||
| fn imm64_from_ieee32(&mut self, val: Ieee32) -> Imm64 { | ||
| Imm64::new(val.bits() as i64) | ||
| } | ||
|
|
||
| #[inline] | ||
| fn imm64_from_ieee64(&mut self, val: Ieee64) -> Imm64 { | ||
| Imm64::new(val.bits() as i64) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we avoid as casts here? Instead, we should use .cast_[un]signed() and {i,u}64::try_from(...).unwrap() as necessary. This makes the behavior we intend explicit.
If we expect that the high bits might be set, and we want to ignore them anyways, then we should explicitly mask them away via Imm64::{zero,sign}_extend_from_width before calling i64::try_from(...).unwrap() and then feeding the bits into Ieee64::with_bits et al.
Otherwise, if the high bits should never be set, then we should just use i64::try_from(...).unwrap() directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this comment! I will keep in mind to use the correct cast later when contributing to this repo, but according to what you have mentioned at the very beginning comment, we will not include the changes for float/vec, is that correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we will not include the changes for float/vec, is that correct?
Correct. The review has my thoughts at a couple of different points in time, apologies for that.
| (rule (simplify | ||
| (isub (fits_in_64 ty) | ||
| (iconst ty (u64_from_imm64 k1)) | ||
| (iconst ty (u64_from_imm64 k2)))) | ||
| (subsume (iconst ty (imm64_masked ty (u64_wrapping_sub k1 k2))))) | ||
| (subsume (const ty (imm64_masked ty (u64_wrapping_sub k1 k2))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is a simple example of how the updated usage doesn't really seem to be an improvement. We are still passing the unwieldy Imm64 type around and explicitly masking to the relevant type, the only difference is we are doing const instead of iconst.
For this kind of change to be an actual improvement, rather than just moving deck chairs around and being size-of-one and half-a-dozen-of-another, I think we would want to have this rule's RHS be something like this:
(subsume (my_const ty (u64_wrapping_sub k1 k2)))Where my_const is whatever the new constant constructor for integer types is called.
Specifically, this folds the imm64_masked call into the helper, which gives us two benefits over the current status quo:
- We are dealing with
u64instead ofImm64, which is what all of the cprop intermediate computations actually use. - We don't need to think about type masking as much ourselves nor repeatedly pass
tyaround in multiple places.
| (subsume (f32const $F32 r))) | ||
| (subsume (const $F32 (imm64_from_ieee32 r)))) | ||
| (rule (simplify (fadd $F64 (f64const $F64 lhs) (f64const $F64 rhs))) | ||
| (if-let r (f64_add lhs rhs)) | ||
| (subsume (f64const $F64 r))) | ||
| (subsume (const $F64 (imm64_from_ieee64 r)))) | ||
|
|
||
| (rule (simplify (fsub $F32 (f32const $F32 lhs) (f32const $F32 rhs))) | ||
| (if-let r (f32_sub lhs rhs)) | ||
| (subsume (f32const $F32 r))) | ||
| (subsume (const $F32 (imm64_from_ieee32 r)))) | ||
| (rule (simplify (fsub $F64 (f64const $F64 lhs) (f64const $F64 rhs))) | ||
| (if-let r (f64_sub lhs rhs)) | ||
| (subsume (f64const $F64 r))) | ||
| (subsume (const $F64 (imm64_from_ieee64 r)))) | ||
|
|
||
| (rule (simplify (fmul $F32 (f32const $F32 lhs) (f32const $F32 rhs))) | ||
| (if-let r (f32_mul lhs rhs)) | ||
| (subsume (f32const $F32 r))) | ||
| (subsume (const $F32 (imm64_from_ieee32 r)))) | ||
| (rule (simplify (fmul $F64 (f64const $F64 lhs) (f64const $F64 rhs))) | ||
| (if-let r (f64_mul lhs rhs)) | ||
| (subsume (f64const $F64 r))) | ||
| (subsume (const $F64 (imm64_from_ieee64 r)))) | ||
|
|
||
| (rule (simplify (fdiv $F32 (f32const $F32 lhs) (f32const $F32 rhs))) | ||
| (if-let r (f32_div lhs rhs)) | ||
| (subsume (f32const $F32 r))) | ||
| (subsume (const $F32 (imm64_from_ieee32 r)))) | ||
| (rule (simplify (fdiv $F64 (f64const $F64 lhs) (f64const $F64 rhs))) | ||
| (if-let r (f64_div lhs rhs)) | ||
| (subsume (f64const $F64 r))) | ||
| (subsume (const $F64 (imm64_from_ieee64 r)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And all of the float constants can remain as {f32,f64}const usages because the ergonomics are better and it doesn't make sense to me why we would want a single constant constructor for both integers and floats, given that a constructor has a single type signature for all rules.
| (const dst n)) | ||
| (rule (simplify (splat (ty_vec128 dst) (iconst $I16 n))) | ||
| (vconst dst (splat16 (u64_uextend_imm64 $I16 n)))) | ||
| (const dst n)) | ||
| (rule (simplify (splat (ty_vec128 dst) (iconst $I32 n))) | ||
| (vconst dst (splat32 (u64_uextend_imm64 $I32 n)))) | ||
| (const dst n)) | ||
| (rule (simplify (splat (ty_vec128 dst) (iconst $I64 n))) | ||
| (vconst dst (splat64 (u64_uextend_imm64 $I64 n)))) | ||
| (const dst n)) | ||
| (rule (simplify (splat (ty_vec128 dst) (f16const _ (u16_from_ieee16 n)))) | ||
| (const dst (imm64 n))) | ||
| (rule (simplify (splat (ty_vec128 dst) (f32const _ (u32_from_ieee32 n)))) | ||
| (vconst dst (splat32 n))) | ||
| (const dst (imm64 n))) | ||
| (rule (simplify (splat (ty_vec128 dst) (f64const _ (u64_from_ieee64 n)))) | ||
| (vconst dst (splat64 n))) | ||
| (const dst (imm64 n))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably makes sense to leave these vector rules as they were as well. Not clear to me that splatting is the best default for vectors, or if we should always take a 128-bit value and reinterpret it as the vector type, or even if we should have a default constant constructor for vector types. Either way, I think the explicit splatting in the old version was clearer here.
Thanks for your review on this @fitzgen ! Given that the purpose of #6038 is to unify the usage of different constant types, but we observed the unnecessary complexity in this PR if we include float/vec, do you think our goal will need to be changed to: replace I will take a closer look soon. And the description of #6038 would then be a bit misleading in this situation. |
Yes, although I think that |
Description
This should close #6038.
This PR builds on top of PR #10528, but replaces the
ty_vectype withty_vec128, which is the only one that's actually used in those productions (e.g.,wasmtime/cranelift/codegen/src/opts/cprop.isle
Lines 227 to 230 in 311bf0f
(decl pure const (Type u64) Value)as by #6038.Note:
f128constsupport is missing at the moment, will include it if maintainers are happy with this PR.Testing
cranelift/filetests/filetests/egraph/cprop-splat.clif- Added%f16x8.cranelift/filetests/filetests/egraph/cprop.clif- Added%f16_fneg_const, coveringf16constusage.cranelift/filetests/filetests/egraph/cprop.clif– Added%u64_to_f64_const, covering the integer-to-float conversion.All tests pass for
cranelift-toolsaccording to the instructions at https://docs.wasmtime.dev/contributing-testing.htmlNotes
A few additional clarifications:
constwith float types incurs an extraIeee64 -> Imm64 -> Ieee64round trip. This is unavoidable becausedecl constcannot be overloaded, so it cannot acceptconst (Type Ieee64)or other float types directly (e.g.,Ieee32orIeee16). I also tried passingDataValueto allow a generic holder for types likeImm64andIeee64, but that likewise caused problems and would require larger changes (the core issue is thatTypeis a struct rather than an enum; ifTypewere an enum likeDataValuethis would be much simpler, but as it stands we can't leverage it directly). After consideration, I'm submitting theImm64-based version. The discardedDataValueversion can be found at efJerryYang@d0df113.F16, along with test cases. I noticed the previous inconsistency but wasn't sure whether it was intentional or an oversight, seewasmtime/cranelift/codegen/src/opts/cprop.isle
Lines 233 to 248 in 8e22ff8
F16. Also,(subsume (f16const $F32 r))inwasmtime/cranelift/codegen/src/opts/cprop.isle
Line 395 in 311bf0f
$F32->$F16).iconst,vconst,f16const,f32constandf64consttoconstand ran thecargo test -p cranelift-toolswith all test cases pass.