diff --git a/src/Air.zig b/src/Air.zig index 290123cee6..df4f861027 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -641,6 +641,8 @@ pub const Inst = struct { /// The element value may be undefined, in which case the destination /// memory region has undefined bytes after this function executes. In /// such case ignoring this instruction is legal lowering. + /// If the length is compile-time known (due to the destination being a + /// pointer-to-array), then it is guaranteed to be greater than zero. memset, /// Same as `memset`, except if the element value is undefined, the memory region /// should be filled with 0xaa bytes, and any other safety metadata such as Valgrind @@ -654,6 +656,9 @@ pub const Inst = struct { /// The two memory regions must not overlap. /// Result type is always void. /// Uses the `bin_op` field. LHS is the dest slice. RHS is the source pointer. + /// If the length is compile-time known (due to the destination or + /// source being a pointer-to-array), then it is guaranteed to be + /// greater than zero. memcpy, /// Uses the `ty_pl` field with payload `Cmpxchg`. diff --git a/src/Sema.zig b/src/Sema.zig index 1ed517ba8c..69b09f63ea 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -21918,8 +21918,6 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void } else break :rs src_src; } else dest_src; - try sema.requireRuntimeBlock(block, src, runtime_src); - const dest_ty = sema.typeOf(dest_ptr); const src_ty = sema.typeOf(src_ptr); @@ -21946,10 +21944,16 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void var new_src_ptr = src_ptr; if (len_val) |val| { const len = val.toUnsignedInt(target); + if (len == 0) { + // This AIR instruction guarantees length > 0 if it is comptime-known. + return; + } new_dest_ptr = try upgradeToArrayPtr(sema, block, dest_ptr, len); new_src_ptr = try upgradeToArrayPtr(sema, block, src_ptr, len); } + try sema.requireRuntimeBlock(block, src, runtime_src); + // Aliasing safety check. if (block.wantSafety()) { const dest_int = try block.addUnOp(.ptrtoint, new_dest_ptr); @@ -21995,13 +21999,18 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void const target = sema.mod.getTarget(); const runtime_src = if (try sema.resolveDefinedValue(block, dest_src, dest_ptr)) |ptr_val| rs: { + const len_air_ref = try sema.fieldVal(block, src, dest_ptr, "len", dest_src); + const len_val = (try sema.resolveDefinedValue(block, dest_src, len_air_ref)) orelse + break :rs dest_src; + const len_u64 = (try len_val.getUnsignedIntAdvanced(target, sema)).?; + const len = try sema.usizeCast(block, dest_src, len_u64); + if (len == 0) { + // This AIR instruction guarantees length > 0 if it is comptime-known. + return; + } + if (!ptr_val.isComptimeMutablePtr()) break :rs dest_src; if (try sema.resolveMaybeUndefVal(uncoerced_elem)) |_| { - const len_air_ref = try sema.fieldVal(block, src, dest_ptr, "len", dest_src); - const len_val = (try sema.resolveDefinedValue(block, dest_src, len_air_ref)) orelse - break :rs dest_src; - const len_u64 = (try len_val.getUnsignedIntAdvanced(target, sema)).?; - const len = try sema.usizeCast(block, dest_src, len_u64); for (0..len) |i| { const elem_index = try sema.addIntUnsigned(Type.usize, i); const elem_ptr = try sema.elemPtr( diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 5738a4fa87..7b63c28c9f 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -8175,23 +8175,62 @@ fn airMemset(self: *Self, inst: Air.Inst.Index, safety: bool) !void { }; defer if (src_val_lock) |lock| self.register_manager.unlockReg(lock); - if (elem_ty.abiSize(self.target.*) != 1) { - return self.fail("TODO implement airMemset when element ABI size > 1", .{}); + if (elem_ty.abiSize(self.target.*) == 1) { + const len = switch (dst_ptr_ty.ptrSize()) { + // TODO: this only handles slices stored in the stack + .Slice => @as(MCValue, .{ .stack_offset = dst_ptr.stack_offset - 8 }), + .One => @as(MCValue, .{ .immediate = dst_ptr_ty.childType().arrayLen() }), + .C, .Many => unreachable, + }; + const len_lock: ?RegisterLock = switch (len) { + .register => |reg| self.register_manager.lockRegAssumeUnused(reg), + else => null, + }; + defer if (len_lock) |lock| self.register_manager.unlockReg(lock); + + // TODO: dst_ptr could be a slice rather than raw pointer + try self.genInlineMemset(dst_ptr, src_val, len, .{}); + return self.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none }); } - const len = switch (dst_ptr_ty.ptrSize()) { - .Slice => @as(MCValue, .{ .stack_offset = dst_ptr.stack_offset - 8 }), - .One => @as(MCValue, .{ .immediate = dst_ptr_ty.childType().arrayLen() }), - .C, .Many => unreachable, - }; - const len_lock: ?RegisterLock = switch (len) { - .register => |reg| self.register_manager.lockRegAssumeUnused(reg), - else => null, - }; - defer if (len_lock) |lock| self.register_manager.unlockReg(lock); + // Store the first element, and then rely on memcpy copying forwards. + // Length zero requires a runtime check - so we handle arrays specially + // here to elide it. + switch (dst_ptr_ty.ptrSize()) { + .Slice => { + // TODO: this only handles slices stored in the stack + const ptr = @as(MCValue, .{ .stack_offset = dst_ptr.stack_offset - 0 }); + const len = @as(MCValue, .{ .stack_offset = dst_ptr.stack_offset - 8 }); + _ = ptr; + _ = len; + return self.fail("TODO implement airMemset for x86_64 with ABI size > 1 using a slice", .{}); + }, + .One => { + const len = dst_ptr_ty.childType().arrayLen(); + assert(len != 0); // prevented by Sema + try self.store(dst_ptr, src_val, dst_ptr_ty, elem_ty); - // TODO: dst_ptr could be a slice rather than raw pointer - try self.genInlineMemset(dst_ptr, src_val, len, .{}); + const second_elem_ptr_reg = try self.register_manager.allocReg(null, gp); + const second_elem_ptr_mcv: MCValue = .{ .register = second_elem_ptr_reg }; + const second_elem_ptr_lock = self.register_manager.lockRegAssumeUnused(second_elem_ptr_reg); + defer self.register_manager.unlockReg(second_elem_ptr_lock); + + const elem_abi_size = @intCast(u31, elem_ty.abiSize(self.target.*)); + + try self.asmRegisterMemory( + .lea, + second_elem_ptr_reg, + Memory.sib(.qword, .{ + .base = try self.copyToTmpRegister(Type.usize, dst_ptr), + .disp = elem_abi_size, + }), + ); + + const bytes_to_copy: MCValue = .{ .immediate = elem_abi_size * (len - 1) }; + try self.genInlineMemcpy(second_elem_ptr_mcv, dst_ptr, bytes_to_copy, .{}); + }, + .C, .Many => unreachable, + } return self.finishAir(inst, .unreach, .{ bin_op.lhs, bin_op.rhs, .none }); } diff --git a/test/behavior/basic.zig b/test/behavior/basic.zig index a59397d0b5..6bf6534213 100644 --- a/test/behavior/basic.zig +++ b/test/behavior/basic.zig @@ -361,10 +361,6 @@ test "@memset on array pointers" { // TODO: implement memset when element ABI size > 1 return error.SkipZigTest; } - if (builtin.zig_backend == .stage2_x86_64) { - // TODO: implement memset when element ABI size > 1 - return error.SkipZigTest; - } try testMemsetArray(); try comptime testMemsetArray();