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

C Backend: f16 and f128 support #10771

Closed
wants to merge 4 commits into from

Conversation

jean-dao
Copy link
Contributor

@jean-dao jean-dao commented Feb 2, 2022

  • 128-bit floats: use __float128 type, re-use 128-bit int literal trick for floats
  • 16-bit floats: use either __fp16 or _Float16 when supported

Compiler support is not super robust, but since __int128 is also used without any #if defined guard, I did the same for __float128.

Support for 16-bit floats is a bit more documented from what I found and I tried to make the #if defined guards match actual compiler support. See https://gcc.gnu.org/onlinedocs/gcc/Half-Precision.html and https://clang.llvm.org/docs/LanguageExtensions.html#id12. (I didn't found any doc for the Microsoft compiler.)

Note/Question: The TODO: f128 ? comment in Value.floatToInt() leads me to think there was maybe a reason (performance related?) not to directly use a f128 instead of a f64, so it may require some rework.

Copy link
Contributor

@matu3ba matu3ba left a comment

Choose a reason for hiding this comment

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

Some things need clarification. Better split this in 2 PRs, as this also includes 128bit integer support. Tests for the c codegen them are also missing.

A few more tests would be great, both for floatop.zig and the C side.
floatop.zig has very few test cases, which do not involve negative numbers or the floating point number edge cases.

assert(bits <= 128);

// Clang and GCC don't support 128-bit integer constants but will
// hopefully unfold them if we construct one manually.
Copy link
Contributor

@matu3ba matu3ba Feb 3, 2022

Choose a reason for hiding this comment

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

It would be helpful, if we could test this in CI. Do you have an idea on that?

const magnitude = if (val.positive)
val.to(u128) catch unreachable
else
std.math.absCast(val.to(i128) catch unreachable);

const high = @truncate(u64, magnitude >> 64);
const low = @truncate(u64, magnitude);

// (int128_t)/<->( ( (uint128_t)( val_high << 64 )u ) + (uint128_t)val_low/u )
Copy link
Contributor

Choose a reason for hiding this comment

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

This reads like it is little endian only.
Make a comptime error for big-endian or clarify.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that I didn't write the int128 handling code, just modifying the function to also accept smaller values. I can put a TODO comment, but I think it should be part of another PR.

Copy link
Contributor

@matu3ba matu3ba Feb 3, 2022

Choose a reason for hiding this comment

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

Better would be a compilation error with comptime check of the endianess.

@@ -386,52 +386,97 @@ pub const DeclGen = struct {
try dg.renderDeclName(decl, writer);
}

fn renderInt128(
fn renderBigIntConst(
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason to rename it like it would accept any integer sizes and then do assert(bits <= 128); in the fn body?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I did those changes (renderInt128/renderBigIntConst refactoring/renaming) is not obvious anymore, I'll remove unneeded changes.


return writer.writeByte(')');
}

fn renderBigIntConst(
fn renderInt128(
Copy link
Contributor

Choose a reason for hiding this comment

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

I dont understand why this fn is necessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason I did those changes (renderInt128/renderBigIntConst refactoring/renaming) is not obvious anymore, I'll remove unneeded changes.

@jean-dao
Copy link
Contributor Author

jean-dao commented Feb 3, 2022

Some things need clarification. Better split this in 2 PRs, as this also includes 128bit integer support. Tests for the c codegen them are also missing.

I didn't write the 128-bit integer support, this was already here. I realize there are a bunch of unneeded changes in this PR, so I'll clean it up. I did have to change a bit the 128-bit int function to handle smaller values as well.

A few more tests would be great, both for floatop.zig and the C side. floatop.zig has very few test cases, which do not involve negative numbers or the floating point number edge cases.

Agreed, will write more tests.

Thanks for the review!

@jean-dao
Copy link
Contributor Author

jean-dao commented Feb 4, 2022

I updated my branch with the following changes:

  • 128-bit floats are not available on all platform (e.g. not available on freebsd or macos), don't use __float128 type in this case
  • remove some unnecessary changes (functions renaming and such)
  • add more tests
  • fix an unrelated bug I found when writing tests (829ea92)
  • add a comptime error when using renderInt128() on big endian arch. I'm not 100% sure this is actually needed, I will look more into it.

@drew-gpf
Copy link
Contributor

drew-gpf commented Feb 4, 2022

Hi, I wrote the original renderInt128(). The endianness check should not be needed because you're just taking integer values and shifting them; the underlying representation only matters when you put it into memory, although given that there's doubt about it it would be worth it if someone could test this with a big endian arch.

@matu3ba
Copy link
Contributor

matu3ba commented Mar 14, 2022

@jean-dao Can you rebase to resolve the conflicts + rerun CI, which hopefully passes this time?

@jean-dao jean-dao force-pushed the c_backend_floats branch 2 times, most recently from 096ca20 to c796979 Compare March 15, 2022 22:03
@@ -697,3 +697,38 @@ test "f128 at compile time is lossy" {

try expect(@as(f128, 10384593717069655257060992658440192.0) + 1 == 10384593717069655257060992658440192.0);
}

test "f16 literals" {
if (builtin.zig_backend != .stage2_c) return error.SkipZigTest; // TODO
Copy link
Contributor

Choose a reason for hiding this comment

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

Why are this test and the next being skipped for the C backend? I would assume them to be passing after your changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right, only the f128 tests are not passing, I'll update again.

Copy link
Contributor Author

@jean-dao jean-dao Mar 16, 2022

Choose a reason for hiding this comment

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

stage2/bin/zig test test/behavior.zig -I test -target aarch64-linux --test-cmd qemu-aarch64 --test-cmd-bin
test/behavior/floatop.zig:701:1: error: TODO implement const of type 'f16'

Actually it's not passing, although for a different reason than f128. So I think it should still be skipped for backend != c. It's hard to tell for me which backend is actually supported apart from C.

@jean-dao jean-dao force-pushed the c_backend_floats branch 2 times, most recently from 8492f51 to e03aabf Compare March 16, 2022 10:22
if (std.math.isNan(v) or std.math.isInf(v)) {
// just generate a bit cast (exactly like we do in airBitcast)
switch (T) {
f16 => return writer.print("zig_bitcast_f16_u16(0x{x})", .{v}),
Copy link
Contributor

Choose a reason for hiding this comment

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

Is a bitCast of v missing here?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think @topolarity is right, without the @bitCast() this will print the float to hex as is, resulting in something like 0x0x1.ecp6: https://zig.godbolt.org/z/9ehccKWW3

Copy link
Contributor

@matu3ba matu3ba left a comment

Choose a reason for hiding this comment

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

add a few examples to test the relevant cases.

src/link/C/zig.h Outdated
#define ZIG_HAS_FLOAT128
#define float128_t long double
#elif defined(__FreeBSD__) || defined(__APPLE__)
#define float128_t float128_t_is_not_supported_on_this_target
Copy link
Contributor

Choose a reason for hiding this comment

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

Just curious: Why not #error float128_t_is_not_supported_on_this_target ?

Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to @matu3ba's point, please use:

#error "float128_t is not supported on this target"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From my understanding, using #error will make all compilations fail on unsupported targets, even if float128_t is not used. I agree this is less clear though.

Copy link
Contributor

@matu3ba matu3ba Mar 25, 2022

Choose a reason for hiding this comment

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

Can you attach a TODO and make this something along ZIG_UNSUPPORTED_SYMBOL_ON_TARGET_float128_t ?
It would be nice to enable a central example header for the user to invoke such that the user can check these kind of things and also includes stuff like abi checks as macros: marler8997/ziglibc#1

Getting a convention for what stuff is unsupported + make it easily checkable and the checks editable sounds very relevant to me.

src/link/C/zig.h Outdated
#define ZIG_HAS_FLOAT16
#define float16_t _Float16
#else
#define float16_t float16_t_is_not_supported_on_this_target
Copy link
Contributor

Choose a reason for hiding this comment

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

same question: Why not #error float128_t_is_not_supported_on_this_target ?


var buf: f16 = math.f16_min;
try expect(math.f16_min == buf);

Copy link
Contributor

Choose a reason for hiding this comment

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

missing test cases +-inf, -0 with at least 1 example with negative sign.
Not sure, if you also want to check f16_true_min or what the expected behavior for denormals is.

if (builtin.zig_backend != .stage2_c) return error.SkipZigTest; // TODO

const f128_min = @bitCast(f128, @as(u128, 0x00010000000000000000000000000000));
const f128_max = @bitCast(f128, @as(u128, 0x7FFEFFFFFFFFFFFFFFFFFFFFFFFFFFFF));
Copy link
Contributor

Choose a reason for hiding this comment

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

same story: at least 1 example with negative sign.

Copy link
Member

@andrewrk andrewrk left a comment

Choose a reason for hiding this comment

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

Thanks for your patch. @topolarity's review comment looks like a merge blocker to me and I found a few other issues as well-

try expect(@as(f16, 0) == buf);
}

test "f128 tests" {
Copy link
Member

Choose a reason for hiding this comment

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

Can you please be more specific? What exactly is this testing?

src/codegen/c.zig Show resolved Hide resolved
test/behavior/floatop.zig Outdated Show resolved Hide resolved
if (std.math.isNan(v) or std.math.isInf(v)) {
// just generate a bit cast (exactly like we do in airBitcast)
switch (T) {
f16 => return writer.print("zig_bitcast_f16_u16(0x{x})", .{v}),
Copy link
Contributor

Choose a reason for hiding this comment

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

I think @topolarity is right, without the @bitCast() this will print the float to hex as is, resulting in something like 0x0x1.ecp6: https://zig.godbolt.org/z/9ehccKWW3

src/codegen/c.zig Outdated Show resolved Hide resolved
src/link/C/zig.h Outdated
#define ZIG_HAS_FLOAT128
#define float128_t long double
#elif defined(__FreeBSD__) || defined(__APPLE__)
#define float128_t float128_t_is_not_supported_on_this_target
Copy link
Contributor

Choose a reason for hiding this comment

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

+1 to @matu3ba's point, please use:

#error "float128_t is not supported on this target"

@jean-dao jean-dao force-pushed the c_backend_floats branch 3 times, most recently from 98e008b to 34ecec5 Compare April 1, 2022 14:06
@jean-dao
Copy link
Contributor Author

jean-dao commented Apr 1, 2022

The new update changelog:

  • use bitcast to unsigned int type before printing hex value in renderSmallFloat
  • more test values (inf, -inf, 0, -0, -1, nan, min, true_min, max, -max)
  • better test names
  • handle C limited support for f16: C (well at least clang) only supports f16 as a storage type, so it cannot be used for e.g. function return types or parameters. I choose to use f32 (float) instead in such cases.

@jmc-88
Copy link
Contributor

jmc-88 commented Apr 2, 2022

The CI run crashed. ☹️ Can you reproduce the crash locally?

@jean-dao
Copy link
Contributor Author

jean-dao commented Apr 3, 2022

@jmc-88 I cannot reproduce it locally. I thought maybe it could be related to musl libc vs glibc, but I couldn't yet reproduce the ci setup on my local setup. Since there is no output from the zig run subcommand that fail it's hard to know how to debug :/

@topolarity
Copy link
Contributor

I was able to reproduce the failure locally. This is the failing test case from basic.zig:

const builtin = @import("builtin");
const expect = @import("std").testing.expect;

test "take address of parameter" {
    try testTakeAddressOfParameter(12.34);
}
fn testTakeAddressOfParameter(f: f32) !void {
    const f_ptr = &f;
    try expect(f_ptr.* == 12.34);
}

Looks like the problem is that 12.34 is rendered in decimal fp 1.23400001e+01 instead of hex fp 0x1.8ae148p3, causing a rounding error.

@jean-dao jean-dao force-pushed the c_backend_floats branch 3 times, most recently from 2b9091c to 1d7af0c Compare April 3, 2022 16:38
@jean-dao
Copy link
Contributor Author

jean-dao commented Apr 3, 2022

@topolarity thank you for your help!

On another topic, it seems the f128 tests are segfaulting on i386. I'm not quite sure how it passed before, I couldn't reproduce it (= passing test), even on an older master branch. I made the f128 test skipped on i386, not sure if that is acceptable.

@jean-dao jean-dao requested a review from andrewrk April 4, 2022 10:13
@topolarity
Copy link
Contributor

Good news, it doesn't like the segfault is related to these changes! 👍

Bug filed here, with root cause: #11383

comptime assert(int_info.bits > 64 and int_info.bits <= 128);

// TODO support big endian arch
comptime assert(builtin.cpu.arch.endian() == .Little);
Copy link
Member

Choose a reason for hiding this comment

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

This is checking the endianness of the compiler, not the target. Also can you just fix the bug, if any, rather than asserting? It's not that hard to solve.

#define ZIG_HAS_FLOAT128
#define float128_t long double
#elif defined(__FreeBSD__) || defined(__APPLE__)
#define float128_t ZIG_UNSUPPORTED_SYMBOL_ON_TARGET_float128_t
Copy link
Member

Choose a reason for hiding this comment

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

In this case make it a 128 bit integer and emit instructions to perform softfloat on it. All the softfloat functions are available in compiler-rt.

@andrewrk
Copy link
Member

This PR seems to be abandoned. Please open a fresh one if you want to continue the effort.

@andrewrk andrewrk closed this May 11, 2022
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.

6 participants