summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.mk118
-rw-r--r--TEST_MAPPING1440
-rw-r--r--adbconnection/adbconnection.cc257
-rw-r--r--adbconnection/adbconnection.h42
-rw-r--r--artd/artd.cc198
-rw-r--r--artd/artd.h40
-rw-r--r--artd/artd_test.cc166
-rw-r--r--artd/binder/com/android/server/art/IArtd.aidl38
-rwxr-xr-xbuild/apex/art_apex_test.py1
-rw-r--r--cmdline/cmdline_parser_test.cc17
-rw-r--r--cmdline/cmdline_types.h6
-rw-r--r--compiler/driver/compiler_options.cc4
-rw-r--r--compiler/driver/compiler_options.h2
-rw-r--r--compiler/jit/jit_compiler.cc3
-rw-r--r--compiler/jni/jni_compiler_test.cc6
-rw-r--r--compiler/optimizing/code_generator.h18
-rw-r--r--compiler/optimizing/code_generator_arm64.cc12
-rw-r--r--compiler/optimizing/code_generator_arm_vixl.cc12
-rw-r--r--compiler/optimizing/code_generator_riscv64.cc4
-rw-r--r--compiler/optimizing/code_generator_x86.cc10
-rw-r--r--compiler/optimizing/code_generator_x86_64.cc9
-rw-r--r--compiler/optimizing/loop_optimization.cc97
-rw-r--r--compiler/optimizing/loop_optimization.h15
-rw-r--r--compiler/optimizing/nodes.h2
-rw-r--r--compiler/optimizing/optimizing_compiler.cc6
-rw-r--r--compiler/utils/riscv64/assembler_riscv64.cc241
-rw-r--r--compiler/utils/riscv64/assembler_riscv64.h32
-rw-r--r--compiler/utils/riscv64/assembler_riscv64_test.cc530
-rw-r--r--dex2oat/Android.bp12
-rw-r--r--dex2oat/driver/compiler_driver.cc12
-rw-r--r--dex2oat/linker/image_writer.cc98
-rw-r--r--dex2oat/linker/image_writer.h12
-rw-r--r--dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl13
-rw-r--r--dexopt_chroot_setup/dexopt_chroot_setup.cc113
-rw-r--r--dexopt_chroot_setup/dexopt_chroot_setup.h11
-rw-r--r--dexopt_chroot_setup/dexopt_chroot_setup_test.cc14
-rw-r--r--libartbase/base/common_art_test.cc32
-rw-r--r--libartbase/base/common_art_test.h6
-rw-r--r--libartbase/base/debugstore.h39
-rw-r--r--libartbase/base/memory_tool.h2
-rw-r--r--libartbase/base/pidfd.h45
-rw-r--r--libartpalette/apex/palette.cc7
-rw-r--r--libartpalette/apex/palette_test.cc27
-rw-r--r--libartpalette/include/palette/palette_method_list.h19
-rw-r--r--libartpalette/libartpalette.map6
-rw-r--r--libartpalette/system/palette_fake.cc6
-rw-r--r--libartservice/service/Android.bp1
-rw-r--r--libartservice/service/java/com/android/server/art/AidlUtils.java8
-rw-r--r--libartservice/service/java/com/android/server/art/ArtJni.java52
-rw-r--r--libartservice/service/java/com/android/server/art/ArtManagerLocal.java30
-rw-r--r--libartservice/service/java/com/android/server/art/ArtShellCommand.java156
-rw-r--r--libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java2
-rw-r--r--libartservice/service/java/com/android/server/art/DexoptHelper.java40
-rw-r--r--libartservice/service/java/com/android/server/art/Dexopter.java34
-rw-r--r--libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java318
-rw-r--r--libartservice/service/java/com/android/server/art/PrimaryDexopter.java11
-rw-r--r--libartservice/service/java/com/android/server/art/SecondaryDexopter.java6
-rw-r--r--libartservice/service/java/com/android/server/art/Utils.java18
-rw-r--r--libartservice/service/java/com/android/server/art/model/DexoptResult.java11
-rw-r--r--libartservice/service/java/com/android/server/art/model/OperationProgress.java22
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java157
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java50
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java78
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java8
-rw-r--r--libartservice/service/java/com/android/server/art/prereboot/PreRebootStatsReporter.java370
-rw-r--r--libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java12
-rw-r--r--libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java20
-rw-r--r--libartservice/service/javatests/com/android/server/art/PreRebootDexoptJobTest.java385
-rw-r--r--libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java12
-rw-r--r--libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java99
-rw-r--r--libartservice/service/javatests/com/android/server/art/prereboot/PreRebootStatsReporterTest.java500
-rw-r--r--libartservice/service/native/service.cc31
-rw-r--r--libartservice/service/proto/pre_reboot_stats.proto73
-rw-r--r--libarttools/art_exec_test.cc66
-rw-r--r--libarttools/include/tools/system_properties.h5
-rw-r--r--libarttools/include/tools/tools.h6
-rw-r--r--libarttools/testing.h93
-rw-r--r--libarttools/tools.cc123
-rw-r--r--libarttools/tools_test.cc107
-rw-r--r--libdexfile/dex/modifiers.h2
-rw-r--r--libnativeloader/public_libraries.cpp12
-rw-r--r--openjdkjvmti/ti_redefine.cc3
-rw-r--r--runtime/Android.bp2
-rw-r--r--runtime/app_info.cc12
-rw-r--r--runtime/app_info.h7
-rw-r--r--runtime/art_method-inl.h7
-rw-r--r--runtime/art_method.cc52
-rw-r--r--runtime/art_method.h41
-rw-r--r--runtime/class_linker.cc156
-rw-r--r--runtime/class_linker.h76
-rw-r--r--runtime/common_runtime_test.cc20
-rw-r--r--runtime/common_runtime_test.h5
-rw-r--r--runtime/common_transaction_test.cc50
-rw-r--r--runtime/common_transaction_test.h39
-rw-r--r--runtime/exec_utils.cc29
-rw-r--r--runtime/gc/collector/concurrent_copying.cc2
-rw-r--r--runtime/gc/collector/mark_compact-inl.h21
-rw-r--r--runtime/gc/collector/mark_compact.cc6
-rw-r--r--runtime/gc/heap.cc14
-rw-r--r--runtime/gc/heap.h11
-rw-r--r--runtime/hidden_api_test.cc76
-rw-r--r--runtime/intern_table.cc7
-rw-r--r--runtime/interpreter/active_transaction_checker.h87
-rw-r--r--runtime/interpreter/interpreter.cc2
-rw-r--r--runtime/interpreter/interpreter_common.cc179
-rw-r--r--runtime/interpreter/interpreter_common.h260
-rw-r--r--runtime/interpreter/interpreter_switch_impl-inl.h330
-rw-r--r--runtime/interpreter/interpreter_switch_impl0.cc141
-rw-r--r--runtime/interpreter/interpreter_switch_impl1.cc188
-rw-r--r--runtime/interpreter/mterp/arm64ng/object.S8
-rw-r--r--runtime/interpreter/mterp/nterp.cc3
-rw-r--r--runtime/interpreter/unstarted_runtime.cc68
-rw-r--r--runtime/interpreter/unstarted_runtime.h2
-rw-r--r--runtime/interpreter/unstarted_runtime_test.cc357
-rw-r--r--runtime/interpreter/unstarted_runtime_test.h125
-rw-r--r--runtime/interpreter/unstarted_runtime_transaction_test.cc236
-rw-r--r--runtime/jit/jit.cc8
-rw-r--r--runtime/jit/jit_code_cache.cc418
-rw-r--r--runtime/jit/jit_code_cache.h50
-rw-r--r--runtime/jit/profile_saver.cc32
-rw-r--r--runtime/jit/profile_saver_options.h18
-rw-r--r--runtime/jit/profiling_info.h8
-rw-r--r--runtime/mirror/array-inl.h3
-rw-r--r--runtime/mirror/class-inl.h11
-rw-r--r--runtime/mirror/dex_cache-inl.h4
-rw-r--r--runtime/mirror/object-inl.h72
-rw-r--r--runtime/mirror/object-readbarrier-inl.h4
-rw-r--r--runtime/native/dalvik_system_VMDebug.cc65
-rw-r--r--runtime/native_gc_triggering.md87
-rw-r--r--runtime/oat/aot_class_linker.cc299
-rw-r--r--runtime/oat/aot_class_linker.h95
-rw-r--r--runtime/quick_exception_handler.cc33
-rw-r--r--runtime/reflection.cc3
-rw-r--r--runtime/runtime.cc226
-rw-r--r--runtime/runtime.h94
-rw-r--r--runtime/runtime_callbacks.cc40
-rw-r--r--runtime/runtime_callbacks.h23
-rw-r--r--runtime/runtime_image.cc4
-rw-r--r--runtime/signal_catcher.cc12
-rw-r--r--runtime/startup_completed_task.cc3
-rw-r--r--runtime/thread_list.cc13
-rw-r--r--runtime/thread_pool.cc20
-rw-r--r--runtime/trace.cc167
-rw-r--r--runtime/trace.h31
-rw-r--r--runtime/transaction.cc98
-rw-r--r--runtime/transaction.h51
-rw-r--r--runtime/transaction_test.cc4
-rw-r--r--test/2230-profile-save-hotness/run.py5
-rw-r--r--test/2230-profile-save-hotness/src-art/Main.java4
-rw-r--r--test/2256-checker-vector-replacement/src/Main.java32
-rw-r--r--test/623-checker-loop-regressions/src/Main.java44
-rw-r--r--test/667-jit-jni-stub/jit_jni_stub_test.cc12
-rw-r--r--test/667-jit-jni-stub/src/Main.java7
-rw-r--r--test/855-native/expected-stderr.txt0
-rw-r--r--test/855-native/expected-stdout.txt1
-rw-r--r--test/855-native/info.txt2
-rw-r--r--test/855-native/run.py20
-rw-r--r--test/855-native/src-art/Main.java95
-rw-r--r--test/855-native/throws_exception.cc26
-rw-r--r--test/856-clone/expected-stderr.txt0
-rw-r--r--test/856-clone/expected-stdout.txt0
-rw-r--r--test/856-clone/info.txt2
-rw-r--r--test/856-clone/src/Main.java29
-rw-r--r--test/989-method-trace-throw/expected-stdout.txt222
-rw-r--r--test/989-method-trace-throw/method_trace.cc6
-rw-r--r--test/989-method-trace-throw/src/art/Test989.java89
-rw-r--r--test/Android.bp18
-rw-r--r--test/README.atest.md14
-rw-r--r--test/jvmti-common/Trace.java5
-rw-r--r--test/knownfailures.json1
-rw-r--r--test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java3
-rw-r--r--test/ti-agent/trace_helper.cc9
-rwxr-xr-xtest/utils/regen-test-files11
-rw-r--r--tools/ahat/Android.bp4
-rw-r--r--tools/ahat/src/main/com/android/ahat/BitmapHandler.java5
-rw-r--r--tools/ahat/src/main/com/android/ahat/OverviewHandler.java31
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatBitmapInstance.java371
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java287
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java17
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java62
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java9
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/Parser.java2
-rw-r--r--tools/ahat/src/main/com/android/ahat/heapdump/Size.java8
-rw-r--r--tools/ahat/src/test-dump/DumpedStuff.java6
-rw-r--r--tools/ahat/src/test-dump/android/graphics/Bitmap.java60
-rw-r--r--tools/ahat/src/test/com/android/ahat/InstanceTest.java41
-rw-r--r--tools/ahat/src/test/com/android/ahat/SiteTest.java10
-rw-r--r--tools/checker/README4
-rw-r--r--tools/hiddenapi/Android.bp2
189 files changed, 9835 insertions, 3077 deletions
diff --git a/Android.mk b/Android.mk
index 87f6b84a53..ecd36fafa9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -27,7 +27,7 @@ include $(art_path)/tools/veridex/Android.mk
include $(art_path)/build/Android.common_path.mk
.PHONY: clean-oat
-clean-oat: clean-oat-host clean-oat-target
+clean-oat: clean-oat-host
.PHONY: clean-oat-host
clean-oat-host:
@@ -35,21 +35,6 @@ clean-oat-host:
rm -rf $(TMPDIR)/*/test-*/dalvik-cache/*
rm -rf $(TMPDIR)/android-data/dalvik-cache/*
-.PHONY: clean-oat-target
-clean-oat-target:
- $(ADB) root
- $(ADB) wait-for-device remount
- $(ADB) shell rm -rf $(ART_TARGET_NATIVETEST_DIR)
- $(ADB) shell rm -rf $(ART_TARGET_TEST_DIR)
- $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*/*
- $(ADB) shell rm -rf $(ART_DEXPREOPT_BOOT_JAR_DIR)/$(DEX2OAT_TARGET_ARCH)
- $(ADB) shell rm -rf system/app/$(DEX2OAT_TARGET_ARCH)
-ifdef TARGET_2ND_ARCH
- $(ADB) shell rm -rf $(ART_DEXPREOPT_BOOT_JAR_DIR)/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH)
- $(ADB) shell rm -rf system/app/$($(TARGET_2ND_ARCH_VAR_PREFIX)DEX2OAT_TARGET_ARCH)
-endif
- $(ADB) shell rm -rf data/run-test/test-*/dalvik-cache/*
-
########################################################################
# cpplint rules to style check art source files
@@ -116,17 +101,6 @@ include $(art_path)/test/Android.run-test.mk
TEST_ART_TARGET_SYNC_DEPS += $(ART_TEST_TARGET_GTEST_DEPENDENCIES) $(ART_TEST_TARGET_RUN_TEST_DEPENDENCIES)
-# Make sure /system is writable on the device.
-TEST_ART_ADB_ROOT_AND_REMOUNT := \
- ($(ADB) root && \
- $(ADB) wait-for-device remount && \
- (($(ADB) shell touch /system/testfile && \
- ($(ADB) shell rm /system/testfile || true)) || \
- ($(ADB) disable-verity && \
- $(ADB) reboot && \
- $(ADB) wait-for-device root && \
- $(ADB) wait-for-device remount)))
-
# "mm test-art" to build and run all tests on host and device
.PHONY: test-art
test-art:
@@ -637,96 +611,6 @@ build-art-target-run-tests: build-art-target \
build-art-target-tests: build-art-target-gtests build-art-target-run-tests
########################################################################
-# targets to switch back and forth from libdvm to libart
-
-.PHONY: use-art
-use-art:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so
- $(ADB) shell start
-
-.PHONY: use-artd
-use-artd:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libartd.so
- $(ADB) shell start
-
-.PHONY: use-dalvik
-use-dalvik:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libdvm.so
- $(ADB) shell start
-
-.PHONY: use-art-full
-use-art-full:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*
- $(ADB) shell setprop dalvik.vm.dex2oat-filter \"\"
- $(ADB) shell setprop dalvik.vm.image-dex2oat-filter \"\"
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so
- $(ADB) shell setprop dalvik.vm.usejit false
- $(ADB) shell start
-
-.PHONY: use-artd-full
-use-artd-full:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*
- $(ADB) shell setprop dalvik.vm.dex2oat-filter \"\"
- $(ADB) shell setprop dalvik.vm.image-dex2oat-filter \"\"
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libartd.so
- $(ADB) shell setprop dalvik.vm.usejit false
- $(ADB) shell start
-
-.PHONY: use-art-jit
-use-art-jit:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*
- $(ADB) shell setprop dalvik.vm.dex2oat-filter "verify-at-runtime"
- $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "verify-at-runtime"
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so
- $(ADB) shell setprop dalvik.vm.usejit true
- $(ADB) shell start
-
-.PHONY: use-art-interpret-only
-use-art-interpret-only:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*
- $(ADB) shell setprop dalvik.vm.dex2oat-filter "interpret-only"
- $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "interpret-only"
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so
- $(ADB) shell setprop dalvik.vm.usejit false
- $(ADB) shell start
-
-.PHONY: use-artd-interpret-only
-use-artd-interpret-only:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*
- $(ADB) shell setprop dalvik.vm.dex2oat-filter "interpret-only"
- $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "interpret-only"
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libartd.so
- $(ADB) shell setprop dalvik.vm.usejit false
- $(ADB) shell start
-
-.PHONY: use-art-verify-none
-use-art-verify-none:
- $(ADB) root
- $(ADB) wait-for-device shell stop
- $(ADB) shell rm -rf $(ART_TARGET_DALVIK_CACHE_DIR)/*
- $(ADB) shell setprop dalvik.vm.dex2oat-filter "verify-none"
- $(ADB) shell setprop dalvik.vm.image-dex2oat-filter "verify-none"
- $(ADB) shell setprop persist.sys.dalvik.vm.lib.2 libart.so
- $(ADB) shell setprop dalvik.vm.usejit false
- $(ADB) shell start
-
-########################################################################
# Clear locally used variables.
TEST_ART_TARGET_SYNC_DEPS :=
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3d144250fd..1b48489d6e 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -1,5 +1,1433 @@
// Generated by `regen-test-files`. Do not edit manually.
{
+ "art-mainline-presubmit": [
+ {
+ "name": "art-run-test-001-HelloWorld"
+ },
+ {
+ "name": "art-run-test-001-Main"
+ },
+ {
+ "name": "art-run-test-002-sleep"
+ },
+ {
+ "name": "art-run-test-004-InterfaceTest"
+ },
+ {
+ "name": "art-run-test-004-checker-UnsafeTest18"
+ },
+ {
+ "name": "art-run-test-006-args"
+ },
+ {
+ "name": "art-run-test-007-count10"
+ },
+ {
+ "name": "art-run-test-009-instanceof"
+ },
+ {
+ "name": "art-run-test-010-instance"
+ },
+ {
+ "name": "art-run-test-011-array-copy"
+ },
+ {
+ "name": "art-run-test-012-math"
+ },
+ {
+ "name": "art-run-test-013-math2"
+ },
+ {
+ "name": "art-run-test-014-math3"
+ },
+ {
+ "name": "art-run-test-015-checker-switch"
+ },
+ {
+ "name": "art-run-test-016-intern"
+ },
+ {
+ "name": "art-run-test-017-float"
+ },
+ {
+ "name": "art-run-test-018-stack-overflow"
+ },
+ {
+ "name": "art-run-test-019-wrong-array-type"
+ },
+ {
+ "name": "art-run-test-020-string"
+ },
+ {
+ "name": "art-run-test-021-string2"
+ },
+ {
+ "name": "art-run-test-022-interface"
+ },
+ {
+ "name": "art-run-test-024-illegal-access"
+ },
+ {
+ "name": "art-run-test-025-access-controller"
+ },
+ {
+ "name": "art-run-test-026-access"
+ },
+ {
+ "name": "art-run-test-027-arithmetic"
+ },
+ {
+ "name": "art-run-test-028-array-write"
+ },
+ {
+ "name": "art-run-test-029-assert"
+ },
+ {
+ "name": "art-run-test-032-concrete-sub"
+ },
+ {
+ "name": "art-run-test-033-class-init-deadlock"
+ },
+ {
+ "name": "art-run-test-035-enum"
+ },
+ {
+ "name": "art-run-test-036-finalizer"
+ },
+ {
+ "name": "art-run-test-037-inherit"
+ },
+ {
+ "name": "art-run-test-039-join-main"
+ },
+ {
+ "name": "art-run-test-040-miranda"
+ },
+ {
+ "name": "art-run-test-041-narrowing"
+ },
+ {
+ "name": "art-run-test-042-new-instance"
+ },
+ {
+ "name": "art-run-test-043-privates"
+ },
+ {
+ "name": "art-run-test-045-reflect-array"
+ },
+ {
+ "name": "art-run-test-046-reflect"
+ },
+ {
+ "name": "art-run-test-047-returns"
+ },
+ {
+ "name": "art-run-test-048-reflect-v8"
+ },
+ {
+ "name": "art-run-test-049-show-object"
+ },
+ {
+ "name": "art-run-test-050-sync-test"
+ },
+ {
+ "name": "art-run-test-052-verifier-fun"
+ },
+ {
+ "name": "art-run-test-053-wait-some"
+ },
+ {
+ "name": "art-run-test-055-enum-performance"
+ },
+ {
+ "name": "art-run-test-058-enum-order"
+ },
+ {
+ "name": "art-run-test-059-finalizer-throw"
+ },
+ {
+ "name": "art-run-test-061-out-of-memory"
+ },
+ {
+ "name": "art-run-test-062-character-encodings"
+ },
+ {
+ "name": "art-run-test-063-process-manager"
+ },
+ {
+ "name": "art-run-test-067-preemptive-unpark"
+ },
+ {
+ "name": "art-run-test-069-field-type"
+ },
+ {
+ "name": "art-run-test-070-nio-buffer"
+ },
+ {
+ "name": "art-run-test-072-precise-gc"
+ },
+ {
+ "name": "art-run-test-072-reachability-fence"
+ },
+ {
+ "name": "art-run-test-073-mismatched-field"
+ },
+ {
+ "name": "art-run-test-074-gc-thrash"
+ },
+ {
+ "name": "art-run-test-075-verification-error"
+ },
+ {
+ "name": "art-run-test-076-boolean-put"
+ },
+ {
+ "name": "art-run-test-077-method-override"
+ },
+ {
+ "name": "art-run-test-078-polymorphic-virtual"
+ },
+ {
+ "name": "art-run-test-079-phantom"
+ },
+ {
+ "name": "art-run-test-080-oom-fragmentation"
+ },
+ {
+ "name": "art-run-test-080-oom-throw"
+ },
+ {
+ "name": "art-run-test-080-oom-throw-with-finalizer"
+ },
+ {
+ "name": "art-run-test-081-hot-exceptions"
+ },
+ {
+ "name": "art-run-test-082-inline-execute"
+ },
+ {
+ "name": "art-run-test-083-compiler-regressions"
+ },
+ {
+ "name": "art-run-test-084-class-init"
+ },
+ {
+ "name": "art-run-test-090-loop-formation"
+ },
+ {
+ "name": "art-run-test-092-locale"
+ },
+ {
+ "name": "art-run-test-093-serialization"
+ },
+ {
+ "name": "art-run-test-094-pattern"
+ },
+ {
+ "name": "art-run-test-095-switch-MAX_INT"
+ },
+ {
+ "name": "art-run-test-096-array-copy-concurrent-gc"
+ },
+ {
+ "name": "art-run-test-099-vmdebug"
+ },
+ {
+ "name": "art-run-test-100-reflect2"
+ },
+ {
+ "name": "art-run-test-1000-non-moving-space-stress"
+ },
+ {
+ "name": "art-run-test-1004-checker-volatile-ref-load"
+ },
+ {
+ "name": "art-run-test-101-fibonacci"
+ },
+ {
+ "name": "art-run-test-102-concurrent-gc"
+ },
+ {
+ "name": "art-run-test-103-string-append"
+ },
+ {
+ "name": "art-run-test-104-growth-limit"
+ },
+ {
+ "name": "art-run-test-105-invoke"
+ },
+ {
+ "name": "art-run-test-106-exceptions2"
+ },
+ {
+ "name": "art-run-test-107-int-math2"
+ },
+ {
+ "name": "art-run-test-108-check-cast"
+ },
+ {
+ "name": "art-run-test-109-suspend-check"
+ },
+ {
+ "name": "art-run-test-110-field-access"
+ },
+ {
+ "name": "art-run-test-112-double-math"
+ },
+ {
+ "name": "art-run-test-114-ParallelGC"
+ },
+ {
+ "name": "art-run-test-120-hashcode"
+ },
+ {
+ "name": "art-run-test-121-simple-suspend-check"
+ },
+ {
+ "name": "art-run-test-122-npe"
+ },
+ {
+ "name": "art-run-test-123-compiler-regressions-mt"
+ },
+ {
+ "name": "art-run-test-123-inline-execute2"
+ },
+ {
+ "name": "art-run-test-125-gc-and-classloading"
+ },
+ {
+ "name": "art-run-test-128-reg-spill-on-implicit-nullcheck"
+ },
+ {
+ "name": "art-run-test-129-ThreadGetId"
+ },
+ {
+ "name": "art-run-test-132-daemon-locks-shutdown"
+ },
+ {
+ "name": "art-run-test-133-static-invoke-super"
+ },
+ {
+ "name": "art-run-test-1338-gc-no-los"
+ },
+ {
+ "name": "art-run-test-140-dce-regression"
+ },
+ {
+ "name": "art-run-test-140-field-packing"
+ },
+ {
+ "name": "art-run-test-143-string-value"
+ },
+ {
+ "name": "art-run-test-145-alloc-tracking-stress"
+ },
+ {
+ "name": "art-run-test-152-dead-large-object"
+ },
+ {
+ "name": "art-run-test-153-reference-stress"
+ },
+ {
+ "name": "art-run-test-156-register-dex-file-multi-loader"
+ },
+ {
+ "name": "art-run-test-159-app-image-fields"
+ },
+ {
+ "name": "art-run-test-160-read-barrier-stress"
+ },
+ {
+ "name": "art-run-test-163-app-image-methods"
+ },
+ {
+ "name": "art-run-test-165-lock-owner-proxy"
+ },
+ {
+ "name": "art-run-test-168-vmstack-annotated"
+ },
+ {
+ "name": "art-run-test-170-interface-init"
+ },
+ {
+ "name": "art-run-test-174-escaping-instance-of-bad-class"
+ },
+ {
+ "name": "art-run-test-175-alloc-big-bignums"
+ },
+ {
+ "name": "art-run-test-176-app-image-string"
+ },
+ {
+ "name": "art-run-test-182-method-linking"
+ },
+ {
+ "name": "art-run-test-1960-checker-bounds-codegen"
+ },
+ {
+ "name": "art-run-test-1961-checker-loop-vectorizer"
+ },
+ {
+ "name": "art-run-test-201-built-in-except-detail-messages"
+ },
+ {
+ "name": "art-run-test-2019-constantcalculationsinking"
+ },
+ {
+ "name": "art-run-test-202-thread-oome"
+ },
+ {
+ "name": "art-run-test-2020-InvokeVirtual-Inlining"
+ },
+ {
+ "name": "art-run-test-2021-InvokeStatic-Inlining"
+ },
+ {
+ "name": "art-run-test-2022-Invariantloops"
+ },
+ {
+ "name": "art-run-test-2023-InvariantLoops_typecast"
+ },
+ {
+ "name": "art-run-test-2024-InvariantNegativeLoop"
+ },
+ {
+ "name": "art-run-test-2025-ChangedArrayValue"
+ },
+ {
+ "name": "art-run-test-2026-DifferentMemoryLSCouples"
+ },
+ {
+ "name": "art-run-test-2027-TwiceTheSameMemoryCouple"
+ },
+ {
+ "name": "art-run-test-2028-MultiBackward"
+ },
+ {
+ "name": "art-run-test-2029-contended-monitors"
+ },
+ {
+ "name": "art-run-test-2030-long-running-child"
+ },
+ {
+ "name": "art-run-test-2042-checker-dce-always-throw"
+ },
+ {
+ "name": "art-run-test-2042-reference-processing"
+ },
+ {
+ "name": "art-run-test-2043-reference-pauses"
+ },
+ {
+ "name": "art-run-test-2044-get-stack-traces"
+ },
+ {
+ "name": "art-run-test-2046-checker-comparison"
+ },
+ {
+ "name": "art-run-test-2047-checker-const-string-length"
+ },
+ {
+ "name": "art-run-test-2231-checker-heap-poisoning"
+ },
+ {
+ "name": "art-run-test-2233-checker-remove-loop-suspend-check"
+ },
+ {
+ "name": "art-run-test-2234-checker-remove-entry-suspendcheck"
+ },
+ {
+ "name": "art-run-test-2236-JdkUnsafeGetLong-regression"
+ },
+ {
+ "name": "art-run-test-2241-checker-inline-try-catch"
+ },
+ {
+ "name": "art-run-test-2242-checker-lse-acquire-release-operations"
+ },
+ {
+ "name": "art-run-test-2243-checker-not-inline-into-throw"
+ },
+ {
+ "name": "art-run-test-2244-checker-remove-try-boundary"
+ },
+ {
+ "name": "art-run-test-2249-checker-return-try-boundary-exit-in-loop"
+ },
+ {
+ "name": "art-run-test-2250-inline-throw-into-try"
+ },
+ {
+ "name": "art-run-test-2252-rem-optimization-dividend-divisor"
+ },
+ {
+ "name": "art-run-test-2253-checker-devirtualize-always-throws"
+ },
+ {
+ "name": "art-run-test-2254-checker-not-var-analyzed-pathological"
+ },
+ {
+ "name": "art-run-test-2254-class-value-before-and-after-u"
+ },
+ {
+ "name": "art-run-test-2255-checker-branch-redirection"
+ },
+ {
+ "name": "art-run-test-2256-checker-vector-replacement"
+ },
+ {
+ "name": "art-run-test-2257-checker-constant-folding-before-codegen"
+ },
+ {
+ "name": "art-run-test-2258-checker-valid-rti"
+ },
+ {
+ "name": "art-run-test-2259-checker-code-sinking-infinite-try-catch"
+ },
+ {
+ "name": "art-run-test-2260-checker-inline-unimplemented-intrinsics"
+ },
+ {
+ "name": "art-run-test-2262-checker-return-sinking"
+ },
+ {
+ "name": "art-run-test-2265-checker-select-binary-unary"
+ },
+ {
+ "name": "art-run-test-2266-checker-remove-empty-ifs"
+ },
+ {
+ "name": "art-run-test-2268-checker-remove-dead-phis"
+ },
+ {
+ "name": "art-run-test-2269-checker-constant-folding-instrinsics"
+ },
+ {
+ "name": "art-run-test-300-package-override"
+ },
+ {
+ "name": "art-run-test-301-abstract-protected"
+ },
+ {
+ "name": "art-run-test-302-float-conversion"
+ },
+ {
+ "name": "art-run-test-304-method-tracing"
+ },
+ {
+ "name": "art-run-test-401-optimizing-compiler"
+ },
+ {
+ "name": "art-run-test-402-optimizing-control-flow"
+ },
+ {
+ "name": "art-run-test-403-optimizing-long"
+ },
+ {
+ "name": "art-run-test-404-optimizing-allocator"
+ },
+ {
+ "name": "art-run-test-405-optimizing-long-allocator"
+ },
+ {
+ "name": "art-run-test-406-fields"
+ },
+ {
+ "name": "art-run-test-407-arrays"
+ },
+ {
+ "name": "art-run-test-408-move-bug"
+ },
+ {
+ "name": "art-run-test-409-materialized-condition"
+ },
+ {
+ "name": "art-run-test-410-floats"
+ },
+ {
+ "name": "art-run-test-411-checker-hdiv-hrem-const"
+ },
+ {
+ "name": "art-run-test-411-checker-hdiv-hrem-pow2"
+ },
+ {
+ "name": "art-run-test-411-checker-instruct-simplifier-hrem"
+ },
+ {
+ "name": "art-run-test-411-optimizing-arith"
+ },
+ {
+ "name": "art-run-test-413-regalloc-regression"
+ },
+ {
+ "name": "art-run-test-414-static-fields"
+ },
+ {
+ "name": "art-run-test-418-const-string"
+ },
+ {
+ "name": "art-run-test-419-long-parameter"
+ },
+ {
+ "name": "art-run-test-420-const-class"
+ },
+ {
+ "name": "art-run-test-421-exceptions"
+ },
+ {
+ "name": "art-run-test-421-large-frame"
+ },
+ {
+ "name": "art-run-test-422-instanceof"
+ },
+ {
+ "name": "art-run-test-422-type-conversion"
+ },
+ {
+ "name": "art-run-test-423-invoke-interface"
+ },
+ {
+ "name": "art-run-test-424-checkcast"
+ },
+ {
+ "name": "art-run-test-426-monitor"
+ },
+ {
+ "name": "art-run-test-427-bitwise"
+ },
+ {
+ "name": "art-run-test-427-bounds"
+ },
+ {
+ "name": "art-run-test-429-ssa-builder"
+ },
+ {
+ "name": "art-run-test-430-live-register-slow-path"
+ },
+ {
+ "name": "art-run-test-433-gvn"
+ },
+ {
+ "name": "art-run-test-434-shifter-operand"
+ },
+ {
+ "name": "art-run-test-435-try-finally-without-catch"
+ },
+ {
+ "name": "art-run-test-436-rem-float"
+ },
+ {
+ "name": "art-run-test-436-shift-constant"
+ },
+ {
+ "name": "art-run-test-437-inline"
+ },
+ {
+ "name": "art-run-test-438-volatile"
+ },
+ {
+ "name": "art-run-test-439-npe"
+ },
+ {
+ "name": "art-run-test-439-swap-double"
+ },
+ {
+ "name": "art-run-test-440-stmp"
+ },
+ {
+ "name": "art-run-test-441-checker-inliner"
+ },
+ {
+ "name": "art-run-test-443-not-bool-inline"
+ },
+ {
+ "name": "art-run-test-444-checker-nce"
+ },
+ {
+ "name": "art-run-test-445-checker-licm"
+ },
+ {
+ "name": "art-run-test-446-checker-inliner2"
+ },
+ {
+ "name": "art-run-test-447-checker-inliner3"
+ },
+ {
+ "name": "art-run-test-449-checker-bce-rem"
+ },
+ {
+ "name": "art-run-test-450-checker-types"
+ },
+ {
+ "name": "art-run-test-451-regression-add-float"
+ },
+ {
+ "name": "art-run-test-451-spill-splot"
+ },
+ {
+ "name": "art-run-test-455-checker-gvn"
+ },
+ {
+ "name": "art-run-test-456-baseline-array-set"
+ },
+ {
+ "name": "art-run-test-458-long-to-fpu"
+ },
+ {
+ "name": "art-run-test-464-checker-inline-sharpen-calls"
+ },
+ {
+ "name": "art-run-test-465-checker-clinit-gvn"
+ },
+ {
+ "name": "art-run-test-469-condition-materialization"
+ },
+ {
+ "name": "art-run-test-470-huge-method"
+ },
+ {
+ "name": "art-run-test-471-deopt-environment"
+ },
+ {
+ "name": "art-run-test-472-type-propagation"
+ },
+ {
+ "name": "art-run-test-473-checker-inliner-constants"
+ },
+ {
+ "name": "art-run-test-473-remove-dead-block"
+ },
+ {
+ "name": "art-run-test-474-checker-boolean-input"
+ },
+ {
+ "name": "art-run-test-474-fp-sub-neg"
+ },
+ {
+ "name": "art-run-test-475-simplify-mul-zero"
+ },
+ {
+ "name": "art-run-test-476-checker-ctor-fence-redun-elim"
+ },
+ {
+ "name": "art-run-test-476-checker-ctor-memory-barrier"
+ },
+ {
+ "name": "art-run-test-476-clinit-inline-static-invoke"
+ },
+ {
+ "name": "art-run-test-477-checker-bound-type"
+ },
+ {
+ "name": "art-run-test-477-long-2-float-convers-precision"
+ },
+ {
+ "name": "art-run-test-478-checker-clinit-check-pruning"
+ },
+ {
+ "name": "art-run-test-478-checker-inline-noreturn"
+ },
+ {
+ "name": "art-run-test-478-checker-inliner-nested-loop"
+ },
+ {
+ "name": "art-run-test-479-regression-implicit-null-check"
+ },
+ {
+ "name": "art-run-test-480-checker-dead-blocks"
+ },
+ {
+ "name": "art-run-test-481-regression-phi-cond"
+ },
+ {
+ "name": "art-run-test-482-checker-loop-back-edge-use"
+ },
+ {
+ "name": "art-run-test-483-dce-block"
+ },
+ {
+ "name": "art-run-test-485-checker-dce-switch"
+ },
+ {
+ "name": "art-run-test-486-checker-must-do-null-check"
+ },
+ {
+ "name": "art-run-test-487-checker-inline-calls"
+ },
+ {
+ "name": "art-run-test-488-checker-inline-recursive-calls"
+ },
+ {
+ "name": "art-run-test-489-current-method-regression"
+ },
+ {
+ "name": "art-run-test-490-checker-inline"
+ },
+ {
+ "name": "art-run-test-491-current-method"
+ },
+ {
+ "name": "art-run-test-492-checker-inline-invoke-interface"
+ },
+ {
+ "name": "art-run-test-493-checker-inline-invoke-interface"
+ },
+ {
+ "name": "art-run-test-494-checker-instanceof-tests"
+ },
+ {
+ "name": "art-run-test-495-checker-checkcast-tests"
+ },
+ {
+ "name": "art-run-test-496-checker-inlining-class-loader"
+ },
+ {
+ "name": "art-run-test-499-bce-phi-array-length"
+ },
+ {
+ "name": "art-run-test-500-instanceof"
+ },
+ {
+ "name": "art-run-test-505-simplifier-type-propagation"
+ },
+ {
+ "name": "art-run-test-507-boolean-test"
+ },
+ {
+ "name": "art-run-test-507-referrer"
+ },
+ {
+ "name": "art-run-test-508-checker-disassembly"
+ },
+ {
+ "name": "art-run-test-508-referrer-method"
+ },
+ {
+ "name": "art-run-test-513-array-deopt"
+ },
+ {
+ "name": "art-run-test-514-shifts"
+ },
+ {
+ "name": "art-run-test-519-bound-load-class"
+ },
+ {
+ "name": "art-run-test-521-checker-array-set-null"
+ },
+ {
+ "name": "art-run-test-521-regression-integer-field-set"
+ },
+ {
+ "name": "art-run-test-524-boolean-simplifier-regression"
+ },
+ {
+ "name": "art-run-test-525-checker-arrays-fields1"
+ },
+ {
+ "name": "art-run-test-525-checker-arrays-fields2"
+ },
+ {
+ "name": "art-run-test-526-checker-caller-callee-regs"
+ },
+ {
+ "name": "art-run-test-526-long-regalloc"
+ },
+ {
+ "name": "art-run-test-527-checker-array-access-simd"
+ },
+ {
+ "name": "art-run-test-527-checker-array-access-split"
+ },
+ {
+ "name": "art-run-test-528-long-hint"
+ },
+ {
+ "name": "art-run-test-529-long-split"
+ },
+ {
+ "name": "art-run-test-530-checker-loops-try-catch"
+ },
+ {
+ "name": "art-run-test-530-checker-loops1"
+ },
+ {
+ "name": "art-run-test-530-checker-loops2"
+ },
+ {
+ "name": "art-run-test-530-checker-loops3"
+ },
+ {
+ "name": "art-run-test-530-checker-loops4"
+ },
+ {
+ "name": "art-run-test-530-checker-loops5"
+ },
+ {
+ "name": "art-run-test-530-checker-lse"
+ },
+ {
+ "name": "art-run-test-530-checker-lse-ctor-fences"
+ },
+ {
+ "name": "art-run-test-530-checker-lse-simd"
+ },
+ {
+ "name": "art-run-test-530-checker-lse-try-catch"
+ },
+ {
+ "name": "art-run-test-530-instanceof-checkcast"
+ },
+ {
+ "name": "art-run-test-532-checker-nonnull-arrayset"
+ },
+ {
+ "name": "art-run-test-534-checker-bce-deoptimization"
+ },
+ {
+ "name": "art-run-test-535-deopt-and-inlining"
+ },
+ {
+ "name": "art-run-test-536-checker-intrinsic-optimization"
+ },
+ {
+ "name": "art-run-test-536-checker-needs-access-check"
+ },
+ {
+ "name": "art-run-test-537-checker-arraycopy"
+ },
+ {
+ "name": "art-run-test-537-checker-inline-and-unverified"
+ },
+ {
+ "name": "art-run-test-537-checker-jump-over-jump"
+ },
+ {
+ "name": "art-run-test-538-checker-embed-constants"
+ },
+ {
+ "name": "art-run-test-540-checker-rtp-bug"
+ },
+ {
+ "name": "art-run-test-542-bitfield-rotates"
+ },
+ {
+ "name": "art-run-test-542-inline-trycatch"
+ },
+ {
+ "name": "art-run-test-542-unresolved-access-check"
+ },
+ {
+ "name": "art-run-test-545-tracing-and-jit"
+ },
+ {
+ "name": "art-run-test-548-checker-inlining-and-dce"
+ },
+ {
+ "name": "art-run-test-549-checker-types-merge"
+ },
+ {
+ "name": "art-run-test-550-checker-multiply-accumulate"
+ },
+ {
+ "name": "art-run-test-550-new-instance-clinit"
+ },
+ {
+ "name": "art-run-test-551-checker-clinit"
+ },
+ {
+ "name": "art-run-test-551-checker-shifter-operand"
+ },
+ {
+ "name": "art-run-test-551-implicit-null-checks"
+ },
+ {
+ "name": "art-run-test-552-checker-x86-avx2-bit-manipulation"
+ },
+ {
+ "name": "art-run-test-554-checker-rtp-checkcast"
+ },
+ {
+ "name": "art-run-test-557-checker-instruct-simplifier-ror"
+ },
+ {
+ "name": "art-run-test-558-switch"
+ },
+ {
+ "name": "art-run-test-559-bce-ssa"
+ },
+ {
+ "name": "art-run-test-559-checker-rtp-ifnotnull"
+ },
+ {
+ "name": "art-run-test-560-packed-switch"
+ },
+ {
+ "name": "art-run-test-561-divrem"
+ },
+ {
+ "name": "art-run-test-561-shared-slowpaths"
+ },
+ {
+ "name": "art-run-test-562-bce-preheader"
+ },
+ {
+ "name": "art-run-test-562-checker-no-intermediate"
+ },
+ {
+ "name": "art-run-test-563-checker-invoke-super"
+ },
+ {
+ "name": "art-run-test-564-checker-bitcount"
+ },
+ {
+ "name": "art-run-test-564-checker-inline-loop"
+ },
+ {
+ "name": "art-run-test-564-checker-negbitwise"
+ },
+ {
+ "name": "art-run-test-565-checker-condition-liveness"
+ },
+ {
+ "name": "art-run-test-566-checker-codegen-select"
+ },
+ {
+ "name": "art-run-test-567-checker-builder-intrinsics"
+ },
+ {
+ "name": "art-run-test-568-checker-onebit"
+ },
+ {
+ "name": "art-run-test-570-checker-select"
+ },
+ {
+ "name": "art-run-test-572-checker-array-get-regression"
+ },
+ {
+ "name": "art-run-test-573-checker-checkcast-regression"
+ },
+ {
+ "name": "art-run-test-576-polymorphic-inlining"
+ },
+ {
+ "name": "art-run-test-577-checker-fp2int"
+ },
+ {
+ "name": "art-run-test-578-bce-visit"
+ },
+ {
+ "name": "art-run-test-578-polymorphic-inlining"
+ },
+ {
+ "name": "art-run-test-579-inline-infinite"
+ },
+ {
+ "name": "art-run-test-580-checker-fp16"
+ },
+ {
+ "name": "art-run-test-580-checker-round"
+ },
+ {
+ "name": "art-run-test-580-checker-string-fact-intrinsics"
+ },
+ {
+ "name": "art-run-test-580-crc32"
+ },
+ {
+ "name": "art-run-test-581-checker-rtp"
+ },
+ {
+ "name": "art-run-test-582-checker-bce-length"
+ },
+ {
+ "name": "art-run-test-583-checker-zero"
+ },
+ {
+ "name": "art-run-test-584-checker-div-bool"
+ },
+ {
+ "name": "art-run-test-589-super-imt"
+ },
+ {
+ "name": "art-run-test-590-checker-arr-set-null-regression"
+ },
+ {
+ "name": "art-run-test-591-checker-regression-dead-loop"
+ },
+ {
+ "name": "art-run-test-593-checker-long-2-float-regression"
+ },
+ {
+ "name": "art-run-test-594-checker-array-alias"
+ },
+ {
+ "name": "art-run-test-594-load-string-regression"
+ },
+ {
+ "name": "art-run-test-603-checker-instanceof"
+ },
+ {
+ "name": "art-run-test-605-new-string-from-bytes"
+ },
+ {
+ "name": "art-run-test-607-daemon-stress"
+ },
+ {
+ "name": "art-run-test-609-checker-inline-interface"
+ },
+ {
+ "name": "art-run-test-609-checker-x86-bounds-check"
+ },
+ {
+ "name": "art-run-test-610-arraycopy"
+ },
+ {
+ "name": "art-run-test-611-checker-simplify-if"
+ },
+ {
+ "name": "art-run-test-614-checker-dump-constant-location"
+ },
+ {
+ "name": "art-run-test-615-checker-arm64-store-zero"
+ },
+ {
+ "name": "art-run-test-617-clinit-oome"
+ },
+ {
+ "name": "art-run-test-618-checker-induction"
+ },
+ {
+ "name": "art-run-test-619-checker-current-method"
+ },
+ {
+ "name": "art-run-test-620-checker-bce-intrinsics"
+ },
+ {
+ "name": "art-run-test-622-checker-bce-regressions"
+ },
+ {
+ "name": "art-run-test-625-checker-licm-regressions"
+ },
+ {
+ "name": "art-run-test-627-checker-unroll"
+ },
+ {
+ "name": "art-run-test-628-vdex"
+ },
+ {
+ "name": "art-run-test-631-checker-get-class"
+ },
+ {
+ "name": "art-run-test-632-checker-char-at-bounds"
+ },
+ {
+ "name": "art-run-test-635-checker-arm64-volatile-load-cc"
+ },
+ {
+ "name": "art-run-test-636-arm64-veneer-pool"
+ },
+ {
+ "name": "art-run-test-637-checker-throw-inline"
+ },
+ {
+ "name": "art-run-test-639-checker-code-sinking"
+ },
+ {
+ "name": "art-run-test-640-checker-boolean-simd"
+ },
+ {
+ "name": "art-run-test-640-checker-integer-valueof"
+ },
+ {
+ "name": "art-run-test-640-checker-simd"
+ },
+ {
+ "name": "art-run-test-641-checker-arraycopy"
+ },
+ {
+ "name": "art-run-test-641-iterations"
+ },
+ {
+ "name": "art-run-test-643-checker-bogus-ic"
+ },
+ {
+ "name": "art-run-test-645-checker-abs-simd"
+ },
+ {
+ "name": "art-run-test-646-checker-arraycopy-large-cst-pos"
+ },
+ {
+ "name": "art-run-test-646-checker-long-const-to-int"
+ },
+ {
+ "name": "art-run-test-646-checker-simd-hadd"
+ },
+ {
+ "name": "art-run-test-650-checker-inline-access-thunks"
+ },
+ {
+ "name": "art-run-test-654-checker-periodic"
+ },
+ {
+ "name": "art-run-test-655-checker-simd-arm-opt"
+ },
+ {
+ "name": "art-run-test-656-checker-simd-opt"
+ },
+ {
+ "name": "art-run-test-657-branches"
+ },
+ {
+ "name": "art-run-test-658-fp-read-barrier"
+ },
+ {
+ "name": "art-run-test-659-unpadded-array"
+ },
+ {
+ "name": "art-run-test-660-checker-sad"
+ },
+ {
+ "name": "art-run-test-660-checker-simd-sad"
+ },
+ {
+ "name": "art-run-test-661-checker-simd-cf-loops"
+ },
+ {
+ "name": "art-run-test-661-checker-simd-reduc"
+ },
+ {
+ "name": "art-run-test-662-regression-alias"
+ },
+ {
+ "name": "art-run-test-663-checker-select-generator"
+ },
+ {
+ "name": "art-run-test-665-checker-simd-zero"
+ },
+ {
+ "name": "art-run-test-666-dex-cache-itf"
+ },
+ {
+ "name": "art-run-test-667-checker-simd-alignment"
+ },
+ {
+ "name": "art-run-test-667-out-of-bounds"
+ },
+ {
+ "name": "art-run-test-669-checker-break"
+ },
+ {
+ "name": "art-run-test-671-npe-field-opts"
+ },
+ {
+ "name": "art-run-test-672-checker-throw-method"
+ },
+ {
+ "name": "art-run-test-673-checker-throw-vmethod"
+ },
+ {
+ "name": "art-run-test-676-proxy-jit-at-first-use"
+ },
+ {
+ "name": "art-run-test-677-fsi2"
+ },
+ {
+ "name": "art-run-test-678-quickening"
+ },
+ {
+ "name": "art-run-test-684-checker-simd-dotprod"
+ },
+ {
+ "name": "art-run-test-684-select-condition"
+ },
+ {
+ "name": "art-run-test-689-multi-catch"
+ },
+ {
+ "name": "art-run-test-694-clinit-jit"
+ },
+ {
+ "name": "art-run-test-695-simplify-throws"
+ },
+ {
+ "name": "art-run-test-696-loop"
+ },
+ {
+ "name": "art-run-test-697-checker-string-append"
+ },
+ {
+ "name": "art-run-test-698-selects"
+ },
+ {
+ "name": "art-run-test-700-LoadArgRegs"
+ },
+ {
+ "name": "art-run-test-703-floating-point-div"
+ },
+ {
+ "name": "art-run-test-704-multiply-accumulate"
+ },
+ {
+ "name": "art-run-test-705-register-conflict"
+ },
+ {
+ "name": "art-run-test-711-checker-type-conversion"
+ },
+ {
+ "name": "art-run-test-713-varhandle-invokers"
+ },
+ {
+ "name": "art-run-test-718-zipfile-finalizer"
+ },
+ {
+ "name": "art-run-test-719-varhandle-concurrency"
+ },
+ {
+ "name": "art-run-test-721-osr"
+ },
+ {
+ "name": "art-run-test-726-array-store"
+ },
+ {
+ "name": "art-run-test-730-checker-inlining-super"
+ },
+ {
+ "name": "art-run-test-731-bounds-check-slow-path"
+ },
+ {
+ "name": "art-run-test-805-TooDeepClassInstanceOf"
+ },
+ {
+ "name": "art-run-test-806-TooWideClassInstanceOf"
+ },
+ {
+ "name": "art-run-test-812-recursive-default"
+ },
+ {
+ "name": "art-run-test-814-large-field-offsets"
+ },
+ {
+ "name": "art-run-test-815-invokeinterface-default"
+ },
+ {
+ "name": "art-run-test-818-clinit-nterp"
+ },
+ {
+ "name": "art-run-test-821-madvise-willneed"
+ },
+ {
+ "name": "art-run-test-828-partial-lse"
+ },
+ {
+ "name": "art-run-test-834-lse"
+ },
+ {
+ "name": "art-run-test-835-b216762268"
+ },
+ {
+ "name": "art-run-test-838-override"
+ },
+ {
+ "name": "art-run-test-839-clinit-throw"
+ },
+ {
+ "name": "art-run-test-841-defaults"
+ },
+ {
+ "name": "art-run-test-843-default-interface"
+ },
+ {
+ "name": "art-run-test-851-null-instanceof"
+ },
+ {
+ "name": "art-run-test-853-checker-inlining"
+ },
+ {
+ "name": "art-run-test-963-default-range-smali"
+ },
+ {
+ "name": "art-run-test-965-default-verify"
+ },
+ {
+ "name": "art-run-test-967-default-ame"
+ },
+ {
+ "name": "art_libnativebridge_cts_tests"
+ },
+ {
+ "name": "art_standalone_artd_tests"
+ },
+ {
+ "name": "art_standalone_cmdline_tests"
+ },
+ {
+ "name": "art_standalone_compiler_tests",
+ "options": [
+ {
+ "exclude-filter": "JniCompilerTest*"
+ }
+ ]
+ },
+ {
+ "name": "art_standalone_dex2oat_tests"
+ },
+ {
+ "name": "art_standalone_dexdump_tests"
+ },
+ {
+ "name": "art_standalone_dexlist_tests"
+ },
+ {
+ "name": "art_standalone_dexoptanalyzer_tests"
+ },
+ {
+ "name": "art_standalone_libartbase_tests"
+ },
+ {
+ "name": "art_standalone_libartpalette_tests",
+ "options": [
+ {
+ "exclude-filter": "PaletteClientJniTest*"
+ }
+ ]
+ },
+ {
+ "name": "art_standalone_libartservice_tests"
+ },
+ {
+ "name": "art_standalone_libarttools_tests"
+ },
+ {
+ "name": "art_standalone_libdexfile_support_tests"
+ },
+ {
+ "name": "art_standalone_libdexfile_tests"
+ },
+ {
+ "name": "art_standalone_libprofile_tests"
+ },
+ {
+ "name": "art_standalone_oatdump_tests"
+ },
+ {
+ "name": "art_standalone_odrefresh_tests"
+ },
+ {
+ "name": "art_standalone_profman_tests"
+ },
+ {
+ "name": "art_standalone_runtime_tests"
+ },
+ {
+ "name": "art_standalone_sigchain_tests"
+ },
+ {
+ "name": "libnativeloader_e2e_tests"
+ },
+ {
+ "name": "libnativeloader_test"
+ }
+ ],
"mainline-presubmit": [
{
"name": "art-run-test-001-HelloWorld[com.google.android.art.apex]"
@@ -1422,6 +2850,9 @@
"name": "art_standalone_sigchain_tests[com.google.android.art.apex]"
},
{
+ "name": "libnativebridge-tests[com.google.android.art.apex]"
+ },
+ {
"name": "libnativeloader_e2e_tests[com.google.android.art.apex]"
},
{
@@ -2858,6 +4289,9 @@
"name": "art_standalone_sigchain_tests"
},
{
+ "name": "libnativebridge-tests"
+ },
+ {
"name": "libnativeloader_e2e_tests"
},
{
@@ -4288,6 +5722,9 @@
"name": "art_standalone_sigchain_tests"
},
{
+ "name": "libnativebridge-tests"
+ },
+ {
"name": "libnativeloader_e2e_tests"
},
{
@@ -4305,9 +5742,6 @@
},
{
"name": "art-run-test-2273-checker-unreachable-intrinsics"
- },
- {
- "name": "libnativebridge-tests"
}
]
}
diff --git a/adbconnection/adbconnection.cc b/adbconnection/adbconnection.cc
index 55eac932d3..2ef7a842fa 100644
--- a/adbconnection/adbconnection.cc
+++ b/adbconnection/adbconnection.cc
@@ -16,6 +16,7 @@
#include "adbconnection.h"
+#include <dlfcn.h>
#include <jni.h>
#include <sys/eventfd.h>
#include <sys/ioctl.h>
@@ -74,8 +75,7 @@ using dt_fd_forward::kSkipHandshakeMessage;
using android::base::StringPrintf;
static constexpr const char kJdwpHandshake[14] = {
- 'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'
-};
+ 'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'};
static constexpr int kEventfdLocked = 0;
static constexpr int kEventfdUnlocked = 1;
@@ -92,8 +92,88 @@ static constexpr uint8_t kDdmChunkCommand = 1;
static std::optional<AdbConnectionState> gState;
static std::optional<pthread_t> gPthread;
-static bool IsDebuggingPossible() {
- return art::Dbg::IsJdwpAllowed();
+// ADB apex method v2
+using AdbApexProcessName = void (*)(const char*);
+AdbApexProcessName apex_adbconnection_client_set_current_process_name = nullptr;
+using AdbApexPackageName = void (*)(const char*);
+AdbApexPackageName apex_adbconnection_client_add_application = nullptr;
+AdbApexPackageName apex_adbconnection_client_remove_application = nullptr;
+using AdbApexWaitingForDebugger = void (*)(bool);
+AdbApexWaitingForDebugger apex_adbconnection_client_set_waiting_for_debugger = nullptr;
+using AdbApexSendUpdate = void (*)(AdbConnectionClientContext*);
+AdbApexSendUpdate apex_adbconnection_client_send_update = nullptr;
+using AdbApexHasPendingUpdate = bool (*)();
+AdbApexHasPendingUpdate apex_adbconnection_client_has_pending_update = nullptr;
+using AdbApexSetUserId = void (*)(int);
+AdbApexSetUserId apex_adbconnection_client_set_user_id = nullptr;
+
+void apex_adbconnection_client_set_current_process_name_noop(const char*) {}
+void apex_adbconnection_client_add_application_noop(const char*) {}
+void apex_adbconnection_client_remove_application_noop(const char*) {}
+void apex_adbconnection_client_set_waiting_for_debugger_noop(bool) {}
+void apex_adbconnection_client_send_update_noop(AdbConnectionClientContext*) {}
+bool apex_adbconnection_client_has_pending_update_noop() { return false; }
+void apex_adbconnection_client_set_user_id_noop(int) {}
+
+static void RetrieveApexPointers() {
+ apex_adbconnection_client_set_current_process_name =
+ (AdbApexProcessName)dlsym(RTLD_DEFAULT, "adbconnection_client_set_current_process_name");
+ if (!apex_adbconnection_client_set_current_process_name) {
+ VLOG(jdwp) << "Unable to dlsym adbconnection_client_set_current_process_name";
+ apex_adbconnection_client_set_current_process_name =
+ apex_adbconnection_client_set_current_process_name_noop;
+ }
+
+ apex_adbconnection_client_add_application =
+ (AdbApexPackageName)dlsym(RTLD_DEFAULT, "adbconnection_client_add_application");
+ if (!apex_adbconnection_client_add_application) {
+ VLOG(jdwp) << "Unable to dlsym adbconnection_client_add_application";
+ apex_adbconnection_client_add_application = apex_adbconnection_client_add_application_noop;
+ }
+
+ apex_adbconnection_client_remove_application =
+ (AdbApexPackageName)dlsym(RTLD_DEFAULT, "adbconnection_client_remove_application");
+ if (!apex_adbconnection_client_remove_application) {
+ VLOG(jdwp) << "Unable to dlsym adbconnection_client_remove_application";
+ apex_adbconnection_client_remove_application =
+ apex_adbconnection_client_remove_application_noop;
+ }
+
+ apex_adbconnection_client_set_waiting_for_debugger = (AdbApexWaitingForDebugger)dlsym(
+ RTLD_DEFAULT, "adbconnection_client_set_waiting_for_debugger");
+ if (!apex_adbconnection_client_set_waiting_for_debugger) {
+ VLOG(jdwp) << "Unable to dlsym adbconnection_client_set_waiting_for_debugger";
+ apex_adbconnection_client_set_waiting_for_debugger =
+ apex_adbconnection_client_set_waiting_for_debugger_noop;
+ }
+
+ apex_adbconnection_client_send_update =
+ (AdbApexSendUpdate)dlsym(RTLD_DEFAULT, "adbconnection_client_send_update");
+ if (!apex_adbconnection_client_send_update) {
+ VLOG(jdwp) << "Unable to dlsym adbconnection_client_send_update";
+ apex_adbconnection_client_send_update = apex_adbconnection_client_send_update_noop;
+ }
+
+ apex_adbconnection_client_has_pending_update =
+ (AdbApexHasPendingUpdate)dlsym(RTLD_DEFAULT, "adbconnection_client_has_pending_update");
+ if (!apex_adbconnection_client_has_pending_update) {
+ VLOG(jdwp) << "Unable to dlsym adbconnection_client_has_pending_update";
+ apex_adbconnection_client_has_pending_update =
+ apex_adbconnection_client_has_pending_update_noop;
+ }
+
+ apex_adbconnection_client_set_user_id =
+ (AdbApexSetUserId)dlsym(RTLD_DEFAULT, "adbconnection_client_set_user_id");
+ if (!apex_adbconnection_client_set_user_id) {
+ VLOG(jdwp) << "Unable to dlsym adbconnection_client_set_user_id";
+ apex_adbconnection_client_set_user_id = apex_adbconnection_client_set_user_id_noop;
+ }
+}
+
+static bool IsDebuggingPossible() { return art::Dbg::IsJdwpAllowed(); }
+
+static bool IsDebuggableOrProfilable() {
+ return IsDebuggingPossible() || art::Runtime::Current()->IsProfileableFromShell();
}
// Begin running the debugger.
@@ -101,7 +181,7 @@ void AdbConnectionDebuggerController::StartDebugger() {
// The debugger thread is started for a debuggable or profileable-from-shell process.
// The pid will be send to adbd for adb's "track-jdwp" and "track-app" services.
// The thread will also set up the jdwp tunnel if the process is debuggable.
- if (IsDebuggingPossible() || art::Runtime::Current()->IsProfileableFromShell()) {
+ if (IsDebuggableOrProfilable()) {
connection_->StartDebuggerThreads();
} else {
LOG(ERROR) << "Not starting debugger since process cannot load the jdwp agent.";
@@ -135,15 +215,31 @@ void AdbConnectionDdmCallback::DdmPublishChunk(uint32_t type,
connection_->PublishDdmData(type, data);
}
+void AdbConnectionAppInfoCallback::SetCurrentProcessName(const std::string& process_name) {
+ connection_->SetCurrentProcessName(process_name);
+}
+
+void AdbConnectionAppInfoCallback::AddApplication(const std::string& package_name) {
+ connection_->AddApplication(package_name);
+}
+
+void AdbConnectionAppInfoCallback::RemoveApplication(const std::string& package_name) {
+ connection_->RemoveApplication(package_name);
+}
+
+void AdbConnectionAppInfoCallback::SetWaitingForDebugger(bool waiting) {
+ connection_->SetWaitingForDebugger(waiting);
+}
+
+void AdbConnectionAppInfoCallback::SetUserId(int user_id) { connection_->SetUserId(user_id); }
+
class ScopedEventFdLock {
public:
explicit ScopedEventFdLock(int fd) : fd_(fd), data_(0) {
TEMP_FAILURE_RETRY(read(fd_, &data_, sizeof(data_)));
}
- ~ScopedEventFdLock() {
- TEMP_FAILURE_RETRY(write(fd_, &data_, sizeof(data_)));
- }
+ ~ScopedEventFdLock() { TEMP_FAILURE_RETRY(write(fd_, &data_, sizeof(data_))); }
private:
int fd_;
@@ -151,24 +247,25 @@ class ScopedEventFdLock {
};
AdbConnectionState::AdbConnectionState(const std::string& agent_name)
- : agent_name_(agent_name),
- controller_(this),
- ddm_callback_(this),
- sleep_event_fd_(-1),
- control_ctx_(nullptr, adbconnection_client_destroy),
- local_agent_control_sock_(-1),
- remote_agent_control_sock_(-1),
- adb_connection_socket_(-1),
- adb_write_event_fd_(-1),
- shutting_down_(false),
- agent_loaded_(false),
- agent_listening_(false),
- agent_has_socket_(false),
- sent_agent_fds_(false),
- performed_handshake_(false),
- notified_ddm_active_(false),
- next_ddm_id_(1),
- started_debugger_threads_(false) {
+ : agent_name_(agent_name),
+ controller_(this),
+ ddm_callback_(this),
+ appinfo_callback_(this),
+ sleep_event_fd_(-1),
+ control_ctx_(nullptr, adbconnection_client_destroy),
+ local_agent_control_sock_(-1),
+ remote_agent_control_sock_(-1),
+ adb_connection_socket_(-1),
+ adb_write_event_fd_(-1),
+ shutting_down_(false),
+ agent_loaded_(false),
+ agent_listening_(false),
+ agent_has_socket_(false),
+ sent_agent_fds_(false),
+ performed_handshake_(false),
+ notified_ddm_active_(false),
+ next_ddm_id_(1),
+ started_debugger_threads_(false) {
// Add the startup callback.
art::ScopedObjectAccess soa(art::Thread::Current());
art::Runtime::Current()->GetRuntimeCallbacks()->AddDebuggerControlCallback(&controller_);
@@ -200,8 +297,10 @@ static art::ObjPtr<art::mirror::Object> CreateAdbConnectionThread(art::Thread* s
art::Handle<art::mirror::Object> system_thread_group = hs.NewHandle(
system_thread_group_field->GetDeclaringClass()->GetFieldObject<art::mirror::Object>(
system_thread_group_field->GetOffset()));
- return art::WellKnownClasses::java_lang_Thread_init->NewObject<'L', 'L', 'I', 'Z'>(
- hs, self, system_thread_group, thr_name, /*priority=*/ 0, /*daemon=*/ true).Get();
+ return art::WellKnownClasses::java_lang_Thread_init
+ ->NewObject<'L', 'L', 'I', 'Z'>(
+ hs, self, system_thread_group, thr_name, /*priority=*/0, /*daemon=*/true)
+ .Get();
}
struct CallbackData {
@@ -211,9 +310,7 @@ struct CallbackData {
static void* CallbackFunction(void* vdata) {
std::unique_ptr<CallbackData> data(reinterpret_cast<CallbackData*>(vdata));
- art::Thread* self = art::Thread::Attach(kAdbConnectionThreadName,
- true,
- data->thr_);
+ art::Thread* self = art::Thread::Attach(kAdbConnectionThreadName, true, data->thr_);
CHECK(self != nullptr) << "threads_being_born_ should have ensured thread could be attached.";
// The name in Attach() is only for logging. Set the thread name. This is important so
// that the thread is no longer seen as starting up.
@@ -254,6 +351,7 @@ void AdbConnectionState::StartDebuggerThreads() {
{
art::ScopedObjectAccess soa(art::Thread::Current());
art::Runtime::Current()->GetRuntimeCallbacks()->AddDdmCallback(&ddm_callback_);
+ art::Runtime::Current()->GetRuntimeCallbacks()->AddAppInfoCallback(&appinfo_callback_);
}
// Setup the socketpair we use to talk to the agent.
bool has_sockets;
@@ -288,13 +386,11 @@ void AdbConnectionState::StartDebuggerThreads() {
}
// Note: Using pthreads instead of std::thread to not abort when the thread cannot be
// created (exception support required).
- std::unique_ptr<CallbackData> data(new CallbackData { this, thr });
+ std::unique_ptr<CallbackData> data(new CallbackData{this, thr});
started_debugger_threads_ = true;
gPthread.emplace();
- int pthread_create_result = pthread_create(&gPthread.value(),
- nullptr,
- &CallbackFunction,
- data.get());
+ int pthread_create_result =
+ pthread_create(&gPthread.value(), nullptr, &CallbackFunction, data.get());
if (pthread_create_result != 0) {
gPthread.reset();
started_debugger_threads_ = false;
@@ -309,9 +405,7 @@ void AdbConnectionState::StartDebuggerThreads() {
data.release(); // NOLINT pthreads API.
}
-static bool FlagsSet(int16_t data, int16_t flags) {
- return (data & flags) == flags;
-}
+static bool FlagsSet(int16_t data, int16_t flags) { return (data & flags) == flags; }
void AdbConnectionState::CloseFds() {
{
@@ -351,6 +445,41 @@ void AdbConnectionState::PublishDdmData(uint32_t type, const art::ArrayRef<const
SendDdmPacket(NextDdmId(), DdmPacketType::kCmd, type, data);
}
+void AdbConnectionState::SetCurrentProcessName(const std::string& process_name) {
+ DCHECK(IsDebuggableOrProfilable());
+ VLOG(jdwp) << "SetCurrentProcessName '" << process_name << "'";
+ apex_adbconnection_client_set_current_process_name(process_name.c_str());
+ WakeupPollLoop();
+}
+
+void AdbConnectionState::AddApplication(const std::string& package_name) {
+ DCHECK(IsDebuggableOrProfilable());
+ VLOG(jdwp) << "AddApplication'" << package_name << "'";
+ apex_adbconnection_client_add_application(package_name.c_str());
+ WakeupPollLoop();
+}
+
+void AdbConnectionState::RemoveApplication(const std::string& package_name) {
+ DCHECK(IsDebuggableOrProfilable());
+ VLOG(jdwp) << "RemoveApplication'" << package_name << "'";
+ apex_adbconnection_client_remove_application(package_name.c_str());
+ WakeupPollLoop();
+}
+
+void AdbConnectionState::SetWaitingForDebugger(bool waiting) {
+ DCHECK(IsDebuggableOrProfilable());
+ VLOG(jdwp) << "SetWaitingForDebugger'" << waiting << "'";
+ apex_adbconnection_client_set_waiting_for_debugger(waiting);
+ WakeupPollLoop();
+}
+
+void AdbConnectionState::SetUserId(int user_id) {
+ DCHECK(IsDebuggableOrProfilable());
+ VLOG(jdwp) << "SetUserId'" << user_id << "'";
+ apex_adbconnection_client_set_user_id(user_id);
+ WakeupPollLoop();
+}
+
void AdbConnectionState::SendDdmPacket(uint32_t id,
DdmPacketType packet_type,
uint32_t type,
@@ -533,7 +662,7 @@ bool AdbConnectionState::SetupAdbConnection() {
}
void AdbConnectionState::RunPollLoop(art::Thread* self) {
- DCHECK(IsDebuggingPossible() || art::Runtime::Current()->IsProfileableFromShell());
+ DCHECK(IsDebuggableOrProfilable());
CHECK_NE(agent_name_, "");
CHECK_EQ(self->GetState(), art::ThreadState::kNative);
art::Locks::mutator_lock_->AssertNotHeld(self);
@@ -547,6 +676,13 @@ void AdbConnectionState::RunPollLoop(art::Thread* self) {
}
while (!shutting_down_ && control_ctx_) {
bool should_listen_on_connection = !agent_has_socket_ && !sent_agent_fds_;
+ // By default we are always interested in read and hangup events on the control ctx.
+ int16_t interestingControlEventSet = POLLIN | POLLRDHUP;
+ if (apex_adbconnection_client_has_pending_update()) {
+ // If we have an update for ADBd, we also want to know when the control ctx
+ // socket is writable.
+ interestingControlEventSet |= POLLOUT;
+ }
struct pollfd pollfds[4] = {
{sleep_event_fd_, POLLIN, 0},
// -1 as an fd causes it to be ignored by poll
@@ -554,7 +690,7 @@ void AdbConnectionState::RunPollLoop(art::Thread* self) {
// Check for the control_sock_ actually going away. We always monitor for POLLIN, even if
// we already have an adbd socket. This allows to reject incoming debugger connection if
// there is already have one connected.
- {adbconnection_client_pollfd(control_ctx_.get()), POLLIN | POLLRDHUP, 0},
+ {adbconnection_client_pollfd(control_ctx_.get()), interestingControlEventSet, 0},
// if we have not loaded the agent either the adb_connection_socket_ is -1 meaning we
// don't have a real connection yet or the socket through adb needs to be listened to for
// incoming data that the agent or this plugin can handle.
@@ -564,8 +700,9 @@ void AdbConnectionState::RunPollLoop(art::Thread* self) {
PLOG(ERROR) << "Failed to poll!";
return;
}
- // We don't actually care about doing this we just use it to wake us up.
- // const struct pollfd& sleep_event_poll = pollfds[0];
+ VLOG(jdwp) << "adbconnection poll awakening";
+
+ const struct pollfd& sleep_event_poll = pollfds[0];
const struct pollfd& agent_control_sock_poll = pollfds[1];
const struct pollfd& control_sock_poll = pollfds[2];
const struct pollfd& adb_socket_poll = pollfds[3];
@@ -652,12 +789,19 @@ void AdbConnectionState::RunPollLoop(art::Thread* self) {
} else if (agent_listening_ && !sent_agent_fds_) {
VLOG(jdwp) << "Sending agent fds again on data.";
// Agent was already loaded so it can deal with the handshake.
- SendAgentFds(/*require_handshake=*/ true);
+ SendAgentFds(/*require_handshake=*/true);
}
+ } else if (FlagsSet(control_sock_poll.revents, POLLOUT)) {
+ VLOG(jdwp) << "Sending state update to adbd";
+ apex_adbconnection_client_send_update(control_ctx_.get());
} else if (FlagsSet(adb_socket_poll.revents, POLLRDHUP)) {
CHECK(IsDebuggingPossible()); // This path is unexpected for a profileable process.
DCHECK(!agent_has_socket_);
CloseFds();
+ } else if (FlagsSet(sleep_event_poll.revents, POLLIN)) {
+ // Poll was awakened via fdevent, we need to decrease fdevent counter to prevent poll from
+ // triggering again.
+ AcknowledgeWakeup();
} else {
VLOG(jdwp) << "Woke up poll without anything to do!";
}
@@ -916,19 +1060,36 @@ std::string AdbConnectionState::MakeAgentArg() {
return agent_name_ + "=" + parameters.join();
}
-void AdbConnectionState::StopDebuggerThreads() {
- // The regular agent system will take care of unloading the agent (if needed).
- shutting_down_ = true;
- // Wakeup the poll loop.
+void AdbConnectionState::WakeupPollLoop() {
+ if (!control_ctx_) {
+ return;
+ }
+
uint64_t data = 1;
if (sleep_event_fd_ != -1) {
TEMP_FAILURE_RETRY(write(sleep_event_fd_, &data, sizeof(data)));
}
}
+void AdbConnectionState::AcknowledgeWakeup() {
+ uint64_t data;
+ if (sleep_event_fd_ != -1) {
+ TEMP_FAILURE_RETRY(read(sleep_event_fd_, &data, sizeof(data)));
+ }
+}
+
+void AdbConnectionState::StopDebuggerThreads() {
+ // The regular agent system will take care of unloading the agent (if needed).
+ shutting_down_ = true;
+ WakeupPollLoop();
+}
+
// The plugin initialization function.
extern "C" bool ArtPlugin_Initialize() {
DCHECK(art::Runtime::Current()->GetJdwpProvider() == art::JdwpProvider::kAdbConnection);
+
+ RetrieveApexPointers();
+
// TODO Provide some way for apps to set this maybe?
gState.emplace(kDefaultJdwpAgentName);
return ValidateJdwpOptions(art::Runtime::Current()->GetJdwpOptions());
diff --git a/adbconnection/adbconnection.h b/adbconnection/adbconnection.h
index b6309e735d..d1d1e51067 100644
--- a/adbconnection/adbconnection.h
+++ b/adbconnection/adbconnection.h
@@ -24,6 +24,7 @@
#include <limits>
#include <memory>
+#include <string>
#include <vector>
#include "adbconnection/client.h"
@@ -69,6 +70,21 @@ struct AdbConnectionDdmCallback : public art::DdmCallback {
AdbConnectionState* connection_;
};
+struct AdbConnectionAppInfoCallback : public art::AppInfoCallback {
+ explicit AdbConnectionAppInfoCallback(AdbConnectionState* connection) : connection_(connection) {}
+
+ void AddApplication(const std::string& package_name) REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void RemoveApplication(const std::string& package_name)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void SetCurrentProcessName(const std::string& process_name)
+ REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void SetWaitingForDebugger(bool waiting) REQUIRES_SHARED(art::Locks::mutator_lock_);
+ void SetUserId(int uid) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+ AdbConnectionState* connection_;
+};
+
class AdbConnectionState {
public:
explicit AdbConnectionState(const std::string& name);
@@ -82,6 +98,15 @@ class AdbConnectionState {
// hand-shaking yet.
void PublishDdmData(uint32_t type, const art::ArrayRef<const uint8_t>& data);
+ // Wake up the poll. Call this if the set of interesting event has changed. They will be
+ // recalculated and poll will be called again via fdevent write. This wakeup relies on fdevent
+ // and should be ACKed via AcknowledgeWakeup.
+ void WakeupPollLoop();
+
+ // After a call to WakeupPollLoop, the fdevent internal counter should be decreased via
+ // this method. This should be called after WakeupPollLoop was called and poll triggered.
+ void AcknowledgeWakeup();
+
// Stops debugger threads during shutdown.
void StopDebuggerThreads();
@@ -90,6 +115,22 @@ class AdbConnectionState {
return started_debugger_threads_;
}
+ // Should be called by Framework when it changes its process name.
+ void SetCurrentProcessName(const std::string& process_name);
+
+ // Should be called by Framework when it adds an app to a process.
+ // This can be called several times (see android:process)
+ void AddApplication(const std::string& package_name);
+
+ // Should be called by Framework when it removes an app from a process.
+ void RemoveApplication(const std::string& package_name);
+
+ // Should be called by Framework when its debugging state changes.
+ void SetWaitingForDebugger(bool waiting);
+
+ // Should be called by Framework when the UserID is known.
+ void SetUserId(int uid);
+
private:
uint32_t NextDdmId();
@@ -121,6 +162,7 @@ class AdbConnectionState {
AdbConnectionDebuggerController controller_;
AdbConnectionDdmCallback ddm_callback_;
+ AdbConnectionAppInfoCallback appinfo_callback_;
// Eventfd used to allow the StopDebuggerThreads function to wake up sleeping threads
android::base::unique_fd sleep_event_fd_;
diff --git a/artd/artd.cc b/artd/artd.cc
index d87588bbc9..2a0fee8efd 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -51,6 +51,7 @@
#include "android-base/errors.h"
#include "android-base/file.h"
#include "android-base/logging.h"
+#include "android-base/parseint.h"
#include "android-base/result.h"
#include "android-base/scopeguard.h"
#include "android-base/strings.h"
@@ -113,10 +114,13 @@ using ::android::base::ErrnoError;
using ::android::base::Error;
using ::android::base::Join;
using ::android::base::make_scope_guard;
+using ::android::base::ParseInt;
using ::android::base::ReadFileToString;
using ::android::base::Result;
using ::android::base::Split;
+using ::android::base::StartsWith;
using ::android::base::Tokenize;
+using ::android::base::Trim;
using ::android::base::WriteStringToFd;
using ::android::base::WriteStringToFile;
using ::android::fs_mgr::FstabEntry;
@@ -158,6 +162,16 @@ std::optional<int64_t> GetSize(std::string_view path) {
return size;
}
+bool DeleteFile(const std::string& path) {
+ std::error_code ec;
+ std::filesystem::remove(path, ec);
+ if (ec) {
+ LOG(ERROR) << ART_FORMAT("Failed to remove '{}': {}", path, ec.message());
+ return false;
+ }
+ return true;
+}
+
// Deletes a file. Returns the size of the deleted file, or 0 if the deleted file is empty or an
// error occurs.
int64_t GetSizeAndDeleteFile(const std::string& path) {
@@ -165,13 +179,9 @@ int64_t GetSizeAndDeleteFile(const std::string& path) {
if (!size.has_value()) {
return 0;
}
-
- std::error_code ec;
- if (!std::filesystem::remove(path, ec)) {
- LOG(ERROR) << ART_FORMAT("Failed to remove '{}': {}", path, ec.message());
+ if (!DeleteFile(path)) {
return 0;
}
-
return size.value();
}
@@ -762,15 +772,9 @@ ndk::ScopedAStatus Artd::commitTmpProfile(const TmpProfilePath& in_profile) {
}
ndk::ScopedAStatus Artd::deleteProfile(const ProfilePath& in_profile) {
- RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_profile, "profile");
-
+ // `in_profile` can be either a Pre-reboot path or an ordinary one.
std::string profile_path = OR_RETURN_FATAL(BuildProfileOrDmPath(in_profile));
-
- std::error_code ec;
- std::filesystem::remove(profile_path, ec);
- if (ec) {
- LOG(ERROR) << ART_FORMAT("Failed to remove '{}': {}", profile_path, ec.message());
- }
+ DeleteFile(profile_path);
return ScopedAStatus::ok();
}
@@ -785,7 +789,7 @@ ndk::ScopedAStatus Artd::getProfileVisibility(const ProfilePath& in_profile,
ndk::ScopedAStatus Artd::getArtifactsVisibility(const ArtifactsPath& in_artifactsPath,
FileVisibility* _aidl_return) {
- RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(in_artifactsPath, "artifactsPath");
+ // `in_artifactsPath` can be either a Pre-reboot path or an ordinary one.
std::string oat_path = OR_RETURN_FATAL(BuildArtifactsPath(in_artifactsPath)).oat_path;
*_aidl_return = OR_RETURN_NON_FATAL(GetFileVisibility(oat_path));
return ScopedAStatus::ok();
@@ -1167,36 +1171,15 @@ ndk::ScopedAStatus Artd::dexopt(
LOG(INFO) << "Running dex2oat: " << Join(art_exec_args.Get(), /*separator=*/" ")
<< "\nOpened FDs: " << fd_logger;
- ExecCallbacks callbacks{
- .on_start =
- [&](pid_t pid) {
- std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
- cancellation_signal->pids_.insert(pid);
- // Handle cancellation signals sent before the process starts.
- if (cancellation_signal->is_cancelled_) {
- int res = kill_(pid, SIGKILL);
- DCHECK_EQ(res, 0);
- }
- },
- .on_end =
- [&](pid_t pid) {
- std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
- // The pid should no longer receive kill signals sent by `cancellation_signal`.
- cancellation_signal->pids_.erase(pid);
- },
- };
-
ProcessStat stat;
- Result<int> result = ExecAndReturnCode(art_exec_args.Get(), kLongTimeoutSec, callbacks, &stat);
+ Result<int> result = ExecAndReturnCode(
+ art_exec_args.Get(), kLongTimeoutSec, cancellation_signal->CreateExecCallbacks(), &stat);
_aidl_return->wallTimeMs = stat.wall_time_ms;
_aidl_return->cpuTimeMs = stat.cpu_time_ms;
if (!result.ok()) {
- {
- std::lock_guard<std::mutex> lock(cancellation_signal->mu_);
- if (cancellation_signal->is_cancelled_) {
- _aidl_return->cancelled = true;
- return ScopedAStatus::ok();
- }
+ if (cancellation_signal->IsCancelled()) {
+ _aidl_return->cancelled = true;
+ return ScopedAStatus::ok();
}
return NonFatal("Failed to run dex2oat: " + result.error().message());
}
@@ -1238,6 +1221,32 @@ ScopedAStatus ArtdCancellationSignal::getType(int64_t* _aidl_return) {
return ScopedAStatus::ok();
}
+ExecCallbacks ArtdCancellationSignal::CreateExecCallbacks() {
+ return {
+ .on_start =
+ [&](pid_t pid) {
+ std::lock_guard<std::mutex> lock(mu_);
+ pids_.insert(pid);
+ // Handle cancellation signals sent before the process starts.
+ if (is_cancelled_) {
+ int res = kill_(pid, SIGKILL);
+ DCHECK_EQ(res, 0);
+ }
+ },
+ .on_end =
+ [&](pid_t pid) {
+ std::lock_guard<std::mutex> lock(mu_);
+ // The pid should no longer receive kill signals sent by `cancellation_signal`.
+ pids_.erase(pid);
+ },
+ };
+}
+
+bool ArtdCancellationSignal::IsCancelled() {
+ std::lock_guard<std::mutex> lock(mu_);
+ return is_cancelled_;
+}
+
ScopedAStatus Artd::createCancellationSignal(
std::shared_ptr<IArtdCancellationSignal>* _aidl_return) {
*_aidl_return = ndk::SharedRefBase::make<ArtdCancellationSignal>(kill_);
@@ -1286,6 +1295,19 @@ ScopedAStatus Artd::cleanup(const std::vector<ProfilePath>& in_profilesToKeep,
return ScopedAStatus::ok();
}
+ScopedAStatus Artd::cleanUpPreRebootStagedFiles() {
+ RETURN_FATAL_IF_PRE_REBOOT(options_);
+ std::string android_data = OR_RETURN_NON_FATAL(GetAndroidDataOrError());
+ std::string android_expand = OR_RETURN_NON_FATAL(GetAndroidExpandOrError());
+ for (const std::string& file : ListManagedFiles(android_data, android_expand)) {
+ if (IsPreRebootStagedFile(file)) {
+ LOG(INFO) << ART_FORMAT("Cleaning up obsolete Pre-reboot staged file '{}'", file);
+ DeleteFile(file);
+ }
+ }
+ return ScopedAStatus::ok();
+}
+
ScopedAStatus Artd::isInDalvikCache(const std::string& in_dexFile, bool* _aidl_return) {
// The artifacts should be in the global dalvik-cache directory if:
// (1). the dex file is on a system partition, even if the partition is remounted read-write,
@@ -1372,9 +1394,9 @@ ScopedAStatus Artd::getProfileSize(const ProfilePath& in_profile, int64_t* _aidl
return ScopedAStatus::ok();
}
-ScopedAStatus Artd::commitPreRebootStagedFiles(
- const std::vector<ArtifactsPath>& in_artifacts,
- const std::vector<WritableProfilePath>& in_profiles) {
+ScopedAStatus Artd::commitPreRebootStagedFiles(const std::vector<ArtifactsPath>& in_artifacts,
+ const std::vector<WritableProfilePath>& in_profiles,
+ bool* _aidl_return) {
RETURN_FATAL_IF_PRE_REBOOT(options_);
std::vector<std::pair<std::string, std::string>> files_to_move;
@@ -1424,6 +1446,40 @@ ScopedAStatus Artd::commitPreRebootStagedFiles(
LOG(INFO) << ART_FORMAT("Committed Pre-reboot staged file '{}' to '{}'", src_path, dst_path);
}
+ *_aidl_return = !files_to_move.empty();
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus Artd::checkPreRebootSystemRequirements(const std::string& in_chrootDir,
+ bool* _aidl_return) {
+ RETURN_FATAL_IF_PRE_REBOOT(options_);
+ BuildSystemProperties new_props =
+ OR_RETURN_NON_FATAL(BuildSystemProperties::Create(in_chrootDir + "/system/build.prop"));
+ std::string old_release_str = props_->GetOrEmpty("ro.build.version.release");
+ int old_release;
+ if (!ParseInt(old_release_str, &old_release)) {
+ return NonFatal(
+ ART_FORMAT("Failed to read or parse old release number, got '{}'", old_release_str));
+ }
+ std::string new_release_str = new_props.GetOrEmpty("ro.build.version.release");
+ int new_release;
+ if (!ParseInt(new_release_str, &new_release)) {
+ return NonFatal(
+ ART_FORMAT("Failed to read or parse new release number, got '{}'", new_release_str));
+ }
+ if (new_release - old_release >= 2) {
+ // When the release version difference is large, there is no particular technical reason why we
+ // can't run Pre-reboot Dexopt, but we cannot test and support those cases.
+ LOG(WARNING) << ART_FORMAT(
+ "Pre-reboot Dexopt not supported due to large difference in release versions (old_release: "
+ "{}, new_release: {})",
+ old_release,
+ new_release);
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
+
+ *_aidl_return = true;
return ScopedAStatus::ok();
}
@@ -1697,7 +1753,8 @@ Result<void> Artd::BindMount(const std::string& source, const std::string& targe
return {};
}
-ScopedAStatus Artd::preRebootInit() {
+ScopedAStatus Artd::preRebootInit(
+ const std::shared_ptr<IArtdCancellationSignal>& in_cancellationSignal, bool* _aidl_return) {
RETURN_FATAL_IF_NOT_PRE_REBOOT(options_);
std::string tmp_dir = pre_reboot_tmp_dir_.value_or(kDefaultPreRebootTmpDir);
@@ -1715,7 +1772,8 @@ ScopedAStatus Artd::preRebootInit() {
return NonFatal(ART_FORMAT("Failed to check dir '{}': {}", tmp_dir, ec.message()));
}
if (!is_empty) {
- return Fatal("preRebootInit must not be concurrently called or retried after failure");
+ return Fatal(
+ "preRebootInit must not be concurrently called or retried after cancellation or failure");
}
}
@@ -1729,7 +1787,12 @@ ScopedAStatus Artd::preRebootInit() {
if (!preparation_done) {
OR_RETURN_NON_FATAL(BindMountNewDir(art_apex_data_dir, GetArtApexData()));
OR_RETURN_NON_FATAL(BindMountNewDir(odrefresh_dir, "/data/misc/odrefresh"));
- OR_RETURN_NON_FATAL(PreRebootInitBootImages());
+ ArtdCancellationSignal* cancellation_signal =
+ OR_RETURN_FATAL(ToArtdCancellationSignal(in_cancellationSignal.get()));
+ if (!OR_RETURN_NON_FATAL(PreRebootInitBootImages(cancellation_signal))) {
+ *_aidl_return = false;
+ return ScopedAStatus::ok();
+ }
}
if (!preparation_done) {
@@ -1739,6 +1802,7 @@ ScopedAStatus Artd::preRebootInit() {
}
}
+ *_aidl_return = true;
return ScopedAStatus::ok();
}
@@ -1812,7 +1876,7 @@ Result<void> Artd::PreRebootInitDeriveClasspath(const std::string& path) {
return {};
}
-Result<void> Artd::PreRebootInitBootImages() {
+Result<bool> Artd::PreRebootInitBootImages(ArtdCancellationSignal* cancellation_signal) {
CmdlineBuilder args = OR_RETURN(GetArtExecCmdlineBuilder());
args.Add("--")
.Add(OR_RETURN(BuildArtBinPath("odrefresh")))
@@ -1821,8 +1885,12 @@ Result<void> Artd::PreRebootInitBootImages() {
LOG(INFO) << "Running odrefresh: " << Join(args.Get(), /*separator=*/" ");
- Result<int> result = ExecAndReturnCode(args.Get(), kLongTimeoutSec);
+ Result<int> result =
+ ExecAndReturnCode(args.Get(), kLongTimeoutSec, cancellation_signal->CreateExecCallbacks());
if (!result.ok()) {
+ if (cancellation_signal->IsCancelled()) {
+ return false;
+ }
return Errorf("Failed to run odrefresh: {}", result.error().message());
}
@@ -1833,7 +1901,7 @@ Result<void> Artd::PreRebootInitBootImages() {
return Errorf("odrefresh returned an unexpected code: {}", result.value());
}
- return {};
+ return true;
}
ScopedAStatus Artd::validateDexPath(const std::string& in_dexFile,
@@ -1860,5 +1928,37 @@ ScopedAStatus Artd::validateClassLoaderContext(const std::string& in_dexFile,
return ScopedAStatus::ok();
}
+Result<BuildSystemProperties> BuildSystemProperties::Create(const std::string& filename) {
+ std::string content;
+ if (!ReadFileToString(filename, &content)) {
+ return ErrnoErrorf("Failed to read '{}'", filename);
+ }
+ std::unordered_map<std::string, std::string> system_properties;
+ for (const std::string& raw_line : Split(content, "\n")) {
+ std::string line = Trim(raw_line);
+ if (line.empty() || StartsWith(line, '#')) {
+ continue;
+ }
+ size_t pos = line.find('=');
+ if (pos == std::string::npos || pos == 0 || (pos == 1 && line[1] == '?')) {
+ return Errorf("Malformed system property line '{}' in file '{}'", line, filename);
+ }
+ if (line[pos - 1] == '?') {
+ std::string key = line.substr(/*pos=*/0, /*n=*/pos - 1);
+ if (system_properties.find(key) == system_properties.end()) {
+ system_properties[key] = line.substr(pos + 1);
+ }
+ } else {
+ system_properties[line.substr(/*pos=*/0, /*n=*/pos)] = line.substr(pos + 1);
+ }
+ }
+ return BuildSystemProperties(std::move(system_properties));
+}
+
+std::string BuildSystemProperties::GetProperty(const std::string& key) const {
+ auto it = system_properties_.find(key);
+ return it != system_properties_.end() ? it->second : "";
+}
+
} // namespace artd
} // namespace art
diff --git a/artd/artd.h b/artd/artd.h
index a926dbc652..0733f098be 100644
--- a/artd/artd.h
+++ b/artd/artd.h
@@ -69,6 +69,12 @@ class ArtdCancellationSignal : public aidl::com::android::server::art::BnArtdCan
ndk::ScopedAStatus getType(int64_t* _aidl_return) override;
+ // Returns callbacks to be provided to `ExecUtils`, to register/unregister the process with this
+ // cancellation signal.
+ ExecCallbacks CreateExecCallbacks();
+
+ bool IsCancelled();
+
private:
std::mutex mu_;
// True if cancellation has been signaled.
@@ -77,8 +83,6 @@ class ArtdCancellationSignal : public aidl::com::android::server::art::BnArtdCan
std::unordered_set<pid_t> pids_ GUARDED_BY(mu_);
std::function<int(pid_t, int)> kill_;
-
- friend class Artd;
};
class Artd : public aidl::com::android::server::art::BnArtd {
@@ -202,6 +206,8 @@ class Artd : public aidl::com::android::server::art::BnArtd {
bool in_keepPreRebootStagedFiles,
int64_t* _aidl_return) override;
+ ndk::ScopedAStatus cleanUpPreRebootStagedFiles() override;
+
ndk::ScopedAStatus isInDalvikCache(const std::string& in_dexFile, bool* _aidl_return) override;
ndk::ScopedAStatus deleteRuntimeArtifacts(
@@ -225,9 +231,16 @@ class Artd : public aidl::com::android::server::art::BnArtd {
ndk::ScopedAStatus commitPreRebootStagedFiles(
const std::vector<aidl::com::android::server::art::ArtifactsPath>& in_artifacts,
const std::vector<aidl::com::android::server::art::ProfilePath::WritableProfilePath>&
- in_profiles) override;
+ in_profiles,
+ bool* _aidl_return) override;
+
+ ndk::ScopedAStatus checkPreRebootSystemRequirements(const std::string& in_chrootDir,
+ bool* _aidl_return) override;
- ndk::ScopedAStatus preRebootInit() override;
+ ndk::ScopedAStatus preRebootInit(
+ const std::shared_ptr<aidl::com::android::server::art::IArtdCancellationSignal>&
+ in_cancellationSignal,
+ bool* _aidl_return) override;
ndk::ScopedAStatus validateDexPath(const std::string& in_dexFile,
std::optional<std::string>* _aidl_return) override;
@@ -300,7 +313,7 @@ class Artd : public aidl::com::android::server::art::BnArtd {
android::base::Result<void> PreRebootInitClearEnvs();
android::base::Result<void> PreRebootInitSetEnvFromFile(const std::string& path);
android::base::Result<void> PreRebootInitDeriveClasspath(const std::string& path);
- android::base::Result<void> PreRebootInitBootImages();
+ android::base::Result<bool> PreRebootInitBootImages(ArtdCancellationSignal* cancellation_signal);
std::mutex cache_mu_;
std::optional<std::vector<std::string>> cached_boot_image_locations_ GUARDED_BY(cache_mu_);
@@ -328,6 +341,23 @@ class Artd : public aidl::com::android::server::art::BnArtd {
const std::optional<std::string> init_environ_rc_path_;
};
+// A class for getting system properties from a `build.prop` file.
+class BuildSystemProperties : public tools::SystemProperties {
+ public:
+ // Creates an instance and loads system properties from the `build.prop` file specified at the
+ // given path.
+ static android::base::Result<BuildSystemProperties> Create(const std::string& filename);
+
+ protected:
+ std::string GetProperty(const std::string& key) const override;
+
+ private:
+ explicit BuildSystemProperties(std::unordered_map<std::string, std::string>&& system_properties)
+ : system_properties_(std::move(system_properties)) {}
+
+ const std::unordered_map<std::string, std::string> system_properties_;
+};
+
} // namespace artd
} // namespace art
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
index 1c71ab2fbd..86a7e1d750 100644
--- a/artd/artd_test.cc
+++ b/artd/artd_test.cc
@@ -2096,9 +2096,7 @@ TEST_F(ArtdTest, mergeProfilesWithOptionsDumpClassesAndMethods) {
class ArtdCleanupTest : public ArtdTest {
protected:
- void SetUp() override {
- ArtdTest::SetUp();
-
+ void SetUpForCleanup() {
// Unmanaged files.
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.odex");
CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/oat/1.odex");
@@ -2236,6 +2234,7 @@ class ArtdCleanupTest : public ArtdTest {
};
TEST_F(ArtdCleanupTest, cleanupKeepingPreRebootStagedFiles) {
+ SetUpForCleanup();
CreateGcKeptFile(
android_expand_ +
"/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex.staged");
@@ -2246,6 +2245,7 @@ TEST_F(ArtdCleanupTest, cleanupKeepingPreRebootStagedFiles) {
}
TEST_F(ArtdCleanupTest, cleanupRemovingPreRebootStagedFiles) {
+ SetUpForCleanup();
CreateGcRemovedFile(
android_expand_ +
"/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex.staged");
@@ -2255,6 +2255,28 @@ TEST_F(ArtdCleanupTest, cleanupRemovingPreRebootStagedFiles) {
Verify();
}
+TEST_F(ArtdCleanupTest, cleanUpPreRebootStagedFiles) {
+ // Unmanaged file.
+ CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/1.odex.staged");
+
+ // Not Pre-reboot staged files.
+ CreateGcKeptFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof");
+ CreateGcKeptFile(
+ android_expand_ +
+ "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex");
+ CreateGcKeptFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.odex");
+
+ // Pre-reboot staged files.
+ CreateGcRemovedFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.staged");
+ CreateGcRemovedFile(
+ android_expand_ +
+ "/123456-7890/app/~~nkfeankfna==/com.android.bar-jfoeaofiew==/oat/arm64/base.odex.staged");
+ CreateGcRemovedFile(android_data_ + "/user_de/0/com.android.foo/aaa/oat/arm64/2.odex.staged");
+
+ ASSERT_STATUS_OK(artd_->cleanUpPreRebootStagedFiles());
+ Verify();
+}
+
TEST_F(ArtdTest, isInDalvikCache) {
TEST_DISABLED_FOR_HOST();
@@ -2509,6 +2531,7 @@ TEST_F(ArtdTest, commitPreRebootStagedFiles) {
CreateFile(android_data_ + "/misc/profiles/ref/com.android.bar/primary.prof", "old_prof_2");
+ bool aidl_return;
ASSERT_STATUS_OK(artd_->commitPreRebootStagedFiles(
{
// Has all new files. All old files should be replaced.
@@ -2529,7 +2552,9 @@ TEST_F(ArtdTest, commitPreRebootStagedFiles) {
PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"},
// Has no new file.
PrimaryRefProfilePath{.packageName = "com.android.bar", .profileName = "primary"},
- }));
+ },
+ &aidl_return));
+ EXPECT_TRUE(aidl_return);
CheckContent(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex",
"new_odex_1");
@@ -2565,6 +2590,62 @@ TEST_F(ArtdTest, commitPreRebootStagedFiles) {
"/misc/profiles/ref/com.android.foo/primary.prof.staged"));
}
+TEST_F(ArtdTest, commitPreRebootStagedFilesNoNewFile) {
+ bool aidl_return;
+ ASSERT_STATUS_OK(artd_->commitPreRebootStagedFiles(
+ {
+ ArtifactsPath{.dexPath = android_data_ + "/app/com.android.foo/base.apk",
+ .isa = "arm",
+ .isInDalvikCache = false},
+ },
+ {},
+ &aidl_return));
+ EXPECT_FALSE(aidl_return);
+}
+
+TEST_F(ArtdTest, checkPreRebootSystemRequirements) {
+ EXPECT_CALL(*mock_props_, GetProperty("ro.build.version.release")).WillRepeatedly(Return("15"));
+ std::string chroot_dir = scratch_path_ + "/chroot";
+ bool aidl_return;
+
+ constexpr const char* kTemplate = R"(
+ # Comment.
+ unrelated.system.property=abc
+
+ ro.build.version.release={}
+ )";
+
+ CreateFile(chroot_dir + "/system/build.prop", ART_FORMAT(kTemplate, 15));
+ ASSERT_STATUS_OK(artd_->checkPreRebootSystemRequirements(chroot_dir, &aidl_return));
+ EXPECT_TRUE(aidl_return);
+
+ CreateFile(chroot_dir + "/system/build.prop", ART_FORMAT(kTemplate, 16));
+ ASSERT_STATUS_OK(artd_->checkPreRebootSystemRequirements(chroot_dir, &aidl_return));
+ EXPECT_TRUE(aidl_return);
+
+ CreateFile(chroot_dir + "/system/build.prop", ART_FORMAT(kTemplate, 17));
+ ASSERT_STATUS_OK(artd_->checkPreRebootSystemRequirements(chroot_dir, &aidl_return));
+ EXPECT_FALSE(aidl_return);
+}
+
+TEST_F(ArtdTest, BuildSystemProperties) {
+ constexpr const char* kContent = R"(
+ # Comment.
+ property.foo=123
+ property.foo?=456
+ property.bar?=000
+ property.bar=789
+ property.baz?=111
+ )";
+
+ CreateFile(scratch_path_ + "/build.prop", kContent);
+ BuildSystemProperties props =
+ OR_FAIL(BuildSystemProperties::Create(scratch_path_ + "/build.prop"));
+ EXPECT_EQ(props.GetOrEmpty("property.foo"), "123");
+ EXPECT_EQ(props.GetOrEmpty("property.bar"), "789");
+ EXPECT_EQ(props.GetOrEmpty("property.baz"), "111");
+}
+
class ArtdPreRebootTest : public ArtdTest {
protected:
void SetUp() override {
@@ -2663,7 +2744,12 @@ TEST_F(ArtdPreRebootTest, preRebootInit) {
_))
.WillOnce(Return(0));
- ASSERT_STATUS_OK(artd_->preRebootInit());
+ std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+ ASSERT_STATUS_OK(artd_->createCancellationSignal(&cancellation_signal));
+
+ bool aidl_return;
+ ASSERT_STATUS_OK(artd_->preRebootInit(cancellation_signal, &aidl_return));
+ EXPECT_TRUE(aidl_return);
auto env_var_count = []() {
int count = 0;
@@ -2686,7 +2772,8 @@ TEST_F(ArtdPreRebootTest, preRebootInit) {
// Calling again will not involve `mount`, `derive_classpath`, or `odrefresh` but only restore env
// vars.
- EXPECT_TRUE(artd_->preRebootInit().isOk());
+ ASSERT_STATUS_OK(artd_->preRebootInit(/*in_cancellationSignal=*/nullptr, &aidl_return));
+ EXPECT_TRUE(aidl_return);
EXPECT_EQ(getenv("ANDROID_ART_ROOT"), art_root_);
EXPECT_EQ(getenv("ANDROID_DATA"), android_data_);
EXPECT_STREQ(getenv("BOOTCLASSPATH"), "/foo:/bar");
@@ -2704,7 +2791,11 @@ TEST_F(ArtdPreRebootTest, preRebootInitFailed) {
EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(Contains(art_root_ + "/bin/odrefresh"), _, _))
.WillOnce(Return(1));
- ndk::ScopedAStatus status = artd_->preRebootInit();
+ std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+ ASSERT_STATUS_OK(artd_->createCancellationSignal(&cancellation_signal));
+
+ bool aidl_return;
+ ndk::ScopedAStatus status = artd_->preRebootInit(cancellation_signal, &aidl_return);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.getExceptionCode(), EX_SERVICE_SPECIFIC);
EXPECT_STREQ(status.getMessage(), "odrefresh returned an unexpected code: 1");
@@ -2714,11 +2805,66 @@ TEST_F(ArtdPreRebootTest, preRebootInitNoRetry) {
// Simulate that a previous attempt failed halfway.
ASSERT_TRUE(WriteStringToFile("", pre_reboot_tmp_dir_ + "/classpath.txt"));
- ndk::ScopedAStatus status = artd_->preRebootInit();
+ bool aidl_return;
+ ndk::ScopedAStatus status = artd_->preRebootInit(/*in_cancellationSignal=*/nullptr, &aidl_return);
EXPECT_FALSE(status.isOk());
EXPECT_EQ(status.getExceptionCode(), EX_ILLEGAL_STATE);
- EXPECT_STREQ(status.getMessage(),
- "preRebootInit must not be concurrently called or retried after failure");
+ EXPECT_STREQ(
+ status.getMessage(),
+ "preRebootInit must not be concurrently called or retried after cancellation or failure");
+}
+
+TEST_F(ArtdPreRebootTest, preRebootInitCancelled) {
+ EXPECT_CALL(*mock_exec_utils_,
+ DoExecAndReturnCode(Contains("/apex/com.android.sdkext/bin/derive_classpath"), _, _))
+ .WillOnce(DoAll(WithArg<0>(WriteToFdFlag("/proc/self/fd/", "export BOOTCLASSPATH /foo:/bar")),
+ Return(0)));
+
+ EXPECT_CALL(mock_mount_, Call).Times(2).WillRepeatedly(Return(0));
+
+ std::shared_ptr<IArtdCancellationSignal> cancellation_signal;
+ ASSERT_STATUS_OK(artd_->createCancellationSignal(&cancellation_signal));
+
+ constexpr pid_t kPid = 123;
+ constexpr std::chrono::duration<int> kTimeout = std::chrono::seconds(1);
+
+ std::condition_variable process_started_cv, process_killed_cv;
+ std::mutex mu;
+
+ EXPECT_CALL(*mock_exec_utils_, DoExecAndReturnCode(Contains(art_root_ + "/bin/odrefresh"), _, _))
+ .WillOnce([&](auto, const ExecCallbacks& callbacks, auto) {
+ std::unique_lock<std::mutex> lock(mu);
+ // Step 2.
+ callbacks.on_start(kPid);
+ process_started_cv.notify_one();
+ EXPECT_EQ(process_killed_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
+ // Step 5.
+ callbacks.on_end(kPid);
+ return Error();
+ });
+
+ EXPECT_CALL(mock_kill_, Call(kPid, SIGKILL)).WillOnce([&](auto, auto) {
+ // Step 4.
+ process_killed_cv.notify_one();
+ return 0;
+ });
+
+ std::thread t;
+ bool aidl_return;
+ {
+ std::unique_lock<std::mutex> lock(mu);
+ // Step 1.
+ t = std::thread(
+ [&] { ASSERT_STATUS_OK(artd_->preRebootInit(cancellation_signal, &aidl_return)); });
+ EXPECT_EQ(process_started_cv.wait_for(lock, kTimeout), std::cv_status::no_timeout);
+ // Step 3.
+ cancellation_signal->cancel();
+ }
+
+ t.join();
+
+ // Step 6.
+ EXPECT_FALSE(aidl_return);
}
TEST_F(ArtdPreRebootTest, dexopt) {
diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
index 54f55f8460..47fc89966b 100644
--- a/artd/binder/com/android/server/art/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -192,6 +192,15 @@ interface IArtd {
boolean keepPreRebootStagedFiles);
/**
+ * Deletes all Pre-reboot staged files.
+ *
+ * Not supported in Pre-reboot Dexopt mode.
+ *
+ * Throws fatal errors. Logs and ignores non-fatal errors.
+ */
+ void cleanUpPreRebootStagedFiles();
+
+ /**
* Returns whether the artifacts of the primary dex files should be in the global dalvik-cache
* directory.
*
@@ -263,24 +272,47 @@ interface IArtd {
* Not supported in Pre-reboot Dexopt mode.
*
* Throws fatal and non-fatal errors.
+ *
+ * @return true if any file has been committed.
*/
- void commitPreRebootStagedFiles(
+ boolean commitPreRebootStagedFiles(
in List<com.android.server.art.ArtifactsPath> artifacts,
in List<com.android.server.art.ProfilePath.WritableProfilePath> profiles);
+ /**
+ * Returns whether the old system and the new system meet the requirements to run Pre-reboot
+ * Dexopt. This method can only be called with a chroot dir set up by
+ * {@link IDexoptChrootSetup#setUp}.
+ *
+ * Not supported in Pre-reboot Dexopt mode.
+ *
+ * Throws fatal and non-fatal errors.
+ */
+ boolean checkPreRebootSystemRequirements(@utf8InCpp String chrootDir);
+
// The methods below are only for Pre-reboot Dexopt and only supported in Pre-reboot Dexopt
// mode.
/**
* Initializes the environment for Pre-reboot Dexopt. This operation includes initializing
- * environment variables and boot images.
+ * environment variables and boot images. Returns true on success, or false on cancellation.
+ * Throws on failure.
*
* Note that this method results in a non-persistent state change, so it must be called every
* time a new instance of artd is started for Pre-reboot Dexopt.
*
+ * On the first call to this method, a cancellation signal must be passed through the {@code
+ * cancellationSignal} parameter. The cancellation signal can then be used for cancelling the
+ * first call. On subsequent calls to this method, the {@code cancellationSignal} parameter is
+ * ignored.
+ *
+ * After cancellation or failure, the environment will not be usable for Pre-reboot Dexopt, and
+ * this operation cannot be retried.
+ *
* Throws fatal and non-fatal errors.
*/
- void preRebootInit();
+ boolean preRebootInit(
+ in @nullable com.android.server.art.IArtdCancellationSignal cancellationSignal);
/** For Pre-reboot Dexopt use. See {@link ArtJni#validateDexPath}. */
@nullable @utf8InCpp String validateDexPath(@utf8InCpp String dexFile);
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index e5b72ee4a7..05cc1a6314 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -569,7 +569,6 @@ class ReleaseTargetChecker:
self._checker.check_native_library('libandroidio')
# Check internal native library dependencies.
- self._checker.check_native_library('libcrypto')
self._checker.check_native_library('libexpat')
diff --git a/cmdline/cmdline_parser_test.cc b/cmdline/cmdline_parser_test.cc
index 93218bfe32..e586dd4292 100644
--- a/cmdline/cmdline_parser_test.cc
+++ b/cmdline/cmdline_parser_test.cc
@@ -38,7 +38,6 @@ namespace art {
return lhs.enabled_ == rhs.enabled_ &&
lhs.min_save_period_ms_ == rhs.min_save_period_ms_ &&
lhs.save_resolved_classes_delay_ms_ == rhs.save_resolved_classes_delay_ms_ &&
- lhs.hot_startup_method_samples_ == rhs.hot_startup_method_samples_ &&
lhs.min_methods_to_save_ == rhs.min_methods_to_save_ &&
lhs.min_classes_to_save_ == rhs.min_classes_to_save_ &&
lhs.min_notification_before_wake_ == rhs.min_notification_before_wake_ &&
@@ -237,6 +236,9 @@ TEST_F(CmdlineParserTest, TestSimpleSuccesses) {
EXPECT_SINGLE_PARSE_VALUE(false, "-XX:DisableHSpaceCompactForOOM", M::EnableHSpaceCompactForOOM);
EXPECT_SINGLE_PARSE_VALUE(0.5, "-XX:HeapTargetUtilization=0.5", M::HeapTargetUtilization);
EXPECT_SINGLE_PARSE_VALUE(5u, "-XX:ParallelGCThreads=5", M::ParallelGCThreads);
+ EXPECT_SINGLE_PARSE_VALUE(10u,
+ "-XX:ParallelGCThreads=5 -XX:ParallelGCThreads=10",
+ M::ParallelGCThreads);
} // TEST_F
TEST_F(CmdlineParserTest, TestSimpleFailures) {
@@ -487,19 +489,18 @@ TEST_F(CmdlineParserTest, TestJitOptions) {
* -Xps-*
*/
TEST_F(CmdlineParserTest, ProfileSaverOptions) {
- ProfileSaverOptions opt = ProfileSaverOptions(true, 1, 2, 3, 4, 5, 6, 7, 8, 9, "abc", true);
+ ProfileSaverOptions opt = ProfileSaverOptions(true, 1, 2, 3, 4, 5, 6, 7, 8, "abc", true);
EXPECT_SINGLE_PARSE_VALUE(opt,
"-Xjitsaveprofilinginfo "
"-Xps-min-save-period-ms:1 "
"-Xps-min-first-save-ms:2 "
"-Xps-save-resolved-classes-delay-ms:3 "
- "-Xps-hot-startup-method-samples:4 "
- "-Xps-min-methods-to-save:5 "
- "-Xps-min-classes-to-save:6 "
- "-Xps-min-notification-before-wake:7 "
- "-Xps-max-notification-before-wake:8 "
- "-Xps-inline-cache-threshold:9 "
+ "-Xps-min-methods-to-save:4 "
+ "-Xps-min-classes-to-save:5 "
+ "-Xps-min-notification-before-wake:6 "
+ "-Xps-max-notification-before-wake:7 "
+ "-Xps-inline-cache-threshold:8 "
"-Xps-profile-path:abc "
"-Xps-profile-boot-class-path",
M::ProfileSaverOpts);
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index 7cacfde12a..34c1b0fc97 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -832,10 +832,8 @@ struct CmdlineType<ProfileSaverOptions> : CmdlineTypeParser<ProfileSaverOptions>
type_parser.Parse(suffix));
}
if (android::base::StartsWith(option, "hot-startup-method-samples:")) {
- CmdlineType<unsigned int> type_parser;
- return ParseInto(existing,
- &ProfileSaverOptions::hot_startup_method_samples_,
- type_parser.Parse(suffix));
+ LOG(WARNING) << "-Xps-hot-startup-method-samples option is deprecated";
+ return Result::SuccessNoValue();
}
if (android::base::StartsWith(option, "min-methods-to-save:")) {
CmdlineType<unsigned int> type_parser;
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index 87dbfee412..8b415c641d 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -135,8 +135,8 @@ bool CompilerOptions::IsImageClass(const char* descriptor) const {
return image_classes_.find(std::string_view(descriptor)) != image_classes_.end();
}
-bool CompilerOptions::IsPreloadedClass(const char* pretty_descriptor) const {
- return preloaded_classes_.find(std::string_view(pretty_descriptor)) != preloaded_classes_.end();
+bool CompilerOptions::IsPreloadedClass(std::string_view pretty_descriptor) const {
+ return preloaded_classes_.find(pretty_descriptor) != preloaded_classes_.end();
}
bool CompilerOptions::ShouldCompileWithClinitCheck(ArtMethod* method) const {
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index abdb01b372..36ecf88199 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -292,7 +292,7 @@ class CompilerOptions final {
// Returns whether the given `pretty_descriptor` is in the list of preloaded
// classes. `pretty_descriptor` should be the result of calling `PrettyDescriptor`.
- EXPORT bool IsPreloadedClass(const char* pretty_descriptor) const;
+ EXPORT bool IsPreloadedClass(std::string_view pretty_descriptor) const;
bool ParseCompilerOptions(const std::vector<std::string>& options,
bool ignore_unrecognized,
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 051368cc8a..4b2f8d2e14 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -178,7 +178,8 @@ bool JitCompiler::CompileMethod(
Thread* self, JitMemoryRegion* region, ArtMethod* method, CompilationKind compilation_kind) {
SCOPED_TRACE << "JIT compiling "
<< method->PrettyMethod()
- << " (kind=" << compilation_kind << ")";
+ << " (kind=" << compilation_kind << ")"
+ << " from " << method->GetDexFile()->GetLocation();
DCHECK(!method->IsProxyMethod());
DCHECK(method->GetDeclaringClass()->IsResolved());
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 77ebff6da2..8a096d0744 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -14,11 +14,11 @@
* limitations under the License.
*/
+#include <math.h>
+
#include <memory>
#include <type_traits>
-#include <math.h>
-
#include "art_method-inl.h"
#include "base/bit_utils.h"
#include "base/casts.h"
@@ -40,7 +40,7 @@
#include "mirror/object-inl.h"
#include "mirror/object_array-inl.h"
#include "mirror/stack_trace_element-inl.h"
-#include "nativehelper/ScopedLocalRef.h"
+#include "nativehelper/scoped_local_ref.h"
#include "nativeloader/native_loader.h"
#include "oat/oat_quick_method_header.h"
#include "runtime.h"
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index 3ac4bd7dcc..aec7b45a1a 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -59,15 +59,15 @@ static int32_t constexpr kPrimIntMax = 0x7fffffff;
// Maximum value for a primitive long.
static int64_t constexpr kPrimLongMax = INT64_C(0x7fffffffffffffff);
-constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
-constexpr size_t status_byte_offset =
- mirror::Class::StatusOffset().SizeValue() + (status_lsb_position / kBitsPerByte);
-constexpr uint32_t shifted_visibly_initialized_value =
- enum_cast<uint32_t>(ClassStatus::kVisiblyInitialized) << (status_lsb_position % kBitsPerByte);
-constexpr uint32_t shifted_initializing_value =
- enum_cast<uint32_t>(ClassStatus::kInitializing) << (status_lsb_position % kBitsPerByte);
-constexpr uint32_t shifted_initialized_value =
- enum_cast<uint32_t>(ClassStatus::kInitialized) << (status_lsb_position % kBitsPerByte);
+constexpr size_t kClassStatusLsbPosition = SubtypeCheckBits::BitStructSizeOf();
+constexpr size_t kClassStatusByteOffset =
+ mirror::Class::StatusOffset().SizeValue() + (kClassStatusLsbPosition / kBitsPerByte);
+constexpr uint32_t kShiftedVisiblyInitializedValue = enum_cast<uint32_t>(
+ ClassStatus::kVisiblyInitialized) << (kClassStatusLsbPosition % kBitsPerByte);
+constexpr uint32_t kShiftedInitializingValue =
+ enum_cast<uint32_t>(ClassStatus::kInitializing) << (kClassStatusLsbPosition % kBitsPerByte);
+constexpr uint32_t kShiftedInitializedValue =
+ enum_cast<uint32_t>(ClassStatus::kInitialized) << (kClassStatusLsbPosition % kBitsPerByte);
class Assembler;
class CodeGenerationData;
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 988809ee48..226e5343e2 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -1350,18 +1350,18 @@ void CodeGeneratorARM64::GenerateFrameEntry() {
// We don't emit a read barrier here to save on code size. We rely on the
// resolution trampoline to do a suspend check before re-entering this code.
__ Ldr(temp1, MemOperand(kArtMethodRegister, ArtMethod::DeclaringClassOffset().Int32Value()));
- __ Ldrb(temp2, HeapOperand(temp1, status_byte_offset));
- __ Cmp(temp2, shifted_visibly_initialized_value);
+ __ Ldrb(temp2, HeapOperand(temp1, kClassStatusByteOffset));
+ __ Cmp(temp2, kShiftedVisiblyInitializedValue);
__ B(hs, &frame_entry_label_);
// Check if we're initialized and jump to code that does a memory barrier if
// so.
- __ Cmp(temp2, shifted_initialized_value);
+ __ Cmp(temp2, kShiftedInitializedValue);
__ B(hs, &memory_barrier);
// Check if we're initializing and the thread initializing is the one
// executing the code.
- __ Cmp(temp2, shifted_initializing_value);
+ __ Cmp(temp2, kShiftedInitializingValue);
__ B(lo, &resolution);
__ Ldr(temp1, HeapOperand(temp1, mirror::Class::ClinitThreadIdOffset().Int32Value()));
@@ -2080,8 +2080,8 @@ void InstructionCodeGeneratorARM64::GenerateClassInitializationCheck(SlowPathCod
// size, load only the high byte of the field and compare with 0xf0.
// Note: The same code size could be achieved with LDR+MNV(asr #24)+CBNZ but benchmarks
// show that this pattern is slower (tested on little cores).
- __ Ldrb(temp, HeapOperand(class_reg, status_byte_offset));
- __ Cmp(temp, shifted_visibly_initialized_value);
+ __ Ldrb(temp, HeapOperand(class_reg, kClassStatusByteOffset));
+ __ Cmp(temp, kShiftedVisiblyInitializedValue);
__ B(lo, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
}
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 7c1a9b21f2..979db6ed75 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -2354,18 +2354,18 @@ void CodeGeneratorARMVIXL::GenerateFrameEntry() {
// We don't emit a read barrier here to save on code size. We rely on the
// resolution trampoline to do a suspend check before re-entering this code.
__ Ldr(temp1, MemOperand(kMethodRegister, ArtMethod::DeclaringClassOffset().Int32Value()));
- __ Ldrb(temp2, MemOperand(temp1, status_byte_offset));
- __ Cmp(temp2, shifted_visibly_initialized_value);
+ __ Ldrb(temp2, MemOperand(temp1, kClassStatusByteOffset));
+ __ Cmp(temp2, kShiftedVisiblyInitializedValue);
__ B(cs, &frame_entry_label_);
// Check if we're initialized and jump to code that does a memory barrier if
// so.
- __ Cmp(temp2, shifted_initialized_value);
+ __ Cmp(temp2, kShiftedInitializedValue);
__ B(cs, &memory_barrier);
// Check if we're initializing and the thread initializing is the one
// executing the code.
- __ Cmp(temp2, shifted_initializing_value);
+ __ Cmp(temp2, kShiftedInitializingValue);
__ B(lo, &resolution);
__ Ldr(temp1, MemOperand(temp1, mirror::Class::ClinitThreadIdOffset().Int32Value()));
@@ -7862,8 +7862,8 @@ void InstructionCodeGeneratorARMVIXL::GenerateClassInitializationCheck(
LoadClassSlowPathARMVIXL* slow_path, vixl32::Register class_reg) {
UseScratchRegisterScope temps(GetVIXLAssembler());
vixl32::Register temp = temps.Acquire();
- __ Ldrb(temp, MemOperand(class_reg, status_byte_offset));
- __ Cmp(temp, shifted_visibly_initialized_value);
+ __ Ldrb(temp, MemOperand(class_reg, kClassStatusByteOffset));
+ __ Cmp(temp, kShiftedVisiblyInitializedValue);
__ B(lo, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
}
diff --git a/compiler/optimizing/code_generator_riscv64.cc b/compiler/optimizing/code_generator_riscv64.cc
index 9b499a08a2..75d5db83a0 100644
--- a/compiler/optimizing/code_generator_riscv64.cc
+++ b/compiler/optimizing/code_generator_riscv64.cc
@@ -137,8 +137,8 @@ static RegisterSet OneRegInReferenceOutSaveEverythingCallerSaves() {
template <ClassStatus kStatus>
static constexpr int64_t ShiftedSignExtendedClassStatusValue() {
// This is used only for status values that have the highest bit set.
- static_assert(CLZ(enum_cast<uint32_t>(kStatus)) == status_lsb_position);
- constexpr uint32_t kShiftedStatusValue = enum_cast<uint32_t>(kStatus) << status_lsb_position;
+ static_assert(CLZ(enum_cast<uint32_t>(kStatus)) == kClassStatusLsbPosition);
+ constexpr uint32_t kShiftedStatusValue = enum_cast<uint32_t>(kStatus) << kClassStatusLsbPosition;
static_assert(kShiftedStatusValue >= 0x80000000u);
return static_cast<int64_t>(kShiftedStatusValue) - (INT64_C(1) << 32);
}
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 4329e40efc..2f6dde3df7 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -1390,17 +1390,18 @@ void CodeGeneratorX86::GenerateFrameEntry() {
NearLabel continue_execution, resolution;
// We'll use EBP as temporary.
__ pushl(EBP);
+ __ cfi().AdjustCFAOffset(4);
// Check if we're visibly initialized.
// We don't emit a read barrier here to save on code size. We rely on the
// resolution trampoline to do a suspend check before re-entering this code.
__ movl(EBP, Address(kMethodRegisterArgument, ArtMethod::DeclaringClassOffset().Int32Value()));
- __ cmpb(Address(EBP, status_byte_offset), Immediate(shifted_visibly_initialized_value));
+ __ cmpb(Address(EBP, kClassStatusByteOffset), Immediate(kShiftedVisiblyInitializedValue));
__ j(kAboveEqual, &continue_execution);
// Check if we're initializing and the thread initializing is the one
// executing the code.
- __ cmpb(Address(EBP, status_byte_offset), Immediate(shifted_initializing_value));
+ __ cmpb(Address(EBP, kClassStatusByteOffset), Immediate(kShiftedInitializingValue));
__ j(kBelow, &resolution);
__ movl(EBP, Address(EBP, mirror::Class::ClinitThreadIdOffset().Int32Value()));
@@ -1409,13 +1410,16 @@ void CodeGeneratorX86::GenerateFrameEntry() {
__ Bind(&resolution);
__ popl(EBP);
+ __ cfi().AdjustCFAOffset(-4);
// Jump to the resolution stub.
ThreadOffset32 entrypoint_offset =
GetThreadOffset<kX86PointerSize>(kQuickQuickResolutionTrampoline);
__ fs()->jmp(Address::Absolute(entrypoint_offset));
__ Bind(&continue_execution);
+ __ cfi().AdjustCFAOffset(4); // Undo the `-4` adjustment above. We get here with EBP pushed.
__ popl(EBP);
+ __ cfi().AdjustCFAOffset(-4);
}
__ Bind(&frame_entry_label_);
@@ -7510,7 +7514,7 @@ void InstructionCodeGeneratorX86::VisitClinitCheck(HClinitCheck* check) {
void InstructionCodeGeneratorX86::GenerateClassInitializationCheck(
SlowPathCode* slow_path, Register class_reg) {
- __ cmpb(Address(class_reg, status_byte_offset), Immediate(shifted_visibly_initialized_value));
+ __ cmpb(Address(class_reg, kClassStatusByteOffset), Immediate(kShiftedVisiblyInitializedValue));
__ j(kBelow, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
}
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 5b4f1b8b25..5c9ee8fea4 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -1839,13 +1839,14 @@ void CodeGeneratorX86_64::GenerateFrameEntry() {
__ movl(CpuRegister(TMP),
Address(CpuRegister(kMethodRegisterArgument),
ArtMethod::DeclaringClassOffset().Int32Value()));
- __ cmpb(Address(CpuRegister(TMP), status_byte_offset),
- Immediate(shifted_visibly_initialized_value));
+ __ cmpb(Address(CpuRegister(TMP), kClassStatusByteOffset),
+ Immediate(kShiftedVisiblyInitializedValue));
__ j(kAboveEqual, &frame_entry_label_);
// Check if we're initializing and the thread initializing is the one
// executing the code.
- __ cmpb(Address(CpuRegister(TMP), status_byte_offset), Immediate(shifted_initializing_value));
+ __ cmpb(Address(CpuRegister(TMP), kClassStatusByteOffset),
+ Immediate(kShiftedInitializingValue));
__ j(kBelow, &resolution);
__ movl(CpuRegister(TMP),
@@ -6596,7 +6597,7 @@ void ParallelMoveResolverX86_64::RestoreScratch(int reg) {
void InstructionCodeGeneratorX86_64::GenerateClassInitializationCheck(
SlowPathCode* slow_path, CpuRegister class_reg) {
- __ cmpb(Address(class_reg, status_byte_offset), Immediate(shifted_visibly_initialized_value));
+ __ cmpb(Address(class_reg, kClassStatusByteOffset), Immediate(kShiftedVisiblyInitializedValue));
__ j(kBelow, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
}
diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc
index 14e6683cf8..8d698d6c82 100644
--- a/compiler/optimizing/loop_optimization.cc
+++ b/compiler/optimizing/loop_optimization.cc
@@ -532,7 +532,7 @@ HLoopOptimization::HLoopOptimization(HGraph* graph,
vector_permanent_map_(nullptr),
vector_external_set_(nullptr),
predicate_info_map_(nullptr),
- vector_mode_(VectorMode::kSequential),
+ synthesis_mode_(LoopSynthesisMode::kSequential),
vector_preheader_(nullptr),
vector_header_(nullptr),
vector_body_(nullptr),
@@ -970,10 +970,10 @@ bool HLoopOptimization::TryVectorizePredicated(LoopNode* node,
}
bool HLoopOptimization::TryVectorizedTraditional(LoopNode* node,
- HBasicBlock* body,
- HBasicBlock* exit,
- HPhi* main_phi,
- int64_t trip_count) {
+ HBasicBlock* body,
+ HBasicBlock* exit,
+ HPhi* main_phi,
+ int64_t trip_count) {
HBasicBlock* header = node->loop_info->GetHeader();
size_t num_of_blocks = header->GetLoopInformation()->GetBlocks().NumSetBits();
@@ -1283,6 +1283,8 @@ void HLoopOptimization::VectorizePredicated(LoopNode* node,
HBasicBlock* exit) {
DCHECK(IsInPredicatedVectorizationMode());
+ vector_external_set_->clear();
+
HBasicBlock* header = node->loop_info->GetHeader();
HBasicBlock* preheader = node->loop_info->GetPreHeader();
@@ -1322,7 +1324,7 @@ void HLoopOptimization::VectorizePredicated(LoopNode* node,
// <vectorized-loop-body>
HBasicBlock* preheader_for_vector_loop =
graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit);
- vector_mode_ = VectorMode::kVector;
+ synthesis_mode_ = LoopSynthesisMode::kVector;
GenerateNewLoopPredicated(node,
preheader_for_vector_loop,
vector_index_,
@@ -1333,7 +1335,7 @@ void HLoopOptimization::VectorizePredicated(LoopNode* node,
// for ( ; i < stc; i += 1)
// <loop-body>
if (needs_disambiguation_test) {
- vector_mode_ = VectorMode::kSequential;
+ synthesis_mode_ = LoopSynthesisMode::kSequential;
HBasicBlock* preheader_for_cleanup_loop =
graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit);
// Use "Traditional" version for the sequential loop.
@@ -1369,6 +1371,8 @@ void HLoopOptimization::VectorizeTraditional(LoopNode* node,
int64_t trip_count) {
DCHECK(!IsInPredicatedVectorizationMode());
+ vector_external_set_->clear();
+
HBasicBlock* header = node->loop_info->GetHeader();
HBasicBlock* preheader = node->loop_info->GetPreHeader();
@@ -1472,7 +1476,7 @@ void HLoopOptimization::VectorizeTraditional(LoopNode* node,
// moved around during suspend checks, since all analysis was based on
// nothing more than the Android runtime alignment conventions.
if (ptc != nullptr) {
- vector_mode_ = VectorMode::kSequential;
+ synthesis_mode_ = LoopSynthesisMode::kSequential;
HBasicBlock* preheader_for_peeling_loop =
graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit);
GenerateNewLoopScalarOrTraditional(node,
@@ -1486,7 +1490,7 @@ void HLoopOptimization::VectorizeTraditional(LoopNode* node,
// Generate vector loop, possibly further unrolled:
// for ( ; i < vtc; i += chunk)
// <vectorized-loop-body>
- vector_mode_ = VectorMode::kVector;
+ synthesis_mode_ = LoopSynthesisMode::kVector;
HBasicBlock* preheader_for_vector_loop =
graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit);
GenerateNewLoopScalarOrTraditional(node,
@@ -1500,7 +1504,7 @@ void HLoopOptimization::VectorizeTraditional(LoopNode* node,
// for ( ; i < stc; i += 1)
// <loop-body>
if (needs_cleanup) {
- vector_mode_ = VectorMode::kSequential;
+ synthesis_mode_ = LoopSynthesisMode::kSequential;
HBasicBlock* preheader_for_cleanup_loop =
graph_->TransformLoopForVectorization(vector_header_, vector_body_, exit);
GenerateNewLoopScalarOrTraditional(node,
@@ -1566,7 +1570,6 @@ HPhi* HLoopOptimization::InitializeForNewLoop(HBasicBlock* new_preheader, HInstr
vector_header_->AddPhi(phi);
vector_index_ = phi;
vector_permanent_map_->clear();
- vector_external_set_->clear();
predicate_info_map_->clear();
return phi;
@@ -1578,7 +1581,7 @@ void HLoopOptimization::GenerateNewLoopScalarOrTraditional(LoopNode* node,
HInstruction* hi,
HInstruction* step,
uint32_t unroll) {
- DCHECK(unroll == 1 || vector_mode_ == VectorMode::kVector);
+ DCHECK(unroll == 1 || synthesis_mode_ == LoopSynthesisMode::kVector);
DataType::Type induc_type = lo->GetType();
HPhi* phi = InitializeForNewLoop(new_preheader, lo);
@@ -1600,7 +1603,7 @@ void HLoopOptimization::GenerateNewLoopPredicated(LoopNode* node,
HInstruction* hi,
HInstruction* step) {
DCHECK(IsInPredicatedVectorizationMode());
- DCHECK_EQ(vector_mode_, VectorMode::kVector);
+ DCHECK(synthesis_mode_ == LoopSynthesisMode::kVector);
DataType::Type induc_type = lo->GetType();
HPhi* phi = InitializeForNewLoop(new_preheader, lo);
@@ -1887,7 +1890,7 @@ bool HLoopOptimization::VectorizeUse(LoopNode* node,
size_from >= size_vec &&
VectorizeUse(node, opa, generate_code, type, restrictions))) {
if (generate_code) {
- if (vector_mode_ == VectorMode::kVector) {
+ if (synthesis_mode_ == LoopSynthesisMode::kVector) {
vector_map_->Put(instruction, vector_map_->Get(opa)); // operand pass-through
} else {
GenerateVecOp(instruction, vector_map_->Get(opa), nullptr, type);
@@ -1963,7 +1966,7 @@ bool HLoopOptimization::VectorizeUse(LoopNode* node,
// Accept shift operator for vectorizable/invariant operands.
// TODO: accept symbolic, albeit loop invariant shift factors.
DCHECK(r != nullptr);
- if (generate_code && vector_mode_ != VectorMode::kVector) { // de-idiom
+ if (generate_code && synthesis_mode_ != LoopSynthesisMode::kVector) { // de-idiom
r = opa;
}
int64_t distance = 0;
@@ -1991,7 +1994,7 @@ bool HLoopOptimization::VectorizeUse(LoopNode* node,
}
// Accept ABS(x) for vectorizable operand.
DCHECK(r != nullptr);
- if (generate_code && vector_mode_ != VectorMode::kVector) { // de-idiom
+ if (generate_code && synthesis_mode_ != LoopSynthesisMode::kVector) { // de-idiom
r = opa;
}
if (VectorizeUse(node, r, generate_code, type, restrictions)) {
@@ -2187,7 +2190,7 @@ void HLoopOptimization::GenerateVecInv(HInstruction* org, DataType::Type type) {
if (vector_map_->find(org) == vector_map_->end()) {
// In scalar code, just use a self pass-through for scalar invariants
// (viz. expression remains itself).
- if (vector_mode_ == VectorMode::kSequential) {
+ if (synthesis_mode_ == LoopSynthesisMode::kSequential) {
vector_map_->Put(org, org);
return;
}
@@ -2208,7 +2211,7 @@ void HLoopOptimization::GenerateVecInv(HInstruction* org, DataType::Type type) {
vector = new (global_allocator_)
HVecReplicateScalar(global_allocator_, input, type, vector_length_, kNoDexPc);
vector_permanent_map_->Put(org, Insert(vector_preheader_, vector));
- vector_external_set_->insert(vector);
+ MaybeInsertInVectorExternalSet(vector);
}
vector_map_->Put(org, vector);
}
@@ -2235,7 +2238,7 @@ void HLoopOptimization::GenerateVecMem(HInstruction* org,
DataType::Type type) {
uint32_t dex_pc = org->GetDexPc();
HInstruction* vector = nullptr;
- if (vector_mode_ == VectorMode::kVector) {
+ if (synthesis_mode_ == LoopSynthesisMode::kVector) {
// Vector store or load.
bool is_string_char_at = false;
HInstruction* base = org->InputAt(0);
@@ -2267,7 +2270,7 @@ void HLoopOptimization::GenerateVecMem(HInstruction* org,
}
} else {
// Scalar store or load.
- DCHECK(vector_mode_ == VectorMode::kSequential);
+ DCHECK(synthesis_mode_ == LoopSynthesisMode::kSequential);
if (opb != nullptr) {
DataType::Type component_type = org->AsArraySet()->GetComponentType();
vector = new (global_allocator_) HArraySet(
@@ -2285,7 +2288,7 @@ void HLoopOptimization::GenerateVecReductionPhi(HPhi* orig_phi) {
DCHECK(reductions_->find(orig_phi) != reductions_->end());
DCHECK(reductions_->Get(orig_phi->InputAt(1)) == orig_phi);
HInstruction* vector = nullptr;
- if (vector_mode_ == VectorMode::kSequential) {
+ if (synthesis_mode_ == LoopSynthesisMode::kSequential) {
HPhi* new_phi = new (global_allocator_) HPhi(
global_allocator_, kNoRegNumber, 0, orig_phi->GetType());
vector_header_->AddPhi(new_phi);
@@ -2314,7 +2317,7 @@ void HLoopOptimization::GenerateVecReductionPhiInputs(HPhi* phi, HInstruction* r
DCHECK(new_phi->IsVecOperation());
}
// Prepare the new initialization.
- if (vector_mode_ == VectorMode::kVector) {
+ if (synthesis_mode_ == LoopSynthesisMode::kVector) {
// Generate a [initial, 0, .., 0] vector for add or
// a [initial, initial, .., initial] vector for min/max.
HVecOperation* red_vector = new_red->AsVecOperation();
@@ -2337,7 +2340,7 @@ void HLoopOptimization::GenerateVecReductionPhiInputs(HPhi* phi, HInstruction* r
vector_length,
kNoDexPc));
}
- vector_external_set_->insert(new_init);
+ MaybeInsertInVectorExternalSet(new_init);
} else {
new_init = ReduceAndExtractIfNeeded(new_init);
}
@@ -2366,24 +2369,24 @@ HInstruction* HLoopOptimization::ReduceAndExtractIfNeeded(HInstruction* instruct
HVecReduce* reduce = new (global_allocator_) HVecReduce(
global_allocator_, instruction, type, vector_length, kind, kNoDexPc);
exit->InsertInstructionBefore(reduce, exit->GetFirstInstruction());
- vector_external_set_->insert(reduce);
+ MaybeInsertInVectorExternalSet(reduce);
instruction = new (global_allocator_) HVecExtractScalar(
global_allocator_, reduce, type, vector_length, 0, kNoDexPc);
exit->InsertInstructionAfter(instruction, reduce);
- vector_external_set_->insert(instruction);
+ MaybeInsertInVectorExternalSet(instruction);
}
}
return instruction;
}
-#define GENERATE_VEC(x, y) \
- if (vector_mode_ == VectorMode::kVector) { \
- vector = (x); \
- } else { \
- DCHECK(vector_mode_ == VectorMode::kSequential); \
- vector = (y); \
- } \
+#define GENERATE_VEC(x, y) \
+ if (synthesis_mode_ == LoopSynthesisMode::kVector) { \
+ vector = (x); \
+ } else { \
+ DCHECK(synthesis_mode_ == LoopSynthesisMode::kSequential); \
+ vector = (y); \
+ } \
break;
HInstruction* HLoopOptimization::GenerateVecOp(HInstruction* org,
@@ -2461,7 +2464,7 @@ HInstruction* HLoopOptimization::GenerateVecOp(HInstruction* org,
new (global_allocator_) HAbs(org_type, opa, dex_pc));
case HInstruction::kEqual: {
// Special case.
- DCHECK_EQ(vector_mode_, VectorMode::kVector);
+ DCHECK_EQ(synthesis_mode_, LoopSynthesisMode::kVector);
vector = new (global_allocator_)
HVecCondition(global_allocator_, opa, opb, type, vector_length_, dex_pc);
}
@@ -2527,14 +2530,14 @@ bool HLoopOptimization::VectorizeHalvingAddIdiom(LoopNode* node,
// Accept recognized halving add for vectorizable operands. Vectorized code uses the
// shorthand idiomatic operation. Sequential code uses the original scalar expressions.
DCHECK(r != nullptr && s != nullptr);
- if (generate_code && vector_mode_ != VectorMode::kVector) { // de-idiom
+ if (generate_code && synthesis_mode_ != LoopSynthesisMode::kVector) { // de-idiom
r = instruction->InputAt(0);
s = instruction->InputAt(1);
}
if (VectorizeUse(node, r, generate_code, type, restrictions) &&
VectorizeUse(node, s, generate_code, type, restrictions)) {
if (generate_code) {
- if (vector_mode_ == VectorMode::kVector) {
+ if (synthesis_mode_ == LoopSynthesisMode::kVector) {
vector_map_->Put(instruction, new (global_allocator_) HVecHalvingAdd(
global_allocator_,
vector_map_->Get(r),
@@ -2603,14 +2606,14 @@ bool HLoopOptimization::VectorizeSADIdiom(LoopNode* node,
// Accept SAD idiom for vectorizable operands. Vectorized code uses the shorthand
// idiomatic operation. Sequential code uses the original scalar expressions.
DCHECK(r != nullptr && s != nullptr);
- if (generate_code && vector_mode_ != VectorMode::kVector) { // de-idiom
+ if (generate_code && synthesis_mode_ != LoopSynthesisMode::kVector) { // de-idiom
r = s = abs->InputAt(0);
}
if (VectorizeUse(node, acc, generate_code, sub_type, restrictions) &&
VectorizeUse(node, r, generate_code, sub_type, restrictions) &&
VectorizeUse(node, s, generate_code, sub_type, restrictions)) {
if (generate_code) {
- if (vector_mode_ == VectorMode::kVector) {
+ if (synthesis_mode_ == LoopSynthesisMode::kVector) {
vector_map_->Put(instruction, new (global_allocator_) HVecSADAccumulate(
global_allocator_,
vector_map_->Get(acc),
@@ -2676,7 +2679,7 @@ bool HLoopOptimization::VectorizeDotProdIdiom(LoopNode* node,
DCHECK(r != nullptr && s != nullptr);
// Accept dot product idiom for vectorizable operands. Vectorized code uses the shorthand
// idiomatic operation. Sequential code uses the original scalar expressions.
- if (generate_code && vector_mode_ != VectorMode::kVector) { // de-idiom
+ if (generate_code && synthesis_mode_ != LoopSynthesisMode::kVector) { // de-idiom
r = mul_left;
s = mul_right;
}
@@ -2684,7 +2687,7 @@ bool HLoopOptimization::VectorizeDotProdIdiom(LoopNode* node,
VectorizeUse(node, r, generate_code, op_type, restrictions) &&
VectorizeUse(node, s, generate_code, op_type, restrictions)) {
if (generate_code) {
- if (vector_mode_ == VectorMode::kVector) {
+ if (synthesis_mode_ == LoopSynthesisMode::kVector) {
vector_map_->Put(instruction, new (global_allocator_) HVecDotProd(
global_allocator_,
vector_map_->Get(acc),
@@ -2762,7 +2765,7 @@ bool HLoopOptimization::VectorizeIfCondition(LoopNode* node,
return false;
}
- if (generate_code && vector_mode_ != VectorMode::kVector) { // de-idiom
+ if (generate_code && synthesis_mode_ != LoopSynthesisMode::kVector) { // de-idiom
opa_promoted = opa;
opb_promoted = opb;
}
@@ -2774,7 +2777,7 @@ bool HLoopOptimization::VectorizeIfCondition(LoopNode* node,
vector_map_->Get(opa_promoted),
vector_map_->Get(opb_promoted),
type);
- DCHECK_EQ(vector_mode_, VectorMode::kVector);
+ DCHECK_EQ(synthesis_mode_, LoopSynthesisMode::kVector);
HInstruction* vec_pred_not = new (global_allocator_)
HVecPredNot(global_allocator_, vec_cond, type, vector_length_, hif->GetDexPc());
@@ -3167,11 +3170,17 @@ void HLoopOptimization::InitPredicateInfoMap(LoopNode* node,
back_edge_info->SetControlPredicate(header_info->GetTruePredicate());
}
-std::ostream& operator<<(std::ostream& os, const HLoopOptimization::VectorMode& mode) {
+void HLoopOptimization::MaybeInsertInVectorExternalSet(HInstruction* instruction) {
+ if (IsInPredicatedVectorizationMode()) {
+ vector_external_set_->insert(instruction);
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, const HLoopOptimization::LoopSynthesisMode& mode) {
switch (mode) {
- case HLoopOptimization::VectorMode::kSequential:
+ case HLoopOptimization::LoopSynthesisMode::kSequential:
return os << "kSequential";
- case HLoopOptimization::VectorMode::kVector:
+ case HLoopOptimization::LoopSynthesisMode::kVector:
return os << "kVector";
}
return os;
diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h
index 4817060dfa..546d42b4fa 100644
--- a/compiler/optimizing/loop_optimization.h
+++ b/compiler/optimizing/loop_optimization.h
@@ -105,14 +105,14 @@ class HLoopOptimization : public HOptimization {
};
/*
- * Vectorization mode during synthesis
+ * Loop synthesis mode during vectorization
* (sequential peeling/cleanup loop or vector loop).
*/
- enum class VectorMode {
+ enum class LoopSynthesisMode {
kSequential,
kVector
};
- friend std::ostream& operator<<(std::ostream& os, const VectorMode& fd_logger);
+ friend std::ostream& operator<<(std::ostream& os, const LoopSynthesisMode& fd_logger);
/*
* Representation of a unit-stride array reference.
@@ -480,6 +480,7 @@ class HLoopOptimization : public HOptimization {
bool CanRemoveCycle(); // Whether the current 'iset_' is removable.
bool IsInPredicatedVectorizationMode() const { return predicated_vectorization_mode_; }
+ void MaybeInsertInVectorExternalSet(HInstruction* instruction);
// Compiler options (to query ISA features).
const CompilerOptions* compiler_options_;
@@ -547,6 +548,12 @@ class HLoopOptimization : public HOptimization {
// Tracks vector operations that are inserted outside of the loop (preheader, exit)
// as part of vectorization (e.g. replicate scalar for loop invariants and reduce ops
// for loop reductions).
+ //
+ // The instructions in the set are live for the whole vectorization process of the current
+ // loop, not just during generation of a particular loop version (as the sets above).
+ //
+ // Currently the set is being only used in the predicated mode - for assigning governing
+ // predicates.
ScopedArenaSet<HInstruction*>* vector_external_set_;
// A mapping between a basic block of the original loop and its associated PredicateInfo.
@@ -555,7 +562,7 @@ class HLoopOptimization : public HOptimization {
ScopedArenaSafeMap<HBasicBlock*, BlockPredicateInfo*>* predicate_info_map_;
// Temporary vectorization bookkeeping.
- VectorMode vector_mode_; // synthesis mode
+ LoopSynthesisMode synthesis_mode_; // synthesis mode
HBasicBlock* vector_preheader_; // preheader of the new loop
HBasicBlock* vector_header_; // header of the new loop
HBasicBlock* vector_body_; // body of the new loop
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 33ffc07ba8..89369f59f1 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -143,6 +143,8 @@ enum GraphAnalysisResult {
kAnalysisSuccess,
};
+std::ostream& operator<<(std::ostream& os, GraphAnalysisResult ga);
+
template <typename T>
static inline typename std::make_unsigned<T>::type MakeUnsigned(T x) {
return static_cast<typename std::make_unsigned<T>::type>(x);
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 45d534a9ec..3f73459a00 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -30,6 +30,7 @@
#include "base/macros.h"
#include "base/mutex.h"
#include "base/scoped_arena_allocator.h"
+#include "base/systrace.h"
#include "base/timing_logger.h"
#include "builder.h"
#include "code_generator.h"
@@ -781,6 +782,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* allocator,
}
if (Compiler::IsPathologicalCase(*code_item, method_idx, dex_file)) {
+ SCOPED_TRACE << "Not compiling because of pathological case";
MaybeRecordStat(compilation_stats_.get(), MethodCompilationStat::kNotCompiledPathological);
return nullptr;
}
@@ -791,6 +793,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* allocator,
if ((compiler_options.GetCompilerFilter() == CompilerFilter::kSpace)
&& (CodeItemInstructionAccessor(dex_file, code_item).InsnsSizeInCodeUnits() >
kSpaceFilterOptimizingThreshold)) {
+ SCOPED_TRACE << "Not compiling because of space filter";
MaybeRecordStat(compilation_stats_.get(), MethodCompilationStat::kNotCompiledSpaceFilter);
return nullptr;
}
@@ -865,6 +868,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* allocator,
compilation_stats_.get());
GraphAnalysisResult result = builder.BuildGraph();
if (result != kAnalysisSuccess) {
+ SCOPED_TRACE << "Not compiling because of " << result;
switch (result) {
case kAnalysisSkipped: {
MaybeRecordStat(compilation_stats_.get(),
@@ -927,6 +931,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* allocator,
// However, we may have run out of memory trying to create it, so in this
// case just abort the compilation.
if (graph->GetProfilingInfo() == nullptr) {
+ SCOPED_TRACE << "Not compiling because of out of memory";
MaybeRecordStat(compilation_stats_.get(), MethodCompilationStat::kJitOutOfMemoryForCommit);
return nullptr;
}
@@ -938,6 +943,7 @@ CodeGenerator* OptimizingCompiler::TryCompile(ArenaAllocator* allocator,
compilation_stats_.get());
if (UNLIKELY(codegen->GetFrameSize() > codegen->GetMaximumFrameSize())) {
+ SCOPED_TRACE << "Not compiling because of stack frame too large";
LOG(WARNING) << "Stack frame size is " << codegen->GetFrameSize()
<< " which is larger than the maximum of " << codegen->GetMaximumFrameSize()
<< " bytes. Method: " << graph->PrettyMethod();
diff --git a/compiler/utils/riscv64/assembler_riscv64.cc b/compiler/utils/riscv64/assembler_riscv64.cc
index 9cd4a45f0c..a0832bc3a8 100644
--- a/compiler/utils/riscv64/assembler_riscv64.cc
+++ b/compiler/utils/riscv64/assembler_riscv64.cc
@@ -6553,6 +6553,13 @@ void Riscv64Assembler::Unimp() {
/////////////////////////////// RV64 MACRO Instructions END ///////////////////////////////
const Riscv64Assembler::Branch::BranchInfo Riscv64Assembler::Branch::branch_info_[] = {
+ // Compressed branches (can be promoted to longer)
+ {2, 0, Riscv64Assembler::Branch::kOffset9}, // kCondCBranch
+ {2, 0, Riscv64Assembler::Branch::kOffset12}, // kUncondCBranch
+ // Compressed branches (can't be promoted to longer)
+ {2, 0, Riscv64Assembler::Branch::kOffset9}, // kBareCondCBranch
+ {2, 0, Riscv64Assembler::Branch::kOffset12}, // kBareUncondCBranch
+
// Short branches (can be promoted to longer).
{4, 0, Riscv64Assembler::Branch::kOffset13}, // kCondBranch
{4, 0, Riscv64Assembler::Branch::kOffset21}, // kUncondBranch
@@ -6562,13 +6569,14 @@ const Riscv64Assembler::Branch::BranchInfo Riscv64Assembler::Branch::branch_info
{4, 0, Riscv64Assembler::Branch::kOffset21}, // kBareUncondBranch
{4, 0, Riscv64Assembler::Branch::kOffset21}, // kBareCall
- // Medium branch.
+ // Medium branches.
+ {6, 2, Riscv64Assembler::Branch::kOffset21}, // kCondCBranch21
{8, 4, Riscv64Assembler::Branch::kOffset21}, // kCondBranch21
// Long branches.
{12, 4, Riscv64Assembler::Branch::kOffset32}, // kLongCondBranch
- {8, 0, Riscv64Assembler::Branch::kOffset32}, // kLongUncondBranch
- {8, 0, Riscv64Assembler::Branch::kOffset32}, // kLongCall
+ {8, 0, Riscv64Assembler::Branch::kOffset32}, // kLongUncondBranch
+ {8, 0, Riscv64Assembler::Branch::kOffset32}, // kLongCall
// label.
{8, 0, Riscv64Assembler::Branch::kOffset32}, // kLabel
@@ -6595,10 +6603,50 @@ void Riscv64Assembler::Branch::InitShortOrLong(Riscv64Assembler::Branch::OffsetB
type_ = type;
}
+void Riscv64Assembler::Branch::InitShortOrLong(Riscv64Assembler::Branch::OffsetBits offset_size,
+ Riscv64Assembler::Branch::Type compressed_type,
+ Riscv64Assembler::Branch::Type short_type,
+ Riscv64Assembler::Branch::Type long_type,
+ Riscv64Assembler::Branch::Type longest_type) {
+ Riscv64Assembler::Branch::Type type = compressed_type;
+ if (offset_size > branch_info_[type].offset_size) {
+ type = short_type;
+ if (offset_size > branch_info_[type].offset_size) {
+ type = long_type;
+ if (offset_size > branch_info_[type].offset_size) {
+ type = longest_type;
+ }
+ }
+ }
+ type_ = type;
+}
+
void Riscv64Assembler::Branch::InitializeType(Type initial_type) {
OffsetBits offset_size_needed = GetOffsetSizeNeeded(location_, target_);
switch (initial_type) {
+ case kCondCBranch:
+ CHECK(IsCompressableCondition());
+ if (condition_ != kUncond) {
+ InitShortOrLong(
+ offset_size_needed, kCondCBranch, kCondBranch, kCondCBranch21, kLongCondBranch);
+ break;
+ }
+ FALLTHROUGH_INTENDED;
+ case kUncondCBranch:
+ InitShortOrLong(offset_size_needed, kUncondCBranch, kUncondBranch, kLongUncondBranch);
+ break;
+ case kBareCondCBranch:
+ if (condition_ != kUncond) {
+ type_ = kBareCondCBranch;
+ CHECK_LE(offset_size_needed, GetOffsetSize());
+ break;
+ }
+ FALLTHROUGH_INTENDED;
+ case kBareUncondCBranch:
+ type_ = kBareUncondCBranch;
+ CHECK_LE(offset_size_needed, GetOffsetSize());
+ break;
case kCondBranch:
if (condition_ != kUncond) {
InitShortOrLong(offset_size_needed, kCondBranch, kCondBranch21, kLongCondBranch);
@@ -6673,7 +6721,21 @@ bool Riscv64Assembler::Branch::IsUncond(BranchCondition condition, XRegister lhs
}
}
-Riscv64Assembler::Branch::Branch(uint32_t location, uint32_t target, XRegister rd, bool is_bare)
+bool Riscv64Assembler::Branch::IsCompressed(Type type) {
+ switch (type) {
+ case kCondCBranch:
+ case kUncondCBranch:
+ case kBareCondCBranch:
+ case kBareUncondCBranch:
+ case kCondCBranch21:
+ return true;
+ default:
+ return false;
+ }
+}
+
+Riscv64Assembler::Branch::Branch(
+ uint32_t location, uint32_t target, XRegister rd, bool is_bare, bool compression_allowed)
: old_location_(location),
location_(location),
target_(target),
@@ -6681,9 +6743,12 @@ Riscv64Assembler::Branch::Branch(uint32_t location, uint32_t target, XRegister r
rhs_reg_(Zero),
freg_(kNoFRegister),
condition_(kUncond),
- next_branch_id_(0u) {
- InitializeType(
- (rd != Zero ? (is_bare ? kBareCall : kCall) : (is_bare ? kBareUncondBranch : kUncondBranch)));
+ next_branch_id_(0u),
+ compression_allowed_(compression_allowed) {
+ InitializeType((rd != Zero ?
+ (is_bare ? kBareCall : kCall) :
+ (is_bare ? (compression_allowed ? kBareUncondCBranch : kBareUncondBranch) :
+ (compression_allowed ? kUncondCBranch : kUncondBranch))));
}
Riscv64Assembler::Branch::Branch(uint32_t location,
@@ -6691,7 +6756,8 @@ Riscv64Assembler::Branch::Branch(uint32_t location,
Riscv64Assembler::BranchCondition condition,
XRegister lhs_reg,
XRegister rhs_reg,
- bool is_bare)
+ bool is_bare,
+ bool compression_allowed)
: old_location_(location),
location_(location),
target_(target),
@@ -6703,7 +6769,12 @@ Riscv64Assembler::Branch::Branch(uint32_t location,
DCHECK_NE(condition, kUncond);
DCHECK(!IsNop(condition, lhs_reg, rhs_reg));
DCHECK(!IsUncond(condition, lhs_reg, rhs_reg));
- InitializeType(is_bare ? kBareCondBranch : kCondBranch);
+ if (!IsCompressableCondition()) {
+ compression_allowed = false;
+ }
+ compression_allowed_ = compression_allowed;
+ InitializeType(is_bare ? (compression_allowed ? kBareCondCBranch : kBareCondBranch) :
+ (compression_allowed ? kCondCBranch : kCondBranch));
}
Riscv64Assembler::Branch::Branch(uint32_t location,
@@ -6717,7 +6788,8 @@ Riscv64Assembler::Branch::Branch(uint32_t location,
rhs_reg_(Zero),
freg_(kNoFRegister),
condition_(kUncond),
- next_branch_id_(0u) {
+ next_branch_id_(0u),
+ compression_allowed_(false) {
CHECK_NE(rd , Zero);
InitializeType(label_or_literal_type);
}
@@ -6733,7 +6805,8 @@ Riscv64Assembler::Branch::Branch(uint32_t location,
rhs_reg_(Zero),
freg_(rd),
condition_(kUncond),
- next_branch_id_(0u) {
+ next_branch_id_(0u),
+ compression_allowed_(false) {
InitializeType(literal_type);
}
@@ -6768,6 +6841,8 @@ Riscv64Assembler::BranchCondition Riscv64Assembler::Branch::OppositeCondition(
Riscv64Assembler::Branch::Type Riscv64Assembler::Branch::GetType() const { return type_; }
+Riscv64Assembler::Branch::Type Riscv64Assembler::Branch::GetOldType() const { return old_type_; }
+
Riscv64Assembler::BranchCondition Riscv64Assembler::Branch::GetCondition() const {
return condition_;
}
@@ -6776,6 +6851,14 @@ XRegister Riscv64Assembler::Branch::GetLeftRegister() const { return lhs_reg_; }
XRegister Riscv64Assembler::Branch::GetRightRegister() const { return rhs_reg_; }
+XRegister Riscv64Assembler::Branch::GetNonZeroRegister() const {
+ DCHECK(GetLeftRegister() == Zero || GetRightRegister() == Zero)
+ << "Either register has to be Zero register";
+ DCHECK(GetLeftRegister() != Zero || GetRightRegister() != Zero)
+ << "Either register has to be non-Zero register";
+ return GetLeftRegister() == Zero ? GetRightRegister() : GetLeftRegister();
+}
+
FRegister Riscv64Assembler::Branch::GetFRegister() const { return freg_; }
uint32_t Riscv64Assembler::Branch::GetTarget() const { return target_; }
@@ -6809,6 +6892,11 @@ bool Riscv64Assembler::Branch::IsBare() const {
bool Riscv64Assembler::Branch::IsResolved() const { return target_ != kUnresolved; }
+bool Riscv64Assembler::Branch::IsCompressableCondition() const {
+ return (condition_ == kCondEQ || condition_ == kCondNE) &&
+ ((lhs_reg_ == Zero && IsShortReg(rhs_reg_)) || (rhs_reg_ == Zero && IsShortReg(lhs_reg_)));
+}
+
Riscv64Assembler::Branch::OffsetBits Riscv64Assembler::Branch::GetOffsetSize() const {
return branch_info_[type_].offset_size;
}
@@ -6818,10 +6906,15 @@ Riscv64Assembler::Branch::OffsetBits Riscv64Assembler::Branch::GetOffsetSizeNeed
// For unresolved targets assume the shortest encoding
// (later it will be made longer if needed).
if (target == kUnresolved) {
- return kOffset13;
+ return kOffset9;
}
int64_t distance = static_cast<int64_t>(target) - location;
- if (IsInt<kOffset13>(distance)) {
+
+ if (IsInt<kOffset9>(distance)) {
+ return kOffset9;
+ } else if (IsInt<kOffset12>(distance)) {
+ return kOffset12;
+ } else if (IsInt<kOffset13>(distance)) {
return kOffset13;
} else if (IsInt<kOffset21>(distance)) {
return kOffset21;
@@ -6848,23 +6941,52 @@ uint32_t Riscv64Assembler::Branch::PromoteIfNeeded() {
DCHECK(IsResolved());
Type old_type = type_;
switch (type_) {
+ // Compressed branches (can be promoted to longer)
+ case kUncondCBranch: {
+ OffsetBits needed_size = GetOffsetSizeNeeded(GetOffsetLocation(), target_);
+ if (needed_size <= GetOffsetSize()) {
+ return 0u;
+ }
+
+ type_ = needed_size <= branch_info_[kUncondBranch].offset_size ? kUncondBranch :
+ kLongUncondBranch;
+ break;
+ }
+ case kCondCBranch: {
+ DCHECK(IsCompressableCondition());
+ OffsetBits needed_size = GetOffsetSizeNeeded(GetOffsetLocation(), target_);
+ if (needed_size <= GetOffsetSize()) {
+ return 0u;
+ }
+
+ if (needed_size <= branch_info_[kCondBranch].offset_size) {
+ type_ = kCondBranch;
+ break;
+ }
+ FALLTHROUGH_INTENDED;
+ }
// Short branches (can be promoted to longer).
case kCondBranch: {
OffsetBits needed_size = GetOffsetSizeNeeded(GetOffsetLocation(), target_);
if (needed_size <= GetOffsetSize()) {
return 0u;
}
- // The offset remains the same for `kCondBranch21` for forward branches.
- DCHECK_EQ(branch_info_[kCondBranch21].length - branch_info_[kCondBranch21].pc_offset,
+
+ Type cond21Type = old_type == kCondCBranch ? kCondCBranch21 : kCondBranch21;
+ if (compression_allowed_ && cond21Type == kCondBranch21 && IsCompressableCondition()) {
+ // If this branch was promoted from compressed one on initialization stage
+ // it could be promoted back to compressed if possible
+ cond21Type = kCondCBranch21;
+ }
+
+ // The offset remains the same for `kCond[C]Branch21` for forward branches.
+ DCHECK_EQ(branch_info_[cond21Type].length - branch_info_[cond21Type].pc_offset,
branch_info_[kCondBranch].length - branch_info_[kCondBranch].pc_offset);
if (target_ <= location_) {
- // Calculate the needed size for kCondBranch21.
- needed_size =
- GetOffsetSizeNeeded(location_ + branch_info_[kCondBranch21].pc_offset, target_);
+ // Calculate the needed size for kCond[C]Branch21.
+ needed_size = GetOffsetSizeNeeded(location_ + branch_info_[cond21Type].pc_offset, target_);
}
- type_ = (needed_size <= branch_info_[kCondBranch21].offset_size)
- ? kCondBranch21
- : kLongCondBranch;
+ type_ = (needed_size <= branch_info_[cond21Type].offset_size) ? cond21Type : kLongCondBranch;
break;
}
case kUncondBranch:
@@ -6880,15 +7002,21 @@ uint32_t Riscv64Assembler::Branch::PromoteIfNeeded() {
type_ = kLongCall;
break;
// Medium branch (can be promoted to long).
- case kCondBranch21:
- if (GetOffsetSizeNeeded(GetOffsetLocation(), target_) <= GetOffsetSize()) {
+ case kCondCBranch21:
+ DCHECK(IsCompressableCondition());
+ FALLTHROUGH_INTENDED;
+ case kCondBranch21: {
+ OffsetBits needed_size = GetOffsetSizeNeeded(GetOffsetLocation(), target_);
+ if (needed_size <= GetOffsetSize()) {
return 0u;
}
type_ = kLongCondBranch;
break;
+ }
default:
// Other branch types cannot be promoted.
- DCHECK_LE(GetOffsetSizeNeeded(GetOffsetLocation(), target_), GetOffsetSize()) << type_;
+ DCHECK_LE(GetOffsetSizeNeeded(GetOffsetLocation(), target_), GetOffsetSize())
+ << static_cast<uint32_t>(type_);
return 0u;
}
DCHECK(type_ != old_type);
@@ -6947,6 +7075,7 @@ void Riscv64Assembler::EmitBranch(Riscv64Assembler::Branch* branch) {
BranchCondition condition = branch->GetCondition();
XRegister lhs = branch->GetLeftRegister();
XRegister rhs = branch->GetRightRegister();
+ // Disable Compressed emitter explicitly and enable where it is needed
ScopedNoCInstructions no_compression(this);
auto emit_auipc_and_next = [&](XRegister reg, auto next) {
@@ -6957,6 +7086,26 @@ void Riscv64Assembler::EmitBranch(Riscv64Assembler::Branch* branch) {
};
switch (branch->GetType()) {
+ // Compressed branches
+ case Branch::kCondCBranch:
+ case Branch::kBareCondCBranch: {
+ ScopedUseCInstructions use_compression(this);
+ CHECK_EQ(overwrite_location_, branch->GetOffsetLocation());
+ DCHECK(branch->IsCompressableCondition());
+ if (condition == kCondEQ) {
+ CBeqz(branch->GetNonZeroRegister(), offset);
+ } else {
+ CBnez(branch->GetNonZeroRegister(), offset);
+ }
+ break;
+ }
+ case Branch::kUncondCBranch:
+ case Branch::kBareUncondCBranch: {
+ ScopedUseCInstructions use_compression(this);
+ CHECK_EQ(overwrite_location_, branch->GetOffsetLocation());
+ CJ(offset);
+ break;
+ }
// Short branches.
case Branch::kUncondBranch:
case Branch::kBareUncondBranch:
@@ -6981,7 +7130,22 @@ void Riscv64Assembler::EmitBranch(Riscv64Assembler::Branch* branch) {
CHECK_EQ(overwrite_location_, branch->GetOffsetLocation());
J(offset);
break;
-
+ case Branch::kCondCBranch21: {
+ DCHECK(branch->IsCompressableCondition());
+ {
+ ScopedUseCInstructions use_compression(this);
+ if (condition == kCondNE) {
+ DCHECK_EQ(Branch::OppositeCondition(condition), kCondEQ);
+ CBeqz(branch->GetNonZeroRegister(), branch->GetLength());
+ } else {
+ DCHECK_EQ(Branch::OppositeCondition(condition), kCondNE);
+ CBnez(branch->GetNonZeroRegister(), branch->GetLength());
+ }
+ }
+ CHECK_EQ(overwrite_location_, branch->GetOffsetLocation());
+ J(offset);
+ break;
+ }
// Long branches.
case Branch::kLongCondBranch:
EmitBcond(Branch::OppositeCondition(condition), lhs, rhs, branch->GetLength());
@@ -7033,11 +7197,12 @@ void Riscv64Assembler::EmitBranches() {
}
void Riscv64Assembler::FinalizeLabeledBranch(Riscv64Label* label) {
+ const uint32_t alignment =
+ IsExtensionEnabled(Riscv64Extension::kZca) ? sizeof(uint16_t) : sizeof(uint32_t);
Branch& this_branch = branches_.back();
- DCHECK_ALIGNED(this_branch.GetLength(), sizeof(uint32_t));
- uint32_t length = this_branch.GetLength() / sizeof(uint32_t);
- ScopedNoCInstructions no_compression(this);
-
+ uint32_t branch_length = this_branch.GetLength();
+ DCHECK(IsAlignedParam(branch_length, alignment));
+ uint32_t length = branch_length / alignment;
if (!label->IsBound()) {
// Branch forward (to a following label), distance is unknown.
// The first branch forward will contain 0, serving as the terminator of
@@ -7050,7 +7215,11 @@ void Riscv64Assembler::FinalizeLabeledBranch(Riscv64Label* label) {
}
// Reserve space for the branch.
for (; length != 0u; --length) {
- Nop();
+ if (alignment == sizeof(uint16_t)) {
+ Emit16(0);
+ } else {
+ Emit32(0);
+ }
}
}
@@ -7067,13 +7236,20 @@ void Riscv64Assembler::Bcond(
}
uint32_t target = label->IsBound() ? GetLabelLocation(label) : Branch::kUnresolved;
- branches_.emplace_back(buffer_.Size(), target, condition, lhs, rhs, is_bare);
+ branches_.emplace_back(buffer_.Size(),
+ target,
+ condition,
+ lhs,
+ rhs,
+ is_bare,
+ IsExtensionEnabled(Riscv64Extension::kZca));
FinalizeLabeledBranch(label);
}
void Riscv64Assembler::Buncond(Riscv64Label* label, XRegister rd, bool is_bare) {
uint32_t target = label->IsBound() ? GetLabelLocation(label) : Branch::kUnresolved;
- branches_.emplace_back(buffer_.Size(), target, rd, is_bare);
+ branches_.emplace_back(
+ buffer_.Size(), target, rd, is_bare, IsExtensionEnabled(Riscv64Extension::kZca));
FinalizeLabeledBranch(label);
}
@@ -7216,6 +7392,7 @@ void Riscv64Assembler::ReserveJumpTableSpace() {
void Riscv64Assembler::PromoteBranches() {
// Promote short branches to long as necessary.
bool changed;
+ // To avoid re-computing predicate on each iteration cache it in local
do {
changed = false;
for (auto& branch : branches_) {
diff --git a/compiler/utils/riscv64/assembler_riscv64.h b/compiler/utils/riscv64/assembler_riscv64.h
index 4f561dc1a6..667d09dc74 100644
--- a/compiler/utils/riscv64/assembler_riscv64.h
+++ b/compiler/utils/riscv64/assembler_riscv64.h
@@ -1943,7 +1943,12 @@ class Riscv64Assembler final : public Assembler {
class Branch {
public:
enum Type : uint8_t {
- // TODO(riscv64): Support 16-bit instructions ("C" Standard Extension).
+ // Compressed branches (can be promoted to longer)
+ kCondCBranch,
+ kUncondCBranch,
+ // Compressed branches (can't be promoted to longer)
+ kBareCondCBranch,
+ kBareUncondCBranch,
// Short branches (can be promoted to longer).
kCondBranch,
@@ -1954,7 +1959,9 @@ class Riscv64Assembler final : public Assembler {
kBareUncondBranch,
kBareCall,
- // Medium branch (can be promoted to long).
+ // Medium branches (can be promoted to long).
+ // Compressed version
+ kCondCBranch21,
kCondBranch21,
// Long branches.
@@ -1975,6 +1982,8 @@ class Riscv64Assembler final : public Assembler {
// Bit sizes of offsets defined as enums to minimize chance of typos.
enum OffsetBits {
+ kOffset9 = 9,
+ kOffset12 = 12,
kOffset13 = 13,
kOffset21 = 21,
kOffset32 = 32,
@@ -1996,14 +2005,16 @@ class Riscv64Assembler final : public Assembler {
static const BranchInfo branch_info_[/* Type */];
// Unconditional branch or call.
- Branch(uint32_t location, uint32_t target, XRegister rd, bool is_bare);
+ Branch(
+ uint32_t location, uint32_t target, XRegister rd, bool is_bare, bool compression_allowed);
// Conditional branch.
Branch(uint32_t location,
uint32_t target,
BranchCondition condition,
XRegister lhs_reg,
XRegister rhs_reg,
- bool is_bare);
+ bool is_bare,
+ bool compression_allowed);
// Label address or literal.
Branch(uint32_t location, uint32_t target, XRegister rd, Type label_or_literal_type);
Branch(uint32_t location, uint32_t target, FRegister rd, Type literal_type);
@@ -2012,13 +2023,16 @@ class Riscv64Assembler final : public Assembler {
// others are effectively unconditional.
static bool IsNop(BranchCondition condition, XRegister lhs, XRegister rhs);
static bool IsUncond(BranchCondition condition, XRegister lhs, XRegister rhs);
+ static bool IsCompressed(Type type);
static BranchCondition OppositeCondition(BranchCondition cond);
Type GetType() const;
+ Type GetOldType() const;
BranchCondition GetCondition() const;
XRegister GetLeftRegister() const;
XRegister GetRightRegister() const;
+ XRegister GetNonZeroRegister() const;
FRegister GetFRegister() const;
uint32_t GetTarget() const;
uint32_t GetLocation() const;
@@ -2032,6 +2046,9 @@ class Riscv64Assembler final : public Assembler {
uint32_t NextBranchId() const;
+ // Checks if condition meets compression requirements
+ bool IsCompressableCondition() const;
+
// Returns the bit size of the signed offset that the branch instruction can handle.
OffsetBits GetOffsetSize() const;
@@ -2067,6 +2084,11 @@ class Riscv64Assembler final : public Assembler {
void InitializeType(Type initial_type);
// Helper for the above.
void InitShortOrLong(OffsetBits ofs_size, Type short_type, Type long_type, Type longest_type);
+ void InitShortOrLong(OffsetBits ofs_size,
+ Type compressed_type,
+ Type short_type,
+ Type long_type,
+ Type longest_type);
uint32_t old_location_; // Offset into assembler buffer in bytes.
uint32_t location_; // Offset into assembler buffer in bytes.
@@ -2085,6 +2107,8 @@ class Riscv64Assembler final : public Assembler {
// NOTE: encoded the same way as a position in a linked Label (id + sizeof(void*))
// Label itself is used to hold the 'head' of this list
uint32_t next_branch_id_;
+
+ bool compression_allowed_;
};
// Branch and literal fixup.
diff --git a/compiler/utils/riscv64/assembler_riscv64_test.cc b/compiler/utils/riscv64/assembler_riscv64_test.cc
index c122706208..256ca12498 100644
--- a/compiler/utils/riscv64/assembler_riscv64_test.cc
+++ b/compiler/utils/riscv64/assembler_riscv64_test.cc
@@ -337,9 +337,11 @@ class AssemblerRISCV64Test : public AssemblerTest<Riscv64Assembler,
}
std::string EmitNops(size_t size) {
- // TODO(riscv64): Support "C" Standard Extension.
- DCHECK_ALIGNED(size, sizeof(uint32_t));
- const size_t num_nops = size / sizeof(uint32_t);
+ const size_t nop_size = GetAssembler()->IsExtensionEnabled(Riscv64Extension::kZca) ?
+ sizeof(uint16_t) :
+ sizeof(uint32_t);
+ DCHECK(IsAlignedParam(size, nop_size));
+ const size_t num_nops = size / nop_size;
return RepeatInsn(num_nops, "nop\n", [&]() { __ Nop(); });
}
@@ -475,6 +477,25 @@ class AssemblerRISCV64Test : public AssemblerTest<Riscv64Assembler,
};
}
+ auto GetPrintBcondOpposite() {
+ return [=]([[maybe_unused]] const std::string& cond,
+ const std::string& opposite_cond,
+ const std::string& args,
+ const std::string& target) {
+ return "b" + opposite_cond + args + ", " + target + "\n";
+ };
+ }
+
+ auto GetPrintBcondAndJ(const std::string& skip_label) {
+ return [=](const std::string& cond,
+ [[maybe_unused]] const std::string& opposite_cond,
+ const std::string& args,
+ const std::string& target) {
+ return "b" + cond + args + ", " + skip_label + "f\n" + "j " + target + "\n" + skip_label +
+ ":\n";
+ };
+ }
+
auto GetPrintBcondOppositeAndJ(const std::string& skip_label) {
return [=]([[maybe_unused]] const std::string& cond,
const std::string& opposite_cond,
@@ -579,34 +600,34 @@ class AssemblerRISCV64Test : public AssemblerTest<Riscv64Assembler,
DriverStr(expected, test_name);
}
- size_t MaxOffset13BackwardDistance() {
- return 4 * KB;
- }
+ size_t MaxOffset9BackwardDistance() const { return KB / 4; }
+ size_t MaxOffset9ForwardDistance() const { return KB / 4 - 2; }
- size_t MaxOffset13ForwardDistance() {
- // TODO(riscv64): Support "C" Standard Extension, max forward distance 4KiB - 2.
- return 4 * KB - 4;
- }
+ size_t MaxOffset13BackwardDistance() const { return 4 * KB; }
+ size_t MaxOffset13ForwardDistance() const { return 4 * KB - 2; }
- size_t MaxOffset21BackwardDistance() {
- return 1 * MB;
- }
+ size_t MaxOffset13BackwardDistance_WithoutC() const { return 4 * KB; }
+ size_t MaxOffset13ForwardDistance_WithoutC() const { return 4 * KB - 4; }
- size_t MaxOffset21ForwardDistance() {
- // TODO(riscv64): Support "C" Standard Extension, max forward distance 1MiB - 2.
- return 1 * MB - 4;
- }
+ size_t MaxOffset21BackwardDistance() const { return 1 * MB; }
+ size_t MaxOffset21ForwardDistance() const { return 1 * MB - 2; }
+
+ size_t MaxOffset21BackwardDistance_WithoutC() { return 1 * MB; }
+ size_t MaxOffset21ForwardDistance_WithoutC() { return 1 * MB - 4; }
template <typename PrintBcond>
- void TestBeqA0A1Forward(const std::string& test_name,
- size_t nops_size,
- const std::string& target_label,
- PrintBcond&& print_bcond,
- bool is_bare = false) {
+ void TestBcondA0RegForward(const std::string& test_name,
+ void (Riscv64Assembler::*f)(XRegister, XRegister, Riscv64Label*, bool),
+ size_t nops_size,
+ const std::string& target_label,
+ PrintBcond&& print_bcond,
+ XRegister reg,
+ bool is_bare = false) {
std::string expected;
Riscv64Label label;
- __ Beq(A0, A1, &label, is_bare);
- expected += print_bcond("eq", "ne", " a0, a1", target_label + "f");
+ (GetAssembler()->*f)(A0, reg, &label, is_bare);
+ std::string args = " a0, " + GetRegisterName(reg);
+ expected += print_bcond("eq", "ne", args, target_label + "f");
expected += EmitNops(nops_size);
__ Bind(&label);
expected += target_label + ":\n";
@@ -614,18 +635,22 @@ class AssemblerRISCV64Test : public AssemblerTest<Riscv64Assembler,
}
template <typename PrintBcond>
- void TestBeqA0A1Backward(const std::string& test_name,
- size_t nops_size,
- const std::string& target_label,
- PrintBcond&& print_bcond,
- bool is_bare = false) {
+ void TestBcondA0RegBackward(
+ const std::string& test_name,
+ void (Riscv64Assembler::*f)(XRegister, XRegister, Riscv64Label*, bool),
+ size_t nops_size,
+ const std::string& target_label,
+ PrintBcond&& print_bcond,
+ XRegister reg,
+ bool is_bare = false) {
std::string expected;
Riscv64Label label;
__ Bind(&label);
expected += target_label + ":\n";
expected += EmitNops(nops_size);
- __ Beq(A0, A1, &label, is_bare);
- expected += print_bcond("eq", "ne", " a0, a1", target_label + "b");
+ (GetAssembler()->*f)(A0, reg, &label, is_bare);
+ std::string args = " a0, " + GetRegisterName(reg);
+ expected += print_bcond("eq", "ne", args, target_label + "b");
DriverStr(expected, test_name);
}
@@ -8399,11 +8424,16 @@ TEST_F(AssemblerRISCV64Test, Jalr0_WithoutC) {
}
TEST_F(AssemblerRISCV64Test, Ret) {
- ScopedCSuppression scs(this);
__ Ret();
DriverStr("ret\n", "Ret");
}
+TEST_F(AssemblerRISCV64Test, Ret_WithoutC) {
+ ScopedCSuppression scs(this);
+ __ Ret();
+ DriverStr("ret\n", "Ret_WithoutC");
+}
+
TEST_F(AssemblerRISCV64Test, RdCycle) {
DriverStr(RepeatR(&Riscv64Assembler::RdCycle, "rdcycle {reg}\n"), "RdCycle");
}
@@ -8523,100 +8553,263 @@ TEST_F(AssemblerRISCV64Test, BcondBackward2MiB) {
TestBcondBackward("BcondBackward2MiB", 2 * MB, "1", GetPrintBcondOppositeAndTail("2", "3"));
}
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset9Forward) {
+ TestBcondA0RegForward("BeqA0ZeroMaxOffset9Forward",
+ &Riscv64Assembler::Beq,
+ MaxOffset9ForwardDistance() - /*C.BEQZ*/ 2u,
+ "1",
+ GetPrintBcond(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset13Forward) {
+ TestBcondA0RegForward("BeqA0ZeroMaxOffset13Forward",
+ &Riscv64Assembler::Beq,
+ MaxOffset13ForwardDistance() - /*C.BEQZ*/ 2u,
+ "1",
+ GetPrintBcond(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BneA0ZeroMaxOffset9ForwardOpposite) {
+ TestBcondA0RegForward("BeqA0ZeroMaxOffset9ForwardOpposite",
+ &Riscv64Assembler::Bne,
+ MaxOffset9ForwardDistance() - /*C.BNEZ*/ 2u,
+ "1",
+ GetPrintBcondOpposite(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BneA0ZeroMaxOffset13Forward) {
+ TestBcondA0RegForward("BneA0ZeroMaxOffset13Forward",
+ &Riscv64Assembler::Bne,
+ MaxOffset13ForwardDistance() - /*C.BbeZ*/ 2u,
+ "1",
+ GetPrintBcondOpposite(),
+ Zero);
+}
+
TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset13Forward) {
ScopedCSuppression scs(this);
- TestBeqA0A1Forward("BeqA0A1MaxOffset13Forward",
- MaxOffset13ForwardDistance() - /*BEQ*/ 4u,
- "1",
- GetPrintBcond());
+ TestBcondA0RegForward("BeqA0A1MaxOffset13Forward",
+ &Riscv64Assembler::Beq,
+ MaxOffset13ForwardDistance_WithoutC() - /*BEQ*/ 4u,
+ "1",
+ GetPrintBcond(),
+ A1);
}
TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset13ForwardBare) {
ScopedCSuppression scs(this);
- TestBeqA0A1Forward("BeqA0A1MaxOffset13ForwardBare",
- MaxOffset13ForwardDistance() - /*BEQ*/ 4u,
- "1",
- GetPrintBcond(),
- /*is_bare=*/ true);
+ TestBcondA0RegForward("BeqA0A1MaxOffset13ForwardBare",
+ &Riscv64Assembler::Beq,
+ MaxOffset13ForwardDistance_WithoutC() - /*BEQ*/ 4u,
+ "1",
+ GetPrintBcond(),
+ A1,
+ /*is_bare=*/true);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset9Backward) {
+ TestBcondA0RegBackward("BeqA0ZeroMaxOffset9Backward",
+ &Riscv64Assembler::Beq,
+ MaxOffset9BackwardDistance() - /*C.BEQZ*/ 2u,
+ "1",
+ GetPrintBcond(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset13Backward) {
+ TestBcondA0RegBackward("BeqA0ZeroMaxOffset13Backward",
+ &Riscv64Assembler::Beq,
+ MaxOffset13ForwardDistance() - /*BEQ*/ 4u,
+ "1",
+ GetPrintBcond(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset9BackwardOpposite) {
+ TestBcondA0RegBackward("BeqA0ZeroMaxOffset9BackwardOpposite",
+ &Riscv64Assembler::Bne,
+ MaxOffset9BackwardDistance() - /*C.BNEZ*/ 2u,
+ "1",
+ GetPrintBcondOpposite(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset13BackwardOpposite) {
+ TestBcondA0RegBackward("BeqA0ZeroMaxOffset13BackwardOpposite",
+ &Riscv64Assembler::Bne,
+ MaxOffset13ForwardDistance() - /*BNE*/ 4u,
+ "1",
+ GetPrintBcondOpposite(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset13Backward_WithoutC) {
+ ScopedCSuppression scs(this);
+ TestBcondA0RegBackward("BeqA0A1MaxOffset13Forward_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset13BackwardDistance_WithoutC(),
+ "1",
+ GetPrintBcond(),
+ A1);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset13Backward) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset13BackwardBare_WithoutC) {
ScopedCSuppression scs(this);
- TestBeqA0A1Backward("BeqA0A1MaxOffset13Forward",
- MaxOffset13BackwardDistance(),
- "1",
- GetPrintBcond());
+ TestBcondA0RegBackward("BeqA0A1MaxOffset13ForwardBare_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset13BackwardDistance_WithoutC(),
+ "1",
+ GetPrintBcond(),
+ A1,
+ /*is_bare=*/true);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroOverMaxOffset9Forward) {
+ TestBcondA0RegForward("BeqA0ZeroOverMaxOffset9Forward",
+ &Riscv64Assembler::Beq,
+ MaxOffset9ForwardDistance() - /*C.BEQZ*/ 2u + /*Exceed max*/ 2u,
+ "1",
+ GetPrintBcond(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroOverMaxOffset9Backward) {
+ TestBcondA0RegBackward("BeqA0ZeroOverMaxOffset9Backward",
+ &Riscv64Assembler::Beq,
+ MaxOffset9BackwardDistance() - /*C.BEQZ*/ 2u + /*Exceed max*/ 2u,
+ "1",
+ GetPrintBcond(),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset13Forward_WithoutC) {
+ ScopedCSuppression scs(this);
+ TestBcondA0RegForward("BeqA0A1OverMaxOffset13Forward_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset13ForwardDistance_WithoutC() - /*BEQ*/ 4u + /*Exceed max*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndJ("2"),
+ A1);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset13BackwardBare) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset13Backward_WithoutC) {
ScopedCSuppression scs(this);
- TestBeqA0A1Backward("BeqA0A1MaxOffset13ForwardBare",
- MaxOffset13BackwardDistance(),
- "1",
- GetPrintBcond(),
- /*is_bare=*/ true);
+ TestBcondA0RegBackward("BeqA0A1OverMaxOffset13Forward_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset13BackwardDistance_WithoutC() + /*Exceed max*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndJ("2"),
+ A1);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset13Forward) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset21Forward_WithoutC) {
ScopedCSuppression scs(this);
- TestBeqA0A1Forward("BeqA0A1OverMaxOffset13Forward",
- MaxOffset13ForwardDistance() - /*BEQ*/ 4u + /*Exceed max*/ 4u,
- "1",
- GetPrintBcondOppositeAndJ("2"));
+ TestBcondA0RegForward("BeqA0A1MaxOffset21Forward_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset21ForwardDistance_WithoutC() - /*J*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndJ("2"),
+ A1);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset13Backward) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset21Backward_WithoutC) {
ScopedCSuppression scs(this);
- TestBeqA0A1Backward("BeqA0A1OverMaxOffset13Forward",
- MaxOffset13BackwardDistance() + /*Exceed max*/ 4u,
- "1",
- GetPrintBcondOppositeAndJ("2"));
+ TestBcondA0RegBackward("BeqA0A1MaxOffset21Backward_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset21BackwardDistance_WithoutC() - /*BNE*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndJ("2"),
+ A1);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset21Forward) {
- ScopedCSuppression scs(this);
- TestBeqA0A1Forward("BeqA0A1MaxOffset21Forward",
- MaxOffset21ForwardDistance() - /*J*/ 4u,
- "1",
- GetPrintBcondOppositeAndJ("2"));
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset21Backward) {
+ TestBcondA0RegBackward("BeqA0ZeroMaxOffset21Backward",
+ &Riscv64Assembler::Beq,
+ MaxOffset21BackwardDistance() - /*BNE*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndJ("2"),
+ Zero);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1MaxOffset21Backward) {
- ScopedCSuppression scs(this);
- TestBeqA0A1Backward("BeqA0A1MaxOffset21Backward",
- MaxOffset21BackwardDistance() - /*BNE*/ 4u,
- "1",
- GetPrintBcondOppositeAndJ("2"));
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset21BackwardNe) {
+ TestBcondA0RegBackward("BeqA0ZeroMaxOffset21BackwardNe",
+ &Riscv64Assembler::Bne,
+ MaxOffset21BackwardDistance() - /*BNE*/ 4u,
+ "1",
+ GetPrintBcondAndJ("2"),
+ Zero);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset21Forward) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset21Forward_WithoutC) {
ScopedCSuppression scs(this);
- TestBeqA0A1Forward("BeqA0A1OverMaxOffset21Forward",
- MaxOffset21ForwardDistance() - /*J*/ 4u + /*Exceed max*/ 4u,
- "1",
- GetPrintBcondOppositeAndTail("2", "3"));
+ TestBcondA0RegForward("BeqA0A1OverMaxOffset21Forward_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset21ForwardDistance_WithoutC() - /*J*/ 4u + /*Exceed max*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndTail("2", "3"),
+ A1);
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset21Backward) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1OverMaxOffset21Backward_WithoutC) {
ScopedCSuppression scs(this);
- TestBeqA0A1Backward("BeqA0A1OverMaxOffset21Backward",
- MaxOffset21BackwardDistance() - /*BNE*/ 4u + /*Exceed max*/ 4u,
- "1",
- GetPrintBcondOppositeAndTail("2", "3"));
+ TestBcondA0RegBackward("BeqA0A1OverMaxOffset21Backward_WithoutC",
+ &Riscv64Assembler::Beq,
+ MaxOffset21BackwardDistance_WithoutC() - /*BNE*/ 4u + /*Exceed max*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndTail("2", "3"),
+ A1);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset21Forward) {
+ TestBcondA0RegForward("BeqA0ZeroMaxOffset21Forward",
+ &Riscv64Assembler::Beq,
+ MaxOffset21ForwardDistance() - /*J*/ 4u,
+ "1",
+ GetPrintBcondOppositeAndJ("2"),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset21ForwardNe) {
+ TestBcondA0RegForward("BeqA0ZeroMaxOffset21ForwardNe",
+ &Riscv64Assembler::Bne,
+ MaxOffset21ForwardDistance() - /*J*/ 4u,
+ "1",
+ GetPrintBcondAndJ("2"),
+ Zero);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0ZeroMaxOffset21ForwardNeNotCompressable) {
+ TestBcondA0RegForward("BeqA0ZeroMaxOffset21ForwardNeNotCompressable",
+ &Riscv64Assembler::Bne,
+ MaxOffset21ForwardDistance() - /*BNE*/ 50u,
+ "1",
+ GetPrintBcondAndJ("2"),
+ A2);
+}
+
+TEST_F(AssemblerRISCV64Test, BeqA0A1AlmostCascade_WithoutC) {
+ ScopedCSuppression scs(this);
+ TestBeqA0A1MaybeCascade("BeqA0A1AlmostCascade_WithoutC", /*cascade=*/false, GetPrintBcond());
}
TEST_F(AssemblerRISCV64Test, BeqA0A1AlmostCascade) {
- ScopedCSuppression scs(this);
- TestBeqA0A1MaybeCascade("BeqA0A1AlmostCascade", /*cascade=*/ false, GetPrintBcond());
+ TestBeqA0A1MaybeCascade("BeqA0A1AlmostCascade", /*cascade=*/false, GetPrintBcond());
}
-TEST_F(AssemblerRISCV64Test, BeqA0A1Cascade) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1Cascade_WithoutC) {
ScopedCSuppression scs(this);
TestBeqA0A1MaybeCascade(
- "BeqA0A1AlmostCascade", /*cascade=*/ true, GetPrintBcondOppositeAndJ("1"));
+ "BeqA0A1AlmostCascade_WithoutC", /*cascade=*/true, GetPrintBcondOppositeAndJ("1"));
}
-TEST_F(AssemblerRISCV64Test, BcondElimination) {
+TEST_F(AssemblerRISCV64Test, BeqA0A1Cascade) {
+ TestBeqA0A1MaybeCascade("BeqA0A1AlmostCascade", /*cascade=*/true, GetPrintBcondOppositeAndJ("1"));
+}
+
+TEST_F(AssemblerRISCV64Test, BcondElimination_WithoutC) {
ScopedCSuppression scs(this);
Riscv64Label label;
__ Bind(&label);
@@ -8628,10 +8821,10 @@ TEST_F(AssemblerRISCV64Test, BcondElimination) {
__ Bltu(reg, reg, &label);
__ Bgtu(reg, reg, &label);
}
- DriverStr("nop\n", "BcondElimination");
+ DriverStr("nop\n", "BcondElimination_WithoutC");
}
-TEST_F(AssemblerRISCV64Test, BcondUnconditional) {
+TEST_F(AssemblerRISCV64Test, BcondUnconditional_WithoutC) {
ScopedCSuppression scs(this);
Riscv64Label label;
__ Bind(&label);
@@ -8647,174 +8840,199 @@ TEST_F(AssemblerRISCV64Test, BcondUnconditional) {
"1:\n"
"nop\n" +
RepeatInsn(5u * GetRegisters().size(), "j 1b\n", []() {});
- DriverStr(expected, "BcondUnconditional");
+ DriverStr(expected, "BcondUnconditional_WithoutC");
}
-TEST_F(AssemblerRISCV64Test, JalRdForward3KiB) {
+TEST_F(AssemblerRISCV64Test, JalRdForward3KiB_WithoutC) {
ScopedCSuppression scs(this);
- TestJalRdForward("JalRdForward3KiB", 3 * KB, "1", GetPrintJalRd());
+ TestJalRdForward("JalRdForward3KiB_WithoutC", 3 * KB, "1", GetPrintJalRd());
}
-TEST_F(AssemblerRISCV64Test, JalRdForward3KiBBare) {
+TEST_F(AssemblerRISCV64Test, JalRdForward3KiBBare_WithoutC) {
ScopedCSuppression scs(this);
- TestJalRdForward("JalRdForward3KiB", 3 * KB, "1", GetPrintJalRd(), /*is_bare=*/ true);
+ TestJalRdForward("JalRdForward3KiB_WithoutC", 3 * KB, "1", GetPrintJalRd(), /*is_bare=*/true);
}
-TEST_F(AssemblerRISCV64Test, JalRdBackward3KiB) {
+TEST_F(AssemblerRISCV64Test, JalRdBackward3KiB_WithoutC) {
ScopedCSuppression scs(this);
- TestJalRdBackward("JalRdBackward3KiB", 3 * KB, "1", GetPrintJalRd());
+ TestJalRdBackward("JalRdBackward3KiB_WithoutC", 3 * KB, "1", GetPrintJalRd());
}
-TEST_F(AssemblerRISCV64Test, JalRdBackward3KiBBare) {
+TEST_F(AssemblerRISCV64Test, JalRdBackward3KiBBare_WithoutC) {
ScopedCSuppression scs(this);
- TestJalRdBackward("JalRdBackward3KiB", 3 * KB, "1", GetPrintJalRd(), /*is_bare=*/ true);
+ TestJalRdBackward("JalRdBackward3KiB_WithoutC", 3 * KB, "1", GetPrintJalRd(), /*is_bare=*/true);
}
-TEST_F(AssemblerRISCV64Test, JalRdForward2MiB) {
+TEST_F(AssemblerRISCV64Test, JalRdForward2MiB_WithoutC) {
ScopedCSuppression scs(this);
- TestJalRdForward("JalRdForward2MiB", 2 * MB, "1", GetPrintCallRd("2"));
+ TestJalRdForward("JalRdForward2MiB_WithoutC", 2 * MB, "1", GetPrintCallRd("2"));
}
-TEST_F(AssemblerRISCV64Test, JalRdBackward2MiB) {
+TEST_F(AssemblerRISCV64Test, JalRdBackward2MiB_WithoutC) {
ScopedCSuppression scs(this);
- TestJalRdBackward("JalRdBackward2MiB", 2 * MB, "1", GetPrintCallRd("2"));
+ TestJalRdBackward("JalRdBackward2MiB_WithoutC", 2 * MB, "1", GetPrintCallRd("2"));
}
-TEST_F(AssemblerRISCV64Test, JForward3KiB) {
+TEST_F(AssemblerRISCV64Test, JForward3KiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("JForward3KiB", 3 * KB, "1", GetEmitJ(), GetPrintJ());
+ TestBuncondForward("JForward3KiB_WithoutC", 3 * KB, "1", GetEmitJ(), GetPrintJ());
+}
+
+TEST_F(AssemblerRISCV64Test, JForward3KiBWithCompression) {
+ TestBuncondForward("JForward3KiBWithCompression", 3 * KB, "1", GetEmitJ(), GetPrintJ());
+}
+
+TEST_F(AssemblerRISCV64Test, JForward2KiB) {
+ TestBuncondForward("JForward2KiB", 2 * KB - 4, "1", GetEmitJ(), GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JForward3KiBBare) {
+TEST_F(AssemblerRISCV64Test, JForward3KiBBare_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("JForward3KiB", 3 * KB, "1", GetEmitJ(/*is_bare=*/ true), GetPrintJ());
+ TestBuncondForward("JForward3KiB_WithoutC", 3 * KB, "1", GetEmitJ(/*is_bare=*/true), GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JBackward3KiB) {
+TEST_F(AssemblerRISCV64Test, JForward2KiBBare) {
+ TestBuncondForward("JForward2KiBBare", 2 * KB - 4, "1", GetEmitJ(/*is_bare=*/true), GetPrintJ());
+}
+
+TEST_F(AssemblerRISCV64Test, JBackward3KiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("JBackward3KiB", 3 * KB, "1", GetEmitJ(), GetPrintJ());
+ TestBuncondBackward("JBackward3KiB_WithoutC", 3 * KB, "1", GetEmitJ(), GetPrintJ());
+}
+
+TEST_F(AssemblerRISCV64Test, JBackward3KiBWithCompression) {
+ TestBuncondBackward("JBackward3KiBWithCompression", 3 * KB, "1", GetEmitJ(), GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JBackward3KiBBare) {
+TEST_F(AssemblerRISCV64Test, JBackward2KiB) {
+ TestBuncondBackward("JBackward2KiB", 2 * KB, "1", GetEmitJ(), GetPrintJ());
+}
+
+TEST_F(AssemblerRISCV64Test, JBackward3KiBBare_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("JBackward3KiB", 3 * KB, "1", GetEmitJ(/*is_bare=*/ true), GetPrintJ());
+ TestBuncondBackward(
+ "JBackward3KiB_WithoutC", 3 * KB, "1", GetEmitJ(/*is_bare=*/true), GetPrintJ());
+}
+
+TEST_F(AssemblerRISCV64Test, JBackward2KiBBare) {
+ TestBuncondBackward("JBackward2KiBBare", 2 * KB, "1", GetEmitJ(/*is_bare=*/true), GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JForward2MiB) {
+TEST_F(AssemblerRISCV64Test, JForward2MiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("JForward2MiB", 2 * MB, "1", GetEmitJ(), GetPrintTail("2"));
+ TestBuncondForward("JForward2MiB_WithoutC", 2 * MB, "1", GetEmitJ(), GetPrintTail("2"));
}
-TEST_F(AssemblerRISCV64Test, JBackward2MiB) {
+TEST_F(AssemblerRISCV64Test, JBackward2MiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("JBackward2MiB", 2 * MB, "1", GetEmitJ(), GetPrintTail("2"));
+ TestBuncondBackward("JBackward2MiB_WithoutC", 2 * MB, "1", GetEmitJ(), GetPrintTail("2"));
}
-TEST_F(AssemblerRISCV64Test, JMaxOffset21Forward) {
+TEST_F(AssemblerRISCV64Test, JMaxOffset21Forward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("JMaxOffset21Forward",
- MaxOffset21ForwardDistance() - /*J*/ 4u,
+ TestBuncondForward("JMaxOffset21Forward_WithoutC",
+ MaxOffset21ForwardDistance_WithoutC() - /*J*/ 4u,
"1",
GetEmitJ(),
GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JMaxOffset21ForwardBare) {
+TEST_F(AssemblerRISCV64Test, JMaxOffset21ForwardBare_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("JMaxOffset21Forward",
- MaxOffset21ForwardDistance() - /*J*/ 4u,
+ TestBuncondForward("JMaxOffset21Forward_WithoutC",
+ MaxOffset21ForwardDistance_WithoutC() - /*J*/ 4u,
"1",
- GetEmitJ(/*is_bare=*/ true),
+ GetEmitJ(/*is_bare=*/true),
GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JMaxOffset21Backward) {
+TEST_F(AssemblerRISCV64Test, JMaxOffset21Backward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("JMaxOffset21Backward",
- MaxOffset21BackwardDistance(),
+ TestBuncondBackward("JMaxOffset21Backward_WithoutC",
+ MaxOffset21BackwardDistance_WithoutC(),
"1",
GetEmitJ(),
GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JMaxOffset21BackwardBare) {
+TEST_F(AssemblerRISCV64Test, JMaxOffset21BackwardBare_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("JMaxOffset21Backward",
- MaxOffset21BackwardDistance(),
+ TestBuncondBackward("JMaxOffset21Backward_WithoutC",
+ MaxOffset21BackwardDistance_WithoutC(),
"1",
- GetEmitJ(/*is_bare=*/ true),
+ GetEmitJ(/*is_bare=*/true),
GetPrintJ());
}
-TEST_F(AssemblerRISCV64Test, JOverMaxOffset21Forward) {
+TEST_F(AssemblerRISCV64Test, JOverMaxOffset21Forward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("JOverMaxOffset21Forward",
- MaxOffset21ForwardDistance() - /*J*/ 4u + /*Exceed max*/ 4u,
+ TestBuncondForward("JOverMaxOffset21Forward_WithoutC",
+ MaxOffset21ForwardDistance_WithoutC() - /*J*/ 4u + /*Exceed max*/ 4u,
"1",
GetEmitJ(),
GetPrintTail("2"));
}
-TEST_F(AssemblerRISCV64Test, JOverMaxOffset21Backward) {
+TEST_F(AssemblerRISCV64Test, JOverMaxOffset21Backward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("JMaxOffset21Backward",
- MaxOffset21BackwardDistance() + /*Exceed max*/ 4u,
+ TestBuncondBackward("JMaxOffset21Backward_WithoutC",
+ MaxOffset21BackwardDistance_WithoutC() + /*Exceed max*/ 4u,
"1",
GetEmitJ(),
GetPrintTail("2"));
}
-TEST_F(AssemblerRISCV64Test, CallForward3KiB) {
+TEST_F(AssemblerRISCV64Test, CallForward3KiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("CallForward3KiB", 3 * KB, "1", GetEmitJal(), GetPrintJal());
+ TestBuncondForward("CallForward3KiB_WithoutC", 3 * KB, "1", GetEmitJal(), GetPrintJal());
}
-TEST_F(AssemblerRISCV64Test, CallBackward3KiB) {
+TEST_F(AssemblerRISCV64Test, CallBackward3KiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("CallBackward3KiB", 3 * KB, "1", GetEmitJal(), GetPrintJal());
+ TestBuncondBackward("CallBackward3KiB_WithoutC", 3 * KB, "1", GetEmitJal(), GetPrintJal());
}
-TEST_F(AssemblerRISCV64Test, CallForward2MiB) {
+TEST_F(AssemblerRISCV64Test, CallForward2MiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("CallForward2MiB", 2 * MB, "1", GetEmitJal(), GetPrintCall("2"));
+ TestBuncondForward("CallForward2MiB_WithoutC", 2 * MB, "1", GetEmitJal(), GetPrintCall("2"));
}
-TEST_F(AssemblerRISCV64Test, CallBackward2MiB) {
+TEST_F(AssemblerRISCV64Test, CallBackward2MiB_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("CallBackward2MiB", 2 * MB, "1", GetEmitJal(), GetPrintCall("2"));
+ TestBuncondBackward("CallBackward2MiB_WithoutC", 2 * MB, "1", GetEmitJal(), GetPrintCall("2"));
}
-TEST_F(AssemblerRISCV64Test, CallMaxOffset21Forward) {
+TEST_F(AssemblerRISCV64Test, CallMaxOffset21Forward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("CallMaxOffset21Forward",
- MaxOffset21ForwardDistance() - /*J*/ 4u,
+ TestBuncondForward("CallMaxOffset21Forward_WithoutC",
+ MaxOffset21ForwardDistance_WithoutC() - /*J*/ 4u,
"1",
GetEmitJal(),
GetPrintJal());
}
-TEST_F(AssemblerRISCV64Test, CallMaxOffset21Backward) {
+TEST_F(AssemblerRISCV64Test, CallMaxOffset21Backward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("CallMaxOffset21Backward",
- MaxOffset21BackwardDistance(),
+ TestBuncondBackward("CallMaxOffset21Backward_WithoutC",
+ MaxOffset21BackwardDistance_WithoutC(),
"1",
GetEmitJal(),
GetPrintJal());
}
-TEST_F(AssemblerRISCV64Test, CallOverMaxOffset21Forward) {
+TEST_F(AssemblerRISCV64Test, CallOverMaxOffset21Forward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondForward("CallOverMaxOffset21Forward",
- MaxOffset21ForwardDistance() - /*J*/ 4u + /*Exceed max*/ 4u,
+ TestBuncondForward("CallOverMaxOffset21Forward_WithoutC",
+ MaxOffset21ForwardDistance_WithoutC() - /*J*/ 4u + /*Exceed max*/ 4u,
"1",
GetEmitJal(),
GetPrintCall("2"));
}
-TEST_F(AssemblerRISCV64Test, CallOverMaxOffset21Backward) {
+TEST_F(AssemblerRISCV64Test, CallOverMaxOffset21Backward_WithoutC) {
ScopedCSuppression scs(this);
- TestBuncondBackward("CallMaxOffset21Backward",
- MaxOffset21BackwardDistance() + /*Exceed max*/ 4u,
+ TestBuncondBackward("CallMaxOffset21Backward_WithoutC",
+ MaxOffset21BackwardDistance_WithoutC() + /*Exceed max*/ 4u,
"1",
GetEmitJal(),
GetPrintCall("2"));
diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp
index 4002cb55ee..1bec1cf889 100644
--- a/dex2oat/Android.bp
+++ b/dex2oat/Android.bp
@@ -77,11 +77,13 @@ art_cc_defaults {
generated_sources: ["art_dex2oat_operator_srcs"],
shared_libs: [
"libbase",
- "libcrypto", // For SHA-1 checksumming of build ID
"liblog",
"liblz4",
"libz",
],
+ static_libs: [
+ "libcrypto_for_art", // For SHA-1 checksumming of build ID
+ ],
export_include_dirs: ["."],
}
@@ -89,7 +91,7 @@ cc_defaults {
name: "libart-dex2oat_static_base_defaults",
whole_static_libs: [
"libbase",
- "libcrypto",
+ "libcrypto_for_art",
"liblog",
"liblz4",
"libz",
@@ -221,12 +223,14 @@ cc_defaults {
shared_libs: [
"libartpalette",
"libbase",
- "libcrypto",
"liblz4", // libart(d)-dex2oat dependency; must be repeated here since it's a static lib.
"liblog",
"libsigchain",
"libz",
],
+ static_libs: [
+ "libcrypto_for_art",
+ ],
},
},
}
@@ -500,7 +504,7 @@ art_cc_defaults {
"liblog",
],
static_libs: [
- "libcrypto_static", // Must be statically linked in standalone tests
+ "libcrypto_for_art",
"liblz4", // libart(d)-dex2oat dependency; must be repeated here since it's a static lib.
],
}
diff --git a/dex2oat/driver/compiler_driver.cc b/dex2oat/driver/compiler_driver.cc
index 6423cc3ead..c696191491 100644
--- a/dex2oat/driver/compiler_driver.cc
+++ b/dex2oat/driver/compiler_driver.cc
@@ -2229,7 +2229,7 @@ class InitializeClassVisitor : public CompilationVisitor {
const dex::TypeId& class_type_id = dex_file.GetTypeId(class_def->class_idx_);
const char* descriptor = dex_file.GetStringData(class_type_id.descriptor_idx_);
StackHandleScope<3> hs(self);
- ClassLinker* const class_linker = manager_->GetClassLinker();
+ AotClassLinker* const class_linker = down_cast<AotClassLinker*>(manager_->GetClassLinker());
Runtime* const runtime = Runtime::Current();
const CompilerOptions& compiler_options = manager_->GetCompiler()->GetCompilerOptions();
const bool is_boot_image = compiler_options.IsBootImage();
@@ -2338,7 +2338,7 @@ class InitializeClassVisitor : public CompilationVisitor {
DCHECK(exception_initialized);
// Run the class initializer in transaction mode.
- runtime->EnterTransactionMode(is_app_image, klass.Get());
+ class_linker->EnterTransactionMode(is_app_image, klass.Get());
bool success = class_linker->EnsureInitialized(self, klass, true, true);
// TODO we detach transaction from runtime to indicate we quit the transactional
@@ -2349,7 +2349,7 @@ class InitializeClassVisitor : public CompilationVisitor {
ScopedAssertNoThreadSuspension ants("Transaction end");
if (success) {
- runtime->ExitTransactionMode();
+ class_linker->ExitTransactionMode();
DCHECK(!runtime->IsActiveTransaction());
if (is_boot_image || is_boot_image_extension) {
@@ -2370,7 +2370,7 @@ class InitializeClassVisitor : public CompilationVisitor {
*file_log << exception->Dump() << "\n";
}
self->ClearException();
- runtime->RollbackAllTransactions();
+ class_linker->RollbackAllTransactions();
CHECK_EQ(old_status, klass->GetStatus()) << "Previous class status not restored";
}
}
@@ -2419,9 +2419,9 @@ class InitializeClassVisitor : public CompilationVisitor {
self->GetJniEnv()->AssertLocalsEmpty();
}
- if (!klass->IsVisiblyInitialized() &&
+ if (!klass->IsInitialized() &&
(is_boot_image || is_boot_image_extension) &&
- !compiler_options.IsPreloadedClass(PrettyDescriptor(descriptor).c_str())) {
+ !compiler_options.IsPreloadedClass(PrettyDescriptor(descriptor))) {
klass->SetInBootImageAndNotInPreloadedClasses();
}
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index abc3354421..9c83da5491 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -452,8 +452,6 @@ static void ClearDexFileCookies() REQUIRES_SHARED(Locks::mutator_lock_) {
}
bool ImageWriter::PrepareImageAddressSpace(TimingLogger* timings) {
- target_ptr_size_ = InstructionSetPointerSize(compiler_options_.GetInstructionSet());
-
Thread* const self = Thread::Current();
gc::Heap* const heap = Runtime::Current()->GetHeap();
@@ -3506,35 +3504,52 @@ const uint8_t* ImageWriter::GetQuickCode(ArtMethod* method, const ImageInfo& ima
return quick_code;
}
+static inline uint32_t ResetNterpFastPathFlags(
+ uint32_t access_flags, ArtMethod* orig, InstructionSet isa)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(orig != nullptr);
+ DCHECK(!orig->IsProxyMethod()); // `UnstartedRuntime` does not support creating proxy classes.
+ DCHECK(!orig->IsRuntimeMethod());
+
+ // Clear old nterp fast path flags.
+ access_flags = ArtMethod::ClearNterpFastPathFlags(access_flags);
+
+ // Check if nterp fast paths are available on the target ISA.
+ std::string_view shorty = orig->GetShortyView(); // Use orig, copy's class not yet ready.
+ uint32_t new_nterp_flags = GetNterpFastPathFlags(shorty, access_flags, isa);
+
+ // Add the new nterp fast path flags, if any.
+ return access_flags | new_nterp_flags;
+}
+
void ImageWriter::CopyAndFixupMethod(ArtMethod* orig,
ArtMethod* copy,
size_t oat_index) {
- if (orig->IsAbstract()) {
- // Ignore the single-implementation info for abstract method.
- // Do this on orig instead of copy, otherwise there is a crash due to methods
- // are copied before classes.
- // TODO: handle fixup of single-implementation method for abstract method.
- orig->SetHasSingleImplementation(false);
- orig->SetSingleImplementation(
- nullptr, Runtime::Current()->GetClassLinker()->GetImagePointerSize());
- }
-
- if (!orig->IsRuntimeMethod()) {
- // If we're compiling a boot image and we have a profile, set methods as
- // being shared memory (to avoid dirtying them with hotness counter). We
- // expect important methods to be AOT, and non-important methods to be run
- // in the interpreter.
- if (CompilerFilter::DependsOnProfile(compiler_options_.GetCompilerFilter()) &&
- (compiler_options_.IsBootImage() || compiler_options_.IsBootImageExtension())) {
- orig->SetMemorySharedMethod();
- }
- }
-
memcpy(copy, orig, ArtMethod::Size(target_ptr_size_));
CopyAndFixupReference(copy->GetDeclaringClassAddressWithoutBarrier(),
orig->GetDeclaringClassUnchecked<kWithoutReadBarrier>());
- ResetNterpFastPathFlags(copy, orig);
+
+ if (!orig->IsRuntimeMethod()) {
+ uint32_t access_flags = orig->GetAccessFlags();
+ if (ArtMethod::IsAbstract(access_flags)) {
+ // Ignore the single-implementation info for abstract method.
+ // TODO: handle fixup of single-implementation method for abstract method.
+ access_flags = ArtMethod::SetHasSingleImplementation(access_flags, /*single_impl=*/ false);
+ copy->SetSingleImplementation(nullptr, target_ptr_size_);
+ } else if (mark_memory_shared_methods_ && LIKELY(!ArtMethod::IsIntrinsic(access_flags))) {
+ access_flags = ArtMethod::SetMemorySharedMethod(access_flags);
+ copy->SetHotCounter();
+ }
+
+ InstructionSet isa = compiler_options_.GetInstructionSet();
+ if (isa != kRuntimeISA) {
+ access_flags = ResetNterpFastPathFlags(access_flags, orig, isa);
+ } else {
+ DCHECK_EQ(access_flags, ResetNterpFastPathFlags(access_flags, orig, isa));
+ }
+ copy->SetAccessFlags(access_flags);
+ }
// OatWriter replaces the code_ with an offset value. Here we re-adjust to a pointer relative to
// oat_begin_
@@ -3754,11 +3769,17 @@ ImageWriter::ImageWriter(const CompilerOptions& compiler_options,
jobject class_loader,
const std::vector<std::string>* dirty_image_objects)
: compiler_options_(compiler_options),
+ target_ptr_size_(InstructionSetPointerSize(compiler_options.GetInstructionSet())),
+ // If we're compiling a boot image and we have a profile, set methods as being shared
+ // memory (to avoid dirtying them with hotness counter). We expect important methods
+ // to be AOT, and non-important methods to be run in the interpreter.
+ mark_memory_shared_methods_(
+ CompilerFilter::DependsOnProfile(compiler_options_.GetCompilerFilter()) &&
+ (compiler_options_.IsBootImage() || compiler_options_.IsBootImageExtension())),
boot_image_begin_(Runtime::Current()->GetHeap()->GetBootImagesStartAddress()),
boot_image_size_(Runtime::Current()->GetHeap()->GetBootImagesSize()),
global_image_begin_(reinterpret_cast<uint8_t*>(image_begin)),
image_objects_offset_begin_(0),
- target_ptr_size_(InstructionSetPointerSize(compiler_options.GetInstructionSet())),
image_infos_(oat_filenames.size()),
jni_stub_map_(JniStubKeyHash(compiler_options.GetInstructionSet()),
JniStubKeyEquals(compiler_options.GetInstructionSet())),
@@ -3841,32 +3862,5 @@ void ImageWriter::CopyAndFixupPointer(void* object, MemberOffset offset, ValueTy
return CopyAndFixupPointer(object, offset, src_value, target_ptr_size_);
}
-void ImageWriter::ResetNterpFastPathFlags(ArtMethod* copy, ArtMethod* orig) {
- DCHECK(copy != nullptr);
- DCHECK(orig != nullptr);
- if (orig->IsRuntimeMethod() || orig->IsProxyMethod()) {
- return; // !IsRuntimeMethod() and !IsProxyMethod() for GetShortyView()
- }
-
- // Clear old nterp fast path flags.
- if (copy->HasNterpEntryPointFastPathFlag()) {
- copy->ClearNterpEntryPointFastPathFlag(); // Flag has other uses, clear it conditionally.
- }
- copy->ClearNterpInvokeFastPathFlag();
-
- // Check if nterp fast paths available on target ISA.
- std::string_view shorty = orig->GetShortyView(); // Use orig, copy's class not yet ready.
- uint32_t new_nterp_flags =
- GetNterpFastPathFlags(shorty, copy->GetAccessFlags(), compiler_options_.GetInstructionSet());
-
- // Set new nterp fast path flags, if approporiate.
- if ((new_nterp_flags & kAccNterpEntryPointFastPathFlag) != 0) {
- copy->SetNterpEntryPointFastPathFlag();
- }
- if ((new_nterp_flags & kAccNterpInvokeFastPathFlag) != 0) {
- copy->SetNterpInvokeFastPathFlag();
- }
-}
-
} // namespace linker
} // namespace art
diff --git a/dex2oat/linker/image_writer.h b/dex2oat/linker/image_writer.h
index 8ffe226bec..efe8fa2304 100644
--- a/dex2oat/linker/image_writer.h
+++ b/dex2oat/linker/image_writer.h
@@ -618,9 +618,6 @@ class ImageWriter final {
void CopyAndFixupPointer(void* object, MemberOffset offset, ValueType src_value)
REQUIRES_SHARED(Locks::mutator_lock_);
- void ResetNterpFastPathFlags(ArtMethod* copy, ArtMethod* orig)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
ALWAYS_INLINE
static bool IsStronglyInternedString(ObjPtr<mirror::String> str)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -639,6 +636,12 @@ class ImageWriter final {
const CompilerOptions& compiler_options_;
+ // Size of pointers on the target architecture.
+ PointerSize target_ptr_size_;
+
+ // Whether to mark non-abstract, non-intrinsic methods as "memory shared methods".
+ bool mark_memory_shared_methods_;
+
// Cached boot image begin and size. This includes heap, native objects and oat files.
const uint32_t boot_image_begin_;
const uint32_t boot_image_size_;
@@ -656,9 +659,6 @@ class ImageWriter final {
// Oat index map for objects.
HashMap<mirror::Object*, uint32_t> oat_index_map_;
- // Size of pointers on the target architecture.
- PointerSize target_ptr_size_;
-
// Image data indexed by the oat file index.
dchecked_vector<ImageInfo> image_infos_;
diff --git a/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl b/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl
index 0b50d22778..f8770147fc 100644
--- a/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl
+++ b/dexopt_chroot_setup/binder/com/android/server/art/IDexoptChrootSetup.aidl
@@ -22,12 +22,21 @@ interface IDexoptChrootSetup {
const @utf8InCpp String CHROOT_DIR = PRE_REBOOT_DEXOPT_DIR + "/chroot";
/**
- * Sets up the chroot environment.
+ * Sets up the chroot environment. All files in chroot, except apexes and the linker config, are
+ * accessible after this method is called.
*
* @param otaSlot The slot that contains the OTA update, "_a" or "_b", or null for a Mainline
* update.
+ * @param mapSnapshotsForOta Whether to map/unmap snapshots. Only applicable to an OTA update.
*/
- void setUp(@nullable @utf8InCpp String otaSlot);
+ void setUp(@nullable @utf8InCpp String otaSlot, boolean mapSnapshotsForOta);
+
+ /**
+ * Initializes the chroot environment. Can only be called after {@link #setUp}. Apexes and
+ * the linker config in chroot are accessible, and binaries are executable in chroot, after
+ * this method is called.
+ */
+ void init();
/** Tears down the chroot environment. */
void tearDown();
diff --git a/dexopt_chroot_setup/dexopt_chroot_setup.cc b/dexopt_chroot_setup/dexopt_chroot_setup.cc
index 35343c5b3c..d98f883df9 100644
--- a/dexopt_chroot_setup/dexopt_chroot_setup.cc
+++ b/dexopt_chroot_setup/dexopt_chroot_setup.cc
@@ -68,6 +68,7 @@ using ::android::base::SetProperty;
using ::android::base::Split;
using ::android::base::Tokenize;
using ::android::base::WaitForProperty;
+using ::android::base::WriteStringToFile;
using ::android::fs_mgr::FstabEntry;
using ::art::tools::CmdlineBuilder;
using ::art::tools::Fatal;
@@ -79,6 +80,10 @@ using ::ndk::ScopedAStatus;
constexpr const char* kServiceName = "dexopt_chroot_setup";
const NoDestructor<std::string> kBindMountTmpDir(
std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + "/mount_tmp");
+const NoDestructor<std::string> kOtaSlotFile(std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) +
+ "/ota_slot");
+const NoDestructor<std::string> kSnapshotMappedFile(
+ std::string(DexoptChrootSetup::PRE_REBOOT_DEXOPT_DIR) + "/snapshot_mapped");
constexpr mode_t kChrootDefaultMode = 0755;
constexpr std::chrono::milliseconds kSnapshotCtlTimeout = std::chrono::seconds(60);
@@ -261,7 +266,7 @@ Result<std::vector<std::string>> GetSupportedFilesystems() {
return filesystems;
}
-Result<void> Mount(const std::string& block_device, const std::string& target) {
+Result<void> Mount(const std::string& block_device, const std::string& target, bool is_optional) {
static const NoDestructor<Result<std::vector<std::string>>> supported_filesystems(
GetSupportedFilesystems());
if (!supported_filesystems->ok()) {
@@ -279,6 +284,10 @@ Result<void> Mount(const std::string& block_device, const std::string& target) {
"Mounted '{}' at '{}' with type '{}'", block_device, target, filesystem);
return {};
} else {
+ if (errno == ENOENT && is_optional) {
+ LOG(INFO) << ART_FORMAT("Skipped non-existing block device '{}'", block_device);
+ return {};
+ }
error_msgs.push_back(ART_FORMAT("Tried '{}': {}", filesystem, strerror(errno)));
if (errno != EINVAL && errno != EBUSY) {
// If the filesystem type is wrong, `errno` must be either `EINVAL` or `EBUSY`. For example,
@@ -303,9 +312,24 @@ Result<void> MountTmpfs(const std::string& target, std::string_view se_context)
return {};
}
+Result<std::optional<std::string>> LoadOtaSlotFile() {
+ std::string content;
+ if (!ReadFileToString(*kOtaSlotFile, &content)) {
+ return ErrnoErrorf("Failed to read '{}'", *kOtaSlotFile);
+ }
+ if (content == "_a" || content == "_b") {
+ return content;
+ }
+ if (content.empty()) {
+ return std::nullopt;
+ }
+ return Errorf("Invalid content of '{}': '{}'", *kOtaSlotFile, content);
+}
+
} // namespace
-ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaSlot) {
+ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaSlot,
+ bool in_mapSnapshotsForOta) {
if (!mu_.try_lock()) {
return Fatal("Unexpected concurrent calls");
}
@@ -314,7 +338,17 @@ ScopedAStatus DexoptChrootSetup::setUp(const std::optional<std::string>& in_otaS
if (in_otaSlot.has_value() && (in_otaSlot.value() != "_a" && in_otaSlot.value() != "_b")) {
return Fatal(ART_FORMAT("Invalid OTA slot '{}'", in_otaSlot.value()));
}
- OR_RETURN_NON_FATAL(SetUpChroot(in_otaSlot));
+ OR_RETURN_NON_FATAL(SetUpChroot(in_otaSlot, in_mapSnapshotsForOta));
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus DexoptChrootSetup::init() {
+ if (!mu_.try_lock()) {
+ return Fatal("Unexpected concurrent calls");
+ }
+ std::lock_guard<std::mutex> lock(mu_, std::adopt_lock);
+
+ OR_RETURN_NON_FATAL(InitChroot());
return ScopedAStatus::ok();
}
@@ -340,7 +374,8 @@ Result<void> DexoptChrootSetup::Start() {
return {};
}
-Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ota_slot) const {
+Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ota_slot,
+ bool map_snapshots_for_ota) const {
// Set the default permission mode for new files and dirs to be `kChrootDefaultMode`.
umask(~kChrootDefaultMode & 0777);
@@ -360,26 +395,43 @@ Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ot
if (!IsOtaUpdate(ota_slot)) { // Mainline update
OR_RETURN(BindMount("/", CHROOT_DIR));
for (const std::string& partition : additional_system_partitions) {
+ // Some additional partitions are optional, but that's okay. The root filesystem (mounted at
+ // `/`) has empty directories for additional partitions. If additional partitions don't exist,
+ // we'll just be bind-mounting empty directories.
OR_RETURN(BindMount("/" + partition, PathInChroot("/" + partition)));
}
} else {
CHECK(ota_slot.value() == "_a" || ota_slot.value() == "_b");
- // Run `snapshotctl map` through init to map block devices. We can't run it ourselves because it
- // requires the UID to be 0. See `sys.snapshotctl.map` in `init.rc`.
- if (!SetProperty("sys.snapshotctl.map", "requested")) {
- return Errorf("Failed to request snapshotctl map");
- }
- if (!WaitForProperty("sys.snapshotctl.map", "finished", kSnapshotCtlTimeout)) {
- return Errorf("snapshotctl timed out");
+ if (map_snapshots_for_ota) {
+ // Write the file early in case `snapshotctl map` fails in the middle, leaving some devices
+ // mapped. We don't assume that `snapshotctl map` is transactional.
+ if (!WriteStringToFile("", *kSnapshotMappedFile)) {
+ return ErrnoErrorf("Failed to write '{}'", *kSnapshotMappedFile);
+ }
+
+ // Run `snapshotctl map` through init to map block devices. We can't run it ourselves because
+ // it requires the UID to be 0. See `sys.snapshotctl.map` in `init.rc`.
+ if (!SetProperty("sys.snapshotctl.map", "requested")) {
+ return Errorf("Failed to request snapshotctl map");
+ }
+ if (!WaitForProperty("sys.snapshotctl.map", "finished", kSnapshotCtlTimeout)) {
+ return Errorf("snapshotctl timed out");
+ }
+
+ // We don't know whether snapshotctl succeeded or not, but if it failed, the mount operation
+ // below will fail with `ENOENT`.
+ OR_RETURN(
+ Mount(GetBlockDeviceName("system", ota_slot.value()), CHROOT_DIR, /*is_optional=*/false));
+ } else {
+ // update_engine has mounted `system` at `/postinstall` for us.
+ OR_RETURN(BindMount("/postinstall", CHROOT_DIR));
}
- // We don't know whether snapshotctl succeeded or not, but if it failed, the mount operation
- // below will fail with `ENOENT`.
- OR_RETURN(Mount(GetBlockDeviceName("system", ota_slot.value()), CHROOT_DIR));
for (const std::string& partition : additional_system_partitions) {
- OR_RETURN(
- Mount(GetBlockDeviceName(partition, ota_slot.value()), PathInChroot("/" + partition)));
+ OR_RETURN(Mount(GetBlockDeviceName(partition, ota_slot.value()),
+ PathInChroot("/" + partition),
+ /*is_optional=*/true));
}
}
@@ -406,6 +458,16 @@ Result<void> DexoptChrootSetup::SetUpChroot(const std::optional<std::string>& ot
OR_RETURN(BindMountRecursive(src, PathInChroot(src)));
}
+ if (!WriteStringToFile(ota_slot.value_or(""), *kOtaSlotFile)) {
+ return ErrnoErrorf("Failed to write '{}'", *kOtaSlotFile);
+ }
+
+ return {};
+}
+
+Result<void> DexoptChrootSetup::InitChroot() const {
+ std::optional<std::string> ota_slot = OR_RETURN(LoadOtaSlotFile());
+
// Generate empty linker config to suppress warnings.
if (!android::base::WriteStringToFile("", PathInChroot("/linkerconfig/ld.config.txt"))) {
PLOG(WARNING) << "Failed to generate empty linker config to suppress warnings";
@@ -482,11 +544,22 @@ Result<void> DexoptChrootSetup::TearDownChroot() const {
return Errorf("Failed to remove dir '{}': {}", *kBindMountTmpDir, ec.message());
}
- if (!SetProperty("sys.snapshotctl.unmap", "requested")) {
- return Errorf("Failed to request snapshotctl unmap");
+ std::filesystem::remove(*kOtaSlotFile, ec);
+ if (ec) {
+ return Errorf("Failed to remove file '{}': {}", *kOtaSlotFile, ec.message());
}
- if (!WaitForProperty("sys.snapshotctl.unmap", "finished", kSnapshotCtlTimeout)) {
- return Errorf("snapshotctl timed out");
+
+ if (OS::FileExists(kSnapshotMappedFile->c_str())) {
+ if (!SetProperty("sys.snapshotctl.unmap", "requested")) {
+ return Errorf("Failed to request snapshotctl unmap");
+ }
+ if (!WaitForProperty("sys.snapshotctl.unmap", "finished", kSnapshotCtlTimeout)) {
+ return Errorf("snapshotctl timed out");
+ }
+ std::filesystem::remove(*kSnapshotMappedFile, ec);
+ if (ec) {
+ return Errorf("Failed to remove file '{}': {}", *kSnapshotMappedFile, ec.message());
+ }
}
return {};
diff --git a/dexopt_chroot_setup/dexopt_chroot_setup.h b/dexopt_chroot_setup/dexopt_chroot_setup.h
index baceb1a480..de0e35fa4d 100644
--- a/dexopt_chroot_setup/dexopt_chroot_setup.h
+++ b/dexopt_chroot_setup/dexopt_chroot_setup.h
@@ -30,15 +30,20 @@ namespace dexopt_chroot_setup {
// A service that sets up the chroot environment for Pre-reboot Dexopt.
class DexoptChrootSetup : public aidl::com::android::server::art::BnDexoptChrootSetup {
public:
- ndk::ScopedAStatus setUp(const std::optional<std::string>& in_otaSlot) override;
+ ndk::ScopedAStatus setUp(const std::optional<std::string>& in_otaSlot,
+ bool in_mapSnapshotsForOta) override;
+
+ ndk::ScopedAStatus init() override;
ndk::ScopedAStatus tearDown() override;
android::base::Result<void> Start();
private:
- android::base::Result<void> SetUpChroot(const std::optional<std::string>& ota_slot) const
- REQUIRES(mu_);
+ android::base::Result<void> SetUpChroot(const std::optional<std::string>& ota_slot,
+ bool map_snapshots_for_ota) const REQUIRES(mu_);
+
+ android::base::Result<void> InitChroot() const REQUIRES(mu_);
android::base::Result<void> TearDownChroot() const REQUIRES(mu_);
diff --git a/dexopt_chroot_setup/dexopt_chroot_setup_test.cc b/dexopt_chroot_setup/dexopt_chroot_setup_test.cc
index bfa38e0aa0..8e8cfef754 100644
--- a/dexopt_chroot_setup/dexopt_chroot_setup_test.cc
+++ b/dexopt_chroot_setup/dexopt_chroot_setup_test.cc
@@ -102,7 +102,9 @@ class DexoptChrootSetupTest : public CommonArtTest {
TEST_F(DexoptChrootSetupTest, Run) {
// We only test the Mainline update case here. There isn't an easy way to test the OTA update case
// in such a unit test. The OTA update case is assumed to be covered by the E2E test.
- ASSERT_STATUS_OK(dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt));
+ ASSERT_STATUS_OK(
+ dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
+ ASSERT_STATUS_OK(dexopt_chroot_setup_->init());
// Some important dirs that should be the same as outside.
std::vector<const char*> same_dirs = {
@@ -172,7 +174,9 @@ TEST_F(DexoptChrootSetupTest, Run) {
// Check that `setUp` can be repetitively called, to simulate the case where an instance of the
// caller (typically system_server) called `setUp` and crashed later, and a new instance called
// `setUp` again.
- ASSERT_STATUS_OK(dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt));
+ ASSERT_STATUS_OK(
+ dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
+ ASSERT_STATUS_OK(dexopt_chroot_setup_->init());
ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown());
@@ -180,6 +184,12 @@ TEST_F(DexoptChrootSetupTest, Run) {
// Check that `tearDown` can be repetitively called too.
ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown());
+
+ // Check that `setUp` can be followed directly by a `tearDown`.
+ ASSERT_STATUS_OK(
+ dexopt_chroot_setup_->setUp(/*in_otaSlot=*/std::nullopt, /*in_mapSnapshotsForOta=*/false));
+ ASSERT_STATUS_OK(dexopt_chroot_setup_->tearDown());
+ EXPECT_FALSE(std::filesystem::exists(DexoptChrootSetup::CHROOT_DIR));
}
} // namespace
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index 0692bf8c20..54069fad2e 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -199,8 +199,6 @@ std::string CommonArtTestImpl::GetAndroidBuildTop() {
if (android_build_top_from_env != nullptr) {
if (std::filesystem::weakly_canonical(android_build_top).string() !=
std::filesystem::weakly_canonical(android_build_top_from_env).string()) {
- LOG(WARNING) << "Execution path (" << argv << ") not below ANDROID_BUILD_TOP ("
- << android_build_top_from_env << ")! Using env-var.";
android_build_top = android_build_top_from_env;
}
} else {
@@ -247,6 +245,26 @@ std::string CommonArtTestImpl::GetAndroidHostOut() {
return expected.string();
}
+std::string CommonArtTestImpl::GetHostBootClasspathInstallRoot() {
+ CHECK(IsHost());
+ std::string build_install_root = GetAndroidHostOut() + "/testcases/art_common/out/host/linux-x86";
+ // Look for the `apex` subdirectory as a discriminator to check the location.
+ if (OS::DirectoryExists((build_install_root + "/apex").c_str())) {
+ // This is the path where "m art-host-tests" installs support files for host
+ // tests, so use it when the tests are run in a build tree (which is the
+ // case when testing locally).
+ return build_install_root;
+ }
+ if (OS::DirectoryExists((GetAndroidRoot() + "/apex").c_str())) {
+ // This is the location for host tests in CI when the files are unzipped
+ // from art-host-tests.zip.
+ return GetAndroidRoot();
+ }
+ LOG(ERROR) << "Neither location has a boot classpath (forgot \"m art-host-tests\"?): "
+ << build_install_root << " or " << GetAndroidRoot();
+ return "<no boot classpath found>";
+}
+
void CommonArtTestImpl::SetUpAndroidRootEnvVars() {
if (IsHost()) {
std::string android_host_out = GetAndroidHostOut();
@@ -448,12 +466,14 @@ std::vector<std::string> CommonArtTestImpl::GetLibCoreModuleNames() const {
std::vector<std::string> CommonArtTestImpl::GetLibCoreDexFileNames(
const std::vector<std::string>& modules) const {
- return art::testing::GetLibCoreDexFileNames(kIsTargetBuild ? "" : GetAndroidRoot(), modules);
+ return art::testing::GetLibCoreDexFileNames(
+ kIsTargetBuild ? "" : GetHostBootClasspathInstallRoot(), modules);
}
std::vector<std::string> CommonArtTestImpl::GetLibCoreDexFileNames() const {
std::vector<std::string> modules = GetLibCoreModuleNames();
- return art::testing::GetLibCoreDexFileNames(kIsTargetBuild ? "" : GetAndroidRoot(), modules);
+ return art::testing::GetLibCoreDexFileNames(
+ kIsTargetBuild ? "" : GetHostBootClasspathInstallRoot(), modules);
}
std::vector<std::string> CommonArtTestImpl::GetLibCoreDexLocations(
@@ -535,9 +555,7 @@ std::unique_ptr<const DexFile> CommonArtTestImpl::OpenTestDexFile(const char* na
std::string CommonArtTestImpl::GetImageDirectory() {
if (IsHost()) {
- const char* host_dir = getenv("ANDROID_HOST_OUT");
- CHECK(host_dir != nullptr);
- return std::string(host_dir) + "/apex/art_boot_images/javalib";
+ return GetHostBootClasspathInstallRoot() + "/apex/art_boot_images/javalib";
}
// On device, the boot image is generated by `generate-boot-image`.
// In a standalone test, the boot image is located next to the gtest binary itself.
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index c490e085cd..f07f1bfc89 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -237,6 +237,12 @@ class CommonArtTestImpl {
// Returns ${ANDROID_HOST_OUT}.
static std::string GetAndroidHostOut();
+ // Returns the path where boot classpath and boot image files are installed
+ // for host tests (by the art_common mk module, typically built through "m
+ // art-host-tests"). Different in CI where they are unpacked from the
+ // art-host-tests.zip file.
+ static std::string GetHostBootClasspathInstallRoot();
+
// File location to boot.art, e.g. /apex/com.android.art/javalib/boot.art
static std::string GetCoreArtLocation();
diff --git a/libartbase/base/debugstore.h b/libartbase/base/debugstore.h
new file mode 100644
index 0000000000..b3e6563c60
--- /dev/null
+++ b/libartbase/base/debugstore.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_LIBARTBASE_BASE_DEBUGSTORE_H_
+#define ART_LIBARTBASE_BASE_DEBUGSTORE_H_
+
+#include <array>
+#include <string>
+
+#include "palette/palette.h"
+
+namespace art {
+static constexpr size_t STORE_MAX_SIZE = 1024;
+
+inline std::string DebugStoreGetString() {
+ std::array<char, STORE_MAX_SIZE> result{};
+ // If PaletteDebugStoreGetString returns PALETTE_STATUS_NOT_SUPPORTED,
+ // set an empty string as the result.
+ result[0] = '\0';
+ PaletteDebugStoreGetString(result.data(), result.size());
+ return std::string(result.data());
+}
+
+} // namespace art
+
+#endif // ART_LIBARTBASE_BASE_DEBUGSTORE_H_
diff --git a/libartbase/base/memory_tool.h b/libartbase/base/memory_tool.h
index 675ceb2cfd..4a93a9642f 100644
--- a/libartbase/base/memory_tool.h
+++ b/libartbase/base/memory_tool.h
@@ -68,10 +68,12 @@ constexpr size_t kMemoryToolStackGuardSizeScale = 1;
#if __has_feature(hwaddress_sanitizer)
# define HWADDRESS_SANITIZER
+constexpr bool kHwAsanEnabled = true;
// NB: The attribute also implies NO_INLINE. If inlined, the hwasan attribute would be lost.
// If method is also separately marked as ALWAYS_INLINE, the NO_INLINE takes precedence.
# define ATTRIBUTE_NO_SANITIZE_HWADDRESS __attribute__((no_sanitize("hwaddress"), noinline))
#else
+constexpr bool kHwAsanEnabled = false;
# define ATTRIBUTE_NO_SANITIZE_HWADDRESS
#endif
diff --git a/libartbase/base/pidfd.h b/libartbase/base/pidfd.h
new file mode 100644
index 0000000000..d209d931c8
--- /dev/null
+++ b/libartbase/base/pidfd.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef ART_LIBARTBASE_BASE_PIDFD_H_
+#define ART_LIBARTBASE_BASE_PIDFD_H_
+
+#include <cstdint>
+#include <cstdio>
+
+#include "android-base/unique_fd.h"
+
+#ifdef __BIONIC__
+#include <sys/pidfd.h>
+#endif
+
+namespace art {
+
+[[maybe_unused]] static android::base::unique_fd PidfdOpen(pid_t pid, uint32_t flags) {
+#ifdef __BIONIC__
+ return android::base::unique_fd(pidfd_open(pid, flags));
+#else
+ // There is no glibc wrapper for pidfd_open.
+#ifndef SYS_pidfd_open
+ constexpr int SYS_pidfd_open = 434;
+#endif
+ return android::base::unique_fd(syscall(SYS_pidfd_open, pid, flags));
+#endif
+}
+
+} // namespace art
+
+#endif // ART_LIBARTBASE_BASE_PIDFD_H_
diff --git a/libartpalette/apex/palette.cc b/libartpalette/apex/palette.cc
index 346cd184ff..3efee45173 100644
--- a/libartpalette/apex/palette.cc
+++ b/libartpalette/apex/palette.cc
@@ -254,4 +254,11 @@ palette_status_t PaletteSetTaskProfiles(int32_t tid,
return m(tid, profiles, profiles_len);
}
+// Methods in version 4 API, corresponding to SDK level 36.
+palette_status_t PaletteDebugStoreGetString(char* result, size_t max_size) {
+ PaletteDebugStoreGetStringMethod m =
+ PaletteLoader::Instance().GetPaletteDebugStoreGetStringMethod();
+ return m(result, max_size);
+}
+
} // extern "C"
diff --git a/libartpalette/apex/palette_test.cc b/libartpalette/apex/palette_test.cc
index 63072c491b..cbdfafa733 100644
--- a/libartpalette/apex/palette_test.cc
+++ b/libartpalette/apex/palette_test.cc
@@ -21,6 +21,7 @@
#include <sys/syscall.h>
#include <unistd.h>
+#include <cstring>
#include <filesystem>
#ifdef ART_TARGET_ANDROID
@@ -50,6 +51,10 @@ bool PaletteSetTaskProfilesIsSupported(palette_status_t res) {
<< "Device API level: " << android_get_device_api_level();
return false;
}
+bool PaletteDebugStoreIsSupported() {
+ // TODO(b/345433959): Switch to android::modules::sdklevel::IsAtLeastW
+ return android_get_device_api_level() >= 36;
+}
#endif
} // namespace
@@ -166,3 +171,25 @@ TEST_F(PaletteClientTest, SetTaskProfilesCpp) {
}
#endif
}
+
+TEST_F(PaletteClientTest, DebugStore) {
+#ifndef ART_TARGET_ANDROID
+ GTEST_SKIP() << "DebugStore is only supported on Android";
+#else
+ std::array<char, 20> result{};
+ // Make sure the we are on a correct API level.
+ if (!PaletteDebugStoreIsSupported()) {
+ GTEST_SKIP() << "DebugStore is only supported on API 36+";
+ }
+ palette_status_t pstatus = PaletteDebugStoreGetString(result.data(), result.size());
+ EXPECT_EQ(PALETTE_STATUS_OK, pstatus);
+
+ size_t len = strnlen(result.data(), result.size());
+ EXPECT_TRUE(len < result.size());
+
+ const char* start = "1,0,";
+ const char* end = "::";
+ EXPECT_TRUE(len > strlen(start) + strlen(end));
+ EXPECT_EQ(strncmp(result.data() + len - strlen(end), end, strlen(end)), 0);
+#endif
+}
diff --git a/libartpalette/include/palette/palette_method_list.h b/libartpalette/include/palette/palette_method_list.h
index e5437cb33a..b2f0476892 100644
--- a/libartpalette/include/palette/palette_method_list.h
+++ b/libartpalette/include/palette/palette_method_list.h
@@ -75,6 +75,23 @@
/* PALETTE_STATUS_NOT_SUPPORTED if the implementation no longer supports this */ \
/* call. This can happen at any future SDK level since this function wraps an */ \
/* internal unstable API. */ \
- M(PaletteSetTaskProfiles, int32_t tid, const char* const profiles[], size_t profiles_len)
+ M(PaletteSetTaskProfiles, int32_t tid, const char* const profiles[], size_t profiles_len) \
+ \
+ /* Methods in version 4 API, corresponding to SDK level 36. */ \
+ \
+ /* Retrieves the debug store as a string. */ \
+ /* */ \
+ /* This function retrieves debug information stored in a predefined debug store. */ \
+ /* The information retrieved are used for debugging and logging purposes. */ \
+ /* */ \
+ /* @param result A pointer to a null-terminated character array where the retrieved */ \
+ /* string will be stored. */ \
+ /* @param max_size The maximum number of characters to be copied into the result, */ \
+ /* including the null terminator. It is the caller's responsibility */ \
+ /* to ensure that the pointed by result is large enough to store */ \
+ /* up to max_size characters. */ \
+ /* @return PALETTE_STATUS_OK if the call succeeded. */ \
+ /* PALETTE_STATUS_INVALID_ARGUMENT if the pointer is a nullptr or max_size is 0 */ \
+ M(PaletteDebugStoreGetString, char* result, size_t max_size)
#endif // ART_LIBARTPALETTE_INCLUDE_PALETTE_PALETTE_METHOD_LIST_H_
diff --git a/libartpalette/libartpalette.map b/libartpalette/libartpalette.map
index 6172a21427..e3c36f5277 100644
--- a/libartpalette/libartpalette.map
+++ b/libartpalette/libartpalette.map
@@ -51,3 +51,9 @@ LIBARTPALETTE_3 { # introduced=34
# --- VERSION 03 API ---
PaletteSetTaskProfiles; # apex
} LIBARTPALETTE_2;
+
+LIBARTPALETTE_4 { # introduced=36
+ global:
+ # --- VERSION 04 API ---
+ PaletteDebugStoreGetString; # apex
+} LIBARTPALETTE_3; \ No newline at end of file
diff --git a/libartpalette/system/palette_fake.cc b/libartpalette/system/palette_fake.cc
index e698267d10..f2fc76d68c 100644
--- a/libartpalette/system/palette_fake.cc
+++ b/libartpalette/system/palette_fake.cc
@@ -147,3 +147,9 @@ palette_status_t PaletteSetTaskProfiles([[maybe_unused]] int32_t tid,
[[maybe_unused]] size_t profiles_len) {
return PALETTE_STATUS_OK;
}
+
+// Methods in version 4 API, corresponding to SDK level 36.
+palette_status_t PaletteDebugStoreGetString([[maybe_unused]] char* result,
+ [[maybe_unused]] size_t max_size) {
+ return PALETTE_STATUS_OK;
+}
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
index ef28353666..0b1bd90c20 100644
--- a/libartservice/service/Android.bp
+++ b/libartservice/service/Android.bp
@@ -32,6 +32,7 @@ cc_defaults {
],
export_include_dirs: ["native"],
shared_libs: [
+ "liblog", // Used by "JNIHelp.h".
"libnativehelper",
],
}
diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java
index 6531e61668..055014fb43 100644
--- a/libartservice/service/java/com/android/server/art/AidlUtils.java
+++ b/libartservice/service/java/com/android/server/art/AidlUtils.java
@@ -53,6 +53,14 @@ public final class AidlUtils {
}
@NonNull
+ public static ArtifactsPath buildArtifactsPathAsInputPreReboot(
+ @NonNull String dexPath, @NonNull String isa, boolean isInDalvikCache) {
+ // Normally this should not be called, unless the caller really means to use Pre-reboot
+ // artifacts as inputs.
+ return buildArtifactsPath(dexPath, isa, isInDalvikCache, true /* isPreReboot */);
+ }
+
+ @NonNull
public static FsPermission buildFsPermission(
int uid, int gid, boolean isOtherReadable, boolean isOtherExecutable) {
var fsPermission = new FsPermission();
diff --git a/libartservice/service/java/com/android/server/art/ArtJni.java b/libartservice/service/java/com/android/server/art/ArtJni.java
index d46b889460..62b53dd6c8 100644
--- a/libartservice/service/java/com/android/server/art/ArtJni.java
+++ b/libartservice/service/java/com/android/server/art/ArtJni.java
@@ -25,6 +25,8 @@ import androidx.annotation.RequiresApi;
import dalvik.system.VMRuntime;
+import java.io.IOException;
+
/**
* JNI methods for ART Service with wrappers.
*
@@ -112,9 +114,59 @@ public class ArtJni {
return getGarbageCollectorNative();
}
+ /**
+ * Sets the system property {@code key} to {@code value}.
+ *
+ * @throws IllegalStateException if the operation fails. This caller should not expect this,
+ * unless the inputs are invalid (e.g., value too long).
+ */
+ public static Void setProperty(@NonNull String key, @NonNull String value) {
+ if (GlobalInjector.getInstance().isPreReboot()) {
+ // We don't need this for Pre-reboot Dexopt.
+ throw new UnsupportedOperationException();
+ }
+ setPropertyNative(key, value);
+ // Return a placeholder value to make this method easier to mock. There is no good way to
+ // mock a method that is both void and static, due to the poor design of Mockito API.
+ return null;
+ }
+
+ /**
+ * Waits for processes whose executable is in the given directory to exit, and kills them if
+ * they don't exit within the timeout.
+ *
+ * Note that this method only checks processes' executable paths, not their open files. If the
+ * executable of a process is outside of the given directory but the process opens a file in
+ * that directory, this method doesn't handle it.
+ *
+ * After killing, the method waits another round with the given timeout. Theoretically, this
+ * method can take at most {@code 2 * timeoutMs}. However, the second round should be pretty
+ * fast in practice.
+ *
+ * This method assumes that no new process is started from an executable in the given directory
+ * while the method is running. It is the callers responsibility to make sure that this
+ * assumption holds.
+ *
+ * @throws IllegalArgumentException if {@code timeoutMs} is negative
+ * @throws IOException if the operation fails
+ */
+ public static Void ensureNoProcessInDir(@NonNull String dir, int timeoutMs) throws IOException {
+ if (GlobalInjector.getInstance().isPreReboot()) {
+ // We don't need this for Pre-reboot Dexopt.
+ throw new UnsupportedOperationException();
+ }
+ ensureNoProcessInDirNative(dir, timeoutMs);
+ // Return a placeholder value to make this method easier to mock. There is no good way to
+ // mock a method that is both void and static, due to the poor design of Mockito API.
+ return null;
+ }
+
@Nullable private static native String validateDexPathNative(@NonNull String dexPath);
@Nullable
private static native String validateClassLoaderContextNative(
@NonNull String dexPath, @NonNull String classLoaderContext);
@NonNull private static native String getGarbageCollectorNative();
+ private static native void setPropertyNative(@NonNull String key, @NonNull String value);
+ private static native void ensureNoProcessInDirNative(@NonNull String dir, int timeoutMs)
+ throws IOException;
}
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index ffbd66a99a..8be328ca9a 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -74,6 +74,7 @@ import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.art.model.DexoptStatus;
import com.android.server.art.model.OperationProgress;
+import com.android.server.art.prereboot.PreRebootStatsReporter;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.AndroidPackageSplit;
@@ -130,6 +131,10 @@ public final class ArtManagerLocal {
private boolean mShouldCommitPreRebootStagedFiles = false;
+ // A temporary object for holding stats while staged files are being committed, used in two
+ // places: `onBoot` and the `BroadcastReceiver` of `ACTION_BOOT_COMPLETED`.
+ @Nullable private PreRebootStatsReporter.AfterRebootSession mStatsAfterRebootSession = null;
+
@Deprecated
public ArtManagerLocal() {
mInjector = new Injector();
@@ -876,6 +881,8 @@ public final class ArtManagerLocal {
// to commit files for secondary dex files because they are not decrypted before
// then.
mShouldCommitPreRebootStagedFiles = true;
+ mStatsAfterRebootSession =
+ mInjector.getPreRebootStatsReporter().new AfterRebootSession();
commitPreRebootStagedFiles(snapshot, false /* forSecondary */);
}
dexoptPackages(snapshot, bootReason, new CancellationSignal(), progressCallbackExecutor,
@@ -903,6 +910,8 @@ public final class ArtManagerLocal {
try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) {
commitPreRebootStagedFiles(snapshot, true /* forSecondary */);
}
+ mStatsAfterRebootSession.reportAsync();
+ mStatsAfterRebootSession = null;
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
@@ -923,13 +932,7 @@ public final class ArtManagerLocal {
@SuppressLint("UnflaggedApi") // Flag support for mainline is not available.
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public void onApexStaged(@NonNull String[] stagedApexModuleNames) {
- // TODO(b/311377497): Check system requirements.
- mInjector.getPreRebootDexoptJob().unschedule();
- // Although `unschedule` implies `cancel`, we explicitly call `cancel` here to wait for
- // the job to exit, if it's running.
- mInjector.getPreRebootDexoptJob().cancel(true /* blocking */);
- mInjector.getPreRebootDexoptJob().updateOtaSlot(null);
- mInjector.getPreRebootDexoptJob().schedule();
+ mInjector.getPreRebootDexoptJob().onUpdateReady(null /* otaSlot */);
}
/**
@@ -1105,7 +1108,7 @@ public final class ArtManagerLocal {
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
private void commitPreRebootStagedFiles(
@NonNull PackageManagerLocal.FilteredSnapshot snapshot, boolean forSecondary) {
- try {
+ try (var pin = mInjector.createArtdPin()) {
// Because we don't know for which packages the Pre-reboot Dexopt job has generated
// staged files, we call artd for all dexoptable packages, which is a superset of the
// packages that we actually expect to have staged files.
@@ -1138,7 +1141,10 @@ public final class ArtManagerLocal {
// ones generated by background dexopt, committing the artifacts while failing
// to commit the profile can potentially cause a permanent performance
// regression.
- mInjector.getArtd().commitPreRebootStagedFiles(artifacts, profiles);
+ if (mInjector.getArtd().commitPreRebootStagedFiles(artifacts, profiles)) {
+ mStatsAfterRebootSession.recordPackageWithArtifacts(
+ pkgState.getPackageName());
+ }
} catch (ServiceSpecificException e) {
AsLog.e("Failed to commit Pre-reboot staged files for package '"
+ pkgState.getPackageName() + "'",
@@ -1650,5 +1656,11 @@ public final class ArtManagerLocal {
public DexMetadataHelper getDexMetadataHelper() {
return new DexMetadataHelper();
}
+
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ @NonNull
+ public PreRebootStatsReporter getPreRebootStatsReporter() {
+ return new PreRebootStatsReporter();
+ }
}
}
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index ee1aa88b50..7d8d7dcaa3 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -60,11 +60,15 @@ import com.android.server.pm.pkg.PackageState;
import libcore.io.Streams;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.nio.ByteBuffer;
+import java.nio.channels.ClosedByInterruptException;
+import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
@@ -183,13 +187,12 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
pw.println("Profiles cleared");
return 0;
}
- case "pre-reboot-dexopt": {
- // TODO(b/311377497): Remove this command once the development is done.
- return handlePreRebootDexopt(pw);
- }
case "on-ota-staged": {
return handleOnOtaStaged(pw);
}
+ case "pr-dexopt-job": {
+ return handlePrDexoptJob(pw);
+ }
default:
pw.printf("Error: Unknown 'art' sub-command '%s'\n", subcmd);
pw.println("See 'pm help' for help");
@@ -646,33 +649,6 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
return 0;
}
- private int handlePreRebootDexopt(@NonNull PrintWriter pw) {
- if (!SdkLevel.isAtLeastV()) {
- pw.println("Error: Unsupported command 'pre-reboot-dexopt'");
- return 1;
- }
-
- String otaSlot = null;
-
- String opt = getNextOption();
- if ("--slot".equals(opt)) {
- otaSlot = getNextArgRequired();
- } else if (opt != null) {
- pw.println("Error: Unknown option: " + opt);
- return 1;
- }
-
- try (var signal = new WithCancellationSignal(pw, true /* verbose */)) {
- if (new PreRebootDriver(mContext).run(otaSlot, signal.get())) {
- pw.println("Job finished. See logs for details");
- return 0;
- } else {
- pw.println("Job failed. See logs for details");
- return 1;
- }
- }
- }
-
private int handleOnOtaStaged(@NonNull PrintWriter pw) {
if (!SdkLevel.isAtLeastV()) {
pw.println("Error: Unsupported command 'on-ota-staged'");
@@ -703,30 +679,110 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
return 1;
}
- int code;
+ if (mArtManagerLocal.getPreRebootDexoptJob().isAsyncForOta()) {
+ return handleSchedulePrDexoptJob(pw, otaSlot);
+ } else {
+ return handleRunPrDexoptJob(pw, otaSlot);
+ }
+ }
+
+ private int handlePrDexoptJob(@NonNull PrintWriter pw) {
+ if (!SdkLevel.isAtLeastV()) {
+ pw.println("Error: Unsupported command 'pr-dexopt-job'");
+ return 1;
+ }
+
+ boolean isTest = false;
+
+ String opt = getNextOption();
+ if ("--test".equals(opt)) {
+ isTest = true;
+ } else if (opt != null) {
+ pw.println("Error: Unknown option: " + opt);
+ return 1;
+ }
+
+ if (isTest) {
+ try {
+ mArtManagerLocal.getPreRebootDexoptJob().test();
+ pw.println("Success");
+ return 0;
+ } catch (Exception e) {
+ pw.println("Failure");
+ e.printStackTrace(pw);
+ return 2; // "1" is for general errors. Use "2" for the test failure.
+ }
+ }
+
+ pw.println("Error: No option specified");
+ return 1;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ private int handleRunPrDexoptJob(@NonNull PrintWriter pw, @Nullable String otaSlot) {
+ PreRebootDexoptJob job = mArtManagerLocal.getPreRebootDexoptJob();
+
+ CompletableFuture<Void> future = job.onUpdateReadyStartNow(otaSlot);
+ if (future == null) {
+ pw.println("Job disabled by system property");
+ return 1;
+ }
+
+ // Put the read in a separate thread because there isn't an easy way in Java to wait for
+ // both the `Future` and the read.
+ var readThread = new Thread(() -> {
+ try (var in = new FileInputStream(getInFileDescriptor())) {
+ ByteBuffer buffer = ByteBuffer.allocate(128 /* capacity */);
+ FileChannel channel = in.getChannel();
+ while (channel.read(buffer) >= 0) {
+ buffer.clear();
+ }
+ // Broken pipe.
+ job.cancelGiven(future, true /* expectInterrupt */);
+ } catch (ClosedByInterruptException e) {
+ // Job finished normally.
+ } catch (IOException e) {
+ AsLog.e("Unexpected exception", e);
+ job.cancelGiven(future, true /* expectInterrupt */);
+ } catch (RuntimeException e) {
+ AsLog.wtf("Unexpected exception", e);
+ job.cancelGiven(future, true /* expectInterrupt */);
+ }
+ });
+ readThread.start();
+ pw.println("Job running... To cancel it, press Ctrl+C.");
+ pw.flush();
- // This operation requires the uid to be "system" (1000).
- long identityToken = Binder.clearCallingIdentity();
try {
- mArtManagerLocal.getPreRebootDexoptJob().unschedule();
- // Although `unschedule` implies `cancel`, we explicitly call `cancel` here to wait for
- // the job to exit, if it's running.
- mArtManagerLocal.getPreRebootDexoptJob().cancel(true /* blocking */);
- mArtManagerLocal.getPreRebootDexoptJob().updateOtaSlot(otaSlot);
- code = mArtManagerLocal.getPreRebootDexoptJob().schedule();
+ Utils.getFuture(future);
+ pw.println("Job finished. See logs for details");
+ } catch (RuntimeException e) {
+ pw.println("Job encountered a fatal error");
+ e.printStackTrace(pw);
} finally {
- Binder.restoreCallingIdentity(identityToken);
+ readThread.interrupt();
+ try {
+ readThread.join();
+ } catch (InterruptedException e) {
+ AsLog.wtf("Interrupted", e);
+ }
}
+ return 0;
+ }
+
+ @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
+ private int handleSchedulePrDexoptJob(@NonNull PrintWriter pw, @Nullable String otaSlot) {
+ int code = mArtManagerLocal.getPreRebootDexoptJob().onUpdateReady(otaSlot);
switch (code) {
case ArtFlags.SCHEDULE_SUCCESS:
- pw.println("Job scheduled");
+ pw.println("Pre-reboot Dexopt job scheduled");
return 0;
case ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP:
- pw.println("Job disabled by system property");
+ pw.println("Pre-reboot Dexopt job disabled by system property");
return 1;
case ArtFlags.SCHEDULE_JOB_SCHEDULER_FAILURE:
- pw.println("Failed to schedule job");
+ pw.println("Failed to schedule Pre-reboot Dexopt job");
return 1;
default:
// Can't happen.
@@ -885,8 +941,16 @@ public final class ArtShellCommand extends BasicShellCommandHandler {
pw.println(" API documentation for 'ArtManagerLocal.dexoptPackages' for details.");
pw.println();
pw.println(" on-ota-staged --slot SLOT");
- pw.println(" Notifies ART Service that an OTA update is staged. A Pre-reboot Dexopt");
- pw.println(" job is scheduled for it.");
+ pw.println(" Notifies ART Service that an OTA update is staged. ART Service decides");
+ pw.println(" what to do with this notification:");
+ pw.println(" - If Pre-reboot Dexopt is disabled or unsupported, the command returns");
+ pw.println(" non-zero.");
+ pw.println(" - If Pre-reboot Dexopt is enabled in synchronous mode, the command blocks");
+ pw.println(" until Pre-reboot Dexopt finishes, and returns zero no matter it");
+ pw.println(" succeeds or not.");
+ pw.println(" - If Pre-reboot Dexopt is enabled in asynchronous mode, the command");
+ pw.println(" schedules an asynchronous job and returns 0 immediately. The job will");
+ pw.println(" then run by the job scheduler when the device is idle and charging.");
pw.println(" Options:");
pw.println(" --slot SLOT The slot that contains the OTA update, '_a' or '_b'.");
}
diff --git a/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
index 6dda5c9980..526a08eb48 100644
--- a/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
+++ b/libartservice/service/java/com/android/server/art/BackgroundDexoptJob.java
@@ -183,7 +183,7 @@ public class BackgroundDexoptJob implements ArtServiceJobInterface {
try (var tracing = new Utils.TracingWithTimingLogging(AsLog.getTag(), "jobExecution")) {
return run(mCancellationSignal);
} catch (RuntimeException e) {
- AsLog.e("Fatal error", e);
+ AsLog.wtf("Fatal error", e);
return new FatalErrorResult();
} finally {
synchronized (this) {
diff --git a/libartservice/service/java/com/android/server/art/DexoptHelper.java b/libartservice/service/java/com/android/server/art/DexoptHelper.java
index 76edfd503f..1f7bd93235 100644
--- a/libartservice/service/java/com/android/server/art/DexoptHelper.java
+++ b/libartservice/service/java/com/android/server/art/DexoptHelper.java
@@ -141,21 +141,37 @@ public class DexoptHelper {
PackageState pkgState = pkgStates.get(i);
CancellationSignal childCancellationSignal = childCancellationSignals.get(i);
futures.add(CompletableFuture.supplyAsync(() -> {
- return dexoptPackage(pkgState, params, childCancellationSignal);
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+ if (canDexoptPackage(pkgState)
+ && (params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
+ // Throws if the split is not found.
+ PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName());
+ }
+ try {
+ return dexoptPackage(pkgState, pkg, params, childCancellationSignal);
+ } catch (RuntimeException e) {
+ AsLog.wtf("Unexpected package-level exception during dexopt", e);
+ return PackageDexoptResult.create(pkgState.getPackageName(),
+ new ArrayList<>() /* dexContainerFileDexoptResults */,
+ DexoptResult.DEXOPT_FAILED);
+ }
}, dexoptExecutor));
}
if (progressCallback != null) {
CompletableFuture.runAsync(() -> {
- progressCallback.accept(
- OperationProgress.create(0 /* current */, futures.size()));
+ progressCallback.accept(OperationProgress.create(
+ 0 /* current */, futures.size(), null /* packageDexoptResult */));
}, progressCallbackExecutor);
AtomicInteger current = new AtomicInteger(0);
for (CompletableFuture<PackageDexoptResult> future : futures) {
- future.thenRunAsync(() -> {
- progressCallback.accept(OperationProgress.create(
- current.incrementAndGet(), futures.size()));
- }, progressCallbackExecutor);
+ future.thenAcceptAsync(result -> {
+ progressCallback.accept(OperationProgress.create(
+ current.incrementAndGet(), futures.size(), result));
+ }, progressCallbackExecutor).exceptionally(t -> {
+ AsLog.e("Failed to update progress", t);
+ return null;
+ });
}
}
@@ -201,23 +217,17 @@ public class DexoptHelper {
*/
@NonNull
private PackageDexoptResult dexoptPackage(@NonNull PackageState pkgState,
- @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal) {
+ @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
+ @NonNull CancellationSignal cancellationSignal) {
List<DexContainerFileDexoptResult> results = new ArrayList<>();
Function<Integer, PackageDexoptResult> createResult = (packageLevelStatus)
-> PackageDexoptResult.create(
pkgState.getPackageName(), results, packageLevelStatus);
- AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
-
if (!canDexoptPackage(pkgState)) {
return createResult.apply(null /* packageLevelStatus */);
}
- if ((params.getFlags() & ArtFlags.FLAG_FOR_SINGLE_SPLIT) != 0) {
- // Throws if the split is not found.
- PrimaryDexUtils.getDexInfoBySplitName(pkg, params.getSplitName());
- }
-
try (var tracing = new Utils.Tracing("dexopt")) {
if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
if (cancellationSignal.isCanceled()) {
diff --git a/libartservice/service/java/com/android/server/art/Dexopter.java b/libartservice/service/java/com/android/server/art/Dexopter.java
index b8aa912723..271dc6144f 100644
--- a/libartservice/service/java/com/android/server/art/Dexopter.java
+++ b/libartservice/service/java/com/android/server/art/Dexopter.java
@@ -118,6 +118,14 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
continue;
}
+ if (mInjector.isPreReboot() && !isDexFileFound(dexInfo)) {
+ // In the pre-reboot case, it's possible that a dex file doesn't exist in the
+ // new system image. Although code below can gracefully handle failures, those
+ // failures can be red herrings in metrics and bug reports, so we skip
+ // non-existing dex files to avoid them.
+ continue;
+ }
+
DexMetadataInfo dmInfo =
mInjector.getDexMetadataHelper().getDexMetadataInfo(buildDmPath(dexInfo));
@@ -200,6 +208,27 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
.setNeedsToBePublic(needsToBeShared)
.build();
+ if (mInjector.isPreReboot()) {
+ ArtifactsPath existingArtifacts =
+ AidlUtils.buildArtifactsPathAsInputPreReboot(
+ target.dexInfo().dexPath(), target.isa(),
+ target.isInDalvikCache());
+ if (mInjector.getArtd().getArtifactsVisibility(existingArtifacts)
+ != FileVisibility.NOT_FOUND) {
+ // Because `getDexoptNeeded` doesn't check Pre-reboot artifacts, we
+ // do a simple check here to handle job resuming. If the Pre-reboot
+ // artifacts exist, we assume they are up-to-date because
+ // `PreRebootDexoptJob` would otherwise clean them up, so we skip
+ // this dex file. The profile and the dex file may have been changed
+ // since the last cancelled job run, but we don't handle such cases
+ // because we are supposed to dexopt every dex file only once for
+ // each ISA.
+ extendedStatusFlags |=
+ DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST;
+ continue;
+ }
+ }
+
GetDexoptNeededResult getDexoptNeededResult =
getDexoptNeeded(target, options);
@@ -639,6 +668,11 @@ public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
/**
+ * Returns true if the dex file is found.
+ */
+ protected abstract boolean isDexFileFound(@NonNull DexInfoType dexInfo);
+
+ /**
* Returns a list of external profiles (e.g., a DM profile) that the reference profile can be
* initialized from, in the order of preference.
*/
diff --git a/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java b/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java
index c890198d96..0c9350f773 100644
--- a/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java
+++ b/libartservice/service/java/com/android/server/art/PreRebootDexoptJob.java
@@ -25,8 +25,12 @@ import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
+import android.os.Binder;
import android.os.Build;
import android.os.CancellationSignal;
+import android.os.PersistableBundle;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
@@ -37,9 +41,11 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.ArtServiceJobInterface;
import com.android.server.art.prereboot.PreRebootDriver;
+import com.android.server.art.prereboot.PreRebootStatsReporter;
import java.time.Duration;
import java.util.Objects;
+import java.util.UUID;
import java.util.concurrent.CompletableFuture;
/**
@@ -61,22 +67,22 @@ public class PreRebootDexoptJob implements ArtServiceJobInterface {
@NonNull private final Injector mInjector;
- // Job state variables.
+ // Job state variables. The monitor of `this` is notified when `mRunningJob` is changed.
@GuardedBy("this") @Nullable private CompletableFuture<Void> mRunningJob = null;
@GuardedBy("this") @Nullable private CancellationSignal mCancellationSignal = null;
+ /** Whether `mRunningJob` is running from the job scheduler's perspective. */
+ @GuardedBy("this") private boolean mIsRunningJobKnownByJobScheduler = false;
+
/** The slot that contains the OTA update, "_a" or "_b", or null for a Mainline update. */
@GuardedBy("this") @Nullable private String mOtaSlot = null;
- /**
- * Whether the job has started at least once, meaning the device is expected to have staged
- * files, no matter it succeed, failed, or cancelled.
- *
- * Note that this flag is not persisted across system server restarts. It's possible that the
- * value is lost due to a system server restart caused by a crash, but this is a minor case, so
- * we don't handle it here for simplicity.
- */
- @GuardedBy("this") private boolean mHasStarted = false;
+ /** Whether to map/unmap snapshots. Only applicable to an OTA update. */
+ @GuardedBy("this") private boolean mMapSnapshotsForOta = false;
+
+ // Mutations to the global state of Pre-reboot Dexopt, including mounts, staged files, and
+ // stats, should only be done when there is no job running and the `this` lock is held, or by
+ // the job itself.
public PreRebootDexoptJob(@NonNull Context context) {
this(new Injector(context));
@@ -88,19 +94,27 @@ public class PreRebootDexoptJob implements ArtServiceJobInterface {
}
@Override
- public boolean onStartJob(
+ public synchronized boolean onStartJob(
@NonNull BackgroundDexoptJobService jobService, @NonNull JobParameters params) {
+ JobInfo pendingJob = mInjector.getJobScheduler().getPendingJob(JOB_ID);
+ if (pendingJob == null
+ || !params.getExtras().getString("ticket").equals(
+ pendingJob.getExtras().getString("ticket"))) {
+ // Job expired. We can only get here due to a race, and this should be very rare.
+ return false;
+ }
+
+ mIsRunningJobKnownByJobScheduler = true;
// No need to handle exceptions thrown by the future because exceptions are handled inside
// the future itself.
- var unused = start().thenRunAsync(() -> {
- try {
+ startLocked(() -> {
+ if (mIsRunningJobKnownByJobScheduler) {
+ mIsRunningJobKnownByJobScheduler = false;
// If it failed, it means something went wrong, so we don't reschedule the job
// because it will likely fail again. If it's cancelled, the job will be rescheduled
// because the return value of `onStopJob` will be respected, and this call will be
- // ignored.
+ // skipped.
jobService.jobFinished(params, false /* wantsReschedule */);
- } catch (RuntimeException e) {
- AsLog.wtf("Unexpected exception", e);
}
});
// "true" means the job will continue running until `jobFinished` is called.
@@ -108,36 +122,88 @@ public class PreRebootDexoptJob implements ArtServiceJobInterface {
}
@Override
- public boolean onStopJob(@NonNull JobParameters params) {
- cancel(false /* blocking */);
+ public synchronized boolean onStopJob(@NonNull JobParameters params) {
+ if (mIsRunningJobKnownByJobScheduler) {
+ cancelGivenLocked(mRunningJob, false /* expectInterrupt */);
+ mIsRunningJobKnownByJobScheduler = false;
+ }
// "true" means to execute again with the default retry policy.
return true;
}
- public @ScheduleStatus int schedule() {
- if (this != BackgroundDexoptJobService.getJob(JOB_ID)) {
- throw new IllegalStateException("This job cannot be scheduled");
+ /**
+ * Notifies this class that an update (OTA or Mainline) is ready.
+ *
+ * @param otaSlot The slot that contains the OTA update, "_a" or "_b", or null for a Mainline
+ * update.
+ */
+ public synchronized @ScheduleStatus int onUpdateReady(@Nullable String otaSlot) {
+ cancelAnyLocked();
+ resetLocked();
+ updateOtaSlotLocked(otaSlot);
+ mMapSnapshotsForOta = true;
+ return scheduleLocked();
+ }
+
+ /**
+ * Same as above, but starts the job immediately, instead of going through the job scheduler.
+ *
+ * @return The future of the job, or null if Pre-reboot Dexopt is not enabled.
+ */
+ @Nullable
+ public synchronized CompletableFuture<Void> onUpdateReadyStartNow(@Nullable String otaSlot) {
+ cancelAnyLocked();
+ resetLocked();
+ updateOtaSlotLocked(otaSlot);
+ // Don't map snapshots when running synchronously. `update_engine` maps snapshots for us.
+ mMapSnapshotsForOta = false;
+ if (!isEnabled()) {
+ return null;
}
+ mInjector.getStatsReporter().recordJobScheduled(false /* isAsync */, isOtaUpdate());
+ return startLocked(null /* onJobFinishedLocked */);
+ }
- if (!SystemProperties.getBoolean("dalvik.vm.enable_pr_dexopt", false /* def */)
- && !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_RUNTIME, "enable_pr_dexopt",
- false /* defaultValue */)) {
- AsLog.i("Pre-reboot Dexopt Job is not enabled by system property");
- return ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP;
+ public synchronized void test() {
+ cancelAnyLocked();
+ mInjector.getPreRebootDriver().test();
+ }
+
+ /** @see #cancelGivenLocked */
+ public synchronized void cancelGiven(
+ @NonNull CompletableFuture<Void> job, boolean expectInterrupt) {
+ cancelGivenLocked(job, expectInterrupt);
+ }
+
+ @VisibleForTesting
+ public synchronized void waitForRunningJob() {
+ while (mRunningJob != null) {
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ AsLog.wtf("Interrupted", e);
+ }
}
+ }
- // If `pm.dexopt.disable_bg_dexopt` is set, the user probably means to disable any dexopt
- // jobs in the background.
- if (SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false /* def */)) {
- AsLog.i("Pre-reboot Dexopt Job is disabled by system property "
- + "'pm.dexopt.disable_bg_dexopt'");
+ @GuardedBy("this")
+ private @ScheduleStatus int scheduleLocked() {
+ if (this != BackgroundDexoptJobService.getJob(JOB_ID)) {
+ throw new IllegalStateException("This job cannot be scheduled");
+ }
+
+ if (!isEnabled()) {
return ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP;
}
+ String ticket = UUID.randomUUID().toString();
+ PersistableBundle extras = new PersistableBundle(1 /* capacity */);
+ extras.putString("ticket", ticket);
JobInfo info = new JobInfo
.Builder(JOB_ID,
new ComponentName(JOB_PKG_NAME,
BackgroundDexoptJobService.class.getName()))
+ .setExtras(extras)
.setRequiresDeviceIdle(true)
.setRequiresCharging(true)
.setRequiresBatteryNotLow(true)
@@ -145,9 +211,19 @@ public class PreRebootDexoptJob implements ArtServiceJobInterface {
.setMinimumLatency(Duration.ofMinutes(10).toMillis())
.build();
- /* @JobScheduler.Result */ int result = mInjector.getJobScheduler().schedule(info);
+ /* @JobScheduler.Result */ int result;
+
+ // This operation requires the uid to be "system" (1000).
+ long identityToken = Binder.clearCallingIdentity();
+ try {
+ result = mInjector.getJobScheduler().schedule(info);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
+
if (result == JobScheduler.RESULT_SUCCESS) {
AsLog.i("Pre-reboot Dexopt Job scheduled");
+ mInjector.getStatsReporter().recordJobScheduled(true /* isAsync */, isOtaUpdate());
return ArtFlags.SCHEDULE_SUCCESS;
} else {
AsLog.i("Failed to schedule Pre-reboot Dexopt Job");
@@ -155,62 +231,112 @@ public class PreRebootDexoptJob implements ArtServiceJobInterface {
}
}
- public void unschedule() {
+ @GuardedBy("this")
+ private void unscheduleLocked() {
if (this != BackgroundDexoptJobService.getJob(JOB_ID)) {
throw new IllegalStateException("This job cannot be unscheduled");
}
- mInjector.getJobScheduler().cancel(JOB_ID);
+ // This operation requires the uid to be "system" (1000).
+ long identityToken = Binder.clearCallingIdentity();
+ try {
+ mInjector.getJobScheduler().cancel(JOB_ID);
+ } finally {
+ Binder.restoreCallingIdentity(identityToken);
+ }
}
+ /**
+ * The future returns true if the job is cancelled by the job scheduler.
+ *
+ * Can only be called when there is no running job.
+ */
+ @GuardedBy("this")
@NonNull
- public synchronized CompletableFuture<Void> start() {
- if (mRunningJob != null) {
- AsLog.i("Job is already running");
- return mRunningJob;
- }
+ private CompletableFuture<Void> startLocked(@Nullable Runnable onJobFinishedLocked) {
+ Utils.check(mRunningJob == null);
String otaSlot = mOtaSlot;
+ boolean mapSnapshotsForOta = mMapSnapshotsForOta;
var cancellationSignal = mCancellationSignal = new CancellationSignal();
- mHasStarted = true;
mRunningJob = new CompletableFuture().runAsync(() -> {
+ markHasStarted(true);
try {
- // TODO(b/336239721): Consume the result and report metrics.
- mInjector.getPreRebootDriver().run(otaSlot, cancellationSignal);
+ mInjector.getPreRebootDriver().run(otaSlot, mapSnapshotsForOta, cancellationSignal);
} catch (RuntimeException e) {
AsLog.e("Fatal error", e);
} finally {
synchronized (this) {
+ if (onJobFinishedLocked != null) {
+ try {
+ onJobFinishedLocked.run();
+ } catch (RuntimeException e) {
+ AsLog.wtf("Unexpected exception", e);
+ }
+ }
mRunningJob = null;
mCancellationSignal = null;
+ this.notifyAll();
}
}
});
+ this.notifyAll();
return mRunningJob;
}
/**
- * Cancels the job.
+ * Cancels the given job and waits for it to exit, if it's running. Temporarily releases the
+ * lock when waiting for the job to exit.
*
- * @param blocking whether to wait for the job to exit.
+ * When this method exits, it's guaranteed that the given job is not running, but another job
+ * might be running.
+ *
+ * @param expectInterrupt if true, this method returns immediately when the thread is
+ * interrupted, with no guarantee on the job state
*/
- public void cancel(boolean blocking) {
- CompletableFuture<Void> runningJob = null;
- synchronized (this) {
- if (mRunningJob == null) {
- return;
+ @GuardedBy("this")
+ private void cancelGivenLocked(@NonNull CompletableFuture<Void> job, boolean expectInterrupt) {
+ while (mRunningJob == job) {
+ if (!mCancellationSignal.isCanceled()) {
+ mCancellationSignal.cancel();
+ AsLog.i("Job cancelled");
+ }
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ if (expectInterrupt) {
+ return;
+ }
+ AsLog.wtf("Interrupted", e);
}
-
- mCancellationSignal.cancel();
- AsLog.i("Job cancelled");
- runningJob = mRunningJob;
}
- if (blocking) {
- Utils.getFuture(runningJob);
+ }
+
+ /**
+ * Cancels any running job, prevents the pending job (if any) from being started by the job
+ * scheduler, and waits for the running job to exit. Temporarily releases the lock when waiting
+ * for the job to exit.
+ *
+ * When this method exits, it's guaranteed that no job is running.
+ */
+ @GuardedBy("this")
+ private void cancelAnyLocked() {
+ unscheduleLocked();
+ while (mRunningJob != null) {
+ if (!mCancellationSignal.isCanceled()) {
+ mCancellationSignal.cancel();
+ AsLog.i("Job cancelled");
+ }
+ try {
+ this.wait();
+ } catch (InterruptedException e) {
+ AsLog.wtf("Interrupted", e);
+ }
}
}
- public synchronized void updateOtaSlot(@NonNull String value) {
+ @GuardedBy("this")
+ private void updateOtaSlotLocked(@Nullable String value) {
Utils.check(value == null || value.equals("_a") || value.equals("_b"));
// It's not possible that this method is called with two different slots.
Utils.check(mOtaSlot == null || value == null || Objects.equals(mOtaSlot, value));
@@ -222,8 +348,66 @@ public class PreRebootDexoptJob implements ArtServiceJobInterface {
}
}
- public synchronized boolean hasStarted() {
- return mHasStarted;
+ private boolean isEnabled() {
+ boolean syspropEnable =
+ SystemProperties.getBoolean("dalvik.vm.enable_pr_dexopt", false /* def */);
+ boolean deviceConfigEnable = mInjector.getDeviceConfigBoolean(
+ DeviceConfig.NAMESPACE_RUNTIME, "enable_pr_dexopt", false /* defaultValue */);
+ boolean deviceConfigForceDisable =
+ mInjector.getDeviceConfigBoolean(DeviceConfig.NAMESPACE_RUNTIME,
+ "force_disable_pr_dexopt", false /* defaultValue */);
+ if ((!syspropEnable && !deviceConfigEnable) || deviceConfigForceDisable) {
+ AsLog.i(String.format(
+ "Pre-reboot Dexopt Job is not enabled (sysprop:dalvik.vm.enable_pr_dexopt=%b, "
+ + "device_config:enable_pr_dexopt=%b, "
+ + "device_config:force_disable_pr_dexopt=%b)",
+ syspropEnable, deviceConfigEnable, deviceConfigForceDisable));
+ return false;
+ }
+ // If `pm.dexopt.disable_bg_dexopt` is set, the user probably means to disable any dexopt
+ // jobs in the background.
+ if (SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt", false /* def */)) {
+ AsLog.i("Pre-reboot Dexopt Job is disabled by system property "
+ + "'pm.dexopt.disable_bg_dexopt'");
+ return false;
+ }
+ return true;
+ }
+
+ public boolean isAsyncForOta() {
+ return SystemProperties.getBoolean("dalvik.vm.pr_dexopt_async_for_ota", false /* def */);
+ }
+
+ @GuardedBy("this")
+ private void resetLocked() {
+ mInjector.getStatsReporter().delete();
+ if (hasStarted()) {
+ try {
+ mInjector.getArtd().cleanUpPreRebootStagedFiles();
+ } catch (ServiceSpecificException | RemoteException e) {
+ AsLog.e("Failed to clean up obsolete Pre-reboot staged files", e);
+ }
+ markHasStarted(false);
+ }
+ }
+
+ /**
+ * Whether the job has started at least once, meaning the device is expected to have staged
+ * files, no matter it succeed, failed, or cancelled.
+ *
+ * This flag is survives across system server restarts, but not device reboots.
+ */
+ public boolean hasStarted() {
+ return SystemProperties.getBoolean("dalvik.vm.pre-reboot.has-started", false /* def */);
+ }
+
+ private void markHasStarted(boolean value) {
+ ArtJni.setProperty("dalvik.vm.pre-reboot.has-started", String.valueOf(value));
+ }
+
+ @GuardedBy("this")
+ private boolean isOtaUpdate() {
+ return mOtaSlot != null;
}
/**
@@ -248,5 +432,23 @@ public class PreRebootDexoptJob implements ArtServiceJobInterface {
public PreRebootDriver getPreRebootDriver() {
return new PreRebootDriver(mContext);
}
+
+ @NonNull
+ public PreRebootStatsReporter getStatsReporter() {
+ return new PreRebootStatsReporter();
+ }
+
+ @NonNull
+ public IArtd getArtd() {
+ return ArtdRefCache.getInstance().getArtd();
+ }
+
+ // Wrap `DeviceConfig` to avoid mocking it directly in tests. `DeviceConfig` backs
+ // read-write Trunk Stable flags used by the framework.
+ @NonNull
+ public boolean getDeviceConfigBoolean(
+ @NonNull String namespace, @NonNull String name, boolean defaultValue) {
+ return DeviceConfig.getBoolean(namespace, name, defaultValue);
+ }
}
}
diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
index e4475d8503..f8234c4df9 100644
--- a/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
+++ b/libartservice/service/java/com/android/server/art/PrimaryDexopter.java
@@ -118,6 +118,17 @@ public class PrimaryDexopter extends Dexopter<DetailedPrimaryDexInfo> {
}
@Override
+ protected boolean isDexFileFound(@NonNull DetailedPrimaryDexInfo dexInfo) {
+ try {
+ return mInjector.getArtd().getDexFileVisibility(dexInfo.dexPath())
+ != FileVisibility.NOT_FOUND;
+ } catch (ServiceSpecificException | RemoteException e) {
+ AsLog.e("Failed to get visibility of " + dexInfo.dexPath(), e);
+ return false;
+ }
+ }
+
+ @Override
@NonNull
protected List<ProfilePath> getExternalProfiles(@NonNull DetailedPrimaryDexInfo dexInfo) {
return PrimaryDexUtils.getExternalProfiles(dexInfo);
diff --git a/libartservice/service/java/com/android/server/art/SecondaryDexopter.java b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
index 07c6c0d654..57fe174d6a 100644
--- a/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
+++ b/libartservice/service/java/com/android/server/art/SecondaryDexopter.java
@@ -87,6 +87,12 @@ public class SecondaryDexopter extends Dexopter<CheckedSecondaryDexInfo> {
}
@Override
+ protected boolean isDexFileFound(@NonNull CheckedSecondaryDexInfo dexInfo) {
+ // `getDexInfoList` has already excluded non-existing dex files.
+ return true;
+ }
+
+ @Override
@NonNull
protected List<ProfilePath> getExternalProfiles(@NonNull CheckedSecondaryDexInfo dexInfo) {
// A secondary dex file doesn't have any external profile to use.
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 8132dbbab2..31e2fec4c0 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -279,16 +279,24 @@ public final class Utils {
try {
return future.get();
} catch (ExecutionException e) {
- Throwable cause = e.getCause();
- if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- }
- throw new RuntimeException(cause);
+ throw toRuntimeException(e.getCause());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
+ @NonNull
+ public static RuntimeException toRuntimeException(@NonNull Throwable t) {
+ if (t instanceof RuntimeException r) {
+ return r;
+ }
+ var r = new RuntimeException(t);
+ // Clear the unhelpful stack trace, which is the stack trace of the constructor call above,
+ // so that the user can focus on the stack trace of `t`.
+ r.setStackTrace(new StackTraceElement[0]);
+ return r;
+ }
+
/**
* Returns true if the given package is dexoptable.
*
diff --git a/libartservice/service/java/com/android/server/art/model/DexoptResult.java b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
index 8dd75a119e..16bc282d55 100644
--- a/libartservice/service/java/com/android/server/art/model/DexoptResult.java
+++ b/libartservice/service/java/com/android/server/art/model/DexoptResult.java
@@ -75,6 +75,13 @@ public abstract class DexoptResult {
*/
public static final int EXTENDED_SKIPPED_NO_DEX_CODE = 1 << 1;
/**
+ * Dexopt is skipped because this operation is for Pre-reboot and the Pre-reboot artifacts
+ * already exist.
+ *
+ * @hide
+ */
+ public static final int EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST = 1 << 3;
+ /**
* Dexopt encountered errors when processing the profiles that are external to the device,
* including the profile in the DM file and the profile embedded in the dex container file.
* Details of the errors can be found in {@link
@@ -90,6 +97,7 @@ public abstract class DexoptResult {
@IntDef(flag = true, prefix = {"EXTENDED_"}, value = {
EXTENDED_SKIPPED_STORAGE_LOW,
EXTENDED_SKIPPED_NO_DEX_CODE,
+ EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST,
EXTENDED_BAD_EXTERNAL_PROFILE,
})
// clang-format on
@@ -180,6 +188,9 @@ public abstract class DexoptResult {
if ((flags & DexoptResult.EXTENDED_SKIPPED_NO_DEX_CODE) != 0) {
strs.add("EXTENDED_SKIPPED_NO_DEX_CODE");
}
+ if ((flags & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST) != 0) {
+ strs.add("EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST");
+ }
if ((flags & DexoptResult.EXTENDED_BAD_EXTERNAL_PROFILE) != 0) {
strs.add("EXTENDED_BAD_EXTERNAL_PROFILE");
}
diff --git a/libartservice/service/java/com/android/server/art/model/OperationProgress.java b/libartservice/service/java/com/android/server/art/model/OperationProgress.java
index a0a7ddc4fc..90186726ea 100644
--- a/libartservice/service/java/com/android/server/art/model/OperationProgress.java
+++ b/libartservice/service/java/com/android/server/art/model/OperationProgress.java
@@ -16,14 +16,21 @@
package com.android.server.art.model;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.SystemApi;
import com.android.internal.annotations.Immutable;
import com.google.auto.value.AutoValue;
-/** @hide */
+/**
+ * Represents the progress of an operation. Currently, this is only for batch dexopt.
+ *
+ * @hide
+ */
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
@Immutable
@AutoValue
@@ -32,8 +39,9 @@ public abstract class OperationProgress {
protected OperationProgress() {}
/** @hide */
- public static @NonNull OperationProgress create(int current, int total) {
- return new AutoValue_OperationProgress(current, total);
+ public static @NonNull OperationProgress create(
+ int current, int total, @Nullable PackageDexoptResult packageDexoptResult) {
+ return new AutoValue_OperationProgress(current, total, packageDexoptResult);
}
/** The overall progress, in the range of [0, 100]. */
@@ -59,4 +67,12 @@ public abstract class OperationProgress {
* @hide
*/
public abstract int getTotal();
+
+ /**
+ * The last package dexopt result, or null if {@link #getCurrent} returns 0. Only applicable if
+ * this class represents batch dexopt progress.
+ *
+ * @hide
+ */
+ public abstract @Nullable PackageDexoptResult getLastPackageDexoptResult();
}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java
index 44809b1c50..a30ca4ab74 100644
--- a/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootDriver.java
@@ -17,6 +17,8 @@
package com.android.server.art.prereboot;
import static com.android.server.art.IDexoptChrootSetup.CHROOT_DIR;
+import static com.android.server.art.prereboot.PreRebootManagerInterface.SystemRequirementException;
+import static com.android.server.art.proto.PreRebootStats.Status;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,22 +28,39 @@ import android.os.Build;
import android.os.CancellationSignal;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.system.ErrnoException;
+import android.system.Os;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.art.ArtJni;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.ArtModuleServiceInitializer;
+import com.android.server.art.ArtdRefCache;
import com.android.server.art.AsLog;
import com.android.server.art.GlobalInjector;
+import com.android.server.art.IArtd;
import com.android.server.art.IDexoptChrootSetup;
+import com.android.server.art.PreRebootDexoptJob;
import com.android.server.art.Utils;
import dalvik.system.DelegateLastClassLoader;
+import libcore.io.Streams;
+
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
/**
* Drives Pre-reboot Dexopt, through reflection.
*
+ * DO NOT use this class directly. Use {@link PreRebootDexoptJob}.
+ *
* During Pre-reboot Dexopt, the old version of this code is run.
*
* @hide
@@ -60,69 +79,128 @@ public class PreRebootDriver {
}
/**
- * Runs Pre-reboot Dexopt and returns whether it is successful.
+ * Runs Pre-reboot Dexopt and returns whether it is successful. Returns false if Pre-reboot
+ * dexopt failed, the system requirement check failed, or system requirements are not met.
*
* @param otaSlot The slot that contains the OTA update, "_a" or "_b", or null for a Mainline
* update.
+ * @param mapSnapshotsForOta Whether to map/unmap snapshots. Only applicable to an OTA update.
*/
- public boolean run(@Nullable String otaSlot, @NonNull CancellationSignal cancellationSignal) {
+ public boolean run(@Nullable String otaSlot, boolean mapSnapshotsForOta,
+ @NonNull CancellationSignal cancellationSignal) {
+ var statsReporter = new PreRebootStatsReporter();
+ boolean success = false;
+ boolean systemRequirementCheckFailed = false;
try {
- setUp(otaSlot);
+ statsReporter.recordJobStarted();
+ setUp(otaSlot, mapSnapshotsForOta);
runFromChroot(cancellationSignal);
+ success = true;
return true;
} catch (RemoteException e) {
Utils.logArtdException(e);
} catch (ServiceSpecificException e) {
AsLog.e("Failed to set up chroot", e);
+ } catch (SystemRequirementException e) {
+ systemRequirementCheckFailed = true;
+ AsLog.e("System requirement check failed", e);
} catch (ReflectiveOperationException e) {
- AsLog.e("Failed to run pre-reboot dexopt", e);
+ Throwable cause = e.getCause();
+ if (cause != null
+ && cause.getClass().getName().equals(
+ SystemRequirementException.class.getName())) {
+ // For future use only. Can't happen for now.
+ systemRequirementCheckFailed = true;
+ AsLog.e("System requirement check failed in chroot", cause);
+ } else {
+ AsLog.wtf("Failed to run Pre-reboot Dexopt", e);
+ }
+ } catch (IOException | ErrnoException e) {
+ AsLog.e("Failed to run Pre-reboot Dexopt", e);
} finally {
- tearDown();
- }
- return false;
- }
-
- private void setUp(@Nullable String otaSlot) throws RemoteException {
- mInjector.getDexoptChrootSetup().setUp(otaSlot);
- }
-
- private void tearDown() {
- // In general, the teardown unmounts apexes and partitions, and open files can keep the
- // mounts busy so that they cannot be unmounted. Therefore, two things can prevent the
- // teardown from succeeding: a running Pre-reboot artd process and the new `service-art.jar`
- // opened and mapped by system server. They are managed by the service manager and the
- // runtime respectively. There aren't reliable APIs to kill the former or close the latter,
- // so we have to do them by triggering GC and finalization, with sleep and retry mechanism.
- for (int numRetries = 3; numRetries > 0;) {
try {
- Runtime.getRuntime().gc();
- Runtime.getRuntime().runFinalization();
- // Wait for the service manager to shut down artd. The shutdown is asynchronous.
- Utils.sleep(5000);
- mInjector.getDexoptChrootSetup().tearDown();
- return;
+ // No need to pass `mapSnapshotsForOta` because `setUp` stores this information in a
+ // temp file.
+ tearDown();
} catch (RemoteException e) {
Utils.logArtdException(e);
- } catch (ServiceSpecificException e) {
+ } catch (ServiceSpecificException | IOException e) {
AsLog.e("Failed to tear down chroot", e);
- } catch (IllegalStateException e) {
- // Not expected, but we still want retries in such an extreme case.
- AsLog.wtf("Unexpected exception", e);
+ } finally {
+ statsReporter.recordJobEnded(success, systemRequirementCheckFailed);
}
+ }
+ return false;
+ }
- if (--numRetries > 0) {
- AsLog.i("Retrying....");
- Utils.sleep(30000);
+ public void test() {
+ boolean teardownAttempted = false;
+ try {
+ setUp(null /* otaSlot */, false /* mapSnapshotsForOta */);
+ // Ideally, we should try dexopting some packages here. However, it's not trivial to
+ // pass a package list into chroot. Besides, we need to generate boot images even if we
+ // dexopt only one package, and that can easily make the test fail the CTS quality
+ // requirement on test duration (<30s).
+ teardownAttempted = true;
+ tearDown();
+ } catch (SystemRequirementException e) {
+ throw new AssertionError("System requirement check failed", e);
+ } catch (RemoteException | IOException e) {
+ throw new AssertionError("Unexpected exception", e);
+ } finally {
+ if (!teardownAttempted) {
+ try {
+ tearDown();
+ } catch (RemoteException | IOException | RuntimeException e) {
+ // Do nothing.
+ }
}
}
}
+ private void setUp(@Nullable String otaSlot, boolean mapSnapshotsForOta)
+ throws RemoteException, SystemRequirementException {
+ mInjector.getDexoptChrootSetup().setUp(otaSlot, mapSnapshotsForOta);
+ if (!mInjector.getArtd().checkPreRebootSystemRequirements(CHROOT_DIR)) {
+ throw new SystemRequirementException("See logs for details");
+ }
+ mInjector.getDexoptChrootSetup().init();
+ }
+
+ private void tearDown() throws RemoteException, IOException {
+ // In general, the teardown unmounts apexes and partitions, and open files can keep the
+ // mounts busy so that they cannot be unmounted. Therefore, a running Pre-reboot artd
+ // process can prevent the teardown from succeeding. It's managed by the service manager,
+ // and there isn't a reliable API to kill it. We deal with it in two steps:
+ // 1. Trigger GC and finalization. The service manager should gracefully shut it down, since
+ // there is no reference to it as this point.
+ // 2. Call `ensureNoProcessInDir` to wait for it to exit. If it doesn't exit in 5 seconds,
+ // `ensureNoProcessInDir` will then kill it.
+ Runtime.getRuntime().gc();
+ Runtime.getRuntime().runFinalization();
+ // At this point, no process other than `artd` is expected to be running. `runFromChroot`
+ // blocks on `artd` calls, even upon cancellation, and `artd` in turn waits for child
+ // processes to exit, even if they are killed due to the cancellation.
+ ArtJni.ensureNoProcessInDir(CHROOT_DIR, 5000 /* timeoutMs */);
+ mInjector.getDexoptChrootSetup().tearDown();
+ }
+
private void runFromChroot(@NonNull CancellationSignal cancellationSignal)
- throws ReflectiveOperationException {
+ throws ReflectiveOperationException, IOException, ErrnoException {
String chrootArtDir = CHROOT_DIR + "/apex/com.android.art";
String dexPath = chrootArtDir + "/javalib/service-art.jar";
- var classLoader =
- new DelegateLastClassLoader(dexPath, this.getClass().getClassLoader() /* parent */);
+
+ // We load the dex file into the memory and close it. In this way, the classloader won't
+ // prevent unmounting even if it fails to unload.
+ ClassLoader classLoader;
+ FileDescriptor memfd = Os.memfd_create("in memory from " + dexPath, 0 /* flags */);
+ try (FileOutputStream out = new FileOutputStream(memfd);
+ InputStream in = new FileInputStream(dexPath)) {
+ Streams.copy(in, out);
+ classLoader = new DelegateLastClassLoader("/proc/self/fd/" + memfd.getInt$(),
+ this.getClass().getClassLoader() /* parent */);
+ }
+
Class<?> preRebootManagerClass =
classLoader.loadClass("com.android.server.art.prereboot.PreRebootManager");
// Check if the dex file is loaded successfully. Note that the constructor of
@@ -160,5 +238,10 @@ public class PreRebootDriver {
public IDexoptChrootSetup getDexoptChrootSetup() {
return GlobalInjector.getInstance().getDexoptChrootSetup();
}
+
+ @NonNull
+ public IArtd getArtd() {
+ return ArtdRefCache.getInstance().getArtd();
+ }
}
}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java
index 6d808ae12c..84c2b34a02 100644
--- a/libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootGlobalInjector.java
@@ -21,14 +21,17 @@ import android.annotation.Nullable;
import android.content.Context;
import android.os.ArtModuleServiceManager;
import android.os.Build;
+import android.os.CancellationSignal;
import android.os.RemoteException;
import androidx.annotation.RequiresApi;
import com.android.server.art.ArtdRefCache;
+import com.android.server.art.AsLog;
import com.android.server.art.DexUseManagerLocal;
import com.android.server.art.GlobalInjector;
import com.android.server.art.IArtd;
+import com.android.server.art.IArtdCancellationSignal;
import com.android.server.art.IDexoptChrootSetup;
import java.util.Objects;
@@ -50,15 +53,19 @@ public class PreRebootGlobalInjector extends GlobalInjector {
mArtModuleServiceManager = artModuleServiceManager;
}
- public static void init(
- @NonNull ArtModuleServiceManager artModuleServiceManager, @NonNull Context context) {
+ /** Returns true on success, or false on cancellation. Throws on failure. */
+ public static boolean init(@NonNull ArtModuleServiceManager artModuleServiceManager,
+ @NonNull Context context, @NonNull CancellationSignal cancellationSignal) {
var instance = new PreRebootGlobalInjector(artModuleServiceManager);
GlobalInjector.setInstance(instance);
+ // This call throws if artd cannot be initialized.
+ if (!instance.initArtd(cancellationSignal)) {
+ return false;
+ }
try (var pin = ArtdRefCache.getInstance().new Pin()) {
- // Fail early if artd cannot be initialized.
- ArtdRefCache.getInstance().getArtd();
instance.mDexUseManager = DexUseManagerLocal.createInstance(context);
}
+ return true;
}
@Override
@@ -69,16 +76,45 @@ public class PreRebootGlobalInjector extends GlobalInjector {
@Override
public void checkArtModuleServiceManager() {}
- @Override
@NonNull
- public IArtd getArtd() {
+ private IArtd getArtdNoInit() {
IArtd artd = IArtd.Stub.asInterface(
mArtModuleServiceManager.getArtdPreRebootServiceRegisterer().waitForService());
if (artd == null) {
throw new IllegalStateException("Unable to connect to artd for pre-reboot dexopt");
}
+ return artd;
+ }
+
+ private boolean initArtd(@NonNull CancellationSignal cancellationSignal) {
+ if (cancellationSignal.isCanceled()) {
+ return false;
+ }
+ try {
+ IArtd artd = getArtdNoInit();
+ IArtdCancellationSignal artdCancellationSignal = artd.createCancellationSignal();
+ cancellationSignal.setOnCancelListener(() -> {
+ try {
+ artdCancellationSignal.cancel();
+ } catch (RemoteException e) {
+ AsLog.e("An error occurred when sending a cancellation signal", e);
+ }
+ });
+ return artd.preRebootInit(artdCancellationSignal);
+ } catch (RemoteException e) {
+ throw new IllegalStateException("Unable to initialize artd for pre-reboot dexopt", e);
+ } finally {
+ // Make sure artd does not leak even if the caller holds `cancellationSignal` forever.
+ cancellationSignal.setOnCancelListener(null);
+ }
+ }
+
+ @Override
+ @NonNull
+ public IArtd getArtd() {
+ IArtd artd = getArtdNoInit();
try {
- artd.preRebootInit();
+ artd.preRebootInit(null /* cancellationSignal */);
} catch (RemoteException e) {
throw new IllegalStateException("Unable to initialize artd for pre-reboot dexopt", e);
}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java
index d6947ba156..d4c4e570e2 100644
--- a/libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManager.java
@@ -16,6 +16,9 @@
package com.android.server.art.prereboot;
+import static com.android.server.art.model.DexoptResult.PackageDexoptResult;
+import static com.android.server.art.proto.PreRebootStats.Status;
+
import android.annotation.NonNull;
import android.content.Context;
import android.os.ArtModuleServiceManager;
@@ -27,10 +30,22 @@ import androidx.annotation.RequiresApi;
import com.android.server.LocalManagerRegistry;
import com.android.server.art.ArtManagerLocal;
import com.android.server.art.ArtdRefCache;
+import com.android.server.art.AsLog;
import com.android.server.art.ReasonMapping;
+import com.android.server.art.Utils;
+import com.android.server.art.model.ArtFlags;
+import com.android.server.art.model.DexoptResult;
+import com.android.server.art.model.OperationProgress;
import com.android.server.pm.PackageManagerLocal;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* Implementation of {@link PreRebootManagerInterface}.
@@ -46,18 +61,75 @@ import java.util.Objects;
public class PreRebootManager implements PreRebootManagerInterface {
public void run(@NonNull ArtModuleServiceManager artModuleServiceManager,
@NonNull Context context, @NonNull CancellationSignal cancellationSignal) {
+ ExecutorService callbackExecutor = Executors.newSingleThreadExecutor();
try {
- PreRebootGlobalInjector.init(artModuleServiceManager, context);
+ if (!PreRebootGlobalInjector.init(
+ artModuleServiceManager, context, cancellationSignal)) {
+ return;
+ }
ArtManagerLocal artManagerLocal = new ArtManagerLocal(context);
PackageManagerLocal packageManagerLocal = Objects.requireNonNull(
LocalManagerRegistry.getManager(PackageManagerLocal.class));
+
+ var progressSession = new PreRebootStatsReporter().new ProgressSession();
+
+ // Contains four values: skipped, performed, failed, has pre-reboot artifacts.
+ List<Integer> values = new ArrayList(List.of(0, 0, 0, 0));
+
+ // Record every progress change right away, in case the job is interrupted by a reboot.
+ Consumer<OperationProgress> progressCallback = progress -> {
+ PackageDexoptResult result = progress.getLastPackageDexoptResult();
+ if (result == null) {
+ return;
+ }
+ switch (result.getStatus()) {
+ case DexoptResult.DEXOPT_SKIPPED:
+ if (hasExistingArtifacts(result)) {
+ values.set(1, values.get(1) + 1);
+ } else {
+ values.set(0, values.get(0) + 1);
+ }
+ break;
+ case DexoptResult.DEXOPT_PERFORMED:
+ values.set(1, values.get(1) + 1);
+ break;
+ case DexoptResult.DEXOPT_FAILED:
+ values.set(2, values.get(2) + 1);
+ break;
+ case DexoptResult.DEXOPT_CANCELLED:
+ break;
+ default:
+ throw new IllegalStateException("Unknown status: " + result.getStatus());
+ }
+ if (hasExistingArtifacts(result) || result.hasUpdatedArtifacts()) {
+ values.set(3, values.get(3) + 1);
+ }
+
+ progressSession.recordProgress(values.get(0), values.get(1), values.get(2),
+ progress.getTotal(), values.get(3));
+ };
+
try (var snapshot = packageManagerLocal.withFilteredSnapshot()) {
artManagerLocal.dexoptPackages(snapshot, ReasonMapping.REASON_PRE_REBOOT_DEXOPT,
- cancellationSignal, null /* processCallbackExecutor */,
- null /* processCallback */);
+ cancellationSignal, callbackExecutor,
+ Map.of(ArtFlags.PASS_MAIN, progressCallback));
}
} finally {
ArtdRefCache.getInstance().reset();
+ callbackExecutor.shutdown();
+ try {
+ // Make sure we have no running threads when we tear down.
+ callbackExecutor.awaitTermination(5, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ AsLog.wtf("Interrupted", e);
+ }
}
}
+
+ private boolean hasExistingArtifacts(@NonNull PackageDexoptResult result) {
+ return result.getDexContainerFileDexoptResults().stream().anyMatch(fileResult
+ -> (fileResult.getExtendedStatusFlags()
+ & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
+ != 0);
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java
index 8422debdbc..85f8b3f931 100644
--- a/libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootManagerInterface.java
@@ -42,5 +42,11 @@ import androidx.annotation.RequiresApi;
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public interface PreRebootManagerInterface {
void run(@NonNull ArtModuleServiceManager artModuleServiceManager, @NonNull Context context,
- @NonNull CancellationSignal cancellationSignal);
+ @NonNull CancellationSignal cancellationSignal) throws SystemRequirementException;
+
+ public static class SystemRequirementException extends Exception {
+ public SystemRequirementException(@NonNull String message) {
+ super(message);
+ }
+ }
}
diff --git a/libartservice/service/java/com/android/server/art/prereboot/PreRebootStatsReporter.java b/libartservice/service/java/com/android/server/art/prereboot/PreRebootStatsReporter.java
new file mode 100644
index 0000000000..b14965df21
--- /dev/null
+++ b/libartservice/service/java/com/android/server/art/prereboot/PreRebootStatsReporter.java
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package com.android.server.art.prereboot;
+
+import static com.android.server.art.proto.PreRebootStats.JobRun;
+import static com.android.server.art.proto.PreRebootStats.JobType;
+import static com.android.server.art.proto.PreRebootStats.Status;
+
+import android.annotation.NonNull;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalManagerRegistry;
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.ArtStatsLog;
+import com.android.server.art.ArtdRefCache;
+import com.android.server.art.AsLog;
+import com.android.server.art.ReasonMapping;
+import com.android.server.art.Utils;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.proto.PreRebootStats;
+import com.android.server.pm.PackageManagerLocal;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+
+/**
+ * A helper class to report the Pre-reboot Dexopt metrics to StatsD.
+ *
+ * This class is not thread-safe.
+ *
+ * During Pre-reboot Dexopt, both the old version and the new version of this code is run. The old
+ * version writes to disk first, and the new version writes to disk later. After reboot, the new
+ * version loads from disk.
+ *
+ * @hide
+ */
+@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+public class PreRebootStatsReporter {
+ private static final String FILENAME = "/data/system/pre-reboot-stats.pb";
+
+ @NonNull private final Injector mInjector;
+
+ public PreRebootStatsReporter() {
+ this(new Injector());
+ }
+
+ /** @hide */
+ @VisibleForTesting
+ public PreRebootStatsReporter(@NonNull Injector injector) {
+ mInjector = injector;
+ }
+
+ public void recordJobScheduled(boolean isAsync, boolean isOtaUpdate) {
+ PreRebootStats.Builder statsBuilder = PreRebootStats.newBuilder();
+ statsBuilder.setStatus(Status.STATUS_SCHEDULED)
+ .setJobType(isOtaUpdate ? JobType.JOB_TYPE_OTA : JobType.JOB_TYPE_MAINLINE);
+ // Omit job_scheduled_timestamp_millis to indicate a synchronous job.
+ if (isAsync) {
+ statsBuilder.setJobScheduledTimestampMillis(mInjector.getCurrentTimeMillis());
+ }
+ save(statsBuilder);
+ }
+
+ public void recordJobStarted() {
+ PreRebootStats.Builder statsBuilder = load();
+ if (statsBuilder.getStatus() == Status.STATUS_UNKNOWN) {
+ // Failed to load, the error is already logged.
+ return;
+ }
+
+ JobRun.Builder runBuilder =
+ JobRun.newBuilder().setJobStartedTimestampMillis(mInjector.getCurrentTimeMillis());
+ statsBuilder.setStatus(Status.STATUS_STARTED)
+ .addJobRuns(runBuilder)
+ .setSkippedPackageCount(0)
+ .setOptimizedPackageCount(0)
+ .setFailedPackageCount(0)
+ .setTotalPackageCount(0)
+ // Some packages may have artifacts from a previously cancelled job, but we count
+ // from scratch for simplicity.
+ .setPackagesWithArtifactsBeforeRebootCount(0);
+ save(statsBuilder);
+ }
+
+ public class ProgressSession {
+ private @NonNull PreRebootStats.Builder mStatsBuilder = load();
+
+ public void recordProgress(int skippedPackageCount, int optimizedPackageCount,
+ int failedPackageCount, int totalPackageCount,
+ int packagesWithArtifactsBeforeRebootCount) {
+ if (mStatsBuilder.getStatus() == Status.STATUS_UNKNOWN) {
+ // Failed to load, the error is already logged.
+ return;
+ }
+
+ mStatsBuilder.setSkippedPackageCount(skippedPackageCount)
+ .setOptimizedPackageCount(optimizedPackageCount)
+ .setFailedPackageCount(failedPackageCount)
+ .setTotalPackageCount(totalPackageCount)
+ .setPackagesWithArtifactsBeforeRebootCount(
+ packagesWithArtifactsBeforeRebootCount);
+ save(mStatsBuilder);
+ }
+ }
+
+ public void recordJobEnded(boolean success, boolean systemRequirementCheckFailed) {
+ PreRebootStats.Builder statsBuilder = load();
+ if (statsBuilder.getStatus() == Status.STATUS_UNKNOWN) {
+ // Failed to load, the error is already logged.
+ return;
+ }
+
+ List<JobRun> jobRuns = statsBuilder.getJobRunsList();
+ Utils.check(jobRuns.size() > 0);
+ JobRun lastRun = jobRuns.get(jobRuns.size() - 1);
+ Utils.check(lastRun.getJobEndedTimestampMillis() == 0);
+
+ JobRun.Builder runBuilder = JobRun.newBuilder(lastRun).setJobEndedTimestampMillis(
+ mInjector.getCurrentTimeMillis());
+
+ Status status;
+ if (success) {
+ // The job is cancelled if it hasn't done package scanning (total package count is 0),
+ // or it's interrupted in the middle of package processing (package counts don't add up
+ // to the total).
+ // TODO(b/336239721): Move this logic to the server.
+ if (statsBuilder.getTotalPackageCount() > 0
+ && (statsBuilder.getOptimizedPackageCount()
+ + statsBuilder.getFailedPackageCount()
+ + statsBuilder.getSkippedPackageCount())
+ == statsBuilder.getTotalPackageCount()) {
+ status = Status.STATUS_FINISHED;
+ } else {
+ status = Status.STATUS_CANCELLED;
+ }
+ } else {
+ if (systemRequirementCheckFailed) {
+ status = Status.STATUS_ABORTED_SYSTEM_REQUIREMENTS;
+ } else {
+ status = Status.STATUS_FAILED;
+ }
+ }
+
+ statsBuilder.setStatus(status).setJobRuns(jobRuns.size() - 1, runBuilder);
+ save(statsBuilder);
+ }
+
+ public class AfterRebootSession {
+ private @NonNull Set<String> mPackagesWithArtifacts = new HashSet<>();
+
+ public void recordPackageWithArtifacts(@NonNull String packageName) {
+ mPackagesWithArtifacts.add(packageName);
+ }
+
+ public void reportAsync() {
+ new CompletableFuture().runAsync(this::report).exceptionally(t -> {
+ AsLog.e("Failed to report stats", t);
+ return null;
+ });
+ }
+
+ @VisibleForTesting
+ public void report() {
+ PreRebootStats.Builder statsBuilder = load();
+ delete();
+
+ if (statsBuilder.getStatus() == Status.STATUS_UNKNOWN) {
+ // Job not scheduled, probably because Pre-reboot Dexopt is not enabled.
+ return;
+ }
+
+ ArtManagerLocal artManagerLocal = mInjector.getArtManagerLocal();
+
+ // This takes some time (~3ms per package). It probably fine because we are running
+ // asynchronously. Consider removing this in the future.
+ int packagesWithArtifactsUsableCount;
+ try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot();
+ var pin = mInjector.createArtdPin()) {
+ packagesWithArtifactsUsableCount =
+ (int) mPackagesWithArtifacts.stream()
+ .map(packageName
+ -> artManagerLocal.getDexoptStatus(snapshot, packageName))
+ .filter(status -> hasUsablePreRebootArtifacts(status))
+ .count();
+ }
+
+ List<JobRun> jobRuns = statsBuilder.getJobRunsList();
+ // The total duration of all runs, or -1 if any run didn't end.
+ long jobDurationMs = 0;
+ for (JobRun run : jobRuns) {
+ if (run.getJobEndedTimestampMillis() == 0) {
+ jobDurationMs = -1;
+ break;
+ }
+ jobDurationMs +=
+ run.getJobEndedTimestampMillis() - run.getJobStartedTimestampMillis();
+ }
+ if (jobRuns.size() == 0) {
+ jobDurationMs = -1;
+ }
+ long jobLatencyMs =
+ (jobRuns.size() > 0 && statsBuilder.getJobScheduledTimestampMillis() > 0)
+ ? (jobRuns.get(0).getJobStartedTimestampMillis()
+ - statsBuilder.getJobScheduledTimestampMillis())
+ : -1;
+
+ mInjector.writeStats(ArtStatsLog.PREREBOOT_DEXOPT_JOB_ENDED,
+ getStatusForStatsd(statsBuilder.getStatus()),
+ statsBuilder.getOptimizedPackageCount(), statsBuilder.getFailedPackageCount(),
+ statsBuilder.getSkippedPackageCount(), statsBuilder.getTotalPackageCount(),
+ jobDurationMs, jobLatencyMs, mPackagesWithArtifacts.size(),
+ packagesWithArtifactsUsableCount, jobRuns.size(),
+ statsBuilder.getPackagesWithArtifactsBeforeRebootCount(),
+ getJobTypeForStatsd(statsBuilder.getJobType()));
+ }
+ }
+
+ private int getStatusForStatsd(@NonNull Status status) {
+ switch (status) {
+ case STATUS_UNKNOWN:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_UNKNOWN;
+ case STATUS_SCHEDULED:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_SCHEDULED;
+ case STATUS_STARTED:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_STARTED;
+ case STATUS_FINISHED:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_FINISHED;
+ case STATUS_FAILED:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_FAILED;
+ case STATUS_CANCELLED:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_CANCELLED;
+ case STATUS_ABORTED_SYSTEM_REQUIREMENTS:
+ return ArtStatsLog
+ .PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORTED_SYSTEM_REQUIREMENTS;
+ default:
+ throw new IllegalStateException("Unknown status: " + status.getNumber());
+ }
+ }
+
+ private int getJobTypeForStatsd(@NonNull JobType jobType) {
+ switch (jobType) {
+ case JOB_TYPE_UNKNOWN:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_UNKNOWN;
+ case JOB_TYPE_OTA:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_OTA;
+ case JOB_TYPE_MAINLINE:
+ return ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_MAINLINE;
+ default:
+ throw new IllegalStateException("Unknown job type: " + jobType.getNumber());
+ }
+ }
+
+ private boolean hasUsablePreRebootArtifacts(@NonNull DexoptStatus status) {
+ // For simplicity, we consider all artifacts of a package usable if we see at least one
+ // `REASON_PRE_REBOOT_DEXOPT` because it's not easy to know which files are committed.
+ return status.getDexContainerFileDexoptStatuses().stream().anyMatch(fileStatus
+ -> fileStatus.getCompilationReason().equals(
+ ReasonMapping.REASON_PRE_REBOOT_DEXOPT));
+ }
+
+ @NonNull
+ private PreRebootStats.Builder load() {
+ PreRebootStats.Builder statsBuilder = PreRebootStats.newBuilder();
+ try (InputStream in = new FileInputStream(mInjector.getFilename())) {
+ statsBuilder.mergeFrom(in);
+ } catch (IOException e) {
+ // Nothing else we can do but to start from scratch.
+ AsLog.e("Failed to load pre-reboot stats", e);
+ }
+ return statsBuilder;
+ }
+
+ private void save(@NonNull PreRebootStats.Builder statsBuilder) {
+ var file = new File(mInjector.getFilename());
+ File tempFile = null;
+ try {
+ tempFile = File.createTempFile(file.getName(), null /* suffix */, file.getParentFile());
+ try (OutputStream out = new FileOutputStream(tempFile.getPath())) {
+ statsBuilder.build().writeTo(out);
+ }
+ Files.move(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING,
+ StandardCopyOption.ATOMIC_MOVE);
+ } catch (IOException e) {
+ AsLog.e("Failed to save pre-reboot stats", e);
+ } finally {
+ Utils.deleteIfExistsSafe(tempFile);
+ }
+ }
+
+ public void delete() {
+ Utils.deleteIfExistsSafe(new File(mInjector.getFilename()));
+ }
+
+ /**
+ * Injector pattern for testing purpose.
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public static class Injector {
+ @NonNull
+ public String getFilename() {
+ return FILENAME;
+ }
+
+ public long getCurrentTimeMillis() {
+ return System.currentTimeMillis();
+ }
+
+ @NonNull
+ public PackageManagerLocal getPackageManagerLocal() {
+ return Objects.requireNonNull(
+ LocalManagerRegistry.getManager(PackageManagerLocal.class));
+ }
+
+ @NonNull
+ public ArtManagerLocal getArtManagerLocal() {
+ return Objects.requireNonNull(LocalManagerRegistry.getManager(ArtManagerLocal.class));
+ }
+
+ @NonNull
+ public ArtdRefCache.Pin createArtdPin() {
+ return ArtdRefCache.getInstance().new Pin();
+ }
+
+ // Wrap the static void method to make it easier to mock. There is no good way to mock a
+ // method that is both void and static, due to the poor design of Mockito API.
+ public void writeStats(int code, int status, int optimizedPackageCount,
+ int failedPackageCount, int skippedPackageCount, int totalPackageCount,
+ long jobDurationMillis, long jobLatencyMillis,
+ int packagesWithArtifactsAfterRebootCount,
+ int packagesWithArtifactsUsableAfterRebootCount, int jobRunCount,
+ int packagesWithArtifactsBeforeRebootCount, int jobType) {
+ ArtStatsLog.write(code, status, optimizedPackageCount, failedPackageCount,
+ skippedPackageCount, totalPackageCount, jobDurationMillis, jobLatencyMillis,
+ packagesWithArtifactsAfterRebootCount,
+ packagesWithArtifactsUsableAfterRebootCount, jobRunCount,
+ packagesWithArtifactsBeforeRebootCount, jobType);
+ }
+ }
+}
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index 1fd7e4bcfc..01e706f0a4 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -70,6 +70,7 @@ import com.android.server.art.model.DeleteResult;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.prereboot.PreRebootStatsReporter;
import com.android.server.art.proto.DexMetadataConfig;
import com.android.server.art.testing.StaticMockitoRule;
import com.android.server.art.testing.TestingUtils;
@@ -138,6 +139,7 @@ public class ArtManagerLocalTest {
@Mock private DexMetadataHelper.Injector mDexMetadataHelperInjector;
@Mock private Context mContext;
@Mock private PreRebootDexoptJob mPreRebootDexoptJob;
+ @Mock private PreRebootStatsReporter.Injector mPreRebootStatsReporterInjector;
private PackageState mPkgState1;
private AndroidPackage mPkg1;
private CheckedSecondaryDexInfo mPkg1SecondaryDexInfo1;
@@ -182,6 +184,10 @@ public class ArtManagerLocalTest {
lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper);
lenient().when(mInjector.getContext()).thenReturn(mContext);
lenient().when(mInjector.getPreRebootDexoptJob()).thenReturn(mPreRebootDexoptJob);
+ lenient()
+ .when(mInjector.getPreRebootStatsReporter())
+ .thenAnswer(
+ invocation -> new PreRebootStatsReporter(mPreRebootStatsReporterInjector));
lenient().when(mArtFileManagerInjector.getArtd()).thenReturn(mArtd);
lenient().when(mArtFileManagerInjector.getUserManager()).thenReturn(mUserManager);
@@ -284,6 +290,12 @@ public class ArtManagerLocalTest {
.when(mContext.registerReceiver(mBroadcastReceiverCaptor.capture(), any()))
.thenReturn(mock(Intent.class));
+ File tempFile = File.createTempFile("pre-reboot-stats", ".pb");
+ tempFile.deleteOnExit();
+ lenient()
+ .when(mPreRebootStatsReporterInjector.getFilename())
+ .thenReturn(tempFile.getAbsolutePath());
+
mArtManagerLocal = new ArtManagerLocal(mInjector);
}
diff --git a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
index 527203b5a3..8fb46b6225 100644
--- a/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexoptHelperTest.java
@@ -76,6 +76,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
@SmallTest
@RunWith(AndroidJUnit4.class)
@@ -670,15 +671,26 @@ public class DexoptHelperTest {
progressCallbackExecutor.runAll();
+ List<DexContainerFileDexoptResult> fileResults =
+ Stream.concat(mPrimaryResults.stream(), mSecondaryResults.stream())
+ .collect(Collectors.toList());
+
InOrder inOrder = inOrder(progressCallback);
inOrder.verify(progressCallback)
- .accept(eq(OperationProgress.create(0 /* current */, 3 /* total */)));
+ .accept(eq(OperationProgress.create(
+ 0 /* current */, 3 /* total */, null /* packageDexoptResult */)));
inOrder.verify(progressCallback)
- .accept(eq(OperationProgress.create(1 /* current */, 3 /* total */)));
+ .accept(eq(OperationProgress.create(1 /* current */, 3 /* total */,
+ PackageDexoptResult.create(
+ PKG_NAME_FOO, fileResults, null /* packageLevelStatus */))));
inOrder.verify(progressCallback)
- .accept(eq(OperationProgress.create(2 /* current */, 3 /* total */)));
+ .accept(eq(OperationProgress.create(2 /* current */, 3 /* total */,
+ PackageDexoptResult.create(
+ PKG_NAME_BAR, fileResults, null /* packageLevelStatus */))));
inOrder.verify(progressCallback)
- .accept(eq(OperationProgress.create(3 /* current */, 3 /* total */)));
+ .accept(eq(OperationProgress.create(3 /* current */, 3 /* total */,
+ PackageDexoptResult.create(
+ PKG_NAME_LIBBAZ, fileResults, null /* packageLevelStatus */))));
}
private AndroidPackage createPackage(boolean multiSplit) {
diff --git a/libartservice/service/javatests/com/android/server/art/PreRebootDexoptJobTest.java b/libartservice/service/javatests/com/android/server/art/PreRebootDexoptJobTest.java
index c555879792..eca19160a8 100644
--- a/libartservice/service/javatests/com/android/server/art/PreRebootDexoptJobTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PreRebootDexoptJobTest.java
@@ -22,15 +22,17 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.job.JobInfo;
+import android.app.job.JobParameters;
import android.app.job.JobScheduler;
import android.os.CancellationSignal;
import android.os.SystemProperties;
@@ -40,18 +42,18 @@ import androidx.test.filters.SmallTest;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.prereboot.PreRebootDriver;
+import com.android.server.art.prereboot.PreRebootStatsReporter;
import com.android.server.art.testing.StaticMockitoRule;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import java.io.File;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.Future;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -62,12 +64,16 @@ public class PreRebootDexoptJobTest {
@Rule
public StaticMockitoRule mockitoRule = new StaticMockitoRule(
- SystemProperties.class, BackgroundDexoptJobService.class, DeviceConfig.class);
+ SystemProperties.class, BackgroundDexoptJobService.class, ArtJni.class);
@Mock private PreRebootDexoptJob.Injector mInjector;
@Mock private JobScheduler mJobScheduler;
@Mock private PreRebootDriver mPreRebootDriver;
+ @Mock private BackgroundDexoptJobService mJobService;
+ @Mock private PreRebootStatsReporter.Injector mPreRebootStatsReporterInjector;
private PreRebootDexoptJob mPreRebootDexoptJob;
+ private JobInfo mJobInfo;
+ private JobParameters mJobParameters;
@Before
public void setUp() throws Exception {
@@ -79,12 +85,48 @@ public class PreRebootDexoptJobTest {
.when(SystemProperties.getBoolean(eq("dalvik.vm.enable_pr_dexopt"), anyBoolean()))
.thenReturn(true);
lenient()
- .when(DeviceConfig.getBoolean(
+ .when(mInjector.getDeviceConfigBoolean(
eq(DeviceConfig.NAMESPACE_RUNTIME), eq("enable_pr_dexopt"), anyBoolean()))
.thenReturn(false);
+ lenient()
+ .when(SystemProperties.getBoolean(
+ eq("dalvik.vm.pre-reboot.has-started"), anyBoolean()))
+ .thenReturn(false);
+
lenient().when(mInjector.getJobScheduler()).thenReturn(mJobScheduler);
lenient().when(mInjector.getPreRebootDriver()).thenReturn(mPreRebootDriver);
+ lenient()
+ .when(mInjector.getStatsReporter())
+ .thenAnswer(
+ invocation -> new PreRebootStatsReporter(mPreRebootStatsReporterInjector));
+
+ File tempFile = File.createTempFile("pre-reboot-stats", ".pb");
+ tempFile.deleteOnExit();
+ lenient()
+ .when(mPreRebootStatsReporterInjector.getFilename())
+ .thenReturn(tempFile.getAbsolutePath());
+
+ lenient().when(mJobScheduler.schedule(any())).thenAnswer(invocation -> {
+ mJobInfo = invocation.<JobInfo>getArgument(0);
+ mJobParameters = mock(JobParameters.class);
+ assertThat(mJobInfo.getId()).isEqualTo(JOB_ID);
+ lenient().when(mJobParameters.getExtras()).thenReturn(mJobInfo.getExtras());
+ return JobScheduler.RESULT_SUCCESS;
+ });
+
+ lenient()
+ .doAnswer(invocation -> {
+ mJobInfo = null;
+ mJobParameters = null;
+ return null;
+ })
+ .when(mJobScheduler)
+ .cancel(JOB_ID);
+
+ lenient().when(mJobScheduler.getPendingJob(JOB_ID)).thenAnswer(invocation -> {
+ return mJobInfo;
+ });
mPreRebootDexoptJob = new PreRebootDexoptJob(mInjector);
lenient().when(BackgroundDexoptJobService.getJob(JOB_ID)).thenReturn(mPreRebootDexoptJob);
@@ -92,16 +134,13 @@ public class PreRebootDexoptJobTest {
@Test
public void testSchedule() throws Exception {
- var captor = ArgumentCaptor.forClass(JobInfo.class);
- when(mJobScheduler.schedule(captor.capture())).thenReturn(JobScheduler.RESULT_SUCCESS);
+ assertThat(mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */))
+ .isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
- assertThat(mPreRebootDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
-
- JobInfo jobInfo = captor.getValue();
- assertThat(jobInfo.isPeriodic()).isFalse();
- assertThat(jobInfo.isRequireDeviceIdle()).isTrue();
- assertThat(jobInfo.isRequireCharging()).isTrue();
- assertThat(jobInfo.isRequireBatteryNotLow()).isTrue();
+ assertThat(mJobInfo.isPeriodic()).isFalse();
+ assertThat(mJobInfo.isRequireDeviceIdle()).isTrue();
+ assertThat(mJobInfo.isRequireCharging()).isTrue();
+ assertThat(mJobInfo.isRequireBatteryNotLow()).isTrue();
}
@Test
@@ -109,155 +148,349 @@ public class PreRebootDexoptJobTest {
when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
.thenReturn(true);
- assertThat(mPreRebootDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP);
+ assertThat(mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */))
+ .isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP);
verify(mJobScheduler, never()).schedule(any());
}
@Test
+ public void testSyncStartDisabled() throws Exception {
+ when(SystemProperties.getBoolean(eq("pm.dexopt.disable_bg_dexopt"), anyBoolean()))
+ .thenReturn(true);
+
+ CompletableFuture<Void> future =
+ mPreRebootDexoptJob.onUpdateReadyStartNow(null /* otaSlot */);
+
+ assertThat(future).isNull();
+ verify(mPreRebootDriver, never()).run(any(), anyBoolean(), any());
+ }
+
+ @Test
public void testScheduleNotEnabled() {
when(SystemProperties.getBoolean(eq("dalvik.vm.enable_pr_dexopt"), anyBoolean()))
.thenReturn(false);
- assertThat(mPreRebootDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP);
+ assertThat(mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */))
+ .isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP);
verify(mJobScheduler, never()).schedule(any());
}
@Test
+ public void testSyncStartNotEnabled() throws Exception {
+ when(SystemProperties.getBoolean(eq("dalvik.vm.enable_pr_dexopt"), anyBoolean()))
+ .thenReturn(false);
+
+ CompletableFuture<Void> future =
+ mPreRebootDexoptJob.onUpdateReadyStartNow(null /* otaSlot */);
+
+ assertThat(future).isNull();
+ verify(mPreRebootDriver, never()).run(any(), anyBoolean(), any());
+ }
+
+ @Test
public void testScheduleEnabledByPhenotypeFlag() {
lenient()
.when(SystemProperties.getBoolean(eq("dalvik.vm.enable_pr_dexopt"), anyBoolean()))
.thenReturn(false);
lenient()
- .when(DeviceConfig.getBoolean(
+ .when(mInjector.getDeviceConfigBoolean(
eq(DeviceConfig.NAMESPACE_RUNTIME), eq("enable_pr_dexopt"), anyBoolean()))
.thenReturn(true);
- when(mJobScheduler.schedule(any())).thenReturn(JobScheduler.RESULT_SUCCESS);
- assertThat(mPreRebootDexoptJob.schedule()).isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
+ assertThat(mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */))
+ .isEqualTo(ArtFlags.SCHEDULE_SUCCESS);
verify(mJobScheduler).schedule(any());
}
@Test
+ public void testScheduleForceDisabledByPhenotypeFlag() {
+ lenient()
+ .when(SystemProperties.getBoolean(eq("dalvik.vm.enable_pr_dexopt"), anyBoolean()))
+ .thenReturn(true);
+ lenient()
+ .when(mInjector.getDeviceConfigBoolean(
+ eq(DeviceConfig.NAMESPACE_RUNTIME), eq("enable_pr_dexopt"), anyBoolean()))
+ .thenReturn(true);
+ when(mInjector.getDeviceConfigBoolean(eq(DeviceConfig.NAMESPACE_RUNTIME),
+ eq("force_disable_pr_dexopt"), anyBoolean()))
+ .thenReturn(true);
+
+ assertThat(mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */))
+ .isEqualTo(ArtFlags.SCHEDULE_DISABLED_BY_SYSPROP);
+
+ verify(mJobScheduler, never()).schedule(any());
+ }
+
+ @Test
public void testUnschedule() {
- mPreRebootDexoptJob.unschedule();
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
verify(mJobScheduler).cancel(JOB_ID);
}
@Test
- public void testStart() {
- when(mPreRebootDriver.run(any(), any())).thenReturn(true);
+ public void testStart() throws Exception {
+ var jobStarted = new Semaphore(0);
+ when(mPreRebootDriver.run(any(), eq(true) /* mapSnapshotsForOta */, any()))
+ .thenAnswer(invocation -> {
+ jobStarted.release();
+ return true;
+ });
+
+ when(ArtJni.setProperty("dalvik.vm.pre-reboot.has-started", "true"))
+ .thenAnswer(invocation -> {
+ when(SystemProperties.getBoolean(
+ eq("dalvik.vm.pre-reboot.has-started"), anyBoolean()))
+ .thenReturn(true);
+ return null;
+ });
assertThat(mPreRebootDexoptJob.hasStarted()).isFalse();
- Future<Void> future = mPreRebootDexoptJob.start();
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+ assertThat(jobStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
assertThat(mPreRebootDexoptJob.hasStarted()).isTrue();
- Utils.getFuture(future);
+ mPreRebootDexoptJob.waitForRunningJob();
}
@Test
- public void testStartAlreadyRunning() {
- Semaphore dexoptDone = new Semaphore(0);
- when(mPreRebootDriver.run(any(), any())).thenAnswer(invocation -> {
- assertThat(dexoptDone.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
- return true;
- });
-
- Future<Void> future1 = mPreRebootDexoptJob.start();
- Future<Void> future2 = mPreRebootDexoptJob.start();
- assertThat(future1).isSameInstanceAs(future2);
+ public void testSyncStart() throws Exception {
+ when(mPreRebootDriver.run(any(), eq(false) /* mapSnapshotsForOta */, any()))
+ .thenReturn(true);
- dexoptDone.release();
- Utils.getFuture(future1);
+ CompletableFuture<Void> future =
+ mPreRebootDexoptJob.onUpdateReadyStartNow(null /* otaSlot */);
- verify(mPreRebootDriver, times(1)).run(any(), any());
+ Utils.getFuture(future);
}
@Test
- public void testStartAnother() {
- when(mPreRebootDriver.run(any(), any())).thenReturn(true);
-
- Future<Void> future1 = mPreRebootDexoptJob.start();
- Utils.getFuture(future1);
- Future<Void> future2 = mPreRebootDexoptJob.start();
- Utils.getFuture(future2);
- assertThat(future1).isNotSameInstanceAs(future2);
+ public void testCancel() {
+ Semaphore dexoptCancelled = new Semaphore(0);
+ Semaphore jobExited = new Semaphore(0);
+ when(mPreRebootDriver.run(any(), anyBoolean(), any())).thenAnswer(invocation -> {
+ var cancellationSignal = invocation.<CancellationSignal>getArgument(2);
+ cancellationSignal.setOnCancelListener(() -> dexoptCancelled.release());
+ assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+ jobExited.release();
+ return true;
+ });
+
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+ mPreRebootDexoptJob.onStopJob(mJobParameters);
+
+ // Check that `onStopJob` is really blocking. If it wasn't, the check below might still pass
+ // due to a race, but we would have a flaky test.
+ assertThat(jobExited.tryAcquire()).isTrue();
}
@Test
- public void testCancel() {
+ public void testSyncCancel() throws Exception {
Semaphore dexoptCancelled = new Semaphore(0);
Semaphore jobExited = new Semaphore(0);
- when(mPreRebootDriver.run(any(), any())).thenAnswer(invocation -> {
+ when(mPreRebootDriver.run(any(), anyBoolean(), any())).thenAnswer(invocation -> {
+ var cancellationSignal = invocation.<CancellationSignal>getArgument(2);
+ cancellationSignal.setOnCancelListener(() -> dexoptCancelled.release());
assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
- var cancellationSignal = invocation.<CancellationSignal>getArgument(1);
- assertThat(cancellationSignal.isCanceled()).isTrue();
jobExited.release();
return true;
});
- var unused = mPreRebootDexoptJob.start();
- Future<Void> future = new CompletableFuture().runAsync(() -> {
- mPreRebootDexoptJob.cancel(false /* blocking */);
- dexoptCancelled.release();
- mPreRebootDexoptJob.cancel(true /* blocking */);
- });
- Utils.getFuture(future);
- // Check that `cancel(true)` is really blocking. If it wasn't, the check below might still
+ CompletableFuture<Void> future =
+ mPreRebootDexoptJob.onUpdateReadyStartNow(null /* otaSlot */);
+ mPreRebootDexoptJob.cancelGiven(future, false /* expectInterrupt */);
+
+ // Check that `cancelGiven` is really blocking. If it wasn't, the check below might still
// pass due to a race, but we would have a flaky test.
assertThat(jobExited.tryAcquire()).isTrue();
}
@Test
public void testUpdateOtaSlotOtaThenMainline() {
- mPreRebootDexoptJob.updateOtaSlot("_b");
- mPreRebootDexoptJob.updateOtaSlot(null);
+ mPreRebootDexoptJob.onUpdateReady("_b" /* otaSlot */);
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
- when(mPreRebootDriver.run(eq("_b"), any())).thenReturn(true);
+ when(mPreRebootDriver.run(eq("_b"), anyBoolean(), any())).thenReturn(true);
- Utils.getFuture(mPreRebootDexoptJob.start());
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+ mPreRebootDexoptJob.waitForRunningJob();
}
@Test
public void testUpdateOtaSlotMainlineThenOta() {
- mPreRebootDexoptJob.updateOtaSlot(null);
- mPreRebootDexoptJob.updateOtaSlot("_a");
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+ mPreRebootDexoptJob.onUpdateReady("_a" /* otaSlot */);
- when(mPreRebootDriver.run(eq("_a"), any())).thenReturn(true);
+ when(mPreRebootDriver.run(eq("_a"), anyBoolean(), any())).thenReturn(true);
- Utils.getFuture(mPreRebootDexoptJob.start());
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+ mPreRebootDexoptJob.waitForRunningJob();
}
@Test
public void testUpdateOtaSlotMainlineThenMainline() {
- mPreRebootDexoptJob.updateOtaSlot(null);
- mPreRebootDexoptJob.updateOtaSlot(null);
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
- when(mPreRebootDriver.run(isNull(), any())).thenReturn(true);
+ when(mPreRebootDriver.run(isNull(), anyBoolean(), any())).thenReturn(true);
- Utils.getFuture(mPreRebootDexoptJob.start());
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+ mPreRebootDexoptJob.waitForRunningJob();
}
@Test
public void testUpdateOtaSlotOtaThenOta() {
- mPreRebootDexoptJob.updateOtaSlot("_b");
- mPreRebootDexoptJob.updateOtaSlot("_b");
+ mPreRebootDexoptJob.onUpdateReady("_b" /* otaSlot */);
+ mPreRebootDexoptJob.onUpdateReady("_b" /* otaSlot */);
- when(mPreRebootDriver.run(eq("_b"), any())).thenReturn(true);
+ when(mPreRebootDriver.run(eq("_b"), anyBoolean(), any())).thenReturn(true);
- Utils.getFuture(mPreRebootDexoptJob.start());
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+ mPreRebootDexoptJob.waitForRunningJob();
}
@Test(expected = IllegalStateException.class)
public void testUpdateOtaSlotOtaThenOtaDifferentSlots() {
- mPreRebootDexoptJob.updateOtaSlot("_b");
- mPreRebootDexoptJob.updateOtaSlot("_a");
+ mPreRebootDexoptJob.onUpdateReady("_b" /* otaSlot */);
+ mPreRebootDexoptJob.onUpdateReady("_a" /* otaSlot */);
}
@Test(expected = IllegalStateException.class)
public void testUpdateOtaSlotOtaBogusSlot() {
- mPreRebootDexoptJob.updateOtaSlot("_bogus");
+ mPreRebootDexoptJob.onUpdateReady("_bogus" /* otaSlot */);
+ }
+
+ /**
+ * Verifies that `jobFinished` is not mistakenly called for an old job after a new job is
+ * started.
+ */
+ @Test
+ public void testRace1() throws Exception {
+ var jobBlocker = new Semaphore(0);
+
+ when(mPreRebootDriver.run(any(), anyBoolean(), any())).thenAnswer(invocation -> {
+ // Simulate that the job takes a while to exit, no matter it's cancelled or not.
+ assertThat(jobBlocker.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+ return true;
+ });
+
+ // An update arrives. A job is scheduled.
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+
+ // The job scheduler starts the job.
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+
+ var jobFinishedCalledAfterNewJobStarted = new Semaphore(0);
+
+ var thread = new Thread(() -> {
+ // Another update arrives. A new job is scheduled, replacing the old job. The old job
+ // doesn't exit immediately, so this call is blocked.
+ JobParameters oldParameters = mJobParameters;
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+
+ // The job scheduler tries to cancel the old job because of the new update. This call
+ // doesn't matter because the job has already been cancelled by ourselves during the
+ // `onUpdateReady` call above.
+ mPreRebootDexoptJob.onStopJob(oldParameters);
+
+ // The job scheduler starts the new job.
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+
+ doAnswer(invocation -> {
+ jobFinishedCalledAfterNewJobStarted.release();
+ return null;
+ })
+ .when(mJobService)
+ .jobFinished(any(), anyBoolean());
+ });
+ thread.start();
+
+ // Wait a while for `thread` to block on waiting for the old job to exit.
+ Utils.sleep(200);
+
+ // The old job now exits, unblocking `thread`.
+ jobBlocker.release();
+ thread.join();
+
+ // Give it 1s for `jobFinished` to be potentially called. Either `jobFinished` is called
+ // before the new job is started, or it should not be called.
+ assertThat(jobFinishedCalledAfterNewJobStarted.tryAcquire(1, TimeUnit.SECONDS)).isFalse();
+
+ // The new job now exits.
+ jobBlocker.release();
+
+ // `jobFinished` is called for the new job.
+ assertThat(jobFinishedCalledAfterNewJobStarted.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS))
+ .isTrue();
+ }
+
+ /** Verifies that `onStartJob` for an old job is ignored after the old job is unscheduled. */
+ @Test
+ public void testRace2() throws Exception {
+ // An update arrives. A job is scheduled.
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+ JobParameters oldParameters = mJobParameters;
+
+ // The job scheduler starts the job. In the meantime, another update arrives. It's not
+ // possible that `onStartJob` is called for the old job after `onUpdateReady` is called
+ // because `onUpdateReady` unschedules the old job. However, since both calls acquire a
+ // lock, the order of execution may be reversed. When this happens, the `onStartJob` request
+ // should not succeed.
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+ assertThat(mPreRebootDexoptJob.onStartJob(mJobService, oldParameters)).isFalse();
+
+ // The job scheduler starts the new job. This request should succeed.
+ assertThat(mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters)).isTrue();
+ }
+
+ /**
+ * Verifies that `onStopJob` for an old job is ignored after a new synchronous job is started.
+ */
+ @Test
+ public void testRace3() throws Exception {
+ Semaphore dexoptCancelled = new Semaphore(0);
+ Semaphore jobExited = new Semaphore(0);
+ when(mPreRebootDriver.run(any(), anyBoolean(), any())).thenAnswer(invocation -> {
+ var cancellationSignal = invocation.<CancellationSignal>getArgument(2);
+ cancellationSignal.setOnCancelListener(() -> dexoptCancelled.release());
+ assertThat(dexoptCancelled.tryAcquire(TIMEOUT_SEC, TimeUnit.SECONDS)).isTrue();
+ jobExited.release();
+ return true;
+ });
+
+ // An update arrives. A job is scheduled.
+ mPreRebootDexoptJob.onUpdateReady(null /* otaSlot */);
+
+ // The job scheduler starts the job.
+ mPreRebootDexoptJob.onStartJob(mJobService, mJobParameters);
+
+ // Another update arrives, requesting a synchronous job run, replacing the old job. The new
+ // job, which is synchronous, is started right after the old job is cancelled by
+ // `onUpdateReadyStartNow`, before the job scheduler calls `onStartJob`.
+ JobParameters oldParameters = mJobParameters;
+ CompletableFuture<Void> future =
+ mPreRebootDexoptJob.onUpdateReadyStartNow(null /* otaSlot */);
+
+ // The old job should be cancelled at this point.
+ // This cannot be the new job having exited because jobs are serialized.
+ assertThat(jobExited.tryAcquire()).isTrue();
+
+ // The `onStopJob` call finally arrives. This call should be a no-op because the job has
+ // already been cancelled by ourselves during the `onUpdateReadyStartNow` call above. It
+ // should not cancel the new job.
+ mPreRebootDexoptJob.onStopJob(oldParameters);
+
+ // The new job should not be cancelled.
+ assertThat(jobExited.tryAcquire()).isFalse();
+
+ // Now cancel the new job.
+ mPreRebootDexoptJob.cancelGiven(future, false /* expectInterrupt */);
+
+ // Now the new job should be cancelled.
+ assertThat(jobExited.tryAcquire()).isTrue();
}
}
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
index 864390c217..49d7c6cb41 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterParameterizedTest.java
@@ -252,6 +252,9 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase {
lenient().when(mArtd.isInDalvikCache(any())).thenReturn(mParams.mIsInDalvikCache);
+ // By default, no artifacts exist.
+ lenient().when(mArtd.getArtifactsVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
if (mParams.mCallbackReturnedCompilerFilter != null) {
mConfig.setAdjustCompilerFilterCallback(
Runnable::run, (packageName, originalCompilerFilter, reason) -> {
@@ -311,6 +314,15 @@ public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase {
.when(mDexMetadataHelperInjector.openZipFile(any()))
.thenThrow(NoSuchFileException.class);
+ if (mParams.mIsPreReboot) {
+ doReturn(FileVisibility.OTHER_READABLE)
+ .when(mArtd)
+ .getDexFileVisibility("/somewhere/app/foo/base.apk");
+ doReturn(FileVisibility.OTHER_READABLE)
+ .when(mArtd)
+ .getDexFileVisibility("/somewhere/app/foo/split_0.apk");
+ }
+
// The first one is normal.
doReturn(dexoptIsNeeded())
.when(mArtd)
diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
index beb8c1eaf4..0030085497 100644
--- a/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
+++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexopterTest.java
@@ -127,6 +127,9 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
.when(mDexMetadataHelperInjector.openZipFile(any()))
.thenThrow(NoSuchFileException.class);
+ // By default, no artifacts exist.
+ lenient().when(mArtd.getArtifactsVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
+
// Dexopt is by default needed and successful.
lenient()
.when(mArtd.getDexoptNeeded(any(), any(), any(), any(), anyInt()))
@@ -140,6 +143,13 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
.when(mArtd.createCancellationSignal())
.thenReturn(mock(IArtdCancellationSignal.class));
+ lenient()
+ .when(mArtd.getDexFileVisibility(mDexPath))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+ lenient()
+ .when(mArtd.getDexFileVisibility(mSplit0DexPath))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
mPrimaryDexopter =
new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
@@ -850,6 +860,95 @@ public class PrimaryDexopterTest extends PrimaryDexopterTestBase {
.isEqualTo(0);
}
+ @Test
+ public void testDexoptPreRebootDexNotFound() throws Exception {
+ when(mInjector.isPreReboot()).thenReturn(true);
+ doReturn(FileVisibility.NOT_FOUND).when(mArtd).getDexFileVisibility(mDexPath);
+ doReturn(FileVisibility.NOT_FOUND).when(mArtd).getDexFileVisibility(mSplit0DexPath);
+
+ mPrimaryDexopter =
+ new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+ List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+ assertThat(results).hasSize(0);
+ }
+
+ @Test
+ public void testDexoptPreRebootSomeDexNotFound() throws Exception {
+ when(mInjector.isPreReboot()).thenReturn(true);
+ doReturn(FileVisibility.OTHER_READABLE).when(mArtd).getDexFileVisibility(mDexPath);
+ doReturn(FileVisibility.NOT_FOUND).when(mArtd).getDexFileVisibility(mSplit0DexPath);
+
+ mPrimaryDexopter =
+ new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+ List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+ assertThat(results).hasSize(2);
+ assertThat(results.get(0).getDexContainerFile()).isEqualTo(mDexPath);
+ assertThat(results.get(0).getAbi()).isEqualTo("arm64-v8a");
+ assertThat(results.get(1).getDexContainerFile()).isEqualTo(mDexPath);
+ assertThat(results.get(1).getAbi()).isEqualTo("armeabi-v7a");
+ }
+
+ @Test
+ public void testDexoptPreRebootArtifactsExist() throws Exception {
+ when(mInjector.isPreReboot()).thenReturn(true);
+
+ when(mArtd.getArtifactsVisibility(deepEq(AidlUtils.buildArtifactsPathAsInputPreReboot(
+ mDexPath, "arm", false /* isInDalvikCache */))))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
+ mPrimaryDexopter =
+ new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+ List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+ // Only the one at index 1 is skipped.
+ assertThat(results).hasSize(4);
+ assertThat(results.get(0).getDexContainerFile()).isEqualTo(mDexPath);
+ assertThat(results.get(0).getAbi()).isEqualTo("arm64-v8a");
+ assertThat(results.get(0).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+ assertThat(results.get(0).getExtendedStatusFlags()
+ & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
+ .isEqualTo(0);
+ assertThat(results.get(1).getDexContainerFile()).isEqualTo(mDexPath);
+ assertThat(results.get(1).getAbi()).isEqualTo("armeabi-v7a");
+ assertThat(results.get(1).getStatus()).isEqualTo(DexoptResult.DEXOPT_SKIPPED);
+ assertThat(results.get(1).getExtendedStatusFlags()
+ & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
+ .isNotEqualTo(0);
+ assertThat(results.get(2).getDexContainerFile()).isEqualTo(mSplit0DexPath);
+ assertThat(results.get(2).getAbi()).isEqualTo("arm64-v8a");
+ assertThat(results.get(2).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+ assertThat(results.get(2).getExtendedStatusFlags()
+ & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
+ .isEqualTo(0);
+ assertThat(results.get(3).getDexContainerFile()).isEqualTo(mSplit0DexPath);
+ assertThat(results.get(3).getAbi()).isEqualTo("armeabi-v7a");
+ assertThat(results.get(3).getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+ assertThat(results.get(3).getExtendedStatusFlags()
+ & DexoptResult.EXTENDED_SKIPPED_PRE_REBOOT_ALREADY_EXIST)
+ .isEqualTo(0);
+ }
+
+ @Test
+ public void testDexoptNotAffectedByPreRebootArtifacts() throws Exception {
+ // Same setup as above, but `isPreReboot` is false.
+ lenient()
+ .when(mArtd.getArtifactsVisibility(
+ deepEq(AidlUtils.buildArtifactsPathAsInputPreReboot(
+ mDexPath, "arm", false /* isInDalvikCache */))))
+ .thenReturn(FileVisibility.OTHER_READABLE);
+
+ mPrimaryDexopter =
+ new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
+
+ List<DexContainerFileDexoptResult> results = mPrimaryDexopter.dexopt();
+ assertThat(results).hasSize(4);
+ for (DexContainerFileDexoptResult result : results) {
+ assertThat(result.getStatus()).isEqualTo(DexoptResult.DEXOPT_PERFORMED);
+ }
+ }
+
private void checkDexoptWithProfile(IArtd artd, String dexPath, String isa, ProfilePath profile,
boolean isOtherReadable) throws Exception {
artd.dexopt(argThat(artifacts
diff --git a/libartservice/service/javatests/com/android/server/art/prereboot/PreRebootStatsReporterTest.java b/libartservice/service/javatests/com/android/server/art/prereboot/PreRebootStatsReporterTest.java
new file mode 100644
index 0000000000..4b8571fa6b
--- /dev/null
+++ b/libartservice/service/javatests/com/android/server/art/prereboot/PreRebootStatsReporterTest.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package com.android.server.art.prereboot;
+
+import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
+import static com.android.server.art.proto.PreRebootStats.JobRun;
+import static com.android.server.art.proto.PreRebootStats.JobType;
+import static com.android.server.art.proto.PreRebootStats.Status;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.art.ArtManagerLocal;
+import com.android.server.art.ArtStatsLog;
+import com.android.server.art.model.DexoptStatus;
+import com.android.server.art.proto.PreRebootStats;
+import com.android.server.pm.PackageManagerLocal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+
+@SmallTest
+@RunWith(MockitoJUnitRunner.StrictStubs.class)
+public class PreRebootStatsReporterTest {
+ private static final String PKG_NAME_FOO = "com.example.foo";
+ private static final String PKG_NAME_BAR = "com.example.bar";
+ private static final String PKG_NAME_BAZ = "com.example.baz";
+
+ @Mock private PreRebootStatsReporter.Injector mInjector;
+ @Mock private PackageManagerLocal mPackageManagerLocal;
+ @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+ @Mock private ArtManagerLocal mArtManagerLocal;
+ private File mTempFile;
+
+ @Before
+ public void setUp() throws Exception {
+ lenient().when(mPackageManagerLocal.withFilteredSnapshot()).thenReturn(mSnapshot);
+
+ mTempFile = File.createTempFile("pre-reboot-stats", ".pb");
+ mTempFile.deleteOnExit();
+ lenient().when(mInjector.getFilename()).thenReturn(mTempFile.getAbsolutePath());
+
+ lenient().when(mInjector.getPackageManagerLocal()).thenReturn(mPackageManagerLocal);
+ lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal);
+ }
+
+ @Test
+ public void testSuccess() throws Exception {
+ var reporter = new PreRebootStatsReporter(mInjector);
+
+ doReturn(50l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobScheduled(true /* isAsync */, false /* isOtaUpdate */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_SCHEDULED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .build());
+
+ {
+ doReturn(200l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobStarted();
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(200))
+ .setSkippedPackageCount(0)
+ .setOptimizedPackageCount(0)
+ .setFailedPackageCount(0)
+ .setTotalPackageCount(0)
+ .setPackagesWithArtifactsBeforeRebootCount(0)
+ .build());
+
+ var reporterInChroot = new PreRebootStatsReporter(mInjector);
+ var progressSession = reporterInChroot.new ProgressSession();
+
+ progressSession.recordProgress(1 /* skippedPackageCount */,
+ 2 /* optimizedPackageCount */, 3 /* failedPackageCount */,
+ 10 /* totalPackageCount */, 4 /* packagesWithArtifactsBeforeRebootCount */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(200))
+ .setSkippedPackageCount(1)
+ .setOptimizedPackageCount(2)
+ .setFailedPackageCount(3)
+ .setTotalPackageCount(10)
+ .setPackagesWithArtifactsBeforeRebootCount(4)
+ .build());
+
+ doReturn(300l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobEnded(true /* success */, false /* systemRequirementCheckFailed */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_CANCELLED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder()
+ .setJobStartedTimestampMillis(200)
+ .setJobEndedTimestampMillis(300))
+ .setSkippedPackageCount(1)
+ .setOptimizedPackageCount(2)
+ .setFailedPackageCount(3)
+ .setTotalPackageCount(10)
+ .setPackagesWithArtifactsBeforeRebootCount(4)
+ .build());
+ }
+
+ {
+ doReturn(400l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobStarted();
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder()
+ .setJobStartedTimestampMillis(200)
+ .setJobEndedTimestampMillis(300))
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(400))
+ .setSkippedPackageCount(0)
+ .setOptimizedPackageCount(0)
+ .setFailedPackageCount(0)
+ .setTotalPackageCount(0)
+ .setPackagesWithArtifactsBeforeRebootCount(0)
+ .build());
+
+ var reporterInChroot = new PreRebootStatsReporter(mInjector);
+ var progressSession = reporterInChroot.new ProgressSession();
+
+ progressSession.recordProgress(1 /* skippedPackageCount */,
+ 6 /* optimizedPackageCount */, 3 /* failedPackageCount */,
+ 10 /* totalPackageCount */, 8 /* packagesWithArtifactsBeforeRebootCount */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder()
+ .setJobStartedTimestampMillis(200)
+ .setJobEndedTimestampMillis(300))
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(400))
+ .setSkippedPackageCount(1)
+ .setOptimizedPackageCount(6)
+ .setFailedPackageCount(3)
+ .setTotalPackageCount(10)
+ .setPackagesWithArtifactsBeforeRebootCount(8)
+ .build());
+
+ doReturn(600l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobEnded(true /* success */, false /* systemRequirementCheckFailed */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_FINISHED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder()
+ .setJobStartedTimestampMillis(200)
+ .setJobEndedTimestampMillis(300))
+ .addJobRuns(JobRun.newBuilder()
+ .setJobStartedTimestampMillis(400)
+ .setJobEndedTimestampMillis(600))
+ .setSkippedPackageCount(1)
+ .setOptimizedPackageCount(6)
+ .setFailedPackageCount(3)
+ .setTotalPackageCount(10)
+ .setPackagesWithArtifactsBeforeRebootCount(8)
+ .build());
+ }
+
+ {
+ var reporterAfterReboot = new PreRebootStatsReporter(mInjector);
+ var afterRebootSession = reporterAfterReboot.new AfterRebootSession();
+
+ // For primary dex.
+ afterRebootSession.recordPackageWithArtifacts(PKG_NAME_FOO);
+ afterRebootSession.recordPackageWithArtifacts(PKG_NAME_BAR);
+
+ // For secondary dex.
+ afterRebootSession.recordPackageWithArtifacts(PKG_NAME_FOO);
+ afterRebootSession.recordPackageWithArtifacts(PKG_NAME_BAZ);
+
+ // No status has "ab-ota".
+ doReturn(DexoptStatus.create(List.of(DexContainerFileDexoptStatus.create(
+ "/somewhere/app/foo/base.apk", true /* isPrimaryDex */,
+ true /* isPrimaryAbi */, "arm64-v8a", "verify", "vdex", "location"))))
+ .when(mArtManagerLocal)
+ .getDexoptStatus(mSnapshot, PKG_NAME_FOO);
+
+ // One status has "ab-ota".
+ doReturn(DexoptStatus.create(List.of(
+ DexContainerFileDexoptStatus.create("/somewhere/app/bar/base.apk",
+ true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+ "speed", "ab-ota", "location"),
+ DexContainerFileDexoptStatus.create("/somewhere/app/bar/base.apk",
+ true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+ "verify", "vdex", "location"))))
+ .when(mArtManagerLocal)
+ .getDexoptStatus(mSnapshot, PKG_NAME_BAR);
+
+ // All statuses have "ab-ota".
+ doReturn(DexoptStatus.create(List.of(
+ DexContainerFileDexoptStatus.create("/somewhere/app/baz/base.apk",
+ true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+ "verify", "ab-ota", "location"),
+ DexContainerFileDexoptStatus.create("/somewhere/app/baz/base.apk",
+ true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
+ "speed-profile", "ab-ota", "location"))))
+ .when(mArtManagerLocal)
+ .getDexoptStatus(mSnapshot, PKG_NAME_BAZ);
+
+ afterRebootSession.report();
+ }
+
+ verify(mInjector).writeStats(ArtStatsLog.PREREBOOT_DEXOPT_JOB_ENDED,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_FINISHED,
+ 6 /* optimizedPackageCount */, 3 /* failedPackageCount */,
+ 1 /* skippedPackageCount */, 10 /* totalPackageCount */,
+ 300 /* jobDurationMillis */, 150 /* jobLatencyMillis */,
+ 3 /* packagesWithArtifactsAfterRebootCount */,
+ 2 /* packagesWithArtifactsUsableAfterRebootCount */, 2 /* jobRunCount */,
+ 8 /* packagesWithArtifactsBeforeRebootCount */,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_MAINLINE);
+ }
+
+ @Test
+ public void testSuccessSync() throws Exception {
+ var reporter = new PreRebootStatsReporter(mInjector);
+
+ reporter.recordJobScheduled(false /* isAsync */, true /* isOtaUpdate */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_SCHEDULED)
+ .setJobType(JobType.JOB_TYPE_OTA)
+ .build());
+
+ {
+ doReturn(200l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobStarted();
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_OTA)
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(200))
+ .setSkippedPackageCount(0)
+ .setOptimizedPackageCount(0)
+ .setFailedPackageCount(0)
+ .setTotalPackageCount(0)
+ .setPackagesWithArtifactsBeforeRebootCount(0)
+ .build());
+
+ var reporterInChroot = new PreRebootStatsReporter(mInjector);
+ var progressSession = reporterInChroot.new ProgressSession();
+
+ progressSession.recordProgress(1 /* skippedPackageCount */,
+ 6 /* optimizedPackageCount */, 3 /* failedPackageCount */,
+ 10 /* totalPackageCount */, 8 /* packagesWithArtifactsBeforeRebootCount */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_OTA)
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(200))
+ .setSkippedPackageCount(1)
+ .setOptimizedPackageCount(6)
+ .setFailedPackageCount(3)
+ .setTotalPackageCount(10)
+ .setPackagesWithArtifactsBeforeRebootCount(8)
+ .build());
+
+ doReturn(300l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobEnded(true /* success */, false /* systemRequirementCheckFailed */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_FINISHED)
+ .setJobType(JobType.JOB_TYPE_OTA)
+ .addJobRuns(JobRun.newBuilder()
+ .setJobStartedTimestampMillis(200)
+ .setJobEndedTimestampMillis(300))
+ .setSkippedPackageCount(1)
+ .setOptimizedPackageCount(6)
+ .setFailedPackageCount(3)
+ .setTotalPackageCount(10)
+ .setPackagesWithArtifactsBeforeRebootCount(8)
+ .build());
+ }
+
+ {
+ var reporterAfterReboot = new PreRebootStatsReporter(mInjector);
+ var afterRebootSession = reporterAfterReboot.new AfterRebootSession();
+
+ afterRebootSession.report();
+ }
+
+ verify(mInjector).writeStats(ArtStatsLog.PREREBOOT_DEXOPT_JOB_ENDED,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_FINISHED,
+ 6 /* optimizedPackageCount */, 3 /* failedPackageCount */,
+ 1 /* skippedPackageCount */, 10 /* totalPackageCount */,
+ 100 /* jobDurationMillis */, -1 /* jobLatencyMillis */,
+ 0 /* packagesWithArtifactsAfterRebootCount */,
+ 0 /* packagesWithArtifactsUsableAfterRebootCount */, 1 /* jobRunCount */,
+ 8 /* packagesWithArtifactsBeforeRebootCount */,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_OTA);
+ }
+
+ private void checkFailure(boolean systemRequirementCheckFailed) throws Exception {
+ var reporter = new PreRebootStatsReporter(mInjector);
+
+ doReturn(50l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobScheduled(true /* isAsync */, false /* isOtaUpdate */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_SCHEDULED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .build());
+
+ {
+ doReturn(200l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobStarted();
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(200))
+ .setSkippedPackageCount(0)
+ .setOptimizedPackageCount(0)
+ .setFailedPackageCount(0)
+ .setTotalPackageCount(0)
+ .setPackagesWithArtifactsBeforeRebootCount(0)
+ .build());
+
+ doReturn(300l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobEnded(false /* success */, systemRequirementCheckFailed);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(systemRequirementCheckFailed
+ ? Status.STATUS_ABORTED_SYSTEM_REQUIREMENTS
+ : Status.STATUS_FAILED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder()
+ .setJobStartedTimestampMillis(200)
+ .setJobEndedTimestampMillis(300))
+ .setSkippedPackageCount(0)
+ .setOptimizedPackageCount(0)
+ .setFailedPackageCount(0)
+ .setTotalPackageCount(0)
+ .setPackagesWithArtifactsBeforeRebootCount(0)
+ .build());
+ }
+
+ {
+ var reporterAfterReboot = new PreRebootStatsReporter(mInjector);
+ var afterRebootSession = reporterAfterReboot.new AfterRebootSession();
+
+ afterRebootSession.report();
+ }
+
+ verify(mInjector).writeStats(ArtStatsLog.PREREBOOT_DEXOPT_JOB_ENDED,
+ systemRequirementCheckFailed
+ ? ArtStatsLog
+ .PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_ABORTED_SYSTEM_REQUIREMENTS
+ : ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_FAILED,
+ 0 /* optimizedPackageCount */, 0 /* failedPackageCount */,
+ 0 /* skippedPackageCount */, 0 /* totalPackageCount */, 100 /* jobDurationMillis */,
+ 150 /* jobLatencyMillis */, 0 /* packagesWithArtifactsAfterRebootCount */,
+ 0 /* packagesWithArtifactsUsableAfterRebootCount */, 1 /* jobRunCount */,
+ 0 /* packagesWithArtifactsBeforeRebootCount */,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_MAINLINE);
+ }
+
+ @Test
+ public void testUnexpectedFailure() throws Exception {
+ checkFailure(false /* systemRequirementCheckFailed */);
+ }
+
+ @Test
+ public void testSystemRequirementCheckFailure() throws Exception {
+ checkFailure(true /* systemRequirementCheckFailed */);
+ }
+
+ @Test
+ public void testStarted() throws Exception {
+ var reporter = new PreRebootStatsReporter(mInjector);
+
+ doReturn(50l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobScheduled(true /* isAsync */, false /* isOtaUpdate */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_SCHEDULED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .build());
+
+ {
+ doReturn(200l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobStarted();
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(200))
+ .setSkippedPackageCount(0)
+ .setOptimizedPackageCount(0)
+ .setFailedPackageCount(0)
+ .setTotalPackageCount(0)
+ .setPackagesWithArtifactsBeforeRebootCount(0)
+ .build());
+
+ var reporterInChroot = new PreRebootStatsReporter(mInjector);
+ var progressSession = reporterInChroot.new ProgressSession();
+
+ progressSession.recordProgress(1 /* skippedPackageCount */,
+ 2 /* optimizedPackageCount */, 3 /* failedPackageCount */,
+ 10 /* totalPackageCount */, 4 /* packagesWithArtifactsBeforeRebootCount */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_STARTED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .addJobRuns(JobRun.newBuilder().setJobStartedTimestampMillis(200))
+ .setSkippedPackageCount(1)
+ .setOptimizedPackageCount(2)
+ .setFailedPackageCount(3)
+ .setTotalPackageCount(10)
+ .setPackagesWithArtifactsBeforeRebootCount(4)
+ .build());
+ }
+
+ {
+ var reporterAfterReboot = new PreRebootStatsReporter(mInjector);
+ var afterRebootSession = reporterAfterReboot.new AfterRebootSession();
+
+ afterRebootSession.report();
+ }
+
+ verify(mInjector).writeStats(ArtStatsLog.PREREBOOT_DEXOPT_JOB_ENDED,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_STARTED,
+ 2 /* optimizedPackageCount */, 3 /* failedPackageCount */,
+ 1 /* skippedPackageCount */, 10 /* totalPackageCount */, -1 /* jobDurationMillis */,
+ 150 /* jobLatencyMillis */, 0 /* packagesWithArtifactsAfterRebootCount */,
+ 0 /* packagesWithArtifactsUsableAfterRebootCount */, 1 /* jobRunCount */,
+ 4 /* packagesWithArtifactsBeforeRebootCount */,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_MAINLINE);
+ }
+
+ @Test
+ public void testScheduled() throws Exception {
+ var reporter = new PreRebootStatsReporter(mInjector);
+
+ doReturn(50l).when(mInjector).getCurrentTimeMillis();
+ reporter.recordJobScheduled(true /* isAsync */, false /* isOtaUpdate */);
+ checkProto(PreRebootStats.newBuilder()
+ .setStatus(Status.STATUS_SCHEDULED)
+ .setJobType(JobType.JOB_TYPE_MAINLINE)
+ .setJobScheduledTimestampMillis(50)
+ .build());
+
+ {
+ var reporterAfterReboot = new PreRebootStatsReporter(mInjector);
+ var afterRebootSession = reporterAfterReboot.new AfterRebootSession();
+
+ afterRebootSession.report();
+ }
+
+ verify(mInjector).writeStats(ArtStatsLog.PREREBOOT_DEXOPT_JOB_ENDED,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__STATUS__STATUS_SCHEDULED,
+ 0 /* optimizedPackageCount */, 0 /* failedPackageCount */,
+ 0 /* skippedPackageCount */, 0 /* totalPackageCount */, -1 /* jobDurationMillis */,
+ -1 /* jobLatencyMillis */, 0 /* packagesWithArtifactsAfterRebootCount */,
+ 0 /* packagesWithArtifactsUsableAfterRebootCount */, 0 /* jobRunCount */,
+ 0 /* packagesWithArtifactsBeforeRebootCount */,
+ ArtStatsLog.PRE_REBOOT_DEXOPT_JOB_ENDED__JOB_TYPE__JOB_TYPE_MAINLINE);
+ }
+
+ private void checkProto(PreRebootStats expected) throws Exception {
+ PreRebootStats actual;
+ try (InputStream in = new FileInputStream(mTempFile.getPath())) {
+ actual = PreRebootStats.parseFrom(in);
+ }
+ assertThat(actual).isEqualTo(expected);
+ }
+}
diff --git a/libartservice/service/native/service.cc b/libartservice/service/native/service.cc
index 5cf51b998a..e97d871dac 100644
--- a/libartservice/service/native/service.cc
+++ b/libartservice/service/native/service.cc
@@ -23,17 +23,21 @@
#include "android-base/errors.h"
#include "android-base/file.h"
+#include "android-base/properties.h"
#include "android-base/result.h"
#include "class_loader_context.h"
#include "gc/heap.h"
+#include "nativehelper/JNIHelp.h"
#include "nativehelper/utils.h"
#include "runtime.h"
+#include "tools/tools.h"
namespace art {
namespace service {
using ::android::base::Dirname;
using ::android::base::Result;
+using ::android::base::SetProperty;
Result<void> ValidateAbsoluteNormalPath(const std::string& path_str) {
if (path_str.empty()) {
@@ -134,5 +138,32 @@ Java_com_android_server_art_ArtJni_getGarbageCollectorNative(JNIEnv* env, jobjec
return CREATE_UTF_OR_RETURN(env, GetGarbageCollector()).release();
}
+extern "C" JNIEXPORT void JNICALL Java_com_android_server_art_ArtJni_setPropertyNative(
+ JNIEnv* env, jobject, jstring j_key, jstring j_value) {
+ std::string key(GET_UTF_OR_RETURN_VOID(env, j_key));
+ std::string value(GET_UTF_OR_RETURN_VOID(env, j_value));
+ if (!SetProperty(key, value)) {
+ jniThrowExceptionFmt(env,
+ "java/lang/IllegalStateException",
+ "Failed to set property '%s' to '%s'",
+ key.c_str(),
+ value.c_str());
+ }
+}
+
+extern "C" JNIEXPORT void JNICALL Java_com_android_server_art_ArtJni_ensureNoProcessInDirNative(
+ JNIEnv* env, jobject, jstring j_dir, jint j_timeout_ms) {
+ if (j_timeout_ms < 0) {
+ jniThrowExceptionFmt(
+ env, "java/lang/IllegalArgumentException", "Negative timeout '%d'", j_timeout_ms);
+ return;
+ }
+ std::string dir(GET_UTF_OR_RETURN_VOID(env, j_dir));
+ if (Result<void> result = tools::EnsureNoProcessInDir(dir, j_timeout_ms, /*try_kill=*/true);
+ !result.ok()) {
+ jniThrowException(env, "java/io/IOException", result.error().message().c_str());
+ }
+}
+
} // namespace service
} // namespace art
diff --git a/libartservice/service/proto/pre_reboot_stats.proto b/libartservice/service/proto/pre_reboot_stats.proto
new file mode 100644
index 0000000000..fe42944815
--- /dev/null
+++ b/libartservice/service/proto/pre_reboot_stats.proto
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+syntax = "proto3";
+
+package com.android.server.art.proto;
+option java_multiple_files = true;
+
+// Pre-reboot Dexopt metrics to persist on disk for being reported after reboot.
+// This proto is persisted on disk and forward and backward compatibility are considerations.
+message PreRebootStats {
+ // Overall status of the job right before the reboot.
+ // See `android.os.statsd.art.PreRebootDexoptJobEnded`.
+ enum Status {
+ STATUS_UNKNOWN = 0;
+ STATUS_SCHEDULED = 1;
+ STATUS_STARTED = 2;
+ STATUS_FAILED = 3;
+ STATUS_FINISHED = 4;
+ STATUS_CANCELLED = 5;
+ STATUS_ABORTED_SYSTEM_REQUIREMENTS = 6;
+ }
+ optional Status status = 1;
+
+ // Number of packages successfully optimized.
+ optional int32 optimized_package_count = 2;
+ // Number of packages failed to optimize.
+ optional int32 failed_package_count = 3;
+ // Number of packages skipped.
+ optional int32 skipped_package_count = 4;
+ // Total number of packages scanned.
+ optional int32 total_package_count = 5;
+
+ // When the job is scheduled, in milliseconds.
+ optional int64 job_scheduled_timestamp_millis = 6;
+
+ // Represents a job run.
+ message JobRun {
+ // When the job is started, in milliseconds.
+ optional int64 job_started_timestamp_millis = 1;
+ // When the job is ended (failed, finished, or cancelled), in milliseconds.
+ optional int64 job_ended_timestamp_millis = 2;
+ }
+
+ // All job runs. The job may be cancelled and rerun multiple times.
+ repeated JobRun job_runs = 7;
+
+ // Number of packages that have Pre-reboot Dexopt artifacts before the reboot. Note that this
+ // isn't necessarily equal to `optimized_package_count` because packages failed to be optimized
+ // may still have some splits successfully optimized.
+ optional int32 packages_with_artifacts_before_reboot_count = 8;
+
+ // The type of the job.
+ enum JobType {
+ JOB_TYPE_UNKNOWN = 0;
+ JOB_TYPE_OTA = 1;
+ JOB_TYPE_MAINLINE = 2;
+ }
+ optional JobType job_type = 9;
+}
diff --git a/libarttools/art_exec_test.cc b/libarttools/art_exec_test.cc
index 63f237c342..c1f15dc187 100644
--- a/libarttools/art_exec_test.cc
+++ b/libarttools/art_exec_test.cc
@@ -17,21 +17,13 @@
#include <sys/capability.h>
#include <sys/resource.h>
#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-#include <csignal>
-#include <filesystem>
-#include <functional>
#include <string>
-#include <utility>
#include "android-base/file.h"
#include "android-base/logging.h"
-#include "android-base/scopeguard.h"
#include "android-base/strings.h"
#include "base/common_art_test.h"
-#include "base/file_utils.h"
#include "base/globals.h"
#include "base/macros.h"
#include "base/os.h"
@@ -40,16 +32,16 @@
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "system/thread_defs.h"
+#include "testing.h"
#ifdef ART_TARGET_ANDROID
#include "android-modules-utils/sdk_level.h"
#endif
namespace art {
+namespace tools {
namespace {
-using ::android::base::make_scope_guard;
-using ::android::base::ScopeGuard;
using ::android::base::Split;
using ::testing::Contains;
using ::testing::ElementsAre;
@@ -59,45 +51,6 @@ using ::testing::Not;
constexpr uid_t kRoot = 0;
constexpr uid_t kNobody = 9999;
-std::string GetArtBin(const std::string& name) {
- return ART_FORMAT("{}/bin/{}", GetArtRoot(), name);
-}
-
-std::string GetBin(const std::string& name) {
- return ART_FORMAT("{}/bin/{}", GetAndroidRoot(), name);
-}
-
-// Executes the command, waits for it to finish, and keeps it in a waitable state until the current
-// scope exits.
-std::pair<pid_t, ScopeGuard<std::function<void()>>> ScopedExecAndWait(
- std::vector<std::string>& args) {
- std::vector<char*> execv_args;
- execv_args.reserve(args.size() + 1);
- for (std::string& arg : args) {
- execv_args.push_back(arg.data());
- }
- execv_args.push_back(nullptr);
-
- pid_t pid = fork();
- if (pid == 0) {
- execv(execv_args[0], execv_args.data());
- UNREACHABLE();
- } else if (pid > 0) {
- siginfo_t info;
- CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0);
- CHECK_EQ(info.si_code, CLD_EXITED);
- CHECK_EQ(info.si_status, 0);
- std::function<void()> cleanup([=] {
- siginfo_t info;
- CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0);
- });
- return std::make_pair(pid, make_scope_guard(std::move(cleanup)));
- } else {
- LOG(FATAL) << "Failed to call fork";
- UNREACHABLE();
- }
-}
-
// Grants the current process the given root capability.
void SetCap(cap_flag_t flag, cap_value_t value) {
ScopedCap cap(cap_get_proc());
@@ -156,7 +109,7 @@ TEST_F(ArtExecTest, SetTaskProfiles) {
GetBin("sh"),
"-c",
"cat /proc/self/cgroup > " + filename};
- auto [pid, scope_guard] = ScopedExecAndWait(args);
+ auto [pid, scope_guard] = ScopedExec(args, /*wait=*/true);
std::string cgroup;
ASSERT_TRUE(android::base::ReadFileToString(filename, &cgroup));
EXPECT_THAT(cgroup, HasSubstr(":cpuset:/foreground\n"));
@@ -164,7 +117,7 @@ TEST_F(ArtExecTest, SetTaskProfiles) {
TEST_F(ArtExecTest, SetPriority) {
std::vector<std::string> args{art_exec_bin_, "--set-priority=background", "--", GetBin("true")};
- auto [pid, scope_guard] = ScopedExecAndWait(args);
+ auto [pid, scope_guard] = ScopedExec(args, /*wait=*/true);
EXPECT_EQ(getpriority(PRIO_PROCESS, pid), ANDROID_PRIORITY_BACKGROUND);
}
@@ -180,13 +133,13 @@ TEST_F(ArtExecTest, DropCapabilities) {
// inherited root capability: CAP_FOWNER).
{
std::vector<std::string> args{art_exec_bin_, "--", GetBin("true")};
- auto [pid, scope_guard] = ScopedExecAndWait(args);
+ auto [pid, scope_guard] = ScopedExec(args, /*wait=*/true);
ASSERT_TRUE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER));
}
{
std::vector<std::string> args{art_exec_bin_, "--drop-capabilities", "--", GetBin("true")};
- auto [pid, scope_guard] = ScopedExecAndWait(args);
+ auto [pid, scope_guard] = ScopedExec(args, /*wait=*/true);
EXPECT_FALSE(GetCap(pid, CAP_EFFECTIVE, CAP_FOWNER));
}
}
@@ -218,7 +171,7 @@ TEST_F(ArtExecTest, CloseFds) {
file3->Fd(),
filename)};
- ScopedExecAndWait(args);
+ ScopedExec(args, /*wait=*/true);
std::string open_fds;
ASSERT_TRUE(android::base::ReadFileToString(filename, &open_fds));
@@ -235,7 +188,7 @@ TEST_F(ArtExecTest, Env) {
std::vector<std::string> args{
art_exec_bin_, "--env=FOO=BAR", "--", GetBin("sh"), "-c", "env > " + filename};
- ScopedExecAndWait(args);
+ ScopedExec(args, /*wait=*/true);
std::string envs;
ASSERT_TRUE(android::base::ReadFileToString(filename, &envs));
@@ -256,7 +209,7 @@ TEST_F(ArtExecTest, ProcessNameSuffix) {
"/proc/self/cmdline",
filename};
- ScopedExecAndWait(args);
+ ScopedExec(args, /*wait=*/true);
std::string cmdline;
ASSERT_TRUE(android::base::ReadFileToString(filename, &cmdline));
@@ -265,4 +218,5 @@ TEST_F(ArtExecTest, ProcessNameSuffix) {
}
} // namespace
+} // namespace tools
} // namespace art
diff --git a/libarttools/include/tools/system_properties.h b/libarttools/include/tools/system_properties.h
index af5aea46e3..5eca765daa 100644
--- a/libarttools/include/tools/system_properties.h
+++ b/libarttools/include/tools/system_properties.h
@@ -29,6 +29,11 @@ namespace tools {
// android::base::GetProperty, this class is mockable.
class SystemProperties {
public:
+ SystemProperties() = default;
+ SystemProperties(const SystemProperties& other) = default;
+ SystemProperties& operator=(const SystemProperties& other) = default;
+ SystemProperties(SystemProperties&& other) = default;
+ SystemProperties& operator=(SystemProperties&& other) = default;
virtual ~SystemProperties() = default;
// Returns the current value of the system property `key`, or `default_value` if the property
diff --git a/libarttools/include/tools/tools.h b/libarttools/include/tools/tools.h
index f536e030c4..9399bcd49d 100644
--- a/libarttools/include/tools/tools.h
+++ b/libarttools/include/tools/tools.h
@@ -17,6 +17,7 @@
#ifndef ART_LIBARTTOOLS_INCLUDE_TOOLS_TOOLS_H_
#define ART_LIBARTTOOLS_INCLUDE_TOOLS_TOOLS_H_
+#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
@@ -56,6 +57,11 @@ android::base::Result<std::vector<android::fs_mgr::FstabEntry>> GetProcMountsAnc
android::base::Result<std::vector<android::fs_mgr::FstabEntry>> GetProcMountsDescendantsOfPath(
std::string_view path);
+// See `ArtJni.ensureNoProcessInDir`.
+android::base::Result<void> EnsureNoProcessInDir(const std::string& dir,
+ uint32_t timeout_ms,
+ bool try_kill);
+
} // namespace tools
} // namespace art
diff --git a/libarttools/testing.h b/libarttools/testing.h
new file mode 100644
index 0000000000..eaa8523a1b
--- /dev/null
+++ b/libarttools/testing.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef ART_LIBARTTOOLS_TESTING_H_
+#define ART_LIBARTTOOLS_TESTING_H_
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <functional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/scopeguard.h"
+#include "base/file_utils.h"
+#include "base/globals.h"
+#include "base/macros.h"
+
+namespace art {
+namespace tools {
+
+using ::android::base::make_scope_guard;
+using ::android::base::ScopeGuard;
+
+[[maybe_unused]] static std::string GetArtBin(const std::string& name) {
+ CHECK(kIsTargetAndroid);
+ return ART_FORMAT("{}/bin/{}", GetArtRoot(), name);
+}
+
+[[maybe_unused]] static std::string GetBin(const std::string& name) {
+ CHECK(kIsTargetAndroid);
+ return ART_FORMAT("{}/bin/{}", GetAndroidRoot(), name);
+}
+
+// Executes the command. If the `wait` is true, waits for the process to finish and keeps it in a
+// waitable state; otherwise, returns immediately after fork. When the current scope exits, destroys
+// the process.
+[[maybe_unused]] static std::pair<pid_t, ScopeGuard<std::function<void()>>> ScopedExec(
+ std::vector<std::string>& args, bool wait) {
+ std::vector<char*> execv_args;
+ execv_args.reserve(args.size() + 1);
+ for (std::string& arg : args) {
+ execv_args.push_back(arg.data());
+ }
+ execv_args.push_back(nullptr);
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ execv(execv_args[0], execv_args.data());
+ UNREACHABLE();
+ } else if (pid > 0) {
+ if (wait) {
+ siginfo_t info;
+ CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT)), 0);
+ CHECK_EQ(info.si_code, CLD_EXITED);
+ CHECK_EQ(info.si_status, 0);
+ }
+ std::function<void()> cleanup([=] {
+ siginfo_t info;
+ if (!wait) {
+ CHECK_EQ(kill(pid, SIGKILL), 0);
+ }
+ CHECK_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED)), 0);
+ });
+ return std::make_pair(pid, make_scope_guard(std::move(cleanup)));
+ } else {
+ LOG(FATAL) << "Failed to call fork";
+ UNREACHABLE();
+ }
+}
+
+} // namespace tools
+} // namespace art
+
+#endif // ART_LIBARTTOOLS_TESTING_H_
diff --git a/libarttools/tools.cc b/libarttools/tools.cc
index e355f6d1fa..0dadfde564 100644
--- a/libarttools/tools.cc
+++ b/libarttools/tools.cc
@@ -18,21 +18,34 @@
#include <errno.h>
#include <fnmatch.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <unistd.h>
#include <algorithm>
+#include <cstdint>
+#include <ctime>
#include <filesystem>
#include <functional>
#include <regex>
#include <string>
#include <string_view>
#include <system_error>
+#include <unordered_map>
+#include <utility>
#include <vector>
+#include "android-base/errors.h"
+#include "android-base/file.h"
#include "android-base/function_ref.h"
#include "android-base/logging.h"
+#include "android-base/process.h"
#include "android-base/result.h"
#include "android-base/strings.h"
+#include "android-base/unique_fd.h"
#include "base/macros.h"
+#include "base/pidfd.h"
#include "fstab/fstab.h"
namespace art {
@@ -40,15 +53,25 @@ namespace tools {
namespace {
+using ::android::base::AllPids;
using ::android::base::ConsumeSuffix;
using ::android::base::function_ref;
+using ::android::base::ReadFileToString;
+using ::android::base::Readlink;
using ::android::base::Result;
using ::android::base::StartsWith;
+using ::android::base::unique_fd;
using ::android::fs_mgr::Fstab;
using ::android::fs_mgr::FstabEntry;
using ::android::fs_mgr::ReadFstabFromProcMounts;
using ::std::placeholders::_1;
+uint64_t MilliTime() {
+ timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return static_cast<uint64_t>(now.tv_sec) * UINT64_C(1000) + now.tv_nsec / UINT64_C(1000000);
+}
+
// Returns true if `path_prefix` matches `pattern` or can be a prefix of a path that matches
// `pattern` (i.e., `path_prefix` represents a directory that may contain a file whose path matches
// `pattern`).
@@ -194,5 +217,105 @@ Result<std::vector<FstabEntry>> GetProcMountsDescendantsOfPath(std::string_view
[&](std::string_view mount_point) { return PathStartsWith(mount_point, path); });
}
+Result<void> EnsureNoProcessInDir(const std::string& dir, uint32_t timeout_ms, bool try_kill) {
+ // Pairs of pid and process name, indexed by pidfd.
+ std::unordered_map<int, std::pair<pid_t, std::string>> running_processes;
+ std::vector<struct pollfd> pollfds;
+ std::vector<unique_fd> pidfds;
+
+ for (pid_t pid : AllPids()) {
+ std::string exe;
+ if (!Readlink(ART_FORMAT("/proc/{}/exe", pid), &exe)) {
+ // The caller may not have access to all processes. That's okay. When using this method, we
+ // must grant the caller access to the processes that we are interested in.
+ continue;
+ }
+
+ if (PathStartsWith(exe, dir)) {
+ unique_fd pidfd = PidfdOpen(pid, /*flags=*/0);
+ if (pidfd < 0) {
+ if (errno == ESRCH) {
+ // The process has gone now.
+ continue;
+ }
+ return ErrnoErrorf("Failed to pidfd_open {}", pid);
+ }
+
+ std::string name;
+ if (!ReadFileToString(ART_FORMAT("/proc/{}/comm", pid), &name)) {
+ PLOG(WARNING) << "Failed to get process name for pid " << pid;
+ }
+ size_t pos = name.find_first_of("\n\0");
+ if (pos != std::string::npos) {
+ name.resize(pos);
+ }
+ LOG(INFO) << ART_FORMAT(
+ "Process '{}' (pid: {}) is still running. Waiting for it to exit", name, pid);
+
+ struct pollfd& pollfd = pollfds.emplace_back();
+ pollfd.fd = pidfd.get();
+ pollfd.events = POLLIN;
+
+ running_processes[pidfd.get()] = std::make_pair(pid, std::move(name));
+ pidfds.push_back(std::move(pidfd));
+ }
+ }
+
+ auto wait_for_processes = [&]() -> Result<void> {
+ uint64_t start_time_ms = MilliTime();
+ uint64_t remaining_timeout_ms = timeout_ms;
+ while (!running_processes.empty() && remaining_timeout_ms > 0) {
+ int poll_ret = TEMP_FAILURE_RETRY(poll(pollfds.data(), pollfds.size(), remaining_timeout_ms));
+ if (poll_ret < 0) {
+ return ErrnoErrorf("Failed to poll pidfd's");
+ }
+ if (poll_ret == 0) {
+ // Timeout.
+ break;
+ }
+ uint64_t elapsed_time_ms = MilliTime() - start_time_ms;
+ for (struct pollfd& pollfd : pollfds) {
+ if (pollfd.fd < 0) {
+ continue;
+ }
+ if ((pollfd.revents & POLLIN) != 0) {
+ const auto& [pid, name] = running_processes[pollfd.fd];
+ LOG(INFO) << ART_FORMAT(
+ "Process '{}' (pid: {}) exited in {}ms", name, pid, elapsed_time_ms);
+ running_processes.erase(pollfd.fd);
+ pollfd.fd = -1;
+ }
+ }
+ remaining_timeout_ms = timeout_ms - elapsed_time_ms;
+ }
+ return {};
+ };
+
+ OR_RETURN(wait_for_processes());
+
+ bool process_killed = false;
+ for (const auto& [pidfd, pair] : running_processes) {
+ const auto& [pid, name] = pair;
+ LOG(ERROR) << ART_FORMAT(
+ "Process '{}' (pid: {}) is still running after {}ms", name, pid, timeout_ms);
+ if (try_kill) {
+ LOG(INFO) << ART_FORMAT("Killing '{}' (pid: {})", name, pid);
+ if (kill(pid, SIGKILL) != 0) {
+ PLOG(ERROR) << ART_FORMAT("Failed to kill '{}' (pid: {})", name, pid);
+ }
+ process_killed = true;
+ }
+ }
+
+ if (process_killed) {
+ // Wait another round for processes to exit after being killed.
+ OR_RETURN(wait_for_processes());
+ }
+ if (!running_processes.empty()) {
+ return Errorf("Some process(es) are still running after {}ms", timeout_ms);
+ }
+ return {};
+}
+
} // namespace tools
} // namespace art
diff --git a/libarttools/tools_test.cc b/libarttools/tools_test.cc
index 43fa3f42c4..fe2d6d4868 100644
--- a/libarttools/tools_test.cc
+++ b/libarttools/tools_test.cc
@@ -16,26 +16,32 @@
#include "tools/tools.h"
-#include <algorithm>
+#include <stdlib.h>
+#include <unistd.h>
+
#include <filesystem>
-#include <iterator>
#include "android-base/file.h"
+#include "android-base/result.h"
#include "base/common_art_test.h"
+#include "base/globals.h"
+#include "base/time_utils.h"
#include "gmock/gmock.h"
#include "gtest/gtest.h"
+#include "testing.h"
namespace art {
namespace tools {
namespace {
+using ::android::base::Result;
using ::android::base::WriteStringToFile;
using ::testing::UnorderedElementsAre;
-void CreateFile(const std::string& filename) {
+void CreateFile(const std::string& filename, const std::string& content = "") {
std::filesystem::path path(filename);
std::filesystem::create_directories(path.parent_path());
- ASSERT_TRUE(WriteStringToFile(/*content=*/"", filename));
+ ASSERT_TRUE(WriteStringToFile(content, filename));
}
class ArtToolsTest : public CommonArtTest {
@@ -157,6 +163,99 @@ TEST_F(ArtToolsTest, PathStartsWith) {
EXPECT_FALSE(PathStartsWith("/", "/a"));
}
+class ArtToolsEnsureNoProcessInDirTest : public ArtToolsTest {
+ protected:
+ void SetUp() override {
+ ArtToolsTest::SetUp();
+
+ related_dir_ = scratch_path_ + "/related";
+ unrelated_dir_ = scratch_path_ + "/unrelated";
+
+ std::string sleep_bin = GetSleepBin();
+ if (sleep_bin.empty()) {
+ GTEST_SKIP() << "'sleep' is not available";
+ }
+
+ std::filesystem::create_directories(related_dir_);
+ std::filesystem::create_directories(unrelated_dir_);
+ std::filesystem::copy(sleep_bin, related_dir_ + "/sleep");
+ std::filesystem::copy(sleep_bin, unrelated_dir_ + "/sleep");
+ }
+
+ std::string related_dir_;
+ std::string unrelated_dir_;
+
+ private:
+ std::string GetSleepBin() {
+ if constexpr (kIsTargetAndroid) {
+ return GetBin("sleep");
+ }
+ if (access("/usr/bin/sleep", X_OK) == 0) {
+ return "/usr/bin/sleep";
+ }
+ return "";
+ }
+};
+
+TEST_F(ArtToolsEnsureNoProcessInDirTest, WaitsProcesses) {
+ std::vector<std::string> args_1{related_dir_ + "/sleep", "0.3"};
+ auto [pid_1, scope_guard_1] = ScopedExec(args_1, /*wait=*/false);
+ std::vector<std::string> args_2{unrelated_dir_ + "/sleep", "2"};
+ auto [pid_2, scope_guard_2] = ScopedExec(args_2, /*wait=*/false);
+ NanoSleep(100'000'000); // Wait for child processes to exec.
+
+ ASSERT_RESULT_OK(EnsureNoProcessInDir(related_dir_, /*timeout_ms=*/5000, /*try_kill=*/false));
+
+ // Check the current status of the process with `WNOHANG`. The related process should have exited,
+ // so `si_signo` should be `SIGCHLD`.
+ siginfo_t info;
+ ASSERT_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid_1, &info, WEXITED | WNOWAIT | WNOHANG)), 0);
+ EXPECT_EQ(info.si_signo, SIGCHLD);
+ EXPECT_EQ(info.si_code, CLD_EXITED);
+ EXPECT_EQ(info.si_status, 0);
+
+ // The method should not wait on unrelated processes. The unrelated process should not have
+ // exited, so `si_signo` should be 0.
+ ASSERT_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid_2, &info, WEXITED | WNOWAIT | WNOHANG)), 0);
+ EXPECT_EQ(info.si_signo, 0);
+}
+
+TEST_F(ArtToolsEnsureNoProcessInDirTest, TimesOut) {
+ std::vector<std::string> args{related_dir_ + "/sleep", "5"};
+ auto [pid, scope_guard] = ScopedExec(args, /*wait=*/false);
+ NanoSleep(100'000'000); // Wait for child processes to exec.
+
+ Result<void> result = EnsureNoProcessInDir(related_dir_, /*timeout_ms=*/200, /*try_kill=*/false);
+ EXPECT_FALSE(result.ok());
+ EXPECT_EQ(result.error().message(), "Some process(es) are still running after 200ms");
+
+ // The process should not have exited.
+ siginfo_t info;
+ ASSERT_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid, &info, WEXITED | WNOWAIT | WNOHANG)), 0);
+ EXPECT_EQ(info.si_signo, 0);
+}
+
+TEST_F(ArtToolsEnsureNoProcessInDirTest, KillsProcesses) {
+ std::vector<std::string> args_1{related_dir_ + "/sleep", "5"};
+ auto [pid_1, scope_guard_1] = ScopedExec(args_1, /*wait=*/false);
+ std::vector<std::string> args_2{unrelated_dir_ + "/sleep", "5"};
+ auto [pid_2, scope_guard_2] = ScopedExec(args_2, /*wait=*/false);
+ NanoSleep(100'000'000); // Wait for child processes to exec.
+
+ ASSERT_RESULT_OK(EnsureNoProcessInDir(related_dir_, /*timeout_ms=*/200, /*try_kill=*/true));
+
+ // The related process should have been killed.
+ siginfo_t info;
+ ASSERT_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid_1, &info, WEXITED | WNOWAIT | WNOHANG)), 0);
+ EXPECT_EQ(info.si_signo, SIGCHLD);
+ EXPECT_EQ(info.si_code, CLD_KILLED);
+ EXPECT_EQ(info.si_status, SIGKILL);
+
+ // The unrelated process should still be running.
+ ASSERT_EQ(TEMP_FAILURE_RETRY(waitid(P_PID, pid_2, &info, WEXITED | WNOWAIT | WNOHANG)), 0);
+ EXPECT_EQ(info.si_signo, 0);
+}
+
} // namespace
} // namespace tools
} // namespace art
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index 1307e0eae5..e4fc74a3e7 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -57,7 +57,7 @@ static constexpr uint32_t kAccSkipHiddenapiChecks = 0x00100000; // class (run
// declaring-class/super-class are to be considered obsolete, meaning they should not be used by.
static constexpr uint32_t kAccObsoleteObject = 0x00200000; // class (runtime)
// Set during boot image compilation to indicate that the class is
-// not initialized at compile tile and not in the list of preloaded classes.
+// not initialized at compile time and not in the list of preloaded classes.
static constexpr uint32_t kAccInBootImageAndNotInPreloadedClasses = 0x00400000; // class (runtime)
// This is set by the class linker during LinkInterfaceMethods. It is used by a method
// to represent that it was copied from its declaring class into another class.
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index c26b8f6bc4..53fcd2f35b 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -34,6 +34,7 @@
#include <log/log.h>
#if defined(ART_TARGET_ANDROID)
+#include <android-modules-utils/sdk_level.h>
#include <android/sysprop/VndkProperties.sysprop.h>
#endif
@@ -426,12 +427,11 @@ const std::map<std::string, std::string>& apex_public_libraries() {
bool is_product_treblelized() {
#if defined(ART_TARGET_ANDROID)
- // Product is not treblelized iff launching version is prior to R and
- // ro.product.vndk.version is not defined
- static bool product_treblelized =
- !(android::base::GetIntProperty("ro.product.first_api_level", 0) < __ANDROID_API_R__ &&
- !android::sysprop::VndkProperties::product_vndk_version().has_value());
- return product_treblelized;
+ // Product is treblelized iff the sdk version is newer than U
+ // or launching version is R or newer or ro.product.vndk.version is defined
+ return android::modules::sdklevel::IsAtLeastV() ||
+ android::base::GetIntProperty("ro.product.first_api_level", 0) >= __ANDROID_API_R__ ||
+ android::sysprop::VndkProperties::product_vndk_version().has_value();
#else
return false;
#endif
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index fc1828a56e..3d0c05734c 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -2550,6 +2550,9 @@ void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class>
const art::DexFile& old_dex_file = mclass->GetDexFile();
// Update methods.
for (art::ArtMethod& method : mclass->GetDeclaredMethods(image_pointer_size)) {
+ // Reinitialize the method by calling `CopyFrom`. This ensures for example
+ // the entrypoint and the hotness are reset.
+ method.CopyFrom(&method, image_pointer_size);
const art::dex::StringId* new_name_id = dex_file_->FindStringId(method.GetName());
art::dex::TypeIndex method_return_idx =
dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(method.GetReturnTypeDescriptorView()));
diff --git a/runtime/Android.bp b/runtime/Android.bp
index 45d416f0cc..35f37aca54 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -949,6 +949,7 @@ art_cc_defaults {
],
srcs: [
"common_runtime_test.cc",
+ "common_transaction_test.cc",
"dexopt_test.cc",
],
static_libs: [
@@ -1094,6 +1095,7 @@ art_cc_defaults {
"intern_table_test.cc",
"interpreter/safe_math_test.cc",
"interpreter/unstarted_runtime_test.cc",
+ "interpreter/unstarted_runtime_transaction_test.cc",
"jit/jit_memory_region_test.cc",
"jit/profile_saver_test.cc",
"jit/profiling_info_test.cc",
diff --git a/runtime/app_info.cc b/runtime/app_info.cc
index 5ec57c7660..50772b1dbf 100644
--- a/runtime/app_info.cc
+++ b/runtime/app_info.cc
@@ -155,5 +155,17 @@ std::string AppInfo::GetPrimaryApkReferenceProfile() {
return "";
}
+std::string AppInfo::GetPrimaryApkPath() {
+ MutexLock mu(Thread::Current(), update_mutex_);
+
+ for (const auto& it : registered_code_locations_) {
+ const CodeLocationInfo& cli = it.second;
+ if (cli.code_type == CodeType::kPrimaryApk) {
+ return it.first;
+ }
+ }
+ return kUnknownValue;
+}
+
} // namespace art
diff --git a/runtime/app_info.h b/runtime/app_info.h
index 20cec94a78..2bb63774b7 100644
--- a/runtime/app_info.h
+++ b/runtime/app_info.h
@@ -85,6 +85,13 @@ class AppInfo {
// Returns an empty string if there is no primary APK.
std::string GetPrimaryApkReferenceProfile();
+ // Returns the path of the primary APK.
+ // If there are multiple primary APKs registed via RegisterAppInfo, the method
+ // will return the path of the first APK, sorted by the location name.
+ //
+ // Returns an empty string if there is no primary APK.
+ std::string GetPrimaryApkPath();
+
// Whether we've received a call to RegisterAppInfo.
bool HasRegisteredAppInfo();
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index b2711d968a..d87040ab49 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -777,13 +777,6 @@ inline bool ArtMethod::CounterIsHot() {
return hotness_count_ == 0;
}
-inline bool ArtMethod::CounterHasReached(uint16_t samples, uint16_t threshold) {
- DCHECK(!IsAbstract());
- DCHECK_EQ(threshold, Runtime::Current()->GetJITOptions()->GetWarmupThreshold());
- DCHECK_LE(samples, threshold);
- return hotness_count_ <= (threshold - samples);
-}
-
inline uint16_t ArtMethod::GetCounter() {
DCHECK(!IsAbstract());
return hotness_count_;
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index 40022c9d6d..dac2c08a46 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -660,10 +660,12 @@ const OatQuickMethodHeader* ArtMethod::GetOatQuickMethodHeader(uintptr_t pc) {
<< ", jit= " << jit;
}
// We are running the GenericJNI stub. The entrypoint may point
- // to different entrypoints or to a JIT-compiled JNI stub.
+ // to different entrypoints, to a JIT-compiled JNI stub, or to a shared boot
+ // image stub.
DCHECK(class_linker->IsQuickGenericJniStub(existing_entry_point) ||
class_linker->IsQuickResolutionStub(existing_entry_point) ||
- (jit != nullptr && jit->GetCodeCache()->ContainsPc(existing_entry_point)))
+ (jit != nullptr && jit->GetCodeCache()->ContainsPc(existing_entry_point)) ||
+ (class_linker->FindBootJniStub(this) != nullptr))
<< " method: " << PrettyMethod()
<< " entrypoint: " << existing_entry_point
<< " size: " << OatQuickMethodHeader::FromEntryPoint(existing_entry_point)->GetCodeSize()
@@ -794,16 +796,18 @@ void ArtMethod::CopyFrom(ArtMethod* src, PointerSize image_pointer_size) {
const void* entry_point = GetEntryPointFromQuickCompiledCodePtrSize(image_pointer_size);
if (runtime->UseJitCompilation()) {
if (runtime->GetJit()->GetCodeCache()->ContainsPc(entry_point)) {
- SetEntryPointFromQuickCompiledCodePtrSize(
- src->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge(),
- image_pointer_size);
+ SetNativePointer(EntryPointFromQuickCompiledCodeOffset(image_pointer_size),
+ src->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge(),
+ image_pointer_size);
}
}
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
if (interpreter::IsNterpSupported() && class_linker->IsNterpEntryPoint(entry_point)) {
// If the entrypoint is nterp, it's too early to check if the new method
// will support it. So for simplicity, use the interpreter bridge.
- SetEntryPointFromQuickCompiledCodePtrSize(GetQuickToInterpreterBridge(), image_pointer_size);
+ SetNativePointer(EntryPointFromQuickCompiledCodeOffset(image_pointer_size),
+ GetQuickToInterpreterBridge(),
+ image_pointer_size);
}
// Clear the data pointer, it will be set if needed by the caller.
@@ -920,4 +924,40 @@ ALWAYS_INLINE static inline void DoGetAccessFlagsHelper(ArtMethod* method)
method->GetDeclaringClass<kReadBarrierOption>()->IsErroneous());
}
+template <typename T>
+bool CompareExchange(uintptr_t ptr, uintptr_t old_value, uintptr_t new_value) {
+ std::atomic<T>* atomic_addr = reinterpret_cast<std::atomic<T>*>(ptr);
+ T cast_old_value = dchecked_integral_cast<T>(old_value);
+ return reinterpret_cast<const void*>(
+ atomic_addr->compare_exchange_strong(cast_old_value,
+ dchecked_integral_cast<T>(new_value),
+ std::memory_order_relaxed));
+}
+
+void ArtMethod::SetEntryPointFromQuickCompiledCodePtrSize(
+ const void* entry_point_from_quick_compiled_code, PointerSize pointer_size) {
+ const void* current_entry_point = GetEntryPointFromQuickCompiledCodePtrSize(pointer_size);
+ if (current_entry_point == entry_point_from_quick_compiled_code) {
+ return;
+ }
+
+ // Do an atomic exchange to avoid potentially unregistering JIT code twice.
+ MemberOffset offset = EntryPointFromQuickCompiledCodeOffset(pointer_size);
+ uintptr_t old_value = reinterpret_cast<uintptr_t>(current_entry_point);
+ uintptr_t new_value = reinterpret_cast<uintptr_t>(entry_point_from_quick_compiled_code);
+ uintptr_t ptr = reinterpret_cast<uintptr_t>(this) + offset.Uint32Value();
+ bool success = (pointer_size == PointerSize::k32)
+ ? CompareExchange<uint32_t>(ptr, old_value, new_value)
+ : CompareExchange<uint64_t>(ptr, old_value, new_value);
+
+ // If we successfully updated the entrypoint and the old entrypoint is JITted
+ // code, register the old entrypoint as zombie.
+ jit::Jit* jit = Runtime::Current()->GetJit();
+ if (success &&
+ jit != nullptr &&
+ jit->GetCodeCache()->ContainsPc(current_entry_point)) {
+ jit->GetCodeCache()->AddZombieCode(this, current_entry_point);
+ }
+}
+
} // namespace art
diff --git a/runtime/art_method.h b/runtime/art_method.h
index 96a204055b..0a6cda65fd 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -359,11 +359,15 @@ class EXPORT ArtMethod final {
}
void SetMemorySharedMethod() REQUIRES_SHARED(Locks::mutator_lock_) {
- uint32_t access_flags = GetAccessFlags();
- if (!IsIntrinsic(access_flags) && !IsAbstract(access_flags)) {
- AddAccessFlags(kAccMemorySharedMethod);
- SetHotCounter();
- }
+ DCHECK(!IsIntrinsic());
+ DCHECK(!IsAbstract());
+ AddAccessFlags(kAccMemorySharedMethod);
+ }
+
+ static uint32_t SetMemorySharedMethod(uint32_t access_flags) {
+ DCHECK(!IsIntrinsic(access_flags));
+ DCHECK(!IsAbstract(access_flags));
+ return access_flags | kAccMemorySharedMethod;
}
void ClearMemorySharedMethod() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -597,6 +601,15 @@ class EXPORT ArtMethod final {
ClearAccessFlags(kAccNterpInvokeFastPathFlag);
}
+ static uint32_t ClearNterpFastPathFlags(uint32_t access_flags) {
+ // `kAccNterpEntryPointFastPathFlag` has a different use for native methods.
+ if (!IsNative(access_flags)) {
+ access_flags &= ~kAccNterpEntryPointFastPathFlag;
+ }
+ access_flags &= ~kAccNterpInvokeFastPathFlag;
+ return access_flags;
+ }
+
// Returns whether the method is a string constructor. The method must not
// be a class initializer. (Class initializers are called from a different
// context where we do not need to check for string constructors.)
@@ -766,11 +779,7 @@ class EXPORT ArtMethod final {
}
ALWAYS_INLINE void SetEntryPointFromQuickCompiledCodePtrSize(
const void* entry_point_from_quick_compiled_code, PointerSize pointer_size)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- SetNativePointer(EntryPointFromQuickCompiledCodeOffset(pointer_size),
- entry_point_from_quick_compiled_code,
- pointer_size);
- }
+ REQUIRES_SHARED(Locks::mutator_lock_);
static constexpr MemberOffset DataOffset(PointerSize pointer_size) {
return MemberOffset(PtrSizedFieldsOffset(pointer_size) + OFFSETOF_MEMBER(
@@ -814,6 +823,15 @@ class EXPORT ArtMethod final {
return (GetAccessFlags() & kAccSingleImplementation) != 0;
}
+ static uint32_t SetHasSingleImplementation(uint32_t access_flags, bool single_impl) {
+ DCHECK(!IsIntrinsic(access_flags)) << "conflict with intrinsic bits";
+ if (single_impl) {
+ return access_flags | kAccSingleImplementation;
+ } else {
+ return access_flags & ~kAccSingleImplementation;
+ }
+ }
+
// Takes a method and returns a 'canonical' one if the method is default (and therefore
// potentially copied from some other class). For example, this ensures that the debugger does not
// get confused as to which method we are in.
@@ -993,7 +1011,6 @@ class EXPORT ArtMethod final {
ALWAYS_INLINE void UpdateCounter(int32_t new_samples);
ALWAYS_INLINE void SetHotCounter();
ALWAYS_INLINE bool CounterIsHot();
- ALWAYS_INLINE bool CounterHasReached(uint16_t samples, uint16_t threshold);
ALWAYS_INLINE uint16_t GetCounter();
ALWAYS_INLINE bool CounterHasChanged(uint16_t threshold);
@@ -1192,6 +1209,8 @@ class EXPORT ArtMethod final {
// Used by GetName and GetNameView to share common code.
const char* GetRuntimeMethodName() REQUIRES_SHARED(Locks::mutator_lock_);
+ friend class RuntimeImageHelper; // For SetNativePointer.
+
DISALLOW_COPY_AND_ASSIGN(ArtMethod); // Need to use CopyFrom to deal with 32 vs 64 bits.
};
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 03611ebc20..52caa9f1cf 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -4163,9 +4163,13 @@ void ClassLinker::LoadMethod(const DexFile& dex_file,
}
}
- if (Runtime::Current()->IsZygote() &&
+ if ((access_flags & kAccAbstract) == 0u &&
+ Runtime::Current()->IsZygote() &&
!Runtime::Current()->GetJITOptions()->GetProfileSaverOptions().GetProfileBootClassPath()) {
+ DCHECK(!ArtMethod::IsAbstract(access_flags));
+ DCHECK(!ArtMethod::IsIntrinsic(access_flags));
dst->SetMemorySharedMethod();
+ dst->SetHotCounter();
}
}
@@ -5857,13 +5861,13 @@ bool ClassLinker::InitializeClass(Thread* self,
WrapExceptionInInitializer(klass);
mirror::Class::SetStatus(klass, ClassStatus::kErrorResolved, self);
success = false;
- } else if (Runtime::Current()->IsTransactionAborted()) {
+ } else if (Runtime::Current()->IsActiveTransaction() && IsTransactionAborted()) {
// The exception thrown when the transaction aborted has been caught and cleared
// so we need to throw it again now.
VLOG(compiler) << "Return from class initializer of "
<< mirror::Class::PrettyDescriptor(klass.Get())
<< " without exception while transaction was aborted: re-throw it now.";
- runtime->ThrowTransactionAbortError(self);
+ ThrowTransactionAbortError(self);
mirror::Class::SetStatus(klass, ClassStatus::kErrorResolved, self);
success = false;
} else {
@@ -11168,26 +11172,166 @@ void ClassLinker::SetEnablePublicSdkChecks([[maybe_unused]] bool enabled) {
}
bool ClassLinker::TransactionWriteConstraint(
- [[maybe_unused]] Thread* self, [[maybe_unused]] ObjPtr<mirror::Object> obj) const {
+ [[maybe_unused]] Thread* self, [[maybe_unused]] ObjPtr<mirror::Object> obj) {
// Should not be called on ClassLinker, only on AotClassLinker that overrides this.
LOG(FATAL) << "UNREACHABLE";
UNREACHABLE();
}
bool ClassLinker::TransactionWriteValueConstraint(
- [[maybe_unused]] Thread* self, [[maybe_unused]] ObjPtr<mirror::Object> value) const {
+ [[maybe_unused]] Thread* self, [[maybe_unused]] ObjPtr<mirror::Object> value) {
// Should not be called on ClassLinker, only on AotClassLinker that overrides this.
LOG(FATAL) << "UNREACHABLE";
UNREACHABLE();
}
bool ClassLinker::TransactionAllocationConstraint(
- [[maybe_unused]] Thread* self, [[maybe_unused]] ObjPtr<mirror::Class> klass) const {
+ [[maybe_unused]] Thread* self, [[maybe_unused]] ObjPtr<mirror::Class> klass) {
// Should not be called on ClassLinker, only on AotClassLinker that overrides this.
LOG(FATAL) << "UNREACHABLE";
UNREACHABLE();
}
+void ClassLinker::RecordWriteFieldBoolean([[maybe_unused]] mirror::Object* obj,
+ [[maybe_unused]] MemberOffset field_offset,
+ [[maybe_unused]] uint8_t value,
+ [[maybe_unused]] bool is_volatile) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWriteFieldByte([[maybe_unused]] mirror::Object* obj,
+ [[maybe_unused]] MemberOffset field_offset,
+ [[maybe_unused]] int8_t value,
+ [[maybe_unused]] bool is_volatile) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWriteFieldChar([[maybe_unused]] mirror::Object* obj,
+ [[maybe_unused]] MemberOffset field_offset,
+ [[maybe_unused]] uint16_t value,
+ [[maybe_unused]] bool is_volatile) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWriteFieldShort([[maybe_unused]] mirror::Object* obj,
+ [[maybe_unused]] MemberOffset field_offset,
+ [[maybe_unused]] int16_t value,
+ [[maybe_unused]] bool is_volatile) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWriteField32([[maybe_unused]] mirror::Object* obj,
+ [[maybe_unused]] MemberOffset field_offset,
+ [[maybe_unused]] uint32_t value,
+ [[maybe_unused]] bool is_volatile) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWriteField64([[maybe_unused]] mirror::Object* obj,
+ [[maybe_unused]] MemberOffset field_offset,
+ [[maybe_unused]] uint64_t value,
+ [[maybe_unused]] bool is_volatile) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWriteFieldReference([[maybe_unused]] mirror::Object* obj,
+ [[maybe_unused]] MemberOffset field_offset,
+ [[maybe_unused]] ObjPtr<mirror::Object> value,
+ [[maybe_unused]] bool is_volatile) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWriteArray([[maybe_unused]] mirror::Array* array,
+ [[maybe_unused]] size_t index,
+ [[maybe_unused]] uint64_t value) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordStrongStringInsertion([[maybe_unused]] ObjPtr<mirror::String> s) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWeakStringInsertion([[maybe_unused]] ObjPtr<mirror::String> s) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordStrongStringRemoval([[maybe_unused]] ObjPtr<mirror::String> s) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordWeakStringRemoval([[maybe_unused]] ObjPtr<mirror::String> s) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordResolveString([[maybe_unused]] ObjPtr<mirror::DexCache> dex_cache,
+ [[maybe_unused]] dex::StringIndex string_idx) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::RecordResolveMethodType([[maybe_unused]] ObjPtr<mirror::DexCache> dex_cache,
+ [[maybe_unused]] dex::ProtoIndex proto_idx) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::ThrowTransactionAbortError([[maybe_unused]] Thread* self) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::AbortTransactionF(
+ [[maybe_unused]] Thread* self, [[maybe_unused]] const char* fmt, ...) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::AbortTransactionV([[maybe_unused]] Thread* self,
+ [[maybe_unused]] const char* fmt,
+ [[maybe_unused]] va_list args) {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+bool ClassLinker::IsTransactionAborted() const {
+ // Should not be called on ClassLinker, only on AotClassLinker that overrides this.
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+}
+
+void ClassLinker::VisitTransactionRoots([[maybe_unused]] RootVisitor* visitor) {
+ // Nothing to do for normal `ClassLinker`, only `AotClassLinker` handles transactions.
+}
+
void ClassLinker::RemoveDexFromCaches(const DexFile& dex_file) {
ReaderMutexLock mu(Thread::Current(), *Locks::dex_lock_);
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index d27664a897..daf9534c3a 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -879,23 +879,83 @@ class EXPORT ClassLinker {
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_, !Roles::uninterruptible_);
- // Verifies if the method is accesible according to the SdkChecker (if installed).
+ // Verifies if the method is accessible according to the SdkChecker (if installed).
virtual bool DenyAccessBasedOnPublicSdk(ArtMethod* art_method) const
REQUIRES_SHARED(Locks::mutator_lock_);
- // Verifies if the field is accesible according to the SdkChecker (if installed).
+ // Verifies if the field is accessible according to the SdkChecker (if installed).
virtual bool DenyAccessBasedOnPublicSdk(ArtField* art_field) const
REQUIRES_SHARED(Locks::mutator_lock_);
- // Verifies if the descriptor is accesible according to the SdkChecker (if installed).
+ // Verifies if the descriptor is accessible according to the SdkChecker (if installed).
virtual bool DenyAccessBasedOnPublicSdk(std::string_view type_descriptor) const;
// Enable or disable public sdk checks.
virtual void SetEnablePublicSdkChecks(bool enabled);
// Transaction constraint checks for AOT compilation.
- virtual bool TransactionWriteConstraint(Thread* self, ObjPtr<mirror::Object> obj) const
- REQUIRES_SHARED(Locks::mutator_lock_);
- virtual bool TransactionWriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value) const
- REQUIRES_SHARED(Locks::mutator_lock_);
- virtual bool TransactionAllocationConstraint(Thread* self, ObjPtr<mirror::Class> klass) const
+ virtual bool TransactionWriteConstraint(Thread* self, ObjPtr<mirror::Object> obj)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual bool TransactionWriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual bool TransactionAllocationConstraint(Thread* self, ObjPtr<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Transaction bookkeeping for AOT compilation.
+ virtual void RecordWriteFieldBoolean(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint8_t value,
+ bool is_volatile);
+ virtual void RecordWriteFieldByte(mirror::Object* obj,
+ MemberOffset field_offset,
+ int8_t value,
+ bool is_volatile);
+ virtual void RecordWriteFieldChar(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint16_t value,
+ bool is_volatile);
+ virtual void RecordWriteFieldShort(mirror::Object* obj,
+ MemberOffset field_offset,
+ int16_t value,
+ bool is_volatile);
+ virtual void RecordWriteField32(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint32_t value,
+ bool is_volatile);
+ virtual void RecordWriteField64(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint64_t value,
+ bool is_volatile);
+ virtual void RecordWriteFieldReference(mirror::Object* obj,
+ MemberOffset field_offset,
+ ObjPtr<mirror::Object> value,
+ bool is_volatile)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual void RecordWriteArray(mirror::Array* array, size_t index, uint64_t value)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual void RecordStrongStringInsertion(ObjPtr<mirror::String> s)
+ REQUIRES(Locks::intern_table_lock_);
+ virtual void RecordWeakStringInsertion(ObjPtr<mirror::String> s)
+ REQUIRES(Locks::intern_table_lock_);
+ virtual void RecordStrongStringRemoval(ObjPtr<mirror::String> s)
+ REQUIRES(Locks::intern_table_lock_);
+ virtual void RecordWeakStringRemoval(ObjPtr<mirror::String> s)
+ REQUIRES(Locks::intern_table_lock_);
+ virtual void RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual void RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
+ dex::ProtoIndex proto_idx)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Aborting transactions for AOT compilation.
+ virtual void ThrowTransactionAbortError(Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual void AbortTransactionF(Thread* self, const char* fmt, ...)
+ __attribute__((__format__(__printf__, 3, 4)))
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual void AbortTransactionV(Thread* self, const char* fmt, va_list args)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ virtual bool IsTransactionAborted() const;
+
+ // Vist transaction roots for AOT compilation.
+ virtual void VisitTransactionRoots(RootVisitor* visitor)
REQUIRES_SHARED(Locks::mutator_lock_);
void RemoveDexFromCaches(const DexFile& dex_file);
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 922d678920..311391dbbf 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -58,6 +58,7 @@
#include "mirror/object_array-alloc-inl.h"
#include "native/dalvik_system_DexFile.h"
#include "noop_compiler_callbacks.h"
+#include "oat/aot_class_linker.h"
#include "profile/profile_compilation_info.h"
#include "runtime-inl.h"
#include "runtime_intrinsics.h"
@@ -543,25 +544,6 @@ std::string CommonRuntimeTestImpl::GetSystemImageFile() {
return GetImageDirectory() + "/" + isa + "/boot.art";
}
-void CommonRuntimeTestImpl::EnterTransactionMode() {
- CHECK(!Runtime::Current()->IsActiveTransaction());
- Runtime::Current()->EnterTransactionMode(/*strict=*/ false, /*root=*/ nullptr);
-}
-
-void CommonRuntimeTestImpl::ExitTransactionMode() {
- Runtime::Current()->ExitTransactionMode();
- CHECK(!Runtime::Current()->IsActiveTransaction());
-}
-
-void CommonRuntimeTestImpl::RollbackAndExitTransactionMode() {
- Runtime::Current()->RollbackAndExitTransactionMode();
- CHECK(!Runtime::Current()->IsActiveTransaction());
-}
-
-bool CommonRuntimeTestImpl::IsTransactionAborted() {
- return Runtime::Current()->IsTransactionAborted();
-}
-
void CommonRuntimeTestImpl::VisitDexes(ArrayRef<const std::string> dexes,
const std::function<void(MethodReference)>& method_visitor,
const std::function<void(TypeReference)>& class_visitor,
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 8f9d7a80ff..87c8d111f0 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -223,11 +223,6 @@ class CommonRuntimeTestImpl : public CommonArtTestImpl {
// Returns the directory where the pre-compiled boot.art can be found.
static std::string GetImageLocation();
static std::string GetSystemImageFile();
-
- static void EnterTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
- static void ExitTransactionMode();
- static void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
- static bool IsTransactionAborted();
};
template <typename TestType>
diff --git a/runtime/common_transaction_test.cc b/runtime/common_transaction_test.cc
new file mode 100644
index 0000000000..85f176944a
--- /dev/null
+++ b/runtime/common_transaction_test.cc
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "common_transaction_test.h"
+
+#include "oat/aot_class_linker.h"
+#include "runtime.h"
+
+namespace art HIDDEN {
+
+void CommonTransactionTestImpl::EnterTransactionMode() {
+ CHECK(!Runtime::Current()->IsActiveTransaction());
+ AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
+ class_linker->EnterTransactionMode(/*strict=*/ false, /*root=*/ nullptr);
+}
+
+void CommonTransactionTestImpl::ExitTransactionMode() {
+ AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
+ class_linker->ExitTransactionMode();
+ CHECK(!Runtime::Current()->IsActiveTransaction());
+}
+
+void CommonTransactionTestImpl::RollbackAndExitTransactionMode() {
+ AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
+ class_linker->RollbackAndExitTransactionMode();
+ CHECK(!Runtime::Current()->IsActiveTransaction());
+}
+
+bool CommonTransactionTestImpl::IsTransactionAborted() {
+ if (!Runtime::Current()->IsActiveTransaction()) {
+ return false;
+ }
+ AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
+ return class_linker->IsTransactionAborted();
+}
+
+} // namespace art
diff --git a/runtime/common_transaction_test.h b/runtime/common_transaction_test.h
new file mode 100644
index 0000000000..937662009f
--- /dev/null
+++ b/runtime/common_transaction_test.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef ART_RUNTIME_COMMON_TRANSACTION_TEST_H_
+#define ART_RUNTIME_COMMON_TRANSACTION_TEST_H_
+
+#include "common_runtime_test.h"
+
+namespace art HIDDEN {
+
+class CommonTransactionTestImpl {
+ protected:
+ static void EnterTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
+ static void ExitTransactionMode();
+ static void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
+ static bool IsTransactionAborted();
+};
+
+template <typename TestType>
+class CommonTransactionTestBase : public TestType, public CommonTransactionTestImpl {};
+
+using CommonTransactionTest = CommonTransactionTestBase<CommonRuntimeTest>;
+
+} // namespace art
+
+#endif // ART_RUNTIME_COMMON_TRANSACTION_TEST_H_
diff --git a/runtime/exec_utils.cc b/runtime/exec_utils.cc
index 1266ed18f7..17550d5aae 100644
--- a/runtime/exec_utils.cc
+++ b/runtime/exec_utils.cc
@@ -17,14 +17,12 @@
#include "exec_utils.h"
#include <poll.h>
+#include <signal.h>
+#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
-#ifdef __BIONIC__
-#include <sys/pidfd.h>
-#endif
-
#include <chrono>
#include <climits>
#include <condition_variable>
@@ -45,6 +43,7 @@
#include "android-base/strings.h"
#include "android-base/unique_fd.h"
#include "base/macros.h"
+#include "base/pidfd.h"
#include "base/utils.h"
#include "runtime.h"
@@ -65,6 +64,10 @@ std::string ToCommandLine(const std::vector<std::string>& args) {
// If there is a runtime (Runtime::Current != nullptr) then the subprocess is created with the
// same environment that existed when the runtime was started.
// Returns the process id of the child process on success, -1 otherwise.
+// The child is killed as soon as the caller thread dies, the caller thread needs to stay around and
+// wait for the child. Therefore, this function is most suitable to be used by
+// `ExecAndReturnResult`, which does the wait. It's not suitable to be used for fire-and-forget
+// exec's.
pid_t ExecWithoutWait(const std::vector<std::string>& arg_vector, std::string* error_msg) {
// Convert the args to char pointers.
const char* program = arg_vector[0].c_str();
@@ -83,6 +86,12 @@ pid_t ExecWithoutWait(const std::vector<std::string>& arg_vector, std::string* e
// change process groups, so we don't get reaped by ProcessManager
setpgid(0, 0);
+ // Kill the child process when the parent process dies.
+ if (prctl(PR_SET_PDEATHSIG, SIGKILL) != 0) {
+ // This should never happen.
+ PLOG(FATAL) << "Failed to call prctl";
+ }
+
// (b/30160149): protect subprocesses from modifications to LD_LIBRARY_PATH, etc.
// Use the snapshot of the environment from the time the runtime was created.
char** envp = (Runtime::Current() == nullptr) ? nullptr : Runtime::Current()->GetEnvSnapshot();
@@ -324,17 +333,7 @@ bool ExecUtils::Exec(const std::vector<std::string>& arg_vector, std::string* er
return true;
}
-unique_fd ExecUtils::PidfdOpen(pid_t pid) const {
-#ifdef __BIONIC__
- return unique_fd(pidfd_open(pid, /*flags=*/0));
-#else
- // There is no glibc wrapper for pidfd_open.
-#ifndef SYS_pidfd_open
- constexpr int SYS_pidfd_open = 434;
-#endif
- return unique_fd(syscall(SYS_pidfd_open, pid, /*flags=*/0));
-#endif
-}
+unique_fd ExecUtils::PidfdOpen(pid_t pid) const { return art::PidfdOpen(pid, /*flags=*/0); }
std::string ExecUtils::GetProcStat(pid_t pid) const {
std::string stat_content;
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index d02701880b..e4b308e846 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -583,7 +583,7 @@ class ConcurrentCopying::FlipCallback : public Closure {
if (UNLIKELY(runtime->IsActiveTransaction())) {
CHECK(runtime->IsAotCompiler());
TimingLogger::ScopedTiming split3("(Paused)VisitTransactionRoots", cc->GetTimings());
- runtime->VisitTransactionRoots(cc);
+ runtime->GetClassLinker()->VisitTransactionRoots(cc);
}
if (kUseBakerReadBarrier && kGrayDirtyImmuneObjects) {
cc->GrayAllNewlyDirtyImmuneObjects();
diff --git a/runtime/gc/collector/mark_compact-inl.h b/runtime/gc/collector/mark_compact-inl.h
index 18095e8b58..24d47638b1 100644
--- a/runtime/gc/collector/mark_compact-inl.h
+++ b/runtime/gc/collector/mark_compact-inl.h
@@ -266,9 +266,10 @@ inline bool MarkCompact::VerifyRootSingleUpdate(void* root,
mirror::Object* old_ref,
const RootInfo& info) {
// ASAN promotes stack-frames to heap in order to detect
- // stack-use-after-return issues. So skip using this double-root update
- // detection on ASAN as well.
- if (kIsDebugBuild && !kMemoryToolIsAvailable) {
+ // stack-use-after-return issues. And HWASAN has pointers tagged, which makes
+ // it difficult to recognize and prevent stack pointers from being checked.
+ // So skip using double-root update detection on ASANs.
+ if (kIsDebugBuild && !kMemoryToolIsAvailable && !kHwAsanEnabled) {
void* stack_low_addr = stack_low_addr_;
void* stack_high_addr = stack_high_addr_;
if (!HasAddress(old_ref)) {
@@ -279,15 +280,21 @@ inline bool MarkCompact::VerifyRootSingleUpdate(void* root,
stack_low_addr = self->GetStackEnd();
stack_high_addr = reinterpret_cast<char*>(stack_low_addr) + self->GetStackSize();
}
- if (root < stack_low_addr || root > stack_high_addr) {
+ if (std::less<void*>{}(root, stack_low_addr) || std::greater<void*>{}(root, stack_high_addr)) {
bool inserted;
{
MutexLock mu(self, lock_);
inserted = updated_roots_->insert(root).second;
}
- DCHECK(inserted) << "root=" << root << " old_ref=" << old_ref
- << " stack_low_addr=" << stack_low_addr
- << " stack_high_addr=" << stack_high_addr;
+ if (!inserted) {
+ std::ostringstream oss;
+ heap_->DumpSpaces(oss);
+ MemMap::DumpMaps(oss, /* terse= */ true);
+ CHECK(inserted) << "root=" << root << " old_ref=" << old_ref
+ << " stack_low_addr=" << stack_low_addr
+ << " stack_high_addr=" << stack_high_addr << " maps\n"
+ << oss.str();
+ }
}
DCHECK(reinterpret_cast<uint8_t*>(old_ref) >= black_allocations_begin_ ||
live_words_bitmap_->Test(old_ref))
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
index ac6a7badb4..292ddc09f4 100644
--- a/runtime/gc/collector/mark_compact.cc
+++ b/runtime/gc/collector/mark_compact.cc
@@ -198,9 +198,10 @@ static gc::CollectorType FetchCmdlineGcType() {
std::string argv;
gc::CollectorType gc_type = gc::CollectorType::kCollectorTypeNone;
if (android::base::ReadFileToString("/proc/self/cmdline", &argv)) {
- if (argv.find("-Xgc:CMC") != std::string::npos) {
+ auto pos = argv.rfind("-Xgc:");
+ if (argv.substr(pos + 5, 3) == "CMC") {
gc_type = gc::CollectorType::kCollectorTypeCMC;
- } else if (argv.find("-Xgc:CC") != std::string::npos) {
+ } else if (argv.substr(pos + 5, 2) == "CC") {
gc_type = gc::CollectorType::kCollectorTypeCC;
}
}
@@ -1066,6 +1067,7 @@ class MarkCompact::ConcurrentCompactionGcTask : public SelfDeletingTask {
};
void MarkCompact::PrepareForCompaction() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
uint8_t* space_begin = bump_pointer_space_->Begin();
size_t vector_len = (black_allocations_begin_ - space_begin) / kOffsetChunkSize;
DCHECK_LE(vector_len, vector_length_);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index a8c6886e1f..07340bd7d3 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -4816,5 +4816,19 @@ std::string Heap::GetForegroundCollectorName() {
return oss.str();
}
+bool Heap::HasAppImageSpaceFor(const std::string& dex_location) const {
+ ScopedObjectAccess soa(Thread::Current());
+ for (space::ContinuousSpace* space : continuous_spaces_) {
+ // An image space is either a boot image space or an app image space.
+ if (space->IsImageSpace() &&
+ !IsBootImageAddress(space->Begin()) &&
+ (space->AsImageSpace()->GetOatFile()->GetOatDexFiles()[0]->GetDexFileLocation() ==
+ dex_location)) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace gc
} // namespace art
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 4c99c182c3..c51fcc54e0 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -809,16 +809,7 @@ class Heap {
bool HasBootImageSpace() const {
return !boot_image_spaces_.empty();
}
- bool HasAppImageSpace() const {
- ScopedObjectAccess soa(Thread::Current());
- for (const space::ContinuousSpace* space : continuous_spaces_) {
- // An image space is either a boot image space or an app image space.
- if (space->IsImageSpace() && !IsBootImageAddress(space->Begin())) {
- return true;
- }
- }
- return false;
- }
+ bool HasAppImageSpaceFor(const std::string& dex_location) const;
ReferenceProcessor* GetReferenceProcessor() {
return reference_processor_.get();
diff --git a/runtime/hidden_api_test.cc b/runtime/hidden_api_test.cc
index 7dc3d901df..96a5822973 100644
--- a/runtime/hidden_api_test.cc
+++ b/runtime/hidden_api_test.cc
@@ -42,20 +42,26 @@ static constexpr uint64_t kHideMaxtargetsdkQHiddenApis = 149994052;
static constexpr uint64_t kAllowTestApiAccess = 166236554;
-static bool Copy(const std::string& src, const std::string& dst, /*out*/ std::string* error_msg) {
- std::ifstream src_stream(src, std::ios::binary);
- std::ofstream dst_stream(dst, std::ios::binary);
- dst_stream << src_stream.rdbuf();
- src_stream.close();
- dst_stream.close();
- if (src_stream.good() && dst_stream.good()) {
- return true;
- } else {
- *error_msg = "Copy " + src + " => " + dst + " (src_good="
- + (src_stream.good() ? "true" : "false") + ", dst_good="
- + (dst_stream.good() ? "true" : "false") + ")";
+static bool Copy(const char* src_filename, File* dst, /*out*/ std::string* error_msg) {
+ std::unique_ptr<File> src(OS::OpenFileForReading(src_filename));
+ if (src == nullptr) {
+ *error_msg = StringPrintf("Failed to open for reading: %s", src_filename);
+ return false;
+ }
+ int64_t length = src->GetLength();
+ if (length < 0) {
+ *error_msg = "Failed to get file length.";
+ return false;
+ }
+ if (!dst->Copy(src.get(), /*offset=*/ 0, length)) {
+ *error_msg = "Failed to copy file contents.";
+ return false;
+ }
+ if (dst->Flush() != 0) {
+ *error_msg = "Failed to flush.";
return false;
}
+ return true;
}
static bool LoadDexFiles(const std::string& path,
@@ -91,11 +97,11 @@ static bool LoadDexFiles(const std::string& path,
return true;
}
-static bool Remove(const std::string& path, /*out*/ std::string* error_msg) {
- if (TEMP_FAILURE_RETRY(remove(path.c_str())) == 0) {
+static bool Remove(const char* path, /*out*/ std::string* error_msg) {
+ if (TEMP_FAILURE_RETRY(remove(path)) == 0) {
return true;
}
- *error_msg = StringPrintf("Unable to remove(\"%s\"): %s", path.c_str(), strerror(errno));
+ *error_msg = StringPrintf("Unable to remove(\"%s\"): %s", path, strerror(errno));
return false;
}
@@ -191,13 +197,43 @@ class HiddenApiTest : public CommonRuntimeTest {
}
void TestLocation(const std::string& location, hiddenapi::Domain expected_domain) {
+ // Create a temp file with a unique name based on `location` to isolate tests
+ // that may run in parallel. b/238730923
+ const std::string_view suffix = ".jar";
+ const std::string_view placeholder = "XXXXXX"; // See `mkstemps()`.
+ ASSERT_TRUE(EndsWith(location, suffix));
+ std::unique_ptr<char[]> unique_location(new char[location.length() + placeholder.length() + 1]);
+ ASSERT_TRUE(unique_location != nullptr);
+ size_t stem_length = location.length() - suffix.length();
+ memcpy(unique_location.get(), location.data(), stem_length);
+ memcpy(unique_location.get() + stem_length, placeholder.data(), placeholder.length());
+ memcpy(unique_location.get() + stem_length + placeholder.length(),
+ location.data() + stem_length,
+ suffix.length());
+ unique_location[location.length() + placeholder.length()] = 0;
+ int fd = mkstemps(unique_location.get(), suffix.length());
+ ASSERT_TRUE(fd != -1) << strerror(errno);
+
+ // Copy "Main" to the temp file.
+ std::string error_msg;
+ {
+ File file(fd, /*check_usage=*/ true);
+ bool copied = Copy(GetTestDexFileName("Main").c_str(), &file, &error_msg);
+ int flush_close_result = file.FlushCloseOrErase();
+ if (!copied && flush_close_result == 0) {
+ // Silently remove the temp file before reporting the `Copy()` failure.
+ std::string ignored_error_msg;
+ Remove(unique_location.get(), &ignored_error_msg);
+ }
+ ASSERT_TRUE(copied) << error_msg;
+ ASSERT_EQ(flush_close_result, 0);
+ }
+
ScopedObjectAccess soa(Thread::Current());
std::vector<std::unique_ptr<const DexFile>> dex_files;
- std::string error_msg;
ObjPtr<mirror::ClassLoader> class_loader;
-
- ASSERT_TRUE(Copy(GetTestDexFileName("Main"), location, &error_msg)) << error_msg;
- ASSERT_TRUE(LoadDexFiles(location, soa.Self(), &dex_files, &class_loader, &error_msg))
+ ASSERT_TRUE(
+ LoadDexFiles(unique_location.get(), soa.Self(), &dex_files, &class_loader, &error_msg))
<< error_msg;
ASSERT_GE(dex_files.size(), 1u);
ASSERT_TRUE(CheckAllDexFilesInDomain(class_loader,
@@ -206,7 +242,7 @@ class HiddenApiTest : public CommonRuntimeTest {
&error_msg)) << error_msg;
dex_files.clear();
- ASSERT_TRUE(Remove(location, &error_msg)) << error_msg;
+ ASSERT_TRUE(Remove(unique_location.get(), &error_msg)) << error_msg;
}
protected:
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index 670fd589a2..1986b90a12 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -18,6 +18,7 @@
#include <memory>
+#include "class_linker.h"
#include "dex/utf.h"
#include "gc/collector/garbage_collector.h"
#include "gc/space/image_space.h"
@@ -149,7 +150,7 @@ void InternTable::AddNewTable() {
ObjPtr<mirror::String> InternTable::InsertStrong(ObjPtr<mirror::String> s, uint32_t hash) {
Runtime* runtime = Runtime::Current();
if (runtime->IsActiveTransaction()) {
- runtime->RecordStrongStringInsertion(s);
+ runtime->GetClassLinker()->RecordStrongStringInsertion(s);
}
if (log_new_roots_) {
new_strong_intern_roots_.push_back(GcRoot<mirror::String>(s));
@@ -161,7 +162,7 @@ ObjPtr<mirror::String> InternTable::InsertStrong(ObjPtr<mirror::String> s, uint3
ObjPtr<mirror::String> InternTable::InsertWeak(ObjPtr<mirror::String> s, uint32_t hash) {
Runtime* runtime = Runtime::Current();
if (runtime->IsActiveTransaction()) {
- runtime->RecordWeakStringInsertion(s);
+ runtime->GetClassLinker()->RecordWeakStringInsertion(s);
}
weak_interns_.Insert(s, hash);
return s;
@@ -174,7 +175,7 @@ void InternTable::RemoveStrong(ObjPtr<mirror::String> s, uint32_t hash) {
void InternTable::RemoveWeak(ObjPtr<mirror::String> s, uint32_t hash) {
Runtime* runtime = Runtime::Current();
if (runtime->IsActiveTransaction()) {
- runtime->RecordWeakStringRemoval(s);
+ runtime->GetClassLinker()->RecordWeakStringRemoval(s);
}
weak_interns_.Remove(s, hash);
}
diff --git a/runtime/interpreter/active_transaction_checker.h b/runtime/interpreter/active_transaction_checker.h
deleted file mode 100644
index 16fa6b1829..0000000000
--- a/runtime/interpreter/active_transaction_checker.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2024 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.
- */
-
-#ifndef ART_RUNTIME_INTERPRETER_ACTIVE_TRANSACTION_CHECKER_H_
-#define ART_RUNTIME_INTERPRETER_ACTIVE_TRANSACTION_CHECKER_H_
-
-#include "base/macros.h"
-#include "gc/heap.h"
-#include "mirror/object-inl.h"
-#include "runtime.h"
-#include "transaction.h"
-
-namespace art HIDDEN {
-namespace interpreter {
-
-class ActiveTransactionChecker {
- public:
- static inline bool WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Runtime* runtime = Runtime::Current();
- if (runtime->GetTransaction()->WriteConstraint(obj)) {
- DCHECK(runtime->GetHeap()->ObjectIsInBootImageSpace(obj) || obj->IsClass());
- const char* extra = runtime->GetHeap()->ObjectIsInBootImageSpace(obj) ? "boot image " : "";
- runtime->AbortTransactionF(
- self, "Can't set fields of %s%s", extra, obj->PrettyTypeOf().c_str());
- return true;
- }
- return false;
- }
-
- static inline bool WriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Runtime* runtime = Runtime::Current();
- if (runtime->GetTransaction()->WriteValueConstraint(value)) {
- DCHECK(value != nullptr);
- const char* description = value->IsClass() ? "class" : "instance of";
- ObjPtr<mirror::Class> klass = value->IsClass() ? value->AsClass() : value->GetClass();
- runtime->AbortTransactionF(
- self, "Can't store reference to %s %s", description, klass->PrettyDescriptor().c_str());
- return true;
- }
- return false;
- }
-
- static inline bool ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(obj->IsClass());
- Runtime* runtime = Runtime::Current();
- if (runtime->GetTransaction()->ReadConstraint(obj)) {
- runtime->AbortTransactionF(
- self,
- "Can't read static fields of %s since it does not belong to clinit's class.",
- obj->PrettyTypeOf().c_str());
- return true;
- }
- return false;
- }
-
- static inline bool AllocationConstraint(Thread* self, ObjPtr<mirror::Class> klass)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (klass->IsFinalizable()) {
- Runtime::Current()->AbortTransactionF(self,
- "Allocating finalizable object in transaction: %s",
- klass->PrettyDescriptor().c_str());
- return true;
- }
- return false;
- }
-};
-
-} // namespace interpreter
-} // namespace art
-
-#endif // ART_RUNTIME_INTERPRETER_ACTIVE_TRANSACTION_CHECKER_H_
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 6097077fe2..fb6f36d5e7 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -309,7 +309,6 @@ static inline JValue Execute(
shadow_frame,
ret,
instrumentation,
- accessor.InsSize(),
/* unlock_monitors= */ false);
return ret;
}
@@ -324,7 +323,6 @@ static inline JValue Execute(
shadow_frame,
ret,
instrumentation,
- accessor.InsSize(),
/* unlock_monitors= */ false);
}
return ret;
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index 080b32f3d5..f11c62bc71 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -20,6 +20,7 @@
#include "base/casts.h"
#include "base/pointer_size.h"
+#include "class_linker.h"
#include "class_root-inl.h"
#include "debugger.h"
#include "dex/dex_file_types.h"
@@ -47,7 +48,7 @@
#include "stack.h"
#include "thread-inl.h"
#include "var_handles.h"
-#include "well_known_classes.h"
+#include "well_known_classes-inl.h"
namespace art HIDDEN {
namespace interpreter {
@@ -468,35 +469,48 @@ static bool DoVarHandleInvokeCommon(Thread* self,
// Do a quick test for "visibly initialized" without a read barrier and, if that fails,
// do a thorough test for "initialized" (including load acquire) with the read barrier.
ArtField* field = WellKnownClasses::java_util_concurrent_ThreadLocalRandom_seeder;
- if (UNLIKELY(!field->GetDeclaringClass<kWithoutReadBarrier>()->IsVisiblyInitialized()) &&
- !field->GetDeclaringClass()->IsInitialized()) {
- VariableSizedHandleScope callsite_type_hs(self);
- mirror::RawMethodType callsite_type(&callsite_type_hs);
- if (!class_linker->ResolveMethodType(self,
- dex::ProtoIndex(vRegH),
- dex_cache,
- class_loader,
- callsite_type)) {
- CHECK(self->IsExceptionPending());
+ if (LIKELY(field->GetDeclaringClass<kWithoutReadBarrier>()->IsVisiblyInitialized()) ||
+ field->GetDeclaringClass()->IsInitialized()) {
+ Handle<mirror::MethodType> callsite_type(hs.NewHandle(
+ class_linker->ResolveMethodType(self, dex::ProtoIndex(vRegH), dex_cache, class_loader)));
+ if (LIKELY(callsite_type != nullptr)) {
+ return VarHandleInvokeAccessor(self,
+ shadow_frame,
+ var_handle,
+ callsite_type,
+ access_mode,
+ &operands,
+ result);
+ }
+ // This implies we couldn't resolve one or more types in this VarHandle,
+ // or we could not allocate the `MethodType` object.
+ CHECK(self->IsExceptionPending());
+ if (self->GetException()->GetClass() != WellKnownClasses::java_lang_OutOfMemoryError.Get()) {
return false;
}
- return VarHandleInvokeAccessor(self,
- shadow_frame,
- var_handle,
- callsite_type,
- access_mode,
- &operands,
- result);
- }
-
- Handle<mirror::MethodType> callsite_type(hs.NewHandle(
- class_linker->ResolveMethodType(self, dex::ProtoIndex(vRegH), dex_cache, class_loader)));
- // This implies we couldn't resolve one or more types in this VarHandle.
- if (UNLIKELY(callsite_type == nullptr)) {
+ // Clear the OOME and retry without creating an actual `MethodType` object.
+ // This prevents unexpected OOME for trivial `VarHandle` operations.
+ // It also prevents odd situations where a `VarHandle` operation succeeds but the same
+ // operation fails later because the `MethodType` object was evicted from the `DexCache`
+ // and we suddenly run out of memory to allocate a new one.
+ //
+ // We have previously seen OOMEs in the run-test `183-rmw-stress-test` with
+ // `--optimizng --no-image` (boot class path methods run in interpreter without JIT)
+ // but it probably happened on the first execution of a trivial `VarHandle` operation
+ // and not due to the `DexCache` eviction mentioned above.
+ self->ClearException();
+ }
+
+ VariableSizedHandleScope callsite_type_hs(self);
+ mirror::RawMethodType callsite_type(&callsite_type_hs);
+ if (!class_linker->ResolveMethodType(self,
+ dex::ProtoIndex(vRegH),
+ dex_cache,
+ class_loader,
+ callsite_type)) {
CHECK(self->IsExceptionPending());
return false;
}
-
return VarHandleInvokeAccessor(self,
shadow_frame,
var_handle,
@@ -1434,7 +1448,7 @@ bool DoCall(ArtMethod* called_method,
is_string_init);
}
-template <bool is_range, bool transaction_active>
+template <bool is_range>
bool DoFilledNewArray(const Instruction* inst,
const ShadowFrame& shadow_frame,
Thread* self,
@@ -1492,13 +1506,21 @@ bool DoFilledNewArray(const Instruction* inst,
} else {
inst->GetVarArgs(arg);
}
- for (int32_t i = 0; i < length; ++i) {
- size_t src_reg = is_range ? vregC + i : arg[i];
- if (is_primitive_int_component) {
- new_array->AsIntArray()->SetWithoutChecks<transaction_active>(
+ // We're initializing a newly allocated array, so we do not need to record that under
+ // a transaction. If the transaction is aborted, the whole array shall be unreachable.
+ if (LIKELY(is_primitive_int_component)) {
+ ObjPtr<mirror::IntArray> int_array = new_array->AsIntArray();
+ for (int32_t i = 0; i < length; ++i) {
+ size_t src_reg = is_range ? vregC + i : arg[i];
+ int_array->SetWithoutChecks</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(
i, shadow_frame.GetVReg(src_reg));
- } else {
- new_array->AsObjectArray<mirror::Object>()->SetWithoutChecks<transaction_active>(
+ }
+ } else {
+ ObjPtr<mirror::ObjectArray<mirror::Object>> object_array =
+ new_array->AsObjectArray<mirror::Object>();
+ for (int32_t i = 0; i < length; ++i) {
+ size_t src_reg = is_range ? vregC + i : arg[i];
+ object_array->SetWithoutChecks</*kTransactionActive=*/ false, /*kCheckTransaction=*/ false>(
i, shadow_frame.GetVRegReference(src_reg));
}
}
@@ -1507,58 +1529,11 @@ bool DoFilledNewArray(const Instruction* inst,
return true;
}
-// TODO: Use ObjPtr here.
-template<typename T>
-static void RecordArrayElementsInTransactionImpl(ObjPtr<mirror::PrimitiveArray<T>> array,
- int32_t count)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Runtime* runtime = Runtime::Current();
- for (int32_t i = 0; i < count; ++i) {
- runtime->RecordWriteArray(array.Ptr(), i, array->GetWithoutChecks(i));
- }
-}
-
-void RecordArrayElementsInTransaction(ObjPtr<mirror::Array> array, int32_t count)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(Runtime::Current()->IsActiveTransaction());
- DCHECK(array != nullptr);
- DCHECK_LE(count, array->GetLength());
- Primitive::Type primitive_component_type = array->GetClass()->GetComponentType()->GetPrimitiveType();
- switch (primitive_component_type) {
- case Primitive::kPrimBoolean:
- RecordArrayElementsInTransactionImpl(array->AsBooleanArray(), count);
- break;
- case Primitive::kPrimByte:
- RecordArrayElementsInTransactionImpl(array->AsByteArray(), count);
- break;
- case Primitive::kPrimChar:
- RecordArrayElementsInTransactionImpl(array->AsCharArray(), count);
- break;
- case Primitive::kPrimShort:
- RecordArrayElementsInTransactionImpl(array->AsShortArray(), count);
- break;
- case Primitive::kPrimInt:
- RecordArrayElementsInTransactionImpl(array->AsIntArray(), count);
- break;
- case Primitive::kPrimFloat:
- RecordArrayElementsInTransactionImpl(array->AsFloatArray(), count);
- break;
- case Primitive::kPrimLong:
- RecordArrayElementsInTransactionImpl(array->AsLongArray(), count);
- break;
- case Primitive::kPrimDouble:
- RecordArrayElementsInTransactionImpl(array->AsDoubleArray(), count);
- break;
- default:
- LOG(FATAL) << "Unsupported primitive type " << primitive_component_type
- << " in fill-array-data";
- UNREACHABLE();
- }
-}
-
void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame)
REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(shadow_frame->GetForcePopFrame() || Runtime::Current()->IsTransactionAborted());
+ DCHECK(shadow_frame->GetForcePopFrame() ||
+ (Runtime::Current()->IsActiveTransaction() &&
+ Runtime::Current()->GetClassLinker()->IsTransactionAborted()));
// Unlock all monitors.
if (shadow_frame->GetMethod()->MustCountLocks()) {
DCHECK(!shadow_frame->GetMethod()->SkipAccessChecks());
@@ -1588,6 +1563,26 @@ void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame)
}
}
+void PerformNonStandardReturn(Thread* self,
+ ShadowFrame& frame,
+ JValue& result,
+ const instrumentation::Instrumentation* instrumentation,
+ bool unlock_monitors) {
+ if (UNLIKELY(self->IsExceptionPending())) {
+ LOG(WARNING) << "Suppressing exception for non-standard method exit: "
+ << self->GetException()->Dump();
+ self->ClearException();
+ }
+ if (unlock_monitors) {
+ UnlockHeldMonitors(self, &frame);
+ DoMonitorCheckOnExit(self, &frame);
+ }
+ result = JValue();
+ if (UNLIKELY(NeedsMethodExitEvent(instrumentation))) {
+ SendMethodExitEvents(self, instrumentation, frame, frame.GetMethod(), result);
+ }
+}
+
// Explicit DoCall template function declarations.
#define EXPLICIT_DO_CALL_TEMPLATE_DECL(_is_range) \
template REQUIRES_SHARED(Locks::mutator_lock_) \
@@ -1613,18 +1608,14 @@ EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL(true);
#undef EXPLICIT_DO_INVOKE_POLYMORPHIC_TEMPLATE_DECL
// Explicit DoFilledNewArray template function declarations.
-#define EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(_is_range_, _transaction_active) \
- template REQUIRES_SHARED(Locks::mutator_lock_) \
- bool DoFilledNewArray<_is_range_, _transaction_active>(const Instruction* inst, \
- const ShadowFrame& shadow_frame, \
- Thread* self, \
- JValue* result)
-#define EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL(_transaction_active) \
- EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(false, _transaction_active); \
- EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(true, _transaction_active)
-EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL(false);
-EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL(true);
-#undef EXPLICIT_DO_FILLED_NEW_ARRAY_ALL_TEMPLATE_DECL
+#define EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(_is_range_) \
+ template REQUIRES_SHARED(Locks::mutator_lock_) \
+ bool DoFilledNewArray<_is_range_>(const Instruction* inst, \
+ const ShadowFrame& shadow_frame, \
+ Thread* self, \
+ JValue* result)
+EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(false);
+EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL(true);
#undef EXPLICIT_DO_FILLED_NEW_ARRAY_TEMPLATE_DECL
} // namespace interpreter
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 24fb8a83c4..bad54ee448 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -66,38 +66,6 @@
namespace art HIDDEN {
namespace interpreter {
-// We declare the helper class for transaction checks here but it shall be defined
-// only when compiling the transactional interpreter.
-class ActiveTransactionChecker;
-
-// Define the helper class that does not do any transaction checks.
-class InactiveTransactionChecker {
- public:
- ALWAYS_INLINE static bool WriteConstraint([[maybe_unused]] Thread* self,
- [[maybe_unused]] ObjPtr<mirror::Object> obj)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return false;
- }
-
- ALWAYS_INLINE static bool WriteValueConstraint([[maybe_unused]] Thread* self,
- [[maybe_unused]] ObjPtr<mirror::Object> value)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return false;
- }
-
- ALWAYS_INLINE static bool ReadConstraint([[maybe_unused]] Thread* self,
- [[maybe_unused]] ObjPtr<mirror::Object> value)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return false;
- }
-
- ALWAYS_INLINE static bool AllocationConstraint([[maybe_unused]] Thread* self,
- [[maybe_unused]] ObjPtr<mirror::Class> klass)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- return false;
- }
-};
-
void ThrowNullPointerExceptionFromInterpreter()
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -142,9 +110,6 @@ static inline bool DoMonitorCheckOnExit(Thread* self, ShadowFrame* frame)
return true;
}
-void RecordArrayElementsInTransaction(ObjPtr<mirror::Array> array, int32_t count)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Invokes the given method. This is part of the invocation support and is used by DoInvoke,
// DoFastInvoke and DoInvokeVirtualQuick functions.
// Returns true on success, otherwise throws an exception and returns false.
@@ -184,29 +149,11 @@ NeedsMethodExitEvent(const instrumentation::Instrumentation* ins)
COLD_ATTR void UnlockHeldMonitors(Thread* self, ShadowFrame* shadow_frame)
REQUIRES_SHARED(Locks::mutator_lock_);
-static inline ALWAYS_INLINE void PerformNonStandardReturn(
- Thread* self,
- ShadowFrame& frame,
- JValue& result,
- const instrumentation::Instrumentation* instrumentation,
- uint16_t num_dex_inst,
- bool unlock_monitors = true) REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Object> thiz(frame.GetThisObject(num_dex_inst));
- StackHandleScope<1u> hs(self);
- if (UNLIKELY(self->IsExceptionPending())) {
- LOG(WARNING) << "Suppressing exception for non-standard method exit: "
- << self->GetException()->Dump();
- self->ClearException();
- }
- if (unlock_monitors) {
- UnlockHeldMonitors(self, &frame);
- DoMonitorCheckOnExit(self, &frame);
- }
- result = JValue();
- if (UNLIKELY(NeedsMethodExitEvent(instrumentation))) {
- SendMethodExitEvents(self, instrumentation, frame, frame.GetMethod(), result);
- }
-}
+void PerformNonStandardReturn(Thread* self,
+ ShadowFrame& frame,
+ JValue& result,
+ const instrumentation::Instrumentation* instrumentation,
+ bool unlock_monitors = true) REQUIRES_SHARED(Locks::mutator_lock_);
// Handles all invoke-XXX/range instructions except for invoke-polymorphic[/range].
// Returns true on success, otherwise throws an exception and returns false.
@@ -373,201 +320,6 @@ static inline void GetFieldInfo(Thread* self,
}
}
-// Handles iget-XXX and sget-XXX instructions.
-// Returns true on success, otherwise throws an exception and returns false.
-template<FindFieldType find_type,
- Primitive::Type field_type,
- bool transaction_active = false>
-ALWAYS_INLINE bool DoFieldGet(Thread* self,
- ShadowFrame& shadow_frame,
- const Instruction* inst,
- uint16_t inst_data) REQUIRES_SHARED(Locks::mutator_lock_) {
- const bool is_static = (find_type == StaticObjectRead) || (find_type == StaticPrimitiveRead);
- bool should_report = Runtime::Current()->GetInstrumentation()->HasFieldReadListeners();
- ArtField* field = nullptr;
- MemberOffset offset(0u);
- bool is_volatile;
- GetFieldInfo(self,
- shadow_frame.GetMethod(),
- reinterpret_cast<const uint16_t*>(inst),
- is_static,
- /*resolve_field_type=*/ false,
- &field,
- &is_volatile,
- &offset);
- if (self->IsExceptionPending()) {
- return false;
- }
-
- ObjPtr<mirror::Object> obj;
- if (is_static) {
- obj = field->GetDeclaringClass();
- using TransactionChecker = typename std::conditional_t<
- transaction_active, ActiveTransactionChecker, InactiveTransactionChecker>;
- if (TransactionChecker::ReadConstraint(self, obj)) {
- return false;
- }
- } else {
- obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
- if (should_report || obj == nullptr) {
- field = ResolveFieldWithAccessChecks(self,
- Runtime::Current()->GetClassLinker(),
- inst->VRegC_22c(),
- shadow_frame.GetMethod(),
- /* is_static= */ false,
- /* is_put= */ false,
- /* resolve_field_type= */ false);
- if (obj == nullptr) {
- ThrowNullPointerExceptionForFieldAccess(
- field, shadow_frame.GetMethod(), /* is_read= */ true);
- return false;
- }
- // Reload in case suspension happened during field resolution.
- obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
- }
- }
-
- uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
- JValue result;
- if (should_report) {
- DCHECK(field != nullptr);
- if (UNLIKELY(!DoFieldGetCommon<field_type>(self, shadow_frame, obj, field, &result))) {
- // Instrumentation threw an error!
- CHECK(self->IsExceptionPending());
- return false;
- }
- }
-
-#define FIELD_GET(prim, type, jtype, vreg) \
- case Primitive::kPrim ##prim: \
- shadow_frame.SetVReg ##vreg(vregA, \
- should_report ? result.Get ##jtype() \
- : is_volatile ? obj->GetField ## type ## Volatile(offset) \
- : obj->GetField ##type(offset)); \
- break;
-
- switch (field_type) {
- FIELD_GET(Boolean, Boolean, Z, )
- FIELD_GET(Byte, Byte, B, )
- FIELD_GET(Char, Char, C, )
- FIELD_GET(Short, Short, S, )
- FIELD_GET(Int, 32, I, )
- FIELD_GET(Long, 64, J, Long)
-#undef FIELD_GET
- case Primitive::kPrimNot:
- shadow_frame.SetVRegReference(
- vregA,
- should_report ? result.GetL()
- : is_volatile ? obj->GetFieldObjectVolatile<mirror::Object>(offset)
- : obj->GetFieldObject<mirror::Object>(offset));
- break;
- default:
- LOG(FATAL) << "Unreachable: " << field_type;
- UNREACHABLE();
- }
- return true;
-}
-
-// Handles iput-XXX and sput-XXX instructions.
-// Returns true on success, otherwise throws an exception and returns false.
-template<FindFieldType find_type, Primitive::Type field_type, bool transaction_active>
-ALWAYS_INLINE bool DoFieldPut(Thread* self,
- const ShadowFrame& shadow_frame,
- const Instruction* inst,
- uint16_t inst_data)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- bool should_report = Runtime::Current()->GetInstrumentation()->HasFieldWriteListeners();
- bool is_static = (find_type == StaticObjectWrite) || (find_type == StaticPrimitiveWrite);
- uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
- bool resolve_field_type = (shadow_frame.GetVRegReference(vregA) != nullptr);
- ArtField* field = nullptr;
- MemberOffset offset(0u);
- bool is_volatile;
- GetFieldInfo(self,
- shadow_frame.GetMethod(),
- reinterpret_cast<const uint16_t*>(inst),
- is_static,
- resolve_field_type,
- &field,
- &is_volatile,
- &offset);
- if (self->IsExceptionPending()) {
- return false;
- }
-
- ObjPtr<mirror::Object> obj;
- if (is_static) {
- obj = field->GetDeclaringClass();
- } else {
- obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
- if (should_report || obj == nullptr) {
- field = ResolveFieldWithAccessChecks(self,
- Runtime::Current()->GetClassLinker(),
- inst->VRegC_22c(),
- shadow_frame.GetMethod(),
- /* is_static= */ false,
- /* is_put= */ true,
- resolve_field_type);
- if (UNLIKELY(obj == nullptr)) {
- ThrowNullPointerExceptionForFieldAccess(
- field, shadow_frame.GetMethod(), /* is_read= */ false);
- return false;
- }
- // Reload in case suspension happened during field resolution.
- obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
- }
- }
- using TransactionChecker = typename std::conditional_t<
- transaction_active, ActiveTransactionChecker, InactiveTransactionChecker>;
- if (TransactionChecker::WriteConstraint(self, obj)) {
- return false;
- }
-
- JValue value = GetFieldValue<field_type>(shadow_frame, vregA);
-
- if (field_type == Primitive::kPrimNot &&
- TransactionChecker::WriteValueConstraint(self, value.GetL())) {
- return false;
- }
- if (should_report) {
- return DoFieldPutCommon<field_type, transaction_active>(self,
- shadow_frame,
- obj,
- field,
- value);
- }
-#define FIELD_SET(prim, type, jtype) \
- case Primitive::kPrim ## prim: \
- if (is_volatile) { \
- obj->SetField ## type ## Volatile<transaction_active>(offset, value.Get ## jtype()); \
- } else { \
- obj->SetField ## type<transaction_active>(offset, value.Get ## jtype()); \
- } \
- break;
-
- switch (field_type) {
- FIELD_SET(Boolean, Boolean, Z)
- FIELD_SET(Byte, Byte, B)
- FIELD_SET(Char, Char, C)
- FIELD_SET(Short, Short, S)
- FIELD_SET(Int, 32, I)
- FIELD_SET(Long, 64, J)
- FIELD_SET(Not, Object, L)
- case Primitive::kPrimVoid: {
- LOG(FATAL) << "Unreachable " << field_type;
- break;
- }
- }
-#undef FIELD_SET
-
- if (transaction_active) {
- if (UNLIKELY(self->IsExceptionPending())) {
- return false;
- }
- }
- return true;
-}
-
// Handles string resolution for const-string and const-string-jumbo instructions. Also ensures the
// java.lang.String class is initialized.
static inline ObjPtr<mirror::String> ResolveString(Thread* self,
@@ -669,7 +421,7 @@ static inline bool DoLongRemainder(ShadowFrame& shadow_frame,
// Handles filled-new-array and filled-new-array-range instructions.
// Returns true on success, otherwise throws an exception and returns false.
-template <bool is_range, bool transaction_active>
+template <bool is_range>
bool DoFilledNewArray(const Instruction* inst,
const ShadowFrame& shadow_frame,
Thread* self,
diff --git a/runtime/interpreter/interpreter_switch_impl-inl.h b/runtime/interpreter/interpreter_switch_impl-inl.h
index 8ff6a897f8..1ebac52c1e 100644
--- a/runtime/interpreter/interpreter_switch_impl-inl.h
+++ b/runtime/interpreter/interpreter_switch_impl-inl.h
@@ -44,6 +44,218 @@
namespace art HIDDEN {
namespace interpreter {
+// We declare the helpers classes for transaction checks here but they shall be defined
+// only when compiling the transactional and non-transactional interpreter.
+class ActiveTransactionChecker; // For transactional interpreter.
+class InactiveTransactionChecker; // For non-transactional interpreter.
+
+// We declare the helpers classes for instrumentation handling here but they shall be defined
+// only when compiling the transactional and non-transactional interpreter.
+class ActiveInstrumentationHandler; // For non-transactional interpreter.
+class InactiveInstrumentationHandler; // For transactional interpreter.
+
+// Handles iget-XXX and sget-XXX instructions.
+// Returns true on success, otherwise throws an exception and returns false.
+template<FindFieldType find_type,
+ Primitive::Type field_type,
+ bool transaction_active = false>
+ALWAYS_INLINE bool DoFieldGet(Thread* self,
+ ShadowFrame& shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ using InstrumentationHandler = typename std::conditional_t<
+ transaction_active, InactiveInstrumentationHandler, ActiveInstrumentationHandler>;
+ bool should_report = InstrumentationHandler::HasFieldReadListeners(instrumentation);
+ const bool is_static = (find_type == StaticObjectRead) || (find_type == StaticPrimitiveRead);
+ ArtField* field = nullptr;
+ MemberOffset offset(0u);
+ bool is_volatile;
+ GetFieldInfo(self,
+ shadow_frame.GetMethod(),
+ reinterpret_cast<const uint16_t*>(inst),
+ is_static,
+ /*resolve_field_type=*/ false,
+ &field,
+ &is_volatile,
+ &offset);
+ if (self->IsExceptionPending()) {
+ return false;
+ }
+
+ ObjPtr<mirror::Object> obj;
+ if (is_static) {
+ obj = field->GetDeclaringClass();
+ using TransactionChecker = typename std::conditional_t<
+ transaction_active, ActiveTransactionChecker, InactiveTransactionChecker>;
+ if (TransactionChecker::ReadConstraint(self, obj)) {
+ return false;
+ }
+ } else {
+ obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
+ if (should_report || obj == nullptr) {
+ field = ResolveFieldWithAccessChecks(self,
+ Runtime::Current()->GetClassLinker(),
+ inst->VRegC_22c(),
+ shadow_frame.GetMethod(),
+ /* is_static= */ false,
+ /* is_put= */ false,
+ /* resolve_field_type= */ false);
+ if (obj == nullptr) {
+ ThrowNullPointerExceptionForFieldAccess(
+ field, shadow_frame.GetMethod(), /* is_read= */ true);
+ return false;
+ }
+ // Reload in case suspension happened during field resolution.
+ obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
+ }
+ }
+
+ uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
+ JValue result;
+ if (should_report) {
+ DCHECK(field != nullptr);
+ if (UNLIKELY(!DoFieldGetCommon<field_type>(self, shadow_frame, obj, field, &result))) {
+ // Instrumentation threw an error!
+ CHECK(self->IsExceptionPending());
+ return false;
+ }
+ }
+
+#define FIELD_GET(prim, type, jtype, vreg) \
+ case Primitive::kPrim ##prim: \
+ shadow_frame.SetVReg ##vreg(vregA, \
+ should_report ? result.Get ##jtype() \
+ : is_volatile ? obj->GetField ## type ## Volatile(offset) \
+ : obj->GetField ##type(offset)); \
+ break;
+
+ switch (field_type) {
+ FIELD_GET(Boolean, Boolean, Z, )
+ FIELD_GET(Byte, Byte, B, )
+ FIELD_GET(Char, Char, C, )
+ FIELD_GET(Short, Short, S, )
+ FIELD_GET(Int, 32, I, )
+ FIELD_GET(Long, 64, J, Long)
+#undef FIELD_GET
+ case Primitive::kPrimNot:
+ shadow_frame.SetVRegReference(
+ vregA,
+ should_report ? result.GetL()
+ : is_volatile ? obj->GetFieldObjectVolatile<mirror::Object>(offset)
+ : obj->GetFieldObject<mirror::Object>(offset));
+ break;
+ default:
+ LOG(FATAL) << "Unreachable: " << field_type;
+ UNREACHABLE();
+ }
+ return true;
+}
+
+// Handles iput-XXX and sput-XXX instructions.
+// Returns true on success, otherwise throws an exception and returns false.
+template<FindFieldType find_type, Primitive::Type field_type, bool transaction_active>
+ALWAYS_INLINE bool DoFieldPut(Thread* self,
+ const ShadowFrame& shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ using InstrumentationHandler = typename std::conditional_t<
+ transaction_active, InactiveInstrumentationHandler, ActiveInstrumentationHandler>;
+ bool should_report = InstrumentationHandler::HasFieldWriteListeners(instrumentation);
+ bool is_static = (find_type == StaticObjectWrite) || (find_type == StaticPrimitiveWrite);
+ uint32_t vregA = is_static ? inst->VRegA_21c(inst_data) : inst->VRegA_22c(inst_data);
+ bool resolve_field_type = (shadow_frame.GetVRegReference(vregA) != nullptr);
+ ArtField* field = nullptr;
+ MemberOffset offset(0u);
+ bool is_volatile;
+ GetFieldInfo(self,
+ shadow_frame.GetMethod(),
+ reinterpret_cast<const uint16_t*>(inst),
+ is_static,
+ resolve_field_type,
+ &field,
+ &is_volatile,
+ &offset);
+ if (self->IsExceptionPending()) {
+ return false;
+ }
+
+ ObjPtr<mirror::Object> obj;
+ if (is_static) {
+ obj = field->GetDeclaringClass();
+ } else {
+ obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
+ if (should_report || obj == nullptr) {
+ field = ResolveFieldWithAccessChecks(self,
+ Runtime::Current()->GetClassLinker(),
+ inst->VRegC_22c(),
+ shadow_frame.GetMethod(),
+ /* is_static= */ false,
+ /* is_put= */ true,
+ resolve_field_type);
+ if (UNLIKELY(obj == nullptr)) {
+ ThrowNullPointerExceptionForFieldAccess(
+ field, shadow_frame.GetMethod(), /* is_read= */ false);
+ return false;
+ }
+ // Reload in case suspension happened during field resolution.
+ obj = shadow_frame.GetVRegReference(inst->VRegB_22c(inst_data));
+ }
+ }
+ using TransactionChecker = typename std::conditional_t<
+ transaction_active, ActiveTransactionChecker, InactiveTransactionChecker>;
+ if (TransactionChecker::WriteConstraint(self, obj)) {
+ return false;
+ }
+
+ JValue value = GetFieldValue<field_type>(shadow_frame, vregA);
+
+ if (field_type == Primitive::kPrimNot &&
+ TransactionChecker::WriteValueConstraint(self, value.GetL())) {
+ return false;
+ }
+ if (should_report) {
+ return DoFieldPutCommon<field_type, transaction_active>(self,
+ shadow_frame,
+ obj,
+ field,
+ value);
+ }
+#define FIELD_SET(prim, type, jtype) \
+ case Primitive::kPrim ## prim: \
+ if (is_volatile) { \
+ obj->SetField ## type ## Volatile<transaction_active>(offset, value.Get ## jtype()); \
+ } else { \
+ obj->SetField ## type<transaction_active>(offset, value.Get ## jtype()); \
+ } \
+ break;
+
+ switch (field_type) {
+ FIELD_SET(Boolean, Boolean, Z)
+ FIELD_SET(Byte, Byte, B)
+ FIELD_SET(Char, Char, C)
+ FIELD_SET(Short, Short, S)
+ FIELD_SET(Int, 32, I)
+ FIELD_SET(Long, 64, J)
+ FIELD_SET(Not, Object, L)
+ case Primitive::kPrimVoid: {
+ LOG(FATAL) << "Unreachable " << field_type;
+ break;
+ }
+ }
+#undef FIELD_SET
+
+ if (transaction_active) {
+ if (UNLIKELY(self->IsExceptionPending())) {
+ return false;
+ }
+ }
+ return true;
+}
+
// Short-lived helper class which executes single DEX bytecode. It is inlined by compiler.
// Any relevant execution information is stored in the fields - it should be kept to minimum.
// All instance functions must be inlined so that the fields can be stored in registers.
@@ -54,13 +266,15 @@ namespace interpreter {
template<bool transaction_active, Instruction::Format kFormat>
class InstructionHandler {
public:
+ using InstrumentationHandler = typename std::conditional_t<
+ transaction_active, InactiveInstrumentationHandler, ActiveInstrumentationHandler>;
using TransactionChecker = typename std::conditional_t<
transaction_active, ActiveTransactionChecker, InactiveTransactionChecker>;
#define HANDLER_ATTRIBUTES ALWAYS_INLINE FLATTEN WARN_UNUSED REQUIRES_SHARED(Locks::mutator_lock_)
HANDLER_ATTRIBUTES bool CheckTransactionAbort() {
- if (transaction_active && Runtime::Current()->IsTransactionAborted()) {
+ if (TransactionChecker::IsTransactionAborted()) {
// Transaction abort cannot be caught by catch handlers.
// Preserve the abort exception while doing non-standard return.
StackHandleScope<1u> hs(Self());
@@ -68,8 +282,7 @@ class InstructionHandler {
DCHECK(abort_exception != nullptr);
DCHECK(abort_exception->GetClass()->DescriptorEquals(kTransactionAbortErrorDescriptor));
Self()->ClearException();
- PerformNonStandardReturn(
- Self(), shadow_frame_, ctx_->result, Instrumentation(), Accessor().InsSize());
+ PerformNonStandardReturn(Self(), shadow_frame_, ctx_->result, Instrumentation());
Self()->SetException(abort_exception.Get());
ExitInterpreterLoop();
return false;
@@ -78,10 +291,9 @@ class InstructionHandler {
}
HANDLER_ATTRIBUTES bool CheckForceReturn() {
- if (shadow_frame_.GetForcePopFrame()) {
+ if (InstrumentationHandler::GetForcePopFrame(shadow_frame_)) {
DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
- PerformNonStandardReturn(
- Self(), shadow_frame_, ctx_->result, Instrumentation(), Accessor().InsSize());
+ PerformNonStandardReturn(Self(), shadow_frame_, ctx_->result, Instrumentation());
ExitInterpreterLoop();
return false;
}
@@ -148,16 +360,16 @@ class InstructionHandler {
if (!CheckForceReturn()) {
return false;
}
- if (UNLIKELY(shadow_frame_.GetNotifyDexPcMoveEvents())) {
+ if (UNLIKELY(InstrumentationHandler::NeedsDexPcEvents(shadow_frame_))) {
uint8_t opcode = inst_->Opcode(inst_data_);
bool is_move_result_object = (opcode == Instruction::MOVE_RESULT_OBJECT);
JValue* save_ref = is_move_result_object ? &ctx_->result_register : nullptr;
- if (UNLIKELY(!DoDexPcMoveEvent(Self(),
- Accessor(),
- shadow_frame_,
- DexPC(),
- Instrumentation(),
- save_ref))) {
+ if (UNLIKELY(!InstrumentationHandler::DoDexPcMoveEvent(Self(),
+ Accessor(),
+ shadow_frame_,
+ DexPC(),
+ Instrumentation(),
+ save_ref))) {
DCHECK(Self()->IsExceptionPending());
// Do not raise exception event if it is caused by other instrumentation event.
shadow_frame_.SetSkipNextExceptionEvent(true);
@@ -170,53 +382,17 @@ class InstructionHandler {
return true;
}
- // Unlike most other events the DexPcMovedEvent can be sent when there is a pending exception (if
- // the next instruction is MOVE_EXCEPTION). This means it needs to be handled carefully to be able
- // to detect exceptions thrown by the DexPcMovedEvent itself. These exceptions could be thrown by
- // jvmti-agents while handling breakpoint or single step events. We had to move this into its own
- // function because it was making ExecuteSwitchImpl have too large a stack.
- NO_INLINE static bool DoDexPcMoveEvent(Thread* self,
- const CodeItemDataAccessor& accessor,
- const ShadowFrame& shadow_frame,
- uint32_t dex_pc_,
- const instrumentation::Instrumentation* instrumentation,
- JValue* save_ref)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(instrumentation->HasDexPcListeners());
- StackHandleScope<2> hs(self);
- Handle<mirror::Throwable> thr(hs.NewHandle(self->GetException()));
- mirror::Object* null_obj = nullptr;
- HandleWrapper<mirror::Object> h(
- hs.NewHandleWrapper(LIKELY(save_ref == nullptr) ? &null_obj : save_ref->GetGCRoot()));
- self->ClearException();
- instrumentation->DexPcMovedEvent(self,
- shadow_frame.GetThisObject(accessor.InsSize()),
- shadow_frame.GetMethod(),
- dex_pc_);
- if (UNLIKELY(self->IsExceptionPending())) {
- // We got a new exception in the dex-pc-moved event.
- // We just let this exception replace the old one.
- // TODO It would be good to add the old exception to the
- // suppressed exceptions of the new one if possible.
- return false; // Pending exception.
- }
- if (UNLIKELY(!thr.IsNull())) {
- self->SetException(thr.Get());
- }
- return true;
- }
-
HANDLER_ATTRIBUTES bool HandleReturn(JValue result) {
Self()->AllowThreadSuspension();
if (!DoMonitorCheckOnExit(Self(), &shadow_frame_)) {
return false;
}
- if (UNLIKELY(NeedsMethodExitEvent(Instrumentation()) &&
- !SendMethodExitEvents(Self(),
- Instrumentation(),
- shadow_frame_,
- shadow_frame_.GetMethod(),
- result))) {
+ if (UNLIKELY(InstrumentationHandler::NeedsMethodExitEvent(Instrumentation()) &&
+ !InstrumentationHandler::SendMethodExitEvents(Self(),
+ Instrumentation(),
+ shadow_frame_,
+ shadow_frame_.GetMethod(),
+ result))) {
DCHECK(Self()->IsExceptionPending());
// Do not raise exception event if it is caused by other instrumentation event.
shadow_frame_.SetSkipNextExceptionEvent(true);
@@ -231,8 +407,9 @@ class InstructionHandler {
if (UNLIKELY(Self()->ObserveAsyncException())) {
return false; // Pending exception.
}
- if (UNLIKELY(Instrumentation()->HasBranchListeners())) {
- Instrumentation()->Branch(Self(), shadow_frame_.GetMethod(), DexPC(), offset);
+ if (UNLIKELY(InstrumentationHandler::HasBranchListeners(Instrumentation()))) {
+ InstrumentationHandler::Branch(
+ Self(), shadow_frame_.GetMethod(), DexPC(), offset, Instrumentation());
}
if (!transaction_active) {
// TODO: Do OSR only on back-edges and check if OSR code is ready here.
@@ -346,13 +523,13 @@ class InstructionHandler {
template<FindFieldType find_type, Primitive::Type field_type>
HANDLER_ATTRIBUTES bool HandleGet() {
return DoFieldGet<find_type, field_type, transaction_active>(
- Self(), shadow_frame_, inst_, inst_data_);
+ Self(), shadow_frame_, inst_, inst_data_, Instrumentation());
}
template<FindFieldType find_type, Primitive::Type field_type>
HANDLER_ATTRIBUTES bool HandlePut() {
return DoFieldPut<find_type, field_type, transaction_active>(
- Self(), shadow_frame_, inst_, inst_data_);
+ Self(), shadow_frame_, inst_, inst_data_, Instrumentation());
}
template<InvokeType type, bool is_range>
@@ -486,14 +663,14 @@ class InstructionHandler {
}
}
result.SetL(obj_result);
- if (UNLIKELY(NeedsMethodExitEvent(Instrumentation()))) {
+ if (UNLIKELY(InstrumentationHandler::NeedsMethodExitEvent(Instrumentation()))) {
StackHandleScope<1> hs(Self());
MutableHandle<mirror::Object> h_result(hs.NewHandle(obj_result));
- if (!SendMethodExitEvents(Self(),
- Instrumentation(),
- shadow_frame_,
- shadow_frame_.GetMethod(),
- h_result)) {
+ if (!InstrumentationHandler::SendMethodExitEvents(Self(),
+ Instrumentation(),
+ shadow_frame_,
+ shadow_frame_.GetMethod(),
+ h_result)) {
DCHECK(Self()->IsExceptionPending());
// Do not raise exception event if it is caused by other instrumentation event.
shadow_frame_.SetSkipNextExceptionEvent(true);
@@ -679,8 +856,13 @@ class InstructionHandler {
gc::AllocatorType allocator_type = Runtime::Current()->GetHeap()->GetCurrentAllocator();
if (UNLIKELY(c->IsStringClass())) {
obj = mirror::String::AllocEmptyString(Self(), allocator_type);
+ // Do not record the allocated string in the transaction.
+ // There can be no transaction records for this immutable object.
} else {
obj = AllocObjectFromCode(c, Self(), allocator_type);
+ if (obj != nullptr) {
+ TransactionChecker::RecordNewObject(obj);
+ }
}
}
if (UNLIKELY(obj == nullptr)) {
@@ -693,27 +875,26 @@ class InstructionHandler {
HANDLER_ATTRIBUTES bool NEW_ARRAY() {
int32_t length = GetVReg(B());
- ObjPtr<mirror::Object> obj = AllocArrayFromCode(
+ ObjPtr<mirror::Array> array = AllocArrayFromCode(
dex::TypeIndex(C()),
length,
shadow_frame_.GetMethod(),
Self(),
Runtime::Current()->GetHeap()->GetCurrentAllocator());
- if (UNLIKELY(obj == nullptr)) {
+ if (UNLIKELY(array == nullptr)) {
return false; // Pending exception.
}
- SetVRegReference(A(), obj);
+ TransactionChecker::RecordNewArray(array);
+ SetVRegReference(A(), array);
return true;
}
HANDLER_ATTRIBUTES bool FILLED_NEW_ARRAY() {
- return DoFilledNewArray<false, transaction_active>(
- inst_, shadow_frame_, Self(), ResultRegister());
+ return DoFilledNewArray</*is_range=*/ false>(inst_, shadow_frame_, Self(), ResultRegister());
}
HANDLER_ATTRIBUTES bool FILLED_NEW_ARRAY_RANGE() {
- return DoFilledNewArray<true, transaction_active>(
- inst_, shadow_frame_, Self(), ResultRegister());
+ return DoFilledNewArray</*is_range=*/ true>(inst_, shadow_frame_, Self(), ResultRegister());
}
HANDLER_ATTRIBUTES bool FILL_ARRAY_DATA() {
@@ -721,12 +902,11 @@ class InstructionHandler {
const Instruction::ArrayDataPayload* payload =
reinterpret_cast<const Instruction::ArrayDataPayload*>(payload_addr);
ObjPtr<mirror::Object> obj = GetVRegReference(A());
+ // If we have an active transaction, record old values before we overwrite them.
+ TransactionChecker::RecordArrayElementsInTransaction(obj, payload->element_count);
if (!FillArrayData(obj, payload)) {
return false; // Pending exception.
}
- if (transaction_active) {
- RecordArrayElementsInTransaction(obj->AsArray(), payload->element_count);
- }
return true;
}
diff --git a/runtime/interpreter/interpreter_switch_impl0.cc b/runtime/interpreter/interpreter_switch_impl0.cc
index 65ae2fe333..1bea9da838 100644
--- a/runtime/interpreter/interpreter_switch_impl0.cc
+++ b/runtime/interpreter/interpreter_switch_impl0.cc
@@ -22,6 +22,147 @@
namespace art HIDDEN {
namespace interpreter {
+// Define the helper class that does not do any transaction checks.
+class InactiveTransactionChecker {
+ public:
+ ALWAYS_INLINE static bool WriteConstraint([[maybe_unused]] Thread* self,
+ [[maybe_unused]] ObjPtr<mirror::Object> obj)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return false;
+ }
+
+ ALWAYS_INLINE static bool WriteValueConstraint([[maybe_unused]] Thread* self,
+ [[maybe_unused]] ObjPtr<mirror::Object> value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return false;
+ }
+
+ ALWAYS_INLINE static bool ReadConstraint([[maybe_unused]] Thread* self,
+ [[maybe_unused]] ObjPtr<mirror::Object> value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return false;
+ }
+
+ ALWAYS_INLINE static bool AllocationConstraint([[maybe_unused]] Thread* self,
+ [[maybe_unused]] ObjPtr<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return false;
+ }
+
+ ALWAYS_INLINE static bool IsTransactionAborted() {
+ return false;
+ }
+
+ static void RecordArrayElementsInTransaction([[maybe_unused]] ObjPtr<mirror::Object> array,
+ [[maybe_unused]] int32_t count)
+ REQUIRES_SHARED(Locks::mutator_lock_) {}
+
+ ALWAYS_INLINE static void RecordNewObject([[maybe_unused]] ObjPtr<mirror::Object> new_object)
+ REQUIRES_SHARED(Locks::mutator_lock_) {}
+
+ ALWAYS_INLINE static void RecordNewArray([[maybe_unused]] ObjPtr<mirror::Array> new_array)
+ REQUIRES_SHARED(Locks::mutator_lock_) {}
+};
+
+class ActiveInstrumentationHandler {
+ public:
+ ALWAYS_INLINE WARN_UNUSED
+ static bool HasFieldReadListeners(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return instrumentation->HasFieldReadListeners();
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool HasFieldWriteListeners(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return instrumentation->HasFieldWriteListeners();
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool HasBranchListeners(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return instrumentation->HasBranchListeners();
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool NeedsDexPcEvents(ShadowFrame& shadow_frame)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK_IMPLIES(shadow_frame.GetNotifyDexPcMoveEvents(),
+ Runtime::Current()->GetInstrumentation()->HasDexPcListeners());
+ return shadow_frame.GetNotifyDexPcMoveEvents();
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool NeedsMethodExitEvent(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return interpreter::NeedsMethodExitEvent(instrumentation);
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool GetForcePopFrame(ShadowFrame& shadow_frame) {
+ DCHECK_IMPLIES(shadow_frame.GetForcePopFrame(),
+ Runtime::Current()->AreNonStandardExitsEnabled());
+ return shadow_frame.GetForcePopFrame();
+ }
+
+ ALWAYS_INLINE
+ static void Branch(Thread* self,
+ ArtMethod* method,
+ uint32_t dex_pc,
+ int32_t dex_pc_offset,
+ const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ instrumentation->Branch(self, method, dex_pc, dex_pc_offset);
+ }
+
+ // Unlike most other events the DexPcMovedEvent can be sent when there is a pending exception (if
+ // the next instruction is MOVE_EXCEPTION). This means it needs to be handled carefully to be able
+ // to detect exceptions thrown by the DexPcMovedEvent itself. These exceptions could be thrown by
+ // jvmti-agents while handling breakpoint or single step events. We had to move this into its own
+ // function because it was making ExecuteSwitchImpl have too large a stack.
+ NO_INLINE static bool DoDexPcMoveEvent(Thread* self,
+ const CodeItemDataAccessor& accessor,
+ const ShadowFrame& shadow_frame,
+ uint32_t dex_pc,
+ const instrumentation::Instrumentation* instrumentation,
+ JValue* save_ref)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(instrumentation->HasDexPcListeners());
+ StackHandleScope<2> hs(self);
+ Handle<mirror::Throwable> thr(hs.NewHandle(self->GetException()));
+ mirror::Object* null_obj = nullptr;
+ HandleWrapper<mirror::Object> h(
+ hs.NewHandleWrapper(LIKELY(save_ref == nullptr) ? &null_obj : save_ref->GetGCRoot()));
+ self->ClearException();
+ instrumentation->DexPcMovedEvent(self,
+ shadow_frame.GetThisObject(accessor.InsSize()),
+ shadow_frame.GetMethod(),
+ dex_pc);
+ if (UNLIKELY(self->IsExceptionPending())) {
+ // We got a new exception in the dex-pc-moved event.
+ // We just let this exception replace the old one.
+ // TODO It would be good to add the old exception to the
+ // suppressed exceptions of the new one if possible.
+ return false; // Pending exception.
+ }
+ if (UNLIKELY(!thr.IsNull())) {
+ self->SetException(thr.Get());
+ }
+ return true;
+ }
+
+ template <typename T>
+ ALWAYS_INLINE WARN_UNUSED
+ static bool SendMethodExitEvents(
+ Thread* self,
+ const instrumentation::Instrumentation* instrumentation,
+ ShadowFrame& frame,
+ ArtMethod* method,
+ T& result) REQUIRES_SHARED(Locks::mutator_lock_) {
+ return interpreter::SendMethodExitEvents(self, instrumentation, frame, method, result);
+ }
+};
+
// Explicit definition of ExecuteSwitchImplCpp.
template HOT_ATTR
void ExecuteSwitchImplCpp<false>(SwitchImplContext* ctx);
diff --git a/runtime/interpreter/interpreter_switch_impl1.cc b/runtime/interpreter/interpreter_switch_impl1.cc
index e56c58c2bc..9c5b5b3266 100644
--- a/runtime/interpreter/interpreter_switch_impl1.cc
+++ b/runtime/interpreter/interpreter_switch_impl1.cc
@@ -19,11 +19,197 @@
#include "interpreter_switch_impl-inl.h"
-#include "active_transaction_checker.h"
+#include "oat/aot_class_linker.h"
+#include "transaction.h"
namespace art HIDDEN {
namespace interpreter {
+class ActiveTransactionChecker {
+ public:
+ static inline bool WriteConstraint(Thread* self, ObjPtr<mirror::Object> obj)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetClassLinker()->TransactionWriteConstraint(self, obj);
+ }
+
+ static inline bool WriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetClassLinker()->TransactionWriteValueConstraint(self, value);
+ }
+
+ static inline bool ReadConstraint(Thread* self, ObjPtr<mirror::Object> obj)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetClassLinker()->TransactionReadConstraint(self, obj);
+ }
+
+ static inline bool AllocationConstraint(Thread* self, ObjPtr<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ return GetClassLinker()->TransactionAllocationConstraint(self, klass);
+ }
+
+ static inline bool IsTransactionAborted() {
+ return GetClassLinker()->IsTransactionAborted();
+ }
+
+ static void RecordArrayElementsInTransaction(ObjPtr<mirror::Object> array, int32_t count)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ static void RecordNewObject(ObjPtr<mirror::Object> new_object)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ GetClassLinker()->GetTransaction()->RecordNewObject(new_object);
+ }
+
+ static void RecordNewArray(ObjPtr<mirror::Array> new_object)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ GetClassLinker()->GetTransaction()->RecordNewArray(new_object);
+ }
+
+ private:
+ static AotClassLinker* GetClassLinker() {
+ return down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
+ }
+};
+
+// TODO: Use ObjPtr here.
+template<typename T>
+static void RecordArrayElementsInTransactionImpl(Transaction* transaction,
+ ObjPtr<mirror::PrimitiveArray<T>> array,
+ int32_t count)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ for (int32_t i = 0; i < count; ++i) {
+ transaction->RecordWriteArray(array.Ptr(), i, array->GetWithoutChecks(i));
+ }
+}
+
+void ActiveTransactionChecker::RecordArrayElementsInTransaction(ObjPtr<mirror::Object> array,
+ int32_t count) {
+ DCHECK(Runtime::Current()->IsActiveTransaction());
+ if (array == nullptr) {
+ return; // The interpreter shall throw NPE.
+ }
+ DCHECK(array->IsArrayInstance());
+ DCHECK_LE(count, array->AsArray()->GetLength());
+ Transaction* transaction = GetClassLinker()->GetTransaction();
+ if (!transaction->ArrayNeedsTransactionRecords(array->AsArray())) {
+ return;
+ }
+ // No read barrier is needed for reading a chain of constant references
+ // for reading a constant primitive value, see `ReadBarrierOption`.
+ Primitive::Type primitive_component_type =
+ array->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>()
+ ->GetComponentType<kDefaultVerifyFlags, kWithoutReadBarrier>()->GetPrimitiveType();
+ switch (primitive_component_type) {
+ case Primitive::kPrimBoolean:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsBooleanArray(), count);
+ break;
+ case Primitive::kPrimByte:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsByteArray(), count);
+ break;
+ case Primitive::kPrimChar:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsCharArray(), count);
+ break;
+ case Primitive::kPrimShort:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsShortArray(), count);
+ break;
+ case Primitive::kPrimInt:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsIntArray(), count);
+ break;
+ case Primitive::kPrimFloat:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsFloatArray(), count);
+ break;
+ case Primitive::kPrimLong:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsLongArray(), count);
+ break;
+ case Primitive::kPrimDouble:
+ RecordArrayElementsInTransactionImpl(transaction, array->AsDoubleArray(), count);
+ break;
+ default:
+ LOG(FATAL) << "Unsupported primitive type " << primitive_component_type
+ << " in fill-array-data";
+ UNREACHABLE();
+ }
+}
+
+class InactiveInstrumentationHandler {
+ public:
+ ALWAYS_INLINE WARN_UNUSED
+ static bool HasFieldReadListeners(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!instrumentation->HasFieldReadListeners());
+ return false;
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool HasFieldWriteListeners(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!instrumentation->HasFieldWriteListeners());
+ return false;
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool HasBranchListeners(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!instrumentation->HasBranchListeners());
+ return false;
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool NeedsDexPcEvents(ShadowFrame& shadow_frame)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!shadow_frame.GetNotifyDexPcMoveEvents());
+ DCHECK(!Runtime::Current()->GetInstrumentation()->HasDexPcListeners());
+ return false;
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool NeedsMethodExitEvent(const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!interpreter::NeedsMethodExitEvent(instrumentation));
+ return false;
+ }
+
+ ALWAYS_INLINE WARN_UNUSED
+ static bool GetForcePopFrame(ShadowFrame& shadow_frame) {
+ DCHECK(!shadow_frame.GetForcePopFrame());
+ DCHECK(!Runtime::Current()->AreNonStandardExitsEnabled());
+ return false;
+ }
+
+ NO_RETURN
+ static void Branch([[maybe_unused]] Thread* self,
+ [[maybe_unused]] ArtMethod* method,
+ [[maybe_unused]] uint32_t dex_pc,
+ [[maybe_unused]] int32_t dex_pc_offset,
+ [[maybe_unused]] const instrumentation::Instrumentation* instrumentation)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+ }
+
+ static bool DoDexPcMoveEvent(
+ [[maybe_unused]] Thread* self,
+ [[maybe_unused]] const CodeItemDataAccessor& accessor,
+ [[maybe_unused]] const ShadowFrame& shadow_frame,
+ [[maybe_unused]] uint32_t dex_pc,
+ [[maybe_unused]] const instrumentation::Instrumentation* instrumentation,
+ [[maybe_unused]] JValue* save_ref)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+ }
+
+ template <typename T>
+ static bool SendMethodExitEvents(
+ [[maybe_unused]] Thread* self,
+ [[maybe_unused]] const instrumentation::Instrumentation* instrumentation,
+ [[maybe_unused]] ShadowFrame& frame,
+ [[maybe_unused]] ArtMethod* method,
+ [[maybe_unused]] T& result) REQUIRES_SHARED(Locks::mutator_lock_) {
+ LOG(FATAL) << "UNREACHABLE";
+ UNREACHABLE();
+ }
+};
+
// Explicit definition of ExecuteSwitchImplCpp.
template
void ExecuteSwitchImplCpp<true>(SwitchImplContext* ctx);
diff --git a/runtime/interpreter/mterp/arm64ng/object.S b/runtime/interpreter/mterp/arm64ng/object.S
index 5202ebcd3d..ade39d5e89 100644
--- a/runtime/interpreter/mterp/arm64ng/object.S
+++ b/runtime/interpreter/mterp/arm64ng/object.S
@@ -195,6 +195,10 @@
mov x3, #0
EXPORT_PC
bl nterp_get_instance_field_offset
+ // Zero extension (nterp_get_instance_field_offset returns uint32_t) of the return value is
+ // needed as the value is used below via wider X0 register - AARCH64 AAPCS specifies that
+ // "... any unused bits in the register have unspecified value" (see 6.8.2, 6.9).
+ mov w0, w0
tbz w0, #31, .L${opcode}_resume
CLEAR_INSTANCE_VOLATILE_MARKER w0
lsr w2, wINST, #12 // w2<- B
@@ -273,6 +277,10 @@
.endif
EXPORT_PC
bl nterp_get_instance_field_offset
+ // Zero extension (nterp_get_instance_field_offset returns uint32_t) of the return value is
+ // needed as the value is used below via wider X0 register - AARCH64 AAPCS specifies that
+ // "... any unused bits in the register have unspecified value" (see 6.8.2, 6.9).
+ mov w0, w0
.if $is_object
// Reload the value as it may have moved.
ubfx w1, wINST, #8, #4 // w1<- A
diff --git a/runtime/interpreter/mterp/nterp.cc b/runtime/interpreter/mterp/nterp.cc
index d8ce2bcaed..64f04d6fde 100644
--- a/runtime/interpreter/mterp/nterp.cc
+++ b/runtime/interpreter/mterp/nterp.cc
@@ -693,7 +693,10 @@ extern "C" jit::OsrData* NterpHotMethod(ArtMethod* method, uint16_t* dex_pc_ptr,
DCHECK_EQ(Thread::Current()->GetSharedMethodHotness(), 0u);
Thread::Current()->ResetSharedMethodHotness();
} else {
+ // Move the counter to the initial threshold in case we have to re-JIT it.
method->ResetCounter(runtime->GetJITOptions()->GetWarmupThreshold());
+ // Mark the method as warm for the profile saver.
+ method->SetPreviouslyWarm();
}
jit::Jit* jit = runtime->GetJit();
if (jit != nullptr && jit->UseJitCompilation()) {
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index d865327473..43e0692eee 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -80,7 +80,7 @@ static void AbortTransactionOrFail(Thread* self, const char* fmt, ...) {
Runtime* runtime = Runtime::Current();
if (runtime->IsActiveTransaction()) {
va_start(args, fmt);
- runtime->AbortTransactionV(self, fmt, args);
+ runtime->GetClassLinker()->AbortTransactionV(self, fmt, args);
va_end(args);
} else {
va_start(args, fmt);
@@ -99,7 +99,7 @@ static void CharacterLowerUpper(Thread* self,
JValue* result,
size_t arg_offset,
bool to_lower_case) REQUIRES_SHARED(Locks::mutator_lock_) {
- uint32_t int_value = static_cast<uint32_t>(shadow_frame->GetVReg(arg_offset));
+ int32_t int_value = shadow_frame->GetVReg(arg_offset);
// Only ASCII (7-bit).
if (!isascii(int_value)) {
@@ -109,14 +109,16 @@ static void CharacterLowerUpper(Thread* self,
return;
}
- std::locale c_locale("C");
- char char_value = static_cast<char>(int_value);
-
- if (to_lower_case) {
- result->SetI(std::tolower(char_value, c_locale));
- } else {
- result->SetI(std::toupper(char_value, c_locale));
- }
+ // Constructing a `std::locale("C")` is slow. Use an explicit calculation, compare in debug mode.
+ int32_t masked_value = int_value & ~0x20; // Clear bit distinguishing `A`..`Z` from `a`..`z`.
+ bool is_ascii_letter = ('A' <= masked_value) && (masked_value <= 'Z');
+ int32_t result_value = is_ascii_letter ? (masked_value | (to_lower_case ? 0x20 : 0)) : int_value;
+ DCHECK_EQ(result_value,
+ to_lower_case
+ ? std::tolower(dchecked_integral_cast<char>(int_value), std::locale("C"))
+ : std::toupper(dchecked_integral_cast<char>(int_value), std::locale("C")))
+ << std::boolalpha << to_lower_case;
+ result->SetI(result_value);
}
void UnstartedRuntime::UnstartedCharacterToLowerCase(
@@ -156,6 +158,12 @@ static void UnstartedRuntimeFindClass(Thread* self,
result->SetL(found);
}
+static inline bool PendingExceptionHasAbortDescriptor(Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(self->IsExceptionPending());
+ return self->GetException()->GetClass()->DescriptorEquals(kTransactionAbortErrorDescriptor);
+}
+
// Common helper for class-loading cutouts in an unstarted runtime. We call Runtime methods that
// rely on Java code to wrap errors in the correct exception class (i.e., NoClassDefFoundError into
// ClassNotFoundException), so need to do the same. The only exception is if the exception is
@@ -165,21 +173,22 @@ static void CheckExceptionGenerateClassNotFound(Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (self->IsExceptionPending()) {
Runtime* runtime = Runtime::Current();
- DCHECK_EQ(runtime->IsTransactionAborted(),
- self->GetException()->GetClass()->DescriptorEquals(kTransactionAbortErrorDescriptor))
- << self->GetException()->GetClass()->PrettyDescriptor();
if (runtime->IsActiveTransaction()) {
// The boot class path at run time may contain additional dex files with
// the required class definition(s). We cannot throw a normal exception at
// compile time because a class initializer could catch it and successfully
// initialize a class differently than when executing at run time.
// If we're not aborting the transaction yet, abort now. b/183691501
- if (!runtime->IsTransactionAborted()) {
- runtime->AbortTransactionF(self, "ClassNotFoundException");
+ if (!runtime->GetClassLinker()->IsTransactionAborted()) {
+ DCHECK(!PendingExceptionHasAbortDescriptor(self));
+ runtime->GetClassLinker()->AbortTransactionF(self, "ClassNotFoundException");
+ } else {
+ DCHECK(PendingExceptionHasAbortDescriptor(self))
+ << self->GetException()->GetClass()->PrettyDescriptor();
}
} else {
// If not in a transaction, it cannot be the transaction abort exception. Wrap it.
- DCHECK(!runtime->IsTransactionAborted());
+ DCHECK(!PendingExceptionHasAbortDescriptor(self));
self->ThrowNewWrappedException("Ljava/lang/ClassNotFoundException;",
"ClassNotFoundException");
}
@@ -761,18 +770,19 @@ void UnstartedRuntime::UnstartedVmClassLoaderFindLoadedClass(
// This might have an error pending. But semantics are to just return null.
if (self->IsExceptionPending()) {
Runtime* runtime = Runtime::Current();
- DCHECK_EQ(runtime->IsTransactionAborted(),
- self->GetException()->GetClass()->DescriptorEquals(kTransactionAbortErrorDescriptor))
- << self->GetException()->GetClass()->PrettyDescriptor();
if (runtime->IsActiveTransaction()) {
// If we're not aborting the transaction yet, abort now. b/183691501
// See CheckExceptionGenerateClassNotFound() for more detailed explanation.
- if (!runtime->IsTransactionAborted()) {
- runtime->AbortTransactionF(self, "ClassNotFoundException");
+ if (!runtime->GetClassLinker()->IsTransactionAborted()) {
+ DCHECK(!PendingExceptionHasAbortDescriptor(self));
+ runtime->GetClassLinker()->AbortTransactionF(self, "ClassNotFoundException");
+ } else {
+ DCHECK(PendingExceptionHasAbortDescriptor(self))
+ << self->GetException()->GetClass()->PrettyDescriptor();
}
} else {
// If not in a transaction, it cannot be the transaction abort exception. Clear it.
- DCHECK(!runtime->IsTransactionAborted());
+ DCHECK(!PendingExceptionHasAbortDescriptor(self));
self->ClearException();
}
}
@@ -1300,7 +1310,7 @@ static void UnstartedMemoryPeekArray(
int64_t address_long = shadow_frame->GetVRegLong(arg_offset);
mirror::Object* obj = shadow_frame->GetVRegReference(arg_offset + 2);
if (obj == nullptr) {
- Runtime::Current()->AbortTransactionAndThrowAbortError(self, "Null pointer in peekArray");
+ Runtime::Current()->GetClassLinker()->AbortTransactionF(self, "Null pointer in peekArray");
return;
}
ObjPtr<mirror::Array> array = obj->AsArray();
@@ -1308,9 +1318,8 @@ static void UnstartedMemoryPeekArray(
int offset = shadow_frame->GetVReg(arg_offset + 3);
int count = shadow_frame->GetVReg(arg_offset + 4);
if (offset < 0 || offset + count > array->GetLength()) {
- std::string error_msg(StringPrintf("Array out of bounds in peekArray: %d/%d vs %d",
- offset, count, array->GetLength()));
- Runtime::Current()->AbortTransactionAndThrowAbortError(self, error_msg);
+ Runtime::Current()->GetClassLinker()->AbortTransactionF(
+ self, "Array out of bounds in peekArray: %d/%d vs %d", offset, count, array->GetLength());
return;
}
@@ -2432,9 +2441,10 @@ void UnstartedRuntime::Jni(Thread* self, ArtMethod* method, mirror::Object* rece
} else {
Runtime* runtime = Runtime::Current();
if (runtime->IsActiveTransaction()) {
- runtime->AbortTransactionF(self,
- "Attempt to invoke native method in non-started runtime: %s",
- ArtMethod::PrettyMethod(method).c_str());
+ runtime->GetClassLinker()->AbortTransactionF(
+ self,
+ "Attempt to invoke native method in non-started runtime: %s",
+ ArtMethod::PrettyMethod(method).c_str());
} else {
LOG(FATAL) << "Calling native method " << ArtMethod::PrettyMethod(method)
<< " in an unstarted non-transactional runtime";
diff --git a/runtime/interpreter/unstarted_runtime.h b/runtime/interpreter/unstarted_runtime.h
index 60092ad958..8019c257d4 100644
--- a/runtime/interpreter/unstarted_runtime.h
+++ b/runtime/interpreter/unstarted_runtime.h
@@ -99,7 +99,7 @@ class UnstartedRuntime {
static void InitializeInvokeHandlers(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
static void InitializeJNIHandlers(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
- friend class UnstartedRuntimeTest;
+ friend class UnstartedRuntimeTestBase;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(UnstartedRuntime);
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index 44c1e2627a..cf7782dcdc 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "unstarted_runtime.h"
+#include "unstarted_runtime_test.h"
#include <limits>
#include <locale>
@@ -30,7 +30,7 @@
#include "dex/dex_instruction.h"
#include "handle.h"
#include "handle_scope-inl.h"
-#include "interpreter/interpreter_common.h"
+#include "interpreter_common.h"
#include "mirror/array-alloc-inl.h"
#include "mirror/class-alloc-inl.h"
#include "mirror/class_loader.h"
@@ -48,55 +48,8 @@
namespace art HIDDEN {
namespace interpreter {
-// Deleter to be used with ShadowFrame::CreateDeoptimizedFrame objects.
-struct DeoptShadowFrameDelete {
- // NOTE: Deleting a const object is valid but free() takes a non-const pointer.
- void operator()(ShadowFrame* ptr) const {
- if (ptr != nullptr) {
- ShadowFrame::DeleteDeoptimizedFrame(ptr);
- }
- }
-};
-// Alias for std::unique_ptr<> that uses the above deleter.
-using UniqueDeoptShadowFramePtr = std::unique_ptr<ShadowFrame, DeoptShadowFrameDelete>;
-
-class UnstartedRuntimeTest : public CommonRuntimeTest {
+class UnstartedRuntimeTest : public UnstartedRuntimeTestBase {
protected:
- // Re-expose all UnstartedRuntime implementations so we don't need to declare a million
- // test friends.
-
- // Methods that intercept available libcore implementations.
-#define UNSTARTED_DIRECT(Name, DescriptorIgnored, NameIgnored, SignatureIgnored) \
- static void Unstarted ## Name(Thread* self, \
- ShadowFrame* shadow_frame, \
- JValue* result, \
- size_t arg_offset) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- interpreter::UnstartedRuntime::Unstarted ## Name(self, shadow_frame, result, arg_offset); \
- }
- UNSTARTED_RUNTIME_DIRECT_LIST(UNSTARTED_DIRECT)
-#undef UNSTARTED_DIRECT
-
- // Methods that are native.
-#define UNSTARTED_JNI(Name, DescriptorIgnored, NameIgnored, SignatureIgnored) \
- static void UnstartedJNI ## Name(Thread* self, \
- ArtMethod* method, \
- mirror::Object* receiver, \
- uint32_t* args, \
- JValue* result) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- interpreter::UnstartedRuntime::UnstartedJNI ## Name(self, method, receiver, args, result); \
- }
- UNSTARTED_RUNTIME_JNI_LIST(UNSTARTED_JNI)
-#undef UNSTARTED_JNI
-
- UniqueDeoptShadowFramePtr CreateShadowFrame(uint32_t num_vregs,
- ArtMethod* method,
- uint32_t dex_pc) {
- return UniqueDeoptShadowFramePtr(
- ShadowFrame::CreateDeoptimizedFrame(num_vregs, method, dex_pc));
- }
-
// Helpers for ArrayCopy.
//
// Note: as we have to use handles, we use StackHandleScope to transfer data. Hardcode a size
@@ -210,16 +163,6 @@ class UnstartedRuntimeTest : public CommonRuntimeTest {
EXPECT_EQ(expect_int64t, result_int64t) << result.GetD() << " vs " << test_pairs[i][1];
}
}
-
- // Prepare for aborts. Aborts assume that the exception class is already resolved, as the
- // loading code doesn't work under transactions.
- void PrepareForAborts() REQUIRES_SHARED(Locks::mutator_lock_) {
- ObjPtr<mirror::Object> result = Runtime::Current()->GetClassLinker()->FindClass(
- Thread::Current(),
- kTransactionAbortErrorDescriptor,
- ScopedNullHandle<mirror::ClassLoader>());
- CHECK(result != nullptr);
- }
};
TEST_F(UnstartedRuntimeTest, MemoryPeekByte) {
@@ -396,8 +339,7 @@ TEST_F(UnstartedRuntimeTest, StringInit) {
ScopedObjectAccess soa(self);
ObjPtr<mirror::Class> klass = GetClassRoot<mirror::String>();
ArtMethod* method =
- klass->FindConstructor("(Ljava/lang/String;)V",
- Runtime::Current()->GetClassLinker()->GetImagePointerSize());
+ klass->FindConstructor("(Ljava/lang/String;)V", class_linker_->GetImagePointerSize());
ASSERT_TRUE(method != nullptr);
// create instruction data for invoke-direct {v0, v1} of method with fake index
@@ -483,9 +425,6 @@ TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTest) {
JValue result;
UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
- StackHandleScope<1> hs_object(self);
- Handle<mirror::Class> object_class(hs_object.NewHandle(GetClassRoot<mirror::Object>()));
-
// Simple test:
// [1,2,3]{1 @ 2} into [4,5,6] = [4,2,6]
{
@@ -507,8 +446,8 @@ TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTest) {
RunArrayCopy(self,
tmp.get(),
false,
- object_class.Get(),
- object_class.Get(),
+ GetClassRoot<mirror::Object>(),
+ GetClassRoot<mirror::Object>(),
hs_src,
1,
hs_dst,
@@ -538,7 +477,7 @@ TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTest) {
RunArrayCopy(self,
tmp.get(),
false,
- object_class.Get(),
+ GetClassRoot<mirror::Object>(),
GetClassRoot<mirror::String>(),
hs_src,
1,
@@ -569,7 +508,7 @@ TEST_F(UnstartedRuntimeTest, SystemArrayCopyObjectArrayTest) {
RunArrayCopy(self,
tmp.get(),
true,
- object_class.Get(),
+ GetClassRoot<mirror::Object>(),
GetClassRoot<mirror::String>(),
hs_src,
0,
@@ -773,51 +712,6 @@ TEST_F(UnstartedRuntimeTest, ToLowerUpper) {
}
}
}
-
- // Check abort for other things. Can't test all.
-
- PrepareForAborts();
-
- for (uint32_t i = 128; i < 256; ++i) {
- {
- JValue result;
- tmp->SetVReg(0, static_cast<int32_t>(i));
- EnterTransactionMode();
- UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0);
- ASSERT_TRUE(IsTransactionAborted());
- ExitTransactionMode();
- ASSERT_TRUE(self->IsExceptionPending());
- }
- {
- JValue result;
- tmp->SetVReg(0, static_cast<int32_t>(i));
- EnterTransactionMode();
- UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0);
- ASSERT_TRUE(IsTransactionAborted());
- ExitTransactionMode();
- ASSERT_TRUE(self->IsExceptionPending());
- }
- }
- for (uint64_t i = 256; i <= std::numeric_limits<uint32_t>::max(); i <<= 1) {
- {
- JValue result;
- tmp->SetVReg(0, static_cast<int32_t>(i));
- EnterTransactionMode();
- UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0);
- ASSERT_TRUE(IsTransactionAborted());
- ExitTransactionMode();
- ASSERT_TRUE(self->IsExceptionPending());
- }
- {
- JValue result;
- tmp->SetVReg(0, static_cast<int32_t>(i));
- EnterTransactionMode();
- UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0);
- ASSERT_TRUE(IsTransactionAborted());
- ExitTransactionMode();
- ASSERT_TRUE(self->IsExceptionPending());
- }
- }
}
TEST_F(UnstartedRuntimeTest, Sin) {
@@ -937,21 +831,20 @@ TEST_F(UnstartedRuntimeTest, ThreadLocalGet) {
UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
StackHandleScope<1> hs(self);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
// Positive test. See that We get something for float conversion.
{
Handle<mirror::Class> floating_decimal = hs.NewHandle(
- class_linker->FindClass(self,
- "Ljdk/internal/math/FloatingDecimal;",
- ScopedNullHandle<mirror::ClassLoader>()));
+ class_linker_->FindClass(self,
+ "Ljdk/internal/math/FloatingDecimal;",
+ ScopedNullHandle<mirror::ClassLoader>()));
ASSERT_TRUE(floating_decimal != nullptr);
- ASSERT_TRUE(class_linker->EnsureInitialized(self, floating_decimal, true, true));
+ ASSERT_TRUE(class_linker_->EnsureInitialized(self, floating_decimal, true, true));
ArtMethod* caller_method = floating_decimal->FindClassMethod(
"getBinaryToASCIIBuffer",
"()Ljdk/internal/math/FloatingDecimal$BinaryToASCIIBuffer;",
- class_linker->GetImagePointerSize());
+ class_linker_->GetImagePointerSize());
// floating_decimal->DumpClass(LOG_STREAM(ERROR), mirror::Class::kDumpClassFullDetail);
ASSERT_TRUE(caller_method != nullptr);
ASSERT_TRUE(caller_method->IsDirect());
@@ -965,27 +858,6 @@ TEST_F(UnstartedRuntimeTest, ThreadLocalGet) {
shadow_frame->ClearLink();
}
-
- // Negative test.
- PrepareForAborts();
-
- {
- // Just use a method in Class.
- ObjPtr<mirror::Class> class_class = GetClassRoot<mirror::Class>();
- ArtMethod* caller_method =
- &*class_class->GetDeclaredMethods(class_linker->GetImagePointerSize()).begin();
- UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, caller_method, 0);
- shadow_frame->SetLink(caller_frame.get());
-
- EnterTransactionMode();
- UnstartedThreadLocalGet(self, shadow_frame.get(), &result, 0);
- ASSERT_TRUE(IsTransactionAborted());
- ExitTransactionMode();
- ASSERT_TRUE(self->IsExceptionPending());
- self->ClearException();
-
- shadow_frame->ClearLink();
- }
}
TEST_F(UnstartedRuntimeTest, FloatConversion) {
@@ -993,17 +865,16 @@ TEST_F(UnstartedRuntimeTest, FloatConversion) {
ScopedObjectAccess soa(self);
StackHandleScope<1> hs(self);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
Handle<mirror::Class> double_class = hs.NewHandle(
- class_linker->FindClass(self,
- "Ljava/lang/Double;",
- ScopedNullHandle<mirror::ClassLoader>()));
+ class_linker_->FindClass(self,
+ "Ljava/lang/Double;",
+ ScopedNullHandle<mirror::ClassLoader>()));
ASSERT_TRUE(double_class != nullptr);
- ASSERT_TRUE(class_linker->EnsureInitialized(self, double_class, true, true));
+ ASSERT_TRUE(class_linker_->EnsureInitialized(self, double_class, true, true));
ArtMethod* method = double_class->FindClassMethod("toString",
"(D)Ljava/lang/String;",
- class_linker->GetImagePointerSize());
+ class_linker_->GetImagePointerSize());
ASSERT_TRUE(method != nullptr);
ASSERT_TRUE(method->IsDirect());
ASSERT_TRUE(method->GetDeclaringClass() == double_class.Get());
@@ -1029,62 +900,26 @@ TEST_F(UnstartedRuntimeTest, FloatConversion) {
EXPECT_EQ("1.23", mod_utf);
}
-TEST_F(UnstartedRuntimeTest, ThreadCurrentThread) {
- Thread* self = Thread::Current();
- ScopedObjectAccess soa(self);
-
- JValue result;
- UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
-
- StackHandleScope<1> hs(self);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- Handle<mirror::Class> thread_class = hs.NewHandle(
- class_linker->FindClass(self, "Ljava/lang/Thread;", ScopedNullHandle<mirror::ClassLoader>()));
- ASSERT_TRUE(thread_class.Get() != nullptr);
- ASSERT_TRUE(class_linker->EnsureInitialized(self, thread_class, true, true));
-
- // Negative test. In general, currentThread should fail (as we should not leak a peer that will
- // be recreated at runtime).
- PrepareForAborts();
-
- {
- EnterTransactionMode();
- UnstartedThreadCurrentThread(self, shadow_frame.get(), &result, 0);
- ASSERT_TRUE(IsTransactionAborted());
- ExitTransactionMode();
- ASSERT_TRUE(self->IsExceptionPending());
- self->ClearException();
- }
-}
-
TEST_F(UnstartedRuntimeTest, LogManager) {
Thread* self = Thread::Current();
ScopedObjectAccess soa(self);
StackHandleScope<1> hs(self);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- Handle<mirror::Class> log_manager_class = hs.NewHandle(
- class_linker->FindClass(self,
- "Ljava/util/logging/LogManager;",
- ScopedNullHandle<mirror::ClassLoader>()));
+ Handle<mirror::Class> log_manager_class = hs.NewHandle(class_linker_->FindClass(
+ self, "Ljava/util/logging/LogManager;", ScopedNullHandle<mirror::ClassLoader>()));
ASSERT_TRUE(log_manager_class.Get() != nullptr);
- ASSERT_TRUE(class_linker->EnsureInitialized(self, log_manager_class, true, true));
+ ASSERT_TRUE(class_linker_->EnsureInitialized(self, log_manager_class, true, true));
}
class UnstartedClassForNameTest : public UnstartedRuntimeTest {
public:
template <typename T>
- void RunTest(T& runner, bool in_transaction, bool should_succeed) {
+ void RunTest(T&& runner) {
Thread* self = Thread::Current();
ScopedObjectAccess soa(self);
// Ensure that Class is initialized.
- {
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- StackHandleScope<1> hs(self);
- Handle<mirror::Class> h_class = hs.NewHandle(GetClassRoot<mirror::Class>());
- CHECK(class_linker->EnsureInitialized(self, h_class, true, true));
- }
+ CHECK(GetClassRoot<mirror::Class>()->IsInitialized());
// A selection of classes from different core classpath components.
constexpr const char* kTestCases[] = {
@@ -1092,99 +927,20 @@ class UnstartedClassForNameTest : public UnstartedRuntimeTest {
"dalvik.system.ClassExt", // From libart.
};
- if (in_transaction) {
- // For transaction mode, we cannot load any classes, as the pre-fence initialization of
- // classes isn't transactional. Load them ahead of time.
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- for (const char* name : kTestCases) {
- class_linker->FindClass(self,
- DotToDescriptor(name).c_str(),
- ScopedNullHandle<mirror::ClassLoader>());
- CHECK(!self->IsExceptionPending()) << self->GetException()->Dump();
- }
- }
-
- if (!should_succeed) {
- // Negative test. In general, currentThread should fail (as we should not leak a peer that will
- // be recreated at runtime).
- PrepareForAborts();
- }
-
JValue result;
UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
for (const char* name : kTestCases) {
ObjPtr<mirror::String> name_string = mirror::String::AllocFromModifiedUtf8(self, name);
CHECK(name_string != nullptr);
-
- if (in_transaction) {
- StackHandleScope<1> hs(self);
- HandleWrapperObjPtr<mirror::String> h(hs.NewHandleWrapper(&name_string));
- EnterTransactionMode();
- }
CHECK(!self->IsExceptionPending());
runner(self, shadow_frame.get(), name_string, &result);
- if (should_succeed) {
- CHECK(!self->IsExceptionPending()) << name << " " << self->GetException()->Dump();
- CHECK(result.GetL() != nullptr) << name;
- } else {
- CHECK(self->IsExceptionPending()) << name;
- if (in_transaction) {
- ASSERT_TRUE(IsTransactionAborted());
- }
- self->ClearException();
- }
-
- if (in_transaction) {
- ExitTransactionMode();
- }
+ CHECK(!self->IsExceptionPending()) << name << " " << self->GetException()->Dump();
+ CHECK(result.GetL() != nullptr) << name;
}
}
-
- mirror::ClassLoader* GetBootClassLoader() REQUIRES_SHARED(Locks::mutator_lock_) {
- Thread* self = Thread::Current();
- StackHandleScope<2> hs(self);
- MutableHandle<mirror::ClassLoader> boot_cp = hs.NewHandle<mirror::ClassLoader>(nullptr);
-
- {
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
-
- // Create the fake boot classloader. Any instance is fine, they are technically interchangeable.
- Handle<mirror::Class> boot_cp_class = hs.NewHandle(
- class_linker->FindClass(self,
- "Ljava/lang/BootClassLoader;",
- ScopedNullHandle<mirror::ClassLoader>()));
- CHECK(boot_cp_class != nullptr);
- CHECK(class_linker->EnsureInitialized(self, boot_cp_class, true, true));
-
- boot_cp.Assign(boot_cp_class->AllocObject(self)->AsClassLoader());
- CHECK(boot_cp != nullptr);
-
- ArtMethod* boot_cp_init = boot_cp_class->FindConstructor(
- "()V", class_linker->GetImagePointerSize());
- CHECK(boot_cp_init != nullptr);
-
- JValue result;
- UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, boot_cp_init, 0);
- shadow_frame->SetVRegReference(0, boot_cp.Get());
-
- // create instruction data for invoke-direct {v0} of method with fake index
- uint16_t inst_data[3] = { 0x1070, 0x0000, 0x0010 };
-
- interpreter::DoCall<false>(boot_cp_init,
- self,
- *shadow_frame,
- Instruction::At(inst_data),
- inst_data[0],
- /* string_init= */ false,
- &result);
- CHECK(!self->IsExceptionPending());
- }
-
- return boot_cp.Get();
- }
};
TEST_F(UnstartedClassForNameTest, ClassForName) {
@@ -1195,7 +951,7 @@ TEST_F(UnstartedClassForNameTest, ClassForName) {
shadow_frame->SetVRegReference(0, name);
UnstartedClassForName(self, shadow_frame, result, 0);
};
- RunTest(runner, false, true);
+ RunTest(runner);
}
TEST_F(UnstartedClassForNameTest, ClassForNameLong) {
@@ -1208,7 +964,7 @@ TEST_F(UnstartedClassForNameTest, ClassForNameLong) {
shadow_frame->SetVRegReference(2, nullptr);
UnstartedClassForNameLong(self, shadow_frame, result, 0);
};
- RunTest(runner, false, true);
+ RunTest(runner);
}
TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoader) {
@@ -1227,50 +983,7 @@ TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoader) {
shadow_frame->SetVRegReference(2, boot_cp.Get());
UnstartedClassForNameLong(th, shadow_frame, result, 0);
};
- RunTest(runner, false, true);
-}
-
-TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoaderTransaction) {
- Thread* self = Thread::Current();
- ScopedObjectAccess soa(self);
-
- StackHandleScope<1> hs(self);
- Handle<mirror::ClassLoader> boot_cp = hs.NewHandle(GetBootClassLoader());
-
- auto runner = [&](Thread* th,
- ShadowFrame* shadow_frame,
- ObjPtr<mirror::String> name,
- JValue* result)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- shadow_frame->SetVRegReference(0, name);
- shadow_frame->SetVReg(1, 0);
- shadow_frame->SetVRegReference(2, boot_cp.Get());
- UnstartedClassForNameLong(th, shadow_frame, result, 0);
- };
- RunTest(runner, true, true);
-}
-
-TEST_F(UnstartedClassForNameTest, ClassForNameLongWithClassLoaderFail) {
- Thread* self = Thread::Current();
- ScopedObjectAccess soa(self);
-
- StackHandleScope<2> hs(self);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
- jobject path_jobj = class_linker->CreatePathClassLoader(self, {});
- ASSERT_TRUE(path_jobj != nullptr);
- Handle<mirror::ClassLoader> path_cp = hs.NewHandle<mirror::ClassLoader>(
- self->DecodeJObject(path_jobj)->AsClassLoader());
-
- auto runner = [&](Thread* th,
- ShadowFrame* shadow_frame,
- ObjPtr<mirror::String> name,
- JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) {
- shadow_frame->SetVRegReference(0, name);
- shadow_frame->SetVReg(1, 0);
- shadow_frame->SetVRegReference(2, path_cp.Get());
- UnstartedClassForNameLong(th, shadow_frame, result, 0);
- };
- RunTest(runner, true, false);
+ RunTest(runner);
}
TEST_F(UnstartedRuntimeTest, ClassGetSignatureAnnotation) {
@@ -1278,13 +991,12 @@ TEST_F(UnstartedRuntimeTest, ClassGetSignatureAnnotation) {
ScopedObjectAccess soa(self);
StackHandleScope<1> hs(self);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
Handle<mirror::Class> list_class = hs.NewHandle(
- class_linker->FindClass(self,
- "Ljava/util/List;",
- ScopedNullHandle<mirror::ClassLoader>()));
+ class_linker_->FindClass(self,
+ "Ljava/util/List;",
+ ScopedNullHandle<mirror::ClassLoader>()));
ASSERT_TRUE(list_class.Get() != nullptr);
- ASSERT_TRUE(class_linker->EnsureInitialized(self, list_class, true, true));
+ ASSERT_TRUE(class_linker_->EnsureInitialized(self, list_class, true, true));
JValue result;
UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
@@ -1315,17 +1027,16 @@ TEST_F(UnstartedRuntimeTest, ConstructorNewInstance0) {
ScopedObjectAccess soa(self);
StackHandleScope<4> hs(self);
- ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
// Get Throwable.
Handle<mirror::Class> throw_class = hs.NewHandle(GetClassRoot<mirror::Throwable>());
- ASSERT_TRUE(class_linker->EnsureInitialized(self, throw_class, true, true));
+ ASSERT_TRUE(class_linker_->EnsureInitialized(self, throw_class, true, true));
// Get an input object.
Handle<mirror::String> input = hs.NewHandle(mirror::String::AllocFromModifiedUtf8(self, "abd"));
// Find the constructor.
- PointerSize pointer_size = class_linker->GetImagePointerSize();
+ PointerSize pointer_size = class_linker_->GetImagePointerSize();
ArtMethod* throw_cons = throw_class->FindConstructor("(Ljava/lang/String;)V", pointer_size);
ASSERT_TRUE(throw_cons != nullptr);
Handle<mirror::Constructor> cons = hs.NewHandle((pointer_size == PointerSize::k64)
diff --git a/runtime/interpreter/unstarted_runtime_test.h b/runtime/interpreter/unstarted_runtime_test.h
new file mode 100644
index 0000000000..465a4268ea
--- /dev/null
+++ b/runtime/interpreter/unstarted_runtime_test.h
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#ifndef ART_RUNTIME_INTERPRETER_UNSTARTED_RUNTIME_TEST_H_
+#define ART_RUNTIME_INTERPRETER_UNSTARTED_RUNTIME_TEST_H_
+
+#include "unstarted_runtime.h"
+
+#include <memory>
+
+#include "class_linker-inl.h"
+#include "common_runtime_test.h"
+#include "handle.h"
+#include "handle_scope-inl.h"
+#include "interpreter_common.h"
+#include "mirror/object_array-alloc-inl.h"
+#include "mirror/object_array-inl.h"
+#include "shadow_frame-inl.h"
+
+namespace art HIDDEN {
+namespace interpreter {
+
+// Deleter to be used with ShadowFrame::CreateDeoptimizedFrame objects.
+struct DeoptShadowFrameDelete {
+ // NOTE: Deleting a const object is valid but free() takes a non-const pointer.
+ void operator()(ShadowFrame* ptr) const {
+ ShadowFrame::DeleteDeoptimizedFrame(ptr);
+ }
+};
+// Alias for std::unique_ptr<> that uses the above deleter.
+using UniqueDeoptShadowFramePtr = std::unique_ptr<ShadowFrame, DeoptShadowFrameDelete>;
+
+class UnstartedRuntimeTestBase : public CommonRuntimeTest {
+ protected:
+ // Re-expose all UnstartedRuntime implementations so we don't need to declare a million
+ // test friends.
+
+ // Methods that intercept available libcore implementations.
+#define UNSTARTED_DIRECT(Name, DescriptorIgnored, NameIgnored, SignatureIgnored) \
+ static void Unstarted ## Name(Thread* self, \
+ ShadowFrame* shadow_frame, \
+ JValue* result, \
+ size_t arg_offset) \
+ REQUIRES_SHARED(Locks::mutator_lock_) { \
+ interpreter::UnstartedRuntime::Unstarted ## Name(self, shadow_frame, result, arg_offset); \
+ }
+ UNSTARTED_RUNTIME_DIRECT_LIST(UNSTARTED_DIRECT)
+#undef UNSTARTED_DIRECT
+
+ // Methods that are native.
+#define UNSTARTED_JNI(Name, DescriptorIgnored, NameIgnored, SignatureIgnored) \
+ static void UnstartedJNI ## Name(Thread* self, \
+ ArtMethod* method, \
+ mirror::Object* receiver, \
+ uint32_t* args, \
+ JValue* result) \
+ REQUIRES_SHARED(Locks::mutator_lock_) { \
+ interpreter::UnstartedRuntime::UnstartedJNI ## Name(self, method, receiver, args, result); \
+ }
+ UNSTARTED_RUNTIME_JNI_LIST(UNSTARTED_JNI)
+#undef UNSTARTED_JNI
+
+ UniqueDeoptShadowFramePtr CreateShadowFrame(uint32_t num_vregs,
+ ArtMethod* method,
+ uint32_t dex_pc) {
+ return UniqueDeoptShadowFramePtr(
+ ShadowFrame::CreateDeoptimizedFrame(num_vregs, method, dex_pc));
+ }
+
+ mirror::ClassLoader* GetBootClassLoader() REQUIRES_SHARED(Locks::mutator_lock_) {
+ Thread* self = Thread::Current();
+ StackHandleScope<2> hs(self);
+
+ // Create the fake boot classloader. Any instance is fine, they are technically interchangeable.
+ Handle<mirror::Class> boot_cp_class = hs.NewHandle(class_linker_->FindClass(
+ self, "Ljava/lang/BootClassLoader;", ScopedNullHandle<mirror::ClassLoader>()));
+ CHECK(boot_cp_class != nullptr);
+ CHECK(class_linker_->EnsureInitialized(
+ self, boot_cp_class, /*can_init_fields=*/ true, /*can_init_parents=*/ true));
+
+ Handle<mirror::ClassLoader> boot_cp =
+ hs.NewHandle(boot_cp_class->AllocObject(self)->AsClassLoader());
+ CHECK(boot_cp != nullptr);
+
+ ArtMethod* boot_cp_init =
+ boot_cp_class->FindConstructor("()V", class_linker_->GetImagePointerSize());
+ CHECK(boot_cp_init != nullptr);
+
+ JValue result;
+ UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, boot_cp_init, 0);
+ shadow_frame->SetVRegReference(0, boot_cp.Get());
+
+ // create instruction data for invoke-direct {v0} of method with fake index
+ uint16_t inst_data[3] = { 0x1070, 0x0000, 0x0010 };
+
+ interpreter::DoCall<false>(boot_cp_init,
+ self,
+ *shadow_frame,
+ Instruction::At(inst_data),
+ inst_data[0],
+ /* string_init= */ false,
+ &result);
+ CHECK(!self->IsExceptionPending());
+
+ return boot_cp.Get();
+ }
+};
+
+} // namespace interpreter
+} // namespace art
+
+#endif // ART_RUNTIME_INTERPRETER_UNSTARTED_RUNTIME_TEST_H_
diff --git a/runtime/interpreter/unstarted_runtime_transaction_test.cc b/runtime/interpreter/unstarted_runtime_transaction_test.cc
new file mode 100644
index 0000000000..abbcbbb12e
--- /dev/null
+++ b/runtime/interpreter/unstarted_runtime_transaction_test.cc
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "unstarted_runtime_test.h"
+
+#include "class_root-inl.h"
+#include "common_transaction_test.h"
+#include "dex/descriptors_names.h"
+#include "interpreter/interpreter_common.h"
+#include "handle.h"
+#include "handle_scope-inl.h"
+#include "mirror/class-inl.h"
+
+namespace art HIDDEN {
+namespace interpreter {
+
+class UnstartedRuntimeTransactionTest : public CommonTransactionTestBase<UnstartedRuntimeTestBase> {
+ protected:
+ // Prepare for aborts. Aborts assume that the exception class is already resolved, as the
+ // loading code doesn't work under transactions.
+ void PrepareForAborts() REQUIRES_SHARED(Locks::mutator_lock_) {
+ ObjPtr<mirror::Object> result = Runtime::Current()->GetClassLinker()->FindClass(
+ Thread::Current(),
+ kTransactionAbortErrorDescriptor,
+ ScopedNullHandle<mirror::ClassLoader>());
+ CHECK(result != nullptr);
+ }
+};
+
+TEST_F(UnstartedRuntimeTransactionTest, ToLowerUpper) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ UniqueDeoptShadowFramePtr tmp = CreateShadowFrame(10, nullptr, 0);
+
+ PrepareForAborts();
+
+ for (uint32_t i = 128; i < 256; ++i) {
+ {
+ JValue result;
+ tmp->SetVReg(0, static_cast<int32_t>(i));
+ EnterTransactionMode();
+ UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0);
+ ASSERT_TRUE(IsTransactionAborted());
+ ExitTransactionMode();
+ ASSERT_TRUE(self->IsExceptionPending());
+ }
+ {
+ JValue result;
+ tmp->SetVReg(0, static_cast<int32_t>(i));
+ EnterTransactionMode();
+ UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0);
+ ASSERT_TRUE(IsTransactionAborted());
+ ExitTransactionMode();
+ ASSERT_TRUE(self->IsExceptionPending());
+ }
+ }
+ for (uint64_t i = 256; i <= std::numeric_limits<uint32_t>::max(); i <<= 1) {
+ {
+ JValue result;
+ tmp->SetVReg(0, static_cast<int32_t>(i));
+ EnterTransactionMode();
+ UnstartedCharacterToLowerCase(self, tmp.get(), &result, 0);
+ ASSERT_TRUE(IsTransactionAborted());
+ ExitTransactionMode();
+ ASSERT_TRUE(self->IsExceptionPending());
+ }
+ {
+ JValue result;
+ tmp->SetVReg(0, static_cast<int32_t>(i));
+ EnterTransactionMode();
+ UnstartedCharacterToUpperCase(self, tmp.get(), &result, 0);
+ ASSERT_TRUE(IsTransactionAborted());
+ ExitTransactionMode();
+ ASSERT_TRUE(self->IsExceptionPending());
+ }
+ }
+}
+
+TEST_F(UnstartedRuntimeTransactionTest, ThreadLocalGet) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
+
+ // Negative test.
+ PrepareForAborts();
+
+ // Just use a method in Class.
+ ObjPtr<mirror::Class> class_class = GetClassRoot<mirror::Class>();
+ ArtMethod* caller_method =
+ &*class_class->GetDeclaredMethods(class_linker_->GetImagePointerSize()).begin();
+ UniqueDeoptShadowFramePtr caller_frame = CreateShadowFrame(10, caller_method, 0);
+ shadow_frame->SetLink(caller_frame.get());
+
+ JValue result;
+ EnterTransactionMode();
+ UnstartedThreadLocalGet(self, shadow_frame.get(), &result, 0);
+ ASSERT_TRUE(IsTransactionAborted());
+ ExitTransactionMode();
+ ASSERT_TRUE(self->IsExceptionPending());
+ self->ClearException();
+
+ shadow_frame->ClearLink();
+}
+
+TEST_F(UnstartedRuntimeTransactionTest, ThreadCurrentThread) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+ UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
+
+ // Negative test. In general, currentThread should fail (as we should not leak a peer that will
+ // be recreated at runtime).
+ PrepareForAborts();
+
+ JValue result;
+ EnterTransactionMode();
+ UnstartedThreadCurrentThread(self, shadow_frame.get(), &result, 0);
+ ASSERT_TRUE(IsTransactionAborted());
+ ExitTransactionMode();
+ ASSERT_TRUE(self->IsExceptionPending());
+ self->ClearException();
+}
+
+class UnstartedClassForNameTransactionTest : public UnstartedRuntimeTransactionTest {
+ public:
+ template <typename T>
+ void RunTest(T&& runner, bool should_succeed) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+
+ // Ensure that Class is initialized.
+ CHECK(GetClassRoot<mirror::Class>()->IsInitialized());
+
+ // A selection of classes from different core classpath components.
+ constexpr const char* kTestCases[] = {
+ "java.net.CookieManager", // From libcore.
+ "dalvik.system.ClassExt", // From libart.
+ };
+
+ // For transaction mode, we cannot load any classes, as the pre-fence initialization of
+ // classes isn't transactional. Load them ahead of time.
+ for (const char* name : kTestCases) {
+ class_linker_->FindClass(self,
+ DotToDescriptor(name).c_str(),
+ ScopedNullHandle<mirror::ClassLoader>());
+ CHECK(!self->IsExceptionPending()) << self->GetException()->Dump();
+ }
+
+ if (!should_succeed) {
+ // Negative test. In general, currentThread should fail (as we should not leak a peer that will
+ // be recreated at runtime).
+ PrepareForAborts();
+ }
+
+ JValue result;
+ UniqueDeoptShadowFramePtr shadow_frame = CreateShadowFrame(10, nullptr, 0);
+
+ for (const char* name : kTestCases) {
+ EnterTransactionMode();
+
+ ObjPtr<mirror::String> name_string = mirror::String::AllocFromModifiedUtf8(self, name);
+ CHECK(name_string != nullptr);
+ CHECK(!self->IsExceptionPending());
+
+ runner(self, shadow_frame.get(), name_string, &result);
+
+ if (should_succeed) {
+ CHECK(!self->IsExceptionPending()) << name << " " << self->GetException()->Dump();
+ CHECK(result.GetL() != nullptr) << name;
+ } else {
+ CHECK(self->IsExceptionPending()) << name;
+ ASSERT_TRUE(IsTransactionAborted());
+ self->ClearException();
+ }
+
+ ExitTransactionMode();
+ }
+ }
+};
+
+TEST_F(UnstartedClassForNameTransactionTest, ClassForNameLongWithClassLoader) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+
+ StackHandleScope<1> hs(self);
+ Handle<mirror::ClassLoader> boot_cp = hs.NewHandle(GetBootClassLoader());
+
+ auto runner = [&](Thread* th,
+ ShadowFrame* shadow_frame,
+ ObjPtr<mirror::String> name,
+ JValue* result)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ shadow_frame->SetVRegReference(0, name);
+ shadow_frame->SetVReg(1, 0);
+ shadow_frame->SetVRegReference(2, boot_cp.Get());
+ UnstartedClassForNameLong(th, shadow_frame, result, 0);
+ };
+ RunTest(runner, /*should_succeed=*/ true);
+}
+
+TEST_F(UnstartedClassForNameTransactionTest, ClassForNameLongWithClassLoaderFail) {
+ Thread* self = Thread::Current();
+ ScopedObjectAccess soa(self);
+
+ StackHandleScope<2> hs(self);
+ jobject path_jobj = class_linker_->CreatePathClassLoader(self, {});
+ ASSERT_TRUE(path_jobj != nullptr);
+ Handle<mirror::ClassLoader> path_cp = hs.NewHandle<mirror::ClassLoader>(
+ self->DecodeJObject(path_jobj)->AsClassLoader());
+
+ auto runner = [&](Thread* th,
+ ShadowFrame* shadow_frame,
+ ObjPtr<mirror::String> name,
+ JValue* result) REQUIRES_SHARED(Locks::mutator_lock_) {
+ shadow_frame->SetVRegReference(0, name);
+ shadow_frame->SetVReg(1, 0);
+ shadow_frame->SetVRegReference(2, path_cp.Get());
+ UnstartedClassForNameLong(th, shadow_frame, result, 0);
+ };
+ RunTest(runner, /*should_succeed=*/ false);
+}
+
+} // namespace interpreter
+} // namespace art
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index cb80b4151d..4d395c8528 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -17,6 +17,7 @@
#include "jit.h"
#include <dlfcn.h>
+#include <sys/resource.h>
#include "art_method-inl.h"
#include "base/file_utils.h"
@@ -1487,6 +1488,13 @@ static void* RunPollingThread(void* arg) {
/* create_peer= */ false);
CHECK(thread_attached);
+ if (getpriority(PRIO_PROCESS, 0 /* this thread */) == 0) {
+ // Slightly reduce thread priority, mostly so the suspend logic notices that we're
+ // not a high priority thread, and can time out more slowly. May fail on host.
+ (void)setpriority(PRIO_PROCESS, 0 /* this thread */, 1);
+ } else {
+ PLOG(ERROR) << "Unexpected BootImagePollingThread priority: " << getpriority(PRIO_PROCESS, 0);
+ }
{
// Prevent other threads from running while we are remapping the boot image
// ArtMethod's. Native threads might still be running, but they cannot
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index eae56a45b3..4b69dc5c01 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -342,6 +342,8 @@ const void* JitCodeCache::GetSavedEntryPointOfPreCompiledMethod(ArtMethod* metho
auto it = saved_compiled_methods_map_.find(method);
if (it != saved_compiled_methods_map_.end()) {
code_ptr = it->second;
+ // Now that we're using the saved entrypoint, remove it from the saved map.
+ saved_compiled_methods_map_.erase(it);
}
}
if (code_ptr != nullptr) {
@@ -488,33 +490,36 @@ void JitCodeCache::FreeAllMethodHeaders(
->RemoveDependentsWithMethodHeaders(method_headers);
}
- ScopedCodeCacheWrite scc(private_region_);
- for (const OatQuickMethodHeader* method_header : method_headers) {
- FreeCodeAndData(method_header->GetCode());
- }
+ {
+ ScopedCodeCacheWrite scc(private_region_);
+ for (const OatQuickMethodHeader* method_header : method_headers) {
+ FreeCodeAndData(method_header->GetCode());
+ }
- // We have potentially removed a lot of debug info. Do maintenance pass to save space.
- RepackNativeDebugInfoForJit();
+ // We have potentially removed a lot of debug info. Do maintenance pass to save space.
+ RepackNativeDebugInfoForJit();
+ }
// Check that the set of compiled methods exactly matches native debug information.
// Does not check zygote methods since they can change concurrently.
if (kIsDebugBuild && !Runtime::Current()->IsZygote()) {
std::map<const void*, ArtMethod*> compiled_methods;
+ std::set<const void*> debug_info;
VisitAllMethods([&](const void* addr, ArtMethod* method) {
if (!IsInZygoteExecSpace(addr)) {
CHECK(addr != nullptr && method != nullptr);
compiled_methods.emplace(addr, method);
}
});
- std::set<const void*> debug_info;
ForEachNativeDebugSymbol([&](const void* addr, size_t, const char* name) {
addr = AlignDown(addr, GetInstructionSetInstructionAlignment(kRuntimeISA)); // Thumb-bit.
- CHECK(debug_info.emplace(addr).second) << "Duplicate debug info: " << addr << " " << name;
+ bool res = debug_info.emplace(addr).second;
+ CHECK(res) << "Duplicate debug info: " << addr << " " << name;
CHECK_EQ(compiled_methods.count(addr), 1u) << "Extra debug info: " << addr << " " << name;
});
if (!debug_info.empty()) { // If debug-info generation is enabled.
for (const auto& [addr, method] : compiled_methods) {
- CHECK_EQ(debug_info.count(addr), 1u) << "No debug info: " << method->PrettyMethod();
+ CHECK_EQ(debug_info.count(addr), 1u) << "Mising debug info";
}
CHECK_EQ(compiled_methods.size(), debug_info.size());
}
@@ -529,52 +534,67 @@ void JitCodeCache::RemoveMethodsIn(Thread* self, const LinearAlloc& alloc) {
// for entries in this set. And it's more efficient to iterate through
// the CHA dependency map just once with an unordered_set.
std::unordered_set<OatQuickMethodHeader*> method_headers;
+ MutexLock mu(self, *Locks::jit_lock_);
+ // We do not check if a code cache GC is in progress, as this method comes
+ // with the classlinker_classes_lock_ held, and suspending ourselves could
+ // lead to a deadlock.
{
- MutexLock mu(self, *Locks::jit_lock_);
- // We do not check if a code cache GC is in progress, as this method comes
- // with the classlinker_classes_lock_ held, and suspending ourselves could
- // lead to a deadlock.
- {
- for (auto it = jni_stubs_map_.begin(); it != jni_stubs_map_.end();) {
- it->second.RemoveMethodsIn(alloc);
- if (it->second.GetMethods().empty()) {
- method_headers.insert(OatQuickMethodHeader::FromCodePointer(it->second.GetCode()));
- it = jni_stubs_map_.erase(it);
- } else {
- it->first.UpdateShorty(it->second.GetMethods().front());
- ++it;
- }
+ for (auto it = jni_stubs_map_.begin(); it != jni_stubs_map_.end();) {
+ it->second.RemoveMethodsIn(alloc);
+ if (it->second.GetMethods().empty()) {
+ method_headers.insert(OatQuickMethodHeader::FromCodePointer(it->second.GetCode()));
+ it = jni_stubs_map_.erase(it);
+ } else {
+ it->first.UpdateShorty(it->second.GetMethods().front());
+ ++it;
}
- for (auto it = method_code_map_.begin(); it != method_code_map_.end();) {
- if (alloc.ContainsUnsafe(it->second)) {
- method_headers.insert(OatQuickMethodHeader::FromCodePointer(it->first));
- VLOG(jit) << "JIT removed " << it->second->PrettyMethod() << ": " << it->first;
- it = method_code_map_.erase(it);
- } else {
- ++it;
- }
+ }
+ for (auto it = zombie_jni_code_.begin(); it != zombie_jni_code_.end();) {
+ if (alloc.ContainsUnsafe(*it)) {
+ it = zombie_jni_code_.erase(it);
+ } else {
+ ++it;
}
}
- for (auto it = osr_code_map_.begin(); it != osr_code_map_.end();) {
- if (alloc.ContainsUnsafe(it->first)) {
- // Note that the code has already been pushed to method_headers in the loop
- // above and is going to be removed in FreeCode() below.
- it = osr_code_map_.erase(it);
+ for (auto it = processed_zombie_jni_code_.begin(); it != processed_zombie_jni_code_.end();) {
+ if (alloc.ContainsUnsafe(*it)) {
+ it = processed_zombie_jni_code_.erase(it);
} else {
++it;
}
}
- for (auto it = profiling_infos_.begin(); it != profiling_infos_.end();) {
- ProfilingInfo* info = it->second;
- if (alloc.ContainsUnsafe(info->GetMethod())) {
- private_region_.FreeWritableData(reinterpret_cast<uint8_t*>(info));
- it = profiling_infos_.erase(it);
+ for (auto it = method_code_map_.begin(); it != method_code_map_.end();) {
+ if (alloc.ContainsUnsafe(it->second)) {
+ method_headers.insert(OatQuickMethodHeader::FromCodePointer(it->first));
+ VLOG(jit) << "JIT removed " << it->second->PrettyMethod() << ": " << it->first;
+ zombie_code_.erase(it->first);
+ processed_zombie_code_.erase(it->first);
+ it = method_code_map_.erase(it);
} else {
++it;
}
}
- FreeAllMethodHeaders(method_headers);
}
+ for (auto it = osr_code_map_.begin(); it != osr_code_map_.end();) {
+ DCHECK(!ContainsElement(zombie_code_, it->second));
+ if (alloc.ContainsUnsafe(it->first)) {
+ // Note that the code has already been pushed to method_headers in the loop
+ // above and is going to be removed in FreeCode() below.
+ it = osr_code_map_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ for (auto it = profiling_infos_.begin(); it != profiling_infos_.end();) {
+ ProfilingInfo* info = it->second;
+ if (alloc.ContainsUnsafe(info->GetMethod())) {
+ private_region_.FreeWritableData(reinterpret_cast<uint8_t*>(info));
+ it = profiling_infos_.erase(it);
+ } else {
+ ++it;
+ }
+ }
+ FreeAllMethodHeaders(method_headers);
}
bool JitCodeCache::IsWeakAccessEnabled(Thread* self) const {
@@ -629,29 +649,6 @@ void JitCodeCache::CopyInlineCacheInto(
}
}
-static void ClearMethodCounter(ArtMethod* method, bool was_warm)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (was_warm) {
- method->SetPreviouslyWarm();
- }
- method->ResetCounter(Runtime::Current()->GetJITOptions()->GetWarmupThreshold());
- // We add one sample so that the profile knows that the method was executed at least once.
- // This is required for layout purposes.
- method->UpdateCounter(/* new_samples= */ 1);
-}
-
-void JitCodeCache::WaitForPotentialCollectionToCompleteRunnable(Thread* self) {
- while (collection_in_progress_) {
- Locks::jit_lock_->Unlock(self);
- {
- ScopedThreadSuspension sts(self, ThreadState::kSuspended);
- MutexLock mu(self, *Locks::jit_lock_);
- WaitForPotentialCollectionToComplete(self);
- }
- Locks::jit_lock_->Lock(self);
- }
-}
-
bool JitCodeCache::Commit(Thread* self,
JitMemoryRegion* region,
ArtMethod* method,
@@ -679,9 +676,6 @@ bool JitCodeCache::Commit(Thread* self,
OatQuickMethodHeader* method_header = nullptr;
{
MutexLock mu(self, *Locks::jit_lock_);
- // We need to make sure that there will be no jit-gcs going on and wait for any ongoing one to
- // finish.
- WaitForPotentialCollectionToCompleteRunnable(self);
const uint8_t* code_ptr = region->CommitCode(reserved_code, code, stack_map_data);
if (code_ptr == nullptr) {
return false;
@@ -722,10 +716,9 @@ bool JitCodeCache::Commit(Thread* self,
bool single_impl_still_valid = true;
for (ArtMethod* single_impl : cha_single_implementation_list) {
if (!single_impl->HasSingleImplementation()) {
- // Simply discard the compiled code. Clear the counter so that it may be recompiled later.
+ // Simply discard the compiled code.
// Hopefully the class hierarchy will be more stable when compilation is retried.
single_impl_still_valid = false;
- ClearMethodCounter(method, /*was_warm=*/ false);
break;
}
}
@@ -781,11 +774,6 @@ bool JitCodeCache::Commit(Thread* self,
method, method_header->GetEntryPoint());
}
}
- if (collection_in_progress_) {
- // We need to update the live bitmap if there is a GC to ensure it sees this new
- // code.
- GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(code_ptr));
- }
VLOG(jit)
<< "JIT added (kind=" << compilation_kind << ") "
<< ArtMethod::PrettyMethod(method) << "@" << method
@@ -825,7 +813,6 @@ bool JitCodeCache::RemoveMethod(ArtMethod* method, bool release_memory) {
return false;
}
- ClearMethodCounter(method, /* was_warm= */ false);
Runtime::Current()->GetInstrumentation()->InitializeMethodsCode(method, /*aot_code=*/ nullptr);
VLOG(jit)
<< "JIT removed (osr=" << std::boolalpha << osr << std::noboolalpha << ") "
@@ -854,6 +841,7 @@ bool JitCodeCache::RemoveMethodLocked(ArtMethod* method, bool release_memory) {
FreeCodeAndData(it->second.GetCode());
}
jni_stubs_map_.erase(it);
+ zombie_jni_code_.erase(method);
} else {
it->first.UpdateShorty(it->second.GetMethods().front());
}
@@ -984,7 +972,6 @@ bool JitCodeCache::Reserve(Thread* self,
{
ScopedThreadSuspension sts(self, ThreadState::kSuspended);
MutexLock mu(self, *Locks::jit_lock_);
- WaitForPotentialCollectionToComplete(self);
ScopedCodeCacheWrite ccw(*region);
code = region->AllocateCode(code_size);
data = region->AllocateData(data_size);
@@ -1001,8 +988,8 @@ bool JitCodeCache::Reserve(Thread* self,
<< PrettySize(data_size);
return false;
}
- // Run a code cache collection and try again.
- GarbageCollectCache(self);
+ // Increase the capacity and try again.
+ IncreaseCodeCacheCapacity(self);
}
*reserved_code = ArrayRef<const uint8_t>(code, code_size);
@@ -1080,11 +1067,6 @@ class MarkCodeClosure final : public Closure {
Barrier* const barrier_;
};
-void JitCodeCache::NotifyCollectionDone(Thread* self) {
- collection_in_progress_ = false;
- lock_cond_.Broadcast(self);
-}
-
void JitCodeCache::MarkCompiledCodeOnThreadStacks(Thread* self) {
Barrier barrier(0);
size_t threads_running_checkpoint = 0;
@@ -1102,86 +1084,116 @@ bool JitCodeCache::IsAtMaxCapacity() const {
return private_region_.GetCurrentCapacity() == private_region_.GetMaxCapacity();
}
-void JitCodeCache::GarbageCollectCache(Thread* self) {
+void JitCodeCache::IncreaseCodeCacheCapacity(Thread* self) {
+ ScopedThreadSuspension sts(self, ThreadState::kSuspended);
+ MutexLock mu(self, *Locks::jit_lock_);
+ // Wait for a potential collection, as the size of the bitmap used by that collection
+ // is of the current capacity.
+ WaitForPotentialCollectionToComplete(self);
+ private_region_.IncreaseCodeCacheCapacity();
+}
+
+void JitCodeCache::RemoveUnmarkedCode(Thread* self) {
ScopedTrace trace(__FUNCTION__);
- // Wait for an existing collection, or let everyone know we are starting one.
- {
- ScopedThreadSuspension sts(self, ThreadState::kSuspended);
- MutexLock mu(self, *Locks::jit_lock_);
- if (!garbage_collect_code_) {
- private_region_.IncreaseCodeCacheCapacity();
- return;
- } else if (WaitForPotentialCollectionToComplete(self)) {
- return;
+ std::unordered_set<OatQuickMethodHeader*> method_headers;
+ ScopedDebugDisallowReadBarriers sddrb(self);
+ MutexLock mu(self, *Locks::jit_lock_);
+ // Iterate over all zombie code and remove entries that are not marked.
+ for (auto it = processed_zombie_code_.begin(); it != processed_zombie_code_.end();) {
+ const void* code_ptr = *it;
+ uintptr_t allocation = FromCodeToAllocation(code_ptr);
+ DCHECK(!IsInZygoteExecSpace(code_ptr));
+ if (GetLiveBitmap()->Test(allocation)) {
+ ++it;
} else {
- number_of_collections_++;
- live_bitmap_.reset(CodeCacheBitmap::Create(
- "code-cache-bitmap",
- reinterpret_cast<uintptr_t>(private_region_.GetExecPages()->Begin()),
- reinterpret_cast<uintptr_t>(
- private_region_.GetExecPages()->Begin() + private_region_.GetCurrentCapacity() / 2)));
- collection_in_progress_ = true;
+ OatQuickMethodHeader* header = OatQuickMethodHeader::FromCodePointer(code_ptr);
+ method_headers.insert(header);
+ method_code_map_.erase(header->GetCode());
+ VLOG(jit) << "JIT removed " << *it;
+ it = processed_zombie_code_.erase(it);
+ }
+ }
+ for (auto it = processed_zombie_jni_code_.begin(); it != processed_zombie_jni_code_.end();) {
+ ArtMethod* method = *it;
+ auto stub = jni_stubs_map_.find(JniStubKey(method));
+ if (stub == jni_stubs_map_.end()) {
+ it = processed_zombie_jni_code_.erase(it);
+ continue;
+ }
+ JniStubData& data = stub->second;
+ if (!data.IsCompiled() || !ContainsElement(data.GetMethods(), method)) {
+ it = processed_zombie_jni_code_.erase(it);
+ } else if (method->GetEntryPointFromQuickCompiledCode() ==
+ OatQuickMethodHeader::FromCodePointer(data.GetCode())->GetEntryPoint()) {
+ // The stub got reused for this method, remove ourselves from the zombie
+ // list.
+ it = processed_zombie_jni_code_.erase(it);
+ } else if (!GetLiveBitmap()->Test(FromCodeToAllocation(data.GetCode()))) {
+ data.RemoveMethod(method);
+ if (data.GetMethods().empty()) {
+ OatQuickMethodHeader* header = OatQuickMethodHeader::FromCodePointer(data.GetCode());
+ method_headers.insert(header);
+ CHECK(ContainsPc(header));
+ VLOG(jit) << "JIT removed native code of" << method->PrettyMethod();
+ jni_stubs_map_.erase(stub);
+ } else {
+ stub->first.UpdateShorty(stub->second.GetMethods().front());
+ }
+ it = processed_zombie_jni_code_.erase(it);
+ } else {
+ ++it;
}
}
+ FreeAllMethodHeaders(method_headers);
+}
- TimingLogger logger("JIT code cache timing logger", true, VLOG_IS_ON(jit));
- {
- TimingLogger::ScopedTiming st("Code cache collection", &logger);
+void JitCodeCache::AddZombieCode(ArtMethod* method, const void* entry_point) {
+ CHECK(ContainsPc(entry_point));
+ CHECK(method->IsNative() || (method->GetEntryPointFromQuickCompiledCode() != entry_point));
+ const void* code_ptr = OatQuickMethodHeader::FromEntryPoint(entry_point)->GetCode();
+ if (!IsInZygoteExecSpace(code_ptr)) {
+ Thread* self = Thread::Current();
+ if (Locks::jit_lock_->IsExclusiveHeld(self)) {
+ AddZombieCodeInternal(method, code_ptr);
+ } else {
+ MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+ AddZombieCodeInternal(method, code_ptr);
+ }
+ }
+}
- VLOG(jit) << "Do code cache collection, code="
- << PrettySize(CodeCacheSize())
- << ", data=" << PrettySize(DataCacheSize());
- DoCollection(self);
+class JitGcTask final : public Task {
+ public:
+ JitGcTask() {}
- VLOG(jit) << "After code cache collection, code="
- << PrettySize(CodeCacheSize())
- << ", data=" << PrettySize(DataCacheSize());
+ void Run(Thread* self) override {
+ Runtime::Current()->GetJit()->GetCodeCache()->DoCollection(self);
+ }
- {
- MutexLock mu(self, *Locks::jit_lock_);
- private_region_.IncreaseCodeCacheCapacity();
- live_bitmap_.reset(nullptr);
- NotifyCollectionDone(self);
- }
+ void Finalize() override {
+ delete this;
}
- Runtime::Current()->GetJit()->AddTimingLogger(logger);
-}
+};
-void JitCodeCache::RemoveUnmarkedCode(Thread* self) {
- ScopedTrace trace(__FUNCTION__);
- ScopedDebugDisallowReadBarriers sddrb(self);
- std::unordered_set<OatQuickMethodHeader*> method_headers;
- {
- MutexLock mu(self, *Locks::jit_lock_);
- // Iterate over all compiled code and remove entries that are not marked.
- for (auto it = jni_stubs_map_.begin(); it != jni_stubs_map_.end();) {
- JniStubData* data = &it->second;
- if (IsInZygoteExecSpace(data->GetCode()) ||
- !data->IsCompiled() ||
- GetLiveBitmap()->Test(FromCodeToAllocation(data->GetCode()))) {
- ++it;
- } else {
- method_headers.insert(OatQuickMethodHeader::FromCodePointer(data->GetCode()));
- for (ArtMethod* method : data->GetMethods()) {
- VLOG(jit) << "JIT removed (JNI) " << method->PrettyMethod() << ": " << data->GetCode();
- }
- it = jni_stubs_map_.erase(it);
- }
- }
- for (auto it = method_code_map_.begin(); it != method_code_map_.end();) {
- const void* code_ptr = it->first;
- uintptr_t allocation = FromCodeToAllocation(code_ptr);
- if (IsInZygoteExecSpace(code_ptr) || GetLiveBitmap()->Test(allocation)) {
- ++it;
- } else {
- OatQuickMethodHeader* header = OatQuickMethodHeader::FromCodePointer(code_ptr);
- method_headers.insert(header);
- VLOG(jit) << "JIT removed " << it->second->PrettyMethod() << ": " << it->first;
- it = method_code_map_.erase(it);
- }
+void JitCodeCache::AddZombieCodeInternal(ArtMethod* method, const void* code_ptr) {
+ if (method->IsNative()) {
+ CHECK(jni_stubs_map_.find(JniStubKey(method)) != jni_stubs_map_.end());
+ zombie_jni_code_.insert(method);
+ } else {
+ CHECK(!ContainsElement(zombie_code_, code_ptr));
+ zombie_code_.insert(code_ptr);
+ }
+ // Arbitrary threshold of number of zombie code before doing a GC.
+ static constexpr size_t kNumberOfZombieCodeThreshold = kIsDebugBuild ? 1 : 1000;
+ size_t number_of_code_to_delete =
+ zombie_code_.size() + zombie_jni_code_.size() + osr_code_map_.size();
+ if (number_of_code_to_delete >= kNumberOfZombieCodeThreshold) {
+ JitThreadPool* pool = Runtime::Current()->GetJit()->GetThreadPool();
+ if (pool != nullptr && !gc_task_scheduled_) {
+ gc_task_scheduled_ = true;
+ pool->AddTask(Thread::Current(), new JitGcTask());
}
- FreeAllMethodHeaders(method_headers);
}
}
@@ -1222,62 +1234,60 @@ void JitCodeCache::MaybeUpdateInlineCache(ArtMethod* method,
info->AddInvokeInfo(dex_pc, cls.Ptr());
}
-void JitCodeCache::ResetHotnessCounter(ArtMethod* method, Thread* self) {
- ScopedDebugDisallowReadBarriers sddrb(self);
- MutexLock mu(self, *Locks::jit_lock_);
- auto it = profiling_infos_.find(method);
- DCHECK(it != profiling_infos_.end());
- it->second->ResetCounter();
-}
-
-
void JitCodeCache::DoCollection(Thread* self) {
ScopedTrace trace(__FUNCTION__);
+
{
ScopedDebugDisallowReadBarriers sddrb(self);
MutexLock mu(self, *Locks::jit_lock_);
- // Mark compiled code that are entrypoints of ArtMethods. Compiled code that is not
- // an entry point is either:
- // - an osr compiled code, that will be removed if not in a thread call stack.
- // - discarded compiled code, that will be removed if not in a thread call stack.
- for (const auto& entry : jni_stubs_map_) {
- const JniStubData& data = entry.second;
- const void* code_ptr = data.GetCode();
- if (IsInZygoteExecSpace(code_ptr)) {
- continue;
- }
- const OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
- for (ArtMethod* method : data.GetMethods()) {
- if (method_header->GetEntryPoint() == method->GetEntryPointFromQuickCompiledCode()) {
- GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(code_ptr));
- break;
- }
- }
- }
- for (const auto& it : method_code_map_) {
- ArtMethod* method = it.second;
- const void* code_ptr = it.first;
- if (IsInZygoteExecSpace(code_ptr)) {
- continue;
- }
- const OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromCodePointer(code_ptr);
- if (method_header->GetEntryPoint() == method->GetEntryPointFromQuickCompiledCode()) {
- GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(code_ptr));
- }
+ if (!garbage_collect_code_) {
+ return;
+ } else if (WaitForPotentialCollectionToComplete(self)) {
+ return;
}
-
+ collection_in_progress_ = true;
+ number_of_collections_++;
+ live_bitmap_.reset(CodeCacheBitmap::Create(
+ "code-cache-bitmap",
+ reinterpret_cast<uintptr_t>(private_region_.GetExecPages()->Begin()),
+ reinterpret_cast<uintptr_t>(
+ private_region_.GetExecPages()->Begin() + private_region_.GetCurrentCapacity() / 2)));
+ processed_zombie_code_.insert(zombie_code_.begin(), zombie_code_.end());
+ zombie_code_.clear();
+ processed_zombie_jni_code_.insert(zombie_jni_code_.begin(), zombie_jni_code_.end());
+ zombie_jni_code_.clear();
// Empty osr method map, as osr compiled code will be deleted (except the ones
// on thread stacks).
+ for (auto it = osr_code_map_.begin(); it != osr_code_map_.end(); ++it) {
+ processed_zombie_code_.insert(it->second);
+ }
osr_code_map_.clear();
}
+ TimingLogger logger("JIT code cache timing logger", true, VLOG_IS_ON(jit));
+ {
+ TimingLogger::ScopedTiming st("Code cache collection", &logger);
- // Run a checkpoint on all threads to mark the JIT compiled code they are running.
- MarkCompiledCodeOnThreadStacks(self);
+ {
+ ScopedObjectAccess soa(self);
+ // Run a checkpoint on all threads to mark the JIT compiled code they are running.
+ MarkCompiledCodeOnThreadStacks(self);
+
+ // Remove zombie code which hasn't been marked.
+ RemoveUnmarkedCode(self);
+ }
- // At this point, mutator threads are still running, and entrypoints of methods can
- // change. We do know they cannot change to a code cache entry that is not marked,
- // therefore we can safely remove those entries.
- RemoveUnmarkedCode(self);
+ MutexLock mu(self, *Locks::jit_lock_);
+ live_bitmap_.reset(nullptr);
+ NotifyCollectionDone(self);
+ }
+
+ Runtime::Current()->GetJit()->AddTimingLogger(logger);
+}
+
+void JitCodeCache::NotifyCollectionDone(Thread* self) {
+ collection_in_progress_ = false;
+ gc_task_scheduled_ = false;
+ lock_cond_.Broadcast(self);
}
OatQuickMethodHeader* JitCodeCache::LookupMethodHeader(uintptr_t pc, ArtMethod* method) {
@@ -1381,7 +1391,7 @@ ProfilingInfo* JitCodeCache::AddProfilingInfo(Thread* self,
}
if (info == nullptr) {
- GarbageCollectCache(self);
+ IncreaseCodeCacheCapacity(self);
MutexLock mu(self, *Locks::jit_lock_);
info = AddProfilingInfoInternal(self, method, inline_cache_entries, branch_cache_entries);
}
@@ -1574,8 +1584,6 @@ bool JitCodeCache::NotifyCompilationOf(ArtMethod* method,
VLOG(jit) << "Not compiling "
<< method->PrettyMethod()
<< " because it has the resolution stub";
- // Give it a new chance to be hot.
- ClearMethodCounter(method, /*was_warm=*/ false);
return false;
}
}
@@ -1609,11 +1617,6 @@ bool JitCodeCache::NotifyCompilationOf(ArtMethod* method,
// as well change them back as this stub shall not be collected anyway and this
// can avoid a few expensive GenericJNI calls.
data->UpdateEntryPoints(entrypoint);
- if (collection_in_progress_) {
- if (!IsInZygoteExecSpace(data->GetCode())) {
- GetLiveBitmap()->AtomicTestAndSet(FromCodeToAllocation(data->GetCode()));
- }
- }
}
return new_compilation;
} else {
@@ -1681,15 +1684,12 @@ void JitCodeCache::InvalidateAllCompiledCode() {
OatQuickMethodHeader::FromCodePointer(data.GetCode());
for (ArtMethod* method : data.GetMethods()) {
if (method->GetEntryPointFromQuickCompiledCode() == method_header->GetEntryPoint()) {
- ClearMethodCounter(method, /*was_warm=*/true);
instr->InitializeMethodsCode(method, /*aot_code=*/ nullptr);
}
}
}
for (const auto& entry : method_code_map_) {
ArtMethod* meth = entry.second;
- // We were compiled, so we must be warm.
- ClearMethodCounter(meth, /*was_warm=*/true);
if (UNLIKELY(meth->IsObsolete())) {
linker->SetEntryPointsForObsoleteMethod(meth);
} else {
@@ -1719,10 +1719,8 @@ void JitCodeCache::InvalidateCompiledCodeFor(ArtMethod* method,
// Clear the method counter if we are running jitted code since we might want to jit this again in
// the future.
if (method_entrypoint == header->GetEntryPoint()) {
- // The entrypoint is the one to invalidate, so we just update it to the interpreter entry point
- // and clear the counter to get the method Jitted again.
+ // The entrypoint is the one to invalidate, so we just update it to the interpreter entry point.
Runtime::Current()->GetInstrumentation()->InitializeMethodsCode(method, /*aot_code=*/ nullptr);
- ClearMethodCounter(method, /*was_warm=*/ true);
} else {
Thread* self = Thread::Current();
ScopedDebugDisallowReadBarriers sddrb(self);
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index 96fc7e2706..3dd57121ca 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -285,11 +285,9 @@ class JitCodeCache {
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::jit_lock_);
void FreeLocked(JitMemoryRegion* region, const uint8_t* code, const uint8_t* data)
- REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(Locks::jit_lock_);
- // Perform a collection on the code cache.
- EXPORT void GarbageCollectCache(Thread* self)
+ void IncreaseCodeCacheCapacity(Thread* self)
REQUIRES(!Locks::jit_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -416,16 +414,26 @@ class JitCodeCache {
}
ProfilingInfo* GetProfilingInfo(ArtMethod* method, Thread* self);
- void ResetHotnessCounter(ArtMethod* method, Thread* self);
void MaybeUpdateInlineCache(ArtMethod* method,
uint32_t dex_pc,
ObjPtr<mirror::Class> cls,
Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // NO_THREAD_SAFETY_ANALYSIS because we may be called with the JIT lock held
+ // or not. The implementation of this method handles the two cases.
+ void AddZombieCode(ArtMethod* method, const void* code_ptr) NO_THREAD_SAFETY_ANALYSIS;
+
+ EXPORT void DoCollection(Thread* self)
+ REQUIRES(!Locks::jit_lock_);
+
private:
JitCodeCache();
+ void AddZombieCodeInternal(ArtMethod* method, const void* code_ptr)
+ REQUIRES(Locks::jit_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
ProfilingInfo* AddProfilingInfoInternal(Thread* self,
ArtMethod* method,
const std::vector<uint32_t>& inline_cache_entries,
@@ -433,20 +441,16 @@ class JitCodeCache {
REQUIRES(Locks::jit_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- // If a collection is in progress, wait for it to finish. Must be called with the mutator lock.
- // The non-mutator lock version should be used if possible. This method will release then
- // re-acquire the mutator lock.
- void WaitForPotentialCollectionToCompleteRunnable(Thread* self)
- REQUIRES(Locks::jit_lock_, !Roles::uninterruptible_) REQUIRES_SHARED(Locks::mutator_lock_);
-
// If a collection is in progress, wait for it to finish. Return
// whether the thread actually waited.
bool WaitForPotentialCollectionToComplete(Thread* self)
- REQUIRES(Locks::jit_lock_) REQUIRES(!Locks::mutator_lock_);
+ REQUIRES(Locks::jit_lock_) REQUIRES_SHARED(!Locks::mutator_lock_);
+
+ // Notify all waiting threads that a collection is done.
+ void NotifyCollectionDone(Thread* self) REQUIRES(Locks::jit_lock_);
// Remove CHA dependents and underlying allocations for entries in `method_headers`.
void FreeAllMethodHeaders(const std::unordered_set<OatQuickMethodHeader*>& method_headers)
- REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(Locks::jit_lock_)
REQUIRES(!Locks::cha_lock_);
@@ -462,8 +466,7 @@ class JitCodeCache {
// Free code and data allocations for `code_ptr`.
void FreeCodeAndData(const void* code_ptr)
- REQUIRES(Locks::jit_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ REQUIRES(Locks::jit_lock_);
// Number of bytes allocated in the code cache.
size_t CodeCacheSize() REQUIRES(!Locks::jit_lock_);
@@ -477,16 +480,9 @@ class JitCodeCache {
// Number of bytes allocated in the data cache.
size_t DataCacheSizeLocked() REQUIRES(Locks::jit_lock_);
- // Notify all waiting threads that a collection is done.
- void NotifyCollectionDone(Thread* self) REQUIRES(Locks::jit_lock_);
-
// Return whether the code cache's capacity is at its maximum.
bool IsAtMaxCapacity() const REQUIRES(Locks::jit_lock_);
- void DoCollection(Thread* self)
- REQUIRES(!Locks::jit_lock_)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
void RemoveUnmarkedCode(Thread* self)
REQUIRES(!Locks::jit_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -563,18 +559,28 @@ class JitCodeCache {
// ProfilingInfo objects we have allocated.
SafeMap<ArtMethod*, ProfilingInfo*> profiling_infos_ GUARDED_BY(Locks::jit_lock_);
+ // Zombie code and JNI methods to consider for collection.
+ std::set<const void*> zombie_code_ GUARDED_BY(Locks::jit_lock_);
+ std::set<ArtMethod*> zombie_jni_code_ GUARDED_BY(Locks::jit_lock_);
+
+ std::set<const void*> processed_zombie_code_ GUARDED_BY(Locks::jit_lock_);
+ std::set<ArtMethod*> processed_zombie_jni_code_ GUARDED_BY(Locks::jit_lock_);
+
// Methods that the zygote has compiled and can be shared across processes
// forked from the zygote.
ZygoteMap zygote_map_;
// -------------- JIT GC related data structures ----------------------- //
- // Condition to wait on during collection.
+ // Condition to wait on during collection and for accessing weak references in inline caches.
ConditionVariable lock_cond_ GUARDED_BY(Locks::jit_lock_);
// Whether there is a code cache collection in progress.
bool collection_in_progress_ GUARDED_BY(Locks::jit_lock_);
+ // Whether a GC task is already scheduled.
+ bool gc_task_scheduled_ GUARDED_BY(Locks::jit_lock_);
+
// Bitmap for collecting code and data.
std::unique_ptr<CodeCacheBitmap> live_bitmap_;
diff --git a/runtime/jit/profile_saver.cc b/runtime/jit/profile_saver.cc
index abf01f5498..3506faaa7f 100644
--- a/runtime/jit/profile_saver.cc
+++ b/runtime/jit/profile_saver.cc
@@ -335,7 +335,6 @@ class ProfileSaver::GetClassesAndMethodsHelper {
REQUIRES_SHARED(Locks::mutator_lock_)
: startup_(startup),
profile_boot_class_path_(options.GetProfileBootClassPath()),
- hot_method_sample_threshold_(CalculateHotMethodSampleThreshold(startup, options)),
extra_flags_(GetExtraMethodHotnessFlags(options)),
annotation_(annotation),
arena_stack_(Runtime::Current()->GetArenaPool()),
@@ -358,10 +357,6 @@ class ProfileSaver::GetClassesAndMethodsHelper {
void CollectClasses(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
void UpdateProfile(const std::set<std::string>& locations, ProfileCompilationInfo* profile_info);
- uint32_t GetHotMethodSampleThreshold() const {
- return hot_method_sample_threshold_;
- }
-
size_t GetNumberOfHotMethods() const {
return number_of_hot_methods_;
}
@@ -409,19 +404,6 @@ class ProfileSaver::GetClassesAndMethodsHelper {
using DexFileRecordsMap = ScopedArenaHashMap<const DexFile*, DexFileRecords*>;
- static uint32_t CalculateHotMethodSampleThreshold(bool startup,
- const ProfileSaverOptions& options) {
- Runtime* runtime = Runtime::Current();
- if (startup) {
- const bool is_low_ram = runtime->GetHeap()->IsLowMemoryMode();
- return options.GetHotStartupMethodSamples(is_low_ram);
- } else if (runtime->GetJit() != nullptr) {
- return runtime->GetJit()->WarmMethodThreshold();
- } else {
- return std::numeric_limits<uint32_t>::max();
- }
- }
-
ALWAYS_INLINE static bool ShouldCollectClasses(bool startup) {
// We only record classes for the startup case. This may change in the future.
return startup;
@@ -434,7 +416,6 @@ class ProfileSaver::GetClassesAndMethodsHelper {
const bool startup_;
const bool profile_boot_class_path_;
- const uint32_t hot_method_sample_threshold_;
const uint32_t extra_flags_;
const ProfileCompilationInfo::ProfileSampleAnnotation annotation_;
ArenaStack arena_stack_;
@@ -609,7 +590,6 @@ void ProfileSaver::GetClassesAndMethodsHelper::UpdateProfile(const std::set<std:
ProfileCompilationInfo* profile_info) {
// Move members to local variables to allow the compiler to optimize this properly.
const bool startup = startup_;
- const uint32_t hot_method_sample_threshold = hot_method_sample_threshold_;
const uint32_t base_flags =
(startup ? Hotness::kFlagStartup : Hotness::kFlagPostStartup) | extra_flags_;
@@ -619,10 +599,9 @@ void ProfileSaver::GetClassesAndMethodsHelper::UpdateProfile(const std::set<std:
uint16_t initial_value = Runtime::Current()->GetJITOptions()->GetWarmupThreshold();
auto get_method_flags = [&](ArtMethod& method) {
- // Mark methods as hot if they have more than hot_method_sample_threshold
- // samples. This means they will get compiled by the compiler driver.
- if (method.PreviouslyWarm() ||
- method.CounterHasReached(hot_method_sample_threshold, initial_value)) {
+ // Mark methods as hot if they are marked as such (warm for the runtime
+ // means hot for the profile).
+ if (method.PreviouslyWarm()) {
++number_of_hot_methods;
return enum_cast<ProfileCompilationInfo::MethodHotness::Flag>(base_flags | Hotness::kFlagHot);
} else if (method.CounterHasChanged(initial_value)) {
@@ -750,7 +729,6 @@ void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) {
profiler_pthread = profiler_pthread_;
}
- uint32_t hot_method_sample_threshold = 0u;
size_t number_of_hot_methods = 0u;
size_t number_of_sampled_methods = 0u;
{
@@ -765,7 +743,6 @@ void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) {
ScopedObjectAccess soa(self);
GetClassesAndMethodsHelper helper(startup, options_, GetProfileSampleAnnotation());
- hot_method_sample_threshold = helper.GetHotMethodSampleThreshold();
helper.CollectClasses(self);
// Release the mutator lock. We shall need to re-acquire the lock for a moment to
@@ -800,8 +777,7 @@ void ProfileSaver::FetchAndCacheResolvedClassesAndMethods(bool startup) {
}
VLOG(profiler) << "Profile saver recorded " << number_of_hot_methods
<< " hot methods and " << number_of_sampled_methods
- << " sampled methods with threshold " << hot_method_sample_threshold
- << " in " << PrettyDuration(NanoTime() - start_time);
+ << " sampled methods in " << PrettyDuration(NanoTime() - start_time);
}
bool ProfileSaver::ProcessProfilingInfo(
diff --git a/runtime/jit/profile_saver_options.h b/runtime/jit/profile_saver_options.h
index f6d928ff6b..ed2f00f48f 100644
--- a/runtime/jit/profile_saver_options.h
+++ b/runtime/jit/profile_saver_options.h
@@ -26,14 +26,10 @@ struct ProfileSaverOptions {
// period is not configured.
static constexpr uint32_t kMinFirstSaveMsNotSet = 0;
static constexpr uint32_t kSaveResolvedClassesDelayMs = 5 * 1000; // 5 seconds
- // Minimum number of JIT samples during launch to mark a method as hot in the profile.
- static constexpr uint32_t kHotStartupMethodSamples = 1;
- static constexpr uint32_t kHotStartupMethodSamplesLowRam = 256;
static constexpr uint32_t kMinMethodsToSave = 10;
static constexpr uint32_t kMinClassesToSave = 10;
static constexpr uint32_t kMinNotificationBeforeWake = 10;
static constexpr uint32_t kMaxNotificationBeforeWake = 50;
- static constexpr uint32_t kHotStartupMethodSamplesNotSet = std::numeric_limits<uint32_t>::max();
static constexpr uint16_t kInlineCacheThreshold = 4000;
ProfileSaverOptions() :
@@ -41,7 +37,6 @@ struct ProfileSaverOptions {
min_save_period_ms_(kMinSavePeriodMs),
min_first_save_ms_(kMinFirstSaveMsNotSet),
save_resolved_classes_delay_ms_(kSaveResolvedClassesDelayMs),
- hot_startup_method_samples_(kHotStartupMethodSamplesNotSet),
min_methods_to_save_(kMinMethodsToSave),
min_classes_to_save_(kMinClassesToSave),
min_notification_before_wake_(kMinNotificationBeforeWake),
@@ -57,7 +52,6 @@ struct ProfileSaverOptions {
uint32_t min_save_period_ms,
uint32_t min_first_save_ms,
uint32_t save_resolved_classes_delay_ms,
- uint32_t hot_startup_method_samples,
uint32_t min_methods_to_save,
uint32_t min_classes_to_save,
uint32_t min_notification_before_wake,
@@ -71,7 +65,6 @@ struct ProfileSaverOptions {
min_save_period_ms_(min_save_period_ms),
min_first_save_ms_(min_first_save_ms),
save_resolved_classes_delay_ms_(save_resolved_classes_delay_ms),
- hot_startup_method_samples_(hot_startup_method_samples),
min_methods_to_save_(min_methods_to_save),
min_classes_to_save_(min_classes_to_save),
min_notification_before_wake_(min_notification_before_wake),
@@ -98,13 +91,6 @@ struct ProfileSaverOptions {
uint32_t GetSaveResolvedClassesDelayMs() const {
return save_resolved_classes_delay_ms_;
}
- uint32_t GetHotStartupMethodSamples(bool is_low_ram) const {
- uint32_t ret = hot_startup_method_samples_;
- if (ret == kHotStartupMethodSamplesNotSet) {
- ret = is_low_ram ? kHotStartupMethodSamplesLowRam : kHotStartupMethodSamples;
- }
- return ret;
- }
uint32_t GetMinMethodsToSave() const {
return min_methods_to_save_;
}
@@ -141,7 +127,6 @@ struct ProfileSaverOptions {
<< ", min_save_period_ms_" << pso.min_save_period_ms_
<< ", min_first_save_ms_" << pso.min_first_save_ms_
<< ", save_resolved_classes_delay_ms_" << pso.save_resolved_classes_delay_ms_
- << ", hot_startup_method_samples_" << pso.hot_startup_method_samples_
<< ", min_methods_to_save_" << pso.min_methods_to_save_
<< ", min_classes_to_save_" << pso.min_classes_to_save_
<< ", min_notification_before_wake_" << pso.min_notification_before_wake_
@@ -157,9 +142,6 @@ struct ProfileSaverOptions {
uint32_t min_save_period_ms_;
uint32_t min_first_save_ms_;
uint32_t save_resolved_classes_delay_ms_;
- // Do not access hot_startup_method_samples_ directly for reading since it may be set to the
- // placeholder default.
- uint32_t hot_startup_method_samples_;
uint32_t min_methods_to_save_;
uint32_t min_classes_to_save_;
uint32_t min_notification_before_wake_;
diff --git a/runtime/jit/profiling_info.h b/runtime/jit/profiling_info.h
index ae59d0ade1..a32b712b0c 100644
--- a/runtime/jit/profiling_info.h
+++ b/runtime/jit/profiling_info.h
@@ -171,14 +171,6 @@ class ProfilingInfo {
return MemberOffset(OFFSETOF_MEMBER(ProfilingInfo, baseline_hotness_count_));
}
- void ResetCounter() {
- baseline_hotness_count_ = GetOptimizeThreshold();
- }
-
- bool CounterHasChanged() const {
- return baseline_hotness_count_ != GetOptimizeThreshold();
- }
-
uint16_t GetBaselineHotnessCount() const {
return baseline_hotness_count_;
}
diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h
index 579b1536a3..830a7e5fb6 100644
--- a/runtime/mirror/array-inl.h
+++ b/runtime/mirror/array-inl.h
@@ -24,6 +24,7 @@
#include "base/bit_utils.h"
#include "base/casts.h"
#include "class.h"
+#include "class_linker.h"
#include "obj_ptr-inl.h"
#include "runtime.h"
#include "thread-current-inl.h"
@@ -99,7 +100,7 @@ inline void PrimitiveArray<T>::SetWithoutChecks(int32_t i, T value) {
DCHECK_EQ(kTransactionActive, Runtime::Current()->IsActiveTransaction());
}
if (kTransactionActive) {
- Runtime::Current()->RecordWriteArray(this, i, GetWithoutChecks(i));
+ Runtime::Current()->GetClassLinker()->RecordWriteArray(this, i, GetWithoutChecks(i));
}
DCHECK(CheckIsValidIndex<kVerifyFlags>(i)) << i << " " << GetLength<kVerifyFlags>();
GetData()[i] = value;
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index 74776c14d4..b2c8e52351 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -1169,7 +1169,16 @@ inline bool Class::CanAccessMember(ObjPtr<Class> access_to, uint32_t member_flag
}
// Check for protected access from a sub-class, which may or may not be in the same package.
if (member_flags & kAccProtected) {
- if (!this->IsInterface() && this->IsSubClass(access_to)) {
+ // This implementation is not compliant. We should actually check whether
+ // the caller is a subclass of the static type of the receiver, instead of the declaring
+ // class of the method we are trying to access.
+ //
+ // For example, a class outside of java.lang should not ne able to access `Object.clone`,
+ // but this implementation allows it.
+ //
+ // To not break existing code, we decided not to fix this and accept the
+ // leniency.
+ if (access_to->IsAssignableFrom(this)) {
return true;
}
}
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index d423a8edfb..8f9d624399 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -141,7 +141,7 @@ inline void DexCache::SetResolvedString(dex::StringIndex string_idx, ObjPtr<Stri
Runtime* const runtime = Runtime::Current();
if (UNLIKELY(runtime->IsActiveTransaction())) {
DCHECK(runtime->IsAotCompiler());
- runtime->RecordResolveString(this, string_idx);
+ runtime->GetClassLinker()->RecordResolveString(this, string_idx);
}
// TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
WriteBarrier::ForEveryFieldWrite(this);
@@ -188,7 +188,7 @@ inline void DexCache::SetResolvedMethodType(dex::ProtoIndex proto_idx, MethodTyp
Runtime* const runtime = Runtime::Current();
if (UNLIKELY(runtime->IsActiveTransaction())) {
DCHECK(runtime->IsAotCompiler());
- runtime->RecordResolveMethodType(this, proto_idx);
+ runtime->GetClassLinker()->RecordResolveMethodType(this, proto_idx);
}
// TODO: Fine-grained marking, so that we don't need to go through all arrays in full.
WriteBarrier::ForEveryFieldWrite(this);
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index 09f1c39058..14b9ca3af0 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -393,11 +393,8 @@ template<bool kTransactionActive,
inline void Object::SetFieldBoolean(MemberOffset field_offset, uint8_t new_value) {
VerifyTransaction<kTransactionActive, kCheckTransaction>();
if (kTransactionActive) {
- Runtime::Current()->RecordWriteFieldBoolean(
- this,
- field_offset,
- GetFieldBoolean<kVerifyFlags, kIsVolatile>(field_offset),
- kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldBoolean(
+ this, field_offset, GetFieldBoolean<kVerifyFlags, kIsVolatile>(field_offset), kIsVolatile);
}
Verify<kVerifyFlags>();
SetFieldPrimitive<uint8_t, kIsVolatile>(field_offset, new_value);
@@ -410,10 +407,8 @@ template<bool kTransactionActive,
inline void Object::SetFieldByte(MemberOffset field_offset, int8_t new_value) {
VerifyTransaction<kTransactionActive, kCheckTransaction>();
if (kTransactionActive) {
- Runtime::Current()->RecordWriteFieldByte(this,
- field_offset,
- GetFieldByte<kVerifyFlags, kIsVolatile>(field_offset),
- kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldByte(
+ this, field_offset, GetFieldByte<kVerifyFlags, kIsVolatile>(field_offset), kIsVolatile);
}
Verify<kVerifyFlags>();
SetFieldPrimitive<int8_t, kIsVolatile>(field_offset, new_value);
@@ -460,10 +455,8 @@ template<bool kTransactionActive,
inline void Object::SetFieldChar(MemberOffset field_offset, uint16_t new_value) {
VerifyTransaction<kTransactionActive, kCheckTransaction>();
if (kTransactionActive) {
- Runtime::Current()->RecordWriteFieldChar(this,
- field_offset,
- GetFieldChar<kVerifyFlags, kIsVolatile>(field_offset),
- kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldChar(
+ this, field_offset, GetFieldChar<kVerifyFlags, kIsVolatile>(field_offset), kIsVolatile);
}
Verify<kVerifyFlags>();
SetFieldPrimitive<uint16_t, kIsVolatile>(field_offset, new_value);
@@ -476,10 +469,8 @@ template<bool kTransactionActive,
inline void Object::SetFieldShort(MemberOffset field_offset, int16_t new_value) {
VerifyTransaction<kTransactionActive, kCheckTransaction>();
if (kTransactionActive) {
- Runtime::Current()->RecordWriteFieldChar(this,
- field_offset,
- GetFieldShort<kVerifyFlags, kIsVolatile>(field_offset),
- kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldChar(
+ this, field_offset, GetFieldShort<kVerifyFlags, kIsVolatile>(field_offset), kIsVolatile);
}
Verify<kVerifyFlags>();
SetFieldPrimitive<int16_t, kIsVolatile>(field_offset, new_value);
@@ -504,10 +495,8 @@ template<bool kTransactionActive,
inline void Object::SetField32(MemberOffset field_offset, int32_t new_value) {
VerifyTransaction<kTransactionActive, kCheckTransaction>();
if (kTransactionActive) {
- Runtime::Current()->RecordWriteField32(this,
- field_offset,
- GetField32<kVerifyFlags, kIsVolatile>(field_offset),
- kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteField32(
+ this, field_offset, GetField32<kVerifyFlags, kIsVolatile>(field_offset), kIsVolatile);
}
Verify<kVerifyFlags>();
SetFieldPrimitive<int32_t, kIsVolatile>(field_offset, new_value);
@@ -534,10 +523,8 @@ template<bool kTransactionActive,
inline void Object::SetField64(MemberOffset field_offset, int64_t new_value) {
VerifyTransaction<kTransactionActive, kCheckTransaction>();
if (kTransactionActive) {
- Runtime::Current()->RecordWriteField64(this,
- field_offset,
- GetField64<kVerifyFlags, kIsVolatile>(field_offset),
- kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteField64(
+ this, field_offset, GetField64<kVerifyFlags, kIsVolatile>(field_offset), kIsVolatile);
}
Verify<kVerifyFlags>();
SetFieldPrimitive<int64_t, kIsVolatile>(field_offset, new_value);
@@ -575,7 +562,8 @@ inline bool Object::CasFieldWeakSequentiallyConsistent64(MemberOffset field_offs
Atomic<int64_t>* atomic_addr = reinterpret_cast<Atomic<int64_t>*>(raw_addr);
bool success = atomic_addr->CompareAndSetWeakSequentiallyConsistent(old_value, new_value);
if (kTransactionActive && success) {
- Runtime::Current()->RecordWriteField64(this, field_offset, old_value, /*is_volatile=*/ true);
+ Runtime::Current()->GetClassLinker()->RecordWriteField64(
+ this, field_offset, old_value, /*is_volatile=*/ true);
}
return success;
}
@@ -590,7 +578,8 @@ inline bool Object::CasFieldStrongSequentiallyConsistent64(MemberOffset field_of
Atomic<int64_t>* atomic_addr = reinterpret_cast<Atomic<int64_t>*>(raw_addr);
bool success = atomic_addr->CompareAndSetStrongSequentiallyConsistent(old_value, new_value);
if (kTransactionActive && success) {
- Runtime::Current()->RecordWriteField64(this, field_offset, old_value, /*is_volatile=*/ true);
+ Runtime::Current()->GetClassLinker()->RecordWriteField64(
+ this, field_offset, old_value, /*is_volatile=*/ true);
}
return success;
}
@@ -630,7 +619,8 @@ inline void Object::SetFieldObjectWithoutWriteBarrier(MemberOffset field_offset,
if (kTransactionActive) {
ObjPtr<Object> old_value =
GetFieldObject<Object, kVerifyFlags, kWithReadBarrier, kIsVolatile>(field_offset);
- Runtime::Current()->RecordWriteFieldReference(this, field_offset, old_value, kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldReference(
+ this, field_offset, old_value, kIsVolatile);
}
Verify<kVerifyFlags>();
VerifyWrite<kVerifyFlags>(new_value);
@@ -689,7 +679,7 @@ inline bool Object::CasFieldObjectWithoutWriteBarrier(MemberOffset field_offset,
Atomic<uint32_t>* atomic_addr = reinterpret_cast<Atomic<uint32_t>*>(raw_addr);
bool success = atomic_addr->CompareAndSet(old_ref, new_ref, mode, memory_order);
if (kTransactionActive && success) {
- Runtime::Current()->RecordWriteFieldReference(
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldReference(
this, field_offset, old_value, /*is_volatile=*/ true);
}
return success;
@@ -731,7 +721,8 @@ inline ObjPtr<Object> Object::CompareAndExchangeFieldObject(MemberOffset field_o
}
if (success) {
if (kTransactionActive) {
- Runtime::Current()->RecordWriteFieldReference(this, field_offset, witness_value, true);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldReference(
+ this, field_offset, witness_value, /*is_volatile=*/ true);
}
WriteBarrier::ForFieldWrite(this, field_offset, new_value);
}
@@ -755,7 +746,8 @@ inline ObjPtr<Object> Object::ExchangeFieldObject(MemberOffset field_offset,
ReadBarrier::AssertToSpaceInvariant(old_value.Ptr());
}
if (kTransactionActive) {
- Runtime::Current()->RecordWriteFieldReference(this, field_offset, old_value, true);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldReference(
+ this, field_offset, old_value, /*is_volatile=*/ true);
}
WriteBarrier::ForFieldWrite(this, field_offset, new_value);
VerifyRead<kVerifyFlags>(old_value);
@@ -777,7 +769,8 @@ inline void Object::UpdateFieldBooleanViaAccessor(MemberOffset field_offset,
if (kTransactionActive) {
static const bool kIsVolatile = true;
uint8_t old_value = GetFieldBoolean<kVerifyFlags, kIsVolatile>(field_offset);
- Runtime::Current()->RecordWriteFieldBoolean(this, field_offset, old_value, kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldBoolean(
+ this, field_offset, old_value, kIsVolatile);
}
Verify<kVerifyFlags>();
uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
@@ -792,7 +785,8 @@ inline void Object::UpdateFieldByteViaAccessor(MemberOffset field_offset,
if (kTransactionActive) {
static const bool kIsVolatile = true;
int8_t old_value = GetFieldByte<kVerifyFlags, kIsVolatile>(field_offset);
- Runtime::Current()->RecordWriteFieldByte(this, field_offset, old_value, kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldByte(
+ this, field_offset, old_value, kIsVolatile);
}
Verify<kVerifyFlags>();
uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
@@ -807,7 +801,8 @@ inline void Object::UpdateFieldCharViaAccessor(MemberOffset field_offset,
if (kTransactionActive) {
static const bool kIsVolatile = true;
uint16_t old_value = GetFieldChar<kVerifyFlags, kIsVolatile>(field_offset);
- Runtime::Current()->RecordWriteFieldChar(this, field_offset, old_value, kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldChar(
+ this, field_offset, old_value, kIsVolatile);
}
Verify<kVerifyFlags>();
uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
@@ -822,7 +817,8 @@ inline void Object::UpdateFieldShortViaAccessor(MemberOffset field_offset,
if (kTransactionActive) {
static const bool kIsVolatile = true;
int16_t old_value = GetFieldShort<kVerifyFlags, kIsVolatile>(field_offset);
- Runtime::Current()->RecordWriteFieldShort(this, field_offset, old_value, kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteFieldShort(
+ this, field_offset, old_value, kIsVolatile);
}
Verify<kVerifyFlags>();
uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
@@ -837,7 +833,8 @@ inline void Object::UpdateField32ViaAccessor(MemberOffset field_offset,
if (kTransactionActive) {
static const bool kIsVolatile = true;
int32_t old_value = GetField32<kVerifyFlags, kIsVolatile>(field_offset);
- Runtime::Current()->RecordWriteField32(this, field_offset, old_value, kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteField32(
+ this, field_offset, old_value, kIsVolatile);
}
Verify<kVerifyFlags>();
uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
@@ -852,7 +849,8 @@ inline void Object::UpdateField64ViaAccessor(MemberOffset field_offset,
if (kTransactionActive) {
static const bool kIsVolatile = true;
int64_t old_value = GetField64<kVerifyFlags, kIsVolatile>(field_offset);
- Runtime::Current()->RecordWriteField64(this, field_offset, old_value, kIsVolatile);
+ Runtime::Current()->GetClassLinker()->RecordWriteField64(
+ this, field_offset, old_value, kIsVolatile);
}
Verify<kVerifyFlags>();
uint8_t* raw_addr = reinterpret_cast<uint8_t*>(this) + field_offset.Int32Value();
diff --git a/runtime/mirror/object-readbarrier-inl.h b/runtime/mirror/object-readbarrier-inl.h
index 6c201f2277..39bc43b63a 100644
--- a/runtime/mirror/object-readbarrier-inl.h
+++ b/runtime/mirror/object-readbarrier-inl.h
@@ -20,6 +20,7 @@
#include "object.h"
#include "base/atomic.h"
+#include "class_linker.h"
#include "heap_poisoning.h"
#include "lock_word-inl.h"
#include "object_reference-inl.h"
@@ -54,7 +55,8 @@ inline bool Object::CasField32(MemberOffset field_offset,
bool success = atomic_addr->CompareAndSet(old_value, new_value, mode, memory_order);
if (kTransactionActive && success) {
- Runtime::Current()->RecordWriteField32(this, field_offset, old_value, /*is_volatile=*/ true);
+ Runtime::Current()->GetClassLinker()->RecordWriteField32(
+ this, field_offset, old_value, /*is_volatile=*/ true);
}
return success;
}
diff --git a/runtime/native/dalvik_system_VMDebug.cc b/runtime/native/dalvik_system_VMDebug.cc
index 137b04fede..18120fe826 100644
--- a/runtime/native/dalvik_system_VMDebug.cc
+++ b/runtime/native/dalvik_system_VMDebug.cc
@@ -55,13 +55,16 @@ namespace art HIDDEN {
static jobjectArray VMDebug_getVmFeatureList(JNIEnv* env, jclass) {
ScopedObjectAccess soa(Thread::ForEnv(env));
- return soa.AddLocalReference<jobjectArray>(CreateStringArray(soa.Self(), {
- "method-trace-profiling",
- "method-trace-profiling-streaming",
- "method-sample-profiling",
- "hprof-heap-dump",
- "hprof-heap-dump-streaming",
- }));
+ return soa.AddLocalReference<jobjectArray>(
+ CreateStringArray(soa.Self(),
+ {
+ "method-trace-profiling",
+ "method-trace-profiling-streaming",
+ "method-sample-profiling",
+ "hprof-heap-dump",
+ "hprof-heap-dump-streaming",
+ "app_info",
+ }));
}
static void VMDebug_startAllocCounting(JNIEnv*, jclass) {
@@ -514,6 +517,49 @@ static void VMDebug_setAllocTrackerStackDepth(JNIEnv* env, jclass, jint stack_de
}
}
+static void VMDebug_setCurrentProcessName(JNIEnv* env, jclass, jstring process_name) {
+ ScopedFastNativeObjectAccess soa(env);
+
+ // Android application ID naming convention states:
+ // "The name can contain uppercase or lowercase letters, numbers, and underscores ('_')"
+ // This is fine to convert to std::string
+ const char* c_process_name = env->GetStringUTFChars(process_name, NULL);
+ Runtime::Current()->GetRuntimeCallbacks()->SetCurrentProcessName(std::string(c_process_name));
+ env->ReleaseStringUTFChars(process_name, c_process_name);
+}
+
+static void VMDebug_addApplication(JNIEnv* env, jclass, jstring package_name) {
+ ScopedFastNativeObjectAccess soa(env);
+
+ // Android application ID naming convention states:
+ // "The name can contain uppercase or lowercase letters, numbers, and underscores ('_')"
+ // This is fine to convert to std::string
+ const char* c_package_name = env->GetStringUTFChars(package_name, NULL);
+ Runtime::Current()->GetRuntimeCallbacks()->AddApplication(std::string(c_package_name));
+ env->ReleaseStringUTFChars(package_name, c_package_name);
+}
+
+static void VMDebug_removeApplication(JNIEnv* env, jclass, jstring package_name) {
+ ScopedFastNativeObjectAccess soa(env);
+
+ // Android application ID naming convention states:
+ // "The name can contain uppercase or lowercase letters, numbers, and underscores ('_')"
+ // This is fine to convert to std::string
+ const char* c_package_name = env->GetStringUTFChars(package_name, NULL);
+ Runtime::Current()->GetRuntimeCallbacks()->RemoveApplication(std::string(c_package_name));
+ env->ReleaseStringUTFChars(package_name, c_package_name);
+}
+
+static void VMDebug_setWaitingForDebugger(JNIEnv* env, jclass, jboolean waiting) {
+ ScopedFastNativeObjectAccess soa(env);
+ Runtime::Current()->GetRuntimeCallbacks()->SetWaitingForDebugger(waiting);
+}
+
+static void VMDebug_setUserId(JNIEnv* env, jclass, jint user_id) {
+ ScopedFastNativeObjectAccess soa(env);
+ Runtime::Current()->GetRuntimeCallbacks()->SetUserId(user_id);
+}
+
static JNINativeMethod gMethods[] = {
NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
NATIVE_METHOD(VMDebug, countInstancesOfClasses, "([Ljava/lang/Class;Z)[J"),
@@ -542,6 +588,11 @@ static JNINativeMethod gMethods[] = {
NATIVE_METHOD(VMDebug, nativeAttachAgent, "(Ljava/lang/String;Ljava/lang/ClassLoader;)V"),
NATIVE_METHOD(VMDebug, allowHiddenApiReflectionFrom, "(Ljava/lang/Class;)V"),
NATIVE_METHOD(VMDebug, setAllocTrackerStackDepth, "(I)V"),
+ NATIVE_METHOD(VMDebug, setCurrentProcessName, "(Ljava/lang/String;)V"),
+ NATIVE_METHOD(VMDebug, setWaitingForDebugger, "(Z)V"),
+ NATIVE_METHOD(VMDebug, addApplication, "(Ljava/lang/String;)V"),
+ NATIVE_METHOD(VMDebug, removeApplication, "(Ljava/lang/String;)V"),
+ NATIVE_METHOD(VMDebug, setUserId, "(I)V"),
};
void register_dalvik_system_VMDebug(JNIEnv* env) {
diff --git a/runtime/native_gc_triggering.md b/runtime/native_gc_triggering.md
new file mode 100644
index 0000000000..3e8fb08f01
--- /dev/null
+++ b/runtime/native_gc_triggering.md
@@ -0,0 +1,87 @@
+Triggering the GC to reclaim non-Java memory
+--------------------------------------------
+
+Android applications and libraries commonly allocate "native" (i.e. C++) objects that are
+effectively owned by a Java object, and reclaimed when the garbage collector determines that the
+owning Java object is no longer reachable. Various mechanisms are used to accomplish this. These
+include traditional Java finalizers, more modern Java `Cleaner`s, Android's `SystemCleaner` and,
+for platform code, `NativeAllocationRegistry`. Internally, these all rely on the garbage
+collector's processing of `java.lang.ref.Reference`s.
+
+Historically, we have encountered issues when large volumes of such "native" objects are owned by
+Java objects of significantly smaller size. The Java garbage collector normally decides when to
+collect based on occupancy of the Java heap. It would not collect if there are few bytes of newly
+allocated Java objects in the Java heap, even if they "own" large amounts of C++ memory, which
+cannot be reclaimed until the Java objects are reclaimed.
+
+This led to the development of the `VMRuntime.registerNativeAllocation` API, and eventually the
+`NativeAllocationRegistry` API. Both of these allow the programmer to inform the ART GC that a
+certain amount of C++ memory had been allocated, and could only be reclaimed as the result of a
+Java GC.
+
+This had the advantage that the GC would theoretically know exactly how much native memory was
+owned by Java objects. However, its major problem was that registering the exact size of such
+native objects frequently turned out to be impractical. Often a Java object does not just own a
+single native object, but instead owns a complex native data structure composed of many objects in
+the C++ heap. Their sizes commonly depended on prior computations by C++ code. Some of these might
+be shared between Java objects, and could only be reclaimed when all of those Java objects became
+unreachable. Often at least some of the native objects were allocated by third-party libraries
+that did not make the sizes of its internal objects available to clients.
+
+In extreme cases, underestimation of native object sizes could cause native memory use to be so
+excessive that the device would become unstable. At one time, a particularly nasty arithmetic
+expression would cause the Google Calculator app to allocate sufficiently large native arrays of
+digits backing Java `BigInteger`s to force a restart of system processes. (This has since been
+addressed in other ways as well.)
+
+Thus we switched to a scheme in which sizes of native objects are commonly no longer directly
+provided by clients. The GC instead occasionally calls `mallinfo()` to determine how much native
+memory has been allocated, and assumes that any of this may need the collector's help to reclaim.
+C++ memory allocated by means other than `malloc`/`new` and owned by Java objects should still be
+explicitly registered. This loses information about Java ownership, but results in much more
+accurate information about the total size of native objects, something that was previously very
+difficult to approximate, even to within an order of magnitude.
+
+The triggering heuristic
+------------------------
+
+We normally check for the need to trigger a GC due to native memory pressure only as a result of
+calls to the `NativeAllocationRegistry` or `VMRuntime.registerNativeAllocation` APIs. Thus an
+application not using these APIs, e.g. because it is running almost entirely native code, may
+never do so. This is generally a feature, rather than a bug.
+
+The actual computation for triggering a native-allocation-GC is performed by
+`Heap::NativeMemoryOverTarget()`. This computes and compares two quantities:
+
+1. An adjusted heap size for GC triggering. This consists of the Java heap size at which we would
+ normally trigger a GC plus an allowance for native heap size. This allowance currently consists
+ of one half (background processes) or three halves (foreground processes) of
+ `NativeAllocationGcWatermark()`. The latter is HeapMaxFree (typically 32MB) plus 1/8 of the
+ currently targeted heap size. For a foreground process, this allowance would typically be in
+ the 50-100 MB range for something other than a low-end device.
+
+2. An adjusted count of current bytes allocated. This is basically the number of bytes currently
+ allocated in the Java heap, plus half the net number of native bytes allocated since the last
+ GC. The latter is computed as the change since the last GC in the total-bytes-allocated
+ obtained from `mallinfo()` plus `native_bytes_registered_`. The `native_bytes_registered_`
+ field tracks the bytes explicitly registered, and not yet unregistered via
+ `registerNativeAllocation` APIs. It excludes bytes registered as malloc-allocated via
+ `NativeAllocationRegistry`. (The computation also considers the total amount of native memory
+ currently allocated by this metric, as opposed to the change since the last GC. But that is
+ currently de-weighted to the point of insignificance.)
+
+A background GC is triggered if the second quantity exceeds the first. A stop-the-world-GC is
+triggered if the second quantity is at least 4 times larger than the first, and the native heap
+currently occupies a large fraction of device memory, suggesting that the GC is falling behind and
+endangering device usability.
+
+The fact that we consider both Java and native memory use at once means that we are more likely to
+trigger a native GC when we are closer to the normal Java GC threshold.
+
+The actual use of `mallinfo()` to compute native heap occupancy reflects experiments with
+different `libc` implementations. These have different performance characteristics, and sometimes
+disagree on the interpretation of `struct mallinfo` fields. We believe our current implementation
+is solid with scudo and jemalloc on Android, and minimally usable for testing elsewhere.
+
+(Some of this assumes typical current (May 2024) configuration constants, and may need to be
+updated.)
diff --git a/runtime/oat/aot_class_linker.cc b/runtime/oat/aot_class_linker.cc
index e1ca63d44e..0a85fa094b 100644
--- a/runtime/oat/aot_class_linker.cc
+++ b/runtime/oat/aot_class_linker.cc
@@ -22,23 +22,24 @@
#include "dex/class_reference.h"
#include "gc/heap.h"
#include "handle_scope-inl.h"
-#include "interpreter/active_transaction_checker.h"
#include "mirror/class-inl.h"
#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "transaction.h"
#include "verifier/verifier_enums.h"
namespace art HIDDEN {
AotClassLinker::AotClassLinker(InternTable* intern_table)
- : ClassLinker(intern_table, /*fast_class_not_found_exceptions=*/ false) {}
+ : ClassLinker(intern_table, /*fast_class_not_found_exceptions=*/ false),
+ preinitialization_transactions_() {}
AotClassLinker::~AotClassLinker() {}
bool AotClassLinker::CanAllocClass() {
// AllocClass doesn't work under transaction, so we abort.
- if (Runtime::Current()->IsActiveTransaction()) {
- Runtime::Current()->AbortTransactionAndThrowAbortError(
- Thread::Current(), "Can't resolve type within transaction.");
+ if (IsActiveTransaction()) {
+ AbortTransactionF(Thread::Current(), "Can't resolve type within transaction.");
return false;
}
return ClassLinker::CanAllocClass();
@@ -49,8 +50,7 @@ bool AotClassLinker::InitializeClass(Thread* self,
Handle<mirror::Class> klass,
bool can_init_statics,
bool can_init_parents) {
- Runtime* const runtime = Runtime::Current();
- bool strict_mode = runtime->IsActiveStrictTransactionMode();
+ bool strict_mode = IsActiveStrictTransactionMode();
DCHECK(klass != nullptr);
if (klass->IsInitialized() || klass->IsInitializing()) {
@@ -61,10 +61,12 @@ bool AotClassLinker::InitializeClass(Thread* self,
// in a dex file belonging to the boot image we're compiling against.
// However, we must allow the initialization of TransactionAbortError,
// VerifyError, etc. outside of a transaction.
- if (!strict_mode && runtime->GetHeap()->ObjectIsInBootImageSpace(klass->GetDexCache())) {
- if (runtime->IsActiveTransaction()) {
- runtime->AbortTransactionAndThrowAbortError(self, "Can't initialize " + klass->PrettyTypeOf()
- + " because it is defined in a boot image dex file.");
+ if (!strict_mode &&
+ Runtime::Current()->GetHeap()->ObjectIsInBootImageSpace(klass->GetDexCache())) {
+ if (IsActiveTransaction()) {
+ AbortTransactionF(self,
+ "Can't initialize %s because it is defined in a boot image dex file.",
+ klass->PrettyTypeOf().c_str());
return false;
}
CHECK(klass->IsThrowableClass()) << klass->PrettyDescriptor();
@@ -72,8 +74,9 @@ bool AotClassLinker::InitializeClass(Thread* self,
// When in strict_mode, don't initialize a class if it belongs to boot but not initialized.
if (strict_mode && klass->IsBootStrapClassLoaded()) {
- runtime->AbortTransactionAndThrowAbortError(self, "Can't resolve "
- + klass->PrettyTypeOf() + " because it is an uninitialized boot class.");
+ AbortTransactionF(self,
+ "Can't resolve %s because it is an uninitialized boot class.",
+ klass->PrettyTypeOf().c_str());
return false;
}
@@ -81,21 +84,22 @@ bool AotClassLinker::InitializeClass(Thread* self,
// the transaction and rolled back after klass's change is commited.
if (strict_mode && !klass->IsInterface() && klass->HasSuperClass()) {
if (klass->GetSuperClass()->GetStatus() == ClassStatus::kInitializing) {
- runtime->AbortTransactionAndThrowAbortError(self, "Can't resolve "
- + klass->PrettyTypeOf() + " because it's superclass is not initialized.");
+ AbortTransactionF(self,
+ "Can't resolve %s because it's superclass is not initialized.",
+ klass->PrettyTypeOf().c_str());
return false;
}
}
if (strict_mode) {
- runtime->EnterTransactionMode(/*strict=*/ true, klass.Get());
+ EnterTransactionMode(/*strict=*/ true, klass.Get());
}
bool success = ClassLinker::InitializeClass(self, klass, can_init_statics, can_init_parents);
if (strict_mode) {
if (success) {
// Exit Transaction if success.
- runtime->ExitTransactionMode();
+ ExitTransactionMode();
} else {
// If not successfully initialized, don't rollback immediately, leave the cleanup to compiler
// driver which needs abort message and exception.
@@ -251,21 +255,260 @@ void AotClassLinker::SetEnablePublicSdkChecks(bool enabled) {
}
}
-bool AotClassLinker::TransactionWriteConstraint(Thread* self, ObjPtr<mirror::Object> obj) const {
- DCHECK(Runtime::Current()->IsActiveTransaction());
- return interpreter::ActiveTransactionChecker::WriteConstraint(self, obj);
+// Transaction support.
+
+bool AotClassLinker::IsActiveTransaction() const {
+ bool result = Runtime::Current()->IsActiveTransaction();
+ DCHECK_EQ(result, !preinitialization_transactions_.empty() && !GetTransaction()->IsRollingBack());
+ return result;
+}
+
+void AotClassLinker::EnterTransactionMode(bool strict, mirror::Class* root) {
+ Runtime* runtime = Runtime::Current();
+ ArenaPool* arena_pool = nullptr;
+ ArenaStack* arena_stack = nullptr;
+ if (preinitialization_transactions_.empty()) { // Top-level transaction?
+ // Make initialized classes visibly initialized now. If that happened during the transaction
+ // and then the transaction was aborted, we would roll back the status update but not the
+ // ClassLinker's bookkeeping structures, so these classes would never be visibly initialized.
+ {
+ Thread* self = Thread::Current();
+ StackHandleScope<1> hs(self);
+ HandleWrapper<mirror::Class> h(hs.NewHandleWrapper(&root));
+ ScopedThreadSuspension sts(self, ThreadState::kNative);
+ MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true);
+ }
+ // Pass the runtime `ArenaPool` to the transaction.
+ arena_pool = runtime->GetArenaPool();
+ } else {
+ // Pass the `ArenaStack` from previous transaction to the new one.
+ arena_stack = preinitialization_transactions_.front().GetArenaStack();
+ }
+ preinitialization_transactions_.emplace_front(strict, root, arena_stack, arena_pool);
+ runtime->SetActiveTransaction();
+}
+
+void AotClassLinker::ExitTransactionMode() {
+ DCHECK(IsActiveTransaction());
+ preinitialization_transactions_.pop_front();
+ if (preinitialization_transactions_.empty()) {
+ Runtime::Current()->ClearActiveTransaction();
+ } else {
+ DCHECK(IsActiveTransaction()); // Trigger the DCHECK() in `IsActiveTransaction()`.
+ }
+}
+
+void AotClassLinker::RollbackAllTransactions() {
+ // If transaction is aborted, all transactions will be kept in the list.
+ // Rollback and exit all of them.
+ while (IsActiveTransaction()) {
+ RollbackAndExitTransactionMode();
+ }
+}
+
+void AotClassLinker::RollbackAndExitTransactionMode() {
+ DCHECK(IsActiveTransaction());
+ Runtime::Current()->ClearActiveTransaction();
+ preinitialization_transactions_.front().Rollback();
+ preinitialization_transactions_.pop_front();
+ if (!preinitialization_transactions_.empty()) {
+ Runtime::Current()->SetActiveTransaction();
+ }
+}
+
+const Transaction* AotClassLinker::GetTransaction() const {
+ DCHECK(!preinitialization_transactions_.empty());
+ return &preinitialization_transactions_.front();
+}
+
+Transaction* AotClassLinker::GetTransaction() {
+ DCHECK(!preinitialization_transactions_.empty());
+ return &preinitialization_transactions_.front();
+}
+
+bool AotClassLinker::IsActiveStrictTransactionMode() const {
+ return IsActiveTransaction() && GetTransaction()->IsStrict();
+}
+
+bool AotClassLinker::TransactionWriteConstraint(Thread* self, ObjPtr<mirror::Object> obj) {
+ DCHECK(IsActiveTransaction());
+ if (GetTransaction()->WriteConstraint(obj)) {
+ Runtime* runtime = Runtime::Current();
+ DCHECK(runtime->GetHeap()->ObjectIsInBootImageSpace(obj) || obj->IsClass());
+ const char* extra = runtime->GetHeap()->ObjectIsInBootImageSpace(obj) ? "boot image " : "";
+ AbortTransactionF(
+ self, "Can't set fields of %s%s", extra, obj->PrettyTypeOf().c_str());
+ return true;
+ }
+ return false;
+}
+
+bool AotClassLinker::TransactionWriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value) {
+ DCHECK(IsActiveTransaction());
+ if (GetTransaction()->WriteValueConstraint(value)) {
+ DCHECK(value != nullptr);
+ const char* description = value->IsClass() ? "class" : "instance of";
+ ObjPtr<mirror::Class> klass = value->IsClass() ? value->AsClass() : value->GetClass();
+ AbortTransactionF(
+ self, "Can't store reference to %s %s", description, klass->PrettyDescriptor().c_str());
+ return true;
+ }
+ return false;
+}
+
+bool AotClassLinker::TransactionReadConstraint(Thread* self, ObjPtr<mirror::Object> obj) {
+ DCHECK(obj->IsClass());
+ if (GetTransaction()->ReadConstraint(obj)) {
+ AbortTransactionF(self,
+ "Can't read static fields of %s since it does not belong to clinit's class.",
+ obj->PrettyTypeOf().c_str());
+ return true;
+ }
+ return false;
+}
+
+bool AotClassLinker::TransactionAllocationConstraint(Thread* self, ObjPtr<mirror::Class> klass) {
+ DCHECK(IsActiveTransaction());
+ if (klass->IsFinalizable()) {
+ AbortTransactionF(self,
+ "Allocating finalizable object in transaction: %s",
+ klass->PrettyDescriptor().c_str());
+ return true;
+ }
+ return false;
+}
+
+void AotClassLinker::RecordWriteFieldBoolean(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint8_t value,
+ bool is_volatile) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile);
+}
+
+void AotClassLinker::RecordWriteFieldByte(mirror::Object* obj,
+ MemberOffset field_offset,
+ int8_t value,
+ bool is_volatile) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteFieldByte(obj, field_offset, value, is_volatile);
+}
+
+void AotClassLinker::RecordWriteFieldChar(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint16_t value,
+ bool is_volatile) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteFieldChar(obj, field_offset, value, is_volatile);
+}
+
+void AotClassLinker::RecordWriteFieldShort(mirror::Object* obj,
+ MemberOffset field_offset,
+ int16_t value,
+ bool is_volatile) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteFieldShort(obj, field_offset, value, is_volatile);
+}
+
+void AotClassLinker::RecordWriteField32(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint32_t value,
+ bool is_volatile) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteField32(obj, field_offset, value, is_volatile);
+}
+
+void AotClassLinker::RecordWriteField64(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint64_t value,
+ bool is_volatile) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteField64(obj, field_offset, value, is_volatile);
+}
+
+void AotClassLinker::RecordWriteFieldReference(mirror::Object* obj,
+ MemberOffset field_offset,
+ ObjPtr<mirror::Object> value,
+ bool is_volatile) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteFieldReference(obj, field_offset, value.Ptr(), is_volatile);
+}
+
+void AotClassLinker::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWriteArray(array, index, value);
+}
+
+void AotClassLinker::RecordStrongStringInsertion(ObjPtr<mirror::String> s) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordStrongStringInsertion(s);
+}
+
+void AotClassLinker::RecordWeakStringInsertion(ObjPtr<mirror::String> s) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWeakStringInsertion(s);
+}
+
+void AotClassLinker::RecordStrongStringRemoval(ObjPtr<mirror::String> s) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordStrongStringRemoval(s);
+}
+
+void AotClassLinker::RecordWeakStringRemoval(ObjPtr<mirror::String> s) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordWeakStringRemoval(s);
+}
+
+void AotClassLinker::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
+ dex::StringIndex string_idx) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordResolveString(dex_cache, string_idx);
}
-bool AotClassLinker::TransactionWriteValueConstraint(
- Thread* self, ObjPtr<mirror::Object> value) const {
- DCHECK(Runtime::Current()->IsActiveTransaction());
- return interpreter::ActiveTransactionChecker::WriteValueConstraint(self, value);
+void AotClassLinker::RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
+ dex::ProtoIndex proto_idx) {
+ DCHECK(IsActiveTransaction());
+ GetTransaction()->RecordResolveMethodType(dex_cache, proto_idx);
}
-bool AotClassLinker::TransactionAllocationConstraint(Thread* self,
- ObjPtr<mirror::Class> klass) const {
- DCHECK(Runtime::Current()->IsActiveTransaction());
- return interpreter::ActiveTransactionChecker::AllocationConstraint(self, klass);
+void AotClassLinker::ThrowTransactionAbortError(Thread* self) {
+ DCHECK(IsActiveTransaction());
+ // Passing nullptr means we rethrow an exception with the earlier transaction abort message.
+ GetTransaction()->ThrowAbortError(self, nullptr);
+}
+
+void AotClassLinker::AbortTransactionF(Thread* self, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ AbortTransactionV(self, fmt, args);
+ va_end(args);
+}
+
+void AotClassLinker::AbortTransactionV(Thread* self, const char* fmt, va_list args) {
+ CHECK(IsActiveTransaction());
+ // Constructs abort message.
+ std::string abort_message;
+ android::base::StringAppendV(&abort_message, fmt, args);
+ // Throws an exception so we can abort the transaction and rollback every change.
+ //
+ // Throwing an exception may cause its class initialization. If we mark the transaction
+ // aborted before that, we may warn with a false alarm. Throwing the exception before
+ // marking the transaction aborted avoids that.
+ // But now the transaction can be nested, and abort the transaction will relax the constraints
+ // for constructing stack trace.
+ GetTransaction()->Abort(abort_message);
+ GetTransaction()->ThrowAbortError(self, &abort_message);
+}
+
+bool AotClassLinker::IsTransactionAborted() const {
+ DCHECK(IsActiveTransaction());
+ return GetTransaction()->IsAborted();
+}
+
+void AotClassLinker::VisitTransactionRoots(RootVisitor* visitor) {
+ for (Transaction& transaction : preinitialization_transactions_) {
+ transaction.VisitRoots(visitor);
+ }
}
} // namespace art
diff --git a/runtime/oat/aot_class_linker.h b/runtime/oat/aot_class_linker.h
index b046f1e66a..0ee2fe1b42 100644
--- a/runtime/oat/aot_class_linker.h
+++ b/runtime/oat/aot_class_linker.h
@@ -17,12 +17,16 @@
#ifndef ART_RUNTIME_OAT_AOT_CLASS_LINKER_H_
#define ART_RUNTIME_OAT_AOT_CLASS_LINKER_H_
+#include <forward_list>
+
#include "base/macros.h"
#include "sdk_checker.h"
#include "class_linker.h"
namespace art HIDDEN {
+class Transaction;
+
namespace gc {
class Heap;
} // namespace gc
@@ -43,19 +47,98 @@ class AotClassLinker : public ClassLinker {
EXPORT void SetSdkChecker(std::unique_ptr<SdkChecker>&& sdk_checker_);
const SdkChecker* GetSdkChecker() const;
+ // Verifies if the method is accessible according to the SdkChecker (if installed).
bool DenyAccessBasedOnPublicSdk(ArtMethod* art_method) const override
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Verifies if the field is accessible according to the SdkChecker (if installed).
bool DenyAccessBasedOnPublicSdk(ArtField* art_field) const override
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Verifies if the descriptor is accessible according to the SdkChecker (if installed).
bool DenyAccessBasedOnPublicSdk(std::string_view type_descriptor) const override;
+ // Enable or disable public sdk checks.
void SetEnablePublicSdkChecks(bool enabled) override;
+ // Transaction support.
+ EXPORT bool IsActiveTransaction() const;
+ // EnterTransactionMode may suspend.
+ EXPORT void EnterTransactionMode(bool strict, mirror::Class* root)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ EXPORT void ExitTransactionMode();
+ EXPORT void RollbackAllTransactions() REQUIRES_SHARED(Locks::mutator_lock_);
+ // Transaction rollback and exit transaction are always done together, it's convenience to
+ // do them in one function.
+ void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
+ const Transaction* GetTransaction() const;
+ Transaction* GetTransaction();
+ bool IsActiveStrictTransactionMode() const;
+
// Transaction constraint checks for AOT compilation.
- bool TransactionWriteConstraint(Thread* self, ObjPtr<mirror::Object> obj) const override
+ bool TransactionWriteConstraint(Thread* self, ObjPtr<mirror::Object> obj) override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ bool TransactionWriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value) override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Note: The read constraint check is non-virtual because it's not needed by `UnstartedRuntime`.
+ bool TransactionReadConstraint(Thread* self, ObjPtr<mirror::Object> obj)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ bool TransactionAllocationConstraint(Thread* self, ObjPtr<mirror::Class> klass) override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Transaction bookkeeping for AOT compilation.
+ void RecordWriteFieldBoolean(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint8_t value,
+ bool is_volatile) override;
+ void RecordWriteFieldByte(mirror::Object* obj,
+ MemberOffset field_offset,
+ int8_t value,
+ bool is_volatile) override;
+ void RecordWriteFieldChar(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint16_t value,
+ bool is_volatile) override;
+ void RecordWriteFieldShort(mirror::Object* obj,
+ MemberOffset field_offset,
+ int16_t value,
+ bool is_volatile) override;
+ void RecordWriteField32(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint32_t value,
+ bool is_volatile) override;
+ void RecordWriteField64(mirror::Object* obj,
+ MemberOffset field_offset,
+ uint64_t value,
+ bool is_volatile) override;
+ void RecordWriteFieldReference(mirror::Object* obj,
+ MemberOffset field_offset,
+ ObjPtr<mirror::Object> value,
+ bool is_volatile) override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void RecordStrongStringInsertion(ObjPtr<mirror::String> s) override
+ REQUIRES(Locks::intern_table_lock_);
+ void RecordWeakStringInsertion(ObjPtr<mirror::String> s) override
+ REQUIRES(Locks::intern_table_lock_);
+ void RecordStrongStringRemoval(ObjPtr<mirror::String> s) override
+ REQUIRES(Locks::intern_table_lock_);
+ void RecordWeakStringRemoval(ObjPtr<mirror::String> s) override
+ REQUIRES(Locks::intern_table_lock_);
+ void RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx) override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache, dex::ProtoIndex proto_idx)
+ override REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Aborting transactions for AOT compilation.
+ void ThrowTransactionAbortError(Thread* self) override
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void AbortTransactionF(Thread* self, const char* fmt, ...) override
+ __attribute__((__format__(__printf__, 3, 4)))
REQUIRES_SHARED(Locks::mutator_lock_);
- bool TransactionWriteValueConstraint(Thread* self, ObjPtr<mirror::Object> value) const override
+ void AbortTransactionV(Thread* self, const char* fmt, va_list args) override
REQUIRES_SHARED(Locks::mutator_lock_);
- bool TransactionAllocationConstraint(Thread* self, ObjPtr<mirror::Class> klass) const override
+ bool IsTransactionAborted() const override;
+
+ void VisitTransactionRoots(RootVisitor* visitor) override
REQUIRES_SHARED(Locks::mutator_lock_);
protected:
@@ -86,6 +169,12 @@ class AotClassLinker : public ClassLinker {
private:
std::unique_ptr<SdkChecker> sdk_checker_;
+
+ // Transactions used for pre-initializing classes at compilation time.
+ // Support nested transactions, maintain a list containing all transactions. Transactions are
+ // handled under a stack discipline. Because GC needs to go over all transactions, we choose list
+ // as substantial data structure instead of stack.
+ std::forward_list<Transaction> preinitialization_transactions_;
};
} // namespace art
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 2ebcbb0fd0..37734f7d74 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -439,19 +439,41 @@ class DeoptimizeStackVisitor final : public StackVisitor {
ArtMethod* method = GetMethod();
VLOG(deopt) << "Deoptimizing stack: depth: " << GetFrameDepth()
<< " at method " << ArtMethod::PrettyMethod(method);
+
if (method == nullptr || single_frame_done_) {
FinishStackWalk();
return false; // End stack walk.
- } else if (method->IsRuntimeMethod()) {
+ }
+
+ // Update if method exit event needs to be reported. We should report exit event only if we
+ // have reported an entry event. So tell interpreter if/ an entry event was reported.
+ bool supports_exit_events = Runtime::Current()->GetInstrumentation()->MethodSupportsExitEvents(
+ method, GetCurrentOatQuickMethodHeader());
+
+ if (method->IsRuntimeMethod()) {
// Ignore callee save method.
DCHECK(method->IsCalleeSaveMethod());
return true;
} else if (method->IsNative()) {
// If we return from JNI with a pending exception and want to deoptimize, we need to skip
// the native method. The top method is a runtime method, the native method comes next.
- // We also deoptimize due to method instrumentation reasons from method entry / exit
- // callbacks. In these cases native method is at the top of stack.
+ // We also deoptimize due to method instrumentation reasons from method exit callbacks.
+ // In these cases native method is at the top of stack.
CHECK((GetFrameDepth() == 1U) || (GetFrameDepth() == 0U));
+ // We see a native frame when:
+ // 1. returning from JNI with a pending exception
+ // 2. deopting from method exit callbacks (with or without a pending exception).
+ // skip_method_exit_callbacks_ is set in this case
+ // 3. handling async exception on suspend points for fast native methods.
+ // We only need to call method unwind event in the first case.
+ if (supports_exit_events &&
+ !skip_method_exit_callbacks_ &&
+ GetThread()->IsExceptionPending()) {
+ // An exception has occurred in a native method and we are deoptimizing past the native
+ // method. So report method unwind event here.
+ Runtime::Current()->GetInstrumentation()->MethodUnwindEvent(
+ GetThread(), method, dex::kDexNoIndex);
+ }
callee_method_ = method;
return true;
} else if (!single_frame_deopt_ &&
@@ -482,11 +504,6 @@ class DeoptimizeStackVisitor final : public StackVisitor {
} else {
HandleOptimizingDeoptimization(method, new_frame, updated_vregs);
}
- // Update if method exit event needs to be reported. We should report exit event only if we
- // have reported an entry event. So tell interpreter if/ an entry event was reported.
- bool supports_exit_events =
- Runtime::Current()->GetInstrumentation()->MethodSupportsExitEvents(
- method, GetCurrentOatQuickMethodHeader());
new_frame->SetSkipMethodExitEvents(!supports_exit_events);
// If we are deoptimizing after method exit callback we shouldn't call the method exit
// callbacks again for the top frame. We may have to deopt after the callback if the callback
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 31dd8a4e0e..5c394b136a 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -496,7 +496,8 @@ bool InvokeMethodImpl(const ScopedObjectAccessAlreadyRunnable& soa,
if (soa.Self()->IsExceptionPending()) {
// To abort a transaction we use a fake exception that should never be caught by the bytecode
// and therefore it makes no sense to wrap it.
- if (Runtime::Current()->IsTransactionAborted()) {
+ if (Runtime::Current()->IsActiveTransaction() &&
+ Runtime::Current()->GetClassLinker()->IsTransactionAborted()) {
DCHECK(soa.Self()->GetException()->GetClass()->DescriptorEquals(
"Ldalvik/system/TransactionAbortError;"))
<< soa.Self()->GetException()->GetClass()->PrettyDescriptor();
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index bc833b8cfa..bb60a863cd 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -178,7 +178,6 @@
#include "thread_list.h"
#include "ti/agent.h"
#include "trace.h"
-#include "transaction.h"
#include "vdex_file.h"
#include "verifier/class_verifier.h"
#include "well_known_classes-inl.h"
@@ -287,7 +286,7 @@ Runtime::Runtime()
system_thread_group_(nullptr),
system_class_loader_(nullptr),
dump_gc_performance_on_shutdown_(false),
- preinitialization_transactions_(),
+ active_transaction_(false),
verify_(verifier::VerifyMode::kNone),
target_sdk_version_(static_cast<uint32_t>(SdkVersion::kUnset)),
compat_framework_(),
@@ -2661,12 +2660,6 @@ void Runtime::VisitConcurrentRoots(RootVisitor* visitor, VisitRootFlags flags) {
}
}
-void Runtime::VisitTransactionRoots(RootVisitor* visitor) {
- for (Transaction& transaction : preinitialization_transactions_) {
- transaction.VisitRoots(visitor);
- }
-}
-
void Runtime::VisitNonThreadRoots(RootVisitor* visitor) {
java_vm_->VisitRoots(visitor);
sentinel_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
@@ -2678,7 +2671,7 @@ void Runtime::VisitNonThreadRoots(RootVisitor* visitor) {
.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
pre_allocated_NoClassDefFoundError_.VisitRootIfNonNull(visitor, RootInfo(kRootVMInternal));
VisitImageRoots(visitor);
- VisitTransactionRoots(visitor);
+ class_linker_->VisitTransactionRoots(visitor);
}
void Runtime::VisitNonConcurrentRoots(RootVisitor* visitor, VisitRootFlags flags) {
@@ -2931,221 +2924,6 @@ void Runtime::RegisterAppInfo(const std::string& package_name,
jit_->StartProfileSaver(profile_output_filename, code_paths, ref_profile_filename);
}
-// Transaction support.
-bool Runtime::IsActiveTransaction() const {
- return !preinitialization_transactions_.empty() && !GetTransaction()->IsRollingBack();
-}
-
-void Runtime::EnterTransactionMode(bool strict, mirror::Class* root) {
- DCHECK(IsAotCompiler());
- ArenaPool* arena_pool = nullptr;
- ArenaStack* arena_stack = nullptr;
- if (preinitialization_transactions_.empty()) { // Top-level transaction?
- // Make initialized classes visibly initialized now. If that happened during the transaction
- // and then the transaction was aborted, we would roll back the status update but not the
- // ClassLinker's bookkeeping structures, so these classes would never be visibly initialized.
- {
- Thread* self = Thread::Current();
- StackHandleScope<1> hs(self);
- HandleWrapper<mirror::Class> h(hs.NewHandleWrapper(&root));
- ScopedThreadSuspension sts(self, ThreadState::kNative);
- GetClassLinker()->MakeInitializedClassesVisiblyInitialized(Thread::Current(), /*wait=*/ true);
- }
- // Pass the runtime `ArenaPool` to the transaction.
- arena_pool = GetArenaPool();
- } else {
- // Pass the `ArenaStack` from previous transaction to the new one.
- arena_stack = preinitialization_transactions_.front().GetArenaStack();
- }
- preinitialization_transactions_.emplace_front(strict, root, arena_stack, arena_pool);
-}
-
-void Runtime::ExitTransactionMode() {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- preinitialization_transactions_.pop_front();
-}
-
-void Runtime::RollbackAndExitTransactionMode() {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- preinitialization_transactions_.front().Rollback();
- preinitialization_transactions_.pop_front();
-}
-
-bool Runtime::IsTransactionAborted() const {
- if (!IsActiveTransaction()) {
- return false;
- } else {
- DCHECK(IsAotCompiler());
- return GetTransaction()->IsAborted();
- }
-}
-
-void Runtime::RollbackAllTransactions() {
- // If transaction is aborted, all transactions will be kept in the list.
- // Rollback and exit all of them.
- while (IsActiveTransaction()) {
- RollbackAndExitTransactionMode();
- }
-}
-
-bool Runtime::IsActiveStrictTransactionMode() const {
- return IsActiveTransaction() && GetTransaction()->IsStrict();
-}
-
-const Transaction* Runtime::GetTransaction() const {
- DCHECK(!preinitialization_transactions_.empty());
- return &preinitialization_transactions_.front();
-}
-
-Transaction* Runtime::GetTransaction() {
- DCHECK(!preinitialization_transactions_.empty());
- return &preinitialization_transactions_.front();
-}
-
-void Runtime::AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- // Throwing an exception may cause its class initialization. If we mark the transaction
- // aborted before that, we may warn with a false alarm. Throwing the exception before
- // marking the transaction aborted avoids that.
- // But now the transaction can be nested, and abort the transaction will relax the constraints
- // for constructing stack trace.
- GetTransaction()->Abort(abort_message);
- GetTransaction()->ThrowAbortError(self, &abort_message);
-}
-
-void Runtime::ThrowTransactionAbortError(Thread* self) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- // Passing nullptr means we rethrow an exception with the earlier transaction abort message.
- GetTransaction()->ThrowAbortError(self, nullptr);
-}
-
-void Runtime::AbortTransactionF(Thread* self, const char* fmt, ...) {
- va_list args;
- va_start(args, fmt);
- AbortTransactionV(self, fmt, args);
- va_end(args);
-}
-
-void Runtime::AbortTransactionV(Thread* self, const char* fmt, va_list args) {
- CHECK(IsActiveTransaction());
- // Constructs abort message.
- std::string abort_msg;
- android::base::StringAppendV(&abort_msg, fmt, args);
- // Throws an exception so we can abort the transaction and rollback every change.
- AbortTransactionAndThrowAbortError(self, abort_msg);
-}
-
-void Runtime::RecordWriteFieldBoolean(mirror::Object* obj,
- MemberOffset field_offset,
- uint8_t value,
- bool is_volatile) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteFieldBoolean(obj, field_offset, value, is_volatile);
-}
-
-void Runtime::RecordWriteFieldByte(mirror::Object* obj,
- MemberOffset field_offset,
- int8_t value,
- bool is_volatile) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteFieldByte(obj, field_offset, value, is_volatile);
-}
-
-void Runtime::RecordWriteFieldChar(mirror::Object* obj,
- MemberOffset field_offset,
- uint16_t value,
- bool is_volatile) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteFieldChar(obj, field_offset, value, is_volatile);
-}
-
-void Runtime::RecordWriteFieldShort(mirror::Object* obj,
- MemberOffset field_offset,
- int16_t value,
- bool is_volatile) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteFieldShort(obj, field_offset, value, is_volatile);
-}
-
-void Runtime::RecordWriteField32(mirror::Object* obj,
- MemberOffset field_offset,
- uint32_t value,
- bool is_volatile) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteField32(obj, field_offset, value, is_volatile);
-}
-
-void Runtime::RecordWriteField64(mirror::Object* obj,
- MemberOffset field_offset,
- uint64_t value,
- bool is_volatile) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteField64(obj, field_offset, value, is_volatile);
-}
-
-void Runtime::RecordWriteFieldReference(mirror::Object* obj,
- MemberOffset field_offset,
- ObjPtr<mirror::Object> value,
- bool is_volatile) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteFieldReference(obj, field_offset, value.Ptr(), is_volatile);
-}
-
-void Runtime::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWriteArray(array, index, value);
-}
-
-void Runtime::RecordStrongStringInsertion(ObjPtr<mirror::String> s) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordStrongStringInsertion(s);
-}
-
-void Runtime::RecordWeakStringInsertion(ObjPtr<mirror::String> s) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWeakStringInsertion(s);
-}
-
-void Runtime::RecordStrongStringRemoval(ObjPtr<mirror::String> s) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordStrongStringRemoval(s);
-}
-
-void Runtime::RecordWeakStringRemoval(ObjPtr<mirror::String> s) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordWeakStringRemoval(s);
-}
-
-void Runtime::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
- dex::StringIndex string_idx) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordResolveString(dex_cache, string_idx);
-}
-
-void Runtime::RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache,
- dex::ProtoIndex proto_idx) {
- DCHECK(IsAotCompiler());
- DCHECK(IsActiveTransaction());
- GetTransaction()->RecordResolveMethodType(dex_cache, proto_idx);
-}
-
void Runtime::SetFaultMessage(const std::string& message) {
std::string* new_msg = new std::string(message);
std::string* cur_msg = fault_message_.exchange(new_msg);
diff --git a/runtime/runtime.h b/runtime/runtime.h
index a48bb55755..96f76652d2 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -20,7 +20,6 @@
#include <jni.h>
#include <stdio.h>
-#include <forward_list>
#include <iosfwd>
#include <memory>
#include <optional>
@@ -120,7 +119,6 @@ class ThreadList;
class ThreadPool;
class Trace;
struct TraceConfig;
-class Transaction;
using RuntimeOptions = std::vector<std::pair<std::string, const void*>>;
@@ -482,9 +480,6 @@ class Runtime {
void VisitNonThreadRoots(RootVisitor* visitor)
REQUIRES_SHARED(Locks::mutator_lock_);
- void VisitTransactionRoots(RootVisitor* visitor)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Sweep system weaks, the system weak is deleted if the visitor return null. Otherwise, the
// system weak is updated to be the visitor's returned value.
EXPORT void SweepSystemWeaks(IsMarkedVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -626,75 +621,19 @@ class Runtime {
const std::string& ref_profile_filename,
int32_t code_type);
- // Transaction support.
- EXPORT bool IsActiveTransaction() const;
- // EnterTransactionMode may suspend.
- EXPORT void EnterTransactionMode(bool strict, mirror::Class* root)
- REQUIRES_SHARED(Locks::mutator_lock_);
- EXPORT void ExitTransactionMode();
- EXPORT void RollbackAllTransactions() REQUIRES_SHARED(Locks::mutator_lock_);
- // Transaction rollback and exit transaction are always done together, it's convenience to
- // do them in one function.
- void RollbackAndExitTransactionMode() REQUIRES_SHARED(Locks::mutator_lock_);
- bool IsTransactionAborted() const;
- const Transaction* GetTransaction() const;
- Transaction* GetTransaction();
- bool IsActiveStrictTransactionMode() const;
-
- void AbortTransactionAndThrowAbortError(Thread* self, const std::string& abort_message)
- REQUIRES_SHARED(Locks::mutator_lock_);
- void ThrowTransactionAbortError(Thread* self)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
- void AbortTransactionF(Thread* self, const char* fmt, ...)
- __attribute__((__format__(__printf__, 3, 4)))
- REQUIRES_SHARED(Locks::mutator_lock_);
+ void SetActiveTransaction() {
+ DCHECK(IsAotCompiler());
+ active_transaction_ = true;
+ }
- void AbortTransactionV(Thread* self, const char* fmt, va_list args)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ void ClearActiveTransaction() {
+ DCHECK(IsAotCompiler());
+ active_transaction_ = false;
+ }
- void RecordWriteFieldBoolean(mirror::Object* obj,
- MemberOffset field_offset,
- uint8_t value,
- bool is_volatile);
- void RecordWriteFieldByte(mirror::Object* obj,
- MemberOffset field_offset,
- int8_t value,
- bool is_volatile);
- void RecordWriteFieldChar(mirror::Object* obj,
- MemberOffset field_offset,
- uint16_t value,
- bool is_volatile);
- void RecordWriteFieldShort(mirror::Object* obj,
- MemberOffset field_offset,
- int16_t value,
- bool is_volatile);
- EXPORT void RecordWriteField32(mirror::Object* obj,
- MemberOffset field_offset,
- uint32_t value,
- bool is_volatile);
- void RecordWriteField64(mirror::Object* obj,
- MemberOffset field_offset,
- uint64_t value,
- bool is_volatile);
- EXPORT void RecordWriteFieldReference(mirror::Object* obj,
- MemberOffset field_offset,
- ObjPtr<mirror::Object> value,
- bool is_volatile) REQUIRES_SHARED(Locks::mutator_lock_);
- EXPORT void RecordWriteArray(mirror::Array* array, size_t index, uint64_t value)
- REQUIRES_SHARED(Locks::mutator_lock_);
- void RecordStrongStringInsertion(ObjPtr<mirror::String> s)
- REQUIRES(Locks::intern_table_lock_);
- void RecordWeakStringInsertion(ObjPtr<mirror::String> s)
- REQUIRES(Locks::intern_table_lock_);
- void RecordStrongStringRemoval(ObjPtr<mirror::String> s)
- REQUIRES(Locks::intern_table_lock_);
- void RecordWeakStringRemoval(ObjPtr<mirror::String> s)
- REQUIRES(Locks::intern_table_lock_);
- void RecordResolveString(ObjPtr<mirror::DexCache> dex_cache, dex::StringIndex string_idx)
- REQUIRES_SHARED(Locks::mutator_lock_);
- void RecordResolveMethodType(ObjPtr<mirror::DexCache> dex_cache, dex::ProtoIndex proto_idx)
- REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsActiveTransaction() {
+ return active_transaction_;
+ }
void SetFaultMessage(const std::string& message);
@@ -1401,11 +1340,12 @@ class Runtime {
// If true, then we dump the GC cumulative timings on shutdown.
bool dump_gc_performance_on_shutdown_;
- // Transactions used for pre-initializing classes at compilation time.
- // Support nested transactions, maintain a list containing all transactions. Transactions are
- // handled under a stack discipline. Because GC needs to go over all transactions, we choose list
- // as substantial data structure instead of stack.
- std::forward_list<Transaction> preinitialization_transactions_;
+ // Transactions are handled by the `AotClassLinker` but we keep a simple flag
+ // in the `Runtime` for quick transaction checks.
+ // Code that's not AOT-specific but needs some transaction-specific behavior
+ // shall check this flag and, for an active transaction, make virtual calls
+ // through `ClassLinker` to `AotClassLinker` to implement that behavior.
+ bool active_transaction_;
// If kNone, verification is disabled. kEnable by default.
verifier::VerifyMode verify_;
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index aad943020d..0b10d2ee1d 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -58,12 +58,52 @@ void RuntimeCallbacks::RemoveDdmCallback(DdmCallback* cb) {
Remove(cb, &ddm_callbacks_);
}
+void RuntimeCallbacks::AddAppInfoCallback(AppInfoCallback* cb) {
+ WriterMutexLock mu(Thread::Current(), *callback_lock_);
+ appinfo_callbacks_.push_back(cb);
+}
+
+void RuntimeCallbacks::RemoveAppInfoCallback(AppInfoCallback* cb) {
+ WriterMutexLock mu(Thread::Current(), *callback_lock_);
+ Remove(cb, &appinfo_callbacks_);
+}
+
void RuntimeCallbacks::DdmPublishChunk(uint32_t type, const ArrayRef<const uint8_t>& data) {
for (DdmCallback* cb : COPY(ddm_callbacks_)) {
cb->DdmPublishChunk(type, data);
}
}
+void RuntimeCallbacks::SetCurrentProcessName(const std::string& process_name) {
+ for (AppInfoCallback* cb : COPY(appinfo_callbacks_)) {
+ cb->SetCurrentProcessName(process_name);
+ }
+}
+
+void RuntimeCallbacks::AddApplication(const std::string& package_name) {
+ for (AppInfoCallback* cb : COPY(appinfo_callbacks_)) {
+ cb->AddApplication(package_name);
+ }
+}
+
+void RuntimeCallbacks::RemoveApplication(const std::string& package_name) {
+ for (AppInfoCallback* cb : COPY(appinfo_callbacks_)) {
+ cb->RemoveApplication(package_name);
+ }
+}
+
+void RuntimeCallbacks::SetWaitingForDebugger(bool waiting) {
+ for (AppInfoCallback* cb : COPY(appinfo_callbacks_)) {
+ cb->SetWaitingForDebugger(waiting);
+ }
+}
+
+void RuntimeCallbacks::SetUserId(int user_id) {
+ for (AppInfoCallback* cb : COPY(appinfo_callbacks_)) {
+ cb->SetUserId(user_id);
+ }
+}
+
void RuntimeCallbacks::AddDebuggerControlCallback(DebuggerControlCallback* cb) {
WriterMutexLock mu(Thread::Current(), *callback_lock_);
debugger_control_callbacks_.push_back(cb);
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index 9d7e199bfa..4e355151da 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -70,6 +70,19 @@ class DdmCallback {
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
};
+class AppInfoCallback {
+ public:
+ virtual ~AppInfoCallback() {}
+ virtual void SetCurrentProcessName(const std::string& process_name)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ virtual void AddApplication(const std::string& package_name)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ virtual void RemoveApplication(const std::string& package_name)
+ REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ virtual void SetWaitingForDebugger(bool waiting) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ virtual void SetUserId(int uid) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+};
+
class DebuggerControlCallback {
public:
virtual ~DebuggerControlCallback() {}
@@ -240,6 +253,15 @@ class EXPORT RuntimeCallbacks {
void AddDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
void RemoveDdmCallback(DdmCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+ void SetCurrentProcessName(const std::string& process_name) REQUIRES_SHARED(Locks::mutator_lock_);
+ void AddApplication(const std::string& package_name) REQUIRES_SHARED(Locks::mutator_lock_);
+ void RemoveApplication(const std::string& package_name) REQUIRES_SHARED(Locks::mutator_lock_);
+ void SetWaitingForDebugger(bool waiting) REQUIRES_SHARED(Locks::mutator_lock_);
+ void SetUserId(int uid) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void AddAppInfoCallback(AppInfoCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+ void RemoveAppInfoCallback(AppInfoCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
+
void StartDebugger() REQUIRES_SHARED(Locks::mutator_lock_);
// NO_THREAD_SAFETY_ANALYSIS since this is only called when we are in the middle of shutting down
// and the mutator_lock_ is no longer acquirable.
@@ -279,6 +301,7 @@ class EXPORT RuntimeCallbacks {
GUARDED_BY(callback_lock_);
std::vector<DdmCallback*> ddm_callbacks_
GUARDED_BY(callback_lock_);
+ std::vector<AppInfoCallback*> appinfo_callbacks_ GUARDED_BY(callback_lock_);
std::vector<DebuggerControlCallback*> debugger_control_callbacks_
GUARDED_BY(callback_lock_);
std::vector<ReflectiveValueVisitCallback*> reflective_value_visit_callbacks_
diff --git a/runtime/runtime_image.cc b/runtime/runtime_image.cc
index 997ea2fde6..b134c79286 100644
--- a/runtime/runtime_image.cc
+++ b/runtime/runtime_image.cc
@@ -964,7 +964,9 @@ class RuntimeImageHelper {
entrypoint = boot_jni_stub;
}
}
- copy->SetEntryPointFromQuickCompiledCode(entrypoint);
+ copy->SetNativePointer(ArtMethod::EntryPointFromQuickCompiledCodeOffset(kRuntimePointerSize),
+ entrypoint,
+ kRuntimePointerSize);
if (method->IsNative()) {
StubType stub_type = method->IsCriticalNative()
diff --git a/runtime/signal_catcher.cc b/runtime/signal_catcher.cc
index 3660ef3a19..09a4b2d85e 100644
--- a/runtime/signal_catcher.cc
+++ b/runtime/signal_catcher.cc
@@ -16,8 +16,8 @@
#include "signal_catcher.h"
-#include <csignal>
-#include <cstdlib>
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/stat.h>
@@ -25,13 +25,13 @@
#include <sys/types.h>
#include <unistd.h>
+#include <csignal>
+#include <cstdlib>
#include <optional>
#include <sstream>
-#include <android-base/file.h>
-#include <android-base/stringprintf.h>
-
#include "arch/instruction_set.h"
+#include "base/debugstore.h"
#include "base/logging.h" // For GetCmdLine.
#include "base/os.h"
#include "base/time_utils.h"
@@ -136,6 +136,8 @@ void SignalCatcher::HandleSigQuit() {
os << "Build type: " << (kIsDebugBuild ? "debug" : "optimized") << "\n";
+ os << "Debug Store: " << DebugStoreGetString() << "\n";
+
runtime->DumpForSigQuit(os);
if ((false)) {
diff --git a/runtime/startup_completed_task.cc b/runtime/startup_completed_task.cc
index fa768d52df..b84c3107f3 100644
--- a/runtime/startup_completed_task.cc
+++ b/runtime/startup_completed_task.cc
@@ -53,11 +53,12 @@ void StartupCompletedTask::Run(Thread* self) {
if (!runtime->IsJavaDebuggable()) {
std::string compiler_filter;
std::string compilation_reason;
+ std::string primary_apk_path = runtime->GetAppInfo()->GetPrimaryApkPath();
runtime->GetAppInfo()->GetPrimaryApkOptimizationStatus(&compiler_filter, &compilation_reason);
CompilerFilter::Filter filter;
if (CompilerFilter::ParseCompilerFilter(compiler_filter.c_str(), &filter) &&
!CompilerFilter::IsAotCompilationEnabled(filter) &&
- !runtime->GetHeap()->HasAppImageSpace()) {
+ !runtime->GetHeap()->HasAppImageSpaceFor(primary_apk_path)) {
std::string error_msg;
if (!RuntimeImage::WriteImageToDisk(&error_msg)) {
LOG(DEBUG) << "Could not write temporary image to disk " << error_msg;
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 436daa0b54..4791b7c072 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -1488,6 +1488,19 @@ void ThreadList::Unregister(Thread* self, bool should_run_callbacks) {
usleep(1);
// We failed to remove the thread due to a suspend request or the like, loop and try again.
}
+
+ // We flush the trace buffer in Thread::Destroy. We have to check again here because once the
+ // Thread::Destroy finishes we wait for any active suspend requests to finish before deleting
+ // the thread. If a new trace was started during the wait period we may allocate the trace buffer
+ // again. The trace buffer would only contain the method entry events for the methods on the stack
+ // of an exiting thread. It is not required to flush these entries but we need to release the
+ // buffer. Ideally we should either not generate trace events for a thread that is exiting or use
+ // a different mechanism to report the initial events on a trace start that doesn't use per-thread
+ // buffer. Both these approaches are not trivial to implement, so we are going with the approach
+ // of just releasing the buffer here.
+ if (UNLIKELY(self->GetMethodTraceBuffer() != nullptr)) {
+ Trace::ReleaseThreadBuffer(self);
+ }
delete self;
// Release the thread ID after the thread is finished and deleted to avoid cases where we can
diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc
index f0ea078cdc..80fd3587ae 100644
--- a/runtime/thread_pool.cc
+++ b/runtime/thread_pool.cc
@@ -86,14 +86,22 @@ ThreadPoolWorker::~ThreadPoolWorker() {
CHECK_PTHREAD_CALL(pthread_join, (pthread_, nullptr), "thread pool worker shutdown");
}
-void ThreadPoolWorker::SetPthreadPriority(int priority) {
+// Set the "nice" priority for tid (0 means self).
+static void SetPriorityForTid(pid_t tid, int priority) {
CHECK_GE(priority, PRIO_MIN);
CHECK_LE(priority, PRIO_MAX);
-#if defined(ART_TARGET_ANDROID)
- int result = setpriority(PRIO_PROCESS, pthread_gettid_np(pthread_), priority);
+ int result = setpriority(PRIO_PROCESS, tid, priority);
if (result != 0) {
- PLOG(ERROR) << "Failed to setpriority to :" << priority;
+#if defined(ART_TARGET_ANDROID)
+ PLOG(WARNING) << "Failed to setpriority to :" << priority;
+#endif
+ // Setpriority may fail on host due to ulimit issues.
}
+}
+
+void ThreadPoolWorker::SetPthreadPriority(int priority) {
+#if defined(ART_TARGET_ANDROID)
+ SetPriorityForTid(pthread_gettid_np(pthread_), priority);
#else
UNUSED(priority);
#endif
@@ -144,6 +152,10 @@ void* ThreadPoolWorker::Callback(void* arg) {
// Do work until its time to shut down.
worker->Run();
runtime->DetachCurrentThread(/* should_run_callbacks= */ false);
+ // On zygote fork, we wait for this thread to exit completely. Set to highest Java priority
+ // to speed that up.
+ constexpr int kJavaMaxPrioNiceness = -8;
+ SetPriorityForTid(0 /* this thread */, kJavaMaxPrioNiceness);
return nullptr;
}
diff --git a/runtime/trace.cc b/runtime/trace.cc
index e2fbd65bbe..71dd74f01b 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -271,8 +271,7 @@ class TraceWriterTask final : public SelfDeletingTask {
index_(index),
buffer_(buffer),
cur_offset_(cur_offset),
- thread_id_(thread_id),
- reserve_buf_for_tid_(0) {}
+ thread_id_(thread_id) {}
void Run(Thread* self ATTRIBUTE_UNUSED) override {
std::unordered_map<ArtMethod*, std::string> method_infos;
@@ -282,23 +281,12 @@ class TraceWriterTask final : public SelfDeletingTask {
}
trace_writer_->FlushBuffer(buffer_, cur_offset_, thread_id_, method_infos);
if (index_ == -1) {
- // This was a temporary buffer we allocated since there are no more free buffers and we
- // couldn't find one by flushing the pending tasks either. This should only happen when we
- // have fewer buffers than the number of threads.
- if (reserve_buf_for_tid_ == 0) {
- // Just free the buffer here if it wasn't reserved for any thread.
- delete[] buffer_;
- }
- } else {
- trace_writer_->FetchTraceBufferForThread(index_, reserve_buf_for_tid_);
+ // This was a temporary buffer we allocated since there are no free buffers and it wasn't
+ // safe to wait for one. This should only happen when we have fewer buffers than the number
+ // of threads.
+ delete[] buffer_;
}
- }
-
- // Reserves the buffer for a particular thread. The thread is free to use this buffer once the
- // task has finished running. This is used when there are no free buffers for the thread to use.
- uintptr_t* ReserveBufferForTid(size_t tid) {
- reserve_buf_for_tid_ = tid;
- return buffer_;
+ trace_writer_->ReleaseBuffer(index_);
}
private:
@@ -307,10 +295,6 @@ class TraceWriterTask final : public SelfDeletingTask {
uintptr_t* buffer_;
size_t cur_offset_;
size_t thread_id_;
- // Sometimes we want to acquire a buffer for a particular thread. This holds
- // the tid of the thread that we want to acquire the buffer for. If this value
- // is 0 then it means we can use it for other threads.
- size_t reserve_buf_for_tid_;
};
std::vector<ArtMethod*>* Trace::AllocStackTrace() {
@@ -750,6 +734,9 @@ void Trace::StopTracing(bool flush_entries) {
CHECK_PTHREAD_CALL(pthread_join, (sampling_pthread, nullptr), "sampling thread shutdown");
}
+ // Wakeup any threads waiting for a buffer and abort allocating a buffer.
+ the_trace_->trace_writer_->StopTracing();
+
// Make a copy of the_trace_, so it can be flushed later. We want to reset
// the_trace_ to nullptr in suspend all scope to prevent any races
Trace* the_trace = the_trace_;
@@ -837,6 +824,17 @@ void Trace::FlushThreadBuffer(Thread* self) {
the_trace_->trace_writer_->FlushBuffer(self, /* is_sync= */ false, /* free_buffer= */ true);
}
+void Trace::ReleaseThreadBuffer(Thread* self) {
+ MutexLock mu(self, *Locks::trace_lock_);
+ // Check if we still need to flush inside the trace_lock_. If we are stopping tracing it is
+ // possible we already deleted the trace and flushed the buffer too.
+ if (the_trace_ == nullptr) {
+ DCHECK_EQ(self->GetMethodTraceBuffer(), nullptr);
+ return;
+ }
+ the_trace_->trace_writer_->ReleaseBufferForThread(self);
+}
+
void Trace::Abort() {
// Do not write anything anymore.
StopTracing(/* flush_entries= */ false);
@@ -919,6 +917,10 @@ TraceWriter::TraceWriter(File* trace_file,
num_records_(0),
clock_overhead_ns_(clock_overhead_ns),
owner_tids_(num_trace_buffers),
+ buffer_pool_lock_("tracing buffer pool lock", kDefaultMutexLevel),
+ buffer_available_("buffer available condition", buffer_pool_lock_),
+ num_waiters_zero_cond_("Num waiters zero", buffer_pool_lock_),
+ num_waiters_for_buffer_(0),
tracing_lock_("tracing lock", LockLevel::kTracingStreamingLock) {
// We initialize the start_time_ from the timestamp counter. This may not match
// with the monotonic timer but we only use this time to calculate the elapsed
@@ -1014,8 +1016,9 @@ void TraceWriter::FinishTracing(int flags, bool flush_entries) {
// shutdown, any unstarted workers can create problems if they try attaching while shutting
// down.
thread_pool_->WaitForWorkersToBeCreated();
- // Wait for any outstanding writer tasks to finish.
- thread_pool_->Wait(self, /* do_work= */ true, /* may_hold_locks= */ true);
+ // Wait for any outstanding writer tasks to finish. Let the thread pool worker finish the
+ // tasks to avoid any re-ordering when processing tasks.
+ thread_pool_->Wait(self, /* do_work= */ false, /* may_hold_locks= */ true);
DCHECK_EQ(thread_pool_->GetTaskCount(self), 0u);
thread_pool_->StopWorkers(self);
}
@@ -1225,24 +1228,6 @@ void Trace::ReadClocks(Thread* thread, uint32_t* thread_clock_diff, uint64_t* ti
}
}
-uintptr_t* TraceWriterThreadPool::FinishTaskAndClaimBuffer(size_t tid) {
- Thread* self = Thread::Current();
- TraceWriterTask* task = static_cast<TraceWriterTask*>(TryGetTask(self));
- if (task == nullptr) {
- // TODO(mythria): We need to ensure we have at least as many buffers in the pool as the number
- // of active threads for efficiency. It's a bit unlikely to hit this case and not trivial to
- // handle this. So we haven't fixed this yet.
- LOG(WARNING)
- << "Fewer buffers in the pool than the number of threads. Might cause some slowdown";
- return nullptr;
- }
-
- uintptr_t* buffer = task->ReserveBufferForTid(tid);
- task->Run(self);
- task->Finalize();
- return buffer;
-}
-
std::string TraceWriter::GetMethodLine(const std::string& method_line, uint32_t method_index) {
return StringPrintf("%#x\t%s", (method_index << TraceActionBits), method_line.c_str());
}
@@ -1399,6 +1384,9 @@ void TraceWriter::InitializeTraceBuffers() {
}
uintptr_t* TraceWriter::AcquireTraceBuffer(size_t tid) {
+ Thread* self = Thread::Current();
+
+ // Fast path, check if there is a free buffer in the pool
for (size_t index = 0; index < owner_tids_.size(); index++) {
size_t owner = 0;
if (owner_tids_[index].compare_exchange_strong(owner, tid)) {
@@ -1406,22 +1394,101 @@ uintptr_t* TraceWriter::AcquireTraceBuffer(size_t tid) {
}
}
- // No free buffers, flush the buffer at the start of task queue synchronously and then use that
- // buffer.
- uintptr_t* buffer = thread_pool_->FinishTaskAndClaimBuffer(tid);
+ // Increment a counter so we know how many threads are potentially suspended in the tracing code.
+ // We need this when stopping tracing. We need to wait for all these threads to finish executing
+ // this code so we can safely delete the trace related data.
+ num_waiters_for_buffer_.fetch_add(1);
+
+ uintptr_t* buffer = nullptr;
+ // If finish_tracing_ is set to true we shouldn't suspend ourselves. So check for finish_tracing_
+ // before the thread suspension. As an example, consider the following:
+ // T2 is looking for a free buffer in the loop above
+ // T1 calls stop tracing -> Sets finish_tracing_ to true -> Checks that there are no waiters ->
+ // Waiting to suspend all threads.
+ // T2 doesn't find a buffer.
+ // If T2 suspends before checking for finish_tracing_ there is a possibility T1 succeeds entering
+ // SuspendAllScope while thread T2 is still in the TraceWriter code.
+ // To avoid this, we increment the num_waiters_for_buffer and then check for finish_tracing
+ // before suspending the thread. StopTracing sets finish_tracing_ to true first and then checks
+ // for num_waiters_for_buffer. Both these are atomic variables and we use sequential consistency
+ // (acquire for load and release for stores), so all threads see the updates for these variables
+ // in the same order. That ensures we don't suspend in the tracing logic after Trace::StopTracing
+ // has returned. This is required so that we can safely delete tracing data.
+ if (self->IsThreadSuspensionAllowable() && !finish_tracing_.load()) {
+ ScopedThreadSuspension sts(self, ThreadState::kSuspended);
+ while (1) {
+ MutexLock mu(self, buffer_pool_lock_);
+ // Tracing is being stopped, so don't wait for a free buffer. Just return early.
+ if (finish_tracing_.load()) {
+ break;
+ }
+
+ // Check if there's a free buffer in the pool
+ for (size_t index = 0; index < owner_tids_.size(); index++) {
+ size_t owner = 0;
+ if (owner_tids_[index].compare_exchange_strong(owner, tid)) {
+ buffer = trace_buffer_.get() + index * kPerThreadBufSize;
+ break;
+ }
+ }
+
+ // Found a buffer
+ if (buffer != nullptr) {
+ break;
+ }
+
+ if (thread_pool_ == nullptr ||
+ (thread_pool_->GetTaskCount(self) < num_waiters_for_buffer_.load())) {
+ // We have fewer buffers than active threads, just allocate a new one.
+ break;
+ }
+
+ buffer_available_.WaitHoldingLocks(self);
+ }
+ }
+
+ // The thread is no longer in the suspend scope, so decrement the counter.
+ num_waiters_for_buffer_.fetch_sub(1);
+ if (num_waiters_for_buffer_.load() == 0 && finish_tracing_.load()) {
+ MutexLock mu(self, buffer_pool_lock_);
+ num_waiters_zero_cond_.Broadcast(self);
+ }
+
if (buffer == nullptr) {
- // We couldn't find a free buffer even after flushing all the tasks. So allocate a new buffer
- // here. This should only happen if we have more threads than the number of pool buffers.
- // TODO(mythria): Add a check for the above case here.
+ // Allocate a new buffer. We either don't want to wait or have too few buffers.
buffer = new uintptr_t[kPerThreadBufSize];
CHECK(buffer != nullptr);
}
return buffer;
}
-void TraceWriter::FetchTraceBufferForThread(int index, size_t tid) {
+void TraceWriter::StopTracing() {
+ Thread* self = Thread::Current();
+ MutexLock mu(self, buffer_pool_lock_);
+ finish_tracing_.store(true);
+ while (num_waiters_for_buffer_.load() != 0) {
+ buffer_available_.Broadcast(self);
+ num_waiters_zero_cond_.WaitHoldingLocks(self);
+ }
+}
+
+void TraceWriter::ReleaseBuffer(int index) {
// Only the trace_writer_ thread can release the buffer.
- owner_tids_[index].store(tid);
+ MutexLock mu(Thread::Current(), buffer_pool_lock_);
+ if (index != -1) {
+ owner_tids_[index].store(0);
+ }
+ buffer_available_.Signal(Thread::Current());
+}
+
+void TraceWriter::ReleaseBufferForThread(Thread* self) {
+ uintptr_t* buffer = self->GetMethodTraceBuffer();
+ int index = GetMethodTraceIndex(buffer);
+ if (index == -1) {
+ delete[] buffer;
+ } else {
+ ReleaseBuffer(index);
+ }
}
int TraceWriter::GetMethodTraceIndex(uintptr_t* current_buffer) {
diff --git a/runtime/trace.h b/runtime/trace.h
index b2012ed8e6..c3d3b66cd3 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -204,13 +204,19 @@ class TraceWriter {
// as the owner tid. This also allocates the buffer pool.
void InitializeTraceBuffers();
- // Releases the trace buffer and transfers the ownership to the specified tid. If the tid is 0,
- // then it means it is free and other threads can claim it.
- void FetchTraceBufferForThread(int index, size_t tid);
+ // Releases the trace buffer and signals any waiting threads about a free buffer.
+ void ReleaseBuffer(int index);
+
+ // Release the trace buffer of the thread. This is called to release the buffer without flushing
+ // the entries. See a comment in ThreadList::Unregister for more detailed explanation.
+ void ReleaseBufferForThread(Thread* self);
// Tries to find a free buffer (which has owner of 0) from the pool. If there are no free buffers
- // it fetches a task, flushes the contents of the buffer and returns that buffer.
- uintptr_t* AcquireTraceBuffer(size_t tid);
+ // then it just waits for a free buffer. To prevent any deadlocks, we only wait if the number of
+ // pending tasks are greater than the number of waiting threads. Allocates a new buffer if it
+ // isn't safe to wait.
+ uintptr_t* AcquireTraceBuffer(size_t tid) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(!tracing_lock_);
// Returns the index corresponding to the start of the current_buffer. We allocate one large
// buffer and assign parts of it for each thread.
@@ -218,6 +224,10 @@ class TraceWriter {
int GetTraceFormatVersion() { return trace_format_version_; }
+ // Ensures that there are no threads suspended waiting for a free buffer. It signals threads
+ // waiting for a free buffer and waits for all the threads to respond to the signal.
+ void StopTracing();
+
private:
void ReadValuesFromRecord(uintptr_t* method_trace_entries,
size_t record_index,
@@ -345,6 +355,12 @@ class TraceWriter {
std::vector<std::atomic<size_t>> owner_tids_;
std::unique_ptr<uintptr_t[]> trace_buffer_;
+ Mutex buffer_pool_lock_;
+ ConditionVariable buffer_available_ GUARDED_BY(buffer_pool_lock_);
+ ConditionVariable num_waiters_zero_cond_ GUARDED_BY(buffer_pool_lock_);
+ std::atomic<size_t> num_waiters_for_buffer_;
+ std::atomic<bool> finish_tracing_ = false;
+
// Lock to protect common data structures accessed from multiple threads like
// art_method_id_map_, thread_id_map_.
Mutex tracing_lock_;
@@ -422,6 +438,11 @@ class Trace final : public instrumentation::InstrumentationListener, public Clas
static void FlushThreadBuffer(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::trace_lock_) NO_THREAD_SAFETY_ANALYSIS;
+ // Release per-thread buffer without flushing any entries. This is used when a new trace buffer is
+ // allocated while the thread is terminating. See ThreadList::Unregister for more details.
+ static void ReleaseThreadBuffer(Thread* thread)
+ REQUIRES(!Locks::trace_lock_) NO_THREAD_SAFETY_ANALYSIS;
+
// Removes any listeners installed for method tracing. This is used in non-streaming case
// when we no longer record any events once the buffer is full. In other cases listeners are
// removed only when tracing stops. This is expected to be called in SuspendAll scope.
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 9a8fcd9739..9b3985503a 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -57,6 +57,7 @@ Transaction::Transaction(bool strict,
heap_(Runtime::Current()->GetHeap()),
strict_(strict),
root_(root),
+ last_allocated_object_(nullptr),
assert_no_new_records_reason_(nullptr) {
DCHECK(Runtime::Current()->IsAotCompiler());
DCHECK_NE(arena_stack != nullptr, arena_pool != nullptr);
@@ -164,6 +165,42 @@ bool Transaction::ReadConstraint(ObjPtr<mirror::Object> obj) const {
}
}
+void Transaction::RecordNewObject(ObjPtr<mirror::Object> obj) {
+ last_allocated_object_ = obj.Ptr();
+ ObjectLog log(&allocator_);
+ log.MarkAsNewObject();
+ object_logs_.Put(obj.Ptr(), std::move(log));
+}
+
+void Transaction::RecordNewArray(ObjPtr<mirror::Array> array) {
+ if (array->IsObjectArray()) {
+ // `ObjectArray<T>::SetWithoutChecks()` uses `SetFieldObject()` which records value
+ // changes in `object_log_`, so we need to record new object arrays as normal objects.
+ RecordNewObject(array);
+ return;
+ }
+ last_allocated_object_ = array.Ptr();
+ ArrayLog log(&allocator_);
+ log.MarkAsNewArray();
+ array_logs_.Put(array.Ptr(), std::move(log));
+}
+
+bool Transaction::ObjectNeedsTransactionRecords(ObjPtr<mirror::Object> obj) {
+ if (obj == last_allocated_object_) {
+ return false;
+ }
+ auto it = object_logs_.find(obj.Ptr());
+ return it == object_logs_.end() || !it->second.IsNewObject();
+}
+
+bool Transaction::ArrayNeedsTransactionRecords(ObjPtr<mirror::Array> array) {
+ if (array == last_allocated_object_) {
+ return false;
+ }
+ auto it = array_logs_.find(array.Ptr());
+ return it == array_logs_.end() || !it->second.IsNewArray();
+}
+
inline Transaction::ObjectLog& Transaction::GetOrCreateObjectLog(mirror::Object* obj) {
return object_logs_.GetOrCreate(obj, [&]() { return ObjectLog(&allocator_); });
}
@@ -174,8 +211,10 @@ void Transaction::RecordWriteFieldBoolean(mirror::Object* obj,
bool is_volatile) {
DCHECK(obj != nullptr);
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ObjectLog& object_log = GetOrCreateObjectLog(obj);
- object_log.LogBooleanValue(field_offset, value, is_volatile);
+ if (obj != last_allocated_object_) {
+ ObjectLog& object_log = GetOrCreateObjectLog(obj);
+ object_log.LogBooleanValue(field_offset, value, is_volatile);
+ }
}
void Transaction::RecordWriteFieldByte(mirror::Object* obj,
@@ -184,8 +223,10 @@ void Transaction::RecordWriteFieldByte(mirror::Object* obj,
bool is_volatile) {
DCHECK(obj != nullptr);
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ObjectLog& object_log = GetOrCreateObjectLog(obj);
- object_log.LogByteValue(field_offset, value, is_volatile);
+ if (obj != last_allocated_object_) {
+ ObjectLog& object_log = GetOrCreateObjectLog(obj);
+ object_log.LogByteValue(field_offset, value, is_volatile);
+ }
}
void Transaction::RecordWriteFieldChar(mirror::Object* obj,
@@ -194,8 +235,10 @@ void Transaction::RecordWriteFieldChar(mirror::Object* obj,
bool is_volatile) {
DCHECK(obj != nullptr);
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ObjectLog& object_log = GetOrCreateObjectLog(obj);
- object_log.LogCharValue(field_offset, value, is_volatile);
+ if (obj != last_allocated_object_) {
+ ObjectLog& object_log = GetOrCreateObjectLog(obj);
+ object_log.LogCharValue(field_offset, value, is_volatile);
+ }
}
@@ -205,8 +248,10 @@ void Transaction::RecordWriteFieldShort(mirror::Object* obj,
bool is_volatile) {
DCHECK(obj != nullptr);
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ObjectLog& object_log = GetOrCreateObjectLog(obj);
- object_log.LogShortValue(field_offset, value, is_volatile);
+ if (obj != last_allocated_object_) {
+ ObjectLog& object_log = GetOrCreateObjectLog(obj);
+ object_log.LogShortValue(field_offset, value, is_volatile);
+ }
}
@@ -216,8 +261,10 @@ void Transaction::RecordWriteField32(mirror::Object* obj,
bool is_volatile) {
DCHECK(obj != nullptr);
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ObjectLog& object_log = GetOrCreateObjectLog(obj);
- object_log.Log32BitsValue(field_offset, value, is_volatile);
+ if (obj != last_allocated_object_) {
+ ObjectLog& object_log = GetOrCreateObjectLog(obj);
+ object_log.Log32BitsValue(field_offset, value, is_volatile);
+ }
}
void Transaction::RecordWriteField64(mirror::Object* obj,
@@ -226,8 +273,10 @@ void Transaction::RecordWriteField64(mirror::Object* obj,
bool is_volatile) {
DCHECK(obj != nullptr);
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ObjectLog& object_log = GetOrCreateObjectLog(obj);
- object_log.Log64BitsValue(field_offset, value, is_volatile);
+ if (obj != last_allocated_object_) {
+ ObjectLog& object_log = GetOrCreateObjectLog(obj);
+ object_log.Log64BitsValue(field_offset, value, is_volatile);
+ }
}
void Transaction::RecordWriteFieldReference(mirror::Object* obj,
@@ -236,8 +285,10 @@ void Transaction::RecordWriteFieldReference(mirror::Object* obj,
bool is_volatile) {
DCHECK(obj != nullptr);
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ObjectLog& object_log = GetOrCreateObjectLog(obj);
- object_log.LogReferenceValue(field_offset, value, is_volatile);
+ if (obj != last_allocated_object_) {
+ ObjectLog& object_log = GetOrCreateObjectLog(obj);
+ object_log.LogReferenceValue(field_offset, value, is_volatile);
+ }
}
void Transaction::RecordWriteArray(mirror::Array* array, size_t index, uint64_t value) {
@@ -245,8 +296,10 @@ void Transaction::RecordWriteArray(mirror::Array* array, size_t index, uint64_t
DCHECK(array->IsArrayInstance());
DCHECK(!array->IsObjectArray());
DCHECK(assert_no_new_records_reason_ == nullptr) << assert_no_new_records_reason_;
- ArrayLog& array_log = array_logs_.GetOrCreate(array, [&]() { return ArrayLog(&allocator_); });
- array_log.LogValue(index, value);
+ if (array != last_allocated_object_) {
+ ArrayLog& array_log = array_logs_.GetOrCreate(array, [&]() { return ArrayLog(&allocator_); });
+ array_log.LogValue(index, value);
+ }
}
void Transaction::RecordResolveString(ObjPtr<mirror::DexCache> dex_cache,
@@ -355,6 +408,7 @@ void Transaction::VisitRoots(RootVisitor* visitor) {
DCHECK(Locks::mutator_lock_->IsExclusiveHeld(Thread::Current()));
visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&root_), RootInfo(kRootUnknown));
+ visitor->VisitRoot(&last_allocated_object_, RootInfo(kRootUnknown));
{
// Create a separate `ArenaStack` for this thread.
ArenaStack arena_stack(Runtime::Current()->GetArenaPool());
@@ -471,6 +525,9 @@ void Transaction::ObjectLog::LogValue(ObjectLog::FieldValueKind kind,
MemberOffset offset,
uint64_t value,
bool is_volatile) {
+ if (is_new_object_) {
+ return;
+ }
auto it = field_values_.find(offset.Uint32Value());
if (it == field_values_.end()) {
ObjectLog::FieldValue field_value;
@@ -679,6 +736,9 @@ Transaction::InternStringLog::InternStringLog(ObjPtr<mirror::String> s,
}
void Transaction::ArrayLog::LogValue(size_t index, uint64_t value) {
+ if (is_new_array_) {
+ return;
+ }
// Add a mapping if there is none yet.
array_values_.FindOrAdd(index, value);
}
@@ -744,7 +804,8 @@ void Transaction::ArrayLog::UndoArrayWrite(mirror::Array* array,
Transaction* ScopedAssertNoNewTransactionRecords::InstallAssertion(const char* reason) {
Transaction* transaction = nullptr;
if (kIsDebugBuild && Runtime::Current()->IsActiveTransaction()) {
- transaction = Runtime::Current()->GetTransaction();
+ AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
+ transaction = class_linker->GetTransaction();
if (transaction != nullptr) {
CHECK(transaction->assert_no_new_records_reason_ == nullptr)
<< "old: " << transaction->assert_no_new_records_reason_ << " new: " << reason;
@@ -756,7 +817,8 @@ Transaction* ScopedAssertNoNewTransactionRecords::InstallAssertion(const char* r
void ScopedAssertNoNewTransactionRecords::RemoveAssertion(Transaction* transaction) {
if (kIsDebugBuild) {
- CHECK(Runtime::Current()->GetTransaction() == transaction);
+ AotClassLinker* class_linker = down_cast<AotClassLinker*>(Runtime::Current()->GetClassLinker());
+ CHECK(class_linker->GetTransaction() == transaction);
CHECK(transaction->assert_no_new_records_reason_ != nullptr);
transaction->assert_no_new_records_reason_ = nullptr;
}
diff --git a/runtime/transaction.h b/runtime/transaction.h
index 847120234d..60d1b3e3fd 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -74,6 +74,24 @@ class Transaction final {
return strict_;
}
+ // Record newly allocated object/array.
+ //
+ // There is no reason to record old values for newly allocated objects because they become
+ // unreachable when the transaction is rolled back, so their data does not need to be rolled back.
+ //
+ // Implementation details: We track all newly allocated objects/arrays by creating an
+ // `ObjectLog`/`ArrayLog` flagged as a new object/array. We also cache the last allocated
+ // object/array which often helps avoid the search for the flagged `ObjectLog`/`ArrayLog`.
+ void RecordNewObject(ObjPtr<mirror::Object> allocated_object)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void RecordNewArray(ObjPtr<mirror::Array> allocated_object)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ bool ObjectNeedsTransactionRecords(ObjPtr<mirror::Object> obj)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ bool ArrayNeedsTransactionRecords(ObjPtr<mirror::Array> array)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Record object field changes.
void RecordWriteFieldBoolean(mirror::Object* obj,
MemberOffset field_offset,
@@ -160,8 +178,18 @@ class Transaction final {
return field_values_.size();
}
+ void MarkAsNewObject() {
+ DCHECK(field_values_.empty());
+ is_new_object_ = true;
+ }
+
+ bool IsNewObject() const {
+ return is_new_object_;
+ }
+
explicit ObjectLog(ScopedArenaAllocator* allocator)
- : field_values_(std::less<uint32_t>(), allocator->Adapter(kArenaAllocTransaction)) {}
+ : is_new_object_(false),
+ field_values_(std::less<uint32_t>(), allocator->Adapter(kArenaAllocTransaction)) {}
ObjectLog(ObjectLog&& log) = default;
private:
@@ -192,6 +220,10 @@ class Transaction final {
MemberOffset field_offset,
const FieldValue& field_value) const REQUIRES_SHARED(Locks::mutator_lock_);
+ // Whether this is a new object. We do not need to keep transaction records for objects
+ // created inside a transaction because they become unreachable on rollback.
+ bool is_new_object_;
+
// Maps field's offset to its value.
ScopedArenaSafeMap<uint32_t, FieldValue> field_values_;
@@ -208,8 +240,18 @@ class Transaction final {
return array_values_.size();
}
+ void MarkAsNewArray() {
+ DCHECK(array_values_.empty());
+ is_new_array_ = true;
+ }
+
+ bool IsNewArray() const {
+ return is_new_array_;
+ }
+
explicit ArrayLog(ScopedArenaAllocator* allocator)
- : array_values_(std::less<size_t>(), allocator->Adapter(kArenaAllocTransaction)) {}
+ : is_new_array_(false),
+ array_values_(std::less<size_t>(), allocator->Adapter(kArenaAllocTransaction)) {}
ArrayLog(ArrayLog&& log) = default;
@@ -219,6 +261,10 @@ class Transaction final {
size_t index,
uint64_t value) const REQUIRES_SHARED(Locks::mutator_lock_);
+ // Whether this is a new array. We do not need to keep transaction records for arrays
+ // created inside a transaction because they become unreachable on rollback.
+ bool is_new_array_;
+
// Maps index to value.
// TODO use JValue instead ?
ScopedArenaSafeMap<size_t, uint64_t> array_values_;
@@ -333,6 +379,7 @@ class Transaction final {
const bool strict_;
std::string abort_message_;
mirror::Class* root_;
+ mirror::Object* last_allocated_object_;
const char* assert_no_new_records_reason_;
friend class ScopedAssertNoNewTransactionRecords;
diff --git a/runtime/transaction_test.cc b/runtime/transaction_test.cc
index e52cef46e0..17a78ad6af 100644
--- a/runtime/transaction_test.cc
+++ b/runtime/transaction_test.cc
@@ -19,7 +19,7 @@
#include "art_field-inl.h"
#include "art_method-inl.h"
#include "class_linker-inl.h"
-#include "common_runtime_test.h"
+#include "common_transaction_test.h"
#include "common_throws.h"
#include "dex/dex_file.h"
#include "mirror/array-alloc-inl.h"
@@ -28,7 +28,7 @@
namespace art HIDDEN {
-class TransactionTest : public CommonRuntimeTest {
+class TransactionTest : public CommonTransactionTest {
protected:
TransactionTest() {
this->use_boot_image_ = true; // We need the boot image for this test.
diff --git a/test/2230-profile-save-hotness/run.py b/test/2230-profile-save-hotness/run.py
index 526b841b87..e2f4473b86 100644
--- a/test/2230-profile-save-hotness/run.py
+++ b/test/2230-profile-save-hotness/run.py
@@ -16,10 +16,11 @@
def run(ctx, args):
ctx.default_run(
args,
+ # Profiling is only done on interpreted and JITted code.
Xcompiler_option=[
- "--count-hotness-in-compiled-code", "--compiler-filter=speed"
+ "--compiler-filter=verify"
],
runtime_option=[
- "-Xps-profile-aot-code", "-Xjitsaveprofilinginfo", "-Xusejit:true"
+ "-Xjitsaveprofilinginfo", "-Xusejit:true"
],
)
diff --git a/test/2230-profile-save-hotness/src-art/Main.java b/test/2230-profile-save-hotness/src-art/Main.java
index c9132e6e7d..06e89b2ade 100644
--- a/test/2230-profile-save-hotness/src-art/Main.java
+++ b/test/2230-profile-save-hotness/src-art/Main.java
@@ -47,7 +47,7 @@ public class Main {
new String[] {codePath},
VMRuntime.CODE_PATH_TYPE_PRIMARY_APK);
- // Test that the profile saves an app method with a profiling info.
+ // Test that the profile saves an app method that gets JITted.
$noinline$hotnessCountWithLoop(100000);
ensureProfileProcessing();
Method appMethod = Main.class.getDeclaredMethod(methodName);
@@ -72,7 +72,7 @@ public class Main {
}
}
- // Checks if the profiles saver has the method as hot/warm.
+ // Checks if the profile saver has the method as hot/warm.
public static native boolean presentInProfile(String profile, Method method);
// Ensures the profile saver does its usual processing.
public static native void ensureProfileProcessing();
diff --git a/test/2256-checker-vector-replacement/src/Main.java b/test/2256-checker-vector-replacement/src/Main.java
index fca80fbeda..2f94094577 100644
--- a/test/2256-checker-vector-replacement/src/Main.java
+++ b/test/2256-checker-vector-replacement/src/Main.java
@@ -20,30 +20,34 @@ public class Main {
}
// Before loop optimization we only had an array get. After it, we optimized to also have
- // VecLoad operations. This happens consistently only for Arm64. Arm32 vectorizes consistently
- // but it also removes the ArrayGet. X86/X86_64 doesn't vectorize consistently (other
- // vectorization tests also ignore x86/x86_64).
+ // VecLoad operations. This happens consistently only for Arm64 when using traditional
+ // vectorization (NEON). Arm32 vectorizes consistently but it also removes the ArrayGet, as does
+ // Arm64 predicated vectorization (SVE) because the scalar tail loop is eliminated. X86/X86_64
+ // doesn't vectorize consistently (other vectorization tests also ignore x86/x86_64).
+ // TODO: Create equivalent ArrayGet-replacement regression test for SVE, when SVE supports LSE.
/// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (before)
- /// CHECK: ArrayGet
+ /// CHECK-IF: not (hasIsaFeature("sve") and os.environ.get('ART_FORCE_TRY_PREDICATED_SIMD') == 'true')
+ /// CHECK-DAG: ArrayGet
+ /// CHECK-NOT: VecLoad
+ /// CHECK-FI:
/// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (after)
- /// CHECK: ArrayGet
-
- /// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (before)
- /// CHECK-NOT: VecLoad
-
- /// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() loop_optimization (after)
- /// CHECK: VecLoad
+ /// CHECK-IF: not (hasIsaFeature("sve") and os.environ.get('ART_FORCE_TRY_PREDICATED_SIMD') == 'true')
+ /// CHECK-DAG: ArrayGet
+ /// CHECK-DAG: VecLoad
+ /// CHECK-FI:
// In LoadStoreElimination both ArrayGet and VecLoad have the same heap location. We will try to
// replace the ArrayGet with the constant 0. The crash happens when we want to do the same with
// the vector operation, changing the vector operation to a scalar.
/// CHECK-START-ARM64: void Main.$noinline$testVectorAndNonVector() load_store_elimination (before)
- /// CHECK-DAG: VecLoad outer_loop:<<VecBlock:B\d+>>
- /// CHECK-DAG: ArrayGet outer_loop:<<ScalarBlock:B\d+>>
- /// CHECK-EVAL: "<<VecBlock>>" == "<<ScalarBlock>>"
+ /// CHECK-IF: not (hasIsaFeature("sve") and os.environ.get('ART_FORCE_TRY_PREDICATED_SIMD') == 'true')
+ /// CHECK-DAG: VecLoad outer_loop:<<VecBlock:B\d+>>
+ /// CHECK-DAG: ArrayGet outer_loop:<<ScalarBlock:B\d+>>
+ /// CHECK-EVAL: "<<VecBlock>>" == "<<ScalarBlock>>"
+ /// CHECK-FI:
private static void $noinline$testVectorAndNonVector() {
int[] result = new int[2];
diff --git a/test/623-checker-loop-regressions/src/Main.java b/test/623-checker-loop-regressions/src/Main.java
index 134e90c6c5..5e9b3d7e15 100644
--- a/test/623-checker-loop-regressions/src/Main.java
+++ b/test/623-checker-loop-regressions/src/Main.java
@@ -977,6 +977,39 @@ public class Main {
return s0 + s1;
}
+ // Regression test for the case, where a loop is vectorized in predicated mode, and there is
+ // a disambiguation scalar loop added. Make sure that the set, which records instructions
+ // inserted outside of new loops, is not reset until the full vectorization process has
+ // happened.
+ //
+ // Based on void android.util.Spline$MonotoneCubicSpline.<init>(float[], float[]).
+ //
+ /// CHECK-START-ARM64: void Main.$noinline$testExternalSetForLoopWithDisambiguation(int[], int[]) loop_optimization (after)
+ /// CHECK-IF: hasIsaFeature("sve") and os.environ.get('ART_FORCE_TRY_PREDICATED_SIMD') == 'true'
+ //
+ /// CHECK-DAG: <<Pred:j\d+>> VecPredSetAll loop:none
+ /// CHECK-DAG: VecReplicateScalar [{{i\d+}},<<Pred>>] loop:none
+ //
+ /// CHECK-ELSE:
+ //
+ /// CHECK-DAG: VecReplicateScalar loop:none
+ //
+ /// CHECK-FI:
+ //
+ // Vector loop.
+ /// CHECK-DAG: Phi loop:<<VectorLoop:B\d+>> outer_loop:none
+ /// CHECK-DAG: VecLoad loop:<<VectorLoop>> outer_loop:none
+ //
+ // Backup scalar loop.
+ /// CHECK-DAG: Phi loop:<<ScalarLoop:B\d+>> outer_loop:none
+ /// CHECK-DAG: ArrayGet loop:<<ScalarLoop>> outer_loop:none
+ public static void $noinline$testExternalSetForLoopWithDisambiguation(int[] d, int[] m) {
+ m[0] = d[0];
+ for (int i = 1; i < m.length; i++) {
+ m[i] = (d[i - 1] + d[i]) * 53;
+ }
+ }
+
public static final int ARRAY_SIZE = 512;
private static byte[] createAndInitByteArray(int x) {
@@ -1253,6 +1286,17 @@ public class Main {
byte[] b_b = createAndInitByteArray(2);
expectEquals(1278, testSADAndDotProdCombined1(b_a, b_b));
}
+ {
+ int[] i_a = createAndInitIntArray(1);
+ int[] i_b = createAndInitIntArray(2);
+ $noinline$testExternalSetForLoopWithDisambiguation(i_a, i_b);
+
+ int sum = 0;
+ for (int i = 0; i < i_b.length; i++) {
+ sum += i_b[i];
+ }
+ expectEquals(-13839413, sum);
+ }
System.out.println("passed");
}
diff --git a/test/667-jit-jni-stub/jit_jni_stub_test.cc b/test/667-jit-jni-stub/jit_jni_stub_test.cc
index 2b8e14c03d..52f467e38b 100644
--- a/test/667-jit-jni-stub/jit_jni_stub_test.cc
+++ b/test/667-jit-jni-stub/jit_jni_stub_test.cc
@@ -39,9 +39,15 @@ extern "C" JNIEXPORT
void Java_Main_jitGc(JNIEnv*, jclass) {
CHECK(Runtime::Current()->GetJit() != nullptr);
jit::JitCodeCache* cache = Runtime::Current()->GetJit()->GetCodeCache();
- ScopedObjectAccess soa(Thread::Current());
- cache->InvalidateAllCompiledCode();
- cache->GarbageCollectCache(Thread::Current());
+ Thread* self = Thread::Current();
+ {
+ ScopedObjectAccess soa(self);
+ cache->InvalidateAllCompiledCode();
+ }
+ cache->DoCollection(self);
+ // Run a second time in case the first run was a no-op due to a concurrent JIT
+ // GC from the JIT thread.
+ cache->DoCollection(self);
}
extern "C" JNIEXPORT
diff --git a/test/667-jit-jni-stub/src/Main.java b/test/667-jit-jni-stub/src/Main.java
index c0829b5100..05039670d1 100644
--- a/test/667-jit-jni-stub/src/Main.java
+++ b/test/667-jit-jni-stub/src/Main.java
@@ -67,9 +67,6 @@ public class Main {
assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
callThrough(Main.class, "testMixedFramesOnStackStage2");
- // We have just returned through the JIT-compiled JNI stub, so it must still
- // be compiled (though not necessarily with the entrypoint pointing to it).
- assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
// Though the callThrough() is on the stack, that frame is using the GenericJNI
// and does not prevent the collection of the JNI stub.
testStubCanBeCollected();
@@ -94,10 +91,6 @@ public class Main {
}
public static void testStubCanBeCollected() {
- assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
- doJitGcsUntilFullJitGcIsScheduled();
- assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
- assertTrue(hasJitCompiledCode(Main.class, "callThrough"));
jitGc(); // JIT GC without callThrough() on the stack should collect the callThrough() stub.
assertFalse(hasJitCompiledEntrypoint(Main.class, "callThrough"));
assertFalse(hasJitCompiledCode(Main.class, "callThrough"));
diff --git a/test/855-native/expected-stderr.txt b/test/855-native/expected-stderr.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/test/855-native/expected-stderr.txt
diff --git a/test/855-native/expected-stdout.txt b/test/855-native/expected-stdout.txt
new file mode 100644
index 0000000000..6a5618ebc6
--- /dev/null
+++ b/test/855-native/expected-stdout.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/855-native/info.txt b/test/855-native/info.txt
new file mode 100644
index 0000000000..b1bf24e925
--- /dev/null
+++ b/test/855-native/info.txt
@@ -0,0 +1,2 @@
+Regression test for b/342077744: handle the case we may running generic JNI for
+a method but its entrypoint is a shared boot image JNI stub.
diff --git a/test/855-native/run.py b/test/855-native/run.py
new file mode 100644
index 0000000000..b02a3f962c
--- /dev/null
+++ b/test/855-native/run.py
@@ -0,0 +1,20 @@
+#!/bin/bash
+#
+# Copyright 2024 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.
+
+# Pass no prebuild to not have an oat file for the test. If there is an oat
+# file, the runtime doesn't hit the crash.
+def run(ctx, args):
+ ctx.default_run(args, prebuild=False)
diff --git a/test/855-native/src-art/Main.java b/test/855-native/src-art/Main.java
new file mode 100644
index 0000000000..2ac83e5c36
--- /dev/null
+++ b/test/855-native/src-art/Main.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 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 dalvik.system.VMDebug;
+
+public class Main {
+
+ private static final String TEMP_FILE_NAME_PREFIX = "test";
+ private static final String TEMP_FILE_NAME_SUFFIX = ".trace";
+
+ 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);
+ }
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+
+ // In case we already run in tracing mode, disable it.
+ if (VMDebug.getMethodTracingMode() != 0) {
+ VMDebug.stopMethodTracing();
+ }
+
+ File tempFile = createTempFile();
+
+ // Start method tracing so that native methods get the generic JNI stub.
+ VMDebug.startMethodTracing(tempFile.getPath(), 0, 0, false, 0);
+
+ // We need the caller of `throwsException` to be nterp or compiled, because the clinit check is
+ // executed within the generic JNI stub. The switch interpreter does a clinit check before the
+ // invoke, and that doesn't trigger the bug.
+ Main.ensureJitCompiled(Runner.class, "run");
+
+ // We want the `Test` class to be loaded after `startMethodTracing`. So we call it through
+ // `Runner` to avoid the verification of the `Main` class preload `Test`.
+ Runner.run();
+ }
+
+ public static native void ensureJitCompiled(Class<?> cls, String method);
+}
+
+class Runner {
+ public static void run() {
+ try {
+ // Will call through the generic JNI stub and when returning from the native code will need
+ // to walk the stack to throw the exception. We used to crash when walking the stack because
+ // we did not expect to have a generic JNI PC with an entrypoint from a shared boot image
+ // JNI stub.
+ Test.throwsException();
+ } catch (Test e) {
+ if (!Test.ranInitializer) {
+ throw new Error("Expected Test.ranInitializer to be true");
+ }
+ return;
+ }
+ throw new Error("Expected exception");
+ }
+}
+
+class Test extends Exception {
+ static {
+ ranInitializer = true;
+ // Will update the entrypoint of `Test.throwsException` to point to a JNI stub from the boot
+ // image.
+ VMDebug.stopMethodTracing();
+ }
+
+ static boolean ranInitializer;
+ static native void throwsException() throws Test;
+}
diff --git a/test/855-native/throws_exception.cc b/test/855-native/throws_exception.cc
new file mode 100644
index 0000000000..6cf5c5a3dc
--- /dev/null
+++ b/test/855-native/throws_exception.cc
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+#include "jni.h"
+
+namespace {
+
+extern "C" JNIEXPORT void JNICALL Java_Test_throwsException(
+ JNIEnv* env, jclass exClass) {
+ env->ThrowNew(exClass, "In native");
+}
+
+} // namespace
diff --git a/test/856-clone/expected-stderr.txt b/test/856-clone/expected-stderr.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/test/856-clone/expected-stderr.txt
diff --git a/test/856-clone/expected-stdout.txt b/test/856-clone/expected-stdout.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/test/856-clone/expected-stdout.txt
diff --git a/test/856-clone/info.txt b/test/856-clone/info.txt
new file mode 100644
index 0000000000..c94aab511f
--- /dev/null
+++ b/test/856-clone/info.txt
@@ -0,0 +1,2 @@
+Regression test for calling `Object.clone` from within an default interface
+method.
diff --git a/test/856-clone/src/Main.java b/test/856-clone/src/Main.java
new file mode 100644
index 0000000000..278cfd9542
--- /dev/null
+++ b/test/856-clone/src/Main.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+interface I {
+ default String[] myClone(String[] strings) {
+ return strings.clone();
+ }
+}
+
+class A implements I {}
+
+public class Main {
+ public static void main(String[] args) {
+ new A().myClone(args);
+ }
+}
diff --git a/test/989-method-trace-throw/expected-stdout.txt b/test/989-method-trace-throw/expected-stdout.txt
index 0911bc35e8..6bd7357699 100644
--- a/test/989-method-trace-throw/expected-stdout.txt
+++ b/test/989-method-trace-throw/expected-stdout.txt
@@ -10,6 +10,11 @@ Received expected error for test[class art.Test989$NormalTracer, class art.Test9
Normal: Entering public static native void art.Test989.throwANative()
Normal: Leaving public static native void art.Test989.throwANative() returned <exception>
Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A
+Normal: Entering public static void art.Test989.throwNativeException()
+Normal: Entering public static native void art.Test989.doThrowNative()
+Normal: Leaving public static native void art.Test989.doThrowNative() returned <exception>
+Normal: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwNativeExceptionClass] - java.lang.Error: Error
Normal: Entering public static java.lang.Object art.Test989.returnValue()
Normal: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(0)
returnValue returned: TestObject(0)
@@ -57,6 +62,9 @@ Received expected error for test[class art.Test989$ThrowEnterTracer, class art.T
ThrowEnter: Entering public static native void art.Test989.throwANative()
ThrowEnter: Leaving public static native void art.Test989.throwANative() returned <exception>
Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.throwANative()
+ThrowEnter: Entering public static void art.Test989.throwNativeException()
+ThrowEnter: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwNativeExceptionClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.throwNativeException()
ThrowEnter: Entering public static java.lang.Object art.Test989.returnValue()
ThrowEnter: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception>
Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while entering public static java.lang.Object art.Test989.returnValue()
@@ -96,6 +104,11 @@ Received expected error for test[class art.Test989$ThrowExitTracer, class art.Te
ThrowExit: Entering public static native void art.Test989.throwANative()
ThrowExit: Leaving public static native void art.Test989.throwANative() returned <exception>
Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.throwANative() returned <exception>
+ThrowExit: Entering public static void art.Test989.throwNativeException()
+ThrowExit: Entering public static native void art.Test989.doThrowNative()
+ThrowExit: Leaving public static native void art.Test989.doThrowNative() returned <exception>
+ThrowExit: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwNativeExceptionClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.throwNativeException() returned <exception>
ThrowExit: Entering public static java.lang.Object art.Test989.returnValue()
ThrowExit: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(7)
Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned TestObject(7)
@@ -137,6 +150,9 @@ Received expected error for test[class art.Test989$ThrowBothTracer, class art.Te
ThrowBoth: Entering public static native void art.Test989.throwANative()
ThrowBoth: Leaving public static native void art.Test989.throwANative() returned <exception>
Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.throwANative() returned <exception>
+ThrowBoth: Entering public static void art.Test989.throwNativeException()
+ThrowBoth: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwNativeExceptionClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.throwNativeException() returned <exception>
ThrowBoth: Entering public static java.lang.Object art.Test989.returnValue()
ThrowBoth: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception>
Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnValueClass] - art.Test989$ErrorC: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned <exception>
@@ -168,6 +184,7 @@ Received no exception as expected for test[class art.Test989$ForceGCTracer, clas
Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$doNothingNativeClass].
Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwAClass] - art.Test989$ErrorA: Throwing Error A
Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A
+Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwNativeExceptionClass] - java.lang.Error: Error
returnValue returned: TestObject(14)
Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnValueClass].
returnValueNative returned: TestObject(15)
@@ -185,4 +202,209 @@ returnDouble returned: 3.14159628
Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleClass].
returnDoubleNative returned: 3.14159628
Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleNativeClass].
+Finished - without non-standard exits!
+Normal: Entering public static void art.Test989.doNothing()
+Normal: Leaving public static void art.Test989.doNothing() returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$doNothingClass].
+Normal: Entering public static native void art.Test989.doNothingNative()
+Normal: Leaving public static native void art.Test989.doNothingNative() returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$doNothingNativeClass].
+Normal: Entering public static void art.Test989.throwA()
+Normal: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwAClass] - art.Test989$ErrorA: Throwing Error A
+Normal: Entering public static native void art.Test989.throwANative()
+Normal: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A
+Normal: Entering public static void art.Test989.throwNativeException()
+Normal: Entering public static native void art.Test989.doThrowNative()
+Normal: Leaving public static native void art.Test989.doThrowNative() returned <exception>
+Normal: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$NormalTracer, class art.Test989$throwNativeExceptionClass] - java.lang.Error: Error
+Normal: Entering public static java.lang.Object art.Test989.returnValue()
+Normal: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(19)
+returnValue returned: TestObject(19)
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnValueClass].
+Normal: Entering public static native java.lang.Object art.Test989.returnValueNative()
+Normal: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(20)
+returnValueNative returned: TestObject(20)
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnValueNativeClass].
+Normal: Entering public static void art.Test989.acceptValue(java.lang.Object)
+Recieved TestObject(21)
+Normal: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$acceptValueClass].
+Normal: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+Recieved TestObject(22)
+Normal: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$acceptValueNativeClass].
+Normal: Entering public static void art.Test989.tryCatchExit()
+Normal: Leaving public static void art.Test989.tryCatchExit() returned null
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$tryCatchExitClass].
+Normal: Entering public static float art.Test989.returnFloat()
+Normal: Leaving public static float art.Test989.returnFloat() returned 1.618
+returnFloat returned: 1.618
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnFloatClass].
+Normal: Entering public static native float art.Test989.returnFloatNative()
+Normal: Leaving public static native float art.Test989.returnFloatNative() returned 1.618
+returnFloatNative returned: 1.618
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnFloatNativeClass].
+Normal: Entering public static double art.Test989.returnDouble()
+Normal: Leaving public static double art.Test989.returnDouble() returned 3.14159628
+returnDouble returned: 3.14159628
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnDoubleClass].
+Normal: Entering public static native double art.Test989.returnDoubleNative()
+Normal: Leaving public static native double art.Test989.returnDoubleNative() returned 3.14159628
+returnDoubleNative returned: 3.14159628
+Received no exception as expected for test[class art.Test989$NormalTracer, class art.Test989$returnDoubleNativeClass].
+ThrowEnter: Entering public static void art.Test989.doNothing()
+ThrowEnter: Leaving public static void art.Test989.doNothing() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$doNothingClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.doNothing()
+ThrowEnter: Entering public static native void art.Test989.doNothingNative()
+ThrowEnter: Leaving public static native void art.Test989.doNothingNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.doNothingNative()
+ThrowEnter: Entering public static void art.Test989.throwA()
+ThrowEnter: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwAClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.throwA()
+ThrowEnter: Entering public static native void art.Test989.throwANative()
+ThrowEnter: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.throwANative()
+ThrowEnter: Entering public static void art.Test989.throwNativeException()
+ThrowEnter: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$throwNativeExceptionClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.throwNativeException()
+ThrowEnter: Entering public static java.lang.Object art.Test989.returnValue()
+ThrowEnter: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while entering public static java.lang.Object art.Test989.returnValue()
+ThrowEnter: Entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowEnter: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowEnter: Entering public static void art.Test989.acceptValue(java.lang.Object)
+ThrowEnter: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.acceptValue(java.lang.Object)
+ThrowEnter: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+ThrowEnter: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+ThrowEnter: Entering public static void art.Test989.tryCatchExit()
+ThrowEnter: Leaving public static void art.Test989.tryCatchExit() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorB: Throwing error while entering public static void art.Test989.tryCatchExit()
+ThrowEnter: Entering public static float art.Test989.returnFloat()
+ThrowEnter: Leaving public static float art.Test989.returnFloat() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorB: Throwing error while entering public static float art.Test989.returnFloat()
+ThrowEnter: Entering public static native float art.Test989.returnFloatNative()
+ThrowEnter: Leaving public static native float art.Test989.returnFloatNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native float art.Test989.returnFloatNative()
+ThrowEnter: Entering public static double art.Test989.returnDouble()
+ThrowEnter: Leaving public static double art.Test989.returnDouble() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorB: Throwing error while entering public static double art.Test989.returnDouble()
+ThrowEnter: Entering public static native double art.Test989.returnDoubleNative()
+ThrowEnter: Leaving public static native double art.Test989.returnDoubleNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowEnterTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorB: Throwing error while entering public static native double art.Test989.returnDoubleNative()
+ThrowExit: Entering public static void art.Test989.doNothing()
+ThrowExit: Leaving public static void art.Test989.doNothing() returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$doNothingClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.doNothing() returned null
+ThrowExit: Entering public static native void art.Test989.doNothingNative()
+ThrowExit: Leaving public static native void art.Test989.doNothingNative() returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.doNothingNative() returned null
+ThrowExit: Entering public static void art.Test989.throwA()
+ThrowExit: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwAClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.throwA() returned <exception>
+ThrowExit: Entering public static native void art.Test989.throwANative()
+ThrowExit: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.throwANative() returned <exception>
+ThrowExit: Entering public static void art.Test989.throwNativeException()
+ThrowExit: Entering public static native void art.Test989.doThrowNative()
+ThrowExit: Leaving public static native void art.Test989.doThrowNative() returned <exception>
+ThrowExit: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$throwNativeExceptionClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.throwNativeException() returned <exception>
+ThrowExit: Entering public static java.lang.Object art.Test989.returnValue()
+ThrowExit: Leaving public static java.lang.Object art.Test989.returnValue() returned TestObject(26)
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnValueClass] - art.Test989$ErrorB: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned TestObject(26)
+ThrowExit: Entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowExit: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(27)
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native java.lang.Object art.Test989.returnValueNative() returned TestObject(27)
+ThrowExit: Entering public static void art.Test989.acceptValue(java.lang.Object)
+Recieved TestObject(28)
+ThrowExit: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.acceptValue(java.lang.Object) returned null
+ThrowExit: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+Recieved TestObject(29)
+ThrowExit: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native void art.Test989.acceptValueNative(java.lang.Object) returned null
+ThrowExit: Entering public static void art.Test989.tryCatchExit()
+ThrowExit: Leaving public static void art.Test989.tryCatchExit() returned null
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorB: Throwing error while exit public static void art.Test989.tryCatchExit() returned null
+ThrowExit: Entering public static float art.Test989.returnFloat()
+ThrowExit: Leaving public static float art.Test989.returnFloat() returned 1.618
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorB: Throwing error while exit public static float art.Test989.returnFloat() returned 1.618
+ThrowExit: Entering public static native float art.Test989.returnFloatNative()
+ThrowExit: Leaving public static native float art.Test989.returnFloatNative() returned 1.618
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native float art.Test989.returnFloatNative() returned 1.618
+ThrowExit: Entering public static double art.Test989.returnDouble()
+ThrowExit: Leaving public static double art.Test989.returnDouble() returned 3.14159628
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorB: Throwing error while exit public static double art.Test989.returnDouble() returned 3.14159628
+ThrowExit: Entering public static native double art.Test989.returnDoubleNative()
+ThrowExit: Leaving public static native double art.Test989.returnDoubleNative() returned 3.14159628
+Received expected error for test[class art.Test989$ThrowExitTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorB: Throwing error while exit public static native double art.Test989.returnDoubleNative() returned 3.14159628
+ThrowBoth: Entering public static void art.Test989.doNothing()
+ThrowBoth: Leaving public static void art.Test989.doNothing() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$doNothingClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.doNothing() returned <exception>
+ThrowBoth: Entering public static native void art.Test989.doNothingNative()
+ThrowBoth: Leaving public static native void art.Test989.doNothingNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$doNothingNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.doNothingNative() returned <exception>
+ThrowBoth: Entering public static void art.Test989.throwA()
+ThrowBoth: Leaving public static void art.Test989.throwA() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwAClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.throwA() returned <exception>
+ThrowBoth: Entering public static native void art.Test989.throwANative()
+ThrowBoth: Leaving public static native void art.Test989.throwANative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.throwANative() returned <exception>
+ThrowBoth: Entering public static void art.Test989.throwNativeException()
+ThrowBoth: Leaving public static void art.Test989.throwNativeException() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$throwNativeExceptionClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.throwNativeException() returned <exception>
+ThrowBoth: Entering public static java.lang.Object art.Test989.returnValue()
+ThrowBoth: Leaving public static java.lang.Object art.Test989.returnValue() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnValueClass] - art.Test989$ErrorC: Throwing error while exit public static java.lang.Object art.Test989.returnValue() returned <exception>
+ThrowBoth: Entering public static native java.lang.Object art.Test989.returnValueNative()
+ThrowBoth: Leaving public static native java.lang.Object art.Test989.returnValueNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnValueNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native java.lang.Object art.Test989.returnValueNative() returned <exception>
+ThrowBoth: Entering public static void art.Test989.acceptValue(java.lang.Object)
+ThrowBoth: Leaving public static void art.Test989.acceptValue(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$acceptValueClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.acceptValue(java.lang.Object) returned <exception>
+ThrowBoth: Entering public static native void art.Test989.acceptValueNative(java.lang.Object)
+ThrowBoth: Leaving public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$acceptValueNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native void art.Test989.acceptValueNative(java.lang.Object) returned <exception>
+ThrowBoth: Entering public static void art.Test989.tryCatchExit()
+ThrowBoth: Leaving public static void art.Test989.tryCatchExit() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$tryCatchExitClass] - art.Test989$ErrorC: Throwing error while exit public static void art.Test989.tryCatchExit() returned <exception>
+ThrowBoth: Entering public static float art.Test989.returnFloat()
+ThrowBoth: Leaving public static float art.Test989.returnFloat() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnFloatClass] - art.Test989$ErrorC: Throwing error while exit public static float art.Test989.returnFloat() returned <exception>
+ThrowBoth: Entering public static native float art.Test989.returnFloatNative()
+ThrowBoth: Leaving public static native float art.Test989.returnFloatNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnFloatNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native float art.Test989.returnFloatNative() returned <exception>
+ThrowBoth: Entering public static double art.Test989.returnDouble()
+ThrowBoth: Leaving public static double art.Test989.returnDouble() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnDoubleClass] - art.Test989$ErrorC: Throwing error while exit public static double art.Test989.returnDouble() returned <exception>
+ThrowBoth: Entering public static native double art.Test989.returnDoubleNative()
+ThrowBoth: Leaving public static native double art.Test989.returnDoubleNative() returned <exception>
+Received expected error for test[class art.Test989$ThrowBothTracer, class art.Test989$returnDoubleNativeClass] - art.Test989$ErrorC: Throwing error while exit public static native double art.Test989.returnDoubleNative() returned <exception>
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$doNothingClass].
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$doNothingNativeClass].
+Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwAClass] - art.Test989$ErrorA: Throwing Error A
+Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwANativeClass] - art.Test989$ErrorA: Throwing Error A
+Received expected error for test[class art.Test989$ForceGCTracer, class art.Test989$throwNativeExceptionClass] - java.lang.Error: Error
+returnValue returned: TestObject(33)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnValueClass].
+returnValueNative returned: TestObject(34)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnValueNativeClass].
+Recieved TestObject(35)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$acceptValueClass].
+Recieved TestObject(36)
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$acceptValueNativeClass].
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$tryCatchExitClass].
+returnFloat returned: 1.618
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnFloatClass].
+returnFloatNative returned: 1.618
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnFloatNativeClass].
+returnDouble returned: 3.14159628
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleClass].
+returnDoubleNative returned: 3.14159628
+Received no exception as expected for test[class art.Test989$ForceGCTracer, class art.Test989$returnDoubleNativeClass].
Finished!
diff --git a/test/989-method-trace-throw/method_trace.cc b/test/989-method-trace-throw/method_trace.cc
index edfff907a0..1f62aa7607 100644
--- a/test/989-method-trace-throw/method_trace.cc
+++ b/test/989-method-trace-throw/method_trace.cc
@@ -63,6 +63,12 @@ extern "C" JNIEXPORT void JNICALL Java_art_Test989_throwANative(JNIEnv* env,
env->CallStaticVoidMethod(klass, targetMethod);
}
+extern "C" JNIEXPORT void JNICALL Java_art_Test989_doThrowNative(JNIEnv* env,
+ [[maybe_unused]] jclass klass) {
+ jclass exception_cls = env->FindClass("java/lang/Error");
+ env->ThrowNew(exception_cls, "Error");
+}
+
extern "C" JNIEXPORT void JNICALL Java_art_Test989_acceptValueNative(JNIEnv* env,
jclass klass,
jobject arg) {
diff --git a/test/989-method-trace-throw/src/art/Test989.java b/test/989-method-trace-throw/src/art/Test989.java
index f18d5ef3d6..fec09e0934 100644
--- a/test/989-method-trace-throw/src/art/Test989.java
+++ b/test/989-method-trace-throw/src/art/Test989.java
@@ -37,6 +37,7 @@ public class Test989 {
testMethods.add(Test989.class.getDeclaredMethod("doNothingNative"));
testMethods.add(Test989.class.getDeclaredMethod("throwA"));
testMethods.add(Test989.class.getDeclaredMethod("throwANative"));
+ testMethods.add(Test989.class.getDeclaredMethod("throwNativeException"));
testMethods.add(Test989.class.getDeclaredMethod("returnFloat"));
testMethods.add(Test989.class.getDeclaredMethod("returnFloatNative"));
testMethods.add(Test989.class.getDeclaredMethod("returnDouble"));
@@ -46,6 +47,7 @@ public class Test989 {
testMethods.add(Test989.class.getDeclaredMethod("acceptValue", Object.class));
testMethods.add(Test989.class.getDeclaredMethod("acceptValueNative", Object.class));
testMethods.add(Test989.class.getDeclaredMethod("tryCatchExit"));
+ testMethods.add(Test989.class.getDeclaredMethod("doThrowNative"));
} catch (Exception e) {
throw new Error("Bad static!", e);
}
@@ -244,35 +246,50 @@ public class Test989 {
}
public static void run() throws Exception {
- MyRunnable[] testCases = new MyRunnable[] {
- new doNothingClass(),
- new doNothingNativeClass(),
- new throwAClass(),
- new throwANativeClass(),
- new returnValueClass(),
- new returnValueNativeClass(),
- new acceptValueClass(),
- new acceptValueNativeClass(),
- new tryCatchExitClass(),
- new returnFloatClass(),
- new returnFloatNativeClass(),
- new returnDoubleClass(),
- new returnDoubleNativeClass(),
- };
- MethodTracer[] tracers = new MethodTracer[] {
- new NormalTracer(),
- new ThrowEnterTracer(),
- new ThrowExitTracer(),
- new ThrowBothTracer(),
- new ForceGCTracer(),
- };
-
- setupTracing();
- for (MethodTracer t : tracers) {
- for (MyRunnable r : testCases) {
- doTest(t, r);
+ MyRunnable[] testCases = new MyRunnable[] {
+ new doNothingClass(),
+ new doNothingNativeClass(),
+ new throwAClass(),
+ new throwANativeClass(),
+ new throwNativeExceptionClass(),
+ new returnValueClass(),
+ new returnValueNativeClass(),
+ new acceptValueClass(),
+ new acceptValueNativeClass(),
+ new tryCatchExitClass(),
+ new returnFloatClass(),
+ new returnFloatNativeClass(),
+ new returnDoubleClass(),
+ new returnDoubleNativeClass(),
+ };
+ MethodTracer[] tracers = new MethodTracer[] {
+ new NormalTracer(),
+ new ThrowEnterTracer(),
+ new ThrowExitTracer(),
+ new ThrowBothTracer(),
+ new ForceGCTracer(),
+ };
+
+ setupTracing();
+ for (MethodTracer t : tracers) {
+ for (MyRunnable r : testCases) {
+ doTest(t, r);
+ }
+ }
+
+ maybeDisableTracing();
+ System.out.println("Finished - without non-standard exits!");
+ Trace.disableTracing(Thread.currentThread());
+
+ // Enabling frame pop events force a different path and deoptimize more often. So redo the
+ // tests by enabling frame pop events.
+ Trace.enableFramePopEvents();
+ setupTracing();
+ for (MethodTracer t : tracers) {
+ for (MyRunnable r : testCases) {
+ doTest(t, r);
+ }
}
- }
maybeDisableTracing();
System.out.println("Finished!");
@@ -299,6 +316,17 @@ public class Test989 {
}
}
+ private static final class throwNativeExceptionClass implements MyRunnable {
+ public void run() {
+ throwNativeException();
+ }
+
+ @Override
+ public Class<?> expectedThrow() {
+ return Error.class;
+ }
+ }
+
private static final class tryCatchExitClass implements MyRunnable {
public void run() {
tryCatchExit();
@@ -419,6 +447,10 @@ public class Test989 {
throw new ErrorA("Throwing Error A");
}
+ public static void throwNativeException() {
+ doThrowNative();
+ }
+
static final class TestObject {
private int idx;
public TestObject(int v) {
@@ -464,4 +496,5 @@ public class Test989 {
public static native void throwANative();
public static native float returnFloatNative();
public static native double returnDoubleNative();
+ public static native void doThrowNative();
}
diff --git a/test/Android.bp b/test/Android.bp
index 611f7ec9b0..988dd3e5d7 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -260,18 +260,11 @@ art_cc_defaults {
header_libs: [
"libnativeloader-headers",
- ],
- shared_libs: [
- // `libart(d)` (`art/runtime/jni/java_vm_ext.cc`) and `libnativehelper`
- // (`libnativehelper/JniInvocation.c`) define symbols with the same name
- // (e.g. `JNI_GetDefaultJavaVMInitArgs`).
- // `JavaVmExtTest#*` tests require `libart(d)` implementation of these symbols.
- // At the moment `libart(d)` is linked statically (through `libart(d)-gtest`)
- // and these symbols are correctly resolved to `libart(d)`.
- // If `libnativehelper` and `libart(d)` are both linked dynamically,
- // `libart(d)` must be specified in shared_libs list before `libnativehelper`,
- // so that its symbols have precedence over `libnativehelper`.
- "libnativehelper",
+ // The full libnativehelper library has wrappers for the JNI APIs (e.g.
+ // `JNI_CreateJavaVM`) that are implemented in libart(d), so it
+ // shouldn't be linked into the tests where libart(d) is linked
+ // statically.
+ "libnativehelper_header_only",
],
target: {
android: {
@@ -956,6 +949,7 @@ cc_defaults {
"720-thread-priority/thread_priority.cc",
"800-smali/jni.cc",
"817-hiddenapi/test_native.cc",
+ "855-native/throws_exception.cc",
"909-attach-agent/disallow_debugging.cc",
"993-breakpoints-non-debuggable/native_attach_agent.cc",
"1001-app-image-regions/app_image_regions.cc",
diff --git a/test/README.atest.md b/test/README.atest.md
index c23a7d6624..9f88ee2f3f 100644
--- a/test/README.atest.md
+++ b/test/README.atest.md
@@ -142,6 +142,20 @@ This sequence:
5. cleans up the environment (deactivates the APEXes and removes the `chroot`
environment).
+### Running ART gtests on host
+
+You first need to build the boot classpath and boot image on host:
+
+```bash
+m art-host-tests
+```
+
+Then you can use `atest --host` to run host gtests, e.g:
+
+```bash
+atest --host art_runtime_tests
+```
+
## Test Mapping
ART Testing supports the execution of tests via [Test
diff --git a/test/jvmti-common/Trace.java b/test/jvmti-common/Trace.java
index 8999bb1368..3df1737c91 100644
--- a/test/jvmti-common/Trace.java
+++ b/test/jvmti-common/Trace.java
@@ -28,6 +28,7 @@ public class Trace {
Method singleStep,
Thread thr);
public static native void disableTracing(Thread thr);
+ public static native void nativeEnableFramePopEvents();
public static void enableFieldTracing(Class<?> methodClass,
Method fieldAccess,
@@ -43,6 +44,10 @@ public class Trace {
enableTracing(methodClass, entryMethod, exitMethod, null, null, null, thr);
}
+ public static void enableFramePopEvents() {
+ nativeEnableFramePopEvents();
+ }
+
public static void enableSingleStepTracing(Class<?> methodClass,
Method singleStep,
Thread thr) {
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 4e5a19d0eb..dff43c55e8 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1179,6 +1179,7 @@
"847-filled-new-aray",
"848-pattern-match",
"854-image-inlining",
+ "855-native",
"999-redefine-hiddenapi",
"1000-non-moving-space-stress",
"1001-app-image-regions",
diff --git a/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
index 4521b6fa59..91a6276845 100644
--- a/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
@@ -120,7 +120,8 @@ public class CompOsTestUtils {
// We have to have kernel support for a VM.
assumeTrue("Need an actual TestDevice", mDevice instanceof TestDevice);
TestDevice testDevice = (TestDevice) mDevice;
- assumeTrue("Requires VM support", testDevice.supportsMicrodroid());
+ assumeTrue("Requires protected VM support",
+ testDevice.supportsMicrodroid(true /* protectedVm*/));
// And the CompOS APEX must be present.
assumeTrue(mDevice.doesFileExist("/apex/com.android.compos/"));
diff --git a/test/ti-agent/trace_helper.cc b/test/ti-agent/trace_helper.cc
index 58958cb268..e34b5bdce9 100644
--- a/test/ti-agent/trace_helper.cc
+++ b/test/ti-agent/trace_helper.cc
@@ -466,6 +466,15 @@ extern "C" JNIEXPORT void JNICALL Java_art_Trace_watchFieldAccess(
env->DeleteLocalRef(klass);
}
+extern "C" JNIEXPORT void JNICALL Java_art_Trace_nativeEnableFramePopEvents(JNIEnv* env) {
+ jvmtiCapabilities caps;
+ memset(&caps, 0, sizeof(caps));
+ caps.can_pop_frame = 1;
+ if (JvmtiErrorToException(env, jvmti_env, jvmti_env->AddCapabilities(&caps))) {
+ return;
+ }
+}
+
extern "C" JNIEXPORT void JNICALL Java_art_Trace_enableTracing2(
JNIEnv* env,
[[maybe_unused]] jclass trace,
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index cedf354882..ed5aa45f6b 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -273,7 +273,6 @@ art_gtest_module_names = sorted(art_gtest_user_module_names + art_gtest_eng_only
# removing them from this set (in order to promote them to
# presubmits).
art_gtest_postsubmit_only_module_names = [
- "libnativebridge-tests",
]
# ART gtests supported in MTS that do not need root access to the device.
@@ -698,6 +697,13 @@ class Generator:
[],
mainline_presubmit_apex_suffix)
+ # ART mainline presubmits tests without APEX suffix
+ art_mainline_presubmit_tests_dict = \
+ gen_tests_dict(mainline_presubmit_tests,
+ failing_tests_excluded_from_mainline_presubmits,
+ [],
+ "")
+
# Android Virtualization Framework presubmits
avf_presubmit_tests = ["ComposHostTestCases"]
avf_presubmit_tests_dict = gen_tests_dict(avf_presubmit_tests,
@@ -731,6 +737,7 @@ class Generator:
(test_group_name, test_group_dict)
for (test_group_name, test_group_dict)
in [
+ ("art-mainline-presubmit", art_mainline_presubmit_tests_dict),
("mainline-presubmit", mainline_presubmit_tests_dict),
("presubmit", presubmit_tests_dict),
("hwasan-presubmit", hwasan_presubmit_tests_dict),
@@ -1054,6 +1061,8 @@ class Generator:
f" of {len(run_tests)} ({expected_succeeding_tests_percentage}%):")
for (num_tests, test_kind, tests_percentage, test_group_name) in [
(num_mainline_presubmit_run_tests, "ART run-tests", mainline_presubmit_run_tests_percentage,
+ "art-mainline-presubmit"),
+ (num_mainline_presubmit_run_tests, "ART run-tests", mainline_presubmit_run_tests_percentage,
"mainline-presubmit"),
(num_presubmit_run_tests, "ART run-tests", presubmit_run_tests_percentage, "presubmit"),
(num_postsubmit_run_tests, "ART run-tests", postsubmit_run_tests_percentage, "postsubmit"),
diff --git a/tools/ahat/Android.bp b/tools/ahat/Android.bp
index 6476120879..540ce6670c 100644
--- a/tools/ahat/Android.bp
+++ b/tools/ahat/Android.bp
@@ -34,6 +34,9 @@ java_binary_host {
// Use a relaxed version to allow distribution against older runtimes.
java_version: "11",
javacflags: ["-Xdoclint:all/protected"],
+ static_libs: [
+ "guava",
+ ],
}
// --- ahat-test-dump.jar --------------
@@ -53,5 +56,6 @@ java_test_helper_library {
host_supported: true,
device_supported: false,
name: "ahat-ri-test-dump",
+ java_version: "17",
srcs: ["src/ri-test-dump/**/*.java"],
}
diff --git a/tools/ahat/src/main/com/android/ahat/BitmapHandler.java b/tools/ahat/src/main/com/android/ahat/BitmapHandler.java
index fb81ad9dbb..7cdbcdf044 100644
--- a/tools/ahat/src/main/com/android/ahat/BitmapHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/BitmapHandler.java
@@ -18,6 +18,7 @@ package com.android.ahat;
import com.android.ahat.heapdump.AhatInstance;
import com.android.ahat.heapdump.AhatSnapshot;
+import com.android.ahat.heapdump.AhatBitmapInstance;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import java.awt.image.BufferedImage;
@@ -61,12 +62,12 @@ class BitmapHandler implements HttpHandler {
Query query = new Query(exchange.getRequestURI());
long id = query.getLong("id", 0);
AhatInstance inst = mSnapshot.findInstance(id);
- if (inst == null) {
+ if (inst == null || !inst.isBitmapInstance()) {
handle_404(exchange);
return;
}
- AhatInstance.Bitmap bitmap = inst.asBitmap();
+ AhatBitmapInstance.Bitmap bitmap = inst.asBitmapInstance().getBitmap();
if (bitmap == null) {
handle_404(exchange);
return;
diff --git a/tools/ahat/src/main/com/android/ahat/OverviewHandler.java b/tools/ahat/src/main/com/android/ahat/OverviewHandler.java
index c6f4a54080..90d44c3b47 100644
--- a/tools/ahat/src/main/com/android/ahat/OverviewHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/OverviewHandler.java
@@ -17,11 +17,14 @@
package com.android.ahat;
import com.android.ahat.heapdump.AhatHeap;
+import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatBitmapInstance;
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.Reachability;
import com.android.ahat.heapdump.Size;
import java.io.File;
import java.io.IOException;
+import java.util.List;
class OverviewHandler implements AhatHandler {
@@ -57,6 +60,9 @@ class OverviewHandler implements AhatHandler {
doc.section("Bytes Retained by Heap");
printHeapSizes(doc);
+
+ doc.section("Heap Analysis Result");
+ printDuplicateBitmaps(doc);
}
private void printHeapSizes(Doc doc) {
@@ -75,5 +81,30 @@ class OverviewHandler implements AhatHandler {
SizeTable.row(doc, DocString.text("Total"), totalSize, totalBase);
SizeTable.end(doc);
}
+
+ private void printDuplicateBitmaps(Doc doc) {
+ List<List<AhatBitmapInstance>> duplicates = mSnapshot.findDuplicateBitmaps();
+ if (duplicates != null && duplicates.size() > 0) {
+ SizeTable.table(doc, mSnapshot.isDiffed(),
+ new Column("Heap"),
+ new Column("Duplicated Bitmaps"));
+ Size totalSize = Size.ZERO;
+ Size totalBase = Size.ZERO;
+ for (List<AhatBitmapInstance> list : duplicates) {
+ for (AhatBitmapInstance inst : list) {
+ AhatInstance base = inst.getBaseline();
+ SizeTable.row(doc, inst.getSize(), base.getSize(),
+ DocString.text(inst.getHeap().getName()),
+ Summarizer.summarize(inst));
+ totalSize = totalSize.plus(inst.getSize());
+ totalBase = totalBase.plus(base.getSize());
+ }
+ }
+ SizeTable.row(doc, totalSize, totalBase,
+ DocString.text("Total"),
+ DocString.text("All duplicated bitmaps"));
+ SizeTable.end(doc);
+ }
+ }
}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatBitmapInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatBitmapInstance.java
new file mode 100644
index 0000000000..982591d1e4
--- /dev/null
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatBitmapInstance.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package com.android.ahat.heapdump;
+
+import java.awt.image.BufferedImage;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.ArrayListMultimap;
+
+/**
+ * A java object that has `android.graphics.Bitmap` as its base class.
+ */
+public class AhatBitmapInstance extends AhatClassInstance {
+
+ private BitmapInfo mBitmapInfo = null;
+
+ AhatBitmapInstance(long id) {
+ super(id);
+ }
+
+ @Override
+ public boolean isBitmapInstance() {
+ return true;
+ }
+
+ @Override
+ public AhatBitmapInstance asBitmapInstance() {
+ return this;
+ }
+
+ /**
+ * Parsed information for bitmap contents dumped in the heapdump
+ */
+ public static class BitmapDumpData {
+ private int count;
+ // See android.graphics.Bitmap.CompressFormat for format values.
+ // -1 means no compression for backward compatibility
+ private int format;
+ private Map<Long, byte[]> buffers;
+ private Set<Long> referenced;
+ private ListMultimap<BitmapInfo, AhatBitmapInstance> instances;
+
+ BitmapDumpData(int count, int format) {
+ this.count = count;
+ this.format = format;
+ this.buffers = new HashMap<Long, byte[]>(count);
+ this.referenced = new HashSet<Long>(count);
+ this.instances = ArrayListMultimap.create();
+ }
+ };
+
+ /**
+ * find the BitmapDumpData that is included in the heap dump
+ *
+ * @param root root of the heap dump
+ * @param instances all the instances from where the bitmap dump data will be excluded
+ * @return true if valid bitmap dump data is found, false if not
+ */
+ public static BitmapDumpData findBitmapDumpData(SuperRoot root, Instances<AhatInstance> instances) {
+ final BitmapDumpData result;
+ AhatClassObj cls = null;
+
+ for (Reference ref : root.getReferences()) {
+ if (ref.ref.isClassObj()) {
+ cls = ref.ref.asClassObj();
+ if (cls.getName().equals("android.graphics.Bitmap")) {
+ break;
+ }
+ }
+ }
+
+ if (cls == null) {
+ return null;
+ }
+
+ Value value = cls.getStaticField("dumpData");
+ if (value == null || !value.isAhatInstance()) {
+ return null;
+ }
+
+ AhatClassInstance inst = value.asAhatInstance().asClassInstance();
+ if (inst == null) {
+ return null;
+ }
+
+ result = toBitmapDumpData(inst);
+ if (result == null) {
+ return null;
+ }
+
+ /* Build the map for all the bitmap instances with its BitmapInfo as key,
+ * the map would be used to identify duplicated bitmaps later. This also
+ * initializes `mBitmapInfo` of each bitmap instance.
+ */
+ for (AhatInstance obj : instances) {
+ AhatBitmapInstance bmp = obj.asBitmapInstance();
+ if (bmp != null) {
+ BitmapInfo info = bmp.getBitmapInfo(result);
+ if (info != null) {
+ result.instances.put(info, bmp);
+ }
+ }
+ }
+
+ /* remove all instances referenced from BitmapDumpData,
+ * these instances shall *not* be counted
+ */
+ instances.removeIf(i -> { return result.referenced.contains(i.getId()); });
+ return result;
+ }
+
+ private static BitmapDumpData toBitmapDumpData(AhatClassInstance inst) {
+ if (!inst.isInstanceOfClass("android.graphics.Bitmap$DumpData")) {
+ return null;
+ }
+
+ int count = inst.getIntField("count", 0);
+ int format = inst.getIntField("format", -1);
+
+ if (count == 0 || format == -1) {
+ return null;
+ }
+
+ BitmapDumpData result = new BitmapDumpData(count, format);
+
+ AhatArrayInstance natives = inst.getArrayField("natives");
+ AhatArrayInstance buffers = inst.getArrayField("buffers");
+ if (natives == null || buffers == null) {
+ return null;
+ }
+
+ result.referenced.add(natives.getId());
+ result.referenced.add(buffers.getId());
+
+ result.buffers = new HashMap<>(result.count);
+ for (int i = 0; i < result.count; i++) {
+ Value nativePtr = natives.getValue(i);
+ Value bufferVal = buffers.getValue(i);
+ if (nativePtr == null || bufferVal == null) {
+ continue;
+ }
+ AhatInstance buffer = bufferVal.asAhatInstance();
+ result.buffers.put(nativePtr.asLong(), buffer.asArrayInstance().asByteArray());
+ result.referenced.add(buffer.getId());
+ }
+ return result;
+ }
+
+ /**
+ * find duplicated bitmap instances
+ *
+ * @param bitmapDumpData parsed bitmap dump data
+ * @return A list of duplicated bitmaps (the same duplication stored in a sub-list)
+ */
+ public static List<List<AhatBitmapInstance>> findDuplicates(BitmapDumpData bitmapDumpData) {
+ if (bitmapDumpData != null) {
+ List<List<AhatBitmapInstance>> result = new ArrayList<>();
+ for (BitmapInfo info : bitmapDumpData.instances.keySet()) {
+ List<AhatBitmapInstance> list = bitmapDumpData.instances.get(info);
+ if (list != null && list.size() > 1) {
+ result.add(list);
+ }
+ }
+ // sort by size in descend order
+ if (result.size() > 1) {
+ result.sort((List<AhatBitmapInstance> l1, List<AhatBitmapInstance> l2) -> {
+ return l2.get(0).getSize().compareTo(l1.get(0).getSize());
+ });
+ }
+ return result;
+ }
+ return null;
+ }
+
+ private static class BitmapInfo {
+ private final int width;
+ private final int height;
+ private final int format;
+ private final byte[] buffer;
+ private final int bufferHash;
+
+ public BitmapInfo(int width, int height, int format, byte[] buffer) {
+ this.width = width;
+ this.height = height;
+ this.format = format;
+ this.buffer = buffer;
+ bufferHash = Arrays.hashCode(buffer);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(width, height, format, bufferHash);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (!(o instanceof BitmapInfo)) {
+ return false;
+ }
+ BitmapInfo other = (BitmapInfo)o;
+ return (this.width == other.width)
+ && (this.height == other.height)
+ && (this.format == other.format)
+ && (this.bufferHash == other.bufferHash);
+ }
+ }
+
+ /**
+ * Return bitmap info for this object, or null if no appropriate bitmap
+ * info is available.
+ */
+ private BitmapInfo getBitmapInfo(BitmapDumpData bitmapDumpData) {
+ if (mBitmapInfo != null) {
+ return mBitmapInfo;
+ }
+
+ if (!isInstanceOfClass("android.graphics.Bitmap")) {
+ return null;
+ }
+
+ Integer width = getIntField("mWidth", null);
+ if (width == null) {
+ return null;
+ }
+
+ Integer height = getIntField("mHeight", null);
+ if (height == null) {
+ return null;
+ }
+
+ byte[] buffer = getByteArrayField("mBuffer");
+ if (buffer != null) {
+ if (buffer.length < 4 * height * width) {
+ return null;
+ }
+ mBitmapInfo = new BitmapInfo(width, height, -1, buffer);
+ return mBitmapInfo;
+ }
+
+ long nativePtr = getLongField("mNativePtr", -1l);
+ if (nativePtr == -1) {
+ return null;
+ }
+
+ if (bitmapDumpData == null || bitmapDumpData.count == 0) {
+ return null;
+ }
+
+ if (!bitmapDumpData.buffers.containsKey(nativePtr)) {
+ return null;
+ }
+
+ buffer = bitmapDumpData.buffers.get(nativePtr);
+ if (buffer == null) {
+ return null;
+ }
+
+ mBitmapInfo = new BitmapInfo(width, height, bitmapDumpData.format, buffer);
+ return mBitmapInfo;
+ }
+
+ /**
+ * Represents a bitmap with either
+ * - its format and content in `buffer`
+ * - or a BufferedImage with its raw pixels
+ */
+ public static class Bitmap {
+ /**
+ * format of the bitmap content in buffer
+ */
+ public String format;
+ /**
+ * byte buffer of the bitmap content
+ */
+ public byte[] buffer;
+ /**
+ * BufferedImage with the bitmap's raw pixels
+ */
+ public BufferedImage image;
+
+ /**
+ * Initialize a Bitmap instance
+ * @param format - format of the bitmap
+ * @param buffer - buffer of the bitmap content
+ * @param image - BufferedImage with the bitmap's raw pixel
+ */
+ public Bitmap(String format, byte[] buffer, BufferedImage image) {
+ this.format = format;
+ this.buffer = buffer;
+ this.image = image;
+ }
+ }
+
+ private BufferedImage asBufferedImage(BitmapInfo info) {
+ // Convert the raw data to an image
+ // Convert BGRA to ABGR
+ int[] abgr = new int[info.height * info.width];
+ for (int i = 0; i < abgr.length; i++) {
+ abgr[i] = (
+ (((int) info.buffer[i * 4 + 3] & 0xFF) << 24)
+ + (((int) info.buffer[i * 4 + 0] & 0xFF) << 16)
+ + (((int) info.buffer[i * 4 + 1] & 0xFF) << 8)
+ + ((int) info.buffer[i * 4 + 2] & 0xFF));
+ }
+
+ BufferedImage bitmap = new BufferedImage(
+ info.width, info.height, BufferedImage.TYPE_4BYTE_ABGR);
+ bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width);
+ return bitmap;
+ }
+
+ /**
+ * Returns the bitmap associated with this instance.
+ * This is relevant for instances of android.graphics.Bitmap.
+ * Returns null if there is no bitmap pixel data associated
+ * with the given instance.
+ *
+ * @return the bitmap pixel data associated with this image
+ */
+ public Bitmap getBitmap() {
+ final BitmapInfo info = mBitmapInfo;
+ if (info == null) {
+ return null;
+ }
+
+ /**
+ * See android.graphics.Bitmap.CompressFormat for definitions
+ * -1 for legacy objects with content in `Bitmap.mBuffer`
+ */
+ switch (info.format) {
+ case 0: /* JPEG */
+ return new Bitmap("image/jpg", info.buffer, null);
+ case 1: /* PNG */
+ return new Bitmap("image/png", info.buffer, null);
+ case 2: /* WEBP */
+ case 3: /* WEBP_LOSSY */
+ case 4: /* WEBP_LOSSLESS */
+ return new Bitmap("image/webp", info.buffer, null);
+ case -1:/* Legacy */
+ return new Bitmap(null, null, asBufferedImage(info));
+ default:
+ return null;
+ }
+ }
+
+}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
index 07e8cf9771..d193f08848 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
@@ -16,9 +16,6 @@
package com.android.ahat.heapdump;
-import java.awt.image.BufferedImage;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.NoSuchElementException;
@@ -32,6 +29,21 @@ import java.util.NoSuchElementException;
* object in addition to those methods inherited from {@link AhatInstance}.
*/
public class AhatClassInstance extends AhatInstance {
+
+ /**
+ * Create an AhatClassInstance or AhatBitmapInstance if it's a subclass of
+ * android.graphics.Bitmap
+ *
+ * @param classObj - the class of this object
+ * @param objectId - the object Id
+ * @return an AhatClassInstance or AhatBitmapInstance
+ */
+ public static AhatClassInstance create(AhatClassObj classObj, long objectId) {
+ return classObj.isSubClassOf("android.graphics.Bitmap")
+ ? new AhatBitmapInstance(objectId)
+ : new AhatClassInstance(objectId);
+ }
+
// Instance fields of the object. These are stored in order of the instance
// field descriptors from the class object, starting with this class first,
// followed by the super class, and so on. We store the values separate from
@@ -68,10 +80,13 @@ public class AhatClassInstance extends AhatInstance {
/**
* Read an int field of an instance.
* The field is assumed to be an int type.
- * Returns <code>def</code> if the field value is not an int or could not be
+ *
+ * @param fieldName name of the int field
+ * @param def default value if the field is not an int or could not be read
+ * @return <code>def</code> if the field value is not an int or could not be
* read.
*/
- private Integer getIntField(String fieldName, Integer def) {
+ protected Integer getIntField(String fieldName, Integer def) {
Value value = getField(fieldName);
if (value == null || !value.isInteger()) {
return def;
@@ -82,10 +97,13 @@ public class AhatClassInstance extends AhatInstance {
/**
* Read a long field of this instance.
* The field is assumed to be a long type.
- * Returns <code>def</code> if the field value is not an long or could not
+ *
+ * @param fieldName name of the long field
+ * @param def default value if the field is not an int or could not be read
+ * @return <code>def</code> if the field value is not an long or could not
* be read.
*/
- private Long getLongField(String fieldName, Long def) {
+ protected Long getLongField(String fieldName, Long def) {
Value value = getField(fieldName);
if (value == null || !value.isLong()) {
return def;
@@ -109,6 +127,30 @@ public class AhatClassInstance extends AhatInstance {
return new ReferenceIterator();
}
+ /**
+ * Returns the value of the field of `fieldName` as an AhatArrayInstance
+ *
+ * @param fieldName name of the array field
+ * @return null if the field is not found, or the field is not an
+ * AhatArrayInstance.
+ */
+ protected AhatArrayInstance getArrayField(String fieldName) {
+ AhatInstance field = getRefField(fieldName);
+ return (field == null) ? null : field.asArrayInstance();
+ }
+
+ /**
+ * Reads the given field from the given instance.
+ * The field is assumed to be a byte[] field.
+ *
+ * @param fieldName name of the byte array field
+ * @return null if the field value is null, not a byte[] or could not be read.
+ */
+ protected byte[] getByteArrayField(String fieldName) {
+ AhatInstance field = getRefField(fieldName);
+ return field == null ? null : field.asByteArray();
+ }
+
@Override public String asString(int maxChars) {
if (!isInstanceOfClass("java.lang.String")) {
return null;
@@ -194,7 +236,7 @@ public class AhatClassInstance extends AhatInstance {
}
@Override public AhatInstance getAssociatedBitmapInstance() {
- return getBitmapInfo() == null ? null : this;
+ return asBitmapInstance();
}
@Override public boolean isClassInstance() {
@@ -228,235 +270,6 @@ public class AhatClassInstance extends AhatInstance {
}
}
- /**
- * Returns the value of the field of `fieldName` as an AhatArrayInstance
- * Returns null if the field is not found, or the field is not an
- * AhatArrayInstance.
- */
- private AhatArrayInstance getArrayField(String fieldName) {
- AhatInstance field = getRefField(fieldName);
- return (field == null) ? null : field.asArrayInstance();
- }
-
- /**
- * Read the given field from the given instance.
- * The field is assumed to be a byte[] field.
- * Returns null if the field value is null, not a byte[] or could not be read.
- */
- private byte[] getByteArrayField(String fieldName) {
- AhatInstance field = getRefField(fieldName);
- return field == null ? null : field.asByteArray();
- }
-
- private static class BitmapDumpData {
- public int count;
- // See android.graphics.Bitmap.CompressFormat for format values.
- // -1 means no compression for backward compatibility
- public int format;
- public HashMap<Long, byte[]> buffers;
- public HashSet<Long> referenced;
-
- public BitmapDumpData(int count, int format) {
- this.count = count;
- this.format = format;
- this.buffers = new HashMap<Long, byte[]>(count);
- this.referenced = new HashSet<Long>(count);
- }
- };
-
- private static BitmapDumpData bitmapDumpData = null;
-
- /**
- * find the BitmapDumpData that is included in the heap dump
- *
- * @param root root of the heap dump
- * @param instances all the instances from where the bitmap dump data will be excluded
- * @return true if valid bitmap dump data is found, false if not
- */
- public static boolean findBitmapDumpData(SuperRoot root, Instances<AhatInstance> instances) {
- final BitmapDumpData result;
- AhatClassObj cls = null;
-
- for (Reference ref : root.getReferences()) {
- if (ref.ref.isClassObj()) {
- cls = ref.ref.asClassObj();
- if (cls.getName().equals("android.graphics.Bitmap")) {
- break;
- }
- }
- }
-
- if (cls == null) {
- return false;
- }
-
- Value value = cls.getStaticField("dumpData");
- if (value == null || !value.isAhatInstance()) {
- return false;
- }
-
- AhatClassInstance inst = value.asAhatInstance().asClassInstance();
- if (inst == null) {
- return false;
- }
-
- result = inst.asBitmapDumpData();
- if (result == null) {
- return false;
- }
-
- /* remove all instances referenced from BitmapDumpData,
- * these instances shall *not* be counted
- */
- final HashSet<Long> referenced = result.referenced;
- instances.removeIf(i -> { return referenced.contains(i.getId()); });
- bitmapDumpData = result;
- return true;
- }
-
- private BitmapDumpData asBitmapDumpData() {
- if (!isInstanceOfClass("android.graphics.Bitmap$DumpData")) {
- return null;
- }
-
- int count = getIntField("count", 0);
- int format = getIntField("format", -1);
-
- if (count == 0 || format == -1) {
- return null;
- }
-
- BitmapDumpData result = new BitmapDumpData(count, format);
-
- AhatArrayInstance natives = getArrayField("natives");
- AhatArrayInstance buffers = getArrayField("buffers");
- if (natives == null || buffers == null) {
- return null;
- }
-
- result.referenced.add(natives.getId());
- result.referenced.add(buffers.getId());
-
- result.buffers = new HashMap<>(result.count);
- for (int i = 0; i < result.count; i++) {
- Value nativePtr = natives.getValue(i);
- Value bufferVal = buffers.getValue(i);
- if (nativePtr == null || bufferVal == null) {
- continue;
- }
- AhatInstance buffer = bufferVal.asAhatInstance();
- result.buffers.put(nativePtr.asLong(), buffer.asArrayInstance().asByteArray());
- result.referenced.add(buffer.getId());
- }
- return result;
- }
-
- private static class BitmapInfo {
- public final int width;
- public final int height;
- public final int format;
- public final byte[] buffer;
-
- public BitmapInfo(int width, int height, int format, byte[] buffer) {
- this.width = width;
- this.height = height;
- this.format = format;
- this.buffer = buffer;
- }
- }
-
- /**
- * Return bitmap info for this object, or null if no appropriate bitmap
- * info is available.
- */
- private BitmapInfo getBitmapInfo() {
- if (!isInstanceOfClass("android.graphics.Bitmap")) {
- return null;
- }
-
- Integer width = getIntField("mWidth", null);
- if (width == null) {
- return null;
- }
-
- Integer height = getIntField("mHeight", null);
- if (height == null) {
- return null;
- }
-
- byte[] buffer = getByteArrayField("mBuffer");
- if (buffer != null) {
- if (buffer.length < 4 * height * width) {
- return null;
- }
- return new BitmapInfo(width, height, -1, buffer);
- }
-
- long nativePtr = getLongField("mNativePtr", -1l);
- if (nativePtr == -1) {
- return null;
- }
-
- if (bitmapDumpData == null || bitmapDumpData.count == 0) {
- return null;
- }
-
- if (!bitmapDumpData.buffers.containsKey(nativePtr)) {
- return null;
- }
-
- buffer = bitmapDumpData.buffers.get(nativePtr);
- if (buffer == null) {
- return null;
- }
-
- return new BitmapInfo(width, height, bitmapDumpData.format, buffer);
- }
-
- private BufferedImage asBufferedImage(BitmapInfo info) {
- // Convert the raw data to an image
- // Convert BGRA to ABGR
- int[] abgr = new int[info.height * info.width];
- for (int i = 0; i < abgr.length; i++) {
- abgr[i] = (
- (((int) info.buffer[i * 4 + 3] & 0xFF) << 24)
- + (((int) info.buffer[i * 4 + 0] & 0xFF) << 16)
- + (((int) info.buffer[i * 4 + 1] & 0xFF) << 8)
- + ((int) info.buffer[i * 4 + 2] & 0xFF));
- }
-
- BufferedImage bitmap = new BufferedImage(
- info.width, info.height, BufferedImage.TYPE_4BYTE_ABGR);
- bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width);
- return bitmap;
- }
-
- @Override public Bitmap asBitmap() {
- BitmapInfo info = getBitmapInfo();
- if (info == null) {
- return null;
- }
-
- /**
- * See android.graphics.Bitmap.CompressFormat for definitions
- * -1 for legacy objects with content in `Bitmap.mBuffer`
- */
- switch (info.format) {
- case 0: /* JPEG */
- return new Bitmap("image/jpg", info.buffer, null);
- case 1: /* PNG */
- return new Bitmap("image/png", info.buffer, null);
- case 2: /* WEBP */
- case 3: /* WEBP_LOSSY */
- case 4: /* WEBP_LOSSLESS */
- return new Bitmap("image/webp", info.buffer, null);
- case -1:/* Legacy */
- return new Bitmap(null, null, asBufferedImage(info));
- default:
- return null;
- }
- }
-
@Override
RegisteredNativeAllocation asRegisteredNativeAllocation() {
if (!isInstanceOfClass("sun.misc.Cleaner")) {
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
index 68b400f501..0294f2eee0 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
@@ -162,6 +162,23 @@ public class AhatClassObj extends AhatInstance {
return true;
}
+ /**
+ * Returns true if this is a subclass of another class with the given name.
+ *
+ * @param className the name of the class to check for subclass
+ * @return true if this is a subclass of another class with the given name
+ */
+ public boolean isSubClassOf(String className) {
+ AhatClassObj cls = this;
+ while (cls != null) {
+ if (className.equals(cls.getName())) {
+ return true;
+ }
+ cls = cls.getSuperClassObj();
+ }
+ return false;
+ }
+
@Override public AhatClassObj asClassObj() {
return this;
}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
index 1278c584c3..8358553722 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -392,6 +392,24 @@ public abstract class AhatInstance implements Diffable<AhatInstance> {
}
/**
+ * Returns true if this instance is a bitmap instance.
+ * @return true if this instance is a bitmap instance
+ */
+ public boolean isBitmapInstance() {
+ return false;
+ }
+
+ /**
+ * Returns this as an AhatBitmapInstance if this is an AhatBitmapInstance.
+ * Returns null if this is not an AhatBitmapInstance.
+ *
+ * @return this instance as a bitmap instance
+ */
+ public AhatBitmapInstance asBitmapInstance() {
+ return null;
+ }
+
+ /**
* Returns the <code>referent</code> associated with this instance.
* This is only relevant for instances of java.lang.ref.Reference or its
* subclasses. Returns null if the instance has no referent associated with
@@ -583,50 +601,6 @@ public abstract class AhatInstance implements Diffable<AhatInstance> {
return asString(-1);
}
- /**
- * Represents a bitmap with either
- * - its format and content in `buffer`
- * - or a BufferedImage with its raw pixels
- */
- public static class Bitmap {
- /**
- * format of the bitmap content in buffer
- */
- public String format;
- /**
- * byte buffer of the bitmap content
- */
- public byte[] buffer;
- /**
- * BufferedImage with the bitmap's raw pixels
- */
- public BufferedImage image;
-
- /**
- * Initialize a Bitmap instance
- * @param format - format of the bitmap
- * @param buffer - buffer of the bitmap content
- * @param image - BufferedImage with the bitmap's raw pixel
- */
- public Bitmap(String format, byte[] buffer, BufferedImage image) {
- this.format = format;
- this.buffer = buffer;
- this.image = image;
- }
- }
-
- /**
- * Returns the bitmap associated with this instance.
- * This is relevant for instances of android.graphics.Bitmap and byte[].
- * Returns null if there is no bitmap pixel data associated with the given
- * instance.
- *
- * @return the bitmap pixel data associated with this image
- */
- public Bitmap asBitmap() {
- return null;
- }
-
static class RegisteredNativeAllocation {
public AhatInstance referent;
public long size;
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
index 16c1e2f576..8f205bfb55 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
@@ -37,6 +37,8 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> {
private AhatSnapshot mBaseline = this;
+ private AhatBitmapInstance.BitmapDumpData mBitmapDumpData = null;
+
AhatSnapshot(SuperRoot root,
Instances<AhatInstance> instances,
List<AhatHeap> heaps,
@@ -49,7 +51,8 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> {
mRootSite = rootSite;
AhatInstance.computeReachability(mSuperRoot, progress, mInstances.size());
- AhatClassInstance.findBitmapDumpData(mSuperRoot, mInstances);
+
+ mBitmapDumpData = AhatBitmapInstance.findBitmapDumpData(mSuperRoot, mInstances);
for (AhatInstance inst : mInstances) {
// Add this instance to its site.
@@ -209,4 +212,8 @@ public class AhatSnapshot implements Diffable<AhatSnapshot> {
@Override public boolean isPlaceHolder() {
return false;
}
+
+ public List<List<AhatBitmapInstance>> findDuplicateBitmaps() {
+ return AhatBitmapInstance.findDuplicates(mBitmapDumpData);
+ }
}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java b/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java
index 7fe444e035..f28792b53d 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/Parser.java
@@ -400,7 +400,7 @@ public class Parser {
Site site = sites.get(stackSerialNumber);
AhatClassObj classObj = classById.get(classId);
- AhatClassInstance obj = new AhatClassInstance(objectId);
+ AhatClassInstance obj = AhatClassInstance.create(classObj, objectId);
obj.initialize(heaps.getCurrentHeap(), site, classObj);
obj.setTemporaryUserData(data);
instances.add(obj);
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Size.java b/tools/ahat/src/main/com/android/ahat/heapdump/Size.java
index b721bac6ba..c5103e46d0 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/Size.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/Size.java
@@ -16,6 +16,7 @@
package com.android.ahat.heapdump;
+import java.lang.Comparable;
import java.util.Objects;
/**
@@ -27,7 +28,7 @@ import java.util.Objects;
* <p>
* Size objects are immutable.
*/
-public class Size {
+public class Size implements Comparable<Size> {
private final long mJavaSize;
private final long mRegisteredNativeSize;
@@ -124,5 +125,10 @@ public class Size {
}
return false;
}
+
+ @Override public int compareTo(Size other) {
+ return Long.compare(this.mJavaSize + this.mRegisteredNativeSize,
+ other.mJavaSize + other.mRegisteredNativeSize);
+ }
}
diff --git a/tools/ahat/src/test-dump/DumpedStuff.java b/tools/ahat/src/test-dump/DumpedStuff.java
index de2968f51b..2b58ab68d9 100644
--- a/tools/ahat/src/test-dump/DumpedStuff.java
+++ b/tools/ahat/src/test-dump/DumpedStuff.java
@@ -19,6 +19,7 @@ import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import libcore.util.NativeAllocationRegistry;
+import android.graphics.Bitmap;
// We take a heap dump that includes a single instance of this
// DumpedStuff class. Objects stored as fields in this class can be easily
@@ -85,6 +86,9 @@ public class DumpedStuff extends SuperDumpedStuff {
}
gcPathArray[2].right.left = gcPathArray[2].left.right;
+
+ bitmapOne = new Bitmap(100, 200, 0xDEADBEEF, bigArray);
+ bitmapTwo = new Bitmap(100, 200, 0xBEEFDEAD, bigArray);
}
public static class ObjectTree {
@@ -179,6 +183,8 @@ public class DumpedStuff extends SuperDumpedStuff {
public SoftReference aSoftReference = new SoftReference(new Object());
public Reference reachabilityReferenceChain;
public byte[] bigArray;
+ public Bitmap bitmapOne = null;
+ public Bitmap bitmapTwo = null;
public ObjectTree[] gcPathArray = new ObjectTree[]{null, null,
new ObjectTree(
new ObjectTree(null, new ObjectTree(null, null)),
diff --git a/tools/ahat/src/test-dump/android/graphics/Bitmap.java b/tools/ahat/src/test-dump/android/graphics/Bitmap.java
new file mode 100644
index 0000000000..dadc5010c6
--- /dev/null
+++ b/tools/ahat/src/test-dump/android/graphics/Bitmap.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 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.
+ */
+
+package android.graphics;
+
+/**
+ * Fake android.graphics.Bitmap class that's minimum for testing
+ */
+public final class Bitmap {
+
+ private final long mNativePtr;
+
+ private final int mWidth;
+ private final int mHeight;
+
+ public Bitmap(int width, int height, long nativePtr, byte[] buffer) {
+ this.mWidth = width;
+ this.mHeight = height;
+ this.mNativePtr = nativePtr;
+ dumpData.add(nativePtr, buffer);
+ }
+
+ private static final class DumpData {
+ private final int format;
+ private final long[] natives;
+ private final byte[][] buffers;
+ private final int max;
+ private int count;
+
+ public DumpData(int format, int max) {
+ this.max = max;
+ this.format = format;
+ this.natives = new long[max];
+ this.buffers = new byte[max][];
+ this.count = 0;
+ }
+
+ public void add(long nativePtr, byte[] buffer) {
+ natives[count] = nativePtr;
+ buffers[count] = buffer;
+ count = (count >= max) ? max : count + 1;
+ }
+ };
+
+ // assume default format 'PNG' and maximum 10 test bitmaps
+ private static DumpData dumpData = new DumpData(1, 10);
+}
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 1f290304c0..e1eb504c1f 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -19,6 +19,7 @@ package com.android.ahat;
import com.android.ahat.heapdump.AhatClassObj;
import com.android.ahat.heapdump.AhatHeap;
import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.AhatBitmapInstance;
import com.android.ahat.heapdump.AhatSnapshot;
import com.android.ahat.heapdump.PathElement;
import com.android.ahat.heapdump.Reachability;
@@ -28,6 +29,7 @@ import java.io.IOException;
import java.util.List;
import org.junit.Test;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -359,21 +361,24 @@ public class InstanceTest {
public void objectNotABitmap() throws IOException {
TestDump dump = TestDump.getTestDump();
AhatInstance obj = dump.getDumpedAhatInstance("anObject");
- assertNull(obj.asBitmap());
+ assertFalse(obj.isBitmapInstance());
+ assertNull(obj.asBitmapInstance());
}
@Test
public void arrayNotABitmap() throws IOException {
TestDump dump = TestDump.getTestDump();
AhatInstance obj = dump.getDumpedAhatInstance("gcPathArray");
- assertNull(obj.asBitmap());
+ assertFalse(obj.isBitmapInstance());
+ assertNull(obj.asBitmapInstance());
}
@Test
public void classObjNotABitmap() throws IOException {
TestDump dump = TestDump.getTestDump();
AhatInstance obj = dump.findClass("Main");
- assertNull(obj.asBitmap());
+ assertFalse(obj.isBitmapInstance());
+ assertNull(obj.asBitmapInstance());
}
@Test
@@ -413,6 +418,36 @@ public class InstanceTest {
}
@Test
+ public void asBitmap() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ AhatInstance obj = dump.getDumpedAhatInstance("bitmapOne");
+ assertNotNull(obj);
+ assertTrue(obj.isBitmapInstance());
+ assertNotNull(obj.asBitmapInstance());
+
+ AhatBitmapInstance.Bitmap bmp = obj.asBitmapInstance().getBitmap();
+ assertNotNull(bmp);
+
+ // default compression format
+ assertEquals(bmp.format, "image/png");
+
+ // check buffer content - expect to be same as in bigArray
+ assertEquals(bmp.buffer.length, 1000000);
+ for (int i = 0; i < 1000000; i++) {
+ assertEquals(bmp.buffer[i], (byte)((i * i) & 0xFF));
+ }
+ }
+
+ @Test
+ public void duplicatedBitmaps() throws IOException {
+ TestDump dump = TestDump.getTestDump();
+ List<List<AhatBitmapInstance>> duplicates = dump.getAhatSnapshot().findDuplicateBitmaps();
+ // expect to have 1 list of 2 duplicated bitmaps
+ assertEquals(duplicates.size(), 1);
+ assertEquals(duplicates.get(0).size(), 2);
+ }
+
+ @Test
public void isRoot() throws IOException {
// We expect the Main class to be a root.
TestDump dump = TestDump.getTestDump();
diff --git a/tools/ahat/src/test/com/android/ahat/SiteTest.java b/tools/ahat/src/test/com/android/ahat/SiteTest.java
index 78ef9b3c60..1f3331ae1c 100644
--- a/tools/ahat/src/test/com/android/ahat/SiteTest.java
+++ b/tools/ahat/src/test/com/android/ahat/SiteTest.java
@@ -37,20 +37,20 @@ public class SiteTest {
Site sKnownSite = oKnownSite.getSite();
assertEquals("DumpedStuff.java", sKnownSite.getFilename());
assertEquals("allocateObjectAtKnownSite", sKnownSite.getMethodName());
- assertEquals(29, sKnownSite.getLineNumber());
+ assertEquals(30, sKnownSite.getLineNumber());
assertSame(sKnownSite, snapshot.getSite(sKnownSite.getId()));
AhatInstance oKnownSubSite = dump.getDumpedAhatInstance("objectAllocatedAtKnownSubSite");
Site sKnownSubSite = oKnownSubSite.getSite();
assertEquals("DumpedStuff.java", sKnownSubSite.getFilename());
assertEquals("allocateObjectAtKnownSubSite", sKnownSubSite.getMethodName());
- assertEquals(37, sKnownSubSite.getLineNumber());
+ assertEquals(38, sKnownSubSite.getLineNumber());
assertSame(sKnownSubSite, snapshot.getSite(sKnownSubSite.getId()));
Site sKnownSubSiteParent = sKnownSubSite.getParent();
assertEquals("DumpedStuff.java", sKnownSubSiteParent.getFilename());
assertEquals("allocateObjectAtKnownSite", sKnownSubSiteParent.getMethodName());
- assertEquals(30, sKnownSubSiteParent.getLineNumber());
+ assertEquals(31, sKnownSubSiteParent.getLineNumber());
assertSame(sKnownSubSiteParent, snapshot.getSite(sKnownSubSiteParent.getId()));
assertNotSame(sKnownSite, sKnownSubSiteParent);
@@ -59,7 +59,7 @@ public class SiteTest {
Site sKnownSiteParent = sKnownSite.getParent();
assertEquals("DumpedStuff.java", sKnownSiteParent.getFilename());
assertEquals("<init>", sKnownSiteParent.getMethodName());
- assertEquals(45, sKnownSiteParent.getLineNumber());
+ assertEquals(46, sKnownSiteParent.getLineNumber());
assertSame(sKnownSiteParent, snapshot.getSite(sKnownSiteParent.getId()));
AhatInstance oObfSuperSite = dump.getDumpedAhatInstance("objectAllocatedAtObfSuperSite");
@@ -80,7 +80,7 @@ public class SiteTest {
Site sOverriddenSite = oOverriddenSite.getSite();
assertEquals("DumpedStuff.java", sOverriddenSite.getFilename());
assertEquals("allocateObjectAtOverriddenSite", sOverriddenSite.getMethodName());
- assertEquals(41, sOverriddenSite.getLineNumber());
+ assertEquals(42, sOverriddenSite.getLineNumber());
assertSame(sOverriddenSite, snapshot.getSite(sOverriddenSite.getId()));
}
diff --git a/tools/checker/README b/tools/checker/README
index e01f0d0750..a87a72be77 100644
--- a/tools/checker/README
+++ b/tools/checker/README
@@ -96,12 +96,12 @@ CHECK-IF and CHECK-ELIF take a Python expression as input that will be evaluated
A possible use case of branching is to check whether the generated code exploits the instruction
architecture features enabled at compile time. For that purpose, you can call the custom made
-function isaHasFeature("feature_name").
+function hasIsaFeature("feature_name").
Example:
/// CHECK-START-ARM64: int other.TestByte.testDotProdComplex(byte[], byte[]) disassembly (after)
/// CHECK: VecDotProd
- /// CHECK-IF: isaHasFeature("dotprod")
+ /// CHECK-IF: hasIsaFeature("dotprod")
/// CHECK: sdot
/// CHECK-ELSE:
/// CHECK-NOT: sdot
diff --git a/tools/hiddenapi/Android.bp b/tools/hiddenapi/Android.bp
index fff94926ab..506197ce46 100644
--- a/tools/hiddenapi/Android.bp
+++ b/tools/hiddenapi/Android.bp
@@ -41,7 +41,7 @@ cc_defaults {
stl: "c++_static",
static_libs: [
"libbase",
- "libcrypto",
+ "libcrypto_for_art",
],
}