Skip to content

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

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open

better const folding for int and bool #317

wants to merge 11 commits into from

Conversation

Firestar99
Copy link
Member

@Firestar99 Firestar99 commented Jul 3, 2025

see #300

const folding used to only support:

This PR adds const folding for:

  • basic int: sub, div, rem, shr, lshl, ashl, neg, not (+ unchecked variants)
  • basic bool: and, or, xor, not
  • comparison for int and bool (icmp)
  • select with const condition

Should probably add some diff tests

@Firestar99
Copy link
Member Author

@eddyb Do you have any explanation why 43d5107 is required to make libm compile? This PR also adds explicit type checking for each operation, and for some reason libm calls add that meant for integers with two floats instead of calling fadd.

Copy link
Collaborator

@eddyb eddyb left a 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?

@Firestar99
Copy link
Member Author

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.

@Firestar99 Firestar99 marked this pull request as ready for review July 9, 2025 18:24
@eddyb
Copy link
Collaborator

eddyb commented Jul 9, 2025

But a quick local rebase on it and it all seems to pass

Looks like the TypedBuffer (abi.rs fix + tests) changes are gone, which is what I was hoping for, thanks!

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
Copy link
Collaborator

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.

Copy link
Member Author

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

Copy link
Collaborator

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)

Comment on lines 97 to 102
$(
(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), )?
Copy link
Collaborator

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)

Copy link
Member Author

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

// 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,
};

Copy link
Collaborator

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).

Copy link
Member Author

@Firestar99 Firestar99 Jul 14, 2025

Choose a reason for hiding this comment

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

So I've setup an entire difftest around this just to notice that 1u32 << 32 doesn't make any sense. In debug this will panic, in a const block this will panic and fail the compile, and in release (without overflow checks) this is UB. How should we go about handling this "correctly" anyway?

Difftests are in https://github.com/Rust-GPU/rust-gpu/tree/const_folding_difftest due to requiring #321
#321 just got merged so there now in this branch

Comment on lines 1582 to 1583
// 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?
Copy link
Collaborator

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.

@Firestar99 Firestar99 force-pushed the const_folding branch 2 times, most recently from 20c541c to 82a0ee2 Compare July 11, 2025 10:52
Comment on lines 1619 to 1646
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);
}
}
Copy link
Collaborator

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)

@Firestar99 Firestar99 force-pushed the const_folding branch 3 times, most recently from b06ba9c to 603c5fc Compare July 14, 2025 15:24
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.

2 participants