core/llvm_intrinsics: remove llvm.roundeven call from call_float_roundeven (issue 396) #428
No reviewers
Labels
No Milestone
No Assignees
2 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: M-Labs/nac3#428
Loading…
Reference in New Issue
No description provided.
Delete Branch "issue-396"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The previous implementation uses
llvm.roundeven
which is not supported on all platforms as discussed in #396. Therefore, I have updated thecall_float_roundeven
function to make use of other LLVM intrinsic calls to provide functionality ofllvm.roundeven
.Hmm, are there any other codebases doing this?
I did not find a similar issue in other codebases online. However, by using simpler
llvm.floor
calls instead ofllvm.roundeven
, the new function should work on all platforms supporting LLVM floor operations.Well OK but will it return the same/correct results?
It passed the testcases in nac3standalone/demo/ndarray.py file. The functionality was relatively simple (round to nearest integer using even numbers to break tie), so it should return the same results as llvm.roundeven.
Floating point stuff can be deceptive. Please look at a reference roundeven implementation e.g. from glibc.
I compared the implementation with the implementation of floor and roundeven in glibc. The operations in glibc perform bitwise manipulation on the integer to implement the rounding logic. Looking at the sequence of operations in the floor and roundeven functions in the glibc library, the current implementation of call_float_roundeven is missing a check for NaN case. Should I add that as well? Also, are floating values in our case always positive or can they be negative as well?
I'm not sure if doing another manual implementation of this function is the right thing to do in the first place.
Indeed there are often edge cases with NaN, denormal numbers, etc.
As an alternative, I can replace the
llvm.roundeven
withllvm.rint
orllvm.round
in the code. However, their exact functionality is slightly different from roundeven at tie cases. I still believe that it would be more straightforward to use a combination of LLVM intrinsic functions to implement the rounding logic instead.I am using
llvm.floor
andllvm.frem
for rounding to avoid handling of all these cases myself. Also, I have added the quick return cases from glibc implementation (return at NaN or Integer). The new structure is similar to the one used in:https://github.com/lattera/glibc/blob/master/sysdeps/ieee754/dbl-64/s_roundeven.c
I have also added some edge cases to the math.py file to test the functionality
The main requirement is the correctness of the numpy function that currently uses roundeven, i.e. it must always return exactly the same result as numpy, including on edge cases.
Reimplementing roundeven in LLVM IR is quite awkward and error prone, as I hinted in previous comments.
c7c7da7ca2
to14d95ff5a5
14d95ff5a5
to1835561217
Using
llvm.roundeven
was breaking sincemusl libc
did not provide an implementation of the roundeven function. Instead of adding a roundeven function patch for platforms withmusl libc
, I made use ofllvm.nearbyint
(https://llvm.org/docs/LangRef.html#llvm-nearbyint-intrinsic).musl libc
provides an implementation fornearbyint
, and therefore the resulting function works for bothglibc
andmusl libc
platforms.I discovered a similar solution being employed here: https://support.xilinx.com/s/question/0D52E00006hpQ42SAE/roundevenf-undefined-reference-hls?language=en_US
But the core device does not, please add it to the runtime (artiq and artiq-zynq).
Why was this not required with the legacy compiler?