From f293fbbeaf6c48e6ce1410743181f89f359eb697 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 10 Feb 2022 15:42:57 -0700 Subject: stage2: LLVM backend: adjust replaceAllUsesWith usage replaceAllUsesWith requires the type to be unchanged. So we bitcast the new global to the old type and use that as the thing to replace old uses. Fixes an LLVM assertion found while troubleshooting #10837. --- src/codegen/llvm.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d85a16d16f..425808efb1 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -661,7 +661,11 @@ pub const DeclGen = struct { new_global.setUnnamedAddr(global.getUnnamedAddress()); new_global.setAlignment(global.getAlignment()); new_global.setInitializer(llvm_init); - global.replaceAllUsesWith(new_global); + // replaceAllUsesWith requires the type to be unchanged. So we bitcast + // the new global to the old type and use that as the thing to replace + // old uses. + const new_global_ptr = new_global.constBitCast(global.typeOf()); + global.replaceAllUsesWith(new_global_ptr); dg.object.decl_map.putAssumeCapacity(decl, new_global); new_global.takeName(global); global.deleteGlobal(); -- cgit v1.2.3 From 166db1a3ed7eca9b04b0626eaea8de0634ab9667 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 10 Feb 2022 17:36:31 -0700 Subject: stage1: fix f80 size and alignment on x86 and arm * F80Repr extern struct needs no explicit padding; let's match the target padding. * stage2: fix lowering of f80 constants. * stage1: decide ABI size and alignment of f80 based on alignment of u64. x86 has alignof u64 equal to 4 but arm has it as 8. * stage2: fix Value.floatReadFromMemory to use F80Repr --- lib/std/math.zig | 7 ++----- src/codegen/llvm.zig | 34 ++++++++++++++++++++++------------ src/stage1/codegen.cpp | 22 +++++++++++++++++----- src/value.zig | 30 ++++++++++++++++++++++++++---- 4 files changed, 67 insertions(+), 26 deletions(-) (limited to 'src/codegen') diff --git a/lib/std/math.zig b/lib/std/math.zig index 6802d420fd..8398842e28 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -36,7 +36,6 @@ pub const sqrt2 = 1.414213562373095048801688724209698079; /// 1/sqrt(2) pub const sqrt1_2 = 0.707106781186547524400844362104849039; -// From a small c++ [program using boost float128](https://github.com/winksaville/cpp_boost_float128) pub const f128_true_min = @bitCast(f128, @as(u128, 0x00000000000000000000000000000001)); pub const f128_min = @bitCast(f128, @as(u128, 0x00010000000000000000000000000000)); pub const f128_max = @bitCast(f128, @as(u128, 0x7FFEFFFFFFFFFFFFFFFFFFFFFFFFFFFF)); @@ -44,12 +43,10 @@ pub const f128_epsilon = @bitCast(f128, @as(u128, 0x3F8F000000000000000000000000 pub const f128_toint = 1.0 / f128_epsilon; pub const F80Repr = if (@import("builtin").cpu.arch.endian() == .Little) extern struct { - fraction: u64, + fraction: u64 align(@alignOf(f80)), exp: u16, - _pad: u32 = undefined, } else extern struct { - exp: u16, - _pad: u32 = undefined, // TODO verify compatibility with hardware + exp: u16 align(@alignOf(f80)), fraction: u64, }; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 425808efb1..54468162ad 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1257,19 +1257,29 @@ pub const DeclGen = struct { }, .Float => { const llvm_ty = try dg.llvmType(tv.ty); - if (tv.ty.floatBits(dg.module.getTarget()) <= 64) { - return llvm_ty.constReal(tv.val.toFloat(f64)); - } - - var buf: [2]u64 = @bitCast([2]u64, tv.val.toFloat(f128)); - // LLVM seems to require that the lower half of the f128 be placed first - // in the buffer. - if (native_endian == .Big) { - std.mem.swap(u64, &buf[0], &buf[1]); + switch (tv.ty.floatBits(dg.module.getTarget())) { + 16, 32, 64 => return llvm_ty.constReal(tv.val.toFloat(f64)), + 80 => { + const float = tv.val.toFloat(f80); + const repr = @ptrCast(*const std.math.F80Repr, &float); + const llvm_i80 = dg.context.intType(80); + var x = llvm_i80.constInt(repr.exp, .False); + x = x.constShl(llvm_i80.constInt(64, .False)); + x = x.constOr(llvm_i80.constInt(repr.fraction, .False)); + return x.constBitCast(llvm_ty); + }, + 128 => { + var buf: [2]u64 = @bitCast([2]u64, tv.val.toFloat(f128)); + // LLVM seems to require that the lower half of the f128 be placed first + // in the buffer. + if (native_endian == .Big) { + std.mem.swap(u64, &buf[0], &buf[1]); + } + const int = dg.context.intType(128).constIntOfArbitraryPrecision(buf.len, &buf); + return int.constBitCast(llvm_ty); + }, + else => unreachable, } - - const int = dg.context.intType(128).constIntOfArbitraryPrecision(buf.len, &buf); - return int.constBitCast(llvm_ty); }, .Pointer => switch (tv.val.tag()) { .decl_ref_mut => return lowerDeclRefValue(dg, tv, tv.val.castTag(.decl_ref_mut).?.data.decl), diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index c06f71e834..7b0bcbe2f5 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -9429,17 +9429,29 @@ static void define_builtin_types(CodeGen *g) { { ZigType *entry = new_type_table_entry(ZigTypeIdFloat); + unsigned u64_alignment = LLVMABIAlignmentOfType(g->target_data_ref, LLVMInt64Type()); + + if (u64_alignment >= 8) { + entry->size_in_bits = 128; + entry->abi_size = 16; + entry->abi_align = 16; + } else if (u64_alignment >= 4) { + entry->size_in_bits = 96; + entry->abi_size = 12; + entry->abi_align = 4; + } else { + entry->size_in_bits = 80; + entry->abi_size = 10; + entry->abi_align = 2; + } if (target_has_f80(g->zig_target)) { entry->llvm_type = LLVMX86FP80Type(); } else { - // We use i128 here instead of x86_fp80 because on targets such as arm, + // We use an int here instead of x86_fp80 because on targets such as arm, // LLVM will give "ERROR: Cannot select" for any instructions involving // the x86_fp80 type. - entry->llvm_type = get_int_type(g, false, 128)->llvm_type; + entry->llvm_type = get_int_type(g, false, entry->size_in_bits)->llvm_type; } - entry->size_in_bits = 8 * 16; - entry->abi_size = 16; // matches LLVMABISizeOfType(LLVMX86FP80Type()) - entry->abi_align = 16; // matches LLVMABIAlignmentOfType(LLVMX86FP80Type()) buf_init_from_str(&entry->name, "f80"); entry->data.floating.bit_count = 80; diff --git a/src/value.zig b/src/value.zig index 33a75e08bb..aefb0a3e20 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1122,10 +1122,32 @@ pub const Value = extern union { fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F { if (F == f80) { - // TODO: use std.math.F80Repr? - const int = std.mem.readInt(u128, buffer[0..16], target.cpu.arch.endian()); - // TODO shouldn't this be a bitcast from u80 to f80 instead of u128 to f80? - return @bitCast(F, int); + switch (target.cpu.arch.endian()) { + .Little => { + const TargetF80Repr = extern struct { + fraction: u64, + exp: u16, + }; + const target_repr = @ptrCast(*align(1) const TargetF80Repr, buffer.ptr); + const real_repr: std.math.F80Repr = .{ + .fraction = target_repr.fraction, + .exp = target_repr.exp, + }; + return @ptrCast(*const f80, &real_repr).*; + }, + .Big => { + const TargetF80Repr = extern struct { + exp: u16, + fraction: u64, + }; + const target_repr = @ptrCast(*align(1) const TargetF80Repr, buffer.ptr); + const real_repr: std.math.F80Repr = .{ + .fraction = target_repr.fraction, + .exp = target_repr.exp, + }; + return @ptrCast(*const f80, &real_repr).*; + }, + } } const Int = @Type(.{ .Int = .{ .signedness = .unsigned, -- cgit v1.2.3 From a024aff9324e827d6595e44f922d87f8ed2dbd0d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 10 Feb 2022 22:06:43 -0700 Subject: make f80 less hacky; lower as u80 on non-x86 Get rid of `std.math.F80Repr`. Instead of trying to match the memory layout of f80, we treat it as a value, same as the other floating point types. The functions `make_f80` and `break_f80` are introduced to compose an f80 value out of its parts, and the inverse operation. stage2 LLVM backend: fix pointer to zero length array tripping LLVM assertion. It now checks for when the element type is a zero-bit type and lowers such thing the same way that pointers to other zero-bit types are lowered. Both stage1 and stage2 LLVM backends are adjusted so that f80 is lowered as x86_fp80 on x86_64 and i386 architectures, and identical to a u80 on others. LLVM constants are lowered in a less hacky way now that #10860 is fixed, by using the expression `(exp << 64) | fraction` using llvm constants. Sema is improved to handle c_longdouble by recursively handling it correctly for whatever the float bit width is. In both stage1 and stage2. --- lib/std/math.zig | 40 ++++++++++------- lib/std/special/compiler_rt/addXf3.zig | 18 ++++---- lib/std/special/compiler_rt/compareXf2.zig | 4 +- lib/std/special/compiler_rt/extend_f80.zig | 6 +-- lib/std/special/compiler_rt/trunc_f80.zig | 6 +-- src/codegen/llvm.zig | 41 +++++++++++------ src/stage1/codegen.cpp | 70 +++++++++++++++--------------- src/type.zig | 55 ++++++++++++++++++++--- src/value.zig | 67 +++++++++++++++++----------- test/behavior/floatop.zig | 5 ++- 10 files changed, 201 insertions(+), 111 deletions(-) (limited to 'src/codegen') diff --git a/lib/std/math.zig b/lib/std/math.zig index 8398842e28..4b8bcf2287 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -42,19 +42,11 @@ pub const f128_max = @bitCast(f128, @as(u128, 0x7FFEFFFFFFFFFFFFFFFFFFFFFFFFFFFF pub const f128_epsilon = @bitCast(f128, @as(u128, 0x3F8F0000000000000000000000000000)); pub const f128_toint = 1.0 / f128_epsilon; -pub const F80Repr = if (@import("builtin").cpu.arch.endian() == .Little) extern struct { - fraction: u64 align(@alignOf(f80)), - exp: u16, -} else extern struct { - exp: u16 align(@alignOf(f80)), - fraction: u64, -}; - // float.h details -pub const f80_true_min = @ptrCast(*const f80, &F80Repr{ .fraction = 1, .exp = 0 }).*; -pub const f80_min = @ptrCast(*const f80, &F80Repr{ .fraction = 0x8000000000000000, .exp = 1 }).*; -pub const f80_max = @ptrCast(*const f80, &F80Repr{ .fraction = 0xFFFFFFFFFFFFFFFF, .exp = 0x7FFE }).*; -pub const f80_epsilon = @ptrCast(*const f80, &F80Repr{ .fraction = 0x8000000000000000, .exp = 0x3FC0 }).*; +pub const f80_true_min = make_f80(.{ .fraction = 1, .exp = 0 }); +pub const f80_min = make_f80(.{ .fraction = 0x8000000000000000, .exp = 1 }); +pub const f80_max = make_f80(.{ .fraction = 0xFFFFFFFFFFFFFFFF, .exp = 0x7FFE }); +pub const f80_epsilon = make_f80(.{ .fraction = 0x8000000000000000, .exp = 0x3FC0 }); pub const f80_toint = 1.0 / f80_epsilon; pub const f64_true_min = 4.94065645841246544177e-324; @@ -104,9 +96,9 @@ pub const qnan_f64 = @bitCast(f64, qnan_u64); pub const inf_u64 = @as(u64, 0x7FF << 52); pub const inf_f64 = @bitCast(f64, inf_u64); -pub const inf_f80 = @ptrCast(*const f80, &F80Repr{ .fraction = 0x8000000000000000, .exp = 0x7fff }).*; -pub const nan_f80 = @ptrCast(*const f80, &F80Repr{ .fraction = 0xA000000000000000, .exp = 0x7fff }).*; -pub const qnan_f80 = @ptrCast(*const f80, &F80Repr{ .fraction = 0xC000000000000000, .exp = 0x7fff }).*; +pub const inf_f80 = make_f80(F80{ .fraction = 0x8000000000000000, .exp = 0x7fff }); +pub const nan_f80 = make_f80(F80{ .fraction = 0xA000000000000000, .exp = 0x7fff }); +pub const qnan_f80 = make_f80(F80{ .fraction = 0xC000000000000000, .exp = 0x7fff }); pub const nan_u128 = @as(u128, 0x7fff0000000000000000000000000001); pub const nan_f128 = @bitCast(f128, nan_u128); @@ -1501,3 +1493,21 @@ test "boolMask" { pub fn comptimeMod(num: anytype, denom: comptime_int) IntFittingRange(0, denom - 1) { return @intCast(IntFittingRange(0, denom - 1), @mod(num, denom)); } + +pub const F80 = struct { + fraction: u64, + exp: u16, +}; + +pub fn make_f80(repr: F80) f80 { + const int = (@as(u80, repr.exp) << 64) | repr.fraction; + return @bitCast(f80, int); +} + +pub fn break_f80(x: f80) F80 { + const int = @bitCast(u80, x); + return .{ + .fraction = @truncate(u64, int), + .exp = @truncate(u16, int >> 64), + }; +} diff --git a/lib/std/special/compiler_rt/addXf3.zig b/lib/std/special/compiler_rt/addXf3.zig index 1339cc340d..13758afce7 100644 --- a/lib/std/special/compiler_rt/addXf3.zig +++ b/lib/std/special/compiler_rt/addXf3.zig @@ -232,8 +232,8 @@ fn normalize_f80(exp: *i32, significand: *u80) void { } pub fn __addxf3(a: f80, b: f80) callconv(.C) f80 { - var a_rep align(16) = @ptrCast(*const std.math.F80Repr, &a).*; - var b_rep align(16) = @ptrCast(*const std.math.F80Repr, &b).*; + var a_rep = std.math.break_f80(a); + var b_rep = std.math.break_f80(b); var a_exp: i32 = a_rep.exp & 0x7FFF; var b_exp: i32 = b_rep.exp & 0x7FFF; @@ -257,7 +257,7 @@ pub fn __addxf3(a: f80, b: f80) callconv(.C) f80 { std.debug.assert(a_rep.fraction & significand_mask != 0); // NaN + anything = qNaN a_rep.fraction |= qnan_bit; - return @ptrCast(*const f80, &a_rep).*; + return std.math.make_f80(a_rep); } } if (b_exp == max_exp) { @@ -268,7 +268,7 @@ pub fn __addxf3(a: f80, b: f80) callconv(.C) f80 { std.debug.assert(b_rep.fraction & significand_mask != 0); // anything + NaN = qNaN b_rep.fraction |= qnan_bit; - return @ptrCast(*const f80, &b_rep).*; + return std.math.make_f80(b_rep); } } @@ -279,7 +279,7 @@ pub fn __addxf3(a: f80, b: f80) callconv(.C) f80 { if (b_zero) { // but we need to get the sign right for zero + zero a_rep.exp &= b_rep.exp; - return @ptrCast(*const f80, &a_rep).*; + return std.math.make_f80(a_rep); } else { return b; } @@ -359,7 +359,7 @@ pub fn __addxf3(a: f80, b: f80) callconv(.C) f80 { if (a_exp >= max_exp) { a_rep.exp = max_exp | result_sign; a_rep.fraction = int_bit; // integer bit is set for +/-inf - return @ptrCast(*const f80, &a_rep).*; + return std.math.make_f80(a_rep); } if (a_exp <= 0) { @@ -387,13 +387,13 @@ pub fn __addxf3(a: f80, b: f80) callconv(.C) f80 { a_rep.fraction = @truncate(u64, a_int); a_rep.exp = @truncate(u16, a_int >> significand_bits); - return @ptrCast(*const f80, &a_rep).*; + return std.math.make_f80(a_rep); } pub fn __subxf3(a: f80, b: f80) callconv(.C) f80 { - var b_rep align(16) = @ptrCast(*const std.math.F80Repr, &b).*; + var b_rep = std.math.break_f80(b); b_rep.exp ^= 0x8000; - return __addxf3(a, @ptrCast(*const f80, &b_rep).*); + return __addxf3(a, std.math.make_f80(b_rep)); } test { diff --git a/lib/std/special/compiler_rt/compareXf2.zig b/lib/std/special/compiler_rt/compareXf2.zig index 36f6f5f1c1..9640298f8f 100644 --- a/lib/std/special/compiler_rt/compareXf2.zig +++ b/lib/std/special/compiler_rt/compareXf2.zig @@ -147,8 +147,8 @@ pub fn __gtdf2(a: f64, b: f64) callconv(.C) i32 { // Comparison between f80 pub inline fn cmp_f80(comptime RT: type, a: f80, b: f80) RT { - const a_rep = @ptrCast(*const std.math.F80Repr, &a).*; - const b_rep = @ptrCast(*const std.math.F80Repr, &b).*; + const a_rep = std.math.break_f80(a); + const b_rep = std.math.break_f80(b); const sig_bits = std.math.floatMantissaBits(f80); const int_bit = 0x8000000000000000; const sign_bit = 0x8000; diff --git a/lib/std/special/compiler_rt/extend_f80.zig b/lib/std/special/compiler_rt/extend_f80.zig index 29ba8560ce..4686421db0 100644 --- a/lib/std/special/compiler_rt/extend_f80.zig +++ b/lib/std/special/compiler_rt/extend_f80.zig @@ -41,7 +41,7 @@ inline fn extendF80(comptime src_t: type, a: std.meta.Int(.unsigned, @typeInfo(s const src_qnan = 1 << (src_sig_bits - 1); const src_nan_code = src_qnan - 1; - var dst: std.math.F80Repr align(16) = undefined; + var dst: std.math.F80 = undefined; // Break a into a sign and representation of the absolute value const a_abs = a & src_abs_mask; @@ -83,7 +83,7 @@ inline fn extendF80(comptime src_t: type, a: std.meta.Int(.unsigned, @typeInfo(s } dst.exp |= sign; - return @ptrCast(*const f80, &dst).*; + return std.math.make_f80(dst); } pub fn __extendxftf2(a: f80) callconv(.C) f128 { @@ -99,7 +99,7 @@ pub fn __extendxftf2(a: f80) callconv(.C) f128 { const dst_min_normal = @as(u128, 1) << dst_sig_bits; // Break a into a sign and representation of the absolute value - var a_rep = @ptrCast(*const std.math.F80Repr, &a).*; + var a_rep = std.math.break_f80(a); const sign = a_rep.exp & 0x8000; a_rep.exp &= 0x7FFF; var abs_result: u128 = undefined; diff --git a/lib/std/special/compiler_rt/trunc_f80.zig b/lib/std/special/compiler_rt/trunc_f80.zig index 567d03be63..19e8d44b86 100644 --- a/lib/std/special/compiler_rt/trunc_f80.zig +++ b/lib/std/special/compiler_rt/trunc_f80.zig @@ -42,7 +42,7 @@ inline fn trunc(comptime dst_t: type, a: f80) dst_t { const dst_nan_mask = dst_qnan - 1; // Break a into a sign and representation of the absolute value - var a_rep = @ptrCast(*const std.math.F80Repr, &a).*; + var a_rep = std.math.break_f80(a); const sign = a_rep.exp & 0x8000; a_rep.exp &= 0x7FFF; a_rep.fraction &= 0x7FFFFFFFFFFFFFFF; @@ -125,7 +125,7 @@ pub fn __trunctfxf2(a: f128) callconv(.C) f80 { const a_abs = a_rep & src_abs_mask; const sign: u16 = if (a_rep & src_sign_mask != 0) 0x8000 else 0; - var res: std.math.F80Repr align(16) = undefined; + var res: std.math.F80 = undefined; if (a_abs > src_inf) { // a is NaN. @@ -155,5 +155,5 @@ pub fn __trunctfxf2(a: f128) callconv(.C) f80 { } res.exp |= sign; - return @ptrCast(*const f80, &res).*; + return std.math.make_f80(res); } diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 54468162ad..b2adb898d3 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -824,23 +824,24 @@ pub const DeclGen = struct { fn llvmType(dg: *DeclGen, t: Type) Allocator.Error!*const llvm.Type { const gpa = dg.gpa; + const target = dg.module.getTarget(); switch (t.zigTypeTag()) { .Void, .NoReturn => return dg.context.voidType(), .Int => { - const info = t.intInfo(dg.module.getTarget()); + const info = t.intInfo(target); return dg.context.intType(info.bits); }, .Enum => { var buffer: Type.Payload.Bits = undefined; const int_ty = t.intTagType(&buffer); - const bit_count = int_ty.intInfo(dg.module.getTarget()).bits; + const bit_count = int_ty.intInfo(target).bits; return dg.context.intType(bit_count); }, - .Float => switch (t.floatBits(dg.module.getTarget())) { + .Float => switch (t.floatBits(target)) { 16 => return dg.context.halfType(), 32 => return dg.context.floatType(), 64 => return dg.context.doubleType(), - 80 => return dg.context.x86FP80Type(), + 80 => return if (backendSupportsF80(target)) dg.context.x86FP80Type() else dg.context.intType(80), 128 => return dg.context.fp128Type(), else => unreachable, }, @@ -859,7 +860,8 @@ pub const DeclGen = struct { const llvm_addrspace = dg.llvmAddressSpace(t.ptrAddressSpace()); const elem_ty = t.childType(); const lower_elem_ty = switch (elem_ty.zigTypeTag()) { - .Opaque, .Array, .Fn => true, + .Opaque, .Fn => true, + .Array => elem_ty.childType().hasRuntimeBits(), else => elem_ty.hasRuntimeBits(), }; const llvm_elem_ty = if (lower_elem_ty) @@ -889,9 +891,11 @@ pub const DeclGen = struct { else => unreachable, }, .Array => { - const elem_type = try dg.llvmType(t.childType()); + const elem_ty = t.childType(); + assert(elem_ty.onePossibleValue() == null); + const elem_llvm_ty = try dg.llvmType(elem_ty); const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null); - return elem_type.arrayType(@intCast(c_uint, total_len)); + return elem_llvm_ty.arrayType(@intCast(c_uint, total_len)); }, .Vector => { const elem_type = try dg.llvmType(t.childType()); @@ -978,7 +982,6 @@ pub const DeclGen = struct { if (struct_obj.layout == .Packed) { try llvm_field_types.ensureUnusedCapacity(gpa, struct_obj.fields.count() * 2); - const target = dg.module.getTarget(); comptime assert(Type.packed_struct_layout_version == 1); var offset: u64 = 0; var big_align: u32 = 0; @@ -1073,7 +1076,6 @@ pub const DeclGen = struct { gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator()); const union_obj = t.cast(Type.Payload.Union).?.data; - const target = dg.module.getTarget(); if (t.unionTagType()) |enum_tag_ty| { const enum_tag_llvm_ty = try dg.llvmType(enum_tag_ty); const layout = union_obj.getLayout(target, true); @@ -1141,7 +1143,6 @@ pub const DeclGen = struct { }, .Fn => { const fn_info = t.fnInfo(); - const target = dg.module.getTarget(); const sret = firstParamSRet(fn_info, target); const return_type = fn_info.return_type; const raw_llvm_ret_ty = try dg.llvmType(return_type); @@ -1257,16 +1258,21 @@ pub const DeclGen = struct { }, .Float => { const llvm_ty = try dg.llvmType(tv.ty); - switch (tv.ty.floatBits(dg.module.getTarget())) { + const target = dg.module.getTarget(); + switch (tv.ty.floatBits(target)) { 16, 32, 64 => return llvm_ty.constReal(tv.val.toFloat(f64)), 80 => { const float = tv.val.toFloat(f80); - const repr = @ptrCast(*const std.math.F80Repr, &float); + const repr = std.math.break_f80(float); const llvm_i80 = dg.context.intType(80); var x = llvm_i80.constInt(repr.exp, .False); x = x.constShl(llvm_i80.constInt(64, .False)); x = x.constOr(llvm_i80.constInt(repr.fraction, .False)); - return x.constBitCast(llvm_ty); + if (backendSupportsF80(target)) { + return x.constBitCast(llvm_ty); + } else { + return x; + } }, 128 => { var buf: [2]u64 = @bitCast([2]u64, tv.val.toFloat(f128)); @@ -5353,3 +5359,12 @@ fn isByRef(ty: Type) bool { }, } } + +/// This function returns true if we expect LLVM to lower x86_fp80 correctly +/// and false if we expect LLVM to crash if it counters an x86_fp80 type. +fn backendSupportsF80(target: std.Target) bool { + return switch (target.cpu.arch) { + .x86_64, .i386 => true, + else => false, + }; +} diff --git a/src/stage1/codegen.cpp b/src/stage1/codegen.cpp index 7b0bcbe2f5..f1a94b9bb9 100644 --- a/src/stage1/codegen.cpp +++ b/src/stage1/codegen.cpp @@ -8195,17 +8195,15 @@ static LLVMValueRef gen_const_val(CodeGen *g, ZigValue *const_val, const char *n case 64: return LLVMConstReal(get_llvm_type(g, type_entry), const_val->data.x_f64); case 80: { - uint64_t buf[2]; - memcpy(&buf, &const_val->data.x_f80, 16); -#if ZIG_BYTE_ORDER == ZIG_BIG_ENDIAN - uint64_t tmp = buf[0]; - buf[0] = buf[1]; - buf[1] = tmp; -#endif - LLVMValueRef as_i128 = LLVMConstIntOfArbitraryPrecision(LLVMInt128Type(), 2, buf); - if (!target_has_f80(g->zig_target)) return as_i128; - LLVMValueRef as_int = LLVMConstTrunc(as_i128, LLVMIntType(80)); - return LLVMConstBitCast(as_int, get_llvm_type(g, type_entry)); + LLVMTypeRef llvm_i80 = LLVMIntType(80); + LLVMValueRef x = LLVMConstInt(llvm_i80, const_val->data.x_f80.signExp, false); + x = LLVMConstShl(x, LLVMConstInt(llvm_i80, 64, false)); + x = LLVMConstOr(x, LLVMConstInt(llvm_i80, const_val->data.x_f80.signif, false)); + if (target_has_f80(g->zig_target)) { + return LLVMConstBitCast(x, LLVMX86FP80Type()); + } else { + return x; + } } case 128: { @@ -9429,32 +9427,36 @@ static void define_builtin_types(CodeGen *g) { { ZigType *entry = new_type_table_entry(ZigTypeIdFloat); - unsigned u64_alignment = LLVMABIAlignmentOfType(g->target_data_ref, LLVMInt64Type()); - - if (u64_alignment >= 8) { - entry->size_in_bits = 128; - entry->abi_size = 16; - entry->abi_align = 16; - } else if (u64_alignment >= 4) { - entry->size_in_bits = 96; - entry->abi_size = 12; - entry->abi_align = 4; - } else { - entry->size_in_bits = 80; - entry->abi_size = 10; - entry->abi_align = 2; - } - if (target_has_f80(g->zig_target)) { - entry->llvm_type = LLVMX86FP80Type(); - } else { - // We use an int here instead of x86_fp80 because on targets such as arm, - // LLVM will give "ERROR: Cannot select" for any instructions involving - // the x86_fp80 type. - entry->llvm_type = get_int_type(g, false, entry->size_in_bits)->llvm_type; - } + entry->size_in_bits = 80; + buf_init_from_str(&entry->name, "f80"); entry->data.floating.bit_count = 80; + switch (g->zig_target->arch) { + case ZigLLVM_x86_64: + entry->llvm_type = LLVMX86FP80Type(); + entry->abi_size = 16; + entry->abi_align = 16; + break; + case ZigLLVM_x86: + entry->llvm_type = LLVMX86FP80Type(); + entry->abi_size = 12; + entry->abi_align = 4; + break; + default: { + // We use an int here instead of x86_fp80 because on targets such as arm, + // LLVM will give "ERROR: Cannot select" for any instructions involving + // the x86_fp80 type. + ZigType *u80_ty = get_int_type(g, false, 80); + assert(!target_has_f80(g->zig_target)); + assert(u80_ty->size_in_bits == entry->size_in_bits); + entry->llvm_type = get_llvm_type(g, u80_ty); + entry->abi_size = u80_ty->abi_size; + entry->abi_align = u80_ty->abi_align; + break; + } + } + entry->llvm_di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name), entry->size_in_bits, ZigLLVMEncoding_DW_ATE_unsigned()); diff --git a/src/type.zig b/src/type.zig index 0827b2e2d7..27fdb0abc8 100644 --- a/src/type.zig +++ b/src/type.zig @@ -1877,9 +1877,28 @@ pub const Type = extern union { .f16 => return 2, .f32 => return 4, .f64 => return 8, - .f80 => return 16, .f128 => return 16, - .c_longdouble => return 16, + + .f80 => switch (target.cpu.arch) { + .i386 => return 4, + .x86_64 => return 16, + else => { + var payload: Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = 80, + }; + const u80_ty = initPayload(&payload.base); + return abiAlignment(u80_ty, target); + }, + }, + .c_longdouble => switch (CType.longdouble.sizeInBits(target)) { + 16 => return abiAlignment(Type.f16, target), + 32 => return abiAlignment(Type.f32, target), + 64 => return abiAlignment(Type.f64, target), + 80 => return abiAlignment(Type.f80, target), + 128 => return abiAlignment(Type.f128, target), + else => unreachable, + }, .error_set, .error_set_single, @@ -2158,9 +2177,28 @@ pub const Type = extern union { .f16 => return 2, .f32 => return 4, .f64 => return 8, - .f80 => return 16, .f128 => return 16, - .c_longdouble => return 16, + + .f80 => switch (target.cpu.arch) { + .i386 => return 12, + .x86_64 => return 16, + else => { + var payload: Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = 80, + }; + const u80_ty = initPayload(&payload.base); + return abiSize(u80_ty, target); + }, + }, + .c_longdouble => switch (CType.longdouble.sizeInBits(target)) { + 16 => return abiSize(Type.f16, target), + 32 => return abiSize(Type.f32, target), + 64 => return abiSize(Type.f64, target), + 80 => return abiSize(Type.f80, target), + 128 => return abiSize(Type.f128, target), + else => unreachable, + }, .error_set, .error_set_single, @@ -2349,7 +2387,7 @@ pub const Type = extern union { .c_ulong => return CType.ulong.sizeInBits(target), .c_longlong => return CType.longlong.sizeInBits(target), .c_ulonglong => return CType.ulonglong.sizeInBits(target), - .c_longdouble => 128, + .c_longdouble => return CType.longdouble.sizeInBits(target), .error_set, .error_set_single, @@ -4772,6 +4810,13 @@ pub const Type = extern union { pub const @"u8" = initTag(.u8); pub const @"u32" = initTag(.u32); pub const @"u64" = initTag(.u64); + + pub const @"f16" = initTag(.f16); + pub const @"f32" = initTag(.f32); + pub const @"f64" = initTag(.f64); + pub const @"f80" = initTag(.f80); + pub const @"f128" = initTag(.f128); + pub const @"bool" = initTag(.bool); pub const @"usize" = initTag(.usize); pub const @"isize" = initTag(.isize); diff --git a/src/value.zig b/src/value.zig index aefb0a3e20..3479819160 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1112,6 +1112,19 @@ pub const Value = extern union { } fn floatWriteToMemory(comptime F: type, f: F, target: Target, buffer: []u8) void { + if (F == f80) { + switch (target.cpu.arch) { + .i386, .x86_64 => { + const repr = std.math.break_f80(f); + std.mem.writeIntLittle(u64, buffer[0..8], repr.fraction); + std.mem.writeIntLittle(u16, buffer[8..10], repr.exp); + // TODO set the rest of the bytes to undefined. should we use 0xaa + // or is there a different way? + return; + }, + else => {}, + } + } const Int = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @typeInfo(F).Float.bits, @@ -1122,41 +1135,43 @@ pub const Value = extern union { fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F { if (F == f80) { - switch (target.cpu.arch.endian()) { - .Little => { - const TargetF80Repr = extern struct { - fraction: u64, - exp: u16, - }; - const target_repr = @ptrCast(*align(1) const TargetF80Repr, buffer.ptr); - const real_repr: std.math.F80Repr = .{ - .fraction = target_repr.fraction, - .exp = target_repr.exp, - }; - return @ptrCast(*const f80, &real_repr).*; - }, - .Big => { - const TargetF80Repr = extern struct { - exp: u16, - fraction: u64, - }; - const target_repr = @ptrCast(*align(1) const TargetF80Repr, buffer.ptr); - const real_repr: std.math.F80Repr = .{ - .fraction = target_repr.fraction, - .exp = target_repr.exp, - }; - return @ptrCast(*const f80, &real_repr).*; - }, + switch (target.cpu.arch) { + .i386, .x86_64 => return std.math.make_f80(.{ + .fraction = std.mem.readIntLittle(u64, buffer[0..8]), + .exp = std.mem.readIntLittle(u16, buffer[8..10]), + }), + else => {}, } } const Int = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @typeInfo(F).Float.bits, } }); - const int = std.mem.readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian()); + const int = readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian()); return @bitCast(F, int); } + fn readInt(comptime Int: type, buffer: *const [@sizeOf(Int)]u8, endian: std.builtin.Endian) Int { + var result: Int = 0; + switch (endian) { + .Big => { + for (buffer) |byte| { + result <<= 8; + result |= byte; + } + }, + .Little => { + var i: usize = buffer.len; + while (i != 0) { + i -= 1; + result <<= 8; + result |= buffer[i]; + } + }, + } + return result; + } + /// Asserts that the value is a float or an integer. pub fn toFloat(val: Value, comptime T: type) T { return switch (val.tag()) { diff --git a/test/behavior/floatop.zig b/test/behavior/floatop.zig index ed632c26c5..00f4cff6e5 100644 --- a/test/behavior/floatop.zig +++ b/test/behavior/floatop.zig @@ -5,7 +5,10 @@ const math = std.math; const pi = std.math.pi; const e = std.math.e; const Vector = std.meta.Vector; -const has_f80_rt = @import("builtin").cpu.arch == .x86_64; +const has_f80_rt = switch (builtin.cpu.arch) { + .x86_64, .i386 => true, + else => false, +}; const epsilon_16 = 0.001; const epsilon = 0.000001; -- cgit v1.2.3 From 335c680cde670776280c3a812adb64abd1311d97 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 10 Feb 2022 22:28:53 -0700 Subject: LLVM backend: fix union with only 1 tag tripping llvm assertion --- src/codegen/llvm.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b2adb898d3..ec0ead94e4 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -835,6 +835,7 @@ pub const DeclGen = struct { var buffer: Type.Payload.Bits = undefined; const int_ty = t.intTagType(&buffer); const bit_count = int_ty.intInfo(target).bits; + assert(bit_count != 0); return dg.context.intType(bit_count); }, .Float => switch (t.floatBits(target)) { @@ -1077,10 +1078,10 @@ pub const DeclGen = struct { const union_obj = t.cast(Type.Payload.Union).?.data; if (t.unionTagType()) |enum_tag_ty| { - const enum_tag_llvm_ty = try dg.llvmType(enum_tag_ty); const layout = union_obj.getLayout(target, true); if (layout.payload_size == 0) { + const enum_tag_llvm_ty = try dg.llvmType(enum_tag_ty); gop.value_ptr.* = enum_tag_llvm_ty; return enum_tag_llvm_ty; } @@ -1111,6 +1112,7 @@ pub const DeclGen = struct { llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False); return llvm_union_ty; } + const enum_tag_llvm_ty = try dg.llvmType(enum_tag_ty); // Put the tag before or after the payload depending on which one's // alignment is greater. -- cgit v1.2.3 From d72f832b1ebab0db106e64bc9f59eba90c414311 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 10 Feb 2022 23:03:13 -0700 Subject: LLVM backend: call constPtrToInt instead of constBitCast when appropriate. Avoids tripping an LLVM assertion. --- src/codegen/llvm.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index ec0ead94e4..85cb808996 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1858,7 +1858,11 @@ pub const DeclGen = struct { try self.resolveGlobalDecl(decl); const llvm_type = try self.llvmType(tv.ty); - return llvm_val.constBitCast(llvm_type); + if (tv.ty.zigTypeTag() == .Int) { + return llvm_val.constPtrToInt(llvm_type); + } else { + return llvm_val.constBitCast(llvm_type); + } } fn lowerPtrToVoid(dg: *DeclGen, ptr_ty: Type) !*const llvm.Value { -- cgit v1.2.3 From 38236533f1d031d31c6d10cbe7d0f8c59a9e3520 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 11 Feb 2022 00:03:53 -0700 Subject: LLVM backend: avoid creating invalid LLVM types Fixes assertions from creating i0 types which are not allowed in LLVM. --- src/codegen/llvm.zig | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 85cb808996..3c1f0c9737 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -687,9 +687,6 @@ pub const DeclGen = struct { const target = dg.module.getTarget(); const sret = firstParamSRet(fn_info, target); - const return_type = fn_info.return_type; - const raw_llvm_ret_ty = try dg.llvmType(return_type); - const fn_type = try dg.llvmType(zig_fn_type); const fqn = try decl.getFullyQualifiedName(dg.gpa); @@ -708,6 +705,8 @@ pub const DeclGen = struct { if (sret) { dg.addArgAttr(llvm_fn, 0, "nonnull"); // Sret pointers must not be address 0 dg.addArgAttr(llvm_fn, 0, "noalias"); + + const raw_llvm_ret_ty = try dg.llvmType(fn_info.return_type); llvm_fn.addSretAttr(0, raw_llvm_ret_ty); } @@ -737,7 +736,7 @@ pub const DeclGen = struct { // Function attributes that are independent of analysis results of the function body. dg.addCommonFnAttributes(llvm_fn); - if (return_type.isNoReturn()) { + if (fn_info.return_type.isNoReturn()) { dg.addFnAttr(llvm_fn, "noreturn"); } @@ -829,6 +828,7 @@ pub const DeclGen = struct { .Void, .NoReturn => return dg.context.voidType(), .Int => { const info = t.intInfo(target); + assert(info.bits != 0); return dg.context.intType(info.bits); }, .Enum => { @@ -1147,17 +1147,17 @@ pub const DeclGen = struct { const fn_info = t.fnInfo(); const sret = firstParamSRet(fn_info, target); const return_type = fn_info.return_type; - const raw_llvm_ret_ty = try dg.llvmType(return_type); - const llvm_ret_ty = if (!return_type.hasRuntimeBits() or sret) - dg.context.voidType() + const llvm_sret_ty = if (return_type.hasRuntimeBits()) + try dg.llvmType(return_type) else - raw_llvm_ret_ty; + dg.context.voidType(); + const llvm_ret_ty = if (sret) dg.context.voidType() else llvm_sret_ty; var llvm_params = std.ArrayList(*const llvm.Type).init(dg.gpa); defer llvm_params.deinit(); if (sret) { - try llvm_params.append(raw_llvm_ret_ty.pointerType(0)); + try llvm_params.append(llvm_sret_ty.pointerType(0)); } for (fn_info.param_types) |param_ty| { @@ -1210,6 +1210,7 @@ pub const DeclGen = struct { const bigint = tv.val.toBigInt(&bigint_space); const target = dg.module.getTarget(); const int_info = tv.ty.intInfo(target); + assert(int_info.bits != 0); const llvm_type = dg.context.intType(int_info.bits); const unsigned_val = v: { @@ -2241,7 +2242,6 @@ pub const FuncGen = struct { }; const fn_info = zig_fn_ty.fnInfo(); const return_type = fn_info.return_type; - const llvm_ret_ty = try self.dg.llvmType(return_type); const llvm_fn = try self.resolveInst(pl_op.operand); const target = self.dg.module.getTarget(); const sret = firstParamSRet(fn_info, target); @@ -2250,6 +2250,7 @@ pub const FuncGen = struct { defer llvm_args.deinit(); const ret_ptr = if (!sret) null else blk: { + const llvm_ret_ty = try self.dg.llvmType(return_type); const ret_ptr = self.buildAlloca(llvm_ret_ty); ret_ptr.setAlignment(return_type.abiAlignment(target)); try llvm_args.append(ret_ptr); @@ -2284,6 +2285,7 @@ pub const FuncGen = struct { } else if (self.liveness.isUnused(inst) or !return_type.hasRuntimeBits()) { return null; } else if (sret) { + const llvm_ret_ty = try self.dg.llvmType(return_type); call.setCallSret(llvm_ret_ty); return ret_ptr; } else { -- cgit v1.2.3 From 91508e10abe4ab82e4f7a4dcdfac178ebd9e52d3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 11 Feb 2022 00:33:51 -0700 Subject: LLVM backend: handle unnamed structs when lowering array values LLVM doesn't support lowering union values, so we have to use unnamed structs to do it, which means any type that contains a union as an element, even if it is nested in another type, has to have a mechanism to detect when it can't be lowered normally and has to resort itself to an unnamed struct. This includes arrays. --- src/codegen/llvm.zig | 66 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 18 deletions(-) (limited to 'src/codegen') diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 3c1f0c9737..3b9bac09ec 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1371,14 +1371,24 @@ pub const DeclGen = struct { const gpa = dg.gpa; const llvm_elems = try gpa.alloc(*const llvm.Value, elem_vals.len); defer gpa.free(llvm_elems); + var need_unnamed = false; for (elem_vals) |elem_val, i| { llvm_elems[i] = try dg.genTypedValue(.{ .ty = elem_ty, .val = elem_val }); + need_unnamed = need_unnamed or dg.isUnnamedType(elem_ty, llvm_elems[i]); + } + if (need_unnamed) { + return dg.context.constStruct( + llvm_elems.ptr, + @intCast(c_uint, llvm_elems.len), + .True, + ); + } else { + const llvm_elem_ty = try dg.llvmType(elem_ty); + return llvm_elem_ty.constArray( + llvm_elems.ptr, + @intCast(c_uint, llvm_elems.len), + ); } - const llvm_elem_ty = try dg.llvmType(elem_ty); - return llvm_elem_ty.constArray( - llvm_elems.ptr, - @intCast(c_uint, llvm_elems.len), - ); }, .repeated => { const val = tv.val.castTag(.repeated).?.data; @@ -1389,25 +1399,46 @@ pub const DeclGen = struct { const gpa = dg.gpa; const llvm_elems = try gpa.alloc(*const llvm.Value, len_including_sent); defer gpa.free(llvm_elems); - for (llvm_elems[0..len]) |*elem| { - elem.* = try dg.genTypedValue(.{ .ty = elem_ty, .val = val }); + + var need_unnamed = false; + if (len != 0) { + for (llvm_elems[0..len]) |*elem| { + elem.* = try dg.genTypedValue(.{ .ty = elem_ty, .val = val }); + } + need_unnamed = need_unnamed or dg.isUnnamedType(elem_ty, llvm_elems[0]); } + if (sentinel) |sent| { llvm_elems[len] = try dg.genTypedValue(.{ .ty = elem_ty, .val = sent }); + need_unnamed = need_unnamed or dg.isUnnamedType(elem_ty, llvm_elems[len]); + } + + if (need_unnamed) { + return dg.context.constStruct( + llvm_elems.ptr, + @intCast(c_uint, llvm_elems.len), + .True, + ); + } else { + const llvm_elem_ty = try dg.llvmType(elem_ty); + return llvm_elem_ty.constArray( + llvm_elems.ptr, + @intCast(c_uint, llvm_elems.len), + ); } - const llvm_elem_ty = try dg.llvmType(elem_ty); - return llvm_elem_ty.constArray( - llvm_elems.ptr, - @intCast(c_uint, llvm_elems.len), - ); }, .empty_array_sentinel => { const elem_ty = tv.ty.elemType(); const sent_val = tv.ty.sentinel().?; const sentinel = try dg.genTypedValue(.{ .ty = elem_ty, .val = sent_val }); const llvm_elems: [1]*const llvm.Value = .{sentinel}; - const llvm_elem_ty = try dg.llvmType(elem_ty); - return llvm_elem_ty.constArray(&llvm_elems, llvm_elems.len); + const need_unnamed = dg.isUnnamedType(elem_ty, llvm_elems[0]); + if (need_unnamed) { + return dg.context.constStruct(&llvm_elems, llvm_elems.len, .True); + } else { + const llvm_elem_ty = try dg.llvmType(elem_ty); + return llvm_elem_ty.constArray(&llvm_elems, llvm_elems.len); + } }, else => unreachable, }, @@ -1495,7 +1526,7 @@ pub const DeclGen = struct { var llvm_fields = try std.ArrayListUnmanaged(*const llvm.Value).initCapacity(gpa, llvm_field_count); defer llvm_fields.deinit(gpa); - var make_unnamed_struct = false; + var need_unnamed = false; const struct_obj = tv.ty.castTag(.@"struct").?.data; if (struct_obj.layout == .Packed) { const target = dg.module.getTarget(); @@ -1596,14 +1627,13 @@ pub const DeclGen = struct { .val = field_val, }); - make_unnamed_struct = make_unnamed_struct or - dg.isUnnamedType(field_ty, field_llvm_val); + need_unnamed = need_unnamed or dg.isUnnamedType(field_ty, field_llvm_val); llvm_fields.appendAssumeCapacity(field_llvm_val); } } - if (make_unnamed_struct) { + if (need_unnamed) { return dg.context.constStruct( llvm_fields.items.ptr, @intCast(c_uint, llvm_fields.items.len), -- cgit v1.2.3