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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 78 additions & 20 deletions src/codegen/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,8 @@ const reserved_idents = std.ComptimeStringMap(void, .{
.{ "enum", {} },
.{ "extern ", {} },
.{ "float", {} },
.{ "float128_t", {} },
.{ "float16_t", {} },
.{ "for", {} },
.{ "fortran", {} },
.{ "goto", {} },
Expand Down Expand Up @@ -420,8 +422,12 @@ pub const DeclGen = struct {
const int_info = @typeInfo(@TypeOf(int_val)).Int;
const is_signed = int_info.signedness == .signed;
const is_neg = int_val < 0;

comptime assert(int_info.bits > 64 and int_info.bits <= 128);
jean-dao marked this conversation as resolved.
Show resolved Hide resolved

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


// Clang and GCC don't support 128-bit integer constants but will hopefully unfold them
// if we construct one manually.
const magnitude = std.math.absCast(int_val);
Expand All @@ -430,13 +436,27 @@ pub const DeclGen = struct {
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.

if (is_signed) try writer.writeAll("(int128_t)");
if (is_neg) try writer.writeByte('-');
if (is_signed) {
try writer.writeAll("(int128_t)");
if (is_neg)
try writer.writeByte('-');
} else {
try writer.writeAll("(uint128_t)");
}

try writer.print("(((uint128_t)0x{x}u<<64)", .{high});
if (magnitude == 0)
return writer.print("0", .{});

try writer.print("(", .{});

if (high > 0)
try writer.print("((uint128_t)0x{x}u << 64)", .{high});

if (high > 0 and low > 0)
try writer.print(" + ", .{});

if (low > 0)
try writer.print("+(uint128_t)0x{x}u", .{low});
try writer.print("(uint128_t)0x{x}u", .{low});

return writer.writeByte(')');
}
Expand Down Expand Up @@ -536,6 +556,39 @@ pub const DeclGen = struct {
}
}

fn renderSmallFloat(
comptime T: type,
writer: anytype,
val: Value,
) error{ OutOfMemory, AnalysisFail }!void {
const v = val.toFloat(T);
if (std.math.isNan(v) or std.math.isInf(v)) {
// just generate a bit cast (exactly like we do in airBitcast)
// Special case: C doesn't support f16 as a return type, so do a 32-bit bit cast,
// and let the C compiler do the conversion from f32 to f16.
switch (T) {
f16 => return writer.print(
"(float16_t)zig_bitcast_f32_u32(0x{x})",
.{@bitCast(u32, val.toFloat(f32))},
),
f32 => return writer.print("zig_bitcast_f32_u32(0x{x})", .{@bitCast(u32, v)}),
f64 => return writer.print("zig_bitcast_f64_u64(0x{x})", .{@bitCast(u64, v)}),
else => unreachable,
}
} else {
return writer.print("{x}", .{v});
}
}

fn renderFloat128(
writer: anytype,
val: f128,
) error{ OutOfMemory, AnalysisFail }!void {
try writer.print("zig_bitcast_f128_u128(", .{});
try renderInt128(writer, @bitCast(u128, val));
try writer.print(")", .{});
}

fn renderValue(
dg: *DeclGen,
writer: anytype,
Expand Down Expand Up @@ -602,19 +655,13 @@ pub const DeclGen = struct {
},
},
.Float => {
if (ty.floatBits(dg.module.getTarget()) <= 64) {
if (std.math.isNan(val.toFloat(f64)) or std.math.isInf(val.toFloat(f64))) {
// just generate a bit cast (exactly like we do in airBitcast)
switch (ty.tag()) {
.f32 => return writer.print("zig_bitcast_f32_u32(0x{x})", .{@bitCast(u32, val.toFloat(f32))}),
.f64 => return writer.print("zig_bitcast_f64_u64(0x{x})", .{@bitCast(u64, val.toFloat(f64))}),
else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}),
}
} else {
return writer.print("{x}", .{val.toFloat(f64)});
}
switch (ty.tag()) {
.f16 => return renderSmallFloat(f16, writer, val),
.f32 => return renderSmallFloat(f32, writer, val),
.f64 => return renderSmallFloat(f64, writer, val),
.f128 => return renderFloat128(writer, val.toFloat(f128)),
else => unreachable,
}
return dg.fail("TODO: C backend: implement lowering large float values", .{});
},
.Pointer => switch (val.tag()) {
.null_value => try writer.writeAll("NULL"),
Expand Down Expand Up @@ -879,7 +926,12 @@ pub const DeclGen = struct {
}
const return_ty = dg.decl.ty.fnReturnType();
if (return_ty.hasRuntimeBits()) {
try dg.renderType(w, return_ty);
// Special case: C does not support f16 as a return type
if (return_ty.tag() == .f16) {
try w.writeAll("float");
} else {
try dg.renderType(w, return_ty);
}
} else if (return_ty.zigTypeTag() == .NoReturn) {
try w.writeAll("zig_noreturn void");
} else {
Expand Down Expand Up @@ -1292,11 +1344,11 @@ pub const DeclGen = struct {
},
.Float => {
switch (t.tag()) {
.f16 => try w.writeAll("float16_t"),
.f32 => try w.writeAll("float"),
.f64 => try w.writeAll("double"),
.f128 => try w.writeAll("float128_t"),
.c_longdouble => try w.writeAll("long double"),
.f16 => return dg.fail("TODO: C backend: implement float type f16", .{}),
.f128 => return dg.fail("TODO: C backend: implement float type f128", .{}),
else => unreachable,
}
},
Expand Down Expand Up @@ -1458,7 +1510,13 @@ pub const DeclGen = struct {

if (alignment != 0)
try w.print("ZIG_ALIGN({}) ", .{alignment});
try dg.renderType(w, render_ty);

// Special case: C does not support f16 as a fn parameter type
if (render_ty.tag() == .f16 and name == .arg) {
try w.writeAll("float");
} else {
try dg.renderType(w, render_ty);
}

const const_prefix = switch (mutability) {
.Const => "const ",
Expand Down
31 changes: 30 additions & 1 deletion src/link/C/zig.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,27 @@

#define int128_t __int128
#define uint128_t unsigned __int128

#if defined(__aarch64__)
#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.

#else
#define ZIG_HAS_FLOAT128
#define float128_t __float128
#endif

#if defined(__clang__)
#define float16_t __fp16
#elif defined(__GNUC__) && (defined(__arm__) || defined(__aarch64__))
#define float16_t __fp16
#elif defined(__GNUC__) && defined(__i386__) && defined(__SSE2__)
#define float16_t _Float16
#else
#define float16_t ZIG_UNSUPPORTED_SYMBOL_ON_TARGET_float16_t
#endif

ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t);
ZIG_EXTERN_C void *memset (void *, int, size_t);

Expand Down Expand Up @@ -402,12 +423,20 @@ static inline float zig_bitcast_f32_u32(uint32_t arg) {
return dest;
}

static inline float zig_bitcast_f64_u64(uint64_t arg) {
static inline double zig_bitcast_f64_u64(uint64_t arg) {
double dest;
memcpy(&dest, &arg, sizeof dest);
return dest;
}

#if defined(ZIG_HAS_FLOAT128)
static inline float128_t zig_bitcast_f128_u128(uint128_t arg) {
float128_t dest;
memcpy(&dest, &arg, sizeof dest);
return dest;
}
#endif

#define zig_add_sat_u(ZT, T) static inline T zig_adds_##ZT(T x, T y, T max) { \
return (x > max - y) ? max : x + y; \
}
Expand Down
4 changes: 2 additions & 2 deletions src/value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2897,7 +2897,7 @@ pub const Value = extern union {
pub fn floatToIntScalar(val: Value, arena: Allocator, int_ty: Type, target: Target) error{ FloatCannotFit, OutOfMemory }!Value {
const Limb = std.math.big.Limb;

var value = val.toFloat(f64); // TODO: f128 ?
var value = val.toFloat(f128);
if (std.math.isNan(value) or std.math.isInf(value)) {
return error.FloatCannotFit;
}
Expand All @@ -2909,7 +2909,7 @@ pub const Value = extern union {

var rational = try std.math.big.Rational.init(arena);
defer rational.deinit();
rational.setFloat(f64, floored) catch |err| switch (err) {
rational.setFloat(f128, floored) catch |err| switch (err) {
error.NonFiniteFloat => unreachable,
error.OutOfMemory => return error.OutOfMemory,
};
Expand Down
90 changes: 90 additions & 0 deletions test/behavior/floatop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,93 @@ test "f128 at compile time is lossy" {

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

test "f16 special values" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO

const f16_inf = @bitCast(f16, @as(u16, 0x7c00));
const f16_nan = @bitCast(f16, @as(u16, 0x7c01));

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

buf = 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.

buf = math.f16_max;
try expect(math.f16_max == buf);

buf = -math.f16_max;
try expect(-math.f16_max == buf);

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

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

buf = -1;
try expect(@as(f16, -1) == buf);

buf = f16_inf;
try expect(f16_inf == buf);

buf = -f16_inf;
try expect(-f16_inf == buf);

buf = f16_nan;
try expect(math.isNan(buf));
}

test "f128 special values" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.cpu.arch == .i386) return error.SkipZigTest; // TODO

const f128_true_min = @bitCast(f128, @as(u128, 0x00000000000000000000000000000001));
const f128_min = @bitCast(f128, @as(u128, 0x00010000000000000000000000000000));
const f128_max = @bitCast(f128, @as(u128, 0x7ffeffffffffffffffffffffffffffff));
const f128_inf = @bitCast(f128, @as(u128, 0x7fff0000000000000000000000000000));
const f128_nan = @bitCast(f128, @as(u128, 0x7fff0000000000000000000000000001));

var buf = f128_true_min;
try expect(f128_true_min == buf);

buf = f128_min;
try expect(f128_min == buf);

buf = f128_max;
try expect(f128_max == buf);

buf = -f128_max;
try expect(-f128_max == buf);

buf = 0;
try expect(@as(f128, 0) == buf);

buf = -0;
try expect(@as(f128, -0) == buf);

buf = -1;
try expect(@as(f128, -1) == buf);

buf = @as(f128, math.f64_max) + 42.0;
try expect(@floatCast(f64, buf - 42.0) == math.f64_max);

buf = @as(f128, math.f64_min) - f128_min;
try expect(@floatCast(f64, buf + f128_min) == math.f64_min);

buf = f128_inf;
try expect(f128_inf == buf);

buf = -f128_inf;
try expect(-f128_inf == buf);

buf = f128_nan;
try expect(math.isNan(buf));
}