Sema: fix coerceArrayLike() for vectors with padding

as explainded at https://llvm.org/docs/LangRef.html#vector-type :

"In general vector elements are laid out in memory in the same way as array types.
Such an analogy works fine as long as the vector elements are byte sized.
However, when the elements of the vector aren’t byte sized it gets a bit more complicated.
One way to describe the layout is by describing what happens when a vector such
as <N x iM> is bitcasted to an integer type with N*M bits, and then following the
rules for storing such an integer to memory."

"When <N*M> isn’t evenly divisible by the byte size the exact memory layout
is unspecified (just like it is for an integral type of the same size)."
This commit is contained in:
Xavier Bouchoux 2023-07-28 08:15:20 +02:00 committed by Andrew Kelley
parent d1dd5aeb07
commit c0fd64af03
2 changed files with 95 additions and 13 deletions

View file

@ -28115,6 +28115,50 @@ fn coerceInMemoryAllowed(
return .ok;
}
// Arrays <-> Vectors
if ((dest_tag == .Vector and src_tag == .Array) or
(dest_tag == .Array and src_tag == .Vector))
{
const dest_len = dest_ty.arrayLen(mod);
const src_len = src_ty.arrayLen(mod);
if (dest_len != src_len) {
return InMemoryCoercionResult{ .array_len = .{
.actual = src_len,
.wanted = dest_len,
} };
}
const dest_elem_ty = dest_ty.childType(mod);
const src_elem_ty = src_ty.childType(mod);
const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src);
if (child != .ok) {
return InMemoryCoercionResult{ .array_elem = .{
.child = try child.dupe(sema.arena),
.actual = src_elem_ty,
.wanted = dest_elem_ty,
} };
}
if (dest_tag == .Array) {
const dest_info = dest_ty.arrayInfo(mod);
if (dest_info.sentinel != null) {
return InMemoryCoercionResult{ .array_sentinel = .{
.actual = Value.@"unreachable",
.wanted = dest_info.sentinel.?,
.ty = dest_info.elem_type,
} };
}
}
// The memory layout of @Vector(N, iM) is the same as the integer type i(N*M),
// that is to say, the padding bits are not in the same place as the array [N]iM.
// If there's no padding, the bitcast is possible.
const elem_bit_size = dest_elem_ty.bitSize(mod);
const elem_abi_byte_size = dest_elem_ty.abiSize(mod);
if (elem_abi_byte_size * 8 == elem_bit_size)
return .ok;
}
// Optionals
if (dest_tag == .Optional and src_tag == .Optional) {
if ((maybe_dest_ptr_ty != null) != (maybe_src_ptr_ty != null)) {
@ -30005,10 +30049,22 @@ fn coerceArrayLike(
) !Air.Inst.Ref {
const mod = sema.mod;
const inst_ty = sema.typeOf(inst);
const inst_len = inst_ty.arrayLen(mod);
const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen(mod));
const target = mod.getTarget();
// try coercion of the whole array
const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src);
if (in_memory_result == .ok) {
if (try sema.resolveMaybeUndefVal(inst)) |inst_val| {
// These types share the same comptime value representation.
return sema.coerceInMemory(inst_val, dest_ty);
}
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addBitCast(dest_ty, inst);
}
// otherwise, try element by element
const inst_len = inst_ty.arrayLen(mod);
const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen(mod));
if (dest_len != inst_len) {
const msg = msg: {
const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{
@ -30023,17 +30079,6 @@ fn coerceArrayLike(
}
const dest_elem_ty = dest_ty.childType(mod);
const inst_elem_ty = inst_ty.childType(mod);
const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, false, target, dest_ty_src, inst_src);
if (in_memory_result == .ok) {
if (try sema.resolveMaybeUndefVal(inst)) |inst_val| {
// These types share the same comptime value representation.
return sema.coerceInMemory(inst_val, dest_ty);
}
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addBitCast(dest_ty, inst);
}
const element_vals = try sema.arena.alloc(InternPool.Index, dest_len);
const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len);
var runtime_src: ?LazySrcLoc = null;

View file

@ -176,6 +176,43 @@ test "array to vector" {
try comptime S.doTheTest();
}
test "array vector coercion - odd sizes" {
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;
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
const S = struct {
fn doTheTest() !void {
var foo1: i48 = 124578;
var vec1: @Vector(2, i48) = [2]i48{ foo1, 1 };
var arr1: [2]i48 = vec1;
try expect(vec1[0] == foo1 and vec1[1] == 1);
try expect(arr1[0] == foo1 and arr1[1] == 1);
var foo2: u4 = 5;
var vec2: @Vector(2, u4) = [2]u4{ foo2, 1 };
var arr2: [2]u4 = vec2;
try expect(vec2[0] == foo2 and vec2[1] == 1);
try expect(arr2[0] == foo2 and arr2[1] == 1);
var foo3: u13 = 13;
var vec3: @Vector(3, u13) = [3]u13{ foo3, 0, 1 };
var arr3: [3]u13 = vec3;
try expect(vec3[0] == foo3 and vec3[1] == 0 and vec3[2] == 1);
try expect(arr3[0] == foo3 and arr3[1] == 0 and arr3[2] == 1);
var arr4 = [4:0]u24{ foo3, foo2, 0, 1 };
var vec4: @Vector(4, u24) = arr4;
try expect(vec4[0] == foo3 and vec4[1] == foo2 and vec4[2] == 0 and vec4[3] == 1);
}
};
try S.doTheTest();
try comptime S.doTheTest();
}
test "array to vector with element type coercion" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_x86_64 and