x86_64: rewrite @abs for scalar floats

This commit is contained in:
Jacob Young 2025-01-20 21:18:56 -05:00 committed by Andrew Kelley
parent f1ce1aff11
commit d652dd0658
4 changed files with 855 additions and 260 deletions

File diff suppressed because it is too large Load Diff

View File

@ -330,8 +330,8 @@ pub const Inst = struct {
f_pi,
/// Float ___ Pop Pop
f_pp,
/// Float ___ stack-top pointer
f_stp,
/// Float ___ crement Stack-Top Pointer
f_cstp,
/// Float ___ Status Word
f_sw,
/// Float ___ Unordered
@ -555,6 +555,7 @@ pub const Inst = struct {
/// Decimal adjust AL after subtraction
da,
/// Decrement by 1
/// Decrement stack-top pointer
/// Decrement shadow stack pointer
de,
/// Unsigned division
@ -587,6 +588,7 @@ pub const Inst = struct {
/// Input from port
/// Input from port to string
/// Increment by 1
/// Increment stack-top pointer
/// Increment shadow stack pointer
in,
/// Call to interrupt procedure
@ -792,14 +794,10 @@ pub const Inst = struct {
comi,
/// Cosine
cos,
/// Decrement stack-top pointer
decstp,
/// Reverse divide
divr,
/// Free floating-point register
free,
/// Increment stack-top pointer
incstp,
/// Initialize floating-point unit
init,
/// Load binary coded decimal integer

View File

@ -427,7 +427,7 @@ pub const zigcc = struct {
const int_param_regs = gp_regs[0 .. volatile_gpr - 1];
const x87_param_regs = x87_regs[0..volatile_x87];
const sse_param_regs = sse_avx_regs[0..volatile_sse];
const sse_param_regs = sse_avx_regs[0 .. volatile_sse / 2];
const int_return_regs = gp_regs[0..volatile_gpr];
const x87_return_regs = x87_regs[0..volatile_x87];
const sse_return_regs = sse_avx_regs[0..volatile_gpr];
@ -443,11 +443,11 @@ pub const SysV = struct {
pub const caller_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 } ++ x87_regs ++ sse_avx_regs;
pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
pub const c_abi_x87_param_regs = x87_regs[0..0].*;
pub const c_abi_sse_param_regs = sse_avx_regs[0..8].*;
pub const c_abi_x87_param_regs = x87_regs[0..0];
pub const c_abi_sse_param_regs = sse_avx_regs[0..8];
pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
pub const c_abi_x87_return_regs = x87_regs[0..2].*;
pub const c_abi_sse_return_regs = sse_avx_regs[0..4].*;
pub const c_abi_x87_return_regs = x87_regs[0..2];
pub const c_abi_sse_return_regs = sse_avx_regs[0..4];
};
pub const Win64 = struct {
@ -460,11 +460,11 @@ pub const Win64 = struct {
pub const caller_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .r8, .r9, .r10, .r11 } ++ x87_regs ++ sse_avx_regs;
pub const c_abi_int_param_regs = [_]Register{ .rcx, .rdx, .r8, .r9 };
pub const c_abi_x87_param_regs = x87_regs[0..0].*;
pub const c_abi_sse_param_regs = sse_avx_regs[0..4].*;
pub const c_abi_x87_param_regs = x87_regs[0..0];
pub const c_abi_sse_param_regs = sse_avx_regs[0..4];
pub const c_abi_int_return_regs = [_]Register{.rax};
pub const c_abi_x87_return_regs = x87_regs[0..0].*;
pub const c_abi_sse_return_regs = sse_avx_regs[0..1].*;
pub const c_abi_x87_return_regs = x87_regs[0..0];
pub const c_abi_sse_return_regs = sse_avx_regs[0..1];
};
pub fn getCalleePreservedRegs(cc: std.builtin.CallingConvention.Tag) []const Register {
@ -497,17 +497,21 @@ pub fn getCAbiIntParamRegs(cc: std.builtin.CallingConvention.Tag) []const Regist
pub fn getCAbiX87ParamRegs(cc: std.builtin.CallingConvention.Tag) []const Register {
return switch (cc) {
.auto => zigcc.x87_param_regs,
.x86_64_sysv => &SysV.c_abi_x87_param_regs,
.x86_64_win => &Win64.c_abi_x87_param_regs,
.x86_64_sysv => SysV.c_abi_x87_param_regs,
.x86_64_win => Win64.c_abi_x87_param_regs,
else => unreachable,
};
}
pub fn getCAbiSseParamRegs(cc: std.builtin.CallingConvention.Tag) []const Register {
pub fn getCAbiSseParamRegs(cc: std.builtin.CallingConvention.Tag, target: *const std.Target) []const Register {
return switch (cc) {
.auto => zigcc.sse_param_regs,
.x86_64_sysv => &SysV.c_abi_sse_param_regs,
.x86_64_win => &Win64.c_abi_sse_param_regs,
.auto => switch (target.cpu.arch) {
else => unreachable,
.x86 => zigcc.sse_param_regs[0 .. zigcc.sse_param_regs.len / 2],
.x86_64 => zigcc.sse_param_regs,
},
.x86_64_sysv => SysV.c_abi_sse_param_regs,
.x86_64_win => Win64.c_abi_sse_param_regs,
else => unreachable,
};
}
@ -524,8 +528,8 @@ pub fn getCAbiIntReturnRegs(cc: std.builtin.CallingConvention.Tag) []const Regis
pub fn getCAbiX87ReturnRegs(cc: std.builtin.CallingConvention.Tag) []const Register {
return switch (cc) {
.auto => zigcc.x87_return_regs,
.x86_64_sysv => &SysV.c_abi_x87_return_regs,
.x86_64_win => &Win64.c_abi_x87_return_regs,
.x86_64_sysv => SysV.c_abi_x87_return_regs,
.x86_64_win => Win64.c_abi_x87_return_regs,
else => unreachable,
};
}
@ -533,8 +537,8 @@ pub fn getCAbiX87ReturnRegs(cc: std.builtin.CallingConvention.Tag) []const Regis
pub fn getCAbiSseReturnRegs(cc: std.builtin.CallingConvention.Tag) []const Register {
return switch (cc) {
.auto => zigcc.sse_return_regs,
.x86_64_sysv => &SysV.c_abi_sse_return_regs,
.x86_64_win => &Win64.c_abi_sse_return_regs,
.x86_64_sysv => SysV.c_abi_sse_return_regs,
.x86_64_win => Win64.c_abi_sse_return_regs,
else => unreachable,
};
}

View File

@ -1,22 +1,136 @@
fn Unary(comptime op: anytype) type {
return struct {
fn testArgs(comptime Type: type, comptime imm_arg: Type) !void {
const expected = op(Type, imm_arg);
try struct {
fn checkExpected(actual: @TypeOf(expected)) !void {
if (switch (@typeInfo(@TypeOf(expected))) {
else => actual != expected,
.vector => @reduce(.Or, actual != expected),
const builtin = @import("builtin");
const inf = math.inf;
const math = std.math;
const max = math.floatMax;
const min = math.floatMin;
const nan = math.nan;
const std = @import("std");
const trueMin = math.floatTrueMin;
const Gpr = switch (builtin.cpu.arch) {
else => unreachable,
.x86 => u32,
.x86_64 => u64,
};
const Sse = if (std.Target.x86.featureSetHas(builtin.cpu.features, .avx))
@Vector(32, u8)
else
@Vector(16, u8);
inline fn sign(rhs: anytype) bool {
return @call(.always_inline, math.signbit, .{rhs});
}
inline fn boolAnd(lhs: anytype, rhs: @TypeOf(lhs)) @TypeOf(lhs) {
switch (@typeInfo(@TypeOf(lhs))) {
.bool => return lhs and rhs,
.vector => |vector| switch (vector.child) {
bool => {
const Bits = @Vector(vector.len, u1);
const lhs_bits: Bits = @bitCast(lhs);
const rhs_bits: Bits = @bitCast(rhs);
return @bitCast(lhs_bits & rhs_bits);
},
else => {},
},
else => {},
}
@compileError("unsupported boolAnd type: " ++ @typeName(@TypeOf(lhs)));
}
inline fn boolOr(lhs: anytype, rhs: @TypeOf(lhs)) @TypeOf(lhs) {
switch (@typeInfo(@TypeOf(lhs))) {
.bool => return lhs or rhs,
.vector => |vector| switch (vector.child) {
bool => {
const Bits = @Vector(vector.len, u1);
const lhs_bits: Bits = @bitCast(lhs);
const rhs_bits: Bits = @bitCast(rhs);
return @bitCast(lhs_bits | rhs_bits);
},
else => {},
},
else => {},
}
@compileError("unsupported boolOr type: " ++ @typeName(@TypeOf(lhs)));
}
// noinline for a more helpful stack trace
noinline fn checkExpected(expected: anytype, actual: @TypeOf(expected)) !void {
const info = @typeInfo(@TypeOf(expected));
const unexpected = switch (switch (info) {
else => info,
.vector => |vector| @typeInfo(vector.child),
}) {
else => expected != actual,
.float => boolOr(boolAnd(expected != actual, boolOr(expected == expected, actual == actual)), sign(expected) != sign(actual)),
};
if (switch (info) {
else => unexpected,
.vector => @reduce(.Or, unexpected),
}) return error.Unexpected;
}
noinline fn testArgKinds(mem_arg: Type) !void {
test checkExpected {
if (checkExpected(nan(f32), nan(f32)) == error.Unexpected) return error.Unexpected;
if (checkExpected(nan(f32), -nan(f32)) != error.Unexpected) return error.Unexpected;
if (checkExpected(@as(f32, 0.0), @as(f32, 0.0)) == error.Unexpected) return error.Unexpected;
if (checkExpected(@as(f32, -0.0), @as(f32, -0.0)) == error.Unexpected) return error.Unexpected;
if (checkExpected(@as(f32, -0.0), @as(f32, 0.0)) != error.Unexpected) return error.Unexpected;
if (checkExpected(@as(f32, 0.0), @as(f32, -0.0)) != error.Unexpected) return error.Unexpected;
}
fn Unary(comptime op: anytype) type {
return struct {
// noinline so that `mem_arg` is on the stack
noinline fn testArgKinds(
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
comptime Type: type,
comptime imm_arg: Type,
mem_arg: Type,
) !void {
const expected = comptime op(Type, imm_arg);
var reg_arg = mem_arg;
_ = .{&reg_arg};
try checkExpected(op(Type, reg_arg));
try checkExpected(op(Type, mem_arg));
try checkExpected(op(Type, imm_arg));
try checkExpected(expected, op(Type, reg_arg));
try checkExpected(expected, op(Type, mem_arg));
try checkExpected(expected, op(Type, imm_arg));
}
}.testArgKinds(imm_arg);
// noinline for a more helpful stack trace
noinline fn testArgs(comptime Type: type, comptime imm_arg: Type) !void {
try testArgKinds(
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
Type,
imm_arg,
imm_arg,
);
}
fn testIntTypes() !void {
try testArgs(i1, -1);
@ -381,6 +495,102 @@ fn Unary(comptime op: anytype) type {
try testArgs(u1025, 1 << 1023);
try testArgs(u1025, 1 << 1024);
}
fn testFloatTypes() !void {
try testArgs(f16, -nan(f16));
try testArgs(f16, -inf(f16));
try testArgs(f16, -max(f16));
try testArgs(f16, -10.0);
try testArgs(f16, -1.0);
try testArgs(f16, -0.1);
try testArgs(f16, -min(f16));
try testArgs(f16, -trueMin(f16));
try testArgs(f16, -0.0);
try testArgs(f16, 0.0);
try testArgs(f16, trueMin(f16));
try testArgs(f16, min(f16));
try testArgs(f16, 0.1);
try testArgs(f16, 1.0);
try testArgs(f16, 10.0);
try testArgs(f16, max(f16));
try testArgs(f16, inf(f16));
try testArgs(f16, nan(f16));
try testArgs(f32, -nan(f32));
try testArgs(f32, -inf(f32));
try testArgs(f32, -max(f32));
try testArgs(f32, -10.0);
try testArgs(f32, -1.0);
try testArgs(f32, -0.1);
try testArgs(f32, -min(f32));
try testArgs(f32, -trueMin(f32));
try testArgs(f32, -0.0);
try testArgs(f32, 0.0);
try testArgs(f32, trueMin(f32));
try testArgs(f32, min(f32));
try testArgs(f32, 0.1);
try testArgs(f32, 1.0);
try testArgs(f32, 10.0);
try testArgs(f32, max(f32));
try testArgs(f32, inf(f32));
try testArgs(f32, nan(f32));
try testArgs(f64, -nan(f64));
try testArgs(f64, -inf(f64));
try testArgs(f64, -max(f64));
try testArgs(f64, -10.0);
try testArgs(f64, -1.0);
try testArgs(f64, -0.1);
try testArgs(f64, -min(f64));
try testArgs(f64, -trueMin(f64));
try testArgs(f64, -0.0);
try testArgs(f64, 0.0);
try testArgs(f64, trueMin(f64));
try testArgs(f64, min(f64));
try testArgs(f64, 0.1);
try testArgs(f64, 1.0);
try testArgs(f64, 10.0);
try testArgs(f64, max(f64));
try testArgs(f64, inf(f64));
try testArgs(f64, nan(f64));
try testArgs(f80, -nan(f80));
try testArgs(f80, -inf(f80));
try testArgs(f80, -max(f80));
try testArgs(f80, -10.0);
try testArgs(f80, -1.0);
try testArgs(f80, -0.1);
try testArgs(f80, -min(f80));
try testArgs(f80, -trueMin(f80));
try testArgs(f80, -0.0);
try testArgs(f80, 0.0);
try testArgs(f80, trueMin(f80));
try testArgs(f80, min(f80));
try testArgs(f80, 0.1);
try testArgs(f80, 1.0);
try testArgs(f80, 10.0);
try testArgs(f80, max(f80));
try testArgs(f80, inf(f80));
try testArgs(f80, nan(f80));
try testArgs(f128, -nan(f128));
try testArgs(f128, -inf(f128));
try testArgs(f128, -max(f128));
try testArgs(f128, -10.0);
try testArgs(f128, -1.0);
try testArgs(f128, -0.1);
try testArgs(f128, -min(f128));
try testArgs(f128, -trueMin(f128));
try testArgs(f128, -0.0);
try testArgs(f128, 0.0);
try testArgs(f128, trueMin(f128));
try testArgs(f128, min(f128));
try testArgs(f128, 0.1);
try testArgs(f128, 1.0);
try testArgs(f128, 10.0);
try testArgs(f128, max(f128));
try testArgs(f128, inf(f128));
try testArgs(f128, nan(f128));
}
fn testIntVectorTypes() !void {
try testArgs(@Vector(3, i1), .{ -1 << 0, -1, 0 });
try testArgs(@Vector(3, u1), .{ 0, 1, 1 << 0 });
@ -931,29 +1141,68 @@ fn Unary(comptime op: anytype) type {
fn Binary(comptime op: anytype) type {
return struct {
fn testArgs(comptime Type: type, comptime imm_lhs: Type, comptime imm_rhs: Type) !void {
const expected = op(Type, imm_lhs, imm_rhs);
try struct {
fn checkExpected(actual: @TypeOf(expected)) !void {
if (switch (@typeInfo(@TypeOf(expected))) {
else => actual != expected,
.vector => @reduce(.Or, actual != expected),
}) return error.Unexpected;
}
noinline fn testArgKinds(mem_lhs: Type, mem_rhs: Type) !void {
// noinline so that `mem_lhs` and `mem_rhs` are on the stack
noinline fn testArgKinds(
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Gpr,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
_: Sse,
comptime Type: type,
comptime imm_lhs: Type,
mem_lhs: Type,
comptime imm_rhs: Type,
mem_rhs: Type,
) !void {
const expected = comptime op(Type, imm_lhs, imm_rhs);
var reg_lhs = mem_lhs;
var reg_rhs = mem_rhs;
_ = .{ &reg_lhs, &reg_rhs };
try checkExpected(op(Type, reg_lhs, reg_rhs));
try checkExpected(op(Type, reg_lhs, mem_rhs));
try checkExpected(op(Type, reg_lhs, imm_rhs));
try checkExpected(op(Type, mem_lhs, reg_rhs));
try checkExpected(op(Type, mem_lhs, mem_rhs));
try checkExpected(op(Type, mem_lhs, imm_rhs));
try checkExpected(op(Type, imm_lhs, reg_rhs));
try checkExpected(op(Type, imm_lhs, mem_rhs));
try checkExpected(expected, op(Type, reg_lhs, reg_rhs));
try checkExpected(expected, op(Type, reg_lhs, mem_rhs));
try checkExpected(expected, op(Type, reg_lhs, imm_rhs));
try checkExpected(expected, op(Type, mem_lhs, reg_rhs));
try checkExpected(expected, op(Type, mem_lhs, mem_rhs));
try checkExpected(expected, op(Type, mem_lhs, imm_rhs));
try checkExpected(expected, op(Type, imm_lhs, reg_rhs));
try checkExpected(expected, op(Type, imm_lhs, mem_rhs));
}
}.testArgKinds(imm_lhs, imm_rhs);
// noinline for a more helpful stack trace
noinline fn testArgs(comptime Type: type, comptime imm_lhs: Type, comptime imm_rhs: Type) !void {
try testArgKinds(
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
undefined,
Type,
imm_lhs,
imm_lhs,
imm_rhs,
imm_rhs,
);
}
fn testIntTypes() !void {
try testArgs(u8, 0xbb, 0x43);
@ -1308,6 +1557,7 @@ inline fn abs(comptime Type: type, rhs: Type) @TypeOf(@abs(rhs)) {
test abs {
try Unary(abs).testIntTypes();
try Unary(abs).testIntVectorTypes();
try Unary(abs).testFloatTypes();
}
inline fn clz(comptime Type: type, rhs: Type) @TypeOf(@clz(rhs)) {