Simplify boxing followed by unboxing.
Also mark boxing `valueOf()` intrinsics as never null
to avoid creating unnecessary `HNullCheck` instructions.
Test: m test-art-host-gtest
Test: testrunner.py --host --optimizing
Change-Id: I86e7721e3af6c59407aa2ddfc1bd11bd2fdac83c
diff --git a/compiler/optimizing/instruction_simplifier.cc b/compiler/optimizing/instruction_simplifier.cc
index fb6d53b..3328f3b 100644
--- a/compiler/optimizing/instruction_simplifier.cc
+++ b/compiler/optimizing/instruction_simplifier.cc
@@ -22,6 +22,7 @@
#include "data_type-inl.h"
#include "driver/compiler_options.h"
#include "escape.h"
+#include "intrinsic_objects.h"
#include "intrinsics.h"
#include "intrinsics_utils.h"
#include "mirror/class-inl.h"
@@ -30,6 +31,7 @@
#include "scoped_thread_state_change-inl.h"
#include "sharpening.h"
#include "string_builder_append.h"
+#include "well_known_classes.h"
namespace art HIDDEN {
@@ -114,6 +116,7 @@
void VisitDeoptimize(HDeoptimize* deoptimize) override;
void VisitVecMul(HVecMul* instruction) override;
void VisitPredicatedInstanceFieldGet(HPredicatedInstanceFieldGet* instruction) override;
+ void SimplifyBoxUnbox(HInvoke* instruction, ArtField* field, DataType::Type type);
void SimplifySystemArrayCopy(HInvoke* invoke);
void SimplifyStringEquals(HInvoke* invoke);
void SimplifyFP2Int(HInvoke* invoke);
@@ -2407,6 +2410,29 @@
TryHandleAssociativeAndCommutativeOperation(instruction);
}
+void InstructionSimplifierVisitor::SimplifyBoxUnbox(
+ HInvoke* instruction, ArtField* field, DataType::Type type) {
+ DCHECK(instruction->GetIntrinsic() == Intrinsics::kByteValueOf ||
+ instruction->GetIntrinsic() == Intrinsics::kShortValueOf ||
+ instruction->GetIntrinsic() == Intrinsics::kCharacterValueOf ||
+ instruction->GetIntrinsic() == Intrinsics::kIntegerValueOf);
+ const HUseList<HInstruction*>& uses = instruction->GetUses();
+ for (auto it = uses.begin(), end = uses.end(); it != end;) {
+ HInstruction* user = it->GetUser();
+ ++it; // Increment the iterator before we potentially remove the node from the list.
+ if (user->IsInstanceFieldGet() &&
+ user->AsInstanceFieldGet()->GetFieldInfo().GetField() == field &&
+ // Note: Due to other simplifications, we may have an `HInstanceFieldGet` with
+ // a different type (Int8 vs. Uint8, Int16 vs. Uint16) for the same field.
+ // Do not optimize that case for now. (We would need to insert a `HTypeConversion`.)
+ user->GetType() == type) {
+ user->ReplaceWith(instruction->InputAt(0));
+ RecordSimplification();
+ // Do not remove `user` while we're iterating over the block's instructions. Let DCE do it.
+ }
+ }
+}
+
void InstructionSimplifierVisitor::SimplifyStringEquals(HInvoke* instruction) {
HInstruction* argument = instruction->InputAt(1);
HInstruction* receiver = instruction->InputAt(0);
@@ -3058,6 +3084,12 @@
void InstructionSimplifierVisitor::VisitInvoke(HInvoke* instruction) {
switch (instruction->GetIntrinsic()) {
+#define SIMPLIFY_BOX_UNBOX(name, low, high, type, start_index) \
+ case Intrinsics::k ## name ## ValueOf: \
+ SimplifyBoxUnbox(instruction, WellKnownClasses::java_lang_##name##_value, type); \
+ break;
+ BOXED_TYPES(SIMPLIFY_BOX_UNBOX)
+#undef SIMPLIFY_BOX_UNBOX
case Intrinsics::kStringEquals:
SimplifyStringEquals(instruction);
break;
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index 53ed49a..2cfe5b3 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -35,6 +35,7 @@
#include "class_root-inl.h"
#include "code_generator.h"
#include "common_dominator.h"
+#include "intrinsic_objects.h"
#include "intrinsics.h"
#include "intrinsics_list.h"
#include "mirror/class-inl.h"
@@ -3363,6 +3364,21 @@
}
}
+bool HInvokeStaticOrDirect::CanBeNull() const {
+ if (GetType() != DataType::Type::kReference || IsStringInit()) {
+ return false;
+ }
+ switch (GetIntrinsic()) {
+#define DEFINE_BOXED_CASE(name, unused1, unused2, unused3, unused4) \
+ case Intrinsics::k##name##ValueOf: \
+ return false;
+ BOXED_TYPES(DEFINE_BOXED_CASE)
+#undef DEFINE_BOXED_CASE
+ default:
+ return true;
+ }
+}
+
bool HInvokeVirtual::CanDoImplicitNullCheckOn(HInstruction* obj) const {
if (obj != InputAt(0)) {
return false;
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 04d5132..4db72a6 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -5065,9 +5065,7 @@
return false;
}
- bool CanBeNull() const override {
- return GetType() == DataType::Type::kReference && !IsStringInit();
- }
+ bool CanBeNull() const override;
MethodLoadKind GetMethodLoadKind() const { return dispatch_info_.method_load_kind; }
CodePtrLocation GetCodePtrLocation() const {
diff --git a/test/640-checker-integer-valueof/src/Main.java b/test/640-checker-integer-valueof/src/Main.java
index 0837fd1..cf0d7d8 100644
--- a/test/640-checker-integer-valueof/src/Main.java
+++ b/test/640-checker-integer-valueof/src/Main.java
@@ -48,6 +48,178 @@
return Integer.valueOf(55555);
}
+ /// CHECK-START: byte Main.$noinline$boxUnboxByte(byte) builder (after)
+ /// CHECK: <<Input:b\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-NOT: NullCheck [<<Boxed>>]
+ /// CHECK: <<Unboxed:b\d+>> InvokeVirtual [<<Boxed>>] method_name:java.lang.Byte.byteValue
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: byte Main.$noinline$boxUnboxByte(byte) inliner (after)
+ /// CHECK: <<Input:b\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK: <<Unboxed:b\d+>> InstanceFieldGet [<<Boxed>>] field_name:java.lang.Byte.value
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: byte Main.$noinline$boxUnboxByte(byte) instruction_simplifier$after_inlining (after)
+ /// CHECK: <<Input:b\d+>> ParameterValue
+ /// CHECK: Return [<<Input>>]
+
+ /// CHECK-START: byte Main.$noinline$boxUnboxByte(byte) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: InvokeStaticOrDirect
+ /// CHECK-NOT: InstanceFieldGet
+
+ public static byte $noinline$boxUnboxByte(byte value) {
+ return Byte.valueOf(value).byteValue();
+ }
+
+ /// CHECK-START: short Main.$noinline$boxUnboxShort(short) builder (after)
+ /// CHECK: <<Input:s\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Short.valueOf intrinsic:ShortValueOf
+ /// CHECK-NOT: NullCheck [<<Boxed>>]
+ /// CHECK: <<Unboxed:s\d+>> InvokeVirtual [<<Boxed>>] method_name:java.lang.Short.shortValue
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: short Main.$noinline$boxUnboxShort(short) inliner (after)
+ /// CHECK: <<Input:s\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Short.valueOf intrinsic:ShortValueOf
+ /// CHECK: <<Unboxed:s\d+>> InstanceFieldGet [<<Boxed>>] field_name:java.lang.Short.value
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: short Main.$noinline$boxUnboxShort(short) instruction_simplifier$after_inlining (after)
+ /// CHECK: <<Input:s\d+>> ParameterValue
+ /// CHECK: Return [<<Input>>]
+
+ /// CHECK-START: short Main.$noinline$boxUnboxShort(short) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: InvokeStaticOrDirect
+ /// CHECK-NOT: InstanceFieldGet
+
+ public static short $noinline$boxUnboxShort(short value) {
+ return Short.valueOf(value).shortValue();
+ }
+
+ /// CHECK-START: char Main.$noinline$boxUnboxCharacter(char) builder (after)
+ /// CHECK: <<Input:c\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Character.valueOf intrinsic:CharacterValueOf
+ /// CHECK-NOT: NullCheck [<<Boxed>>]
+ /// CHECK: <<Unboxed:c\d+>> InvokeVirtual [<<Boxed>>] method_name:java.lang.Character.charValue
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: char Main.$noinline$boxUnboxCharacter(char) inliner (after)
+ /// CHECK: <<Input:c\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Character.valueOf intrinsic:CharacterValueOf
+ /// CHECK: <<Unboxed:c\d+>> InstanceFieldGet [<<Boxed>>] field_name:java.lang.Character.value
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: char Main.$noinline$boxUnboxCharacter(char) instruction_simplifier$after_inlining (after)
+ /// CHECK: <<Input:c\d+>> ParameterValue
+ /// CHECK: Return [<<Input>>]
+
+ /// CHECK-START: char Main.$noinline$boxUnboxCharacter(char) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: InvokeStaticOrDirect
+ /// CHECK-NOT: InstanceFieldGet
+
+ public static char $noinline$boxUnboxCharacter(char value) {
+ return Character.valueOf(value).charValue();
+ }
+
+ /// CHECK-START: int Main.$noinline$boxUnboxInteger(int) builder (after)
+ /// CHECK: <<Input:i\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Integer.valueOf intrinsic:IntegerValueOf
+ /// CHECK-NOT: NullCheck [<<Boxed>>]
+ /// CHECK: <<Unboxed:i\d+>> InvokeVirtual [<<Boxed>>] method_name:java.lang.Integer.intValue
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxInteger(int) inliner (after)
+ /// CHECK: <<Input:i\d+>> ParameterValue
+ /// CHECK: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Integer.valueOf intrinsic:IntegerValueOf
+ /// CHECK: <<Unboxed:i\d+>> InstanceFieldGet [<<Boxed>>] field_name:java.lang.Integer.value
+ /// CHECK: Return [<<Unboxed>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxInteger(int) instruction_simplifier$after_inlining (after)
+ /// CHECK: <<Input:i\d+>> ParameterValue
+ /// CHECK: Return [<<Input>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxInteger(int) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: InvokeStaticOrDirect
+ /// CHECK-NOT: InstanceFieldGet
+
+ public static int $noinline$boxUnboxInteger(int value) {
+ return Integer.valueOf(value).intValue();
+ }
+
+ /// CHECK-START: int Main.$noinline$boxUnboxByteAsUint8(byte) builder (after)
+ /// CHECK-DAG: <<Input:b\d+>> ParameterValue
+ /// CHECK-DAG: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-DAG: <<Phi:l\d+>> Phi
+ /// CHECK-DAG: <<NC:l\d+>> NullCheck [<<Phi>>]
+ /// CHECK-DAG: <<Unboxed:b\d+>> InvokeVirtual [<<NC>>] method_name:java.lang.Byte.byteValue
+ /// CHECK-DAG: <<And:i\d+>> And
+ /// CHECK-DAG: Return [<<And>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxByteAsUint8(byte) instruction_simplifier (after)
+ /// CHECK-DAG: <<Input:b\d+>> ParameterValue
+ /// CHECK-DAG: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-DAG: <<Phi:l\d+>> Phi
+ /// CHECK-DAG: <<NC:l\d+>> NullCheck [<<Phi>>]
+ /// CHECK-DAG: <<Unboxed:b\d+>> InvokeVirtual [<<NC>>] method_name:java.lang.Byte.byteValue
+ /// CHECK-DAG: <<Conv:a\d+>> TypeConversion [<<Unboxed>>]
+ /// CHECK-DAG: Return [<<Conv>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxByteAsUint8(byte) inliner (after)
+ /// CHECK-DAG: <<Input:b\d+>> ParameterValue
+ /// CHECK-DAG: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-DAG: <<Phi:l\d+>> Phi
+ /// CHECK-DAG: <<NC:l\d+>> NullCheck [<<Phi>>]
+ /// CHECK-DAG: <<Unboxed:b\d+>> InstanceFieldGet [<<NC>>] field_name:java.lang.Byte.value
+ /// CHECK-DAG: <<Conv:a\d+>> TypeConversion [<<Unboxed>>]
+ /// CHECK-DAG: Return [<<Conv>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxByteAsUint8(byte) instruction_simplifier$after_inlining (after)
+ /// CHECK-DAG: <<Input:b\d+>> ParameterValue
+ /// CHECK-DAG: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-DAG: <<Phi:l\d+>> Phi
+ /// CHECK-DAG: <<NC:l\d+>> NullCheck [<<Phi>>]
+ /// CHECK-DAG: <<Conv:a\d+>> InstanceFieldGet [<<NC>>] field_name:java.lang.Byte.value
+ /// CHECK-DAG: Return [<<Conv>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxByteAsUint8(byte) dead_code_elimination$after_inlining (after)
+ /// CHECK-DAG: <<Input:b\d+>> ParameterValue
+ /// CHECK-DAG: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-DAG: <<NC:l\d+>> NullCheck [<<Boxed>>]
+ /// CHECK-DAG: <<Conv:a\d+>> InstanceFieldGet [<<NC>>] field_name:java.lang.Byte.value
+ /// CHECK-DAG: Return [<<Conv>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxByteAsUint8(byte) instruction_simplifier$after_gvn (after)
+ /// CHECK-DAG: <<Input:b\d+>> ParameterValue
+ /// CHECK-DAG: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-DAG: <<Conv:a\d+>> InstanceFieldGet [<<Boxed>>] field_name:java.lang.Byte.value
+ /// CHECK-DAG: Return [<<Conv>>]
+
+ /// CHECK-START: int Main.$noinline$boxUnboxByteAsUint8(byte) disassembly (after)
+ /// CHECK-DAG: <<Input:b\d+>> ParameterValue
+ /// CHECK-DAG: <<Boxed:l\d+>> InvokeStaticOrDirect [<<Input>>{{(,[ij]\d+)?}}] method_name:java.lang.Byte.valueOf intrinsic:ByteValueOf
+ /// CHECK-DAG: <<Conv:a\d+>> InstanceFieldGet [<<Boxed>>] field_name:java.lang.Byte.value
+ /// CHECK-DAG: Return [<<Conv>>]
+
+ public static int $noinline$boxUnboxByteAsUint8(byte value) {
+ Byte boxed = Byte.valueOf(value);
+
+ // Hide the unboxing from the initial instruction simplifier pass or the one after inlining,
+ // so that after inlining we can merge the `TypeConversion` into `InstanceFieldGet` with
+ // a modified type. The Phi shall be eliminated by `dead_code_elimination$after_inlining`.
+ // The null check that shall be eliminated by `instruction_simplifier$after_gvn (after)`
+ Byte merged = $inline$returnTrue() ? boxed : null;
+
+ // Due to the type mismatch, we shall not simplify the unboxing. The boxing and unboxing
+ // shall be kept during subsequent simplifier passes all the way to the disassembly.
+ return merged.byteValue() & 0xff;
+ }
+
+ public static boolean $inline$returnTrue() {
+ return true;
+ }
+
public static void main(String[] args) {
assertEqual("42", foo(intField));
assertEqual(foo(intField), foo(intField2));
@@ -65,6 +237,18 @@
assertEqual("127", foo(intField127));
assertEqual(foo(intField127), foo(intField127));
assertEqual("128", foo(intField128));
+
+ assertEqual(42, (int) $noinline$boxUnboxByte((byte) 42));
+ assertEqual(-42, (int) $noinline$boxUnboxByte((byte) -42));
+ assertEqual(42, (int) $noinline$boxUnboxShort((short) 42));
+ assertEqual(-42, (int) $noinline$boxUnboxShort((short) -42));
+ assertEqual((int) (char) 42, (int) $noinline$boxUnboxCharacter((char) 42));
+ assertEqual((int) (char) -42, (int) $noinline$boxUnboxCharacter((char) -42));
+ assertEqual(42, $noinline$boxUnboxInteger(42));
+ assertEqual(-42, $noinline$boxUnboxInteger(-42));
+
+ assertEqual(42, $noinline$boxUnboxByteAsUint8((byte) 42));
+ assertEqual(-42 & 0xff, $noinline$boxUnboxByteAsUint8((byte) -42));
}
static void assertEqual(String a, Integer b) {
@@ -79,6 +263,12 @@
}
}
+ static void assertEqual(int a, int b) {
+ if (a != b) {
+ throw new Error("Expected " + a + ", got " + b);
+ }
+ }
+
static int intField = 42;
static int intField2 = 42;
static int intField3 = 55555;