Merge pull request #10861 from ziglang/f80

make f80 less hacky; lower as u80 on non-x86
This commit is contained in:
Jakub Konka 2022-02-12 13:25:44 +01:00 committed by GitHub
commit 9643f32c99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 338 additions and 135 deletions

View File

@ -791,6 +791,9 @@ add_library(opt_c_util STATIC ${OPTIMIZED_C_SOURCES})
set_target_properties(opt_c_util PROPERTIES
COMPILE_FLAGS "${OPTIMIZED_C_FLAGS}"
)
target_include_directories(opt_c_util PRIVATE
"${CMAKE_SOURCE_DIR}/deps/SoftFloat-3e-prebuilt"
)
add_library(zigstage1 STATIC ${STAGE1_SOURCES})
set_target_properties(zigstage1 PROPERTIES

View File

@ -5,6 +5,7 @@
ZIG=$DEBUG_STAGING/bin/zig
$ZIG test test/behavior.zig -fno-stage1 -I test -fLLVM
$ZIG test test/behavior.zig -fno-stage1 -I test -fLLVM -target aarch64-linux --test-cmd qemu-aarch64 --test-cmd-bin
$ZIG test test/behavior.zig -fno-stage1 -I test -ofmt=c
$ZIG test test/behavior.zig -fno-stage1 -I test -target wasm32-wasi --test-cmd wasmtime --test-cmd-bin
$ZIG test test/behavior.zig -fno-stage1 -I test -target arm-linux --test-cmd qemu-arm --test-cmd-bin

View File

@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef primitiveTypes_h
#define primitiveTypes_h 1
#include "platform.h"
#include <stdint.h>
#ifdef SOFTFLOAT_FAST_INT64

View File

@ -37,6 +37,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef softfloat_types_h
#define softfloat_types_h 1
#include "platform.h"
#include <stdint.h>
/*----------------------------------------------------------------------------

View File

@ -36,28 +36,17 @@ 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));
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,
exp: u16,
_pad: u32 = undefined,
} else extern struct {
exp: u16,
_pad: u32 = undefined, // TODO verify compatibility with hardware
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;
@ -107,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);
@ -1504,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),
};
}

View File

@ -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 {

View File

@ -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;

View File

@ -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;

View File

@ -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);
}

View File

@ -5977,8 +5977,13 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air
return sema.fail(block, src, "unable to cast runtime value to 'comptime_int'", .{});
}
try sema.requireRuntimeBlock(block, operand_src);
// TODO insert safety check to make sure the value fits in the dest type
if ((try sema.typeHasOnePossibleValue(block, dest_ty_src, dest_ty))) |opv| {
return sema.addConstant(dest_ty, opv);
}
try sema.requireRuntimeBlock(block, operand_src);
return block.addTyOp(.intcast, dest_ty, operand);
}
@ -7537,17 +7542,21 @@ fn zirShl(
const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs);
const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs);
if (maybe_rhs_val) |rhs_val| {
if (rhs_val.isUndef()) {
return sema.addConstUndef(sema.typeOf(lhs));
}
if (rhs_val.compareWithZero(.eq)) {
return lhs;
}
}
const runtime_src = if (maybe_lhs_val) |lhs_val| rs: {
const lhs_ty = sema.typeOf(lhs);
if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty);
const rhs_val = maybe_rhs_val orelse break :rs rhs_src;
if (rhs_val.isUndef()) return sema.addConstUndef(lhs_ty);
// If rhs is 0, return lhs without doing any calculations.
if (rhs_val.compareWithZero(.eq)) {
return sema.addConstant(lhs_ty, lhs_val);
}
const target = sema.mod.getTarget();
const val = switch (air_tag) {
.shl_exact => val: {
@ -7577,12 +7586,7 @@ fn zirShl(
};
return sema.addConstant(lhs_ty, val);
} else rs: {
if (maybe_rhs_val) |rhs_val| {
if (rhs_val.isUndef()) return sema.addConstUndef(sema.typeOf(lhs));
}
break :rs lhs_src;
};
} else lhs_src;
// TODO: insert runtime safety check for shl_exact

View File

@ -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();
@ -683,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);
@ -704,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);
}
@ -733,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");
}
@ -820,23 +823,26 @@ 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);
assert(info.bits != 0);
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;
assert(bit_count != 0);
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,
},
@ -855,7 +861,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)
@ -885,9 +892,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());
@ -974,7 +983,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;
@ -1069,12 +1077,11 @@ 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);
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;
}
@ -1105,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.
@ -1137,20 +1145,19 @@ 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);
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| {
@ -1203,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: {
@ -1253,19 +1261,34 @@ 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));
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 = 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));
if (backendSupportsF80(target)) {
return x.constBitCast(llvm_ty);
} else {
return x;
}
},
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,
}
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);
},
.Pointer => switch (tv.val.tag()) {
.decl_ref_mut => return lowerDeclRefValue(dg, tv, tv.val.castTag(.decl_ref_mut).?.data.decl),
@ -1348,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;
@ -1366,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,
},
@ -1472,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();
@ -1573,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),
@ -1836,7 +1889,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 {
@ -2215,7 +2272,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);
@ -2224,6 +2280,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);
@ -2258,6 +2315,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 {
@ -5339,3 +5397,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,
};
}

View File

@ -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,20 +9427,47 @@ static void define_builtin_types(CodeGen *g) {
{
ZigType *entry = new_type_table_entry(ZigTypeIdFloat);
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,
// 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->size_in_bits = 8 * 16;
entry->abi_size = 16; // matches LLVMABISizeOfType(LLVMX86FP80Type())
entry->abi_align = 16; // matches LLVMABIAlignmentOfType(LLVMX86FP80Type())
entry->size_in_bits = 80;
buf_init_from_str(&entry->name, "f80");
entry->data.floating.bit_count = 80;
if (target_has_f80(g->zig_target)) {
entry->llvm_type = LLVMX86FP80Type();
// Note the following u64 alignments:
// x86-linux: 4
// x86-windows: 8
// LLVM makes x86_fp80 have the following alignment and sizes regardless
// of operating system:
// x86_64: size=16, align=16
// x86: size=12, align=4
// However in Zig we override x86-windows to have size=16, align=16
// in order for the property to hold that u80 and f80 have the same ABI size.
unsigned u64_alignment = LLVMABIAlignmentOfType(g->target_data_ref, LLVMInt64Type());
if (u64_alignment >= 8) {
entry->abi_size = 16;
entry->abi_align = 16;
} else if (u64_alignment >= 4) {
entry->abi_size = 12;
entry->abi_align = 4;
} else {
entry->abi_size = 10;
entry->abi_align = u64_alignment;
}
} 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.
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;
}
entry->llvm_di_type = ZigLLVMCreateDebugBasicType(g->dbuilder, buf_ptr(&entry->name),
entry->size_in_bits, ZigLLVMEncoding_DW_ATE_unsigned());

View File

@ -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);

View File

@ -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,19 +1135,43 @@ 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) {
.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()) {

View File

@ -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;

View File

@ -126,6 +126,13 @@ fn testOneCtz(comptime T: type, x: T) u32 {
}
test "@ctz vectors" {
if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .aarch64) {
// TODO this is tripping an LLVM assert:
// zig: /home/andy/Downloads/llvm-project-13/llvm/lib/CodeGen/GlobalISel/LegalizerInfo.cpp:198: llvm::LegalizeActionStep llvm::LegalizeRuleSet::apply(const llvm::LegalityQuery&) const: Assertion `mutationIsSane(Rule, Query, Mutation) && "legality mutation invalid for match"' failed.
// I need to report a zig issue and an llvm issue
return error.SkipZigTest;
}
try testCtzVectors();
comptime try testCtzVectors();
}
@ -981,12 +988,14 @@ test "NaN comparison" {
try testNanEqNan(f32);
try testNanEqNan(f64);
try testNanEqNan(f128);
if (has_f80_rt and (builtin.zig_backend == .stage1)) try testNanEqNan(f80); // TODO
comptime try testNanEqNan(f16);
comptime try testNanEqNan(f32);
comptime try testNanEqNan(f64);
comptime try testNanEqNan(f128);
// comptime try testNanEqNan(f80); // TODO
// TODO make this pass on all targets
// try testNanEqNan(f80);
// comptime try testNanEqNan(f80);
}
fn testNanEqNan(comptime F: type) !void {