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