From 00b690540e561391d17c65e45f818db6be8fecec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 26 Apr 2023 12:49:32 -0700 Subject: [PATCH] llvm backend: fix lowering of memset The bitcast of ABI size 1 elements was problematic for some types. --- src/codegen/llvm.zig | 14 ++++- test/behavior.zig | 1 + test/behavior/basic.zig | 90 --------------------------- test/behavior/memset.zig | 128 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 92 deletions(-) create mode 100644 test/behavior/memset.zig diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 94f49e801d..e5bdec7526 100644 --- a/src/codegen/llvm.zig +++ b/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; diff --git a/test/behavior.zig b/test/behavior.zig index b6fe8d120f..c75e7bc788 100644 --- a/test/behavior.zig +++ b/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"); diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index 06b1fdda64..437b1b1373 100644 --- a/test/behavior/basic.zig +++ b/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)); diff --git a/test/behavior/memset.zig b/test/behavior/memset.zig new file mode 100644 index 0000000000..69add499f9 --- /dev/null +++ b/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'); +}