stage2: lower float negation explicitly

Rather than lowering float negation as `0.0 - x`.

 * Add AIR instruction for float negation.
 * Add compiler-rt functions for f128, f80 negation

closes #11853
This commit is contained in:
Andrew Kelley 2022-06-30 00:02:00 -07:00
parent 54454fd010
commit 6bc6e47b15
25 changed files with 237 additions and 57 deletions

View File

@ -608,7 +608,6 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/multf3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/multi3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/mulxf3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/negXf2.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/negXi2.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/negv.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/os_version_check.zig"
@ -623,11 +622,15 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/sincos.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/sqrt.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/stack_probe.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/subdf3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/subo.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/subsf3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/subdf3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/subtf3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/subxf3.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/negsf2.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/negdf2.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/negtf2.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/negxf2.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/tan.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/trig.zig"
"${CMAKE_SOURCE_DIR}/lib/compiler_rt/trunc.zig"

View File

@ -4,12 +4,13 @@ comptime {
_ = @import("compiler_rt/atomics.zig");
_ = @import("compiler_rt/addf3.zig");
_ = @import("compiler_rt/adddf3.zig");
_ = @import("compiler_rt/addsf3.zig");
_ = @import("compiler_rt/adddf3.zig");
_ = @import("compiler_rt/addtf3.zig");
_ = @import("compiler_rt/addxf3.zig");
_ = @import("compiler_rt/subdf3.zig");
_ = @import("compiler_rt/subsf3.zig");
_ = @import("compiler_rt/subdf3.zig");
_ = @import("compiler_rt/subtf3.zig");
_ = @import("compiler_rt/subxf3.zig");
@ -19,6 +20,11 @@ comptime {
_ = @import("compiler_rt/multf3.zig");
_ = @import("compiler_rt/mulxf3.zig");
_ = @import("compiler_rt/negsf2.zig");
_ = @import("compiler_rt/negdf2.zig");
_ = @import("compiler_rt/negtf2.zig");
_ = @import("compiler_rt/negxf2.zig");
_ = @import("compiler_rt/comparef.zig");
_ = @import("compiler_rt/cmpsf2.zig");
_ = @import("compiler_rt/cmpdf2.zig");
@ -172,8 +178,6 @@ comptime {
_ = @import("compiler_rt/mulo.zig");
_ = @import("compiler_rt/cmp.zig");
_ = @import("compiler_rt/negXf2.zig");
_ = @import("compiler_rt/os_version_check.zig");
_ = @import("compiler_rt/emutls.zig");
_ = @import("compiler_rt/arm.zig");

View File

@ -188,3 +188,15 @@ pub fn normalize(comptime T: type, significand: *std.meta.Int(.unsigned, @typeIn
significand.* <<= @intCast(std.math.Log2Int(Z), shift);
return @as(i32, 1) - shift;
}
pub inline fn fneg(a: anytype) @TypeOf(a) {
const F = @TypeOf(a);
const bits = @typeInfo(F).Float.bits;
const U = @Type(.{ .Int = .{
.signedness = .unsigned,
.bits = bits,
} });
const sign_bit_mask = @as(U, 1) << (bits - 1);
const negated = @bitCast(U, a) ^ sign_bit_mask;
return @bitCast(F, negated);
}

View File

@ -1,42 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const common = @import("common.zig");
pub const panic = common.panic;
comptime {
if (common.want_aeabi) {
@export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage });
@export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage });
} else {
@export(__negsf2, .{ .name = "__negsf2", .linkage = common.linkage });
@export(__negdf2, .{ .name = "__negdf2", .linkage = common.linkage });
}
}
pub fn __negsf2(a: f32) callconv(.C) f32 {
return negXf2(f32, a);
}
fn __aeabi_fneg(a: f32) callconv(.AAPCS) f32 {
return negXf2(f32, a);
}
pub fn __negdf2(a: f64) callconv(.C) f64 {
return negXf2(f64, a);
}
fn __aeabi_dneg(a: f64) callconv(.AAPCS) f64 {
return negXf2(f64, a);
}
inline fn negXf2(comptime T: type, a: T) T {
const Z = std.meta.Int(.unsigned, @typeInfo(T).Float.bits);
const significandBits = std.math.floatMantissaBits(T);
const exponentBits = std.math.floatExponentBits(T);
const signBit = (@as(Z, 1) << (significandBits + exponentBits));
return @bitCast(T, @bitCast(Z, a) ^ signBit);
}

View File

@ -0,0 +1,19 @@
const common = @import("./common.zig");
pub const panic = common.panic;
comptime {
if (common.want_aeabi) {
@export(__aeabi_dneg, .{ .name = "__aeabi_dneg", .linkage = common.linkage });
} else {
@export(__negdf2, .{ .name = "__negdf2", .linkage = common.linkage });
}
}
fn __negdf2(a: f64) callconv(.C) f64 {
return common.fneg(a);
}
fn __aeabi_dneg(a: f64) callconv(.AAPCS) f64 {
return common.fneg(a);
}

View File

@ -0,0 +1,19 @@
const common = @import("./common.zig");
pub const panic = common.panic;
comptime {
if (common.want_aeabi) {
@export(__aeabi_fneg, .{ .name = "__aeabi_fneg", .linkage = common.linkage });
} else {
@export(__negsf2, .{ .name = "__negsf2", .linkage = common.linkage });
}
}
fn __negsf2(a: f32) callconv(.C) f32 {
return common.fneg(a);
}
fn __aeabi_fneg(a: f32) callconv(.AAPCS) f32 {
return common.fneg(a);
}

View File

@ -0,0 +1,11 @@
const common = @import("./common.zig");
pub const panic = common.panic;
comptime {
@export(__negtf2, .{ .name = "__negtf2", .linkage = common.linkage });
}
fn __negtf2(a: f128) callconv(.C) f128 {
return common.fneg(a);
}

View File

@ -0,0 +1,11 @@
const common = @import("./common.zig");
pub const panic = common.panic;
comptime {
@export(__negxf2, .{ .name = "__negxf2", .linkage = common.linkage });
}
fn __negxf2(a: f80) callconv(.C) f80 {
return common.fneg(a);
}

View File

@ -2225,7 +2225,6 @@ test "float.scientific.precision" {
}
test "float.special" {
if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
try expectFmt("f64: nan", "f64: {}", .{math.nan_f64});
// negative nan is not defined by IEE 754,
// and ARM thus normalizes it to positive nan
@ -2237,7 +2236,6 @@ test "float.special" {
}
test "float.hexadecimal.special" {
if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
try expectFmt("f64: nan", "f64: {x}", .{math.nan_f64});
// negative nan is not defined by IEE 754,
// and ARM thus normalizes it to positive nan

View File

@ -13,7 +13,6 @@ pub fn copysign(magnitude: anytype, sign: @TypeOf(magnitude)) @TypeOf(magnitude)
}
test "math.copysign" {
if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(copysign(@as(T, 1.0), @as(T, 1.0)) == 1.0);
try expect(copysign(@as(T, 2.0), @as(T, -2.0)) == -2.0);

View File

@ -10,7 +10,6 @@ pub fn signbit(x: anytype) bool {
}
test "math.signbit" {
if (@import("builtin").zig_backend != .stage1) return error.SkipZigTest; // TODO
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
try expect(!signbit(@as(T, 0.0)));
try expect(!signbit(@as(T, 1.0)));

View File

@ -288,6 +288,11 @@ pub const Inst = struct {
/// Rounds a floating pointer number to the nearest integer towards zero.
/// Uses the `un_op` field.
trunc_float,
/// Float negation. This affects the sign of zero, inf, and NaN, which is impossible
/// to do with sub. Integers are not allowed and must be represented with sub with
/// LHS of zero.
/// Uses the `un_op` field.
neg,
/// `<`. Result type is always bool.
/// Uses the `bin_op` field.
@ -970,6 +975,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.ceil,
.round,
.trunc_float,
.neg,
=> return air.typeOf(datas[inst].un_op),
.cmp_lt,

View File

@ -287,6 +287,7 @@ pub fn categorizeOperand(
.ceil,
.round,
.trunc_float,
.neg,
.cmp_lt_errors_len,
=> {
const o = air_datas[inst].un_op;
@ -834,6 +835,7 @@ fn analyzeInst(
.ceil,
.round,
.trunc_float,
.neg,
.cmp_lt_errors_len,
.set_err_return_trace,
=> {

View File

@ -10070,12 +10070,14 @@ fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.
}
if (rhs_scalar_ty.isAnyFloat()) {
// We handle comptime negation here to ensure negative zero is represented in the bits.
// We handle float negation here to ensure negative zero is represented in the bits.
if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| {
if (rhs_val.isUndef()) return sema.addConstUndef(rhs_ty);
const target = sema.mod.getTarget();
return sema.addConstant(rhs_ty, try rhs_val.floatNeg(rhs_ty, sema.arena, target));
}
try sema.requireRuntimeBlock(block, rhs_src);
return block.addUnOp(.neg, rhs);
}
const lhs = if (rhs_ty.zigTypeTag() == .Vector)

View File

@ -582,7 +582,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.floor,
.ceil,
.round,
.trunc_float
.trunc_float,
.neg,
=> try self.airUnaryMath(inst),
.add_with_overflow => try self.airOverflow(inst),

View File

@ -596,6 +596,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.ceil,
.round,
.trunc_float,
.neg,
=> try self.airUnaryMath(inst),
.add_with_overflow => try self.airOverflow(inst),

View File

@ -516,6 +516,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.ceil,
.round,
.trunc_float,
.neg,
=> try self.airUnaryMath(inst),
.add_with_overflow => try self.airAddWithOverflow(inst),

View File

@ -529,6 +529,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.ceil,
.round,
.trunc_float,
.neg,
=> @panic("TODO try self.airUnaryMath(inst)"),
.add_with_overflow => try self.airAddSubWithOverflow(inst),

View File

@ -1607,6 +1607,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.log10,
.fabs,
.round,
.neg,
.cmpxchg_weak,
.cmpxchg_strong,

View File

@ -605,6 +605,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.ceil,
.round,
.trunc_float,
.neg,
=> try self.airUnaryMath(inst),
.add_with_overflow => try self.airAddSubShlWithOverflow(inst),

View File

@ -1755,6 +1755,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.mul_sat => try airSatOp(f, inst, "muls_"),
.shl_sat => try airSatOp(f, inst, "shls_"),
.neg => try airNeg(f, inst),
.sqrt,
.sin,
.cos,
@ -4098,6 +4100,20 @@ fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue {
return local;
}
fn airNeg(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst)) return CValue.none;
const un_op = f.air.instructions.items(.data)[inst].un_op;
const writer = f.object.writer();
const inst_ty = f.air.typeOfIndex(inst);
const operand = try f.resolveInst(un_op);
const local = try f.allocLocal(inst_ty, .Const);
try writer.writeAll("-");
try f.writeCValue(writer, operand);
try writer.writeAll(";\n");
return local;
}
fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue {
if (f.liveness.isUnused(inst)) return CValue.none;
const pl_op = f.air.instructions.items(.data)[inst].pl_op;

View File

@ -4019,6 +4019,7 @@ pub const FuncGen = struct {
.ceil => try self.airUnaryOp(inst, .ceil),
.round => try self.airUnaryOp(inst, .round),
.trunc_float => try self.airUnaryOp(inst, .trunc),
.neg => try self.airUnaryOp(inst, .neg),
.cmp_eq => try self.airCmp(inst, .eq),
.cmp_gt => try self.airCmp(inst, .gt),
@ -6545,13 +6546,14 @@ pub const FuncGen = struct {
fabs,
floor,
fma,
fmax,
fmin,
fmod,
log,
log10,
log2,
fmax,
fmin,
mul,
fmod,
neg,
round,
sin,
sqrt,
@ -6584,6 +6586,7 @@ pub const FuncGen = struct {
var fn_name_buf: [64]u8 = undefined;
const strat: FloatOpStrat = if (intrinsics_allowed) switch (op) {
// Some operations are dedicated LLVM instructions, not available as intrinsics
.neg => return self.builder.buildFNeg(params[0], ""),
.add => return self.builder.buildFAdd(params[0], params[1], ""),
.sub => return self.builder.buildFSub(params[0], params[1], ""),
.mul => return self.builder.buildFMul(params[0], params[1], ""),
@ -6595,6 +6598,11 @@ pub const FuncGen = struct {
} else b: {
const float_bits = scalar_ty.floatBits(target);
break :b switch (op) {
.neg => FloatOpStrat{
.libc = std.fmt.bufPrintZ(&fn_name_buf, "__neg{s}f2", .{
compilerRtFloatAbbrev(float_bits),
}) catch unreachable,
},
.add, .sub, .div, .mul => FloatOpStrat{
.libc = std.fmt.bufPrintZ(&fn_name_buf, "__{s}{s}f3", .{
@tagName(op), compilerRtFloatAbbrev(float_bits),

View File

@ -549,6 +549,9 @@ pub const Builder = opaque {
pub const buildFSub = LLVMBuildFSub;
extern fn LLVMBuildFSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
pub const buildFNeg = LLVMBuildFNeg;
extern fn LLVMBuildFNeg(*const Builder, V: *const Value, Name: [*:0]const u8) *const Value;
pub const buildSub = LLVMBuildSub;
extern fn LLVMBuildSub(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;

View File

@ -168,6 +168,7 @@ const Writer = struct {
.ceil,
.round,
.trunc_float,
.neg,
.cmp_lt_errors_len,
.set_err_return_trace,
=> try w.writeUnOp(s, inst),

View File

@ -574,6 +574,7 @@ test "negation f32" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
@ -593,6 +594,8 @@ test "negation f64" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
@ -707,3 +710,103 @@ test "comptime_float zero divided by zero produces zero" {
try expect((0.0 / 0.0) == 0.0);
}
test "nan negation f16" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const nan_comptime = comptime math.nan(f16);
const neg_nan_comptime = -nan_comptime;
var nan_runtime = math.nan(f16);
const neg_nan_runtime = -nan_runtime;
try expect(!math.signbit(nan_runtime));
try expect(math.signbit(neg_nan_runtime));
try expect(!math.signbit(nan_comptime));
try expect(math.signbit(neg_nan_comptime));
}
test "nan negation f32" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const nan_comptime = comptime math.nan(f32);
const neg_nan_comptime = -nan_comptime;
var nan_runtime = math.nan(f32);
const neg_nan_runtime = -nan_runtime;
try expect(!math.signbit(nan_runtime));
try expect(math.signbit(neg_nan_runtime));
try expect(!math.signbit(nan_comptime));
try expect(math.signbit(neg_nan_comptime));
}
test "nan negation f64" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const nan_comptime = comptime math.nan(f64);
const neg_nan_comptime = -nan_comptime;
var nan_runtime = math.nan(f64);
const neg_nan_runtime = -nan_runtime;
try expect(!math.signbit(nan_runtime));
try expect(math.signbit(neg_nan_runtime));
try expect(!math.signbit(nan_comptime));
try expect(math.signbit(neg_nan_comptime));
}
test "nan negation f128" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const nan_comptime = comptime math.nan(f128);
const neg_nan_comptime = -nan_comptime;
var nan_runtime = math.nan(f128);
const neg_nan_runtime = -nan_runtime;
try expect(!math.signbit(nan_runtime));
try expect(math.signbit(neg_nan_runtime));
try expect(!math.signbit(nan_comptime));
try expect(math.signbit(neg_nan_comptime));
}
test "nan negation f80" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
const nan_comptime = comptime math.nan(f80);
const neg_nan_comptime = -nan_comptime;
var nan_runtime = math.nan(f80);
const neg_nan_runtime = -nan_runtime;
try expect(!math.signbit(nan_runtime));
try expect(math.signbit(neg_nan_runtime));
try expect(!math.signbit(nan_comptime));
try expect(math.signbit(neg_nan_comptime));
}