You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2026/06/06 16:07:44 UTC

(groovy) 01/01: GROOVY-12065: Implement peephole optimization for bytecode generation

This is an automated email from the ASF dual-hosted git repository.

asf-gitbox-commits pushed a commit to branch GROOVY-12065
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit d4cb73bf05d4edb8ba18f4d815876bb5d45ae3b0
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Jun 7 01:06:59 2026 +0900

    GROOVY-12065: Implement peephole optimization for bytecode generation
---
 .../groovy/classgen/AsmClassGenerator.java         |  21 +-
 .../groovy/classgen/asm/BytecodeHelper.java        |   4 +
 .../codehaus/groovy/classgen/asm/OperandStack.java |  57 +-
 .../asm/PeepholeOptimizingMethodVisitor.java       | 738 +++++++++++++++++++++
 .../classgen/asm/BinaryOperationsTest.groovy       |  27 +-
 .../asm/PeepholeOptimizingMethodVisitorTest.groovy | 240 +++++++
 .../classgen/asm/sc/StaticCompilationTest.groovy   |   4 -
 .../asm/sc/StaticCompileComparisonTest.groovy      |   4 +-
 ...StaticCompileNullCompareOptimizationTest.groovy |   6 +-
 9 files changed, 1033 insertions(+), 68 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index 5a74c2cd24..fc7b9ffcd6 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -32,6 +32,7 @@ import org.codehaus.groovy.ast.FieldNode;
 import org.codehaus.groovy.ast.GenericsType;
 import org.codehaus.groovy.ast.InnerClassNode;
 import org.codehaus.groovy.ast.InterfaceHelperClassNode;
+import org.codehaus.groovy.ast.IntersectionTypeClassNode;
 import org.codehaus.groovy.ast.MethodNode;
 import org.codehaus.groovy.ast.ModuleNode;
 import org.codehaus.groovy.ast.PackageNode;
@@ -56,7 +57,6 @@ import org.codehaus.groovy.ast.expr.EmptyExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.FieldExpression;
 import org.codehaus.groovy.ast.expr.GStringExpression;
-import org.codehaus.groovy.ast.IntersectionTypeClassNode;
 import org.codehaus.groovy.ast.expr.LambdaExpression;
 import org.codehaus.groovy.ast.expr.ListExpression;
 import org.codehaus.groovy.ast.expr.MapEntryExpression;
@@ -103,6 +103,7 @@ import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter;
 import org.codehaus.groovy.classgen.asm.MopWriter;
 import org.codehaus.groovy.classgen.asm.OperandStack;
 import org.codehaus.groovy.classgen.asm.OptimizingStatementWriter;
+import org.codehaus.groovy.classgen.asm.PeepholeOptimizingMethodVisitor;
 import org.codehaus.groovy.classgen.asm.WriterController;
 import org.codehaus.groovy.classgen.asm.WriterControllerFactory;
 import org.codehaus.groovy.control.SourceUnit;
@@ -622,11 +623,15 @@ public class AsmClassGenerator extends ClassGenerator {
             receiver = parameters[0]; // non-static method or inner class ctor
             parameters = Arrays.copyOfRange(parameters, 1, parameters.length);
         }
-        MethodVisitor mv = classVisitor.visitMethod(
-                node.getModifiers() | (isVargs(parameters) ? ACC_VARARGS : 0), node.getName(),
-                BytecodeHelper.getMethodDescriptor(node.getReturnType(), parameters),
-                BytecodeHelper.getGenericsMethodSignature(node),
-                buildExceptions(node.getExceptions()));
+
+        MethodVisitor mv = new PeepholeOptimizingMethodVisitor(
+                                    classVisitor.visitMethod(
+                                        node.getModifiers() | (isVargs(parameters) ? ACC_VARARGS : 0),
+                                        node.getName(),
+                                        BytecodeHelper.getMethodDescriptor(node.getReturnType(), parameters),
+                                        BytecodeHelper.getGenericsMethodSignature(node),
+                                        buildExceptions(node.getExceptions())));
+
         controller.setMethodVisitor(mv);
         controller.resetLineNumber();
 
@@ -678,10 +683,10 @@ public class AsmClassGenerator extends ClassGenerator {
                 mv.visitMaxs(0, 0);
             } catch (Throwable t) {
                 Writer writer = null;
-                if (mv instanceof TraceMethodVisitor) {
+                if (mv instanceof TraceMethodVisitor tmv) {
                     writer = new StringBuilderWriter();
                     PrintWriter p = new PrintWriter(writer);
-                    ((TraceMethodVisitor) mv).p.print(p);
+                    tmv.p.print(p);
                     p.flush();
                 }
                 StringBuilder message = new StringBuilder(64);
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
index 4dc160afc8..1187065a13 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/BytecodeHelper.java
@@ -69,6 +69,7 @@ import static org.objectweb.asm.Opcodes.ICONST_2;
 import static org.objectweb.asm.Opcodes.ICONST_3;
 import static org.objectweb.asm.Opcodes.ICONST_4;
 import static org.objectweb.asm.Opcodes.ICONST_5;
+import static org.objectweb.asm.Opcodes.ICONST_M1;
 import static org.objectweb.asm.Opcodes.IFEQ;
 import static org.objectweb.asm.Opcodes.IFNE;
 import static org.objectweb.asm.Opcodes.ILOAD;
@@ -249,6 +250,9 @@ public class BytecodeHelper {
      */
     public static void pushConstant(MethodVisitor mv, int value) {
         switch (value) {
+          case -1:
+            mv.visitInsn(ICONST_M1);
+            break;
           case 0:
             mv.visitInsn(ICONST_0);
             break;
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java b/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java
index acd6f5d99f..4f46a57a1e 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/OperandStack.java
@@ -49,13 +49,10 @@ import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveShort;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveVoid;
 import static org.objectweb.asm.Opcodes.ACONST_NULL;
 import static org.objectweb.asm.Opcodes.ALOAD;
-import static org.objectweb.asm.Opcodes.BIPUSH;
 import static org.objectweb.asm.Opcodes.CHECKCAST;
 import static org.objectweb.asm.Opcodes.D2F;
 import static org.objectweb.asm.Opcodes.D2I;
 import static org.objectweb.asm.Opcodes.D2L;
-import static org.objectweb.asm.Opcodes.DCONST_0;
-import static org.objectweb.asm.Opcodes.DCONST_1;
 import static org.objectweb.asm.Opcodes.DUP;
 import static org.objectweb.asm.Opcodes.DUP2;
 import static org.objectweb.asm.Opcodes.DUP2_X1;
@@ -64,9 +61,6 @@ import static org.objectweb.asm.Opcodes.DUP_X2;
 import static org.objectweb.asm.Opcodes.F2D;
 import static org.objectweb.asm.Opcodes.F2I;
 import static org.objectweb.asm.Opcodes.F2L;
-import static org.objectweb.asm.Opcodes.FCONST_0;
-import static org.objectweb.asm.Opcodes.FCONST_1;
-import static org.objectweb.asm.Opcodes.FCONST_2;
 import static org.objectweb.asm.Opcodes.GETSTATIC;
 import static org.objectweb.asm.Opcodes.I2B;
 import static org.objectweb.asm.Opcodes.I2C;
@@ -74,15 +68,11 @@ import static org.objectweb.asm.Opcodes.I2D;
 import static org.objectweb.asm.Opcodes.I2F;
 import static org.objectweb.asm.Opcodes.I2L;
 import static org.objectweb.asm.Opcodes.I2S;
-import static org.objectweb.asm.Opcodes.ICONST_0;
-import static org.objectweb.asm.Opcodes.ICONST_1;
 import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
 import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
 import static org.objectweb.asm.Opcodes.L2D;
 import static org.objectweb.asm.Opcodes.L2F;
 import static org.objectweb.asm.Opcodes.L2I;
-import static org.objectweb.asm.Opcodes.LCONST_0;
-import static org.objectweb.asm.Opcodes.LCONST_1;
 import static org.objectweb.asm.Opcodes.NEW;
 import static org.objectweb.asm.Opcodes.POP;
 import static org.objectweb.asm.Opcodes.POP2;
@@ -165,11 +155,7 @@ public class OperandStack {
         MethodVisitor mv = controller.getMethodVisitor();
         if (mark == size) {
             // no element, so use emptyDefault
-            if (emptyDefault) {
-                mv.visitIntInsn(BIPUSH, 1);
-            } else {
-                mv.visitIntInsn(BIPUSH, 0);
-            }
+            mv.visitLdcInsn(emptyDefault ? 1 : 0);
             stack.add(null);
         } else if (mark == size - 1) {
             ClassNode last = stack.get(size - 1);
@@ -585,42 +571,11 @@ public class OperandStack {
         boolean isChar = isPrimitiveChar(type);
         if (isInt || isShort || isByte || isChar) {
             int val = isInt ? (Integer) value : isShort ? (Short) value : isChar ? (Character) value : (Byte) value;
-            BytecodeHelper.pushConstant(mv, val);
-        } else if (isPrimitiveLong(type)) {
-            if ((Long) value == 0L) {
-                mv.visitInsn(LCONST_0);
-            } else if ((Long) value == 1L) {
-                mv.visitInsn(LCONST_1);
-            } else {
-                mv.visitLdcInsn(value);
-            }
-        } else if (isPrimitiveFloat(type)) {
-            // GROOVY-9797: Use Float.equals to differentiate between positive and negative zero
-            if (value.equals(0f)) {
-                mv.visitInsn(FCONST_0);
-            } else if ((Float) value == 1f) {
-                mv.visitInsn(FCONST_1);
-            } else if ((Float) value == 2f) {
-                mv.visitInsn(FCONST_2);
-            } else {
-                mv.visitLdcInsn(value);
-            }
-        } else if (isPrimitiveDouble(type)) {
-            // GROOVY-9797: Use Double.equals to differentiate between positive and negative zero
-            if (value.equals(0d)) {
-                mv.visitInsn(DCONST_0);
-            } else if ((Double) value == 1d) {
-                mv.visitInsn(DCONST_1);
-            } else {
-                mv.visitLdcInsn(value);
-            }
+            mv.visitLdcInsn(val);
+        } else if (isPrimitiveLong(type) || isPrimitiveFloat(type) || isPrimitiveDouble(type)) {
+            mv.visitLdcInsn(value);
         } else if (isPrimitiveBoolean(type)) {
-            boolean b = (Boolean) value;
-            if (b) {
-                mv.visitInsn(ICONST_1);
-            } else {
-                mv.visitInsn(ICONST_0);
-            }
+            mv.visitLdcInsn((Boolean) value ? 1 : 0);
         } else {
             mv.visitLdcInsn(value);
         }
@@ -729,7 +684,7 @@ public class OperandStack {
      */
     public void pushBool(final boolean value) {
         MethodVisitor mv = controller.getMethodVisitor();
-        mv.visitInsn(value ? ICONST_1 : ICONST_0);
+        mv.visitLdcInsn(value ? 1 : 0);
         push(ClassHelper.boolean_TYPE);
     }
 
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/PeepholeOptimizingMethodVisitor.java b/src/main/java/org/codehaus/groovy/classgen/asm/PeepholeOptimizingMethodVisitor.java
new file mode 100644
index 0000000000..b4e3c4a94d
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/PeepholeOptimizingMethodVisitor.java
@@ -0,0 +1,738 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.classgen.asm;
+
+import org.codehaus.groovy.control.CompilerConfiguration;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ConstantDynamic;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.TypePath;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ASTORE;
+import static org.objectweb.asm.Opcodes.BIPUSH;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
+import static org.objectweb.asm.Opcodes.DCONST_0;
+import static org.objectweb.asm.Opcodes.DCONST_1;
+import static org.objectweb.asm.Opcodes.DLOAD;
+import static org.objectweb.asm.Opcodes.DSTORE;
+import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.DUP2;
+import static org.objectweb.asm.Opcodes.FCONST_0;
+import static org.objectweb.asm.Opcodes.FCONST_1;
+import static org.objectweb.asm.Opcodes.FCONST_2;
+import static org.objectweb.asm.Opcodes.FLOAD;
+import static org.objectweb.asm.Opcodes.FSTORE;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.ICONST_1;
+import static org.objectweb.asm.Opcodes.ICONST_2;
+import static org.objectweb.asm.Opcodes.ICONST_3;
+import static org.objectweb.asm.Opcodes.ICONST_4;
+import static org.objectweb.asm.Opcodes.ICONST_5;
+import static org.objectweb.asm.Opcodes.ICONST_M1;
+import static org.objectweb.asm.Opcodes.IFEQ;
+import static org.objectweb.asm.Opcodes.IFGE;
+import static org.objectweb.asm.Opcodes.IFGT;
+import static org.objectweb.asm.Opcodes.IFLE;
+import static org.objectweb.asm.Opcodes.IFLT;
+import static org.objectweb.asm.Opcodes.IFNE;
+import static org.objectweb.asm.Opcodes.IF_ICMPEQ;
+import static org.objectweb.asm.Opcodes.IF_ICMPGE;
+import static org.objectweb.asm.Opcodes.IF_ICMPGT;
+import static org.objectweb.asm.Opcodes.IF_ICMPLE;
+import static org.objectweb.asm.Opcodes.IF_ICMPLT;
+import static org.objectweb.asm.Opcodes.IF_ICMPNE;
+import static org.objectweb.asm.Opcodes.IINC;
+import static org.objectweb.asm.Opcodes.ILOAD;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.ISTORE;
+import static org.objectweb.asm.Opcodes.LCONST_0;
+import static org.objectweb.asm.Opcodes.LCONST_1;
+import static org.objectweb.asm.Opcodes.LLOAD;
+import static org.objectweb.asm.Opcodes.LSTORE;
+import static org.objectweb.asm.Opcodes.NEW;
+import static org.objectweb.asm.Opcodes.POP;
+import static org.objectweb.asm.Opcodes.POP2;
+import static org.objectweb.asm.Opcodes.PUTSTATIC;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.SIPUSH;
+
+/**
+ * Single-pass bytecode compaction inspired by Groovy++'s peephole adapters.
+ * The visitor only buffers the current stack-local candidate and flushes before
+ * labels, frames, debug metadata, and other non-local boundaries.
+ */
+public final class PeepholeOptimizingMethodVisitor extends MethodVisitor {
+
+    private static final String BIG_DECIMAL_TYPE = "java/math/BigDecimal";
+    private static final String BIG_INTEGER_TYPE = "java/math/BigInteger";
+    private static final String STRING_CTOR_DESCRIPTOR = "(Ljava/lang/String;)V";
+    private static final int NO_OPCODE = -1;
+
+    private enum PendingLoadKind {
+        NONE,
+        CONSTANT,
+        VARIABLE,
+        CHECKCAST
+    }
+
+    private enum PendingStoreKind {
+        NONE,
+        VARIABLE,
+        STATIC_FIELD
+    }
+
+    private PendingLoadKind pendingLoadKind = PendingLoadKind.NONE;
+    private Object pendingConstant;
+    private int pendingLoadOpcode = NO_OPCODE;
+    private int pendingLoadVar = NO_OPCODE;
+    private boolean pendingLoadHasIinc;
+    private int pendingLoadIncrement;
+    private String pendingCheckcastDescriptor;
+
+    private int pendingDupOpcode = NO_OPCODE;
+    private PendingStoreKind pendingStoreKind = PendingStoreKind.NONE;
+    private int pendingStoreOpcode = NO_OPCODE;
+    private int pendingStoreVar = NO_OPCODE;
+    private String pendingStoreOwner;
+    private String pendingStoreName;
+    private String pendingStoreDescriptor;
+
+    public PeepholeOptimizingMethodVisitor(final MethodVisitor delegate) {
+        super(CompilerConfiguration.ASM_API_VERSION, delegate);
+    }
+
+    @Override
+    public void visitAttribute(final Attribute attribute) {
+        flushPending();
+        super.visitAttribute(attribute);
+    }
+
+    @Override
+    public void visitFrame(final int type, final int numLocal, final Object[] local, final int numStack, final Object[] stack) {
+        flushPending();
+        super.visitFrame(type, numLocal, local, numStack, stack);
+    }
+
+    @Override
+    public void visitInsn(final int opcode) {
+        if (tryRemovePendingLoad(opcode) || tryDropPendingLoadOnReturn(opcode) || tryRemovePendingDupStore(opcode)) {
+            return;
+        }
+
+        flushPendingLoad();
+        if (opcode == DUP || opcode == DUP2) {
+            flushPendingDupStore();
+            pendingDupOpcode = opcode;
+            return;
+        }
+
+        flushPendingDupStore();
+        switch (opcode) {
+          case ACONST_NULL:
+            bufferConstant(null);
+            return;
+          case ICONST_M1:
+            bufferConstant(-1);
+            return;
+          case ICONST_0:
+            bufferConstant(0);
+            return;
+          case ICONST_1:
+            bufferConstant(1);
+            return;
+          case ICONST_2:
+            bufferConstant(2);
+            return;
+          case ICONST_3:
+            bufferConstant(3);
+            return;
+          case ICONST_4:
+            bufferConstant(4);
+            return;
+          case ICONST_5:
+            bufferConstant(5);
+            return;
+          case LCONST_0:
+            bufferConstant(0L);
+            return;
+          case LCONST_1:
+            bufferConstant(1L);
+            return;
+          case FCONST_0:
+            bufferConstant(0f);
+            return;
+          case FCONST_1:
+            bufferConstant(1f);
+            return;
+          case FCONST_2:
+            bufferConstant(2f);
+            return;
+          case DCONST_0:
+            bufferConstant(0d);
+            return;
+          case DCONST_1:
+            bufferConstant(1d);
+            return;
+          default:
+            super.visitInsn(opcode);
+        }
+    }
+
+    @Override
+    public void visitIntInsn(final int opcode, final int operand) {
+        flushPendingLoad();
+        flushPendingDupStore();
+        if (opcode == BIPUSH || opcode == SIPUSH) {
+            bufferConstant(operand);
+            return;
+        }
+        super.visitIntInsn(opcode, operand);
+    }
+
+    @Override
+    public void visitVarInsn(final int opcode, final int varIndex) {
+        flushPendingLoad();
+        if (pendingDupOpcode != NO_OPCODE && pendingStoreKind == PendingStoreKind.NONE && isStoreOpcode(opcode)) {
+            bufferVariableStore(opcode, varIndex);
+            return;
+        }
+
+        flushPendingDupStore();
+        if (isLoadOpcode(opcode)) {
+            bufferVariableLoad(opcode, varIndex);
+            return;
+        }
+        super.visitVarInsn(opcode, varIndex);
+    }
+
+    @Override
+    public void visitTypeInsn(final int opcode, final String descriptor) {
+        flushPendingLoad();
+        flushPendingDupStore();
+        if (opcode == CHECKCAST) {
+            bufferCheckcast(descriptor);
+            return;
+        }
+        super.visitTypeInsn(opcode, descriptor);
+    }
+
+    @Override
+    public void visitFieldInsn(final int opcode, final String owner, final String name, final String descriptor) {
+        flushPendingLoad();
+        if (pendingDupOpcode != NO_OPCODE && pendingStoreKind == PendingStoreKind.NONE && opcode == PUTSTATIC) {
+            bufferStaticStore(opcode, owner, name, descriptor);
+            return;
+        }
+
+        flushPendingDupStore();
+        super.visitFieldInsn(opcode, owner, name, descriptor);
+    }
+
+    @Override
+    public void visitMethodInsn(final int opcode, final String owner, final String name, final String descriptor, final boolean isInterface) {
+        flushPending();
+        super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+    }
+
+    @Override
+    public void visitInvokeDynamicInsn(final String name, final String descriptor, final Handle bootstrapMethodHandle, final Object... bootstrapMethodArguments) {
+        flushPending();
+        super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
+    }
+
+    @Override
+    public void visitJumpInsn(final int opcode, final Label label) {
+        flushPendingDupStore();
+        if (tryRewriteZeroCompare(opcode, label)) {
+            return;
+        }
+
+        flushPendingLoad();
+        super.visitJumpInsn(opcode, label);
+    }
+
+    @Override
+    public void visitLabel(final Label label) {
+        flushPending();
+        super.visitLabel(label);
+    }
+
+    @Override
+    public void visitLdcInsn(final Object value) {
+        flushPendingLoad();
+        flushPendingDupStore();
+        if (value instanceof ConstantDynamic) {
+            super.visitLdcInsn(value);
+            return;
+        }
+        bufferConstant(value);
+    }
+
+    @Override
+    public void visitIincInsn(final int varIndex, final int increment) {
+        if (pendingLoadKind == PendingLoadKind.VARIABLE
+                && pendingLoadOpcode == ILOAD
+                && pendingLoadVar == varIndex
+                && !pendingLoadHasIinc) {
+            pendingLoadHasIinc = true;
+            pendingLoadIncrement = increment;
+            return;
+        }
+
+        flushPending();
+        super.visitIincInsn(varIndex, increment);
+    }
+
+    @Override
+    public void visitTableSwitchInsn(final int min, final int max, final Label dflt, final Label... labels) {
+        flushPending();
+        super.visitTableSwitchInsn(min, max, dflt, labels);
+    }
+
+    @Override
+    public void visitLookupSwitchInsn(final Label dflt, final int[] keys, final Label[] labels) {
+        flushPending();
+        super.visitLookupSwitchInsn(dflt, keys, labels);
+    }
+
+    @Override
+    public void visitMultiANewArrayInsn(final String descriptor, final int dims) {
+        flushPending();
+        super.visitMultiANewArrayInsn(descriptor, dims);
+    }
+
+    @Override
+    public AnnotationVisitor visitInsnAnnotation(final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+        flushPending();
+        return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible);
+    }
+
+    @Override
+    public void visitTryCatchBlock(final Label start, final Label end, final Label handler, final String type) {
+        flushPending();
+        super.visitTryCatchBlock(start, end, handler, type);
+    }
+
+    @Override
+    public AnnotationVisitor visitTryCatchAnnotation(final int typeRef, final TypePath typePath, final String descriptor, final boolean visible) {
+        flushPending();
+        return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible);
+    }
+
+    @Override
+    public void visitLocalVariable(final String name, final String descriptor, final String signature, final Label start, final Label end, final int index) {
+        flushPending();
+        super.visitLocalVariable(name, descriptor, signature, start, end, index);
+    }
+
+    @Override
+    public AnnotationVisitor visitLocalVariableAnnotation(final int typeRef, final TypePath typePath, final Label[] start, final Label[] end, final int[] index, final String descriptor, final boolean visible) {
+        flushPending();
+        return super.visitLocalVariableAnnotation(typeRef, typePath, start, end, index, descriptor, visible);
+    }
+
+    @Override
+    public void visitLineNumber(final int line, final Label start) {
+        flushPending();
+        super.visitLineNumber(line, start);
+    }
+
+    @Override
+    public void visitMaxs(final int maxStack, final int maxLocals) {
+        flushPending();
+        super.visitMaxs(maxStack, maxLocals);
+    }
+
+    @Override
+    public void visitEnd() {
+        flushPending();
+        super.visitEnd();
+    }
+
+    private void flushPending() {
+        flushPendingLoad();
+        flushPendingDupStore();
+    }
+
+    private void flushPendingLoad() {
+        switch (pendingLoadKind) {
+          case CONSTANT:
+            emitConstant(pendingConstant);
+            break;
+          case VARIABLE:
+            super.visitVarInsn(pendingLoadOpcode, pendingLoadVar);
+            if (pendingLoadHasIinc) {
+                super.visitIincInsn(pendingLoadVar, pendingLoadIncrement);
+            }
+            break;
+          case CHECKCAST:
+            super.visitTypeInsn(CHECKCAST, pendingCheckcastDescriptor);
+            break;
+          case NONE:
+          default:
+        }
+        clearPendingLoad();
+    }
+
+    private void flushPendingDupStore() {
+        if (pendingDupOpcode == NO_OPCODE) {
+            return;
+        }
+
+        super.visitInsn(pendingDupOpcode);
+        if (pendingStoreKind != PendingStoreKind.NONE) {
+            emitPendingStore();
+        }
+        clearPendingDupStore();
+    }
+
+    private boolean tryRewriteZeroCompare(final int opcode, final Label label) {
+        if (!(pendingLoadKind == PendingLoadKind.CONSTANT && pendingConstant instanceof Integer intValue && intValue == 0)) {
+            return false;
+        }
+
+        int replacement = switch (opcode) {
+          case IF_ICMPEQ -> IFEQ;
+          case IF_ICMPNE -> IFNE;
+          case IF_ICMPGE -> IFGE;
+          case IF_ICMPGT -> IFGT;
+          case IF_ICMPLE -> IFLE;
+          case IF_ICMPLT -> IFLT;
+          default -> NO_OPCODE;
+        };
+        if (replacement == NO_OPCODE) {
+            return false;
+        }
+
+        clearPendingLoad();
+        super.visitJumpInsn(replacement, label);
+        return true;
+    }
+
+    private boolean tryRemovePendingLoad(final int opcode) {
+        if (pendingLoadKind == PendingLoadKind.NONE) {
+            return false;
+        }
+
+        int popSize = stackSizeForPop(opcode);
+        if (popSize == 0) {
+            return false;
+        }
+
+        if (pendingLoadKind == PendingLoadKind.CHECKCAST) {
+            if (opcode != POP) {
+                return false;
+            }
+            clearPendingLoad();
+            super.visitInsn(POP);
+            return true;
+        }
+
+        if (stackSizeForPendingLoad() != popSize) {
+            return false;
+        }
+
+        if (pendingLoadKind == PendingLoadKind.VARIABLE && pendingLoadHasIinc) {
+            super.visitIincInsn(pendingLoadVar, pendingLoadIncrement);
+        }
+        clearPendingLoad();
+        return true;
+    }
+
+    private boolean tryDropPendingLoadOnReturn(final int opcode) {
+        if (opcode != RETURN || pendingLoadKind == PendingLoadKind.NONE) {
+            return false;
+        }
+
+        if (pendingLoadKind == PendingLoadKind.VARIABLE && pendingLoadHasIinc) {
+            super.visitIincInsn(pendingLoadVar, pendingLoadIncrement);
+        }
+        clearPendingLoad();
+        super.visitInsn(RETURN);
+        return true;
+    }
+
+    private boolean tryRemovePendingDupStore(final int opcode) {
+        if (pendingStoreKind == PendingStoreKind.NONE) {
+            return false;
+        }
+
+        int popSize = stackSizeForPop(opcode);
+        if (popSize == 0 || stackSizeForDup() != popSize || stackSizeForPendingStore() != popSize) {
+            return false;
+        }
+
+        emitPendingStore();
+        clearPendingDupStore();
+        return true;
+    }
+
+    private void bufferConstant(final Object value) {
+        clearPendingLoad();
+        pendingLoadKind = PendingLoadKind.CONSTANT;
+        pendingConstant = value;
+    }
+
+    private void bufferVariableLoad(final int opcode, final int varIndex) {
+        clearPendingLoad();
+        pendingLoadKind = PendingLoadKind.VARIABLE;
+        pendingLoadOpcode = opcode;
+        pendingLoadVar = varIndex;
+    }
+
+    private void bufferCheckcast(final String descriptor) {
+        clearPendingLoad();
+        pendingLoadKind = PendingLoadKind.CHECKCAST;
+        pendingCheckcastDescriptor = descriptor;
+    }
+
+    private void bufferVariableStore(final int opcode, final int varIndex) {
+        pendingStoreKind = PendingStoreKind.VARIABLE;
+        pendingStoreOpcode = opcode;
+        pendingStoreVar = varIndex;
+    }
+
+    private void bufferStaticStore(final int opcode, final String owner, final String name, final String descriptor) {
+        pendingStoreKind = PendingStoreKind.STATIC_FIELD;
+        pendingStoreOpcode = opcode;
+        pendingStoreOwner = owner;
+        pendingStoreName = name;
+        pendingStoreDescriptor = descriptor;
+    }
+
+    private void emitPendingStore() {
+        switch (pendingStoreKind) {
+          case VARIABLE:
+            super.visitVarInsn(pendingStoreOpcode, pendingStoreVar);
+            break;
+          case STATIC_FIELD:
+            super.visitFieldInsn(pendingStoreOpcode, pendingStoreOwner, pendingStoreName, pendingStoreDescriptor);
+            break;
+          case NONE:
+          default:
+        }
+    }
+
+    private void clearPendingLoad() {
+        pendingLoadKind = PendingLoadKind.NONE;
+        pendingConstant = null;
+        pendingLoadOpcode = NO_OPCODE;
+        pendingLoadVar = NO_OPCODE;
+        pendingLoadHasIinc = false;
+        pendingLoadIncrement = 0;
+        pendingCheckcastDescriptor = null;
+    }
+
+    private void clearPendingDupStore() {
+        pendingDupOpcode = NO_OPCODE;
+        pendingStoreKind = PendingStoreKind.NONE;
+        pendingStoreOpcode = NO_OPCODE;
+        pendingStoreVar = NO_OPCODE;
+        pendingStoreOwner = null;
+        pendingStoreName = null;
+        pendingStoreDescriptor = null;
+    }
+
+    private int stackSizeForPendingLoad() {
+        return switch (pendingLoadKind) {
+          case VARIABLE -> stackSizeForLoadOpcode(pendingLoadOpcode);
+          case CONSTANT -> (pendingConstant instanceof Long || pendingConstant instanceof Double) ? 2 : 1;
+          case CHECKCAST -> 1;
+          case NONE -> 0;
+        };
+    }
+
+    private int stackSizeForDup() {
+        return switch (pendingDupOpcode) {
+          case DUP -> 1;
+          case DUP2 -> 2;
+          default -> 0;
+        };
+    }
+
+    private int stackSizeForPendingStore() {
+        return switch (pendingStoreKind) {
+          case VARIABLE -> stackSizeForStoreOpcode(pendingStoreOpcode);
+          case STATIC_FIELD -> stackSizeForFieldDescriptor(pendingStoreDescriptor);
+          case NONE -> 0;
+        };
+    }
+
+    private static int stackSizeForPop(final int opcode) {
+        return switch (opcode) {
+          case POP -> 1;
+          case POP2 -> 2;
+          default -> 0;
+        };
+    }
+
+    private static int stackSizeForLoadOpcode(final int opcode) {
+        return switch (opcode) {
+          case LLOAD, DLOAD -> 2;
+          case ILOAD, FLOAD, ALOAD -> 1;
+          default -> 0;
+        };
+    }
+
+    private static int stackSizeForStoreOpcode(final int opcode) {
+        return switch (opcode) {
+          case LSTORE, DSTORE -> 2;
+          case ISTORE, FSTORE, ASTORE -> 1;
+          default -> 0;
+        };
+    }
+
+    private static int stackSizeForFieldDescriptor(final String descriptor) {
+        char first = descriptor.charAt(0);
+        return (first == 'J' || first == 'D') ? 2 : 1;
+    }
+
+    private static boolean isLoadOpcode(final int opcode) {
+        return switch (opcode) {
+          case ILOAD, LLOAD, FLOAD, DLOAD, ALOAD -> true;
+          default -> false;
+        };
+    }
+
+    private static boolean isStoreOpcode(final int opcode) {
+        return switch (opcode) {
+          case ISTORE, LSTORE, FSTORE, DSTORE, ASTORE -> true;
+          default -> false;
+        };
+    }
+
+    private void emitConstant(final Object value) {
+        if (value == null) {
+            super.visitInsn(ACONST_NULL);
+            return;
+        }
+
+        if (value instanceof BigDecimal || value instanceof BigInteger) {
+            emitStringConstructedConstant(value);
+            return;
+        }
+
+        if (value instanceof Integer intValue) {
+            emitIntConstant(intValue);
+            return;
+        }
+
+        if (value instanceof Long longValue) {
+            emitLongConstant(longValue);
+            return;
+        }
+
+        if (value instanceof Float floatValue) {
+            emitFloatConstant(floatValue);
+            return;
+        }
+
+        if (value instanceof Double doubleValue) {
+            emitDoubleConstant(doubleValue);
+            return;
+        }
+
+        super.visitLdcInsn(value);
+    }
+
+    private void emitStringConstructedConstant(final Object value) {
+        String type = (value instanceof BigDecimal ? BIG_DECIMAL_TYPE : BIG_INTEGER_TYPE);
+        super.visitTypeInsn(NEW, type);
+        super.visitInsn(DUP);
+        super.visitLdcInsn(value.toString());
+        super.visitMethodInsn(INVOKESPECIAL, type, "<init>", STRING_CTOR_DESCRIPTOR, false);
+    }
+
+    private void emitIntConstant(final int value) {
+        switch (value) {
+          case -1:
+            super.visitInsn(ICONST_M1);
+            return;
+          case 0:
+            super.visitInsn(ICONST_0);
+            return;
+          case 1:
+            super.visitInsn(ICONST_1);
+            return;
+          case 2:
+            super.visitInsn(ICONST_2);
+            return;
+          case 3:
+            super.visitInsn(ICONST_3);
+            return;
+          case 4:
+            super.visitInsn(ICONST_4);
+            return;
+          case 5:
+            super.visitInsn(ICONST_5);
+            return;
+          default:
+        }
+
+        if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
+            super.visitIntInsn(BIPUSH, value);
+        } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
+            super.visitIntInsn(SIPUSH, value);
+        } else {
+            super.visitLdcInsn(value);
+        }
+    }
+
+    private void emitLongConstant(final long value) {
+        if (value == 0L) {
+            super.visitInsn(LCONST_0);
+        } else if (value == 1L) {
+            super.visitInsn(LCONST_1);
+        } else {
+            super.visitLdcInsn(value);
+        }
+    }
+
+    private void emitFloatConstant(final float value) {
+        int rawBits = Float.floatToRawIntBits(value);
+        if (rawBits == Float.floatToRawIntBits(0f)) {
+            super.visitInsn(FCONST_0);
+        } else if (rawBits == Float.floatToRawIntBits(1f)) {
+            super.visitInsn(FCONST_1);
+        } else if (rawBits == Float.floatToRawIntBits(2f)) {
+            super.visitInsn(FCONST_2);
+        } else {
+            super.visitLdcInsn(value);
+        }
+    }
+
+    private void emitDoubleConstant(final double value) {
+        long rawBits = Double.doubleToRawLongBits(value);
+        if (rawBits == Double.doubleToRawLongBits(0d)) {
+            super.visitInsn(DCONST_0);
+        } else if (rawBits == Double.doubleToRawLongBits(1d)) {
+            super.visitInsn(DCONST_1);
+        } else {
+            super.visitLdcInsn(value);
+        }
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/BinaryOperationsTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/BinaryOperationsTest.groovy
index 2e8f751783..cc857c5761 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/asm/BinaryOperationsTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/BinaryOperationsTest.groovy
@@ -87,7 +87,12 @@ final class BinaryOperationsTest extends AbstractBytecodeTestCase {
                     "ICONST_$it",
             ])
         }
-        [-1, 6,Byte.MIN_VALUE,Byte.MAX_VALUE].each {
+        assert compile("""\
+            int a = -1
+        """).hasStrictSequence([
+                "ICONST_M1",
+        ])
+        [6,Byte.MIN_VALUE,Byte.MAX_VALUE].each {
             assert compile("""\
                     int a = $it
                 """).hasStrictSequence([
@@ -110,6 +115,26 @@ final class BinaryOperationsTest extends AbstractBytecodeTestCase {
         }
     }
 
+    @Test
+    void testPrimitiveZeroConstants() {
+        assumeFalse(config.indyEnabled)
+        assert compile("""\
+            long a = 0L
+        """).hasStrictSequence([
+                'LCONST_0',
+        ])
+        assert compile("""\
+            float a = 0f
+        """).hasStrictSequence([
+                'FCONST_0',
+        ])
+        assert compile("""\
+            double a = 0d
+        """).hasStrictSequence([
+                'DCONST_0',
+        ])
+    }
+
     @Test
     void testCharXor() {
         assumeFalse(config.indyEnabled)
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/PeepholeOptimizingMethodVisitorTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/PeepholeOptimizingMethodVisitorTest.groovy
new file mode 100644
index 0000000000..9a38498d3c
--- /dev/null
+++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/PeepholeOptimizingMethodVisitorTest.groovy
@@ -0,0 +1,240 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.codehaus.groovy.classgen.asm
+
+import org.codehaus.groovy.control.CompilerConfiguration
+import org.junit.jupiter.api.Test
+import org.objectweb.asm.Label
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.AbstractInsnNode
+import org.objectweb.asm.tree.FieldInsnNode
+import org.objectweb.asm.tree.IincInsnNode
+import org.objectweb.asm.tree.IntInsnNode
+import org.objectweb.asm.tree.LdcInsnNode
+import org.objectweb.asm.tree.MethodInsnNode
+import org.objectweb.asm.tree.MethodNode
+import org.objectweb.asm.tree.TypeInsnNode
+import org.objectweb.asm.tree.VarInsnNode
+import org.objectweb.asm.util.Printer
+
+import java.math.BigDecimal
+import java.math.BigInteger
+
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC
+import static org.objectweb.asm.Opcodes.ACC_STATIC
+import static org.objectweb.asm.Opcodes.RETURN
+
+final class PeepholeOptimizingMethodVisitorTest {
+
+    @Test
+    void compactsNumericConstants() {
+        def instructions = collectInstructions {
+            visitLdcInsn(-1)
+            visitLdcInsn(5)
+            visitLdcInsn(6)
+            visitIntInsn(org.objectweb.asm.Opcodes.SIPUSH, 120)
+            visitLdcInsn(0L)
+            visitLdcInsn(1L)
+            visitLdcInsn(2L)
+            visitLdcInsn(0f)
+            visitLdcInsn(1f)
+            visitLdcInsn(2f)
+            visitLdcInsn(0d)
+            visitLdcInsn(1d)
+            visitVarInsn(Opcodes.DSTORE, 0)
+            visitInsn(RETURN)
+        }
+
+        assert instructions == [
+                'ICONST_M1',
+                'ICONST_5',
+                'BIPUSH 6',
+                'BIPUSH 120',
+                'LCONST_0',
+                'LCONST_1',
+                'LDC 2',
+                'FCONST_0',
+                'FCONST_1',
+                'FCONST_2',
+                'DCONST_0',
+                'DCONST_1',
+                'DSTORE 0',
+                'RETURN',
+        ]
+    }
+
+    @Test
+    void preservesSignedZeroConstants() {
+        def instructions = collectInsnNodes {
+            visitLdcInsn(-0.0f)
+            visitVarInsn(Opcodes.FSTORE, 0)
+            visitLdcInsn(-0.0d)
+            visitVarInsn(Opcodes.DSTORE, 1)
+            visitInsn(RETURN)
+        }
+
+        assert instructions[0] instanceof LdcInsnNode
+        assert instructions[0].cst.equals(-0.0f)
+        assert render(instructions[1]) == 'FSTORE 0'
+        assert instructions[2] instanceof LdcInsnNode
+        assert instructions[2].cst.equals(-0.0d)
+        assert render(instructions[3]) == 'DSTORE 1'
+        assert render(instructions[4]) == 'RETURN'
+    }
+
+    @Test
+    void rewritesIntegerComparisonsAgainstZero() {
+        def zeroComparisons = [
+                (Opcodes.IF_ICMPEQ): 'IFEQ',
+                (Opcodes.IF_ICMPNE): 'IFNE',
+                (Opcodes.IF_ICMPGE): 'IFGE',
+                (Opcodes.IF_ICMPGT): 'IFGT',
+                (Opcodes.IF_ICMPLE): 'IFLE',
+                (Opcodes.IF_ICMPLT): 'IFLT',
+        ]
+
+        zeroComparisons.each { opcode, expected ->
+            def label = new Label()
+            def instructions = collectInstructions('(I)V') {
+                visitVarInsn(Opcodes.ILOAD, 0)
+                visitLdcInsn(0)
+                visitJumpInsn(opcode, label)
+                visitInsn(RETURN)
+                visitLabel(label)
+                visitInsn(RETURN)
+            }
+
+            assert instructions == ['ILOAD 0', expected, 'RETURN', 'RETURN']
+        }
+    }
+
+    @Test
+    void removesRedundantLoadsAndConstantPops() {
+        def instructions = collectInstructions('(Ljava/lang/Object;J)V') {
+            visitVarInsn(Opcodes.ALOAD, 0)
+            visitInsn(Opcodes.POP)
+            visitVarInsn(Opcodes.LLOAD, 1)
+            visitInsn(Opcodes.POP2)
+            visitLdcInsn('unused')
+            visitInsn(Opcodes.POP)
+            visitInsn(RETURN)
+        }
+
+        assert instructions == ['RETURN']
+    }
+
+    @Test
+    void preservesIincWhenDroppingRedundantLoads() {
+        def instructions = collectInstructions('(I)V') {
+            visitVarInsn(Opcodes.ILOAD, 0)
+            visitIincInsn(0, 1)
+            visitInsn(Opcodes.POP)
+            visitInsn(RETURN)
+        }
+
+        assert instructions == ['IINC 0 1', 'RETURN']
+    }
+
+    @Test
+    void removesRedundantDupStorePops() {
+        def instructions = collectInstructions {
+            visitInsn(Opcodes.ICONST_1)
+            visitInsn(Opcodes.DUP)
+            visitVarInsn(Opcodes.ISTORE, 0)
+            visitInsn(Opcodes.POP)
+            visitLdcInsn(2L)
+            visitInsn(Opcodes.DUP2)
+            visitFieldInsn(Opcodes.PUTSTATIC, 'Owner', 'VALUE', 'J')
+            visitInsn(Opcodes.POP2)
+            visitInsn(RETURN)
+        }
+
+        assert instructions == [
+                'ICONST_1',
+                'ISTORE 0',
+                'LDC 2',
+                'PUTSTATIC Owner.VALUE : J',
+                'RETURN',
+        ]
+    }
+
+    @Test
+    void lowersBigNumberConstantsBeforeEmission() {
+        def instructions = collectInstructions {
+            visitLdcInsn(new BigDecimal('123.45'))
+            visitFieldInsn(Opcodes.PUTSTATIC, 'Owner', 'DECIMAL', 'Ljava/math/BigDecimal;')
+            visitLdcInsn(new BigInteger('42'))
+            visitFieldInsn(Opcodes.PUTSTATIC, 'Owner', 'INTEGER', 'Ljava/math/BigInteger;')
+            visitInsn(RETURN)
+        }
+
+        assert instructions == [
+                'NEW java/math/BigDecimal',
+                'DUP',
+                'LDC 123.45',
+                'INVOKESPECIAL java/math/BigDecimal.<init> (Ljava/lang/String;)V',
+                'PUTSTATIC Owner.DECIMAL : Ljava/math/BigDecimal;',
+                'NEW java/math/BigInteger',
+                'DUP',
+                'LDC 42',
+                'INVOKESPECIAL java/math/BigInteger.<init> (Ljava/lang/String;)V',
+                'PUTSTATIC Owner.INTEGER : Ljava/math/BigInteger;',
+                'RETURN',
+        ]
+    }
+
+    private static List<String> collectInstructions(String descriptor = '()V', @DelegatesTo(MethodVisitor) Closure emitter) {
+        collectInsnNodes(descriptor, emitter).collect { AbstractInsnNode insn -> render(insn) }
+    }
+
+    private static List<AbstractInsnNode> collectInsnNodes(String descriptor = '()V', @DelegatesTo(MethodVisitor) Closure emitter) {
+        def sink = new MethodNode(CompilerConfiguration.ASM_API_VERSION, ACC_PUBLIC | ACC_STATIC, 'sample', descriptor, null, null)
+        def visitor = new PeepholeOptimizingMethodVisitor(sink)
+        visitor.visitCode()
+        emitter.delegate = visitor
+        emitter.resolveStrategy = Closure.DELEGATE_FIRST
+        emitter.call()
+        visitor.visitMaxs(0, 0)
+        visitor.visitEnd()
+        sink.instructions.toArray()
+                .findAll { AbstractInsnNode insn -> insn.opcode >= 0 }
+    }
+
+    private static String render(AbstractInsnNode insn) {
+        switch (insn) {
+          case FieldInsnNode:
+            return "${Printer.OPCODES[insn.opcode]} ${insn.owner}.${insn.name} : ${insn.desc}"
+          case IincInsnNode:
+            return "IINC ${insn.var} ${insn.incr}"
+          case IntInsnNode:
+            return "${Printer.OPCODES[insn.opcode]} ${insn.operand}"
+          case LdcInsnNode:
+            return "LDC ${insn.cst}"
+          case MethodInsnNode:
+            return "${Printer.OPCODES[insn.opcode]} ${insn.owner}.${insn.name} ${insn.desc}"
+          case TypeInsnNode:
+            return "${Printer.OPCODES[insn.opcode]} ${insn.desc}"
+          case VarInsnNode:
+            return "${Printer.OPCODES[insn.opcode]} ${insn.var}"
+          default:
+            return Printer.OPCODES[insn.opcode]
+        }
+    }
+}
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompilationTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompilationTest.groovy
index f2450f94b3..832f9502ed 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompilationTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompilationTest.groovy
@@ -536,9 +536,7 @@ final class StaticCompilationTest extends AbstractBytecodeTestCase {
                 'ILOAD 1',
                 'BIPUSH 13',
                 'IADD',
-/*TODO*/        'DUP',
                 'ISTORE 1',
-/*TODO*/        'POP',
                 'L2',
                 'LINENUMBER 5',
                 'ILOAD 1',
@@ -558,9 +556,7 @@ final class StaticCompilationTest extends AbstractBytecodeTestCase {
                 'ILOAD 1',
                 'ILOAD 2',
                 'IADD',
-/*TODO*/        'DUP',
                 'ISTORE 1',
-/*TODO*/        'POP',
                 'L1',
                 'LINENUMBER 4',
                 'RETURN'
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileComparisonTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileComparisonTest.groovy
index 324977392f..eac814b2ca 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileComparisonTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileComparisonTest.groovy
@@ -71,7 +71,7 @@ final class StaticCompileComparisonTest extends AbstractBytecodeTestCase {
             }
         ''')
         assert bytecode.hasStrictSequence(
-                ['ALOAD','ARRAYLENGTH', 'ICONST_0','IF_ICMPLE']
+                ['ALOAD','ARRAYLENGTH', 'IFLE']
         )
     }
 
@@ -88,7 +88,7 @@ final class StaticCompileComparisonTest extends AbstractBytecodeTestCase {
             }
         ''')
         assert bytecode.hasStrictSequence(
-                ['ALOAD','ARRAYLENGTH', 'ICONST_0','IF_ICMPLE']
+                ['ALOAD','ARRAYLENGTH', 'IFLE']
         )
     }
 
diff --git a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy
index e87c3d04c2..eb73678bd9 100644
--- a/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy
+++ b/src/test/groovy/org/codehaus/groovy/classgen/asm/sc/StaticCompileNullCompareOptimizationTest.groovy
@@ -78,7 +78,8 @@ final class StaticCompileNullCompareOptimizationTest extends AbstractBytecodeTes
                 null == x
             }
         ''')
-        assert bytecode.hasStrictSequence(['ICONST_0', 'POP'])
+        assert !bytecode.hasSequence(['ICONST_0', 'POP'])
+        assert bytecode.hasSequence(['RETURN'])
     }
 
     @Test
@@ -89,7 +90,8 @@ final class StaticCompileNullCompareOptimizationTest extends AbstractBytecodeTes
                 x == null
             }
         ''')
-        assert bytecode.hasStrictSequence(['ICONST_0', 'POP'])
+        assert !bytecode.hasSequence(['ICONST_0', 'POP'])
+        assert bytecode.hasSequence(['RETURN'])
     }
 
     @Test