Re: [PATCH] x86-64: Add load-after-store elimination peephole optimization
Anthony Green <[email protected]>
| Newsgroups | gmane.lisp.steel-bank.devel |
|---|---|
| Message-ID | <CACxje59+_GUXrcJJtRm+Aow8=hCyjKQp7bdcck4=JcXWX77tzQ@mail.gmail.com> |
No, I don't think so. AG On Fri, Nov 7, 2025 at 8:45 PM Charles Zhang <[email protected]> wrote: > Would it be possible to do this as part of the cost model in register > allocation? There is a backend-independent move coalescing pass already. > > On Friday, November 7, 2025, 11:23 PM, Anthony Green <[email protected]> > wrote: > > When code stores a register to memory and immediately loads from the > same memory location, eliminate the redundant memory load by copying > from the source register instead. > > This optimization transforms: > MOV [memory], REG1 > MOV REG2, [memory] > > Into: > MOV [memory], REG1 > MOV REG2, REG1 ; faster and one byte shorter > > The pattern commonly appears in structure slot updates and list > manipulation code. During a full SBCL build, this optimization fires > approximately 19 times in src/code/seq.lisp alone. > > While modern CPUs have store-to-load forwarding, register-to-register > moves can be eliminated entirely via register renaming. > > Anthony > > --- > src/compiler/x86-64/insts.lisp | 16 ++++++++++++++++ > tests/x86-64-codegen.impure.lisp | 22 ++++++++++++++++++++++ > 2 files changed, 38 insertions(+) > > diff --git a/src/compiler/x86-64/insts.lisp > b/src/compiler/x86-64/insts.lisp > index d7dcfd624..8c69b2922 100644 > --- a/src/compiler/x86-64/insts.lisp > +++ b/src/compiler/x86-64/insts.lisp > @@ -3579,3 +3579,19 @@ > (add-stmt-labels stmt (stmt-labels next)) > (delete-stmt next) > stmt))) > + > +;;; Load after store elimination - Transform store-then-load from same > memory > +;;; location into a register-to-register move, eliminating the load. > +(defpattern "load after store elim" ((mov) (mov)) (stmt next) > + (binding* (((size1 dst1 src1) (parse-2-operands stmt)) > + ((size2 dst2 src2) (parse-2-operands next))) > + (when (and (eq size1 :qword) > + (eq size2 :qword) > + (gpr-tn-p src1) ; First MOV stores from register > + (gpr-tn-p dst2) ; Second MOV loads into register > + (ea-p dst1) ; First MOV stores to memory > + (ea-p src2) ; Second MOV loads from memory > + (ea= dst1 src2)) ; Same effective address > + ;; Transform the load to copy from the register instead > + (setf (stmt-operands next) `(,(encode-size-prefix size2) ,dst2 > ,src1)) > + next))) > diff --git a/tests/x86-64-codegen.impure.lisp > b/tests/x86-64-codegen.impure.lisp > index dbc2df515..73f78730c 100644 > --- a/tests/x86-64-codegen.impure.lisp > +++ b/tests/x86-64-codegen.impure.lisp > @@ -1383,3 +1383,25 @@ > > (with-test (:name :signed-vops) (test-signed)) > (with-test (:name :unsigned-vops) (test-unsigned)) > + > +(with-test (:name :load-after-store-elim) > + ;; Test that store-then-load from same memory location is optimized > + ;; to store-then-register-move, eliminating the redundant memory load. > + (let* ((f (checked-compile > + '(lambda (cons-cell new-car) > + (declare (optimize (speed 3) (safety 0))) > + (setf (car cons-cell) new-car) > + (car cons-cell)))) > + (lines (disassembly-lines f))) > + ;; Without optimization: MOV [mem], reg ; MOV reg, [mem] > + ;; With optimization: MOV [mem], reg ; MOV reg, reg > + ;; Check that after the store, we don't load from memory > + (let ((store-pos (position-if (lambda (line) > + (and (search "MOV [" line) > + (search "], " line))) > + lines))) > + (when store-pos > + (assert (notany (lambda (line) > + (and (search "MOV " line) > + (search ", [" line))) > + (nthcdr (1+ store-pos) lines))))))) > -- > > _______________________________________________ > Sbcl-devel mailing list > [email protected] > https://lists.sourceforge.net/lists/listinfo/sbcl-devel > > _______________________________________________ Sbcl-devel mailing list [email protected] https://lists.sourceforge.net/lists/listinfo/sbcl-devel