Commit 00b690540e

Andrew Kelley <andrew@ziglang.org>
2023-04-26 21:49:32
llvm backend: fix lowering of memset
The bitcast of ABI size 1 elements was problematic for some types.
1 parent fd6200e
Changed files (4)
src
codegen
test
src/codegen/llvm.zig
@@ -7939,11 +7939,15 @@ pub const FuncGen = struct {
         return self.builder.buildPtrToInt(operand_ptr, dest_llvm_ty, "");
     }
 
-    fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
+    fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !*llvm.Value {
         const ty_op = self.air.instructions.items(.data)[inst].ty_op;
         const operand_ty = self.air.typeOf(ty_op.operand);
         const inst_ty = self.air.typeOfIndex(inst);
         const operand = try self.resolveInst(ty_op.operand);
+        return self.bitCast(operand, operand_ty, inst_ty);
+    }
+
+    fn bitCast(self: *FuncGen, operand: *llvm.Value, operand_ty: Type, inst_ty: Type) !*llvm.Value {
         const operand_is_ref = isByRef(operand_ty);
         const result_is_ref = isByRef(inst_ty);
         const llvm_dest_ty = try self.dg.lowerType(inst_ty);
@@ -7954,6 +7958,12 @@ pub const FuncGen = struct {
             return operand;
         }
 
+        if (llvm_dest_ty.getTypeKind() == .Integer and
+            operand.typeOf().getTypeKind() == .Integer)
+        {
+            return self.builder.buildZExtOrBitCast(operand, llvm_dest_ty, "");
+        }
+
         if (operand_ty.zigTypeTag() == .Int and inst_ty.isPtrAtRuntime()) {
             return self.builder.buildIntToPtr(operand, llvm_dest_ty, "");
         }
@@ -8442,7 +8452,7 @@ pub const FuncGen = struct {
 
         if (elem_abi_size == 1) {
             // In this case we can take advantage of LLVM's intrinsic.
-            const fill_byte = self.builder.buildBitCast(value, u8_llvm_ty, "");
+            const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
             const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
             _ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, ptr_ty.isVolatilePtr());
             return null;
test/behavior/basic.zig
@@ -353,96 +353,6 @@ fn f2(x: bool) []const u8 {
     return (if (x) &fA else &fB)();
 }
 
-test "@memset on array pointers" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_wasm) {
-        // TODO: implement memset when element ABI size > 1
-        return error.SkipZigTest;
-    }
-
-    try testMemsetArray();
-    try comptime testMemsetArray();
-}
-
-fn testMemsetArray() !void {
-    {
-        // memset array to non-undefined, ABI size == 1
-        var foo: [20]u8 = undefined;
-        @memset(&foo, 'A');
-        try expect(foo[0] == 'A');
-        try expect(foo[11] == 'A');
-        try expect(foo[19] == 'A');
-    }
-    {
-        // memset array to non-undefined, ABI size > 1
-        var foo: [20]u32 = undefined;
-        @memset(&foo, 1234);
-        try expect(foo[0] == 1234);
-        try expect(foo[11] == 1234);
-        try expect(foo[19] == 1234);
-    }
-}
-
-test "@memset on slices" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_wasm) {
-        // TODO: implement memset when element ABI size > 1
-        // TODO: implement memset on slices
-        return error.SkipZigTest;
-    }
-
-    try testMemsetSlice();
-    try comptime testMemsetSlice();
-}
-
-fn testMemsetSlice() !void {
-    {
-        // memset slice to non-undefined, ABI size == 1
-        var array: [20]u8 = undefined;
-        var len = array.len;
-        var slice = array[0..len];
-        @memset(slice, 'A');
-        try expect(slice[0] == 'A');
-        try expect(slice[11] == 'A');
-        try expect(slice[19] == 'A');
-    }
-    {
-        // memset slice to non-undefined, ABI size > 1
-        var array: [20]u32 = undefined;
-        var len = array.len;
-        var slice = array[0..len];
-        @memset(slice, 1234);
-        try expect(slice[0] == 1234);
-        try expect(slice[11] == 1234);
-        try expect(slice[19] == 1234);
-    }
-}
-
-test "memcpy and memset intrinsics" {
-    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
-    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
-
-    try testMemcpyMemset();
-    try comptime testMemcpyMemset();
-}
-
-fn testMemcpyMemset() !void {
-    var foo: [20]u8 = undefined;
-    var bar: [20]u8 = undefined;
-
-    @memset(&foo, 'A');
-    @memcpy(&bar, &foo);
-
-    try expect(bar[0] == 'A');
-    try expect(bar[11] == 'A');
-    try expect(bar[19] == 'A');
-}
-
 test "variable is allowed to be a pointer to an opaque type" {
     var x: i32 = 1234;
     _ = hereIsAnOpaqueType(@ptrCast(*OpaqueA, &x));
test/behavior/memset.zig
@@ -0,0 +1,128 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const expect = std.testing.expect;
+
+test "@memset on array pointers" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_wasm) {
+        // TODO: implement memset when element ABI size > 1
+        return error.SkipZigTest;
+    }
+
+    try testMemsetArray();
+    try comptime testMemsetArray();
+}
+
+fn testMemsetArray() !void {
+    {
+        // memset array to non-undefined, ABI size == 1
+        var foo: [20]u8 = undefined;
+        @memset(&foo, 'A');
+        try expect(foo[0] == 'A');
+        try expect(foo[11] == 'A');
+        try expect(foo[19] == 'A');
+    }
+    {
+        // memset array to non-undefined, ABI size > 1
+        var foo: [20]u32 = undefined;
+        @memset(&foo, 1234);
+        try expect(foo[0] == 1234);
+        try expect(foo[11] == 1234);
+        try expect(foo[19] == 1234);
+    }
+}
+
+test "@memset on slices" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_wasm) {
+        // TODO: implement memset when element ABI size > 1
+        // TODO: implement memset on slices
+        return error.SkipZigTest;
+    }
+
+    try testMemsetSlice();
+    try comptime testMemsetSlice();
+}
+
+fn testMemsetSlice() !void {
+    {
+        // memset slice to non-undefined, ABI size == 1
+        var array: [20]u8 = undefined;
+        var len = array.len;
+        var slice = array[0..len];
+        @memset(slice, 'A');
+        try expect(slice[0] == 'A');
+        try expect(slice[11] == 'A');
+        try expect(slice[19] == 'A');
+    }
+    {
+        // memset slice to non-undefined, ABI size > 1
+        var array: [20]u32 = undefined;
+        var len = array.len;
+        var slice = array[0..len];
+        @memset(slice, 1234);
+        try expect(slice[0] == 1234);
+        try expect(slice[11] == 1234);
+        try expect(slice[19] == 1234);
+    }
+}
+
+test "memset with bool element" {
+    var buf: [5]bool = undefined;
+    @memset(&buf, true);
+    try expect(buf[2]);
+    try expect(buf[4]);
+}
+
+test "memset with 1-byte struct element" {
+    const S = struct { x: bool };
+    var buf: [5]S = undefined;
+    @memset(&buf, .{ .x = true });
+    try expect(buf[2].x);
+    try expect(buf[4].x);
+}
+
+test "memset with 1-byte array element" {
+    const A = [1]bool;
+    var buf: [5]A = undefined;
+    @memset(&buf, .{true});
+    try expect(buf[2][0]);
+    try expect(buf[4][0]);
+}
+
+test "memset with large array element" {
+    const A = [128]u64;
+    var buf: [5]A = undefined;
+    var runtime_known_element = [_]u64{0} ** 128;
+    @memset(&buf, runtime_known_element);
+    for (buf[0]) |elem| try expect(elem == 0);
+    for (buf[1]) |elem| try expect(elem == 0);
+    for (buf[2]) |elem| try expect(elem == 0);
+    for (buf[3]) |elem| try expect(elem == 0);
+    for (buf[4]) |elem| try expect(elem == 0);
+}
+
+test "memcpy and memset intrinsics" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
+
+    try testMemcpyMemset();
+    try comptime testMemcpyMemset();
+}
+
+fn testMemcpyMemset() !void {
+    var foo: [20]u8 = undefined;
+    var bar: [20]u8 = undefined;
+
+    @memset(&foo, 'A');
+    @memcpy(&bar, &foo);
+
+    try expect(bar[0] == 'A');
+    try expect(bar[11] == 'A');
+    try expect(bar[19] == 'A');
+}
test/behavior.zig
@@ -177,6 +177,7 @@ test {
     _ = @import("behavior/math.zig");
     _ = @import("behavior/maximum_minimum.zig");
     _ = @import("behavior/member_func.zig");
+    _ = @import("behavior/memset.zig");
     _ = @import("behavior/merge_error_sets.zig");
     _ = @import("behavior/muladd.zig");
     _ = @import("behavior/namespace_depends_on_compile_var.zig");