diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 408530594184..f53f267cd67e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -154,6 +154,8 @@ const reserved_idents = std.ComptimeStringMap(void, .{ .{ "enum", {} }, .{ "extern ", {} }, .{ "float", {} }, + .{ "float128_t", {} }, + .{ "float16_t", {} }, .{ "for", {} }, .{ "fortran", {} }, .{ "goto", {} }, @@ -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); + // TODO support big endian arch + comptime assert(builtin.cpu.arch.endian() == .Little); + // 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); @@ -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 ) - 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(')'); } @@ -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, @@ -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"), @@ -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 { @@ -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, } }, @@ -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 ", diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 85c7856d2bde..040a91acf1e7 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -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 +#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); @@ -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; \ } diff --git a/src/value.zig b/src/value.zig index c7960741f6ed..20b2d4d184da 100644 --- a/src/value.zig +++ b/src/value.zig @@ -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; } @@ -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, }; diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index 6d8c33efa248..cb2779ba7f48 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -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); + + 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)); +}