From efe34243c674a06ead171adcce67a71efdf057e3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Jul 2022 23:29:21 -0700 Subject: [PATCH 1/3] std.math: add `inline` to some functions These functions semantically benefit from being inline; it makes sense that `isInf(x)` where `x` is comptime-known should have a comptime-known result. --- lib/std/math/float.zig | 24 ++++++++++++------------ lib/std/math/isinf.zig | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/std/math/float.zig b/lib/std/math/float.zig index 72c7f086ac25..1e4477857616 100644 --- a/lib/std/math/float.zig +++ b/lib/std/math/float.zig @@ -3,19 +3,19 @@ const assert = std.debug.assert; const expect = std.testing.expect; /// Creates a raw "1.0" mantissa for floating point type T. Used to dedupe f80 logic. -fn mantissaOne(comptime T: type) comptime_int { +inline fn mantissaOne(comptime T: type) comptime_int { return if (@typeInfo(T).Float.bits == 80) 1 << floatFractionalBits(T) else 0; } /// Creates floating point type T from an unbiased exponent and raw mantissa. -fn reconstructFloat(comptime T: type, exponent: comptime_int, mantissa: comptime_int) T { +inline fn reconstructFloat(comptime T: type, exponent: comptime_int, mantissa: comptime_int) T { const TBits = std.meta.Int(.unsigned, @bitSizeOf(T)); const biased_exponent = @as(TBits, exponent + floatExponentMax(T)); return @bitCast(T, (biased_exponent << floatMantissaBits(T)) | @as(TBits, mantissa)); } /// Returns the number of bits in the exponent of floating point type T. -pub fn floatExponentBits(comptime T: type) comptime_int { +pub inline fn floatExponentBits(comptime T: type) comptime_int { assert(@typeInfo(T) == .Float); return switch (@typeInfo(T).Float.bits) { @@ -29,7 +29,7 @@ pub fn floatExponentBits(comptime T: type) comptime_int { } /// Returns the number of bits in the mantissa of floating point type T. -pub fn floatMantissaBits(comptime T: type) comptime_int { +pub inline fn floatMantissaBits(comptime T: type) comptime_int { assert(@typeInfo(T) == .Float); return switch (@typeInfo(T).Float.bits) { @@ -43,7 +43,7 @@ pub fn floatMantissaBits(comptime T: type) comptime_int { } /// Returns the number of fractional bits in the mantissa of floating point type T. -pub fn floatFractionalBits(comptime T: type) comptime_int { +pub inline fn floatFractionalBits(comptime T: type) comptime_int { assert(@typeInfo(T) == .Float); // standard IEEE floats have an implicit 0.m or 1.m integer part @@ -61,39 +61,39 @@ pub fn floatFractionalBits(comptime T: type) comptime_int { /// Returns the minimum exponent that can represent /// a normalised value in floating point type T. -pub fn floatExponentMin(comptime T: type) comptime_int { +pub inline fn floatExponentMin(comptime T: type) comptime_int { return -floatExponentMax(T) + 1; } /// Returns the maximum exponent that can represent /// a normalised value in floating point type T. -pub fn floatExponentMax(comptime T: type) comptime_int { +pub inline fn floatExponentMax(comptime T: type) comptime_int { return (1 << (floatExponentBits(T) - 1)) - 1; } /// Returns the smallest subnormal number representable in floating point type T. -pub fn floatTrueMin(comptime T: type) T { +pub inline fn floatTrueMin(comptime T: type) T { return reconstructFloat(T, floatExponentMin(T) - 1, 1); } /// Returns the smallest normal number representable in floating point type T. -pub fn floatMin(comptime T: type) T { +pub inline fn floatMin(comptime T: type) T { return reconstructFloat(T, floatExponentMin(T), mantissaOne(T)); } /// Returns the largest normal number representable in floating point type T. -pub fn floatMax(comptime T: type) T { +pub inline fn floatMax(comptime T: type) T { const all1s_mantissa = (1 << floatMantissaBits(T)) - 1; return reconstructFloat(T, floatExponentMax(T), all1s_mantissa); } /// Returns the machine epsilon of floating point type T. -pub fn floatEps(comptime T: type) T { +pub inline fn floatEps(comptime T: type) T { return reconstructFloat(T, -floatFractionalBits(T), mantissaOne(T)); } /// Returns the value inf for floating point type T. -pub fn inf(comptime T: type) T { +pub inline fn inf(comptime T: type) T { return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T)); } diff --git a/lib/std/math/isinf.zig b/lib/std/math/isinf.zig index a26332411f85..ac30470f31c3 100644 --- a/lib/std/math/isinf.zig +++ b/lib/std/math/isinf.zig @@ -3,7 +3,7 @@ const math = std.math; const expect = std.testing.expect; /// Returns whether x is an infinity, ignoring sign. -pub fn isInf(x: anytype) bool { +pub inline fn isInf(x: anytype) bool { const T = @TypeOf(x); const TBits = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); const remove_sign = ~@as(TBits, 0) >> 1; @@ -11,12 +11,12 @@ pub fn isInf(x: anytype) bool { } /// Returns whether x is an infinity with a positive sign. -pub fn isPositiveInf(x: anytype) bool { +pub inline fn isPositiveInf(x: anytype) bool { return x == math.inf(@TypeOf(x)); } /// Returns whether x is an infinity with a negative sign. -pub fn isNegativeInf(x: anytype) bool { +pub inline fn isNegativeInf(x: anytype) bool { return x == -math.inf(@TypeOf(x)); } From 35e70111248f795fbdcefd5ae0d6fc494d1b0683 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 12 Jul 2022 23:30:26 -0700 Subject: [PATCH 2/3] LLVM: implement signext/zeroext attributes For calling convention ABI purposes, integer attributes and return values need to have an LLVM attribute signext or zeroext added sometimes. This commit implements that logic. It also implements a proof-of-concept of moving the F16T type from being a compiler_rt hack to being how the compiler lowers f16 in functions that need to match certain calling conventions. Closes #12054 --- lib/compiler_rt/common.zig | 6 +++- src/codegen/llvm.zig | 65 +++++++++++++++++++++++++++++++++++++- test/behavior/math.zig | 30 ------------------ 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/lib/compiler_rt/common.zig b/lib/compiler_rt/common.zig index 1f95d31c037c..3f91a0c79b4c 100644 --- a/lib/compiler_rt/common.zig +++ b/lib/compiler_rt/common.zig @@ -68,7 +68,11 @@ pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) nore /// need for extending them to wider fp types. /// TODO remove this; do this type selection in the language rather than /// here in compiler-rt. -pub const F16T = if (builtin.cpu.arch.isAARCH64()) f16 else u16; +pub const F16T = switch (builtin.cpu.arch) { + .aarch64, .aarch64_be, .aarch64_32 => f16, + .riscv64 => if (builtin.zig_backend == .stage1) u16 else f16, + else => u16, +}; pub fn wideMultiply(comptime Z: type, a: Z, b: Z, hi: *Z, lo: *Z) void { switch (Z) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 8857c96bc11e..fe35620d3854 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -717,6 +717,11 @@ pub const Object = struct { const ret_ptr = if (sret) llvm_func.getParam(0) else null; const gpa = dg.gpa; + if (ccAbiPromoteInt(fn_info.cc, target, fn_info.return_type)) |s| switch (s) { + .signed => dg.addAttr(llvm_func, 0, "signext"), + .unsigned => dg.addAttr(llvm_func, 0, "zeroext"), + }; + const err_return_tracing = fn_info.return_type.isError() and dg.module.comp.bin_file.options.error_return_tracing; @@ -774,7 +779,10 @@ pub const Object = struct { ); dg.addArgAttrInt(llvm_func, llvm_arg_i, "align", elem_align); } - } + } else if (ccAbiPromoteInt(fn_info.cc, target, param_ty)) |s| switch (s) { + .signed => dg.addArgAttr(llvm_func, llvm_arg_i, "signext"), + .unsigned => dg.addArgAttr(llvm_func, llvm_arg_i, "zeroext"), + }; } llvm_arg_i += 1; }, @@ -887,6 +895,13 @@ pub const Object = struct { }; try args.append(loaded); }, + .as_u16 => { + const param = llvm_func.getParam(llvm_arg_i); + llvm_arg_i += 1; + const casted = builder.buildBitCast(param, dg.context.halfType(), ""); + try args.ensureUnusedCapacity(1); + args.appendAssumeCapacity(casted); + }, }; } @@ -2794,6 +2809,9 @@ pub const DeclGen = struct { llvm_params.appendAssumeCapacity(big_int_ty); } }, + .as_u16 => { + try llvm_params.append(dg.context.intType(16)); + }, }; return llvm.functionType( @@ -4234,6 +4252,12 @@ pub const FuncGen = struct { llvm_args.appendAssumeCapacity(load_inst); } }, + .as_u16 => { + const arg = args[it.zig_index - 1]; + const llvm_arg = try self.resolveInst(arg); + const casted = self.builder.buildBitCast(llvm_arg, self.dg.context.intType(16), ""); + try llvm_args.append(casted); + }, }; const call = self.builder.buildCall( @@ -8965,6 +8989,7 @@ const ParamTypeIterator = struct { abi_sized_int, multiple_llvm_ints, slice, + as_u16, }; pub fn next(it: *ParamTypeIterator) ?Lowering { @@ -9025,6 +9050,15 @@ const ParamTypeIterator = struct { else => false, }; switch (it.target.cpu.arch) { + .riscv32, .riscv64 => { + it.zig_index += 1; + it.llvm_index += 1; + if (ty.tag() == .f16) { + return .as_u16; + } else { + return .byval; + } + }, .mips, .mipsel => { it.zig_index += 1; it.llvm_index += 1; @@ -9135,6 +9169,35 @@ fn iterateParamTypes(dg: *DeclGen, fn_info: Type.Payload.Function.Data) ParamTyp }; } +fn ccAbiPromoteInt( + cc: std.builtin.CallingConvention, + target: std.Target, + ty: Type, +) ?std.builtin.Signedness { + switch (cc) { + .Unspecified, .Inline, .Async => return null, + else => {}, + } + const int_info = switch (ty.zigTypeTag()) { + .Int, .Enum, .ErrorSet => ty.intInfo(target), + else => return null, + }; + if (int_info.bits <= 16) return int_info.signedness; + switch (target.cpu.arch) { + .sparc64, + .riscv64, + .powerpc64, + .powerpc64le, + => { + if (int_info.bits < 64) { + return int_info.signedness; + } + }, + else => {}, + } + return null; +} + fn isByRef(ty: Type) bool { // For tuples and structs, if there are more than this many non-void // fields, then we make it byref, otherwise byval. diff --git a/test/behavior/math.zig b/test/behavior/math.zig index a0796d386b71..7b280bca4e49 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1168,11 +1168,6 @@ test "remainder division" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .riscv64) { - // https://github.com/ziglang/zig/issues/12054 - return error.SkipZigTest; - } - comptime try remdiv(f16); comptime try remdiv(f32); comptime try remdiv(f64); @@ -1204,11 +1199,6 @@ test "float remainder division using @rem" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .riscv64) { - // https://github.com/ziglang/zig/issues/12054 - return error.SkipZigTest; - } - comptime try frem(f16); comptime try frem(f32); comptime try frem(f64); @@ -1251,11 +1241,6 @@ test "float modulo division using @mod" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .riscv64) { - // https://github.com/ziglang/zig/issues/12054 - return error.SkipZigTest; - } - comptime try fmod(f16); comptime try fmod(f32); comptime try fmod(f64); @@ -1431,11 +1416,6 @@ test "@ceil f80" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .riscv64) { - // https://github.com/ziglang/zig/issues/12054 - return error.SkipZigTest; - } - try testCeil(f80, 12.0); comptime try testCeil(f80, 12.0); } @@ -1447,11 +1427,6 @@ test "@ceil f128" { if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .riscv64) { - // https://github.com/ziglang/zig/issues/12054 - return error.SkipZigTest; - } - try testCeil(f128, 12.0); comptime try testCeil(f128, 12.0); } @@ -1600,11 +1575,6 @@ test "NaN comparison" { if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO - if (builtin.zig_backend == .stage2_llvm and builtin.cpu.arch == .riscv64) { - // https://github.com/ziglang/zig/issues/12054 - return error.SkipZigTest; - } - try testNanEqNan(f16); try testNanEqNan(f32); try testNanEqNan(f64); From 92bc3cbe27792be0300fb5f104c011a11f3cf40f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 13 Jul 2022 13:14:37 -0700 Subject: [PATCH 3/3] stage2: fix comptime bitcast involving f80 * Sema: implement comptime bitcast of f80 with integer-like types bitwise rather than taking a round trip through memory layout. * Type: introduce `isAbiInt`. * Value: comptime memory write of f80 writes 0 bytes for padding instead of leaving the memory uninitialized. * Value: floatReadFromMemory has a more general implementation, checking the endianness rather than checking for specific architectures. This fixes behavior test failures occurring on MIPS. --- lib/std/math/float.zig | 10 +++++----- src/Sema.zig | 42 ++++++++++++++++++++++++++++++++++++++++++ src/type.zig | 10 ++++++++++ src/value.zig | 17 +++++++---------- 4 files changed, 64 insertions(+), 15 deletions(-) diff --git a/lib/std/math/float.zig b/lib/std/math/float.zig index 1e4477857616..30e456fcbd87 100644 --- a/lib/std/math/float.zig +++ b/lib/std/math/float.zig @@ -9,14 +9,14 @@ inline fn mantissaOne(comptime T: type) comptime_int { /// Creates floating point type T from an unbiased exponent and raw mantissa. inline fn reconstructFloat(comptime T: type, exponent: comptime_int, mantissa: comptime_int) T { - const TBits = std.meta.Int(.unsigned, @bitSizeOf(T)); + const TBits = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @bitSizeOf(T) } }); const biased_exponent = @as(TBits, exponent + floatExponentMax(T)); return @bitCast(T, (biased_exponent << floatMantissaBits(T)) | @as(TBits, mantissa)); } /// Returns the number of bits in the exponent of floating point type T. pub inline fn floatExponentBits(comptime T: type) comptime_int { - assert(@typeInfo(T) == .Float); + comptime assert(@typeInfo(T) == .Float); return switch (@typeInfo(T).Float.bits) { 16 => 5, @@ -30,7 +30,7 @@ pub inline fn floatExponentBits(comptime T: type) comptime_int { /// Returns the number of bits in the mantissa of floating point type T. pub inline fn floatMantissaBits(comptime T: type) comptime_int { - assert(@typeInfo(T) == .Float); + comptime assert(@typeInfo(T) == .Float); return switch (@typeInfo(T).Float.bits) { 16 => 10, @@ -44,7 +44,7 @@ pub inline fn floatMantissaBits(comptime T: type) comptime_int { /// Returns the number of fractional bits in the mantissa of floating point type T. pub inline fn floatFractionalBits(comptime T: type) comptime_int { - assert(@typeInfo(T) == .Float); + comptime assert(@typeInfo(T) == .Float); // standard IEEE floats have an implicit 0.m or 1.m integer part // f80 is special and has an explicitly stored bit in the MSB @@ -97,7 +97,7 @@ pub inline fn inf(comptime T: type) T { return reconstructFloat(T, floatExponentMax(T) + 1, mantissaOne(T)); } -test "std.math.float" { +test "float bits" { inline for ([_]type{ f16, f32, f64, f80, f128, c_longdouble }) |T| { // (1 +) for the sign bit, since it is separate from the other bits const size = 1 + floatExponentBits(T) + floatMantissaBits(T); diff --git a/src/Sema.zig b/src/Sema.zig index b139c3f89e9f..29840820d071 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -22571,6 +22571,48 @@ fn bitCastVal( const target = sema.mod.getTarget(); if (old_ty.eql(new_ty, sema.mod)) return val; + // Some conversions have a bitwise definition that ignores in-memory layout, + // such as converting between f80 and u80. + + if (old_ty.eql(Type.f80, sema.mod) and new_ty.isAbiInt()) { + const float = val.toFloat(f80); + switch (new_ty.intInfo(target).signedness) { + .signed => { + const int = @bitCast(i80, float); + const limbs = try sema.arena.alloc(std.math.big.Limb, 2); + const big_int = std.math.big.int.Mutable.init(limbs, int); + return Value.fromBigInt(sema.arena, big_int.toConst()); + }, + .unsigned => { + const int = @bitCast(u80, float); + const limbs = try sema.arena.alloc(std.math.big.Limb, 2); + const big_int = std.math.big.int.Mutable.init(limbs, int); + return Value.fromBigInt(sema.arena, big_int.toConst()); + }, + } + } + + if (new_ty.eql(Type.f80, sema.mod) and old_ty.isAbiInt()) { + var bigint_space: Value.BigIntSpace = undefined; + var bigint = try val.toBigIntAdvanced(&bigint_space, target, sema.kit(block, src)); + switch (old_ty.intInfo(target).signedness) { + .signed => { + // This conversion cannot fail because we already checked bit size before + // calling bitCastVal. + const int = bigint.to(i80) catch unreachable; + const float = @bitCast(f80, int); + return Value.Tag.float_80.create(sema.arena, float); + }, + .unsigned => { + // This conversion cannot fail because we already checked bit size before + // calling bitCastVal. + const int = bigint.to(u80) catch unreachable; + const float = @bitCast(f80, int); + return Value.Tag.float_80.create(sema.arena, float); + }, + } + } + // For types with well-defined memory layouts, we serialize them a byte buffer, // then deserialize to the new type. const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target)); diff --git a/src/type.zig b/src/type.zig index 765f1da18c12..0744a50579fd 100644 --- a/src/type.zig +++ b/src/type.zig @@ -4439,6 +4439,16 @@ pub const Type = extern union { }; } + /// Returns true for integers, enums, error sets, and packed structs. + /// If this function returns true, then intInfo() can be called on the type. + pub fn isAbiInt(ty: Type) bool { + return switch (ty.zigTypeTag()) { + .Int, .Enum, .ErrorSet => true, + .Struct => ty.containerLayout() == .Packed, + else => false, + }; + } + /// Asserts the type is an integer, enum, error set, or vector of one of them. pub fn intInfo(self: Type, target: Target) struct { signedness: std.builtin.Signedness, bits: u16 } { var ty = self; diff --git a/src/value.zig b/src/value.zig index 04999c778a83..b52e67e31c81 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1468,8 +1468,7 @@ pub const Value = extern union { const repr = std.math.break_f80(f); std.mem.writeInt(u64, buffer[0..8], repr.fraction, endian); std.mem.writeInt(u16, buffer[8..10], repr.exp, endian); - // TODO set the rest of the bytes to undefined. should we use 0xaa - // or is there a different way? + std.mem.set(u8, buffer[10..], 0); return; } const Int = @Type(.{ .Int = .{ @@ -1481,20 +1480,18 @@ pub const Value = extern union { } fn floatReadFromMemory(comptime F: type, target: Target, buffer: []const u8) F { + const endian = target.cpu.arch.endian(); if (F == f80) { - 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 => {}, - } + return std.math.make_f80(.{ + .fraction = readInt(u64, buffer[0..8], endian), + .exp = readInt(u16, buffer[8..10], endian), + }); } const Int = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = @typeInfo(F).Float.bits, } }); - const int = readInt(Int, buffer[0..@sizeOf(Int)], target.cpu.arch.endian()); + const int = readInt(Int, buffer[0..@sizeOf(Int)], endian); return @bitCast(F, int); }