Commit 8bf4b3c611

Carl Åstholm <carl@astholm.se>
2023-11-01 19:12:54
translate-c: translate 80/128-bit long double literals
1 parent 40bd93e
src/clang.zig
@@ -506,6 +506,9 @@ pub const FloatingLiteral = opaque {
     pub const getValueAsApproximateDouble = ZigClangFloatingLiteral_getValueAsApproximateDouble;
     extern fn ZigClangFloatingLiteral_getValueAsApproximateDouble(*const FloatingLiteral) f64;
 
+    pub const getValueAsApproximateQuadBits = ZigClangFloatingLiteral_getValueAsApproximateQuadBits;
+    extern fn ZigClangFloatingLiteral_getValueAsApproximateQuadBits(*const FloatingLiteral, low: *u64, high: *u64) void;
+
     pub const getBeginLoc = ZigClangFloatingLiteral_getBeginLoc;
     extern fn ZigClangFloatingLiteral_getBeginLoc(*const FloatingLiteral) SourceLocation;
 
src/translate_c.zig
@@ -3936,11 +3936,26 @@ fn transCPtrCast(
 }
 
 fn transFloatingLiteral(c: *Context, expr: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node {
+    // TODO use something more accurate than widening to a larger float type and printing that result
     switch (expr.getRawSemantics()) {
         .IEEEhalf, // f16
         .IEEEsingle, // f32
         .IEEEdouble, // f64
-        => {},
+        => {
+            var dbl = expr.getValueAsApproximateDouble();
+            const is_negative = dbl < 0; // -0.0 is considered non-negative
+            if (is_negative) dbl = -dbl;
+            const str = if (dbl == @floor(dbl))
+                try std.fmt.allocPrint(c.arena, "{d}.0", .{dbl})
+            else
+                try std.fmt.allocPrint(c.arena, "{d}", .{dbl});
+            var node = try Tag.float_literal.create(c.arena, str);
+            if (is_negative) node = try Tag.negate.create(c.arena, node);
+            return maybeSuppressResult(c, used, node);
+        },
+        .x87DoubleExtended, // f80
+        .IEEEquad, // f128
+        => return transFloatingLiteralQuad(c, expr, used),
         else => |format| return fail(
             c,
             error.UnsupportedTranslation,
@@ -3949,14 +3964,44 @@ fn transFloatingLiteral(c: *Context, expr: *const clang.FloatingLiteral, used: R
             .{format},
         ),
     }
-    // TODO use something more accurate
-    var dbl = expr.getValueAsApproximateDouble();
-    const is_negative = dbl < 0;
-    if (is_negative) dbl = -dbl;
-    const str = if (dbl == @floor(dbl))
-        try std.fmt.allocPrint(c.arena, "{d}.0", .{dbl})
-    else
-        try std.fmt.allocPrint(c.arena, "{d}", .{dbl});
+}
+
+fn transFloatingLiteralQuad(c: *Context, expr: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node {
+    assert(switch (expr.getRawSemantics()) {
+        .x87DoubleExtended, .IEEEquad => true,
+        else => false,
+    });
+
+    var low: u64 = undefined;
+    var high: u64 = undefined;
+    expr.getValueAsApproximateQuadBits(&low, &high);
+    var quad: f128 = @bitCast(low | @as(u128, high) << 64);
+    const is_negative = quad < 0; // -0.0 is considered non-negative
+    if (is_negative) quad = -quad;
+
+    // TODO implement decimal format for f128 <https://github.com/ziglang/zig/issues/1181>
+    // in the meantime, if the value can be roundtripped by casting it to f64, serializing it to
+    // the decimal format and parsing it back as the exact same f128 value, then use that serialized form
+    const str = fmt_decimal: {
+        var buf: [512]u8 = undefined; // should be large enough to print any f64 in decimal form
+        const dbl: f64 = @floatCast(quad);
+        const temp_str = if (dbl == @floor(dbl))
+            std.fmt.bufPrint(&buf, "{d}.0", .{dbl}) catch |err| switch (err) {
+                error.NoSpaceLeft => unreachable,
+            }
+        else
+            std.fmt.bufPrint(&buf, "{d}", .{dbl}) catch |err| switch (err) {
+                error.NoSpaceLeft => unreachable,
+            };
+        const could_roundtrip = if (std.fmt.parseFloat(f128, temp_str)) |parsed_quad|
+            quad == parsed_quad
+        else |_|
+            false;
+        break :fmt_decimal if (could_roundtrip) try c.arena.dupe(u8, temp_str) else null;
+    }
+    // otherwise, fall back to the hexadecimal format
+    orelse try std.fmt.allocPrint(c.arena, "{x}", .{quad});
+
     var node = try Tag.float_literal.create(c.arena, str);
     if (is_negative) node = try Tag.negate.create(c.arena, node);
     return maybeSuppressResult(c, used, node);
src/zig_clang.cpp
@@ -3245,6 +3245,17 @@ double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatin
     return casted->getValueAsApproximateDouble();
 }
 
+void ZigClangFloatingLiteral_getValueAsApproximateQuadBits(const ZigClangFloatingLiteral *self, uint64_t *low, uint64_t *high) {
+    auto casted = reinterpret_cast<const clang::FloatingLiteral *>(self);
+    llvm::APFloat apf = casted->getValue();
+    bool ignored;
+    apf.convert(llvm::APFloat::IEEEquad(), llvm::APFloat::rmNearestTiesToEven, &ignored);
+    const llvm::APInt api = apf.bitcastToAPInt();
+    const uint64_t *api_data = api.getRawData();
+    *low = api_data[0];
+    *high = api_data[1];
+}
+
 struct ZigClangSourceLocation ZigClangFloatingLiteral_getBeginLoc(const struct ZigClangFloatingLiteral *self) {
     auto casted = reinterpret_cast<const clang::FloatingLiteral *>(self);
     return bitcast(casted->getBeginLoc());
src/zig_clang.h
@@ -1510,6 +1510,7 @@ ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangDeclStmt_getBeginLoc(const st
 ZIG_EXTERN_C unsigned ZigClangAPFloat_convertToHexString(const struct ZigClangAPFloat *self, char *DST,
         unsigned HexDigits, bool UpperCase, enum ZigClangAPFloat_roundingMode RM);
 ZIG_EXTERN_C double ZigClangFloatingLiteral_getValueAsApproximateDouble(const ZigClangFloatingLiteral *self);
+ZIG_EXTERN_C void ZigClangFloatingLiteral_getValueAsApproximateQuadBits(const ZigClangFloatingLiteral *self, uint64_t *low, uint64_t *high);
 ZIG_EXTERN_C struct ZigClangSourceLocation ZigClangFloatingLiteral_getBeginLoc(const struct ZigClangFloatingLiteral *);
 ZIG_EXTERN_C ZigClangAPFloatBase_Semantics ZigClangFloatingLiteral_getRawSemantics(const ZigClangFloatingLiteral *self);
 
test/translate_c.zig
@@ -1240,6 +1240,7 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         \\extern const float my_float = 1.0f;
         \\extern const double my_double = 1.0;
         \\extern const long double my_longdouble = 1.0l;
+        \\extern const long double my_extended_precision_longdouble = 1.0000000000000003l;
     , &([_][]const u8{
         "pub const foo = @as(f32, 3.14);",
         "pub const bar = @as(c_longdouble, 16.0e-2);",
@@ -1250,13 +1251,14 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
         "pub const foobar = -@as(c_longdouble, 73.0);",
         "pub export const my_float: f32 = 1.0;",
         "pub export const my_double: f64 = 1.0;",
-    } ++ if (@bitSizeOf(c_longdouble) != 64) .{
-        // TODO properly translate non-64-bit long doubles
-        "source.h:10:42: warning: unsupported floating point constant format",
-        "source.h:10:26: warning: unable to translate variable initializer, demoted to extern",
-        "pub extern const my_longdouble: c_longdouble;",
-    } else .{
         "pub export const my_longdouble: c_longdouble = 1.0;",
+        switch (@bitSizeOf(c_longdouble)) {
+            // TODO implement decimal format for f128 <https://github.com/ziglang/zig/issues/1181>
+            // (so that f80/f128 values not exactly representable as f64 can be emitted in decimal form)
+            80 => "pub export const my_extended_precision_longdouble: c_longdouble = 0x1.000000000000159ep0;",
+            128 => "pub export const my_extended_precision_longdouble: c_longdouble = 0x1.000000000000159e05f1e2674d21p0;",
+            else => "pub export const my_extended_precision_longdouble: c_longdouble = 1.0000000000000002;",
+        },
     }));
 
     cases.add("macro defines hexadecimal float",