ART: Ignore unneeded environment uses.
Some of the environment uses of primitive-typed values are
not really needed in non-debuggable/non-OSR methods. Ignoring
those uses during liveness analysis significantly reduces the
size of stack maps in the oat file.
Code reduction on arm64:
boot-framework.oat: -1.8%
boot.oat: -1.4%
Test: 466-get-live-vreg, 564-checker-condition-liveness.
Test: 639-checker-code-sinking.
Test: angler boots to GUI.
Test: test-art-host, test-art-target
Change-Id: I91dcb6d0a8ab86f56c7b243bf9b100f69bcd5979
diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc
index f6bd052..2f782f3 100644
--- a/compiler/optimizing/ssa_liveness_analysis.cc
+++ b/compiler/optimizing/ssa_liveness_analysis.cc
@@ -195,14 +195,19 @@
// SsaLivenessAnalysis.
for (size_t i = 0, e = environment->Size(); i < e; ++i) {
HInstruction* instruction = environment->GetInstructionAt(i);
+ if (instruction == nullptr) {
+ continue;
+ }
bool should_be_live = ShouldBeLiveForEnvironment(current, instruction);
+ // If this environment use does not keep the instruction live, it does not
+ // affect the live range of that instruction.
if (should_be_live) {
CHECK(instruction->HasSsaIndex()) << instruction->DebugName();
live_in->SetBit(instruction->GetSsaIndex());
- }
- if (instruction != nullptr) {
- instruction->GetLiveInterval()->AddUse(
- current, environment, i, /* actual_user */ nullptr, should_be_live);
+ instruction->GetLiveInterval()->AddUse(current,
+ environment,
+ i,
+ /* actual_user */ nullptr);
}
}
}
diff --git a/compiler/optimizing/ssa_liveness_analysis.h b/compiler/optimizing/ssa_liveness_analysis.h
index f83bb52..83ca5bd 100644
--- a/compiler/optimizing/ssa_liveness_analysis.h
+++ b/compiler/optimizing/ssa_liveness_analysis.h
@@ -300,8 +300,7 @@
void AddUse(HInstruction* instruction,
HEnvironment* environment,
size_t input_index,
- HInstruction* actual_user = nullptr,
- bool keep_alive = false) {
+ HInstruction* actual_user = nullptr) {
bool is_environment = (environment != nullptr);
LocationSummary* locations = instruction->GetLocations();
if (actual_user == nullptr) {
@@ -359,12 +358,6 @@
uses_.push_front(*new_use);
}
- if (is_environment && !keep_alive) {
- // If this environment use does not keep the instruction live, it does not
- // affect the live range of that instruction.
- return;
- }
-
size_t start_block_position = instruction->GetBlock()->GetLifetimeStart();
if (first_range_ == nullptr) {
// First time we see a use of that interval.
@@ -1157,8 +1150,11 @@
* of an instruction that has a primitive type make the instruction live.
* If the graph does not have the debuggable property, the environment
* use has no effect, and may get a 'none' value after register allocation.
+ * (d) When compiling in OSR mode, all loops in the compiled method may be entered
+ * from the interpreter via SuspendCheck; such use in SuspendCheck makes the instruction
+ * live.
*
- * (b) and (c) are implemented through SsaLivenessAnalysis::ShouldBeLiveForEnvironment.
+ * (b), (c) and (d) are implemented through SsaLivenessAnalysis::ShouldBeLiveForEnvironment.
*/
class SsaLivenessAnalysis : public ValueObject {
public:
@@ -1259,14 +1255,18 @@
// Returns whether `instruction` in an HEnvironment held by `env_holder`
// should be kept live by the HEnvironment.
static bool ShouldBeLiveForEnvironment(HInstruction* env_holder, HInstruction* instruction) {
- if (instruction == nullptr) return false;
+ DCHECK(instruction != nullptr);
// A value that's not live in compiled code may still be needed in interpreter,
// due to code motion, etc.
if (env_holder->IsDeoptimize()) return true;
// A value live at a throwing instruction in a try block may be copied by
// the exception handler to its location at the top of the catch block.
if (env_holder->CanThrowIntoCatchBlock()) return true;
- if (instruction->GetBlock()->GetGraph()->IsDebuggable()) return true;
+ HGraph* graph = instruction->GetBlock()->GetGraph();
+ if (graph->IsDebuggable()) return true;
+ // When compiling in OSR mode, all loops in the compiled method may be entered
+ // from the interpreter via SuspendCheck; thus we need to preserve the environment.
+ if (env_holder->IsSuspendCheck() && graph->IsCompilingOsr()) return true;
return instruction->GetType() == DataType::Type::kReference;
}
diff --git a/compiler/optimizing/ssa_liveness_analysis_test.cc b/compiler/optimizing/ssa_liveness_analysis_test.cc
index b9bfbaa..ae5e4e7 100644
--- a/compiler/optimizing/ssa_liveness_analysis_test.cc
+++ b/compiler/optimizing/ssa_liveness_analysis_test.cc
@@ -134,12 +134,12 @@
static const char* const expected[] = {
"ranges: { [2,21) }, uses: { 15 17 21 }, { 15 19 } is_fixed: 0, is_split: 0 is_low: 0 "
"is_high: 0",
- "ranges: { [4,21) }, uses: { 19 21 }, { 15 19 } is_fixed: 0, is_split: 0 is_low: 0 "
+ "ranges: { [4,21) }, uses: { 19 21 }, { } is_fixed: 0, is_split: 0 is_low: 0 "
"is_high: 0",
- "ranges: { [6,21) }, uses: { 21 }, { 15 19 } is_fixed: 0, is_split: 0 is_low: 0 "
+ "ranges: { [6,21) }, uses: { 21 }, { } is_fixed: 0, is_split: 0 is_low: 0 "
"is_high: 0",
// Environment uses do not keep the non-reference argument alive.
- "ranges: { [8,10) }, uses: { }, { 15 19 } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
+ "ranges: { [8,10) }, uses: { }, { } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
// Environment uses keep the reference argument alive.
"ranges: { [10,19) }, uses: { }, { 15 19 } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
};
@@ -207,11 +207,11 @@
static const char* const expected[] = {
"ranges: { [2,23) }, uses: { 15 17 23 }, { 15 21 } is_fixed: 0, is_split: 0 is_low: 0 "
"is_high: 0",
- "ranges: { [4,23) }, uses: { 19 23 }, { 15 21 } is_fixed: 0, is_split: 0 is_low: 0 "
+ "ranges: { [4,23) }, uses: { 19 23 }, { 21 } is_fixed: 0, is_split: 0 is_low: 0 "
"is_high: 0",
- "ranges: { [6,23) }, uses: { 23 }, { 15 21 } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
+ "ranges: { [6,23) }, uses: { 23 }, { 21 } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
// Environment use in HDeoptimize keeps even the non-reference argument alive.
- "ranges: { [8,21) }, uses: { }, { 15 21 } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
+ "ranges: { [8,21) }, uses: { }, { 21 } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
// Environment uses keep the reference argument alive.
"ranges: { [10,21) }, uses: { }, { 15 21 } is_fixed: 0, is_split: 0 is_low: 0 is_high: 0",
};
diff --git a/test/466-get-live-vreg/get_live_vreg_jni.cc b/test/466-get-live-vreg/get_live_vreg_jni.cc
index 44ea0c9..58ffe04 100644
--- a/test/466-get-live-vreg/get_live_vreg_jni.cc
+++ b/test/466-get-live-vreg/get_live_vreg_jni.cc
@@ -36,32 +36,46 @@
ArtMethod* m = GetMethod();
std::string m_name(m->GetName());
- if (m_name.compare("testLiveArgument") == 0) {
+ if (m_name.compare("$noinline$testLiveArgument") == 0) {
found_method_ = true;
- uint32_t value = 0;
- CHECK(GetVReg(m, 0, kIntVReg, &value));
- CHECK_EQ(value, 42u);
- } else if (m_name.compare("$opt$noinline$testIntervalHole") == 0) {
+ CHECK_EQ(CodeItemDataAccessor(m->DexInstructionData()).RegistersSize(), 3u);
+ CheckOptimizedOutRegLiveness(m, 1, kIntVReg, true, 42);
+
+ uint32_t value;
+ CHECK(GetVReg(m, 2, kReferenceVReg, &value));
+ } else if (m_name.compare("$noinline$testIntervalHole") == 0) {
+ found_method_ = true;
uint32_t number_of_dex_registers =
CodeItemDataAccessor(m->DexInstructionData()).RegistersSize();
uint32_t dex_register_of_first_parameter = number_of_dex_registers - 2;
+ CheckOptimizedOutRegLiveness(m, dex_register_of_first_parameter, kIntVReg, true, 1);
+ } else if (m_name.compare("$noinline$testCodeSinking") == 0) {
found_method_ = true;
- uint32_t value = 0;
- if (GetCurrentQuickFrame() != nullptr &&
- GetCurrentOatQuickMethodHeader()->IsOptimized() &&
- !Runtime::Current()->IsJavaDebuggable()) {
- CHECK_EQ(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value), false);
- } else {
- CHECK(GetVReg(m, dex_register_of_first_parameter, kIntVReg, &value));
- CHECK_EQ(value, 1u);
- }
+ CheckOptimizedOutRegLiveness(m, 0, kReferenceVReg);
}
return true;
}
- // Value returned to Java to ensure the methods testSimpleVReg and testPairVReg
- // have been found and tested.
+ void CheckOptimizedOutRegLiveness(ArtMethod* m,
+ uint32_t dex_reg,
+ VRegKind vreg_kind,
+ bool check_val = false,
+ uint32_t expected = 0) REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint32_t value = 0;
+ if (GetCurrentQuickFrame() != nullptr &&
+ GetCurrentOatQuickMethodHeader()->IsOptimized() &&
+ !Runtime::Current()->IsJavaDebuggable()) {
+ CHECK_EQ(GetVReg(m, dex_reg, vreg_kind, &value), false);
+ } else {
+ CHECK(GetVReg(m, dex_reg, vreg_kind, &value));
+ if (check_val) {
+ CHECK_EQ(value, expected);
+ }
+ }
+ }
+
+ // Value returned to Java to ensure the required methods have been found and tested.
bool found_method_ = false;
};
diff --git a/test/466-get-live-vreg/src/Main.java b/test/466-get-live-vreg/src/Main.java
index 19032601..29a6901 100644
--- a/test/466-get-live-vreg/src/Main.java
+++ b/test/466-get-live-vreg/src/Main.java
@@ -18,9 +18,9 @@
public Main() {
}
- static int testLiveArgument(int arg) {
+ static int $noinline$testLiveArgument(int arg1, Integer arg2) {
doStaticNativeCallLiveVreg();
- return arg;
+ return arg1 + arg2.intValue();
}
static void moveArgToCalleeSave() {
@@ -31,7 +31,7 @@
}
}
- static void $opt$noinline$testIntervalHole(int arg, boolean test) {
+ static void $noinline$testIntervalHole(int arg, boolean test) {
// Move the argument to callee save to ensure it is in
// a readable register.
moveArgToCalleeSave();
@@ -53,16 +53,18 @@
public static void main(String[] args) {
System.loadLibrary(args[0]);
- if (testLiveArgument(staticField3) != staticField3) {
- throw new Error("Expected " + staticField3);
+ if ($noinline$testLiveArgument(staticField3, Integer.valueOf(1)) != staticField3 + 1) {
+ throw new Error("Expected " + staticField3 + 1);
}
- if (testLiveArgument(staticField3) != staticField3) {
- throw new Error("Expected " + staticField3);
+ if ($noinline$testLiveArgument(staticField3,Integer.valueOf(1)) != staticField3 + 1) {
+ throw new Error("Expected " + staticField3 + 1);
}
testWrapperIntervalHole(1, true);
testWrapperIntervalHole(1, false);
+
+ $noinline$testCodeSinking(1);
}
// Wrapper method to avoid inlining, which affects liveness
@@ -70,12 +72,25 @@
static void testWrapperIntervalHole(int arg, boolean test) {
try {
Thread.sleep(0);
- $opt$noinline$testIntervalHole(arg, test);
+ $noinline$testIntervalHole(arg, test);
} catch (Exception e) {
throw new Error(e);
}
}
+ // The value of dex register which originally holded "Object[] o = new Object[1];" will not be
+ // live at the call to doStaticNativeCallLiveVreg after code sinking optimizizaion.
+ static void $noinline$testCodeSinking(int x) {
+ Object[] o = new Object[1];
+ o[0] = o;
+ doStaticNativeCallLiveVreg();
+ if (doThrow) {
+ throw new Error(o.toString());
+ }
+ }
+
+ static boolean doThrow;
+
static int staticField1;
static int staticField2;
static int staticField3 = 42;
diff --git a/test/565-checker-condition-liveness/info.txt b/test/565-checker-condition-liveness/info.txt
index 67b6ceb..e716c04 100644
--- a/test/565-checker-condition-liveness/info.txt
+++ b/test/565-checker-condition-liveness/info.txt
Binary files differ
diff --git a/test/565-checker-condition-liveness/src/Main.java b/test/565-checker-condition-liveness/src/Main.java
index acfcecd..6b6619f 100644
--- a/test/565-checker-condition-liveness/src/Main.java
+++ b/test/565-checker-condition-liveness/src/Main.java
@@ -31,6 +31,82 @@
return (arg > 5.0f) ? 0 : -1;
}
+ /// CHECK-START: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[21,25]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,21,25]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,21,25]
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[21,25]
+ /// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
+ /// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
+ /// CHECK-DAG: BoundsCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:24
+ /// CHECK-DAG: TryBoundary
+
+ /// CHECK-START-DEBUGGABLE: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[11,21,25]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,21,25]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,21,25]
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[21,25]
+ /// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
+ /// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
+ /// CHECK-DAG: BoundsCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:24
+ /// CHECK-DAG: TryBoundary
+ //
+ // A value live at a throwing instruction in a try block may be copied by
+ // the exception handler to its location at the top of the catch block.
+ public static void testThrowIntoCatchBlock(int x, Object y, int[] a) {
+ try {
+ a[1] = x;
+ } catch (ArrayIndexOutOfBoundsException exception) {
+ }
+ }
+
+ /// CHECK-START: void Main.testBoundsCheck(int, java.lang.Object, int[]) liveness (after)
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,17,21]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,17,21]
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[]
+ /// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
+ /// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:16
+ /// CHECK-DAG: BoundsCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
+
+ /// CHECK-START-DEBUGGABLE: void Main.testBoundsCheck(int, java.lang.Object, int[]) liveness (after)
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[11,17,21]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,17,21]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,17,21]
+ /// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[17,21]
+ /// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
+ /// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:16
+ /// CHECK-DAG: BoundsCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
+ public static void testBoundsCheck(int x, Object y, int[] a) {
+ a[1] = x;
+ }
+
+ /// CHECK-START: void Main.testDeoptimize(int, java.lang.Object, int[]) liveness (after)
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[25]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[13,19,25]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[13,19,25]
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 env_uses:[25]
+ /// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:12
+ /// CHECK-DAG: NullCheck env:[[<<Const0>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:18
+ /// CHECK-DAG: Deoptimize env:[[<<Const0>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:24
+
+ /// CHECK-START-DEBUGGABLE: void Main.testDeoptimize(int, java.lang.Object, int[]) liveness (after)
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[13,19,25]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[13,19,25]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[13,19,25]
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0 env_uses:[19,25]
+ /// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:12
+ /// CHECK-DAG: NullCheck env:[[<<Const0>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:18
+ /// CHECK-DAG: Deoptimize env:[[<<Const0>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:24
+ //
+ // A value that's not live in compiled code may still be needed in interpreter,
+ // due to code motion, etc.
+ public static void testDeoptimize(int x, Object y, int[] a) {
+ a[0] = x;
+ a[1] = x;
+ }
+
+
/// CHECK-START: void Main.main(java.lang.String[]) liveness (after)
/// CHECK: <<X:i\d+>> ArrayLength uses:[<<UseInput:\d+>>]
/// CHECK: <<Y:i\d+>> StaticFieldGet uses:[<<UseInput>>]
@@ -44,7 +120,15 @@
if (x > y) {
System.nanoTime();
}
+
+ int val = 14;
+ int[] array = new int[2];
+ Integer intObj = Integer.valueOf(0);
+ testThrowIntoCatchBlock(val, intObj, array);
+ testBoundsCheck(val, intObj, array);
+ testDeoptimize(val, intObj, array);
}
+
public static int field = 42;
}
diff --git a/test/639-checker-code-sinking/src/Main.java b/test/639-checker-code-sinking/src/Main.java
index a1c30f7..8efac92 100644
--- a/test/639-checker-code-sinking/src/Main.java
+++ b/test/639-checker-code-sinking/src/Main.java
@@ -343,6 +343,37 @@
}
}
+ static native void doStaticNativeCallLiveVreg();
+
+ // Test ensures that 'o' has been moved into the if despite the InvokeStaticOrDirect.
+ //
+ /// CHECK-START: void Main.testSinkingOverInvoke() code_sinking (before)
+ /// CHECK: <<Int1:i\d+>> IntConstant 1
+ /// CHECK: <<Int0:i\d+>> IntConstant 0
+ /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object[]
+ /// CHECK-NOT: begin_block
+ /// CHECK: NewArray [<<LoadClass>>,<<Int1>>]
+ /// CHECK: If
+ /// CHECK: begin_block
+ /// CHECK: Throw
+
+ /// CHECK-START: void Main.testSinkingOverInvoke() code_sinking (after)
+ /// CHECK: <<Int1:i\d+>> IntConstant 1
+ /// CHECK: <<Int0:i\d+>> IntConstant 0
+ /// CHECK: If
+ /// CHECK: begin_block
+ /// CHECK: <<LoadClass:l\d+>> LoadClass class_name:java.lang.Object[]
+ /// CHECK: NewArray [<<LoadClass>>,<<Int1>>]
+ /// CHECK: Throw
+ static void testSinkingOverInvoke() {
+ Object[] o = new Object[1];
+ o[0] = o;
+ doStaticNativeCallLiveVreg();
+ if (doThrow) {
+ throw new Error(o.toString());
+ }
+ }
+
public String $opt$noinline$toString() {
return "" + intField;
}