Files
zig/test/behavior/maximum_minimum.zig
Robin Voetter 9c20449cc5 wasm: lower min/max for floats to compiler_rt
The min and max builtins in Zig have some intricate behavior
related to floats, that is not replicated with the min and max
wasm instructions or using simple select operations. By lowering
these instructions to compiler_rt, handling around NaNs is done
correctly.

See also https://github.com/WebAssembly/design/issues/214
2023-10-15 14:00:32 +02:00

322 lines
12 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const mem = std.mem;
const assert = std.debug.assert;
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
test "@max" {
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_sparc64) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var x: i32 = 10;
var y: f32 = 0.68;
var nan: f32 = std.math.nan(f32);
try expect(@as(i32, 10) == @max(@as(i32, -3), x));
try expect(@as(f32, 3.2) == @max(@as(f32, 3.2), y));
try expect(y == @max(nan, y));
try expect(y == @max(y, nan));
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "@max on vectors" {
if (builtin.zig_backend == .stage2_wasm) 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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and
!comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest;
const S = struct {
fn doTheTest() !void {
var a: @Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
var b: @Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
var x = @max(a, b);
try expect(mem.eql(i32, &@as([4]i32, x), &[4]i32{ 2147483647, 2147483647, 30, 40 }));
var c: @Vector(4, f32) = [4]f32{ 0, 0.4, -2.4, 7.8 };
var d: @Vector(4, f32) = [4]f32{ -0.23, 0.42, -0.64, 0.9 };
var y = @max(c, d);
try expect(mem.eql(f32, &@as([4]f32, y), &[4]f32{ 0, 0.42, -0.64, 7.8 }));
var e: @Vector(2, f32) = [2]f32{ 0, std.math.nan(f32) };
var f: @Vector(2, f32) = [2]f32{ std.math.nan(f32), 0 };
var z = @max(e, f);
try expect(mem.eql(f32, &@as([2]f32, z), &[2]f32{ 0, 0 }));
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "@min" {
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_sparc64) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var x: i32 = 10;
var y: f32 = 0.68;
var nan: f32 = std.math.nan(f32);
try expect(@as(i32, -3) == @min(@as(i32, -3), x));
try expect(@as(f32, 0.68) == @min(@as(f32, 3.2), y));
try expect(y == @min(nan, y));
try expect(y == @min(y, nan));
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "@min for vectors" {
if (builtin.zig_backend == .stage2_wasm) 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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and
!comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest;
const S = struct {
fn doTheTest() !void {
var a: @Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 };
var b: @Vector(4, i32) = [4]i32{ 1, 2147483647, 3, 4 };
var x = @min(a, b);
try expect(mem.eql(i32, &@as([4]i32, x), &[4]i32{ 1, -2, 3, 4 }));
var c: @Vector(4, f32) = [4]f32{ 0, 0.4, -2.4, 7.8 };
var d: @Vector(4, f32) = [4]f32{ -0.23, 0.42, -0.64, 0.9 };
var y = @min(c, d);
try expect(mem.eql(f32, &@as([4]f32, y), &[4]f32{ -0.23, 0.4, -2.4, 0.9 }));
var e: @Vector(2, f32) = [2]f32{ 0, std.math.nan(f32) };
var f: @Vector(2, f32) = [2]f32{ std.math.nan(f32), 0 };
var z = @max(e, f);
try expect(mem.eql(f32, &@as([2]f32, z), &[2]f32{ 0, 0 }));
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "@min/max for floats" {
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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_c and comptime builtin.cpu.arch.isArmOrThumb()) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
const S = struct {
fn doTheTest(comptime T: type) !void {
var x: T = -3.14;
var y: T = 5.27;
try expectEqual(x, @min(x, y));
try expectEqual(x, @min(y, x));
try expectEqual(y, @max(x, y));
try expectEqual(y, @max(y, x));
if (T != comptime_float) {
var nan: T = std.math.nan(T);
try expectEqual(y, @max(nan, y));
try expectEqual(y, @max(y, nan));
}
}
};
inline for (.{ f16, f32, f64, f80, f128, c_longdouble }) |T| {
try S.doTheTest(T);
try comptime S.doTheTest(T);
}
try comptime S.doTheTest(comptime_float);
}
test "@min/@max on lazy values" {
const A = extern struct { u8_4: [4]u8 };
const B = extern struct { u8_16: [16]u8 };
const size = @max(@sizeOf(A), @sizeOf(B));
try expect(size == @sizeOf(B));
}
test "@min/@max more than two arguments" {
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
const x: u32 = 30;
const y: u32 = 10;
const z: u32 = 20;
try expectEqual(@as(u32, 10), @min(x, y, z));
try expectEqual(@as(u32, 30), @max(x, y, z));
}
test "@min/@max more than two vector arguments" {
if (builtin.zig_backend == .stage2_wasm) 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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
const x: @Vector(2, u32) = .{ 3, 2 };
const y: @Vector(2, u32) = .{ 4, 1 };
const z: @Vector(2, u32) = .{ 5, 0 };
try expectEqual(@Vector(2, u32){ 3, 0 }, @min(x, y, z));
try expectEqual(@Vector(2, u32){ 5, 2 }, @max(x, y, z));
}
test "@min/@max notices bounds" {
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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
var x: u16 = 20;
const y = 30;
var z: u32 = 100;
const min = @min(x, y, z);
const max = @max(x, y, z);
try expectEqual(x, min);
try expectEqual(u5, @TypeOf(min));
try expectEqual(z, max);
try expectEqual(u32, @TypeOf(max));
}
test "@min/@max notices vector bounds" {
if (builtin.zig_backend == .stage2_wasm) 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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
var x: @Vector(2, u16) = .{ 140, 40 };
const y: @Vector(2, u64) = .{ 5, 100 };
var z: @Vector(2, u32) = .{ 10, 300 };
const min = @min(x, y, z);
const max = @max(x, y, z);
try expectEqual(@Vector(2, u32){ 5, 40 }, min);
try expectEqual(@Vector(2, u7), @TypeOf(min));
try expectEqual(@Vector(2, u32){ 140, 300 }, max);
try expectEqual(@Vector(2, u32), @TypeOf(max));
}
test "@min/@max on comptime_int" {
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_sparc64) return error.SkipZigTest; // TODO
const min = @min(1, 2, -2, -1);
const max = @max(1, 2, -2, -1);
try expectEqual(comptime_int, @TypeOf(min));
try expectEqual(comptime_int, @TypeOf(max));
try expectEqual(-2, min);
try expectEqual(2, max);
}
test "@min/@max notices bounds from types" {
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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
var x: u16 = 123;
var y: u32 = 456;
var z: u8 = 10;
const min = @min(x, y, z);
const max = @max(x, y, z);
comptime assert(@TypeOf(min) == u8);
comptime assert(@TypeOf(max) == u32);
try expectEqual(z, min);
try expectEqual(y, max);
}
test "@min/@max notices bounds from vector types" {
if (builtin.zig_backend == .stage2_wasm) 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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
var x: @Vector(2, u16) = .{ 30, 67 };
var y: @Vector(2, u32) = .{ 20, 500 };
var z: @Vector(2, u8) = .{ 60, 15 };
const min = @min(x, y, z);
const max = @max(x, y, z);
comptime assert(@TypeOf(min) == @Vector(2, u8));
comptime assert(@TypeOf(max) == @Vector(2, u32));
try expectEqual(@Vector(2, u8){ 20, 15 }, min);
try expectEqual(@Vector(2, u32){ 60, 500 }, max);
}
test "@min/@max notices bounds from types when comptime-known value is undef" {
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_sparc64) return error.SkipZigTest; // TODO
var x: u32 = 1_000_000;
const y: u16 = undefined;
// y is comptime-known, but is undef, so bounds cannot be refined using its value
const min = @min(x, y);
const max = @max(x, y);
comptime assert(@TypeOf(min) == u16);
comptime assert(@TypeOf(max) == u32);
// Cannot assert values as one was undefined
}
test "@min/@max notices bounds from vector types when element of comptime-known vector is undef" {
if (builtin.zig_backend == .stage2_wasm) 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_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and
!comptime std.Target.x86.featureSetHas(builtin.cpu.features, .sse4_1)) return error.SkipZigTest;
var x: @Vector(2, u32) = .{ 1_000_000, 12345 };
const y: @Vector(2, u16) = .{ 10, undefined };
// y is comptime-known, but an element is undef, so bounds cannot be refined using its value
const min = @min(x, y);
const max = @max(x, y);
comptime assert(@TypeOf(min) == @Vector(2, u16));
comptime assert(@TypeOf(max) == @Vector(2, u32));
try expectEqual(@as(u16, 10), min[0]);
try expectEqual(@as(u32, 1_000_000), max[0]);
// Cannot assert values at index 1 as one was undefined
}
test "@min/@max of signed and unsigned runtime integers" {
if (builtin.zig_backend == .stage2_spirv64) return error.SkipZigTest;
var x: i32 = -1;
var y: u31 = 1;
const min = @min(x, y);
const max = @max(x, y);
comptime assert(@TypeOf(min) == i32);
comptime assert(@TypeOf(max) == u31);
try expectEqual(x, @min(x, y));
try expectEqual(y, @max(x, y));
}