Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/compiler_rt/common.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
34 changes: 17 additions & 17 deletions lib/std/math/float.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions lib/std/math/isinf.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@ 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;
return @bitCast(TBits, x) & remove_sign == @bitCast(TBits, math.inf(T));
}

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

Expand Down
42 changes: 42 additions & 0 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
65 changes: 64 additions & 1 deletion src/codegen/llvm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
},
Expand Down Expand Up @@ -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);
},
};
}

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -8965,6 +8989,7 @@ const ParamTypeIterator = struct {
abi_sized_int,
multiple_llvm_ints,
slice,
as_u16,
};

pub fn next(it: *ParamTypeIterator) ?Lowering {
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
17 changes: 7 additions & 10 deletions src/value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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 = .{
Expand All @@ -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);
}

Expand Down
Loading