From 98e008b00f59043a0e3690769a5078a6376afa25 Mon Sep 17 00:00:00 2001
From: Jean Dao <jean@pfudke.fr>
Date: Wed, 2 Feb 2022 18:53:12 +0100
Subject: [PATCH] C backend: better f16 support

---
 src/codegen/c.zig         | 38 +++++++++++++++++++++++++++-----------
 src/link/C/zig.h          | 10 ++++++++++
 test/behavior/floatop.zig | 37 +++++++++++++++++++++++++++++++++++++
 3 files changed, 74 insertions(+), 11 deletions(-)

diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index fc4ff6fe815b..baf0409ff707 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -155,6 +155,7 @@ const reserved_idents = std.ComptimeStringMap(void, .{
     .{ "extern ", {} },
     .{ "float", {} },
     .{ "float128_t", {} },
+    .{ "float16_t", {} },
     .{ "for", {} },
     .{ "fortran", {} },
     .{ "goto", {} },
@@ -556,7 +557,6 @@ pub const DeclGen = struct {
     }
 
     fn renderSmallFloat(
-        dg: *DeclGen,
         comptime T: type,
         writer: anytype,
         val: Value,
@@ -564,14 +564,19 @@ pub const DeclGen = struct {
         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 dg.fail("TODO: C backend: implement 16-bit float lowering", .{}),
-                f32 => return writer.print("zig_bitcast_f32_u32(0x{x})", .{v}),
-                f64 => return writer.print("zig_bitcast_f64_u64(0x{x})", .{v}),
+                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});
+            return writer.print("{}", .{v});
         }
     }
 
@@ -651,9 +656,9 @@ pub const DeclGen = struct {
             },
             .Float => {
                 switch (ty.tag()) {
-                    .f16 => return dg.renderSmallFloat(f16, writer, val),
-                    .f32 => return dg.renderSmallFloat(f32, writer, val),
-                    .f64 => return dg.renderSmallFloat(f64, writer, val),
+                    .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,
                 }
@@ -921,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 {
@@ -1334,7 +1344,7 @@ pub const DeclGen = struct {
             },
             .Float => {
                 switch (t.tag()) {
-                    .f16 => return dg.fail("TODO: C backend: implement float type f16", .{}),
+                    .f16 => try w.writeAll("float16_t"),
                     .f32 => try w.writeAll("float"),
                     .f64 => try w.writeAll("double"),
                     .f128 => try w.writeAll("float128_t"),
@@ -1500,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 ",
diff --git a/src/link/C/zig.h b/src/link/C/zig.h
index 85f2244d539d..040a91acf1e7 100644
--- a/src/link/C/zig.h
+++ b/src/link/C/zig.h
@@ -176,6 +176,16 @@
 #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);
 
diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig
index 58df8a49fe24..5518f1a9386f 100644
--- a/test/behavior/floatop.zig
+++ b/test/behavior/floatop.zig
@@ -697,6 +697,43 @@ 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
+
+    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);
+
+    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