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/lib/std/math/float.zig b/lib/std/math/float.zig index 72c7f086ac25..30e456fcbd87 100644 --- a/lib/std/math/float.zig +++ b/lib/std/math/float.zig @@ -3,20 +3,20 @@ 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 { - const TBits = std.meta.Int(.unsigned, @bitSizeOf(T)); +inline fn reconstructFloat(comptime T: type, exponent: comptime_int, mantissa: comptime_int) 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 fn floatExponentBits(comptime T: type) comptime_int { - assert(@typeInfo(T) == .Float); +pub inline fn floatExponentBits(comptime T: type) comptime_int { + comptime assert(@typeInfo(T) == .Float); return switch (@typeInfo(T).Float.bits) { 16 => 5, @@ -29,8 +29,8 @@ 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 { - assert(@typeInfo(T) == .Float); +pub inline fn floatMantissaBits(comptime T: type) comptime_int { + comptime assert(@typeInfo(T) == .Float); return switch (@typeInfo(T).Float.bits) { 16 => 10, @@ -43,8 +43,8 @@ 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 { - assert(@typeInfo(T) == .Float); +pub inline fn floatFractionalBits(comptime T: type) comptime_int { + 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 @@ -61,43 +61,43 @@ 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)); } -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/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)); } 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/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/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); } 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);