diff options
author | Roland Levillain <rpl@google.com> | 2015-04-24 16:43:49 +0100 |
---|---|---|
committer | Roland Levillain <rpl@google.com> | 2015-04-24 16:43:49 +0100 |
commit | 4c0eb42259d790fddcd9978b66328dbb3ab65615 (patch) | |
tree | 9d1ac505dfd4d0225f479d860b72a58747c8f6ce /test/478-checker-clinit-check-pruning | |
parent | 223f2f5b2a20ca8246da1523494900a2424d5956 (diff) | |
download | art-4c0eb42259d790fddcd9978b66328dbb3ab65615.zip art-4c0eb42259d790fddcd9978b66328dbb3ab65615.tar.gz art-4c0eb42259d790fddcd9978b66328dbb3ab65615.tar.bz2 |
Ensure inlined static calls perform clinit checks in Optimizing.
Calls to static methods have implicit class initialization
(clinit) checks of the method's declaring class in
Optimizing. However, when such a static call is inlined,
the implicit clinit check vanishes, possibly leading to an
incorrect behavior.
To ensure that inlining static methods does not change the
behavior of a program, add explicit class initialization
checks (art::HClinitCheck) as well as load class
instructions (art::HLoadClass) as last input of static
calls (art::HInvokeStaticOrDirect) in Optimizing' control
flow graphs, when the declaring class is reachable and not
known to be already initialized. Then when considering the
inlining of a static method call, proceed only if the method
has no implicit clinit check requirement.
The added explicit clinit checks are already removed by the
art::PrepareForRegisterAllocation visitor. This CL also
extends this visitor to turn explicit clinit checks from
static invokes into implicit ones after the inlining step,
by removing the added art::HLoadClass nodes mentioned
hereinbefore.
Change-Id: I9ba452b8bd09ae1fdd9a3797ef556e3e7e19c651
Diffstat (limited to 'test/478-checker-clinit-check-pruning')
-rw-r--r-- | test/478-checker-clinit-check-pruning/expected.txt | 4 | ||||
-rw-r--r-- | test/478-checker-clinit-check-pruning/info.txt | 3 | ||||
-rw-r--r-- | test/478-checker-clinit-check-pruning/src/Main.java | 200 |
3 files changed, 207 insertions, 0 deletions
diff --git a/test/478-checker-clinit-check-pruning/expected.txt b/test/478-checker-clinit-check-pruning/expected.txt new file mode 100644 index 0000000..39d9d75 --- /dev/null +++ b/test/478-checker-clinit-check-pruning/expected.txt @@ -0,0 +1,4 @@ +Main$ClassWithClinit1's static initializer +Main$ClassWithClinit2's static initializer +Main$ClassWithClinit3's static initializer +Main$ClassWithClinit4's static initializer diff --git a/test/478-checker-clinit-check-pruning/info.txt b/test/478-checker-clinit-check-pruning/info.txt new file mode 100644 index 0000000..deb64de --- /dev/null +++ b/test/478-checker-clinit-check-pruning/info.txt @@ -0,0 +1,3 @@ +Test ensuring class initializations checks (and load class instructions) +added by the graph builder during the construction of a static invoke +are properly pruned. diff --git a/test/478-checker-clinit-check-pruning/src/Main.java b/test/478-checker-clinit-check-pruning/src/Main.java new file mode 100644 index 0000000..5370f86 --- /dev/null +++ b/test/478-checker-clinit-check-pruning/src/Main.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +public class Main { + + /* + * Ensure an inlined static invoke explicitly triggers the + * initialization check of the called method's declaring class, and + * that the corresponding load class instruction does not get + * removed before register allocation & code generation. + */ + + // CHECK-START: void Main.invokeStaticInlined() builder (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + // CHECK-DAG: InvokeStaticOrDirect [ [[ClinitCheck]] ] + + // CHECK-START: void Main.invokeStaticInlined() inliner (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + + // CHECK-START: void Main.invokeStaticInlined() inliner (after) + // CHECK-NOT: InvokeStaticOrDirect + + // The following checks ensure the clinit check instruction added by + // the builder is pruned by the PrepareForRegisterAllocation, while + // the load class instruction is preserved. As the control flow + // graph is not dumped after (nor before) this step, we check the + // CFG as it is before the next pass (liveness analysis) instead. + + // CHECK-START: void Main.invokeStaticInlined() liveness (before) + // CHECK-DAG: LoadClass + + // CHECK-START: void Main.invokeStaticInlined() liveness (before) + // CHECK-NOT: ClinitCheck + // CHECK-NOT: InvokeStaticOrDirect + + static void invokeStaticInlined() { + ClassWithClinit1.$opt$inline$StaticMethod(); + } + + static class ClassWithClinit1 { + static { + System.out.println("Main$ClassWithClinit1's static initializer"); + } + + static void $opt$inline$StaticMethod() { + } + } + + /* + * Ensure a non-inlined static invoke eventually has an implicit + * initialization check of the called method's declaring class. + */ + + // CHECK-START: void Main.invokeStaticNotInlined() builder (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + // CHECK-DAG: InvokeStaticOrDirect [ [[ClinitCheck]] ] + + // CHECK-START: void Main.invokeStaticNotInlined() inliner (after) + // CHECK-DAG: [[LoadClass:l\d+]] LoadClass + // CHECK-DAG: [[ClinitCheck:l\d+]] ClinitCheck [ [[LoadClass]] ] + // CHECK-DAG: InvokeStaticOrDirect [ [[ClinitCheck]] ] + + // The following checks ensure the clinit check and load class + // instructions added by the builder are pruned by the + // PrepareForRegisterAllocation. As the control flow graph is not + // dumped after (nor before) this step, we check the CFG as it is + // before the next pass (liveness analysis) instead. + + // CHECK-START: void Main.invokeStaticNotInlined() liveness (before) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main.invokeStaticNotInlined() liveness (before) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + static void invokeStaticNotInlined() { + ClassWithClinit2.staticMethod(); + } + + static class ClassWithClinit2 { + static { + System.out.println("Main$ClassWithClinit2's static initializer"); + } + + static boolean doThrow = false; + + static void staticMethod() { + if (doThrow) { + // Try defeating inlining. + throw new Error(); + } + } + } + + /* + * Ensure an inlined call to a static method whose declaring class + * is statically known to have been initialized does not require an + * explicit clinit check. + */ + + // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() builder (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + // CHECK-START: void Main$ClassWithClinit3.invokeStaticInlined() inliner (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + // CHECK-NOT: InvokeStaticOrDirect + + static class ClassWithClinit3 { + static void invokeStaticInlined() { + // The invocation of invokeStaticInlined triggers the + // initialization of ClassWithClinit3, meaning that the + // hereinbelow call to $opt$inline$StaticMethod does not need a + // clinit check. + $opt$inline$StaticMethod(); + } + + static { + System.out.println("Main$ClassWithClinit3's static initializer"); + } + + static void $opt$inline$StaticMethod() { + } + } + + /* + * Ensure an non-inlined call to a static method whose declaring + * class is statically known to have been initialized does not + * require an explicit clinit check. + */ + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() builder (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after) + // CHECK-DAG: InvokeStaticOrDirect + + // CHECK-START: void Main$ClassWithClinit4.invokeStaticNotInlined() inliner (after) + // CHECK-NOT: LoadClass + // CHECK-NOT: ClinitCheck + + static class ClassWithClinit4 { + static void invokeStaticNotInlined() { + // The invocation of invokeStaticNotInlined triggers the + // initialization of ClassWithClinit4, meaning that the + // hereinbelow call to staticMethod does not need a clinit + // check. + staticMethod(); + } + + static { + System.out.println("Main$ClassWithClinit4's static initializer"); + } + + static boolean doThrow = false; + + static void staticMethod() { + if (doThrow) { + // Try defeating inlining. + throw new Error(); + } + } + } + + // TODO: Add a test for the case of a static method whose declaring + // class type index is not available (i.e. when `storage_index` + // equals `DexFile::kDexNoIndex` in + // art::HGraphBuilder::BuildInvoke). + + public static void main(String[] args) { + invokeStaticInlined(); + invokeStaticNotInlined(); + ClassWithClinit3.invokeStaticInlined(); + ClassWithClinit4.invokeStaticNotInlined(); + } +} |