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);
}
}