| /* |
| * Copyright (C) 2023 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. |
| */ |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.Method; |
| import java.util.Arrays; |
| import java.util.stream.Collectors; |
| |
| public class Main { |
| public static void main(String[] args) throws Exception { |
| System.loadLibrary(args[0]); |
| |
| if (!hasJit()) { |
| // Test requires JIT for creating profiling infos. |
| return; |
| } |
| |
| File file = null; |
| try { |
| file = createTempFile(); |
| String codePath = System.getenv("DEX_LOCATION") + "/2271-profile-inline-cache.jar"; |
| VMRuntime.registerAppInfo("test.app", file.getPath(), file.getPath(), |
| new String[] {codePath}, VMRuntime.CODE_PATH_TYPE_PRIMARY_APK); |
| |
| Derived1 derived1 = new Derived1(); |
| Derived2 derived2 = new Derived2(); |
| |
| // This method is below the inline cache threshold. |
| Method method1 = Main.class.getDeclaredMethod("$noinline$method1", Base.class); |
| ensureJitBaselineCompiled(method1); |
| // The baseline code doesn't update the inline cache if we are marking. Force a GC now |
| // so that GC will unlikely take place during the method calls below. |
| Runtime.getRuntime().gc(); |
| $noinline$method1(derived1); |
| for (int i = 0; i < 2998; i++) { |
| $noinline$method1(derived2); |
| } |
| checkMethodHasNoInlineCache(file, method1); |
| |
| // This method is right on the inline cache threshold. |
| Method method2 = Main.class.getDeclaredMethod("$noinline$method2", Base.class); |
| ensureJitBaselineCompiled(method2); |
| // The baseline code doesn't update the inline cache if we are marking. Force a GC now |
| // so that GC will unlikely take place during the method calls below. |
| Runtime.getRuntime().gc(); |
| $noinline$method2(derived1); |
| for (int i = 0; i < 2999; i++) { |
| $noinline$method2(derived2); |
| } |
| checkMethodHasInlineCache(file, method2, Derived1.class, Derived2.class); |
| |
| // This method is above the inline cache threshold. |
| Method method3 = Main.class.getDeclaredMethod("$noinline$method3", Base.class); |
| ensureJitBaselineCompiled(method3); |
| // The baseline code doesn't update the inline cache if we are marking. Force a GC now |
| // so that GC will unlikely take place during the method calls below. |
| Runtime.getRuntime().gc(); |
| for (int i = 0; i < 10000; i++) { |
| $noinline$method3(derived1); |
| } |
| for (int i = 0; i < 10000; i++) { |
| $noinline$method3(derived2); |
| } |
| checkMethodHasInlineCache(file, method3, Derived1.class, Derived2.class); |
| |
| // This method is above the JIT threshold. |
| Method method4 = Main.class.getDeclaredMethod("$noinline$method4", Base.class); |
| ensureJitBaselineCompiled(method4); |
| // The baseline code doesn't update the inline cache if we are marking. Force a GC now |
| // so that GC will unlikely take place during the method calls below. |
| Runtime.getRuntime().gc(); |
| $noinline$method4(derived1); |
| $noinline$method4(derived2); |
| ensureMethodJitCompiled(method4); |
| checkMethodHasInlineCache(file, method4, Derived1.class, Derived2.class); |
| } finally { |
| if (file != null) { |
| file.delete(); |
| } |
| } |
| } |
| |
| public static void $noinline$method1(Base obj) { |
| obj.f(); |
| } |
| |
| public static void $noinline$method2(Base obj) { |
| obj.f(); |
| } |
| |
| public static void $noinline$method3(Base obj) { |
| obj.f(); |
| } |
| |
| public static void $noinline$method4(Base obj) { |
| obj.f(); |
| } |
| |
| public static class Base { |
| public void f() {} |
| } |
| |
| public static class Derived1 extends Base { |
| @Override |
| public void f() {} |
| } |
| |
| public static class Derived2 extends Base { |
| @Override |
| public void f() {} |
| } |
| |
| private static void checkMethodHasInlineCache(File file, Method m, Class<?>... targetTypes) { |
| ensureProfileProcessing(); |
| if (!hasInlineCacheInProfile(file.getPath(), m, targetTypes)) { |
| throw new RuntimeException("Expected method " + m |
| + " to have inline cache in the profile with target types " |
| + Arrays.stream(targetTypes) |
| .map(Class::getName) |
| .collect(Collectors.joining(", "))); |
| } |
| } |
| |
| private static void checkMethodHasNoInlineCache(File file, Method m) { |
| ensureProfileProcessing(); |
| if (hasInlineCacheInProfile(file.getPath(), m)) { |
| throw new RuntimeException( |
| "Expected method " + m + " not to have inline cache in the profile"); |
| } |
| } |
| |
| public static void ensureJitBaselineCompiled(Method method) { |
| ensureJitBaselineCompiled(method.getDeclaringClass(), method.getName()); |
| } |
| public static native void ensureMethodJitCompiled(Method method); |
| public static native void ensureJitBaselineCompiled(Class<?> cls, String methodName); |
| public static native void ensureProfileProcessing(); |
| public static native boolean hasInlineCacheInProfile( |
| String profile, Method method, Class<?>... targetTypes); |
| public static native boolean hasJit(); |
| |
| private static final String TEMP_FILE_NAME_PREFIX = "temp"; |
| private static final String TEMP_FILE_NAME_SUFFIX = "-file"; |
| |
| private static File createTempFile() throws Exception { |
| try { |
| return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); |
| } catch (IOException e) { |
| System.setProperty("java.io.tmpdir", "/data/local/tmp"); |
| try { |
| return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); |
| } catch (IOException e2) { |
| System.setProperty("java.io.tmpdir", "/sdcard"); |
| return File.createTempFile(TEMP_FILE_NAME_PREFIX, TEMP_FILE_NAME_SUFFIX); |
| } |
| } |
| } |
| |
| private static void sleep(long millis) { |
| try { |
| Thread.sleep(millis); |
| } catch (InterruptedException ignored) { |
| } |
| } |
| |
| private static class VMRuntime { |
| public static final int CODE_PATH_TYPE_PRIMARY_APK = 1 << 0; |
| private static final Method registerAppInfoMethod; |
| |
| static { |
| try { |
| Class<? extends Object> c = Class.forName("dalvik.system.VMRuntime"); |
| registerAppInfoMethod = c.getDeclaredMethod("registerAppInfo", String.class, |
| String.class, String.class, String[].class, int.class); |
| } catch (Exception e) { |
| throw new RuntimeException(e); |
| } |
| } |
| |
| public static void registerAppInfo(String packageName, String curProfile, String refProfile, |
| String[] codePaths, int codePathsType) throws Exception { |
| registerAppInfoMethod.invoke( |
| null, packageName, curProfile, refProfile, codePaths, codePathsType); |
| } |
| } |
| } |