summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Artem Serov <artem.serov@linaro.org> 2018-06-22 11:35:46 +0100
committer Artem Serov <artem.serov@linaro.org> 2018-08-31 12:34:32 +0100
commitd1aa7d0eec33e5f297d1eadedda714d4d0a3ef91 (patch)
treee8a3876705be966611a06659e9f4b848205f3957
parent11529ab4cdf06e579182fa4252170aa4541f4ce9 (diff)
ART: Remove unneeded SideEffects for fatal instructions.
Remove "CanTriggerGC" side effect for NullCheck, BoundsCheck and DivZeroCheck - instructions which have fatal slow paths. Even though GC might happen after going through those instructions' slow path execution doesn't return to the next instruction after the exceptional one so side effects can be relaxed. Performance improvement (angler, arm64, little core): - Geomean: 2.8% - Particular benchmarks - algorithm/Sort.SystemSort: 13.0% - stanford/IntMM: 12.7% - stanford/Puzzle: 9.5% - benchmarksgame/revcomp: 8.9% - reversigame/Reversi: 3.5% Test: 510-checker-try-catch. Test: 706-checker-scheduler. Test: 527-checker-array-access-split. Test: test-art-host, test-art-target. Change-Id: I55ac011822e5dbac82c828a700213dbea87329c8
-rw-r--r--compiler/optimizing/code_generator.cc7
-rw-r--r--compiler/optimizing/nodes.h46
-rw-r--r--compiler/optimizing/side_effects_test.cc1
-rw-r--r--test/510-checker-try-catch/smali/Runtime.smali70
-rw-r--r--test/510-checker-try-catch/src/Main.java119
-rw-r--r--test/527-checker-array-access-split/src/Main.java27
-rw-r--r--test/706-checker-scheduler/src/Main.java8
7 files changed, 265 insertions, 13 deletions
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index a13efcaee2..095b273182 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -1514,7 +1514,12 @@ void CodeGenerator::ValidateInvokeRuntime(QuickEntrypointEnum entrypoint,
<< " instruction->GetSideEffects().ToString()="
<< instruction->GetSideEffects().ToString();
} else {
- DCHECK(instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()) ||
+ // 'CanTriggerGC' side effect is used to restrict optimization of instructions which depend
+ // on GC (e.g. IntermediateAddress) - to ensure they are not alive across GC points. However
+ // if execution never returns to the compiled code from a GC point this restriction is
+ // unnecessary - in particular for fatal slow paths which might trigger GC.
+ DCHECK((slow_path->IsFatal() && !instruction->GetLocations()->WillCall()) ||
+ instruction->GetSideEffects().Includes(SideEffects::CanTriggerGC()) ||
// When (non-Baker) read barriers are enabled, some instructions
// use a slow path to emit a read barrier, which does not trigger
// GC.
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 8b9e1da0d3..48bc5e8c15 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -1626,6 +1626,21 @@ using HConstInputsRef = TransformArrayRef<const HUserRecord<HInstruction*>, HInp
* the same, and any reference read depends on any reference read without
* further regard of its type).
*
+ * kDependsOnGCBit is defined in the following way: instructions with kDependsOnGCBit must not be
+ * alive across the point where garbage collection might happen.
+ *
+ * Note: Instructions with kCanTriggerGCBit do not depend on each other.
+ *
+ * kCanTriggerGCBit must be used for instructions for which GC might happen on the path across
+ * those instructions from the compiler perspective (between this instruction and the next one
+ * in the IR).
+ *
+ * Note: Instructions which can cause GC only on a fatal slow path do not need
+ * kCanTriggerGCBit as the execution never returns to the instruction next to the exceptional
+ * one. However the execution may return to compiled code if there is a catch block in the
+ * current method; for this purpose the TryBoundary exit instruction has kCanTriggerGCBit
+ * set.
+ *
* The internal representation uses 38-bit and is described in the table below.
* The first line indicates the side effect, and for field/array accesses the
* second line indicates the type of the access (in the order of the
@@ -1698,10 +1713,17 @@ class SideEffects : public ValueObject {
return SideEffects(TypeFlag(type, kArrayReadOffset));
}
+ // Returns whether GC might happen across this instruction from the compiler perspective so
+ // the next instruction in the IR would see that.
+ //
+ // See the SideEffect class comments.
static SideEffects CanTriggerGC() {
return SideEffects(1ULL << kCanTriggerGCBit);
}
+ // Returns whether the instruction must not be alive across a GC point.
+ //
+ // See the SideEffect class comments.
static SideEffects DependsOnGC() {
return SideEffects(1ULL << kDependsOnGCBit);
}
@@ -3123,8 +3145,15 @@ class HTryBoundary FINAL : public HExpression<0> {
kLast = kExit
};
+ // SideEffects::CanTriggerGC prevents instructions with SideEffects::DependOnGC to be alive
+ // across the catch block entering edges as GC might happen during throwing an exception.
+ // TryBoundary with BoundaryKind::kExit is conservatively used for that as there is no
+ // HInstruction which a catch block must start from.
explicit HTryBoundary(BoundaryKind kind, uint32_t dex_pc = kNoDexPc)
- : HExpression(kTryBoundary, SideEffects::None(), dex_pc) {
+ : HExpression(kTryBoundary,
+ (kind == BoundaryKind::kExit) ? SideEffects::CanTriggerGC()
+ : SideEffects::None(),
+ dex_pc) {
SetPackedField<BoundaryKindField>(kind);
}
@@ -5150,9 +5179,10 @@ class HAbs FINAL : public HUnaryOperation {
class HDivZeroCheck FINAL : public HExpression<1> {
public:
// `HDivZeroCheck` can trigger GC, as it may call the `ArithmeticException`
- // constructor.
+ // constructor. However it can only do it on a fatal slow path so execution never returns to the
+ // instruction following the current one; thus 'SideEffects::None()' is used.
HDivZeroCheck(HInstruction* value, uint32_t dex_pc)
- : HExpression(kDivZeroCheck, value->GetType(), SideEffects::CanTriggerGC(), dex_pc) {
+ : HExpression(kDivZeroCheck, value->GetType(), SideEffects::None(), dex_pc) {
SetRawInputAt(0, value);
}
@@ -5629,9 +5659,10 @@ static constexpr uint32_t kNoRegNumber = -1;
class HNullCheck FINAL : public HExpression<1> {
public:
// `HNullCheck` can trigger GC, as it may call the `NullPointerException`
- // constructor.
+ // constructor. However it can only do it on a fatal slow path so execution never returns to the
+ // instruction following the current one; thus 'SideEffects::None()' is used.
HNullCheck(HInstruction* value, uint32_t dex_pc)
- : HExpression(kNullCheck, value->GetType(), SideEffects::CanTriggerGC(), dex_pc) {
+ : HExpression(kNullCheck, value->GetType(), SideEffects::None(), dex_pc) {
SetRawInputAt(0, value);
}
@@ -6058,12 +6089,13 @@ class HArrayLength FINAL : public HExpression<1> {
class HBoundsCheck FINAL : public HExpression<2> {
public:
// `HBoundsCheck` can trigger GC, as it may call the `IndexOutOfBoundsException`
- // constructor.
+ // constructor. However it can only do it on a fatal slow path so execution never returns to the
+ // instruction following the current one; thus 'SideEffects::None()' is used.
HBoundsCheck(HInstruction* index,
HInstruction* length,
uint32_t dex_pc,
bool is_string_char_at = false)
- : HExpression(kBoundsCheck, index->GetType(), SideEffects::CanTriggerGC(), dex_pc) {
+ : HExpression(kBoundsCheck, index->GetType(), SideEffects::None(), dex_pc) {
DCHECK_EQ(DataType::Type::kInt32, DataType::Kind(index->GetType()));
SetPackedFlag<kFlagIsStringCharAt>(is_string_char_at);
SetRawInputAt(0, index);
diff --git a/compiler/optimizing/side_effects_test.cc b/compiler/optimizing/side_effects_test.cc
index 97317124ef..4b0be07f3b 100644
--- a/compiler/optimizing/side_effects_test.cc
+++ b/compiler/optimizing/side_effects_test.cc
@@ -202,6 +202,7 @@ TEST(SideEffectsTest, GC) {
EXPECT_TRUE(depends_on_gc.MayDependOn(all_changes));
EXPECT_TRUE(depends_on_gc.Union(can_trigger_gc).MayDependOn(all_changes));
EXPECT_FALSE(can_trigger_gc.MayDependOn(all_changes));
+ EXPECT_FALSE(can_trigger_gc.MayDependOn(can_trigger_gc));
EXPECT_TRUE(all_changes.Includes(can_trigger_gc));
EXPECT_FALSE(all_changes.Includes(depends_on_gc));
diff --git a/test/510-checker-try-catch/smali/Runtime.smali b/test/510-checker-try-catch/smali/Runtime.smali
index 19b43a3f30..d080a0c94b 100644
--- a/test/510-checker-try-catch/smali/Runtime.smali
+++ b/test/510-checker-try-catch/smali/Runtime.smali
@@ -549,6 +549,76 @@
.end array-data
.end method
+
+## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after)
+## CHECK-DAG: <<Const1:i\d+>> IntConstant 1
+## CHECK-DAG: <<Offset:i\d+>> IntConstant 12
+## CHECK-DAG: <<IndexParam:i\d+>> ParameterValue
+## CHECK-DAG: <<Array:l\d+>> ParameterValue
+
+## CHECK-DAG: <<NullCh1:l\d+>> NullCheck [<<Array>>]
+## CHECK-DAG: <<Length:i\d+>> ArrayLength
+## CHECK-DAG: <<BoundsCh1:i\d+>> BoundsCheck [<<IndexParam>>,<<Length>>]
+## CHECK-DAG: <<IntAddr1:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+## CHECK-DAG: ArrayGet [<<IntAddr1>>,<<BoundsCh1>>]
+## CHECK-DAG: TryBoundary
+
+## CHECK-DAG: <<Xplus1:i\d+>> Add [<<IndexParam>>,<<Const1>>]
+## CHECK-DAG: <<BoundsCh2:i\d+>> BoundsCheck [<<Xplus1>>,<<Length>>]
+## CHECK-DAG: ArrayGet [<<IntAddr1>>,<<BoundsCh2>>]
+## CHECK-DAG: TryBoundary
+
+## CHECK-DAG: <<Phi:i\d+>> Phi [<<Xplus1>>]
+## CHECK-DAG: <<Phiplus1:i\d+>> Add [<<Phi>>,<<Const1>>]
+## CHECK-DAG: <<BoundsCh3:i\d+>> BoundsCheck [<<Phiplus1>>,<<Length>>]
+## CHECK-DAG: <<IntAddr3:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+## CHECK-DAG: ArrayGet [<<IntAddr3>>,<<BoundsCh3>>]
+
+## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after)
+## CHECK: NullCheck
+## CHECK-NOT: NullCheck
+
+## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after)
+## CHECK: IntermediateAddress
+## CHECK: IntermediateAddress
+## CHECK-NOT: IntermediateAddress
+
+## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after)
+## CHECK: BoundsCheck
+## CHECK: BoundsCheck
+## CHECK: BoundsCheck
+## CHECK-NOT: BoundsCheck
+
+## CHECK-START-{ARM,ARM64}: int Runtime.testIntAddressCatch(int, int[]) GVN$after_arch (after)
+## CHECK: ArrayGet
+## CHECK: ArrayGet
+## CHECK: ArrayGet
+## CHECK-NOT: ArrayGet
+.method public static testIntAddressCatch(I[I)I
+ .registers 4
+ aget v0, p1, p0
+ add-int v1, v0, v0
+
+ :try_start
+ const/4 v0, 0x1
+ add-int p0, p0, v0
+ aget v0, p1, p0
+
+ :try_end
+ .catch Ljava/lang/ArithmException; {:try_start .. :try_end} :catch_block
+
+ :return
+ add-int v1, v1, v0
+ return v1
+
+ :catch_block
+ const/4 v0, 0x1
+ add-int p0, p0, v0
+ aget v0, p1, p0
+
+ goto :return
+.end method
+
.field public static intArray:[I
.field public static longArray:[J
.field public static floatArray:[F
diff --git a/test/510-checker-try-catch/src/Main.java b/test/510-checker-try-catch/src/Main.java
index d6dcd30f3c..18658cd5e0 100644
--- a/test/510-checker-try-catch/src/Main.java
+++ b/test/510-checker-try-catch/src/Main.java
@@ -37,6 +37,114 @@ public class Main {
public int expected;
}
+ // Test that IntermediateAddress instruction is not alive across BoundsCheck which can throw to
+ // a catch block.
+ //
+ /// CHECK-START-{ARM,ARM64}: void Main.boundsCheckAndCatch(int, int[], int[]) GVN$after_arch (before)
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1
+ /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2
+ /// CHECK-DAG: <<Offset:i\d+>> IntConstant 12
+ /// CHECK-DAG: <<IndexParam:i\d+>> ParameterValue
+ /// CHECK-DAG: <<ArrayA:l\d+>> ParameterValue
+ /// CHECK-DAG: <<ArrayB:l\d+>> ParameterValue
+ //
+
+ /// CHECK-DAG: <<NullCh1:l\d+>> NullCheck [<<ArrayA>>]
+ /// CHECK-DAG: <<LengthA:i\d+>> ArrayLength
+ /// CHECK-DAG: <<BoundsCh1:i\d+>> BoundsCheck [<<IndexParam>>,<<LengthA>>]
+ /// CHECK-DAG: <<IntAddr1:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+ /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh1>>,<<Const1>>]
+ /// CHECK-DAG: TryBoundary
+ //
+ /// CHECK-DAG: <<IntAddr2:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+ /// CHECK-DAG: ArraySet [<<IntAddr2>>,<<BoundsCh1>>,<<Const2>>]
+ /// CHECK-DAG: <<NullChB:l\d+>> NullCheck [<<ArrayB>>]
+ /// CHECK-DAG: <<LengthB:i\d+>> ArrayLength
+ /// CHECK-DAG: <<BoundsChB:i\d+>> BoundsCheck [<<Const0>>,<<LengthB>>]
+ /// CHECK-DAG: <<GetB:i\d+>> ArrayGet [<<NullChB>>,<<BoundsChB>>]
+ /// CHECK-DAG: <<ZeroCheck:i\d+>> DivZeroCheck [<<IndexParam>>]
+ /// CHECK-DAG: <<Div:i\d+>> Div [<<GetB>>,<<ZeroCheck>>]
+ /// CHECK-DAG: <<Xplus1:i\d+>> Add [<<IndexParam>>,<<Const1>>]
+ /// CHECK-DAG: <<BoundsCh2:i\d+>> BoundsCheck [<<Xplus1>>,<<LengthA>>]
+ /// CHECK-DAG: <<IntAddr3:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+ /// CHECK-DAG: ArraySet [<<IntAddr3>>,<<BoundsCh2>>,<<Div>>]
+ /// CHECK-DAG: TryBoundary
+ //
+ /// CHECK-DAG: ClearException
+ /// CHECK-DAG: <<IntAddr4:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+ /// CHECK-DAG: ArraySet [<<IntAddr4>>,<<BoundsCh1>>,<<Const1>>]
+ //
+ /// CHECK-NOT: NullCheck
+ /// CHECK-NOT: IntermediateAddress
+
+ /// CHECK-START-{ARM,ARM64}: void Main.boundsCheckAndCatch(int, int[], int[]) GVN$after_arch (after)
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1
+ /// CHECK-DAG: <<Const2:i\d+>> IntConstant 2
+ /// CHECK-DAG: <<Offset:i\d+>> IntConstant 12
+ /// CHECK-DAG: <<IndexParam:i\d+>> ParameterValue
+ /// CHECK-DAG: <<ArrayA:l\d+>> ParameterValue
+ /// CHECK-DAG: <<ArrayB:l\d+>> ParameterValue
+ //
+ /// CHECK-DAG: <<NullCh1:l\d+>> NullCheck [<<ArrayA>>]
+ /// CHECK-DAG: <<LengthA:i\d+>> ArrayLength
+ /// CHECK-DAG: <<BoundsCh1:i\d+>> BoundsCheck [<<IndexParam>>,<<LengthA>>]
+ /// CHECK-DAG: <<IntAddr1:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+ /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh1>>,<<Const1>>]
+ /// CHECK-DAG: TryBoundary
+ //
+ /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh1>>,<<Const2>>]
+ /// CHECK-DAG: <<NullChB:l\d+>> NullCheck [<<ArrayB>>]
+ /// CHECK-DAG: <<LengthB:i\d+>> ArrayLength
+ /// CHECK-DAG: <<BoundsChB:i\d+>> BoundsCheck [<<Const0>>,<<LengthB>>]
+ /// CHECK-DAG: <<GetB:i\d+>> ArrayGet [<<NullChB>>,<<BoundsChB>>]
+ /// CHECK-DAG: <<ZeroCheck:i\d+>> DivZeroCheck [<<IndexParam>>]
+ /// CHECK-DAG: <<Div:i\d+>> Div [<<GetB>>,<<ZeroCheck>>]
+ /// CHECK-DAG: <<Xplus1:i\d+>> Add [<<IndexParam>>,<<Const1>>]
+ /// CHECK-DAG: <<BoundsCh2:i\d+>> BoundsCheck [<<Xplus1>>,<<LengthA>>]
+ /// CHECK-DAG: ArraySet [<<IntAddr1>>,<<BoundsCh2>>,<<Div>>]
+ /// CHECK-DAG: TryBoundary
+ //
+ /// CHECK-DAG: ClearException
+ /// CHECK-DAG: <<IntAddr4:i\d+>> IntermediateAddress [<<NullCh1>>,<<Offset>>]
+ /// CHECK-DAG: ArraySet [<<IntAddr4>>,<<BoundsCh1>>,<<Const1>>]
+ //
+ /// CHECK-NOT: NullCheck
+ /// CHECK-NOT: IntermediateAddress
+
+ // Make sure that BoundsCheck, DivZeroCheck and NullCheck don't stop IntermediateAddress sharing.
+ public static void boundsCheckAndCatch(int x, int[] a, int[] b) {
+ a[x] = 1;
+ try {
+ a[x] = 2;
+ a[x + 1] = b[0] / x;
+ } catch (Exception e) {
+ a[x] = 1;
+ }
+ }
+
+ private static void expectEquals(int expected, int result) {
+ if (expected != result) {
+ throw new Error("Expected: " + expected + ", found: " + result);
+ }
+ }
+
+ public final static int ARRAY_SIZE = 128;
+
+ public static void testBoundsCheckAndCatch() {
+ int[] a = new int[ARRAY_SIZE];
+ int[] b = new int[ARRAY_SIZE];
+
+ int index = ARRAY_SIZE - 2;
+ boundsCheckAndCatch(index, a, b);
+ expectEquals(2, a[index]);
+
+ index = ARRAY_SIZE - 1;
+ boundsCheckAndCatch(index, a, b);
+ expectEquals(1, a[index]);
+ }
+
public static void testMethod(String method) throws Exception {
Class<?> c = Class.forName("Runtime");
Method m = c.getMethod(method, boolean.class, boolean.class);
@@ -52,6 +160,14 @@ public class Main {
}
}
+ public static void testIntAddressCatch() throws Exception {
+ int[] a = new int[3];
+
+ Class<?> c = Class.forName("Runtime");
+ Method m = c.getMethod("testIntAddressCatch", int.class, Class.forName("[I"));
+ m.invoke(null, 0, a);
+ }
+
public static void main(String[] args) throws Exception {
testMethod("testUseAfterCatch_int");
testMethod("testUseAfterCatch_long");
@@ -64,5 +180,8 @@ public class Main {
testMethod("testCatchPhi_double");
testMethod("testCatchPhi_singleSlot");
testMethod("testCatchPhi_doubleSlot");
+
+ testBoundsCheckAndCatch();
+ testIntAddressCatch();
}
}
diff --git a/test/527-checker-array-access-split/src/Main.java b/test/527-checker-array-access-split/src/Main.java
index 935b37858d..f83c924de9 100644
--- a/test/527-checker-array-access-split/src/Main.java
+++ b/test/527-checker-array-access-split/src/Main.java
@@ -552,6 +552,28 @@ public class Main {
return (int)s;
}
+ //
+ // Check that IntermediateAddress can be shared across BoundsCheck, DivZeroCheck and NullCheck -
+ // instruction which have fatal slow paths.
+ //
+ /// CHECK-START-{ARM,ARM64}: void Main.checkGVNForFatalChecks(int, int, char[], int[]) GVN$after_arch (before)
+ /// CHECK: IntermediateAddress
+ /// CHECK: IntermediateAddress
+ //
+ /// CHECK-NOT: IntermediateAddress
+
+ /// CHECK-START-{ARM,ARM64}: void Main.checkGVNForFatalChecks(int, int, char[], int[]) GVN$after_arch (after)
+ /// CHECK: IntermediateAddress
+ //
+ /// CHECK-NOT: IntermediateAddress
+ public final static void checkGVNForFatalChecks(int begin, int end, char[] buf1, int[] buf2) {
+ buf1[begin] = 'a';
+ buf2[0] = begin / end;
+ buf1[end] = 'n';
+ }
+
+ public final static int ARRAY_SIZE = 128;
+
public static void main(String[] args) {
int[] array = {123, 456, 789};
@@ -575,5 +597,10 @@ public class Main {
assertIntEquals(2097152, canMergeAfterBCE2());
assertIntEquals(18, checkLongFloatDouble());
+
+ char[] c1 = new char[ARRAY_SIZE];
+ int[] i1 = new int[ARRAY_SIZE];
+ checkGVNForFatalChecks(1, 2, c1, i1);
+ assertIntEquals('n', c1[2]);
}
}
diff --git a/test/706-checker-scheduler/src/Main.java b/test/706-checker-scheduler/src/Main.java
index 9f4caecccd..af18193388 100644
--- a/test/706-checker-scheduler/src/Main.java
+++ b/test/706-checker-scheduler/src/Main.java
@@ -292,8 +292,7 @@ public class Main {
/// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none
/// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<NullB>>,{{i\d+}}] loop:<<Loop>> outer_loop:none
/// CHECK: Add loop:<<Loop>> outer_loop:none
- /// CHECK: <<Addr2:i\d+>> IntermediateAddress [<<NullA>>,{{i\d+}}] loop:<<Loop>> outer_loop:none
- /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr2>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none
+ /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none
/// CHECK: Add loop:<<Loop>> outer_loop:none
/// CHECK-START-ARM64: void Main.CrossOverLoop(int[], int[]) scheduler (after)
@@ -303,13 +302,12 @@ public class Main {
/// CHECK: <<NullA:l\d+>> NullCheck [<<ParamA>>] loop:none
/// CHECK: Phi loop:<<Loop:B\d+>> outer_loop:none
/// CHECK: <<ArrayGet1:i\d+>> ArrayGet [<<NullB>>,{{i\d+}}] loop:<<Loop>> outer_loop:none
- /// CHECK: Add loop:<<Loop>> outer_loop:none
/// CHECK: <<Addr1:i\d+>> IntermediateAddress [<<NullA>>,{{i\d+}}] loop:<<Loop>> outer_loop:none
+ /// CHECK: Add [<<ArrayGet1>>,{{i\d+}}] loop:<<Loop>> outer_loop:none
/// CHECK: <<ArraySet1:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none
/// CHECK: <<ArrayGet2:i\d+>> ArrayGet [<<NullB>>,{{i\d+}}] loop:<<Loop>> outer_loop:none
/// CHECK: Add loop:<<Loop>> outer_loop:none
- /// CHECK: <<Addr2:i\d+>> IntermediateAddress [<<NullA>>,{{i\d+}}] loop:<<Loop>> outer_loop:none
- /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr2>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none
+ /// CHECK: <<ArraySet2:v\d+>> ArraySet [<<Addr1>>,{{i\d+}},{{i\d+}}] loop:<<Loop>> outer_loop:none
/// CHECK: Add loop:<<Loop>> outer_loop:none
private static void CrossOverLoop(int a[], int b[]) {
b[20] = 99;