| ... | ... | @@ -182106,68 +182106,135 @@ fn genSetMem( |
| 182106 | 182106 | } |
| 182107 | 182107 | } |
| 182108 | 182108 | |
| 182109 | | fn genInlineMemcpy(self: *CodeGen, dst_ptr: MCValue, src_ptr: MCValue, len: MCValue, opts: struct { |
| 182109 | fn genInlineMemcpy(cg: *CodeGen, dst_ptr: MCValue, src_ptr: MCValue, len: MCValue, opts: struct { |
| 182110 | 182110 | no_alias: bool, |
| 182111 | 182111 | }) InnerError!void { |
| 182112 | | if (opts.no_alias and dst_ptr.isAddress() and src_ptr.isAddress()) switch (len) { |
| 182113 | | else => {}, |
| 182114 | | .immediate => |len_imm| switch (len_imm) { |
| 182115 | | else => {}, |
| 182116 | | 1 => if (self.register_manager.tryAllocReg(null, abi.RegisterClass.gp)) |reg| { |
| 182117 | | try self.asmRegisterMemory(.{ ._, .mov }, reg.to8(), try src_ptr.deref().mem(self, .{ .size = .byte })); |
| 182118 | | try self.asmMemoryRegister(.{ ._, .mov }, try dst_ptr.deref().mem(self, .{ .size = .byte }), reg.to8()); |
| 182119 | | return; |
| 182120 | | }, |
| 182121 | | 2 => if (self.register_manager.tryAllocReg(null, abi.RegisterClass.gp)) |reg| { |
| 182122 | | try self.asmRegisterMemory(.{ ._, .mov }, reg.to16(), try src_ptr.deref().mem(self, .{ .size = .word })); |
| 182123 | | try self.asmMemoryRegister(.{ ._, .mov }, try dst_ptr.deref().mem(self, .{ .size = .word }), reg.to16()); |
| 182124 | | return; |
| 182125 | | }, |
| 182126 | | 4 => if (self.register_manager.tryAllocReg(null, abi.RegisterClass.gp)) |reg| { |
| 182127 | | try self.asmRegisterMemory(.{ ._, .mov }, reg.to32(), try src_ptr.deref().mem(self, .{ .size = .dword })); |
| 182128 | | try self.asmMemoryRegister(.{ ._, .mov }, try dst_ptr.deref().mem(self, .{ .size = .dword }), reg.to32()); |
| 182129 | | return; |
| 182130 | | }, |
| 182131 | | 8 => if (self.target.cpu.arch == .x86_64) { |
| 182132 | | if (self.register_manager.tryAllocReg(null, abi.RegisterClass.gp)) |reg| { |
| 182133 | | try self.asmRegisterMemory(.{ ._, .mov }, reg.to64(), try src_ptr.deref().mem(self, .{ .size = .qword })); |
| 182134 | | try self.asmMemoryRegister(.{ ._, .mov }, try dst_ptr.deref().mem(self, .{ .size = .qword }), reg.to64()); |
| 182135 | | return; |
| 182136 | | } |
| 182137 | | }, |
| 182138 | | 16 => if (self.hasFeature(.avx)) { |
| 182139 | | if (self.register_manager.tryAllocReg(null, abi.RegisterClass.sse)) |reg| { |
| 182140 | | try self.asmRegisterMemory(.{ .v_dqu, .mov }, reg.to128(), try src_ptr.deref().mem(self, .{ .size = .xword })); |
| 182141 | | try self.asmMemoryRegister(.{ .v_dqu, .mov }, try dst_ptr.deref().mem(self, .{ .size = .xword }), reg.to128()); |
| 182142 | | return; |
| 182143 | | } |
| 182144 | | } else if (self.hasFeature(.sse2)) { |
| 182145 | | if (self.register_manager.tryAllocReg(null, abi.RegisterClass.sse)) |reg| { |
| 182146 | | try self.asmRegisterMemory(.{ ._dqu, .mov }, reg.to128(), try src_ptr.deref().mem(self, .{ .size = .xword })); |
| 182147 | | try self.asmMemoryRegister(.{ ._dqu, .mov }, try dst_ptr.deref().mem(self, .{ .size = .xword }), reg.to128()); |
| 182148 | | return; |
| 182149 | | } |
| 182150 | | } else if (self.hasFeature(.sse)) { |
| 182151 | | if (self.register_manager.tryAllocReg(null, abi.RegisterClass.sse)) |reg| { |
| 182152 | | try self.asmRegisterMemory(.{ ._ps, .movu }, reg.to128(), try src_ptr.deref().mem(self, .{ .size = .xword })); |
| 182153 | | try self.asmMemoryRegister(.{ ._ps, .movu }, try dst_ptr.deref().mem(self, .{ .size = .xword }), reg.to128()); |
| 182154 | | return; |
| 182155 | | } |
| 182156 | | }, |
| 182157 | | 32 => if (self.hasFeature(.avx)) { |
| 182158 | | if (self.register_manager.tryAllocReg(null, abi.RegisterClass.sse)) |reg| { |
| 182159 | | try self.asmRegisterMemory(.{ .v_dqu, .mov }, reg.to256(), try src_ptr.deref().mem(self, .{ .size = .yword })); |
| 182160 | | try self.asmMemoryRegister(.{ .v_dqu, .mov }, try dst_ptr.deref().mem(self, .{ .size = .yword }), reg.to256()); |
| 182161 | | return; |
| 182162 | | } |
| 182163 | | }, |
| 182164 | | }, |
| 182112 | if (opts.no_alias and len == .immediate and cg.useConstMemcpyForSize(len.immediate)) { |
| 182113 | if (try cg.genConstMemcpy(dst_ptr, src_ptr, len.immediate)) { |
| 182114 | return; |
| 182115 | } |
| 182116 | } |
| 182117 | |
| 182118 | try cg.spillRegisters(&.{ .rsi, .rdi, .rcx }); |
| 182119 | try cg.genSetReg(.rsi, .usize, src_ptr, .{}); |
| 182120 | try cg.genSetReg(.rdi, .usize, dst_ptr, .{}); |
| 182121 | try cg.genSetReg(.rcx, .usize, len, .{}); |
| 182122 | try cg.asmOpOnly(.{ .@"rep _sb", .mov }); |
| 182123 | } |
| 182124 | |
| 182125 | /// Returns `true` iff it would be efficient to use `genConstMemcpy` for a copy |
| 182126 | /// of `len` bytes instead of using `rep movsb`. |
| 182127 | fn useConstMemcpyForSize(cg: *CodeGen, len: u64) bool { |
| 182128 | // LLVM's threshold here is 8 load/store pairs. However, after that threshold, it typically |
| 182129 | // calls through to `memcpy` instead of emitting `rep movsb`; if we don't have the `fsrm` CPU |
| 182130 | // feature, `rep movsb` has a high startup cost, so is inefficient for small copies. Since we |
| 182131 | // don't currently call compiler_rt in the case of longer copies, we use a higher threshold if |
| 182132 | // we don't have the `fsrm` feature. |
| 182133 | return switch (len) { |
| 182134 | 0...128 => true, |
| 182135 | 129...256 => cg.hasFeature(.avx) or !cg.hasFeature(.fsrm), // if we have AVX, each pair is 32 bytes |
| 182136 | 257...512 => !cg.hasFeature(.fsrm), // the threshold should probably be higher, but I'm being cautious |
| 182137 | else => false, |
| 182165 | 182138 | }; |
| 182166 | | try self.spillRegisters(&.{ .rsi, .rdi, .rcx }); |
| 182167 | | try self.genSetReg(.rsi, .usize, src_ptr, .{}); |
| 182168 | | try self.genSetReg(.rdi, .usize, dst_ptr, .{}); |
| 182169 | | try self.genSetReg(.rcx, .usize, len, .{}); |
| 182170 | | try self.asmOpOnly(.{ .@"rep _sb", .mov }); |
| 182139 | } |
| 182140 | /// If we can trivially copy `len` bytes from `orig_src_ptr` to `dst_ptr` using simple loads and |
| 182141 | /// stored through already-free register[s] (i.e. without spilling anything), emits that code and |
| 182142 | /// returns `true`. Otherwise, returns `false`, and the caller must lower the operation themselves, |
| 182143 | /// for instance with `rep movsb`. |
| 182144 | fn genConstMemcpy( |
| 182145 | cg: *CodeGen, |
| 182146 | dst_ptr: MCValue, |
| 182147 | orig_src_ptr: MCValue, |
| 182148 | len: u64, |
| 182149 | ) !bool { |
| 182150 | if (!dst_ptr.isAddress()) return false; |
| 182151 | |
| 182152 | const src_reg: ?Register = r: { |
| 182153 | if (orig_src_ptr.isAddress()) break :r null; |
| 182154 | if (orig_src_ptr == .lea_uav or orig_src_ptr == .lea_nav) { |
| 182155 | // To "hack around linker relocation bugs", a `lea_uav` isn't considered an address, but |
| 182156 | // we want to avoid pessimising that case because it's common (e.g. returning constant |
| 182157 | // values over 8 bytes). So if there's a register free, load the source address into it. |
| 182158 | if (cg.register_manager.tryAllocReg(null, abi.RegisterClass.gp)) |src_reg| { |
| 182159 | // We'll actually do the load a little later, in case another register alloc fails. |
| 182160 | break :r src_reg; |
| 182161 | } |
| 182162 | } |
| 182163 | return false; |
| 182164 | }; |
| 182165 | |
| 182166 | // Prevent `src_reg` from aliasing `gp_reg` below. |
| 182167 | const src_lock: ?RegisterLock = if (src_reg) |r| cg.register_manager.lockReg(r) else null; |
| 182168 | defer if (src_lock) |l| cg.register_manager.unlockReg(l); |
| 182169 | |
| 182170 | const want_sse_reg = len >= 16 and cg.hasFeature(.sse); |
| 182171 | const sse_reg: ?Register = if (want_sse_reg) r: { |
| 182172 | break :r cg.register_manager.tryAllocReg(null, abi.RegisterClass.sse); |
| 182173 | } else null; |
| 182174 | |
| 182175 | const need_gp_reg = len % 16 != 0 or sse_reg == null; |
| 182176 | const gp_reg: Register = if (need_gp_reg) r: { |
| 182177 | break :r cg.register_manager.tryAllocReg(null, abi.RegisterClass.gp) orelse return false; |
| 182178 | } else undefined; |
| 182179 | |
| 182180 | const src_ptr: MCValue = src_ptr: { |
| 182181 | const reg = src_reg orelse break :src_ptr orig_src_ptr; |
| 182182 | try cg.asmRegisterMemory(.{ ._, .lea }, reg.to64(), switch (orig_src_ptr) { |
| 182183 | .lea_uav => |uav| .{ .base = .{ .uav = uav } }, |
| 182184 | .lea_nav => |nav| .{ .base = .{ .nav = nav } }, |
| 182185 | else => unreachable, |
| 182186 | }); |
| 182187 | break :src_ptr .{ .register = reg.to64() }; |
| 182188 | }; |
| 182189 | |
| 182190 | var offset: u64 = 0; |
| 182191 | |
| 182192 | if (sse_reg) |r| { |
| 182193 | if (cg.hasFeature(.avx)) { |
| 182194 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ .v_dqu, .mov }, r, .yword); |
| 182195 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ .v_dqu, .mov }, r, .xword); |
| 182196 | } else if (cg.hasFeature(.sse2)) { |
| 182197 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ ._dqu, .mov }, r, .xword); |
| 182198 | } else if (cg.hasFeature(.sse)) { |
| 182199 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ ._ps, .movu }, r, .xword); |
| 182200 | } |
| 182201 | } |
| 182202 | |
| 182203 | if (need_gp_reg) { |
| 182204 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ ._, .mov }, gp_reg, .qword); |
| 182205 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ ._, .mov }, gp_reg, .dword); |
| 182206 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ ._, .mov }, gp_reg, .word); |
| 182207 | try cg.memcpyPart(dst_ptr, src_ptr, len, &offset, .{ ._, .mov }, gp_reg, .byte); |
| 182208 | } |
| 182209 | |
| 182210 | assert(offset == len); |
| 182211 | |
| 182212 | return true; |
| 182213 | } |
| 182214 | fn memcpyPart( |
| 182215 | cg: *CodeGen, |
| 182216 | dst_ptr: MCValue, |
| 182217 | src_ptr: MCValue, |
| 182218 | len: u64, |
| 182219 | offset: *u64, |
| 182220 | tag: Mir.Inst.FixedTag, |
| 182221 | temp_reg: Register, |
| 182222 | size: Memory.Size, |
| 182223 | ) !void { |
| 182224 | const bytes_len = @divExact(size.bitSize(cg.target), 8); |
| 182225 | while (len - offset.* >= bytes_len) { |
| 182226 | try cg.asmRegisterMemory( |
| 182227 | tag, |
| 182228 | temp_reg.toSize(size, cg.target), |
| 182229 | try src_ptr.deref().mem(cg, .{ .size = size, .disp = @intCast(offset.*) }), |
| 182230 | ); |
| 182231 | try cg.asmMemoryRegister( |
| 182232 | tag, |
| 182233 | try dst_ptr.deref().mem(cg, .{ .size = size, .disp = @intCast(offset.*) }), |
| 182234 | temp_reg.toSize(size, cg.target), |
| 182235 | ); |
| 182236 | offset.* += bytes_len; |
| 182237 | } |
| 182171 | 182238 | } |
| 182172 | 182239 | |
| 182173 | 182240 | fn genInlineMemset( |
| ... | ... | @@ -186834,10 +186901,8 @@ const Temp = struct { |
| 186834 | 186901 | }, |
| 186835 | 186902 | .memory, .indirect, .load_frame, .load_nav, .load_uav, .load_lazy_sym => { |
| 186836 | 186903 | var val_ptr = try cg.tempInit(.usize, val_mcv.address()); |
| 186837 | | var len = try cg.tempInit(.usize, .{ .immediate = val_ty.abiSize(cg.pt.zcu) }); |
| 186838 | | try val_ptr.memcpy(ptr, &len, cg); |
| 186904 | try val_ptr.memcpy(ptr, val_ty.abiSize(cg.pt.zcu), cg); |
| 186839 | 186905 | try val_ptr.die(cg); |
| 186840 | | try len.die(cg); |
| 186841 | 186906 | }, |
| 186842 | 186907 | } |
| 186843 | 186908 | return val; |
| ... | ... | @@ -186939,10 +187004,8 @@ const Temp = struct { |
| 186939 | 187004 | .lea_frame, .lea_nav, .lea_uav, .lea_lazy_sym => continue :val_to_gpr, |
| 186940 | 187005 | .memory, .indirect, .load_frame, .load_nav, .load_uav, .load_lazy_sym => { |
| 186941 | 187006 | var val_ptr = try cg.tempInit(.usize, val_mcv.address()); |
| 186942 | | var len = try cg.tempInit(.usize, .{ .immediate = val_ty.abiSize(cg.pt.zcu) }); |
| 186943 | | try ptr.memcpy(&val_ptr, &len, cg); |
| 187007 | try ptr.memcpy(&val_ptr, val_ty.abiSize(cg.pt.zcu), cg); |
| 186944 | 187008 | try val_ptr.die(cg); |
| 186945 | | try len.die(cg); |
| 186946 | 187009 | }, |
| 186947 | 187010 | } |
| 186948 | 187011 | break; |
| ... | ... | @@ -186981,11 +187044,9 @@ const Temp = struct { |
| 186981 | 187044 | var val_ptr = try cg.tempInit(.usize, val_mcv.address()); |
| 186982 | 187045 | var src_ptr = |
| 186983 | 187046 | try cg.tempInit(.usize, src.tracking(cg).short.address().offset(opts.disp)); |
| 186984 | | var len = try cg.tempInit(.usize, .{ .immediate = val_ty.abiSize(cg.pt.zcu) }); |
| 186985 | | try val_ptr.memcpy(&src_ptr, &len, cg); |
| 187047 | try val_ptr.memcpy(&src_ptr, val_ty.abiSize(cg.pt.zcu), cg); |
| 186986 | 187048 | try val_ptr.die(cg); |
| 186987 | 187049 | try src_ptr.die(cg); |
| 186988 | | try len.die(cg); |
| 186989 | 187050 | }, |
| 186990 | 187051 | } |
| 186991 | 187052 | } |
| ... | ... | @@ -187076,11 +187137,9 @@ const Temp = struct { |
| 187076 | 187137 | var dst_ptr = |
| 187077 | 187138 | try cg.tempInit(.usize, dst.tracking(cg).short.address().offset(opts.disp)); |
| 187078 | 187139 | var val_ptr = try cg.tempInit(.usize, val_mcv.address()); |
| 187079 | | var len = try cg.tempInit(.usize, .{ .immediate = val_ty.abiSize(cg.pt.zcu) }); |
| 187080 | | try dst_ptr.memcpy(&val_ptr, &len, cg); |
| 187140 | try dst_ptr.memcpy(&val_ptr, val_ty.abiSize(cg.pt.zcu), cg); |
| 187081 | 187141 | try dst_ptr.die(cg); |
| 187082 | 187142 | try val_ptr.die(cg); |
| 187083 | | try len.die(cg); |
| 187084 | 187143 | }, |
| 187085 | 187144 | } |
| 187086 | 187145 | break; |
| ... | ... | @@ -187182,10 +187241,8 @@ const Temp = struct { |
| 187182 | 187241 | try ptr.toOffset(deferred_disp, cg); |
| 187183 | 187242 | deferred_disp = 0; |
| 187184 | 187243 | var src_ptr = try cg.tempInit(.usize, .{ .lea_frame = .{ .index = frame_index } }); |
| 187185 | | var len = try cg.tempInit(.usize, .{ .immediate = src_abi_size }); |
| 187186 | | try ptr.memcpy(&src_ptr, &len, cg); |
| 187244 | try ptr.memcpy(&src_ptr, src_abi_size, cg); |
| 187187 | 187245 | try src_ptr.die(cg); |
| 187188 | | try len.die(cg); |
| 187189 | 187246 | } |
| 187190 | 187247 | part_disp += part_size; |
| 187191 | 187248 | deferred_disp += part_size; |
| ... | ... | @@ -187224,11 +187281,9 @@ const Temp = struct { |
| 187224 | 187281 | var dst_ptr = try cg.tempInit(.usize, dst.tracking(cg).short.address()); |
| 187225 | 187282 | try dst_ptr.toOffset(disp, cg); |
| 187226 | 187283 | var src_ptr = try cg.tempInit(.usize, .{ .lea_frame = .{ .index = frame_index } }); |
| 187227 | | var len = try cg.tempInit(.usize, .{ .immediate = src_abi_size }); |
| 187228 | | try dst_ptr.memcpy(&src_ptr, &len, cg); |
| 187284 | try dst_ptr.memcpy(&src_ptr, src_abi_size, cg); |
| 187229 | 187285 | try dst_ptr.die(cg); |
| 187230 | 187286 | try src_ptr.die(cg); |
| 187231 | | try len.die(cg); |
| 187232 | 187287 | } |
| 187233 | 187288 | } |
| 187234 | 187289 | |
| ... | ... | @@ -187273,11 +187328,21 @@ const Temp = struct { |
| 187273 | 187328 | assert(next_class_index == classes.len); |
| 187274 | 187329 | } |
| 187275 | 187330 | |
| 187276 | | fn memcpy(dst: *Temp, src: *Temp, len: *Temp, cg: *CodeGen) InnerError!void { |
| 187277 | | while (true) for ([_]*Temp{ dst, src, len }, [_]Register{ .rdi, .rsi, .rcx }) |temp, reg| { |
| 187278 | | if (try temp.toReg(reg, cg)) break; |
| 187279 | | } else break; |
| 187331 | fn memcpy(dst: *Temp, src: *Temp, len: u64, cg: *CodeGen) InnerError!void { |
| 187332 | if (cg.useConstMemcpyForSize(len)) { |
| 187333 | while (try dst.toLea(cg) or try src.toLea(cg)) {} // `genConstMemcpy` wants these values to be address operands |
| 187334 | if (try cg.genConstMemcpy(dst.tracking(cg).short, src.tracking(cg).short, len)) { |
| 187335 | return; |
| 187336 | } |
| 187337 | } |
| 187338 | var temp = try cg.tempAllocReg(.usize, abi.RegisterClass.gp); |
| 187339 | while (try dst.toReg(.rdi, cg) or |
| 187340 | try src.toReg(.rsi, cg) or |
| 187341 | try temp.toReg(.rcx, cg)) |
| 187342 | {} |
| 187343 | try cg.asmRegisterImmediate(.{ ._, .mov }, .rcx, .{ .unsigned = len }); |
| 187280 | 187344 | try cg.asmOpOnly(.{ .@"rep _sb", .mov }); |
| 187345 | try temp.die(cg); |
| 187281 | 187346 | } |
| 187282 | 187347 | |
| 187283 | 187348 | fn memset(dst: *Temp, val: *Temp, len: *Temp, cg: *CodeGen) InnerError!void { |