Skip to content
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

Draft for adding IEEE half-precision floating-point type. #172

Merged
merged 1 commit into from
Jul 21, 2021

Conversation

zakk0610
Copy link
Contributor

@zakk0610 zakk0610 commented Feb 2, 2021

In order to provide the broadest level of compatibility across language versions
and compilers in libraries and programs, we adopt the __fp16 rather than _Float16
as half-precision floating-point types. For example in the printf case, there is
no format specifier for the C language extension type half-precision floating-point.
In the case of passing a _Float16 to %f or %d the value will be ignored and
silently fail, and no value will be formatted. In the case of __fp16, there is an
implicit cast to double, therefore the value will be formatted. So if you pass
_Float16 to printf, you won't be able to print it, as there is no printf format code
for half-precision floating-point types. If you pass __fp16 to printf, it will print
fine, as it will promote to double.

For implementation it in LLVM,
In RISCV, set Opts.NativeHalfArgsAndReturns = 1;
When zfh extension is enabled, set Opts.NativeHalfType = 1;

ref:

  1. https://clang.llvm.org/docs/LanguageExtensions.html#half-precision-floating-point
  2. ARM C Language Extensions http://infocenter.arm.com/help/topic/com.arm.doc.ihi0053d/IHI0053D_acle_2_1.pdf

@nick-knight
Copy link
Contributor

nick-knight commented Feb 2, 2021

Thanks for the proposal.

First, I think this belongs in the C API doc (not the ELF psABI). Although I guess we have a bunch of C type definitions here already (not sure I agree with that decision either).

Second, I'm opposed to this proposal. __fp16 is a historical mess: it was an ARM custom extension, which even ARM doesn't recommend any more. _Float16 is intended to support IEEE 754 binary16 (like Zfh), and is on track to become part of the C standard (although no guarantees it will be selected for C2x).

The observation that printf accidentally (arguably) "works" for __fp16 doesn't change my mind. My perspective here is that printf really shouldn't be expected to work at all on a new type. (And presumably the C language will evolve to address this if _Float16 is added.)

We debated this at length on internal SiFive channels and I believe we established that _Float16 does the right thing already on modern Clang and GCC, except for, if I recall correctly, the case of C++ programs in GCC for RISC-V targets.

EDIT: looking more closely at your proposal, I see that you specify promotion to float or double under certain circumstances, which I'm not sure are compatible with the C-language extension proposal I cited above. Moreover, the properties vary depending on the presence/absence of the Zfh extension. With this in mind, I guess it really is a different type from (proposed) _Float16, so perhaps it's justifiable not to call it that.

@kito-cheng
Copy link
Collaborator

Could you add a description about RISC-V are using IEEE 754-2008 format for __fp16? I think it would be clear since there is 3 different formats could be used in ARM for __fp16.

@kito-cheng
Copy link
Collaborator

I prefer __fp16 rather than _Float16, there is several reasons why:

  • IEC 60559[1] provide very flexible on implementation, see page 5. line 15 in IEC 60559, and it's not finalize or included in any language standard yet, so in theory it could be changed, and I would like to having a more deterministic and control-able type, in case it included in C2X in future, we definitely could support that too.
  • We could define target extension type to what we want, we could clearly define that having different behavior dependent on zfh enabled or not.
  • Unsupported for C++ on GCC, and seems like clang only support IEC 60559 very limited part.
    • Technically supporting that for C++ on GCC isn't too hard, but it's more like ABI change for all GCC target, since most target didn't define ABI for _Float16/_Float16x, of cause we can using some tricky way to support that for RISC-V only, but it's seems like target extension again.
  • No promotion for variadic arguments with _Float16, which is the printf issue Zakk mentioned.
  • It's kind of ironic, ARM's document claim they commanded _Float16, but all their header files are all using __fp16
  • Interesting data from Google search:
    • "__fp16": About 51,500 results (0.45 seconds)
    • "_Float16": About 2,940 results (0.72 seconds) Did you mean: "Float16"

[1] IEC 60559 http://www.open-std.org/jtc1/sc22/wg14/www/docs/n2405.pdf

@zakk0610
Copy link
Contributor Author

zakk0610 commented Feb 2, 2021

Thanks for the proposal.

First, I think this belongs in the C API doc (not the ELF psABI). Although I guess we have a bunch of C type definitions here already (not sure I agree with that decision either).

Do you mean create a new section C language extensions for half float type in https://github.com/riscv/riscv-c-api-doc/blob/master/riscv-c-api.md?

Second, I'm opposed to this proposal. __fp16 is a historical mess: it was an ARM custom extension, which even ARM doesn't recommend any more. _Float16 is intended to support IEEE 754 binary16 (like Zfh), and is on track to become part of the C standard (although no guarantees it will be selected for C2x).

The observation that printf accidentally (arguably) "works" for __fp16 doesn't change my mind. My perspective here is that printf really shouldn't be expected to work at all on a new type. (And presumably the C language will evolve to address this if _Float16 is added.)

We debated this at length on internal SiFive channels and I believe we established that _Float16 does the right thing already on modern Clang and GCC, except for, if I recall correctly, the case of C++ programs in GCC for RISC-V targets.

EDIT: looking more closely at your proposal, I see that you specify promotion to float or double under certain circumstances, which I'm not sure are compatible with the C-language extension proposal I cited above. Moreover, the properties vary depending on the presence/absence of the Zfh extension. With this in mind, I guess it really is a different type from (proposed) _Float16, so perhaps it's justifiable not to call it that.

Yes, it's different to ARM's __fp16.
I will update the issue title, sorry for confusing.

Could you add a description about RISC-V are using IEEE 754-2008 format for __fp16? I think it would be clear since there is 3 >different formats could be used in ARM for __fp16.

Okay

@zakk0610 zakk0610 changed the title Draft for adding __fp16 for half-precision floating-point type. Draft for adding IEEE 754-2008 __fp16 for half-precision floating-point type. Feb 2, 2021
@zakk0610
Copy link
Contributor Author

zakk0610 commented Feb 2, 2021

address kito's comment and fix typo.

@jim-wilson
Copy link
Collaborator

To expand on Kito's comment, IEC 60559 defines _Float16 as an interchange type. It is meant to be used as a type for exchanging FP vales between two computers that may have different FP implementations. So _Float16 is always a 16-bit IEEE FP type, it never promotes, and may not be usable for native arithmetic if the hardware doesn't support exactly the right FP semantics. Thus it isn't equivalent to float. Use of this may cause problems.

__fp16 is intended to work the same as float, except 16-bits instead of 32-bits, but isn't formally specified, so there may be issues with that.

There is an ISO C proposal to add a "short float" type to be used for 16-bit FP arithmetic. This includes adding a printf format specifier for short float values. There is no implementation that I'm aware of, and I don't know the status of the proposal.
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0192r4.html
Actually that is the C++ one, but there is an equivalent C one that I can't find at the moment.

I think short float would be the best approach, except it doesn't exist yet. I think __fp16 is the second best approach, as it is usable today, for both C and C++ in both gcc and clang. _Float16 isn't supported in gcc C++ and because it never promotes it can't be used with stdarg functions like printf. We would also be at risk of violating a standard if we don't design the RISC-V FP support exactly the same way that the standard requires. If someone wants bfloat16 instead of ieee fp16, then you absolutely can not use _Float16 for that, but you can use __fp16 (and short float if it actually existed).

@jim-wilson
Copy link
Collaborator

Actually it is scanf that gets a new format code for short float. printf doesn't need one because short float promotes.

@nick-knight
Copy link
Contributor

So _Float16 is always a 16-bit IEEE FP type, it never promotes, and may not be usable for native arithmetic if the hardware doesn't support exactly the right FP semantics. Thus it isn't equivalent to float. Use of this may cause problems.

I don't see how these problems are relevant: this proposal is explicitly about adding IEEE binary16 support to the C language, and the RISC-V Zfh-extension implements IEEE binary16. I agree there's a risk in effectively adopting a not-yet-approved C-language extension. But the IEEE spec is ratified/published, and Zfh is conformant.

My opinion is that if Zfh is not present, then the compiler should call soft-float routines, just like it currently does if you use float without the F-extension.

We keep coming back to printf... I guess I don't understand the issue. Why can't we introduce a new conversion specifier, or just extend the h length modifier to the existing floating-point conversion specifiers? Or just require the user to explicitly cast to float and use the existing specifiers?

I agree there's a bigger issue concerning supporting non-IEEE types, like BF16, which may be simultaneously present with binary16 on a RISC-V machine. But I think that's out of scope here.

@jim-wilson
Copy link
Collaborator

_Float16 is a distictly different kind of type from float and double. float and double are standard FP types. _Float16 is an interchange FP type. The compiler does not handle them the same way. We run the risk of user confusion if we recommend _Float16 in places where people are accustomed to using float or double. We also accidentally run the risk of violating the proposed standard if we get something wrong.

_Float16 is defined by a proposed extension to the ISO C standard. I'm not aware of an equivalent ISO C++ extension. The GCC support for _Float16 suggests that the C++ proposal doesn't exist yet. Trying to use something in C++ that hasn't been formally defined yet may lead to trouble.

I think both __fp and _Float16 have obvious problems. And that "short float" is the only good solution long term, but there is no implementation as yet. In the short term, since __fp isn't part of any standard, we run no risk of accidentally violating a standard if we use it, and hence I think it is the best solution.

The workaround for printf _Float16 is an explicit cast to float. The "short float" proposed type is a standard FP type, and hence works for printf as it gets promoted. __fp works the same as short float so is OK. Printf isn't a big problem, it is just an example to show that _Float16 does not work the same as standard FP types, and that can potentially lead to user confusion.

Maybe the best solution is to not define a standard C/C++ interface for the zfh extension at this time, and wait until the C and C++ standards have better support for 16-bit float values.

@zakk0610
Copy link
Contributor Author

zakk0610 commented Feb 3, 2021

After digging into clang implementation, __fp16 value in ARM/Aarch64 can be passed by function argument or
returned by a function (https://github.com/llvm/llvm-project/blob/main/clang/lib/Driver/ToolChains/Clang.cpp#L1611).
maybe we could define an alias name to __fp16 for RISCV and write excepted behavior we want in psabi? (ex. IEEE 754-2008 format, allow passed by value and returned by function, promote to double in vaarg, etc).
In the future, we just need to replace __fp16 with our best solution short float type. Does it make sense?

@kito-cheng
Copy link
Collaborator

Just read the spec and gcc implementation again, binary interchange floating types like _Float16 are not guarantee no promotion during evaluation, section X.3 Characteristics in <float.h>, describe that how the type evaluate:

X.3 Characteristics in <float.h>
...
[2] If FLT_RADIX is 2, the value of the macro FLT_EVAL_METHOD (5.2.4.2.2) characterizes the use of 
evaluation formats for standard floating types and for binary interchange and extended floating types:
−1  indeterminable;
 0  evaluate all operations and constants, whose semantic type has at most the range and 
    precision of float, to the range and precision of float; evaluate all other 
    operations and constants to the range and precision of the semantic type;
 1  evaluate operations and constants, whose semantic type has at most the range and 
    precision of double, to the range and precision of double; evaluate all other 
    operations and constants to the range and precision of the semantic type;
 2  evaluate operations and constants, whose semantic type has at most the range and 
    precision of long double, to the range and precision of long double; evaluate 
    all other operations and constants to the range and precision of the semantic type;
 N, where _FloatN is a supported interchange floating type
    evaluate operations and constants, whose semantic type has at most the range and 
    precision of the _FloatN type, to the range and precision of the _FloatN type; 
    evaluate all other operations and constants to the range and precision of the semantic 
    type;

I think that means promote to float, double or long double before evaluation is legal behavior to _Float16, but of cause compiler must define the right FLT_EVAL_METHOD, but unfortunately clang didn't implement this part, it just using no promotion for _Float16, which means set FLT_EVAL_METHOD=16, but ...seem like clang didn't define the value right.

@kito-cheng
Copy link
Collaborator

Oh, one thing we missed is we should define the C++ name mangling for __fp16, and Dh is common used, so I think we could use the same.

@zakk0610 zakk0610 changed the title Draft for adding IEEE 754-2008 __fp16 for half-precision floating-point type. Draft for adding IEEE half-precision floating-point type. Jun 29, 2021
@zakk0610
Copy link
Contributor Author

After discussion with clang and gcc community, we think
C ISO/IEC TS 18661-3 _Float16 is better than redefining
a different behavior type __fp16 (from ACLE) in RISC-V.

  1. ARM folks are working on deprecating __fp16. [1]
  2. In Clang, _Float16 is available in both C and C++ mode. [2]
  3. In GCC, _Float16 is only avaiable in C mode but GCC folks
    think making it available in C++ as GCC extension is doable. [3]

[1] https://lists.llvm.org/pipermail/cfe-dev/2021-March/067850.html
[2] https://gcc.gnu.org/pipermail/gcc/2021-March/234981.html
[3] https://gcc.gnu.org/pipermail/gcc/2021-March/234972.html

@jrtc27 jrtc27 added this to the First Release DoD milestone Jul 9, 2021
riscv-elf.md Outdated
Comment on lines 162 to 163
The half precision floating-point value can be passed as function argument and
returned by a function.
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is already covered by the general "Floating-point reals ...' paragraph above, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, you are right.

riscv-elf.md Outdated Show resolved Hide resolved
After discussion with clang and gcc community, we choose
C ISO/IEC TS 18661-3 `_Float16` rather than redefine
a different behavior type `__fp16` in RISC-V.

1. ARM folks are working on deprecating `__fp16`. [1]
2. In Clang, `_Float16` is available in both C and C++ mode. [2]
3. In GCC, `_Float16` is only avaiable in C mode but GCC folks
think making it available in C++ as GCC extension is doable. [3]

[1] https://lists.llvm.org/pipermail/cfe-dev/2021-March/067850.html
[2] https://gcc.gnu.org/pipermail/gcc/2021-March/234981.html
[3] https://gcc.gnu.org/pipermail/gcc/2021-March/234972.html
@zakk0610
Copy link
Contributor Author

Fixed! Thanks!!

Copy link
Collaborator

@kito-cheng kito-cheng left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM!

@kito-cheng
Copy link
Collaborator

I suppose this is less controversial changes since it's defined same as other targets and also has been implemented on clang and GCC, so we'll merge that once @jrtc27 give an explicit LGTM.

Copy link
Collaborator

@jrtc27 jrtc27 left a comment

Choose a reason for hiding this comment

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

Oops, I meant to approve this but didn't. PR policy requirements are met; Clang support has even been merged by now (llvm/llvm-project@77bb82d).

@kito-cheng kito-cheng merged commit 1942a05 into riscv-non-isa:master Jul 21, 2021
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.

None yet

5 participants