diff options
author | 2018-05-30 20:07:43 +0100 | |
---|---|---|
committer | 2018-06-15 17:21:02 +0100 | |
commit | d6750532bae8dbb072e78e410c9a1f14aa071a5b (patch) | |
tree | bab48e4844d3ed54924b95f0bd1c51ccbbb464b6 | |
parent | 0e3a330f4a545a6a2d352bd4a803c8387f54e76b (diff) |
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
-rw-r--r-- | compiler/optimizing/ssa_liveness_analysis.cc | 13 | ||||
-rw-r--r-- | compiler/optimizing/ssa_liveness_analysis.h | 22 | ||||
-rw-r--r-- | compiler/optimizing/ssa_liveness_analysis_test.cc | 12 | ||||
-rw-r--r-- | test/466-get-live-vreg/get_live_vreg_jni.cc | 46 | ||||
-rw-r--r-- | test/466-get-live-vreg/src/Main.java | 31 | ||||
-rw-r--r-- | test/565-checker-condition-liveness/info.txt | 2 | ||||
-rw-r--r-- | test/565-checker-condition-liveness/src/Main.java | 84 | ||||
-rw-r--r-- | test/639-checker-code-sinking/src/Main.java | 31 |
8 files changed, 195 insertions, 46 deletions
diff --git a/compiler/optimizing/ssa_liveness_analysis.cc b/compiler/optimizing/ssa_liveness_analysis.cc index f6bd05269e..2f782f39fc 100644 --- a/compiler/optimizing/ssa_liveness_analysis.cc +++ b/compiler/optimizing/ssa_liveness_analysis.cc @@ -195,14 +195,19 @@ void SsaLivenessAnalysis::ComputeLiveRanges() { // 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 f83bb52b69..83ca5bd5fa 100644 --- a/compiler/optimizing/ssa_liveness_analysis.h +++ b/compiler/optimizing/ssa_liveness_analysis.h @@ -300,8 +300,7 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { 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 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { 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 @@ class LiveInterval : public ArenaObject<kArenaAllocSsaLiveness> { * 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 @@ class SsaLivenessAnalysis : public ValueObject { // 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 b9bfbaa173..ae5e4e7176 100644 --- a/compiler/optimizing/ssa_liveness_analysis_test.cc +++ b/compiler/optimizing/ssa_liveness_analysis_test.cc @@ -134,12 +134,12 @@ TEST_F(SsaLivenessAnalysisTest, TestAput) { 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 @@ TEST_F(SsaLivenessAnalysisTest, TestDeoptimize) { 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 44ea0c9877..58ffe04fee 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 @@ class TestVisitor : public StackVisitor { 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; + 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 value = 0; - CHECK(GetVReg(m, 0, kIntVReg, &value)); - CHECK_EQ(value, 42u); - } else if (m_name.compare("$opt$noinline$testIntervalHole") == 0) { 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 19032601fa..29a6901a70 100644 --- a/test/466-get-live-vreg/src/Main.java +++ b/test/466-get-live-vreg/src/Main.java @@ -18,9 +18,9 @@ public class Main { 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 @@ public class Main { } } - 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 class Main { 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 @@ public class Main { 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 67b6ceb53f..e716c04491 100644 --- a/test/565-checker-condition-liveness/info.txt +++ b/test/565-checker-condition-liveness/info.txt @@ -1 +1 @@ -Test the use positions of inputs of non-materialized conditions.
\ No newline at end of file +Test the results of liveness analysis e.g. use positions of inputs of non-materialized conditions.
\ No newline at end of file diff --git a/test/565-checker-condition-liveness/src/Main.java b/test/565-checker-condition-liveness/src/Main.java index acfcecdba8..6b6619fa43 100644 --- a/test/565-checker-condition-liveness/src/Main.java +++ b/test/565-checker-condition-liveness/src/Main.java @@ -31,6 +31,82 @@ public class Main { 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 @@ public class Main { 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 a1c30f7b4e..8efac92c34 100644 --- a/test/639-checker-code-sinking/src/Main.java +++ b/test/639-checker-code-sinking/src/Main.java @@ -343,6 +343,37 @@ public class Main { } } + 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; } |