-
Notifications
You must be signed in to change notification settings - Fork 60
better const folding for int and bool #317
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?
Conversation
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.
Love to see this, and I would love to debug & land this ASAP (at a glance it seems reasonably done), can you rebase it?
There's nothing per se preventing this merge, I was going to wait on #321 for some free testing :D But a quick local rebase on it and it all seems to pass, so feel free to merge. |
Looks like the Also, what I thought was a blocker was #317 (comment) - but I see now that you're asking why you had to do such a hack (I would still want to investigate it, but we could open an issue and land this PR as-is). |
} | ||
simple_uni_op! {fneg, float: f_negate} | ||
|
||
/// already unchecked by default |
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.
These comments are kind of funny - I forget exactly how this works but I believe e.g. unchecked_uadd(a, b)
is uN::checked_add(a, b).unchecked_unwrap()
.
That is, overflowing becomes Undefined Behavior.
LLVM has flags that start with n
for this (e.g. nuw
/nsw
meaning "no (un)signed wrap"),
and it turns out rustc_codegen_llvm
is emitting exactly those flags.
SPIR-V seems to have added nuw
/nsw
equivalents in this extension, and later SPIR-V 1.4:
https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_no_integer_wrap_decoration.html#_decorations
I don't expect you to implement using it (which would have to be conditional on the SPIR-V version or that extension being in target-features, anyway), but it would be nice to remove these (wrong-ish) comments, maybe replace them with a big // TODO
or // FIXME
before the block of unchecked_*
etc.
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.
sounds like something that should be an issue and handled in a followup
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.
I only brought it up because IIRC I saw //
comments turn into ///
comments.
If you keep them //
comments I can kinda see the argument for it being preexisting, but it's still sad that it's effectively a lie, and we could at least turn them into e.g.:
// FIXME: take advantage of `unchecked_*` making overflow UB.
(feel free to workshop it further, but even that should be good enough to replace the incorrect comments with)
$( | ||
(ConstValue::Unsigned($int_lhs), ConstValue::Unsigned($int_rhs)) => return self.const_uint_big(result_type, $fold_int), | ||
(ConstValue::Signed($int_lhs), ConstValue::Signed($int_rhs)) => return self.const_uint_big(result_type, $fold_int as u128), | ||
)? | ||
$((ConstValue::Unsigned($uint_lhs), ConstValue::Unsigned($uint_rhs)) => return self.const_uint_big(result_type, $fold_uint), )? | ||
$((ConstValue::Signed($sint_lhs), ConstValue::Signed($sint_rhs)) => return self.const_uint_big(result_type, $fold_sint as u128), )? |
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.
Hmm, I don't see a failure path here (i.e. "what if const-folding fails"), does const_uint_big
truncate? (truncation might be enough for both signed and unsigned integers)
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.
Currently, none of the const-fold ops can fail.
does const_uint_big truncate?
It seems like it does
rust-gpu/crates/rustc_codegen_spirv/src/builder_spirv.rs
Lines 562 to 574 in 05b3449
// HACK(eddyb) this is done so late (just before interning `val`) to | |
// minimize any potential misuse from direct `def_constant` calls. | |
let val = match (val, scalar_ty) { | |
(SpirvConst::Scalar(val), Some(SpirvType::Integer(bits, signed))) => { | |
let size = Size::from_bits(bits); | |
SpirvConst::Scalar(if signed { | |
size.sign_extend(val) as u128 | |
} else { | |
size.truncate(val) | |
}) | |
} | |
_ => val, | |
}; |
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.
Alright, this should be fine for both signed and unsigned, for both arithmetic and bitwise ops (tho the only bitwise op that would ever be a concern is flipping all the bits).
You only have to worry about anything that explicitly cares about the number of bits - e.g. instead of x.wrapping_shl(y)
you should do x.checked_shl(y & ((x_size.bits() - 1) as u128)).unwrap()
, and same for wrapping_shr
.
If you add a test for something like 1u32 << 32
, the result should be 1
(as the shift amount gets masked out and becomes 1u32 << 0
) not 0
(like (1u128 << 32) as u32
would cause).
sdiv, | ||
sint: s_div, | ||
fold_const { | ||
sint(a, b) => a.wrapping_div(b); |
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.
Uhh why does wrapping_div
even exist, it's not an operation that can overflow??
Okay, yeah, the docs do point that out:
Wrapped division on unsigned types is just normal division. There’s no way wrapping could ever happen. This function exists so that all operations are accounted for in the wrapping operations.
What you want is checked_div
and to bail out of if it returns None
. Which probably means that you want to replace if let Some(const_x) = f(x) {...}
with f(x).and_then(|const_x| Some({...}))
, so that the expression can use ?
(or even return None;
, tho that would be confusing).
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.
From the spirv spec:
OpUDiv: Behavior is undefined if Operand 2 is 0.
OpSDiv: Behavior is undefined if Operand 2 is 0. Behavior is undefined if Operand 2 is -1 and Operand 1 is the minimum representable value for the operands' type, causing signed overflow.
From Rust docs:
U: This operation will panic if
other == 0
.
S: This operation will panic ifother == 0
or the division results in overflow.
(Does this panic require debug asserts on?)
If behavior in these rare cases is undefined in spirv anyway, why explicitly not const fold them for them to trigger UB during execution?
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.
(Does this panic require debug asserts on?)
No, it's always there, because there is no possible value it could give (unlike wrapping arithmetic).
If behavior in these rare cases is undefined in spirv anyway, why explicitly not const fold them for them to trigger UB during execution?
Maybe try adding a test? This code will panic and you will get an ICE, most likely.
At runtime, at least in safe Rust code, these divisions are gated behind a panic.
So e.g. this code:
let y = x / 0;
will generate MIR that looks like:
if 0 == 0 { panic!("division by zero"); }
let y = unsafe { x.unchecked_div(0) };
which should optimize to an unconditional panic (the actual UB after the panic never being reachable), not crash the compiler.
// Note: exactudiv is UB when there's a remainder, so it's valid to implement as a normal div. | ||
// TODO: Can we take advantage of the UB and emit something else? |
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.
Oh, that's funny, that's basically the exact situation unchecked_*
is in, but only this has an accurate comment that mentions UB.
918c44f
to
e86b20e
Compare
20c541c
to
82a0ee2
Compare
prevent const folding from hiding issues
simple_shift_op! { | ||
shl, | ||
int: shift_left_logical, | ||
fold_const { | ||
int(a, b) => a.wrapping_shl(b as u32); | ||
} | ||
} | ||
simple_shift_op! { | ||
lshr, | ||
uint: shift_right_logical, | ||
fold_const { | ||
uint(a, b) => a.wrapping_shr(b as u32); | ||
} | ||
} | ||
simple_shift_op! { | ||
ashr, | ||
sint: shift_right_arithmetic, | ||
fold_const { | ||
sint(a, b) => a.wrapping_shr(b as u32); | ||
} | ||
} |
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.
It should be possible to show in a test that these misbehave - see #317 (comment)
see #300
const folding used to only support:
This PR adds const folding for:
icmp
)Should probably add some diff tests