Support Math.clamp

Bug: b/386403585
Change-Id: I0316e421eb2f82a8cdddb05eb727fdd75dd4fedf
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 178aa89..4ac57c6 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1936,6 +1936,41 @@
       addProvider(
           new MethodGenerator(
               method, BackportedMethods::CharacterMethods_toStringCodepoint, "toStringCodepoint"));
+
+      // int {Math.StrictMath}.clamp(long, int, int)
+      // long {Math.StrictMath}.clamp(long, long, long)
+      // double {Math.StrictMath}.clamp(double, double, double)
+      // float {Math.StrictMath}.clamp(float, float, float)
+      name = factory.createString("clamp");
+      for (DexType mathType : new DexType[] {factory.mathType, factory.strictMathType}) {
+        proto =
+            factory.createProto(
+                factory.intType, factory.longType, factory.intType, factory.intType);
+        method = factory.createMethod(mathType, proto, name);
+        addProvider(
+            new MethodGenerator(method, BackportedMethods::MathMethods_clampInt, "clampInt"));
+
+        proto =
+            factory.createProto(
+                factory.longType, factory.longType, factory.longType, factory.longType);
+        method = factory.createMethod(mathType, proto, name);
+        addProvider(
+            new MethodGenerator(method, BackportedMethods::MathMethods_clampLong, "clampLong"));
+
+        proto =
+            factory.createProto(
+                factory.doubleType, factory.doubleType, factory.doubleType, factory.doubleType);
+        method = factory.createMethod(mathType, proto, name);
+        addProvider(
+            new MethodGenerator(method, BackportedMethods::MathMethods_clampDouble, "clampDouble"));
+
+        proto =
+            factory.createProto(
+                factory.floatType, factory.floatType, factory.floatType, factory.floatType);
+        method = factory.createMethod(mathType, proto, name);
+        addProvider(
+            new MethodGenerator(method, BackportedMethods::MathMethods_clampFloat, "clampFloat"));
+      }
     }
 
     private void initializeAndroidUStreamMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index a1078a8..be409eb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -4799,6 +4799,547 @@
         ImmutableList.of());
   }
 
+  public static CfCode MathMethods_clampDouble(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        6,
+        6,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.DOUBLE, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Double;"),
+                    factory.createProto(factory.booleanType, factory.doubleType),
+                    factory.createString("isNaN")),
+                false),
+            new CfIf(IfType.EQ, ValueType.INT, label2),
+            label1,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstString(factory.createString("min is NaN")),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5},
+                    new FrameType[] {
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType(),
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType(),
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType()
+                    })),
+            new CfLoad(ValueType.DOUBLE, 4),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Double;"),
+                    factory.createProto(factory.booleanType, factory.doubleType),
+                    factory.createString("isNaN")),
+                false),
+            new CfIf(IfType.EQ, ValueType.INT, label4),
+            label3,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstString(factory.createString("max is NaN")),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5},
+                    new FrameType[] {
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType(),
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType(),
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType()
+                    })),
+            new CfLoad(ValueType.DOUBLE, 2),
+            new CfLoad(ValueType.DOUBLE, 4),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Double;"),
+                    factory.createProto(factory.intType, factory.doubleType, factory.doubleType),
+                    factory.createString("compare")),
+                false),
+            new CfIf(IfType.LE, ValueType.INT, label6),
+            label5,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfNew(factory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            new CfLoad(ValueType.DOUBLE, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.doubleType),
+                    factory.createString("append")),
+                false),
+            new CfConstString(factory.createString(" > ")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.stringType),
+                    factory.createString("append")),
+                false),
+            new CfLoad(ValueType.DOUBLE, 4),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.doubleType),
+                    factory.createString("append")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringType),
+                    factory.createString("toString")),
+                false),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label6,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5},
+                    new FrameType[] {
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType(),
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType(),
+                      FrameType.doubleType(),
+                      FrameType.doubleHighType()
+                    })),
+            new CfLoad(ValueType.DOUBLE, 4),
+            new CfLoad(ValueType.DOUBLE, 0),
+            new CfLoad(ValueType.DOUBLE, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType),
+                    factory.createString("max")),
+                false),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.doubleType, factory.doubleType, factory.doubleType),
+                    factory.createString("min")),
+                false),
+            new CfReturn(ValueType.DOUBLE),
+            label7),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode MathMethods_clampFloat(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    CfLabel label5 = new CfLabel();
+    CfLabel label6 = new CfLabel();
+    CfLabel label7 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        3,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.FLOAT, 1),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Float;"),
+                    factory.createProto(factory.booleanType, factory.floatType),
+                    factory.createString("isNaN")),
+                false),
+            new CfIf(IfType.EQ, ValueType.INT, label2),
+            label1,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstString(factory.createString("min is NaN")),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.floatType(), FrameType.floatType(), FrameType.floatType()
+                    })),
+            new CfLoad(ValueType.FLOAT, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Float;"),
+                    factory.createProto(factory.booleanType, factory.floatType),
+                    factory.createString("isNaN")),
+                false),
+            new CfIf(IfType.EQ, ValueType.INT, label4),
+            label3,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfConstString(factory.createString("max is NaN")),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label4,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.floatType(), FrameType.floatType(), FrameType.floatType()
+                    })),
+            new CfLoad(ValueType.FLOAT, 1),
+            new CfLoad(ValueType.FLOAT, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Float;"),
+                    factory.createProto(factory.intType, factory.floatType, factory.floatType),
+                    factory.createString("compare")),
+                false),
+            new CfIf(IfType.LE, ValueType.INT, label6),
+            label5,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfNew(factory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            new CfLoad(ValueType.FLOAT, 1),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.floatType),
+                    factory.createString("append")),
+                false),
+            new CfConstString(factory.createString(" > ")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.stringType),
+                    factory.createString("append")),
+                false),
+            new CfLoad(ValueType.FLOAT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.floatType),
+                    factory.createString("append")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringType),
+                    factory.createString("toString")),
+                false),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label6,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2},
+                    new FrameType[] {
+                      FrameType.floatType(), FrameType.floatType(), FrameType.floatType()
+                    })),
+            new CfLoad(ValueType.FLOAT, 2),
+            new CfLoad(ValueType.FLOAT, 0),
+            new CfLoad(ValueType.FLOAT, 1),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.floatType, factory.floatType, factory.floatType),
+                    factory.createString("max")),
+                false),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.floatType, factory.floatType, factory.floatType),
+                    factory.createString("min")),
+                false),
+            new CfReturn(ValueType.FLOAT),
+            label7),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode MathMethods_clampInt(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        6,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 3),
+            new CfIfCmp(IfType.LE, ValueType.INT, label2),
+            label1,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfNew(factory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.intType),
+                    factory.createString("append")),
+                false),
+            new CfConstString(factory.createString(" > ")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.stringType),
+                    factory.createString("append")),
+                false),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.intType),
+                    factory.createString("append")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringType),
+                    factory.createString("toString")),
+                false),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.longType(),
+                      FrameType.longHighType(),
+                      FrameType.intType(),
+                      FrameType.intType()
+                    })),
+            new CfLoad(ValueType.INT, 3),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfLoad(ValueType.LONG, 0),
+            new CfLoad(ValueType.INT, 2),
+            new CfNumberConversion(NumericType.INT, NumericType.LONG),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.longType, factory.longType, factory.longType),
+                    factory.createString("max")),
+                false),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.longType, factory.longType, factory.longType),
+                    factory.createString("min")),
+                false),
+            new CfNumberConversion(NumericType.LONG, NumericType.INT),
+            new CfReturn(ValueType.INT),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
+  public static CfCode MathMethods_clampLong(DexItemFactory factory, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        6,
+        6,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.LONG, 2),
+            new CfLoad(ValueType.LONG, 4),
+            new CfCmp(Cmp.Bias.NONE, NumericType.LONG),
+            new CfIf(IfType.LE, ValueType.INT, label2),
+            label1,
+            new CfNew(factory.createType("Ljava/lang/IllegalArgumentException;")),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfNew(factory.stringBuilderType),
+            new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.voidType),
+                    factory.createString("<init>")),
+                false),
+            new CfLoad(ValueType.LONG, 2),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.longType),
+                    factory.createString("append")),
+                false),
+            new CfConstString(factory.createString(" > ")),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.stringType),
+                    factory.createString("append")),
+                false),
+            new CfLoad(ValueType.LONG, 4),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringBuilderType, factory.longType),
+                    factory.createString("append")),
+                false),
+            new CfInvoke(
+                182,
+                factory.createMethod(
+                    factory.stringBuilderType,
+                    factory.createProto(factory.stringType),
+                    factory.createString("toString")),
+                false),
+            new CfInvoke(
+                183,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/IllegalArgumentException;"),
+                    factory.createProto(factory.voidType, factory.stringType),
+                    factory.createString("<init>")),
+                false),
+            new CfThrow(),
+            label2,
+            new CfFrame(
+                new Int2ObjectAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3, 4, 5},
+                    new FrameType[] {
+                      FrameType.longType(),
+                      FrameType.longHighType(),
+                      FrameType.longType(),
+                      FrameType.longHighType(),
+                      FrameType.longType(),
+                      FrameType.longHighType()
+                    })),
+            new CfLoad(ValueType.LONG, 4),
+            new CfLoad(ValueType.LONG, 0),
+            new CfLoad(ValueType.LONG, 2),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.longType, factory.longType, factory.longType),
+                    factory.createString("max")),
+                false),
+            new CfInvoke(
+                184,
+                factory.createMethod(
+                    factory.createType("Ljava/lang/Math;"),
+                    factory.createProto(factory.longType, factory.longType, factory.longType),
+                    factory.createString("min")),
+                false),
+            new CfReturn(ValueType.LONG),
+            label3),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode MathMethods_decrementExactInt(DexItemFactory factory, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/test/examplesJava21/backport/MathJava21Test.java b/src/test/examplesJava21/backport/MathJava21Test.java
new file mode 100644
index 0000000..15b0e54
--- /dev/null
+++ b/src/test/examplesJava21/backport/MathJava21Test.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package backport;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class MathJava21Test extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK21)
+        .withAllApiLevelsAlsoForCf()
+        .build();
+  }
+
+  public MathJava21Test(TestParameters parameters) {
+    super(parameters, Math.class, Main.class);
+    registerTarget(AndroidApiLevel.V, 30);
+  }
+
+  static final class Main extends MiniAssert {
+
+    public static void main(String[] args) {
+      testClampInt();
+      testClampLong();
+      testClampDouble();
+      testClampFloat();
+    }
+
+    private static void testClampInt() {
+      assertEquals(1, Math.clamp(1, 0, 5));
+      assertEquals(0, Math.clamp(-1, 0, 5));
+      assertEquals(5, Math.clamp(10, 0, 5));
+      try {
+        Math.clamp(1, 10, 5);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+
+    private static void testClampLong() {
+      assertEquals(1, Math.clamp(1L, 0L, 5L));
+      assertEquals(0, Math.clamp(-1L, 0L, 5L));
+      assertEquals(5, Math.clamp(10L, 0L, 5L));
+      try {
+        Math.clamp(1L, 10L, 5L);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+
+    private static void testClampDouble() {
+      assertEquals(1.0, Math.clamp(1.0, 0.0, 5.0));
+      assertEquals(0.0, Math.clamp(-1.0, 0.0, 5.0));
+      assertEquals(5.0, Math.clamp(10.0, 0.0, 5.0));
+      try {
+        Math.clamp(1.0, 10.0, 5.0);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+
+      }
+      // Check for -/+0.0.
+      assertEquals(0.0, Math.clamp(-0.0, 0.0, 1.0));
+      assertEquals(0.0, Math.clamp(0.0, -0.0, 1.0));
+      assertEquals(-0.0, Math.clamp(-0.0, -1.0, 0.0));
+      assertEquals(-0.0, Math.clamp(0.0, -1.0, -0.0));
+      // Check for NaN.
+      assertEquals(Double.NaN, Math.clamp(Double.NaN, 0.0, 1.0));
+      try {
+        Math.clamp(1.0, Double.NaN, 5.0);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+      try {
+        Math.clamp(1.0, 10.0, Double.NaN);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+
+    private static void testClampFloat() {
+      assertEquals(1.0f, Math.clamp(1.0f, 0.0f, 5.0f));
+      assertEquals(0.0f, Math.clamp(-1.0f, 0.0f, 5.0f));
+      assertEquals(5.0f, Math.clamp(10.0f, 0.0f, 5.0f));
+      try {
+        Math.clamp(1.0f, 10.0f, 5.0f);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+
+      }
+      // Check for -/+0.0f.
+      assertEquals(0.0f, Math.clamp(-0.0f, 0.0f, 1.0f));
+      assertEquals(0.0f, Math.clamp(0.0f, -0.0f, 1.0f));
+      assertEquals(-0.0f, Math.clamp(-0.0f, -1.0f, 0.0f));
+      assertEquals(-0.0f, Math.clamp(0.0f, -1.0f, -0.0f));
+      // Check for NaN.
+      assertEquals(Float.NaN, Math.clamp(Float.NaN, 0.0f, 1.0f));
+      try {
+        Math.clamp(1.0f, Float.NaN, 5.0f);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+      try {
+        Math.clamp(1.0f, 10.0f, Float.NaN);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+  }
+}
diff --git a/src/test/examplesJava21/backport/StrictMathJava21Test.java b/src/test/examplesJava21/backport/StrictMathJava21Test.java
new file mode 100644
index 0000000..09b0b11
--- /dev/null
+++ b/src/test/examplesJava21/backport/StrictMathJava21Test.java
@@ -0,0 +1,120 @@
+// Copyright (c) 2025, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package backport;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.desugar.backports.AbstractBackportTest;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public final class StrictMathJava21Test extends AbstractBackportTest {
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimes()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK21)
+        .withAllApiLevelsAlsoForCf()
+        .build();
+  }
+
+  public StrictMathJava21Test(TestParameters parameters) {
+    super(parameters, StrictMath.class, Main.class);
+    registerTarget(AndroidApiLevel.V, 30);
+  }
+
+  static final class Main extends MiniAssert {
+
+    public static void main(String[] args) {
+      testClampInt();
+      testClampLong();
+      testClampDouble();
+      testClampFloat();
+    }
+
+    private static void testClampInt() {
+      assertEquals(1, StrictMath.clamp(1, 0, 5));
+      assertEquals(0, StrictMath.clamp(-1, 0, 5));
+      assertEquals(5, StrictMath.clamp(10, 0, 5));
+      try {
+        StrictMath.clamp(1, 10, 5);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+
+    private static void testClampLong() {
+      assertEquals(1, Math.clamp(1L, 0L, 5L));
+      assertEquals(0, Math.clamp(-1L, 0L, 5L));
+      assertEquals(5, Math.clamp(10L, 0L, 5L));
+      try {
+        Math.clamp(1L, 10L, 5L);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+
+    private static void testClampDouble() {
+      assertEquals(1.0, StrictMath.clamp(1.0, 0.0, 5.0));
+      assertEquals(0.0, StrictMath.clamp(-1.0, 0.0, 5.0));
+      assertEquals(5.0, StrictMath.clamp(10.0, 0.0, 5.0));
+      try {
+        StrictMath.clamp(1.0, 10.0, 5.0);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+
+      }
+      // Check for -/+0.0.
+      assertEquals(0.0, StrictMath.clamp(-0.0, 0.0, 1.0));
+      assertEquals(0.0, StrictMath.clamp(0.0, -0.0, 1.0));
+      assertEquals(-0.0, StrictMath.clamp(-0.0, -1.0, 0.0));
+      assertEquals(-0.0, StrictMath.clamp(0.0, -1.0, -0.0));
+      // Check for NaN.
+      assertEquals(Double.NaN, StrictMath.clamp(Double.NaN, 0.0, 1.0));
+      try {
+        StrictMath.clamp(1.0, Double.NaN, 5.0);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+      try {
+        StrictMath.clamp(1.0, 10.0, Double.NaN);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+
+    private static void testClampFloat() {
+      assertEquals(1.0f, StrictMath.clamp(1.0f, 0.0f, 5.0f));
+      assertEquals(0.0f, StrictMath.clamp(-1.0f, 0.0f, 5.0f));
+      assertEquals(5.0f, StrictMath.clamp(10.0f, 0.0f, 5.0f));
+      try {
+        StrictMath.clamp(1.0f, 10.0f, 5.0f);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+
+      }
+      // Check for -/+0.0f.
+      assertEquals(0.0f, StrictMath.clamp(-0.0f, 0.0f, 1.0f));
+      assertEquals(0.0f, StrictMath.clamp(0.0f, -0.0f, 1.0f));
+      assertEquals(-0.0f, StrictMath.clamp(-0.0f, -1.0f, 0.0f));
+      assertEquals(-0.0f, StrictMath.clamp(0.0f, -1.0f, -0.0f));
+      // Check for NaN.
+      assertEquals(Float.NaN, StrictMath.clamp(Float.NaN, 0.0f, 1.0f));
+      try {
+        StrictMath.clamp(1.0f, Float.NaN, 5.0f);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+      try {
+        StrictMath.clamp(1.0f, 10.0f, Float.NaN);
+        fail("Should have thrown");
+      } catch (IllegalArgumentException ignored) {
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/MathMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/MathMethods.java
index 84b218b..bef9589 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/MathMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/MathMethods.java
@@ -241,4 +241,44 @@
     }
     return Math.abs(a);
   }
+
+  public static int clampInt(long value, int min, int max) {
+    if (min > max) {
+      throw new IllegalArgumentException(min + " > " + max);
+    }
+    return (int) Math.min(max, Math.max(value, min));
+  }
+
+  public static long clampLong(long value, long min, long max) {
+    if (min > max) {
+      throw new IllegalArgumentException(min + " > " + max);
+    }
+    return Math.min(max, Math.max(value, min));
+  }
+
+  public static double clampDouble(double value, double min, double max) {
+    if (Double.isNaN(min)) {
+      throw new IllegalArgumentException("min is NaN");
+    }
+    if (Double.isNaN(max)) {
+      throw new IllegalArgumentException("max is NaN");
+    }
+    if (Double.compare(min, max) > 0) {
+      throw new IllegalArgumentException(min + " > " + max);
+    }
+    return Math.min(max, Math.max(value, min));
+  }
+
+  public static float clampFloat(float value, float min, float max) {
+    if (Float.isNaN(min)) {
+      throw new IllegalArgumentException("min is NaN");
+    }
+    if (Float.isNaN(max)) {
+      throw new IllegalArgumentException("max is NaN");
+    }
+    if (Float.compare(min, max) > 0) {
+      throw new IllegalArgumentException(min + " > " + max);
+    }
+    return Math.min(max, Math.max(value, min));
+  }
 }
diff --git a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 99a087c..8eebd4a 100644
--- a/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/testbase/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -318,58 +318,58 @@
 
   /** JUnit {@link Assert} isn't available in the VM runtime. This is a mini mirror of its API. */
   public abstract static class MiniAssert {
-    static void assertTrue(boolean value) {
+    protected static void assertTrue(boolean value) {
       assertEquals(true, value);
     }
 
-    static void assertFalse(boolean value) {
+    protected static void assertFalse(boolean value) {
       assertEquals(false, value);
     }
 
-    static void assertEquals(boolean expected, boolean actual) {
+    protected static void assertEquals(boolean expected, boolean actual) {
       if (expected != actual) {
         throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
       }
     }
 
-    static void assertEquals(int expected, int actual) {
+    protected static void assertEquals(int expected, int actual) {
       if (expected != actual) {
         throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
       }
     }
 
-    static void assertEquals(long expected, long actual) {
+    protected static void assertEquals(long expected, long actual) {
       if (expected != actual) {
         throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
       }
     }
 
-    static void assertEquals(float expected, float actual) {
+    protected static void assertEquals(float expected, float actual) {
       if (Float.compare(expected, actual) != 0) {
         throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
       }
     }
 
-    static void assertEquals(double expected, double actual) {
+    protected static void assertEquals(double expected, double actual) {
       if (Double.compare(expected, actual) != 0) {
         throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
       }
     }
 
-    static void assertEquals(Object expected, Object actual) {
+    protected static void assertEquals(Object expected, Object actual) {
       if (expected != actual && (expected == null || !expected.equals(actual))) {
         throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
       }
     }
 
-    static void assertSame(Object expected, Object actual) {
+    protected static void assertSame(Object expected, Object actual) {
       if (expected != actual) {
         throw new AssertionError(
             "Expected <" + expected + "> to be same instance as <" + actual + '>');
       }
     }
 
-    static void fail(String message) {
+    protected static void fail(String message) {
       throw new AssertionError("Failed: " + message);
     }
   }