Sema: improve lowering of stores to bitcasted vector pointers

Detect if we are storing an array operand to a bitcasted vector pointer.
If so, we instead reach through the bitcasted pointer to the vector pointer,
bitcast the array operand to a vector, and then lower this as a store of
a vector value to a vector pointer. This generally results in better code,
as well as working around an LLVM bug.

See #11154
This commit is contained in:
Andrew Kelley 2022-03-14 00:10:35 -07:00
parent eee989d2a0
commit b2a1b4c085
5 changed files with 324 additions and 86 deletions

View File

@ -13276,7 +13276,7 @@ fn checkFloatType(
ty: Type,
) CompileError!void {
switch (ty.zigTypeTag()) {
.ComptimeFloat, .Float => {},
.ComptimeInt, .ComptimeFloat, .Float => {},
else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty}),
}
}
@ -17177,10 +17177,25 @@ fn storePtr2(
return;
}
// TODO do the same thing for anon structs as for tuples above.
// Detect if we are storing an array operand to a bitcasted vector pointer.
// If so, we instead reach through the bitcasted pointer to the vector pointer,
// bitcast the array operand to a vector, and then lower this as a store of
// a vector value to a vector pointer. This generally results in better code,
// as well as working around an LLVM bug:
// https://github.com/ziglang/zig/issues/11154
if (sema.obtainBitCastedVectorPtr(ptr)) |vector_ptr| {
const vector_ty = sema.typeOf(vector_ptr).childType();
const vector = try sema.coerce(block, vector_ty, uncasted_operand, operand_src);
try sema.storePtr2(block, src, vector_ptr, ptr_src, vector, operand_src, .store);
return;
}
const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src);
const maybe_operand_val = try sema.resolveMaybeUndefVal(block, operand_src, operand);
const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: {
const maybe_operand_val = try sema.resolveMaybeUndefVal(block, operand_src, operand);
const operand_val = maybe_operand_val orelse {
try sema.checkPtrIsNotComptimeMutable(block, ptr_val, ptr_src, operand_src);
break :rs operand_src;
@ -17203,6 +17218,39 @@ fn storePtr2(
_ = try block.addBinOp(air_tag, ptr, operand);
}
/// Traverse an arbitrary number of bitcasted pointers and return the underyling vector
/// pointer. Only if the final element type matches the vector element type, and the
/// lengths match.
fn obtainBitCastedVectorPtr(sema: *Sema, ptr: Air.Inst.Ref) ?Air.Inst.Ref {
const array_ty = sema.typeOf(ptr).childType();
if (array_ty.zigTypeTag() != .Array) return null;
var ptr_inst = Air.refToIndex(ptr) orelse return null;
const air_datas = sema.air_instructions.items(.data);
const air_tags = sema.air_instructions.items(.tag);
const prev_ptr = while (air_tags[ptr_inst] == .bitcast) {
const prev_ptr = air_datas[ptr_inst].ty_op.operand;
const prev_ptr_ty = sema.typeOf(prev_ptr);
const prev_ptr_child_ty = switch (prev_ptr_ty.tag()) {
.single_mut_pointer => prev_ptr_ty.castTag(.single_mut_pointer).?.data,
.pointer => prev_ptr_ty.castTag(.pointer).?.data.pointee_type,
else => return null,
};
if (prev_ptr_child_ty.zigTypeTag() == .Vector) break prev_ptr;
ptr_inst = Air.refToIndex(prev_ptr) orelse return null;
} else return null;
// We have a pointer-to-array and a pointer-to-vector. If the elements and
// lengths match, return the result.
const vector_ty = sema.typeOf(prev_ptr).childType();
if (array_ty.childType().eql(vector_ty.childType()) and
array_ty.arrayLen() == vector_ty.vectorLen())
{
return prev_ptr;
} else {
return null;
}
}
/// Call when you have Value objects rather than Air instructions, and you want to
/// assert the store must be done at comptime.
fn storePtrVal(

View File

@ -3931,15 +3931,12 @@ pub const Value = extern union {
},
80 => {
if (true) {
@panic("TODO implement compiler_rt fabs for f80");
@panic("TODO implement compiler_rt fabs for f80 (__fabsx)");
}
const f = val.toFloat(f80);
return Value.Tag.float_80.create(arena, @fabs(f));
},
128 => {
if (true) {
@panic("TODO implement compiler_rt fabs for f128");
}
const f = val.toFloat(f128);
return Value.Tag.float_128.create(arena, @fabs(f));
},
@ -3963,15 +3960,12 @@ pub const Value = extern union {
},
80 => {
if (true) {
@panic("TODO implement compiler_rt floor for f80");
@panic("TODO implement compiler_rt floor for f80 (__floorx)");
}
const f = val.toFloat(f80);
return Value.Tag.float_80.create(arena, @floor(f));
},
128 => {
if (true) {
@panic("TODO implement compiler_rt floor for f128");
}
const f = val.toFloat(f128);
return Value.Tag.float_128.create(arena, @floor(f));
},
@ -4001,9 +3995,6 @@ pub const Value = extern union {
return Value.Tag.float_80.create(arena, @ceil(f));
},
128 => {
if (true) {
@panic("TODO implement compiler_rt ceil for f128");
}
const f = val.toFloat(f128);
return Value.Tag.float_128.create(arena, @ceil(f));
},
@ -4033,9 +4024,6 @@ pub const Value = extern union {
return Value.Tag.float_80.create(arena, @round(f));
},
128 => {
if (true) {
@panic("TODO implement compiler_rt round for f128");
}
const f = val.toFloat(f128);
return Value.Tag.float_128.create(arena, @round(f));
},
@ -4065,9 +4053,6 @@ pub const Value = extern union {
return Value.Tag.float_80.create(arena, @trunc(f));
},
128 => {
if (true) {
@panic("TODO implement compiler_rt trunc for f128");
}
const f = val.toFloat(f128);
return Value.Tag.float_128.create(arena, @trunc(f));
},

View File

@ -95,8 +95,29 @@ test "comptime_int @intToFloat" {
}
}
test "@intToFloat" {
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_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
try testIntToFloat(-2);
}
fn testIntToFloat(k: i32) !void {
const f = @intToFloat(f32, k);
const i = @floatToInt(i32, f);
try expect(i == k);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "@floatToInt" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
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_arm) return error.SkipZigTest; // TODO
@ -1007,8 +1028,6 @@ test "peer type resolve array pointer and unknown pointer" {
}
test "comptime float casts" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
const a = @intToFloat(comptime_float, 1);
try expect(a == 1);
try expect(@TypeOf(a) == comptime_float);

View File

@ -333,7 +333,6 @@ fn testLog() !void {
}
test "@log with vectors" {
if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO
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
@ -343,15 +342,19 @@ test "@log with vectors" {
{
var v: @Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 };
var result = @log(v);
try expect(math.approxEqAbs(f32, @log(@as(f32, 1.1)), result[0], epsilon));
try expect(math.approxEqAbs(f32, @log(@as(f32, 2.2)), result[1], epsilon));
try expect(@log(@as(f32, 1.1)) == result[0]);
try expect(@log(@as(f32, 2.2)) == result[1]);
try expect(@log(@as(f32, 0.3)) == result[2]);
try expect(math.approxEqAbs(f32, @log(@as(f32, 0.4)), result[3], epsilon));
try expect(@log(@as(f32, 0.4)) == result[3]);
}
}
test "@log2" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
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
comptime try testLog2();
try testLog2();
@ -368,15 +371,19 @@ fn testLog2() !void {
{
var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 };
var result = @log2(v);
try expect(math.approxEqAbs(f32, @log2(@as(f32, 1.1)), result[0], epsilon));
try expect(math.approxEqAbs(f32, @log2(@as(f32, 2.2)), result[1], epsilon));
try expect(math.approxEqAbs(f32, @log2(@as(f32, 0.3)), result[2], epsilon));
try expect(math.approxEqAbs(f32, @log2(@as(f32, 0.4)), result[3], epsilon));
try expect(@log2(@as(f32, 1.1)) == result[0]);
try expect(@log2(@as(f32, 2.2)) == result[1]);
try expect(@log2(@as(f32, 0.3)) == result[2]);
try expect(@log2(@as(f32, 0.4)) == result[3]);
}
}
test "@log10" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
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
comptime try testLog10();
try testLog10();
@ -393,10 +400,10 @@ fn testLog10() !void {
{
var v: Vector(4, f32) = [_]f32{ 1.1, 2.2, 0.3, 0.4 };
var result = @log10(v);
try expect(math.approxEqAbs(f32, @log10(@as(f32, 1.1)), result[0], epsilon));
try expect(math.approxEqAbs(f32, @log10(@as(f32, 2.2)), result[1], epsilon));
try expect(math.approxEqAbs(f32, @log10(@as(f32, 0.3)), result[2], epsilon));
try expect(math.approxEqAbs(f32, @log10(@as(f32, 0.4)), result[3], epsilon));
try expect(@log10(@as(f32, 1.1)) == result[0]);
try expect(@log10(@as(f32, 2.2)) == result[1]);
try expect(@log10(@as(f32, 0.3)) == result[2]);
try expect(@log10(@as(f32, 0.4)) == result[3]);
}
}
@ -537,7 +544,71 @@ fn testTrunc() !void {
}
}
test "negation" {
test "negation f16" {
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
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.os.tag == .freebsd) {
// TODO file issue to track this failure
return error.SkipZigTest;
}
const S = struct {
fn doTheTest() !void {
var a: f16 = 1;
a = -a;
try expect(a == -1);
a = -a;
try expect(a == 1);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "negation f32" {
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_c) return error.SkipZigTest; // TODO
const S = struct {
fn doTheTest() !void {
var a: f32 = 1;
a = -a;
try expect(a == -1);
a = -a;
try expect(a == 1);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
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
const S = struct {
fn doTheTest() !void {
var a: f64 = 1;
a = -a;
try expect(a == -1);
a = -a;
try expect(a == 1);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "negation f80" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
if (builtin.os.tag == .freebsd) {
@ -547,11 +618,37 @@ test "negation" {
const S = struct {
fn doTheTest() !void {
inline for ([_]type{ f16, f32, f64, f80, f128 }) |T| {
var a: T = 1;
a = -a;
try expect(a == -1);
}
var a: f80 = 1;
a = -a;
try expect(a == -1);
a = -a;
try expect(a == 1);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "negation f128" {
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_c) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.os.tag == .freebsd) {
// TODO file issue to track this failure
return error.SkipZigTest;
}
const S = struct {
fn doTheTest() !void {
var a: f128 = 1;
a = -a;
try expect(a == -1);
a = -a;
try expect(a == 1);
}
};
@ -583,7 +680,13 @@ test "float literal at compile time not lossy" {
}
test "f128 at compile time is lossy" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
if (builtin.zig_backend != .stage1) {
// this one is happening because we represent comptime-known f128 integers with
// Value.Tag.bigint and only convert to f128 representation if it stops being an
// integer. Is this something we want? need to have a lang spec discussion on this
// topic.
return error.SkipZigTest; // TODO
}
try expect(@as(f128, 10384593717069655257060992658440192.0) + 1 == 10384593717069655257060992658440192.0);
}

View File

@ -6,7 +6,6 @@ const expectEqualSlices = std.testing.expectEqualSlices;
const maxInt = std.math.maxInt;
const minInt = std.math.minInt;
const mem = std.mem;
const has_f80_rt = builtin.cpu.arch == .x86_64;
test "assignment operators" {
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
@ -1046,12 +1045,14 @@ fn testSqrt(comptime T: type, x: T) !void {
}
test "@fabs" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
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_x86_64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
try testFabs(f128, 12.0);
comptime try testFabs(f128, 12.0);
if (has_f80_rt) try testFabs(f80, 12.0);
// comptime try testFabs(f80, 12.0);
try testFabs(f64, 12.0);
comptime try testFabs(f64, 12.0);
try testFabs(f32, 12.0);
@ -1065,20 +1066,25 @@ test "@fabs" {
comptime try expectEqual(x, z);
}
test "@fabs f80" {
if (true) {
// https://github.com/ziglang/zig/issues/11030
return error.SkipZigTest;
}
try testFabs(f80, 12.0);
comptime try testFabs(f80, 12.0);
}
fn testFabs(comptime T: type, x: T) !void {
const y = -x;
const z = @fabs(y);
try expectEqual(x, z);
try expect(x == z);
}
test "@floor" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
// FIXME: Generates a floorl function call
// testFloor(f128, 12.0);
comptime try testFloor(f128, 12.0);
// try testFloor(f80, 12.0);
comptime try testFloor(f80, 12.0);
try testFloor(f64, 12.0);
comptime try testFloor(f64, 12.0);
try testFloor(f32, 12.0);
@ -1089,23 +1095,39 @@ test "@floor" {
const x = 14.0;
const y = x + 0.7;
const z = @floor(y);
comptime try expectEqual(x, z);
comptime try expect(x == z);
}
test "@floor f80" {
if (true) {
// https://github.com/ziglang/zig/issues/11030
return error.SkipZigTest;
}
try testFloor(f80, 12.0);
comptime try testFloor(f80, 12.0);
}
test "@floor f128" {
if (builtin.zig_backend == .stage1) {
// Fails because it incorrectly lowers to a floorl function call.
return error.SkipZigTest;
}
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
testFloor(f128, 12.0);
comptime try testFloor(f128, 12.0);
}
fn testFloor(comptime T: type, x: T) !void {
const y = x + 0.6;
const z = @floor(y);
try expectEqual(x, z);
try expect(x == z);
}
test "@ceil" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
// FIXME: Generates a ceill function call
//testCeil(f128, 12.0);
comptime try testCeil(f128, 12.0);
// try testCeil(f80, 12.0);
comptime try testCeil(f80, 12.0);
try testCeil(f64, 12.0);
comptime try testCeil(f64, 12.0);
try testCeil(f32, 12.0);
@ -1116,29 +1138,40 @@ test "@ceil" {
const x = 14.0;
const y = x - 0.7;
const z = @ceil(y);
comptime try expectEqual(x, z);
comptime try expect(x == z);
}
test "@ceil f80" {
if (true) {
// https://github.com/ziglang/zig/issues/11030
return error.SkipZigTest;
}
try testCeil(f80, 12.0);
comptime try testCeil(f80, 12.0);
}
test "@ceil f128" {
if (builtin.zig_backend == .stage1) {
// Fails because it incorrectly lowers to a ceill function call.
return error.SkipZigTest;
}
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
testCeil(f128, 12.0);
comptime try testCeil(f128, 12.0);
}
fn testCeil(comptime T: type, x: T) !void {
const y = x - 0.8;
const z = @ceil(y);
try expectEqual(x, z);
try expect(x == z);
}
test "@trunc" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
// FIXME: Generates a truncl function call
//testTrunc(f128, 12.0);
comptime try testTrunc(f128, 12.0);
// try testTrunc(f80, 12.0);
// comptime try testTrunc(f80, 12.0);
comptime {
const x: f80 = 12.0;
const y = x + 0.8;
const z = @trunc(y);
try expectEqual(x, z);
}
try testTrunc(f64, 12.0);
comptime try testTrunc(f64, 12.0);
try testTrunc(f32, 12.0);
@ -1149,31 +1182,54 @@ test "@trunc" {
const x = 14.0;
const y = x + 0.7;
const z = @trunc(y);
comptime try expectEqual(x, z);
comptime try expect(x == z);
}
test "@trunc f80" {
if (true) {
// https://github.com/ziglang/zig/issues/11030
return error.SkipZigTest;
}
try testTrunc(f80, 12.0);
comptime try testTrunc(f80, 12.0);
comptime {
const x: f80 = 12.0;
const y = x + 0.8;
const z = @trunc(y);
try expect(x == z);
}
}
test "@trunc f128" {
if (builtin.zig_backend == .stage1) {
// Fails because it incorrectly lowers to a truncl function call.
return error.SkipZigTest;
}
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
testTrunc(f128, 12.0);
comptime try testTrunc(f128, 12.0);
}
fn testTrunc(comptime T: type, x: T) !void {
{
const y = x + 0.8;
const z = @trunc(y);
try expectEqual(x, z);
try expect(x == z);
}
{
const y = -x - 0.8;
const z = @trunc(y);
try expectEqual(-x, z);
try expect(-x == z);
}
}
test "@round" {
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
// FIXME: Generates a roundl function call
//testRound(f128, 12.0);
comptime try testRound(f128, 12.0);
// try testRound(f80, 12.0);
comptime try testRound(f80, 12.0);
try testRound(f64, 12.0);
comptime try testRound(f64, 12.0);
try testRound(f32, 12.0);
@ -1184,13 +1240,35 @@ test "@round" {
const x = 14.0;
const y = x + 0.4;
const z = @round(y);
comptime try expectEqual(x, z);
comptime try expect(x == z);
}
test "@round f80" {
if (true) {
// https://github.com/ziglang/zig/issues/11030
return error.SkipZigTest;
}
try testRound(f80, 12.0);
comptime try testRound(f80, 12.0);
}
test "@round f128" {
if (builtin.zig_backend == .stage1) {
// Fails because it incorrectly lowers to a roundl function call.
return error.SkipZigTest;
}
if (builtin.zig_backend != .stage1) return error.SkipZigTest; // TODO
testRound(f128, 12.0);
comptime try testRound(f128, 12.0);
}
fn testRound(comptime T: type, x: T) !void {
const y = x - 0.5;
const z = @round(y);
try expectEqual(x, z);
try expect(x == z);
}
test "vector integer addition" {
@ -1225,10 +1303,15 @@ test "NaN comparison" {
comptime try testNanEqNan(f32);
comptime try testNanEqNan(f64);
comptime try testNanEqNan(f128);
}
// TODO https://github.com/ziglang/zig/issues/11030
// try testNanEqNan(f80);
// comptime try testNanEqNan(f80);
test "NaN comparison f80" {
if (true) {
// https://github.com/ziglang/zig/issues/11030
return error.SkipZigTest;
}
try testNanEqNan(f80);
comptime try testNanEqNan(f80);
}
fn testNanEqNan(comptime F: type) !void {