xref: /aosp_15_r20/art/test/2271-profile-inline-cache/src/Main.java (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 import java.io.File;
18 import java.io.IOException;
19 import java.lang.reflect.Method;
20 import java.util.Arrays;
21 import java.util.stream.Collectors;
22 
23 public class Main {
24     private static File sFile = null;
25     private static Method sMethod1 = null;
26     private static Method sMethod2 = null;
27     private static Method sMethod3 = null;
28     private static Method sMethod4 = null;
29     private static Method sMethod5 = null;
30 
main(String[] args)31     public static void main(String[] args) throws Exception {
32         System.loadLibrary(args[0]);
33 
34         if (!hasJit()) {
35             // Test requires JIT for creating profiling infos.
36             return;
37         }
38 
39         sMethod1 = Main.class.getDeclaredMethod("$noinline$method1", Base.class);
40         sMethod2 = Main.class.getDeclaredMethod("$noinline$method2", Base.class);
41         sMethod3 = Main.class.getDeclaredMethod("$noinline$method3", Base.class);
42         sMethod4 = Main.class.getDeclaredMethod("$noinline$method4", Base.class);
43         sMethod5 = Main.class.getDeclaredMethod("$noinline$method5", Base.class);
44 
45         sFile = createTempFile();
46         sFile.deleteOnExit();
47         String codePath = System.getenv("DEX_LOCATION") + "/2271-profile-inline-cache.jar";
48         VMRuntime.registerAppInfo("test.app", sFile.getPath(), sFile.getPath(),
49                 new String[] {codePath}, VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
50 
51         for (int i = 0; i < 10; i++) {
52             try {
53                 test();
54                 return;
55             } catch (ScopedAssertNoGc.NoGcAssertionFailure e) {
56                 // This should rarely happen. When it happens, reset the state, delete the profile,
57                 // and try again.
58                 reset();
59                 sFile.delete();
60             }
61         }
62 
63         // The possibility of hitting this line only exists in theory, unless the test is wrong.
64         throw new RuntimeException("NoGcAssertionFailure occurred 10 times");
65     }
66 
test()67     private static void test() throws Exception {
68         Derived1 derived1 = new Derived1();
69         Derived2 derived2 = new Derived2();
70 
71         // This method is below the inline cache threshold.
72         ensureJitBaselineCompiled(sMethod1);
73         try (ScopedAssertNoGc noGc = new ScopedAssertNoGc()) {
74             $noinline$method1(derived1);
75             for (int i = 0; i < 2998; i++) {
76                 $noinline$method1(derived2);
77             }
78         }
79         checkMethodHasNoInlineCache(sFile, sMethod1);
80 
81         // This method is right on the inline cache threshold.
82         ensureJitBaselineCompiled(sMethod2);
83         try (ScopedAssertNoGc noGc = new ScopedAssertNoGc()) {
84             $noinline$method2(derived1);
85             for (int i = 0; i < 2999; i++) {
86                 $noinline$method2(derived2);
87             }
88         }
89         checkMethodHasInlineCache(sFile, sMethod2, Derived1.class, Derived2.class);
90 
91         // This method is above the inline cache threshold.
92         ensureJitBaselineCompiled(sMethod3);
93         try (ScopedAssertNoGc noGc = new ScopedAssertNoGc()) {
94             for (int i = 0; i < 10000; i++) {
95                 $noinline$method3(derived1);
96             }
97             for (int i = 0; i < 10000; i++) {
98                 $noinline$method3(derived2);
99             }
100         }
101         checkMethodHasInlineCache(sFile, sMethod3, Derived1.class, Derived2.class);
102 
103         // This method is above the JIT threshold.
104         ensureJitBaselineCompiled(sMethod4);
105         try (ScopedAssertNoGc noGc = new ScopedAssertNoGc()) {
106             $noinline$method4(derived1);
107             $noinline$method4(derived2);
108         }
109         ensureMethodJitCompiled(sMethod4);
110         checkMethodHasInlineCache(sFile, sMethod4, Derived1.class, Derived2.class);
111 
112         // This method is above the JIT threshold.
113         ensureJitBaselineCompiled(sMethod5);
114         try (ScopedAssertNoGc noGc = new ScopedAssertNoGc()) {
115             $noinline$method5(derived1);
116             $noinline$method5(derived2);
117         }
118         ensureMethodJitCompiled(sMethod5);
119         // We currently do not encode inlined inline caches.
120         checkMethodHasNoInlineCache(sFile, sMethod5);
121     }
122 
reset()123     private static void reset() {
124         removeJitCompiledMethod(sMethod1, false /* releaseMemory */);
125         removeJitCompiledMethod(sMethod2, false /* releaseMemory */);
126         removeJitCompiledMethod(sMethod3, false /* releaseMemory */);
127         removeJitCompiledMethod(sMethod4, false /* releaseMemory */);
128     }
129 
$noinline$method1(Base obj)130     public static void $noinline$method1(Base obj) {
131         obj.f();
132     }
133 
$noinline$method2(Base obj)134     public static void $noinline$method2(Base obj) {
135         obj.f();
136     }
137 
$noinline$method3(Base obj)138     public static void $noinline$method3(Base obj) {
139         obj.f();
140     }
141 
$noinline$method4(Base obj)142     public static void $noinline$method4(Base obj) {
143         obj.f();
144     }
145 
$noinline$method5(Base obj)146     public static void $noinline$method5(Base obj) {
147         $inline$callF(obj);
148     }
149 
$inline$callF(Base obj)150     public static void $inline$callF(Base obj) {
151       obj.f();
152     }
153 
154     public static class Base {
f()155         public void f() {}
156     }
157 
158     public static class Derived1 extends Base {
159         @Override
f()160         public void f() {}
161     }
162 
163     public static class Derived2 extends Base {
164         @Override
f()165         public void f() {}
166     }
167 
checkMethodHasInlineCache(File file, Method m, Class<?>... targetTypes)168     private static void checkMethodHasInlineCache(File file, Method m, Class<?>... targetTypes) {
169         ensureProfileProcessing();
170         if (!hasInlineCacheInProfile(file.getPath(), m, targetTypes)) {
171             throw new RuntimeException("Expected method " + m
172                     + " to have inline cache in the profile with target types "
173                     + Arrays.stream(targetTypes)
174                               .map(Class::getName)
175                               .collect(Collectors.joining(", ")));
176         }
177     }
178 
checkMethodHasNoInlineCache(File file, Method m)179     private static void checkMethodHasNoInlineCache(File file, Method m) {
180         ensureProfileProcessing();
181         if (hasInlineCacheInProfile(file.getPath(), m)) {
182             throw new RuntimeException(
183                     "Expected method " + m + " not to have inline cache in the profile");
184         }
185     }
186 
ensureJitBaselineCompiled(Method method)187     public static void ensureJitBaselineCompiled(Method method) {
188         ensureJitBaselineCompiled(method.getDeclaringClass(), method.getName());
189     }
ensureMethodJitCompiled(Method method)190     public static native void ensureMethodJitCompiled(Method method);
ensureJitBaselineCompiled(Class<?> cls, String methodName)191     public static native void ensureJitBaselineCompiled(Class<?> cls, String methodName);
ensureProfileProcessing()192     public static native void ensureProfileProcessing();
hasInlineCacheInProfile( String profile, Method method, Class<?>... targetTypes)193     public static native boolean hasInlineCacheInProfile(
194             String profile, Method method, Class<?>... targetTypes);
hasJit()195     public static native boolean hasJit();
getCurrentGcNum()196     public static native int getCurrentGcNum();
removeJitCompiledMethod(Method method, boolean releaseMemory)197     public static native boolean removeJitCompiledMethod(Method method, boolean releaseMemory);
198 
199     private static final String TEMP_FILE_NAME_PREFIX = "temp";
200     private static final String TEMP_FILE_NAME_SUFFIX = "-file";
201 
createTempFile()202     private static File createTempFile() throws Exception {
203         try {
204             return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
205         } catch (IOException e) {
206             System.setProperty("java.io.tmpdir", "/data/local/tmp");
207             try {
208                 return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
209             } catch (IOException e2) {
210                 System.setProperty("java.io.tmpdir", "/sdcard");
211                 return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX);
212             }
213         }
214     }
215 
216     private static class VMRuntime {
217         public static final int CODE_PATH_TYPE_PRIMARY_APK = 1 << 0;
218         private static final Method registerAppInfoMethod;
219 
220         static {
221             try {
222                 Class<? extends Object> c = Class.forName("dalvik.system.VMRuntime");
223                 registerAppInfoMethod = c.getDeclaredMethod("registerAppInfo", String.class,
224                         String.class, String.class, String[].class, int.class);
225             } catch (Exception e) {
226                 throw new RuntimeException(e);
227             }
228         }
229 
registerAppInfo(String packageName, String curProfile, String refProfile, String[] codePaths, int codePathsType)230         public static void registerAppInfo(String packageName, String curProfile, String refProfile,
231                 String[] codePaths, int codePathsType) throws Exception {
232             registerAppInfoMethod.invoke(
233                     null, packageName, curProfile, refProfile, codePaths, codePathsType);
234         }
235     }
236 
237     // This scope is intended to guard code that doesn't expect GC to take place. Because we can't
238     // really prevent GC in Java code (calling a native method that enters a GCCriticalSection will
239     // cause the runtime to hang forever when transitioning from native back to Java), this is a
240     // workaround that forces a GC at the beginning so that GC will unlikely take place within the
241     // scope. If a GC still takes place within the scope, this will throw NoGcAssertionFailure.
242     //
243     // The baseline code doesn't update the inline cache if we are marking, so we use this scope to
244     // guard calls to virtual methods for which we want inline cache to be updated.
245     private static class ScopedAssertNoGc implements AutoCloseable {
246         private final int mLastGcNum;
247 
ScopedAssertNoGc()248         public ScopedAssertNoGc() {
249             System.gc();
250             mLastGcNum = getCurrentGcNum();
251         }
252 
253         @Override
close()254         public void close() throws NoGcAssertionFailure {
255             int currentGcNum = getCurrentGcNum();
256             if (currentGcNum != mLastGcNum) {
257                 throw new NoGcAssertionFailure(
258                         String.format("GC happened within the scope (before: %d, after: %d)",
259                                 mLastGcNum, currentGcNum));
260             }
261         }
262 
263         public static class NoGcAssertionFailure extends Exception {
NoGcAssertionFailure(String message)264             public NoGcAssertionFailure(String message) {
265                 super(message);
266             }
267         }
268     }
269 }
270