Hi Uros,
Thanks for the review. Here's version 2 of this patch implementing your
suggestions
to use any_QIreg_operand and QIreg_operand predicates. This revised patch has
been tested on x86_64-pc-linux-gnu with make bootstrap and make -k check, both
with and without --target_board=unix{-m32} with no new failures.
2026-05-13 Roger Sayle <[email protected]>
Uros Bizjak <[email protected]>
gcc/ChangeLog
PR target/32803
* config/i386/i386.md (peephole2): Don't transform xorl;movb into
movzb with -Oz.
(peephole2): Convert movl into xorl;movb (strict_low_part) with -Oz.
(peephole2): Likewise, convert movl into xorl;movb [abcd]h with -Oz.
gcc/testsuite/ChangeLog
PR target/32803
* gcc.target/i386/pr32803-2.c: New test case.
* gcc.target/i386/pr32803-3.c: Likewise.
Thanks again,
Roger
> -----Original Message-----
> From: Uros Bizjak <[email protected]>
> Sent: 03 May 2026 19:49
> To: Roger Sayle <[email protected]>
> Cc: GCC Patches <[email protected]>
> Subject: Re: [x86 PATCH] Shorter load immediate constants with -Oz
>
> On Sat, May 2, 2026 at 10:17 PM Roger Sayle <[email protected]>
> wrote:
> >
> >
> > This patch adds two peephole2 patterns to i386.md to decrease the size
> > of some inteer loads. These replace "movl $const, %eax" (5 bytes)
> > with "xorl %eax, %eax" followed by either "movb $const,%al" or "movb
> > $const,%ah" (together 4 bytes), for suitable constants and suitable
> > genral registers, when the flags register is dead.
> >
> > Ideally modern Intel and AMD prcoessors can recognize these sequences
> > during instruction decode (avoiding any partial register stall in the
> > same way they avoid the false dependence for the xorl), and internally
> > generate a single uop, treating these bytes like an alternate
> > instruction encoding.
> >
> >
> > This patch has been tested on x86_64-pc-linux-gnu with make bootstrap
> > and make -k check, both with and without --target_board=unix{-m32}
> > with no new failures. Ok for mainline?
> >
> >
> > 2026-05-02 Roger Sayle <[email protected]>
> >
> > gcc/ChangeLog
> > PR target/32803
> > * config/i386/i386.md (peephole2): Don't transform xorl;movb into
> > movzb with -Oz.
> > (peephole2): Convert movl into xorl;movb (strict_low_part) with -Oz.
> > (peephole2): Likewise, convert movl into xorl;movb [abcd]h with -Oz.
> >
> > gcc/testsuite/ChangeLog
> > PR target/32803
> > * gcc.target/i386/pr32803-2.c: New test case.
> > * gcc.target/i386/pr32803-3.c: Likewise.
>
> +;; With -Oz, convert mov eax,200 (5 bytes) to xor eax,eax; mov al,200
> +;; (4 bytes) when the flags register is dead.
> +(define_peephole2
> + [(set (match_operand:SWI48 0 "general_reg_operand")
>
> Please use any_QIreg_operand predicate here to avoid generating invalid QI
> registers on 32-bit targets.
>
> + (match_operand:SWI48 1 "const_int_operand"))]
> + "optimize_insn_for_size_p () && optimize_size > 1
> + && IN_RANGE (INTVAL (operands[1]), 128, 255)
> + && peep2_regno_dead_p (0, FLAGS_REG)"
> + [(parallel [(set (match_dup 0) (const_int 0))
> + (clobber (reg:CC FLAGS_REG))])
> + (set (strict_low_part (match_dup 2)) (match_dup 3))] {
> + operands[2] = gen_lowpart (QImode, operands[0]);
> + operands[3] = gen_int_mode (INTVAL (operands[1]), QImode);
> +})
> +
> +;; With -Oz, convert mov eax,512 (5 bytes) to xor eax,eax; mov ah,2 ;;
> +(4 bytes) when the flags register is dead.
> +(define_peephole2
> + [(set (match_operand:SWI48 0 "general_reg_operand")
>
> Please use QIreg_operand predicate here instead of QI_REGNO_P predicate in
> insn constraint.
>
> + (match_operand:SWI48 1 "const_int_operand"))]
> + "optimize_insn_for_size_p () && optimize_size > 1
> + && (INTVAL (operands[1]) & ~0xff00) == 0
> + && QI_REGNO_P (REGNO (operands[0]))
> + && peep2_regno_dead_p (0, FLAGS_REG)"
> + [(parallel [(set (match_dup 0) (const_int 0))
> + (clobber (reg:CC FLAGS_REG))])
> + (set (zero_extract:SWI48 (match_dup 0) (const_int 8) (const_int 8))
> + (match_dup 2))]
> + "operands[2] = gen_int_mode (INTVAL (operands[1]) >> 8, QImode);")
>
> Uros.
diff --git a/gcc/config/i386/i386.md b/gcc/config/i386/i386.md
index 648b9b1b951..a2579c9dbf5 100644
--- a/gcc/config/i386/i386.md
+++ b/gcc/config/i386/i386.md
@@ -4948,7 +4948,8 @@
"REGNO (operands[0]) == REGNO (operands[1])
&& (<SWI48:MODE>mode != SImode
|| !TARGET_ZERO_EXTEND_WITH_AND
- || !optimize_function_for_speed_p (cfun))"
+ || !optimize_function_for_speed_p (cfun))
+ && !(optimize_insn_for_size_p () && optimize_size > 1)"
[(set (match_dup 0) (zero_extend:SWI48 (match_dup 2)))])
;; Likewise, but preserving FLAGS_REG.
@@ -4961,6 +4962,36 @@
|| !TARGET_ZERO_EXTEND_WITH_AND
|| !optimize_function_for_speed_p (cfun))"
[(set (match_dup 0) (zero_extend:SWI48 (match_dup 2)))])
+
+;; With -Oz, convert mov eax,200 (5 bytes) to xor eax,eax; mov al,200
+;; (4 bytes) when the flags register is dead.
+(define_peephole2
+ [(set (match_operand:SWI48 0 "any_QIreg_operand")
+ (match_operand:SWI48 1 "const_int_operand"))]
+ "optimize_insn_for_size_p () && optimize_size > 1
+ && IN_RANGE (INTVAL (operands[1]), 128, 255)
+ && peep2_regno_dead_p (0, FLAGS_REG)"
+ [(parallel [(set (match_dup 0) (const_int 0))
+ (clobber (reg:CC FLAGS_REG))])
+ (set (strict_low_part (match_dup 2)) (match_dup 3))]
+{
+ operands[2] = gen_lowpart (QImode, operands[0]);
+ operands[3] = gen_int_mode (INTVAL (operands[1]), QImode);
+})
+
+;; With -Oz, convert mov eax,512 (5 bytes) to xor eax,eax; mov ah,2
+;; (4 bytes) when the flags register is dead.
+(define_peephole2
+ [(set (match_operand:SWI48 0 "QIreg_operand")
+ (match_operand:SWI48 1 "const_int_operand"))]
+ "optimize_insn_for_size_p () && optimize_size > 1
+ && (INTVAL (operands[1]) & ~0xff00) == 0
+ && peep2_regno_dead_p (0, FLAGS_REG)"
+ [(parallel [(set (match_dup 0) (const_int 0))
+ (clobber (reg:CC FLAGS_REG))])
+ (set (zero_extract:SWI48 (match_dup 0) (const_int 8) (const_int 8))
+ (match_dup 2))]
+ "operands[2] = gen_int_mode (INTVAL (operands[1]) >> 8, QImode);")
;; Sign extension instructions
diff --git a/gcc/testsuite/gcc.target/i386/pr32803-2.c
b/gcc/testsuite/gcc.target/i386/pr32803-2.c
new file mode 100644
index 00000000000..7fc8f5aa18e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr32803-2.c
@@ -0,0 +1,25 @@
+/* { dg-do compile } */
+/* { dg-options "-Oz" } */
+
+int foo()
+{
+ return 200;
+}
+
+long fool()
+{
+ return 200;
+}
+
+int bar()
+{
+ return 512;
+}
+
+long barl()
+{
+ return 512;
+}
+/* { dg-final { scan-assembler-times "xorl\[ \t\]*%eax, %eax" 4 } } */
+/* { dg-final { scan-assembler-times "movb\[ \t\]*\\\$-56, %al" 2 } } */
+/* { dg-final { scan-assembler-times "movb\[ \t\]*\\\$2, %ah" 2 } } */
diff --git a/gcc/testsuite/gcc.target/i386/pr32803-3.c
b/gcc/testsuite/gcc.target/i386/pr32803-3.c
new file mode 100644
index 00000000000..052c7b3c7ed
--- /dev/null
+++ b/gcc/testsuite/gcc.target/i386/pr32803-3.c
@@ -0,0 +1,24 @@
+/* { dg-do compile } */
+/* { dg-options "-Os" } */
+
+int foo()
+{
+ return 200;
+}
+
+long fool()
+{
+ return 200;
+}
+
+int bar()
+{
+ return 512;
+}
+
+long barl()
+{
+ return 512;
+}
+/* { dg-final { scan-assembler-times "movl\[\\t \]*\\\$200, %eax" 2 } } */
+/* { dg-final { scan-assembler-times "movl\[\\t \]*\\\$512, %eax" 2 } } */