Skip to content

AK/Math: Generic rint, and better rounding inlining + fallout care - #26835

Open
Hendiadyoin1 wants to merge 14 commits into
SerenityOS:masterfrom
Hendiadyoin1:clang-can-math
Open

AK/Math: Generic rint, and better rounding inlining + fallout care#26835
Hendiadyoin1 wants to merge 14 commits into
SerenityOS:masterfrom
Hendiadyoin1:clang-can-math

Conversation

@Hendiadyoin1

Copy link
Copy Markdown
Contributor

No description provided.

@github-actions github-actions Bot added the 👀 pr-needs-review PR needs review from a maintainer or community member label Jun 23, 2026
Comment thread AK/Math/Rounding.h Outdated
@nico

nico commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Do we have any test coverage for rint? (I don't see it in TestMath.cpp and TestAKMath.cpp at least.) Could you add some?

Could a similar impl work in LibC/math.cpp for {,l}lrint{f,,l}? If so, this would also help with #25934.

@Hendiadyoin1
Hendiadyoin1 force-pushed the clang-can-math branch 2 times, most recently from bf02a77 to 6b71064 Compare June 30, 2026 00:08
@Hendiadyoin1

Copy link
Copy Markdown
Contributor Author

Could a similar impl work in LibC/math.cpp for {,l}lrint{f,,l}? If so, this would also help with #25934.

lrint* now delegates to AK/Math, so now the issue is the lack of a long double there I guess?

as for tests, I added a few crude ones for rint and round_to was covered by FixedPoint, which actually exposed an off by one in my rint impl

Comment thread AK/Math/Rounding.h Outdated
Comment thread AK/Math/Rounding.h Outdated
@BuggieBot

Copy link
Copy Markdown
Member

Hello!

One or more of the commit messages in this PR do not match the SerenityOS code submission policy, please check the lint_commits CI job for more details on which commits were flagged and why.
Please do not close this PR and open another, instead modify your commit message(s) with git commit --amend and force push those changes to update this PR.

Comment thread AK/Math/Rounding.h Outdated
{
CONSTEXPR_STATE(round, x);
#if ARCH(AARCH64) || ARCH(RISCV64) || defined(__SSE4_1__)
ELEMENTWISE_BUILTIN(round, x);

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.

let me confirm my understanding here.

The way these get stamped out is something like this:

constexpr FP foo(FP x) {
    if (is_constant_evaluated()) // if consteval? 🤔 
         { some slow path or builtin that the compiler can use at compile time }

   if (has_builtin(builtin_elementwise_foo) {
         call builtin_elementwise_foo(); // guaranteed to not libcall?
   }

#if arch(aarch64)
    call assembly for foo (FP == float, double)
#elif arch(riscv64)
    call assembly for foo (FP == float, double, long double?)
#elif ( SSE 4.1 ) // or w/e revision
    call x86_64 assembly for foo (FP == float, double, long double)
#endif

   return some slow path;
}
  1. Is that what all of these are doing?

  2. Is there a way to keep track of which methods are doing which steps here?

  3. Is anyone doing it out of order that you've touched in this PR?

  4. Should we have all this gunk in AK, or move it to LibC and just have AK consumers call libc methods? (personally, I would prefer AK::foo to be: constexpr version and/or call builtin, and let the compiler call a libcall into libc if it wants)

I don't think we need to answer all these questions right now, but at the very least, question 3 should be answered before we merge this.

Comment thread AK/Math/Rounding.h Outdated
{
if constexpr (sizeof(I) <= sizeof(u32)) {
i32 res;
asm("fcvt.w.d %0, %1"

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.

It'd be nice if we could do builtins instead of inline asm as much as possible. The inline asm is a barrier for at least clang's autovectorizers, and I locally have sped up a bunch of loops dramatically by replacing Math/Rounding.h inline asm with intrinsics and telling clang to vectorize a loop.

@spholz spholz Jul 20, 2026

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.

Don't we currently forward most LibC functions to AK? I'm a bit worried that some of these builtins get compiled to LibC calls. (I did already have that problem with __builtin_ffs before on RISC-V)

But I agree that AK should ideally be completely constexpr. Making it constexpr-only should probably(?) also prevent this exact problem.

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.

I think we should strive to strip the inline asm from AK and move it all into LibC. Then reversing the call-into arrow from LibC -> AK to AK -> LibC would be manageable. It's kind of pointless to include a bunch of if constexpr speculation in the implementation of libcalls.

The only concern imo would be integer operations that the Kernel calls. But for floating point operations, the only thing the kernel knows (and needs to know) how to do is push/pop the FP register state.

@Hendiadyoin1

Copy link
Copy Markdown
Contributor Author

So with the latest commit, I think I can answer Andrew's questions a bit and alleviate some of thakis' concerns:
I have checked and documented the behavior of all rounding builtins and reordered the checks in a way,
that those are only used when we can guarantee they are libcall free,
this also removes a lot of the handwritten assembly so vectorization and compiler reasoning should work better now

I have not really checked all other inline asm spaghetti caves, but I know that the Trig header has some unnecessary nesting

I think I should squash that commit, but I am not sure what the best final commit order and composition would be

@Hendiadyoin1
Hendiadyoin1 force-pushed the clang-can-math branch 5 times, most recently from 65bda6a to bb01b9f Compare July 22, 2026 12:58
Comment thread AK/Math/Rounding.h Outdated
{
if constexpr (sizeof(I) <= sizeof(u32)) {
i32 res;
asm("fcvt.w.d %0, %1"

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.

let's not add new inline asm impls

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure...

Should I also remove the other ones?
afaict compilers really dont like rint that much, only clang 20+ risc-v and x86 clang 21+ seems to concat the rint with the ltrunc

@Hendiadyoin1

Copy link
Copy Markdown
Contributor Author

OK,
I removed the more feng-shui elementwise builtin,
and now only rely on the fallback in round_to, which with the compiler parseable rint should be similarly fast if not faster after optimizations

@Hendiadyoin1
Hendiadyoin1 requested a review from nico July 23, 2026 22:13
@Hendiadyoin1

Copy link
Copy Markdown
Contributor Author

Ah seems like i forgot to remove one, will fix
(Tomorrow)

@Hendiadyoin1
Hendiadyoin1 force-pushed the clang-can-math branch 2 times, most recently from 75f3077 to 87caaa5 Compare July 24, 2026 10:00
Comment thread AK/FloatingPoint.h
};
static_assert(AssertSize<FloatExtractor<f80>, sizeof(f80)>());
#else
// On some platforms (Arm Mac and Arm Windows) long double is a distinct version of f64

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.

I think Windows in general (at least x86 windows) uses double precision floats for long double.

@Hendiadyoin1
Hendiadyoin1 force-pushed the clang-can-math branch 2 times, most recently from 9eb6951 to 65c6a3a Compare July 25, 2026 21:36
@Hendiadyoin1 Hendiadyoin1 changed the title AK/Math: Generic rint, and smarter clang builtins AK/Math: Generic rint, and better rounding inlining + fallout care Jul 25, 2026
Otherwise we try to convert a Nan to u8, which is undefined
This is
a) computationally smarter, and
b) prevents a division by 0 when "mixing" with a 0 alpha color and full
   weight
Seems like we never did, not sure how it ever worked without
That test previously succeeded, as it by coincidence produced a negative
NaNs, which is not the correct result none the less.

Explcitely checking the value does not help, as EXPECT_APPROXIMATE
does not handle NaNs, and enabling them has quite the fallout
While both versions are constexpr-able, directly relying on the compiler
feels nicer
While slightly less efficient with current compilers, this allows more
reasoning and optimizations by those compilers.

There were also some copy-os in the aarch version of the old code,
which now disappears together with the other inline asm implementations
This makes it pick up generic implementations in these cases
To allow compiler reasoning and vectorization we should try to use the
builtins when possible, and avoid assembly in other cases

This commit does a few things:
* Exclude long double from some early `if consteval` paths
  As noted, we cant round-trip cast those in all cases
* Add a table of tested inlining behavior
* Always rely on inlining when guaranteed
* Adjust floor/ceil/trunc to match each other and the compiler (gcc)
  internal expansion more closely
* Reimplement the round fallback based on a trick borrowed from clang
  This now also has an alive2 proof :^)
We now have AK tests relying on this and the fallback paths go through
here
@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. Thank you for your contributions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

👀 pr-needs-review PR needs review from a maintainer or community member

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants