[automerger skipped] Address missing ART gtests dependencies on `generate-boot-image`. am: da7c017cef -s ours
am skip reason: Merged-In Ie828a37f5f3c0843cf33cc40b07420eba0ee6dd0 with SHA-1 78d86b5810 is already in history
Original change: https://googleplex-android-review.googlesource.com/c/platform/art/+/19866448
Change-Id: Idb9d9c219ead414084b2ae9ceafac4b1c102f854
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/.clang-format b/.clang-format
index 0d6a160..c2114f7 100644
--- a/.clang-format
+++ b/.clang-format
@@ -22,3 +22,19 @@
FixNamespaceComments: true
PointerAlignment: Left
TabWidth: 2
+
+---
+
+Language: Java
+
+AccessModifierOffset: -4
+AlignOperands: false
+AllowShortFunctionsOnASingleLine: Inline
+AlwaysBreakBeforeMultilineStrings: false
+ColumnLimit: 100
+CommentPragmas: NOLINT:.*
+ConstructorInitializerIndentWidth: 6
+ContinuationIndentWidth: 8
+IndentWidth: 4
+PenaltyBreakBeforeFirstCallParameter: 100000
+SpacesBeforeTrailingComments: 1
diff --git a/Android.mk b/Android.mk
index 9257c22..d3e37dc 100644
--- a/Android.mk
+++ b/Android.mk
@@ -521,7 +521,6 @@
lib/libart-disassembler.so \
lib/libartpalette.so \
lib/libart.so \
- lib/libbacktrace.so \
lib/libdexfile.so \
lib/libdt_fd_forward.so \
lib/libdt_socket.so \
@@ -551,7 +550,6 @@
lib64/libart-disassembler.so \
lib64/libartpalette.so \
lib64/libart.so \
- lib64/libbacktrace.so \
lib64/libdexfile.so \
lib64/libdt_fd_forward.so \
lib64/libdt_socket.so \
@@ -649,6 +647,13 @@
#
# TODO(b/129332183): Remove this when Golem has full support for the
# ART APEX.
+#
+# TODO(b/129332183): This approach is flawed: We mix DSOs from prebuilt APEXes
+# and prebuilts/runtime/mainline/platform/impl with source built ones, and both
+# may depend on the same DSOs, and some of them don't have stable ABIs.
+# libbase.so in particular is such a problematic dependency. When those
+# dependencies eventually don't work anymore we don't have much choice but to
+# update all prebuilts.
.PHONY: standalone-apex-files
standalone-apex-files: deapexer \
$(RELEASE_ART_APEX) \
@@ -882,7 +887,7 @@
define create_public_sdk_dex
public_sdk_$(1)_stub := $$(call get_public_sdk_stub_dex,$(1))
$$(public_sdk_$(1)_stub): PRIVATE_MIN_SDK_VERSION := $(1)
-$$(public_sdk_$(1)_stub): $$(call resolve-prebuilt-sdk-jar-path,$(1)) $$(DX) $$(ZIP2ZIP)
+$$(public_sdk_$(1)_stub): $$(call resolve-prebuilt-sdk-jar-path,$(1)) $$(D8) $$(ZIP2ZIP)
$$(transform-classes.jar-to-dex)
$$(call declare-1p-target,$$(public_sdk_$(1)_stub),art)
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index b8ee3c5..2182991 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -7,6 +7,8 @@
# so we don't need the custom runtests script and this check.
check_libnativebridge_test_field = libnativebridge/tests/preupload_check_test_tag.sh ${PREUPLOAD_COMMIT_MESSAGE} ${PREUPLOAD_FILES}
+check_expectation_jsons = tools/check_presubmit_json_expectations.sh ${REPO_ROOT} ${PREUPLOAD_FILES}
+
[Builtin Hooks]
cpplint = true
bpfmt = true
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e799c0a..416d2aa 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -401,12 +401,24 @@
"name": "art-run-test-2042-checker-dce-always-throw[com.google.android.art.apex]"
},
{
+ "name": "art-run-test-2042-reference-processing[com.google.android.art.apex]"
+ },
+ {
+ "name": "art-run-test-2043-reference-pauses[com.google.android.art.apex]"
+ },
+ {
+ "name": "art-run-test-2044-get-stack-traces[com.google.android.art.apex]"
+ },
+ {
"name": "art-run-test-2231-checker-heap-poisoning[com.google.android.art.apex]"
},
{
"name": "art-run-test-2232-write-metrics-to-log[com.google.android.art.apex]"
},
{
+ "name": "art-run-test-2233-checker-remove-loop-suspend-check[com.google.android.art.apex]"
+ },
+ {
"name": "art-run-test-2234-checker-remove-entry-suspendcheck[com.google.android.art.apex]"
},
{
@@ -1223,12 +1235,21 @@
"name": "art-run-test-835-b216762268[com.google.android.art.apex]"
},
{
+ "name": "art-run-test-838-override[com.google.android.art.apex]"
+ },
+ {
+ "name": "art-run-test-841-defaults[com.google.android.art.apex]"
+ },
+ {
"name": "art-run-test-963-default-range-smali[com.google.android.art.apex]"
},
{
"name": "art_libnativebridge_cts_tests[com.google.android.art.apex]"
},
{
+ "name": "art_standalone_artd_tests[com.google.android.art.apex]"
+ },
+ {
"name": "art_standalone_cmdline_tests[com.google.android.art.apex]"
},
{
@@ -1277,21 +1298,27 @@
"name": "art_standalone_sigchain_tests[com.google.android.art.apex]"
},
{
+ "name": "libnativeloader_e2e_tests[com.google.android.art.apex]"
+ },
+ {
"name": "libnativeloader_test[com.google.android.art.apex]"
}
],
"presubmit": [
{
- "name": "CtsJdwpTestCases"
+ "name": "ArtServiceTests"
},
{
"name": "BootImageProfileTest"
},
{
- "name": "ArtServiceTests"
+ "name": "ComposHostTestCases"
},
{
- "name": "ComposHostTestCases"
+ "name": "CtsJdwpTestCases"
+ },
+ {
+ "name": "art-apex-update-rollback"
},
{
"name": "art_standalone_dexpreopt_tests"
@@ -1693,12 +1720,24 @@
"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-2231-checker-heap-poisoning"
},
{
"name": "art-run-test-2232-write-metrics-to-log"
},
{
+ "name": "art-run-test-2233-checker-remove-loop-suspend-check"
+ },
+ {
"name": "art-run-test-2234-checker-remove-entry-suspendcheck"
},
{
@@ -2515,12 +2554,21 @@
"name": "art-run-test-835-b216762268"
},
{
+ "name": "art-run-test-838-override"
+ },
+ {
+ "name": "art-run-test-841-defaults"
+ },
+ {
"name": "art-run-test-963-default-range-smali"
},
{
"name": "art_libnativebridge_cts_tests"
},
{
+ "name": "art_standalone_artd_tests"
+ },
+ {
"name": "art_standalone_cmdline_tests"
},
{
@@ -2572,6 +2620,1325 @@
"name": "art_standalone_sigchain_tests"
},
{
+ "name": "libnativeloader_e2e_tests"
+ },
+ {
+ "name": "libnativeloader_test"
+ }
+ ],
+ "hwasan-presubmit": [
+ {
+ "name": "ArtServiceTests"
+ },
+ {
+ "name": "ComposHostTestCases"
+ },
+ {
+ "name": "art-apex-update-rollback"
+ },
+ {
+ "name": "art_standalone_dexpreopt_tests"
+ },
+ {
+ "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-NativeAllocations"
+ },
+ {
+ "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-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-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-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-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-070-nio-buffer"
+ },
+ {
+ "name": "art-run-test-072-precise-gc"
+ },
+ {
+ "name": "art-run-test-072-reachability-fence"
+ },
+ {
+ "name": "art-run-test-074-gc-thrash"
+ },
+ {
+ "name": "art-run-test-076-boolean-put"
+ },
+ {
+ "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-144-static-field-sigquit"
+ },
+ {
+ "name": "art-run-test-145-alloc-tracking-stress"
+ },
+ {
+ "name": "art-run-test-151-OpenFileLimit"
+ },
+ {
+ "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-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-2231-checker-heap-poisoning"
+ },
+ {
+ "name": "art-run-test-2232-write-metrics-to-log"
+ },
+ {
+ "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-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-537-checker-arraycopy"
+ },
+ {
+ "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-reduc"
+ },
+ {
+ "name": "art-run-test-662-regression-alias"
+ },
+ {
+ "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-841-defaults"
+ },
+ {
+ "name": "art-run-test-963-default-range-smali"
+ },
+ {
+ "name": "art_libnativebridge_cts_tests"
+ },
+ {
+ "name": "art_standalone_artd_tests"
+ },
+ {
+ "name": "art_standalone_cmdline_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"
+ },
+ {
+ "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_compiler_tests"
+ },
+ {
+ "name": "art_standalone_runtime_tests"
+ },
+ {
+ "name": "art_standalone_sigchain_tests"
+ },
+ {
+ "name": "libnativeloader_e2e_tests"
+ },
+ {
"name": "libnativeloader_test"
}
]
diff --git a/artd/Android.bp b/artd/Android.bp
index 6db1287..c0ffd0a 100644
--- a/artd/Android.bp
+++ b/artd/Android.bp
@@ -22,22 +22,31 @@
default_applicable_licenses: ["art_license"],
}
-art_cc_binary {
- name: "artd",
+cc_defaults {
+ name: "artd_defaults",
defaults: ["art_defaults"],
-
srcs: [
"artd.cc",
],
-
shared_libs: [
- "artd-aidl-ndk",
- "libartbase",
"libarttools",
"libbase",
"libbinder_ndk",
],
+ static_libs: [
+ "artd-aidl-ndk",
+ ],
+}
+art_cc_binary {
+ name: "artd",
+ defaults: ["artd_defaults"],
+ srcs: [
+ "artd_main.cc",
+ ],
+ shared_libs: [
+ "libartbase",
+ ],
apex_available: [
"com.android.art",
"com.android.art.debug",
@@ -50,3 +59,35 @@
filename: "init.rc",
installable: false,
}
+
+art_cc_defaults {
+ name: "art_artd_tests_defaults",
+ defaults: ["artd_defaults"],
+ srcs: [
+ "artd_test.cc",
+ ],
+}
+
+// Version of ART gtest `art_artd_tests` bundled with the ART APEX on target.
+//
+// This test requires the full libbinder_ndk implementation on host, which is
+// not available as a prebuilt on the thin master-art branch. Hence it won't
+// work there, and there's a conditional in Android.gtest.mk to exclude it from
+// test-art-host-gtest.
+art_cc_test {
+ name: "art_artd_tests",
+ defaults: [
+ "art_gtest_defaults",
+ "art_artd_tests_defaults",
+ ],
+}
+
+// Standalone version of ART gtest `art_artd_tests`, not bundled with the ART
+// APEX on target.
+art_cc_test {
+ name: "art_standalone_artd_tests",
+ defaults: [
+ "art_standalone_gtest_defaults",
+ "art_artd_tests_defaults",
+ ],
+}
diff --git a/artd/artd.cc b/artd/artd.cc
index 1dcd2e8..27a609d 100644
--- a/artd/artd.cc
+++ b/artd/artd.cc
@@ -1,87 +1,62 @@
/*
-** Copyright 2021, 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.
-*/
+ * Copyright (C) 2021 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 "artd.h"
+
+#include <unistd.h>
#include <string>
-#define LOG_TAG "artd"
-#include <android/binder_manager.h>
-#include <android/binder_process.h>
-#include <unistd.h>
-#include <utils/Errors.h>
-
-#include "aidl/android/os/BnArtd.h"
-#include "base/logging.h"
-#include "base/macros.h"
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/logging.h"
+#include "android-base/result.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_manager.h"
+#include "android/binder_process.h"
#include "tools/tools.h"
-using ::ndk::ScopedAStatus;
-
-namespace android {
+namespace art {
namespace artd {
-class Artd : public aidl::android::os::BnArtd {
- constexpr static const char* const SERVICE_NAME = "artd";
+namespace {
- public:
- Artd() {}
+using ::android::base::Error;
+using ::android::base::Result;
+using ::ndk::ScopedAStatus;
- /*
- * Binder API
- */
+constexpr const char* kServiceName = "artd";
- ScopedAStatus isAlive(bool* _aidl_return) {
- *_aidl_return = true;
- return ScopedAStatus::ok();
+} // namespace
+
+ScopedAStatus Artd::isAlive(bool* _aidl_return) {
+ *_aidl_return = true;
+ return ScopedAStatus::ok();
+}
+
+Result<void> Artd::Start() {
+ ScopedAStatus status = ScopedAStatus::fromStatus(
+ AServiceManager_registerLazyService(this->asBinder().get(), kServiceName));
+ if (!status.isOk()) {
+ return Error() << status.getDescription();
}
- /*
- * Server API
- */
+ ABinderProcess_startThreadPool();
- ScopedAStatus Start() {
- LOG(INFO) << "Starting artd";
-
- status_t ret = AServiceManager_addService(this->asBinder().get(), SERVICE_NAME);
- if (ret != android::OK) {
- return ScopedAStatus::fromStatus(ret);
- }
-
- ABinderProcess_startThreadPool();
-
- return ScopedAStatus::ok();
- }
-};
+ return {};
+}
} // namespace artd
-} // namespace android
-
-int main(const int argc __attribute__((unused)), char* argv[]) {
- setenv("ANDROID_LOG_TAGS", "*:v", 1);
- android::base::InitLogging(argv);
-
- android::artd::Artd artd;
-
- if (auto ret = artd.Start(); !ret.isOk()) {
- LOG(ERROR) << "Unable to start artd: " << ret.getMessage();
- exit(1);
- }
-
- ABinderProcess_joinThreadPool();
-
- LOG(INFO) << "artd shutting down";
-
- return 0;
-}
+} // namespace art
diff --git a/artd/artd.h b/artd/artd.h
new file mode 100644
index 0000000..f01d9a8
--- /dev/null
+++ b/artd/artd.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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_ARTD_ARTD_H_
+#define ART_ARTD_ARTD_H_
+
+#include "aidl/com/android/server/art/BnArtd.h"
+#include "android-base/result.h"
+#include "android/binder_auto_utils.h"
+
+namespace art {
+namespace artd {
+
+class Artd : public aidl::com::android::server::art::BnArtd {
+ public:
+ ndk::ScopedAStatus isAlive(bool* _aidl_return) override;
+
+ android::base::Result<void> Start();
+};
+
+} // namespace artd
+} // namespace art
+
+#endif // ART_ARTD_ARTD_H_
diff --git a/artd/artd.rc b/artd/artd.rc
index eebec2d..5ddfcdc 100644
--- a/artd/artd.rc
+++ b/artd/artd.rc
@@ -12,8 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# This service is disabled until b/192042812 is resolved.
+# A lazy service that is started and stopped dynamically as needed.
service artd /apex/com.android.art/bin/artd
- disabled
+ interface aidl artd
+ disabled # Prevents the service from automatically starting at boot.
+ oneshot # Prevents the service from automatically restarting each time it is stopped.
class core
- user artd
\ No newline at end of file
+ user artd
+ group artd
+ capabilities DAC_OVERRIDE DAC_READ_SEARCH FOWNER CHOWN
diff --git a/artd/artd_main.cc b/artd/artd_main.cc
new file mode 100644
index 0000000..3644eba
--- /dev/null
+++ b/artd/artd_main.cc
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 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 <stdlib.h>
+
+#include "android-base/logging.h"
+#include "android-base/macros.h"
+#include "android/binder_interface_utils.h"
+#include "android/binder_process.h"
+#include "artd.h"
+
+int main(int argc ATTRIBUTE_UNUSED, char* argv[]) {
+ android::base::InitLogging(argv);
+
+ auto artd = ndk::SharedRefBase::make<art::artd::Artd>();
+
+ LOG(INFO) << "Starting artd";
+
+ if (auto ret = artd->Start(); !ret.ok()) {
+ LOG(ERROR) << "Unable to start artd: " << ret.error();
+ exit(1);
+ }
+
+ ABinderProcess_joinThreadPool();
+
+ LOG(INFO) << "artd shutting down";
+
+ return 0;
+}
diff --git a/artd/artd_test.cc b/artd/artd_test.cc
new file mode 100644
index 0000000..14bccc2
--- /dev/null
+++ b/artd/artd_test.cc
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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 "artd.h"
+
+#include <memory>
+
+#include "android/binder_interface_utils.h"
+#include "base/common_art_test.h"
+#include "gtest/gtest.h"
+
+namespace art {
+namespace artd {
+namespace {
+
+class ArtdTest : public CommonArtTest {
+ protected:
+ void SetUp() override {
+ CommonArtTest::SetUp();
+ artd_ = ndk::SharedRefBase::make<Artd>();
+ }
+
+ void TearDown() override { CommonArtTest::TearDown(); }
+
+ std::shared_ptr<Artd> artd_;
+};
+
+TEST_F(ArtdTest, isAlive) {
+ bool result = false;
+ artd_->isAlive(&result);
+ EXPECT_TRUE(result);
+}
+
+} // namespace
+} // namespace artd
+} // namespace art
diff --git a/artd/binder/Android.bp b/artd/binder/Android.bp
index 6acfe4e..ad8474f 100644
--- a/artd/binder/Android.bp
+++ b/artd/binder/Android.bp
@@ -25,7 +25,7 @@
aidl_interface {
name: "artd-aidl",
srcs: [
- "android/os/IArtd.aidl",
+ "com/android/server/art/*.aidl",
],
host_supported: true,
backend: {
diff --git a/artd/binder/android/os/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl
similarity index 95%
rename from artd/binder/android/os/IArtd.aidl
rename to artd/binder/com/android/server/art/IArtd.aidl
index a16764b..58b2aae 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/artd/binder/com/android/server/art/IArtd.aidl
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.os;
+package com.android.server.art;
/** {@hide} */
interface IArtd {
diff --git a/artd/tests/src/com/android/art/ArtdIntegrationTests.java b/artd/tests/src/com/android/art/ArtdIntegrationTests.java
index 7d40adb..2a32972 100644
--- a/artd/tests/src/com/android/art/ArtdIntegrationTests.java
+++ b/artd/tests/src/com/android/art/ArtdIntegrationTests.java
@@ -16,11 +16,12 @@
package com.android.art;
-import android.os.IArtd;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import com.android.server.art.IArtd;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
diff --git a/benchmark/Android.bp b/benchmark/Android.bp
index d781f84..43f46c3 100644
--- a/benchmark/Android.bp
+++ b/benchmark/Android.bp
@@ -51,6 +51,7 @@
// TODO(ngeoffray): find a way to link against the libraries in the apex.
shared_libs: [
"libart",
+ "libartbase",
"libbase",
],
}
diff --git a/build/Android.bp b/build/Android.bp
index d2545a4..5f9c1fa 100644
--- a/build/Android.bp
+++ b/build/Android.bp
@@ -29,27 +29,28 @@
art_clang_tidy_errors = [
"android-cloexec-open",
+ "bugprone-argument-comment",
"bugprone-lambda-function-name",
+ "bugprone-macro-parentheses",
"bugprone-unused-raii", // Protect scoped things like MutexLock.
+ "bugprone-unused-return-value",
"bugprone-virtual-near-miss",
+ "misc-unused-using-decls",
"modernize-use-bool-literals",
+ "modernize-use-nullptr",
+ "performance-faster-string-find",
+ "performance-for-range-copy",
"performance-implicit-conversion-in-loop",
+ "performance-noexcept-move-constructor",
"performance-unnecessary-copy-initialization",
+ "performance-unnecessary-value-param",
]
art_clang_tidy_allowed = [
// Many files have these warnings. Move them to art_clang_tidy_errors
// when all files are free of these warnings.
"android-cloexec-dup",
- "bugprone-argument-comment",
- "bugprone-unused-return-value",
- "misc-unused-using-decls",
- "modernize-use-nullptr",
- "modernize-use-using",
- "performance-faster-string-find",
- "performance-for-range-copy",
- "performance-noexcept-move-constructor",
- "performance-unnecessary-value-param",
+ "modernize-use-using", // TODO: move to art_clang_tidy_errors after b/236243696 is done
]
art_clang_tidy_disabled = [
diff --git a/build/Android.gtest.mk b/build/Android.gtest.mk
index def3190..0c897b4 100644
--- a/build/Android.gtest.mk
+++ b/build/Android.gtest.mk
@@ -27,7 +27,7 @@
# Manually add system libraries that we need to run the host ART tools.
my_files += \
- $(foreach lib, libbacktrace libbase libc++ libicu libicu_jni liblog libsigchain libunwindstack \
+ $(foreach lib, libbase libc++ libicu libicu_jni liblog libsigchain libunwindstack \
libziparchive libjavacore libandroidio libopenjdkd liblz4 liblzma, \
$(call intermediates-dir-for,SHARED_LIBRARIES,$(lib),HOST)/$(lib).so:lib64/$(lib).so \
$(call intermediates-dir-for,SHARED_LIBRARIES,$(lib),HOST,,2ND)/$(lib).so:lib/$(lib).so) \
@@ -114,6 +114,7 @@
art_dexlayout_tests \
art_dexlist_tests \
art_dexoptanalyzer_tests \
+ art_disassembler_tests \
art_hiddenapi_tests \
art_imgdiag_tests \
art_libartbase_tests \
@@ -131,9 +132,19 @@
art_runtime_tests \
art_sigchain_tests \
-ART_TEST_MODULES_TARGET := $(ART_TEST_MODULES_COMMON) art_odrefresh_tests
+ART_TEST_MODULES_TARGET := $(ART_TEST_MODULES_COMMON) \
+ art_artd_tests \
+ art_odrefresh_tests \
+
ART_TEST_MODULES_HOST := $(ART_TEST_MODULES_COMMON)
+ifneq (,$(wildcard frameworks/native/libs/binder))
+ # Only include the artd host tests if we have the binder sources available and
+ # can build the libbinder_ndk dependency. It is not available as a prebuilt on
+ # master-art.
+ ART_TEST_MODULES_HOST += art_artd_tests
+endif
+
ART_TARGET_GTEST_NAMES := $(foreach tm,$(ART_TEST_MODULES_TARGET),\
$(foreach path,$(ART_TEST_LIST_device_$(TARGET_ARCH)_$(tm)),\
$(notdir $(path))\
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index fb071f1..80acd81 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -59,7 +59,6 @@
// TODO(b/124476339): Clean up the following libraries once "required"
// dependencies work with APEX libraries.
"libart-compiler",
- "libartservice",
"libdt_fd_forward",
"libdt_socket",
"libjdwp",
@@ -267,6 +266,9 @@
native_shared_libs: art_runtime_base_native_shared_libs +
art_runtime_base_native_device_only_shared_libs +
libcore_native_shared_libs,
+ jni_libs: [
+ "libartservice",
+ ],
binaries: [
"artd",
],
@@ -298,9 +300,6 @@
enabled: false,
},
},
- // Indicates that pre-installed version of this apex can be compressed.
- // Whether it actually will be compressed is controlled on per-device basis.
- compressible: true,
}
apex_defaults {
@@ -321,6 +320,9 @@
art_runtime_run_test_libs +
art_runtime_debug_native_shared_libs +
libcore_debug_native_shared_libs,
+ jni_libs: [
+ "libartserviced",
+ ],
multilib: {
both: {
binaries: art_tools_debug_binaries_both +
@@ -358,6 +360,7 @@
file_contexts: ":com.android.art-file_contexts",
certificate: ":com.android.art.certificate",
installable: false,
+ compressible: false,
}
apex_test {
@@ -395,6 +398,7 @@
// ART gtests with dependencies on internal ART APEX libraries.
art_gtests = [
+ "art_artd_tests",
"art_cmdline_tests",
"art_compiler_tests",
"art_dex2oat_tests",
@@ -403,6 +407,7 @@
"art_dexdump_tests",
"art_dexlayout_tests",
"art_dexlist_tests",
+ "art_disassembler_tests",
"art_dexoptanalyzer_tests",
"art_imgdiag_tests",
"art_libartbase_tests",
@@ -430,7 +435,21 @@
certificate: ":com.android.art.certificate",
tests: art_gtests,
binaries: ["signal_dumper"], // Need signal_dumper for run-tests.
+ // Mark this test APEX as non-updatable, as its contains
+ // additional files (used only for testing) that would not pass
+ // dependency checks performed on updatable APEXes (see
+ // go/apex-allowed-deps-error).
updatable: false,
+ // Because this APEX is non-updatable, some of its native shared
+ // libraries (implicitly added as dependencies) are eligible to
+ // the symlink optimization. As we want this APEX to be
+ // self-contained (for testing purposes), we want to package
+ // these dependencies in this APEX, instead of symbolic links to
+ // their counterparts on the `system` partition, which may not
+ // even exist, as in the case of `libbacktrace` (see b/232790938
+ // and b/233357459). Marking this APEX as "future updatable"
+ // disables all symlink optimizations for it.
+ future_updatable: true,
}
// TODO: Do this better. art_apex_test_host will disable host builds when
diff --git a/build/apex/art_apex_test.py b/build/apex/art_apex_test.py
index 428baf4..0ee79bf 100755
--- a/build/apex/art_apex_test.py
+++ b/build/apex/art_apex_test.py
@@ -217,6 +217,8 @@
return False, 'Could not find %s'
if fs_object.is_dir:
return False, '%s is a directory'
+ if fs_object.is_symlink:
+ return False, '%s is a symlink'
return True, ''
def is_dir(self, path):
@@ -272,17 +274,22 @@
def check_art_test_executable(self, filename, multilib=None):
dirs = self.arch_dirs_for_path(ART_TEST_DIR, multilib)
if not dirs:
- self.fail('ART test binary missing: %s', filename)
+ self.fail('Directories for ART test binary missing: %s', filename)
+ return
for dir in dirs:
test_path = '%s/%s' % (dir, filename)
self._expected_file_globs.add(test_path)
- if not self._provider.get(test_path).is_exec:
+ file_obj = self._provider.get(test_path)
+ if not file_obj:
+ self.fail('ART test binary missing: %s', test_path)
+ elif not file_obj.is_exec:
self.fail('%s is not executable', test_path)
def check_art_test_data(self, filename):
dirs = self.arch_dirs_for_path(ART_TEST_DIR)
if not dirs:
- self.fail('ART test data missing: %s', filename)
+ self.fail('Directories for ART test data missing: %s', filename)
+ return
for dir in dirs:
if not self.check_file('%s/%s' % (dir, filename)):
return
@@ -471,7 +478,6 @@
self._checker.check_native_library('libart-disassembler')
self._checker.check_native_library('libartbase')
self._checker.check_native_library('libartpalette')
- self._checker.check_native_library('libartservice')
self._checker.check_native_library('libarttools')
self._checker.check_native_library('libdt_fd_forward')
self._checker.check_native_library('libopenjdkjvm')
@@ -504,7 +510,6 @@
# catch invalid dependencies on /system or other APEXes that should go
# through an exported library with stubs (b/128708192 tracks implementing a
# better approach for that).
- self._checker.check_native_library('libbacktrace')
self._checker.check_native_library('libbase')
self._checker.check_native_library('libc++')
self._checker.check_native_library('libdt_socket')
@@ -550,8 +555,8 @@
self._checker.check_symlinked_multilib_executable('dex2oat')
# Check internal libraries for ART.
+ self._checker.check_native_library('libartservice')
self._checker.check_native_library('libperfetto_hprof')
- self._checker.check_prefer64_library('artd-aidl-ndk')
# Check internal Java libraries
self._checker.check_java_library("service-art")
@@ -637,6 +642,7 @@
self._checker.check_symlinked_multilib_executable('dex2oatd')
# Check ART internal libraries.
+ self._checker.check_native_library('libartserviced')
self._checker.check_native_library('libperfetto_hprofd')
# Check internal native library dependencies.
@@ -664,6 +670,7 @@
def run(self):
# Check ART test binaries.
+ self._checker.check_art_test_executable('art_artd_tests')
self._checker.check_art_test_executable('art_cmdline_tests')
self._checker.check_art_test_executable('art_compiler_tests')
self._checker.check_art_test_executable('art_dex2oat_tests')
@@ -673,6 +680,7 @@
self._checker.check_art_test_executable('art_dexlayout_tests')
self._checker.check_art_test_executable('art_dexlist_tests')
self._checker.check_art_test_executable('art_dexoptanalyzer_tests')
+ self._checker.check_art_test_executable('art_disassembler_tests')
self._checker.check_art_test_executable('art_imgdiag_tests')
self._checker.check_art_test_executable('art_libartbase_tests')
self._checker.check_art_test_executable('art_libartpalette_tests')
@@ -698,6 +706,7 @@
# Check ART jar files which are needed for gtests.
self._checker.check_art_test_data('art-gtest-jars-AbstractMethod.jar')
+ self._checker.check_art_test_data('art-gtest-jars-ArrayClassWithUnresolvedComponent.dex')
self._checker.check_art_test_data('art-gtest-jars-MyClassNatives.jar')
self._checker.check_art_test_data('art-gtest-jars-Main.jar')
self._checker.check_art_test_data('art-gtest-jars-ProtoCompare.jar')
@@ -749,6 +758,7 @@
self._checker.check_art_test_data('art-gtest-jars-MainEmptyUncompressed.jar')
self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexTestDex.jar')
self._checker.check_art_test_data('art-gtest-jars-Dex2oatVdexPublicSdkDex.dex')
+ self._checker.check_art_test_data('art-gtest-jars-SuperWithAccessChecks.dex')
class NoSuperfluousBinariesChecker:
diff --git a/build/apex/manifest-art.json b/build/apex/manifest-art.json
index 9f2962e..4f20be6 100644
--- a/build/apex/manifest-art.json
+++ b/build/apex/manifest-art.json
@@ -1,6 +1,10 @@
{
"name": "com.android.art",
- "version": 330400000,
+
+ // Placeholder module version to be replaced during build.
+ // Do not change!
+ "version": 0,
+
"provideNativeLibs": [
"libjdwp.so"
],
diff --git a/build/art.go b/build/art.go
index 7914950..383517b 100644
--- a/build/art.go
+++ b/build/art.go
@@ -39,8 +39,7 @@
cflags = append(cflags, opt)
tlab := false
-
- gcType := ctx.Config().GetenvWithDefault("ART_DEFAULT_GC_TYPE", "CMS")
+ gcType := ctx.Config().GetenvWithDefault("ART_DEFAULT_GC_TYPE", "CMC")
if ctx.Config().IsEnvTrue("ART_TEST_DEBUG_GC") {
gcType = "SS"
@@ -48,9 +47,6 @@
}
cflags = append(cflags, "-DART_DEFAULT_GC_TYPE_IS_"+gcType)
- if tlab {
- cflags = append(cflags, "-DART_USE_TLAB=1")
- }
if ctx.Config().IsEnvTrue("ART_HEAP_POISONING") {
cflags = append(cflags, "-DART_HEAP_POISONING=1")
@@ -70,10 +66,21 @@
asflags = append(asflags,
"-DART_USE_READ_BARRIER=1",
"-DART_READ_BARRIER_TYPE_IS_"+barrierType+"=1")
+
+ if !ctx.Config().IsEnvFalse("ART_USE_GENERATIONAL_CC") {
+ cflags = append(cflags, "-DART_USE_GENERATIONAL_CC=1")
+ }
+ // For now force CC as we don't want to make userfaultfd GC the default.
+ // Eventually, make it such that we force CC only if ART_USE_READ_BARRIER
+ // was set to true explicitly during build time.
+ cflags = append(cflags, "-DART_FORCE_USE_READ_BARRIER=1")
+ tlab = true
+ } else if gcType == "CMC" {
+ tlab = true
}
- if !ctx.Config().IsEnvFalse("ART_USE_GENERATIONAL_CC") {
- cflags = append(cflags, "-DART_USE_GENERATIONAL_CC=1")
+ if tlab {
+ cflags = append(cflags, "-DART_USE_TLAB=1")
}
cdexLevel := ctx.Config().GetenvWithDefault("ART_DEFAULT_COMPACT_DEX_LEVEL", "fast")
@@ -132,7 +139,7 @@
)
cflags = append(cflags, "-DART_BASE_ADDRESS="+ctx.Config().LibartImgDeviceBaseAddress())
- minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA", "-0x1000000")
+ minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_TARGET_MIN_BASE_ADDRESS_DELTA", "(-0x1000000)")
maxDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_TARGET_MAX_BASE_ADDRESS_DELTA", "0x1000000")
cflags = append(cflags, "-DART_BASE_ADDRESS_MIN_DELTA="+minDelta)
cflags = append(cflags, "-DART_BASE_ADDRESS_MAX_DELTA="+maxDelta)
@@ -154,7 +161,7 @@
)
cflags = append(cflags, "-DART_BASE_ADDRESS="+ctx.Config().LibartImgHostBaseAddress())
- minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA", "-0x1000000")
+ minDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_HOST_MIN_BASE_ADDRESS_DELTA", "(-0x1000000)")
maxDelta := ctx.Config().GetenvWithDefault("LIBART_IMG_HOST_MAX_BASE_ADDRESS_DELTA", "0x1000000")
cflags = append(cflags, "-DART_BASE_ADDRESS_MIN_DELTA="+minDelta)
cflags = append(cflags, "-DART_BASE_ADDRESS_MAX_DELTA="+maxDelta)
@@ -165,7 +172,7 @@
}
clang_path := filepath.Join(config.ClangDefaultBase, ctx.Config().PrebuiltOS(), config.ClangDefaultVersion)
- cflags = append(cflags, "-DART_CLANG_PATH=\""+clang_path+"\"")
+ cflags = append(cflags, fmt.Sprintf("-DART_CLANG_PATH=\"%s\"", clang_path))
return cflags
}
@@ -305,7 +312,7 @@
// The 'key' is the file in testcases and 'value' is the path to copy it from.
// The actual copy will be done in make since soong does not do installations.
func addTestcasesFile(ctx android.InstallHookContext) {
- if ctx.Os() != ctx.Config().BuildOS || ctx.Module().IsSkipInstall() {
+ if ctx.Os() != ctx.Config().BuildOS || ctx.Target().HostCross || ctx.Module().IsSkipInstall() {
return
}
@@ -456,7 +463,8 @@
}
func artTest() android.Module {
- module := cc.TestFactory()
+ // Disable bp2build.
+ module := cc.NewTest(android.HostAndDeviceSupported, false /* bazelable */).Init()
installCodegenCustomizer(module, binary)
diff --git a/build/sdk/Android.bp b/build/sdk/Android.bp
index a9ee675..b47bdc2 100644
--- a/build/sdk/Android.bp
+++ b/build/sdk/Android.bp
@@ -93,17 +93,10 @@
},
android: {
- bootclasspath_fragments: [
- // Adds the fragment and its contents to the sdk.
- "art-bootclasspath-fragment",
- ],
-
- systemserverclasspath_fragments: [
- "art-systemserverclasspath-fragment",
- ],
-
- compat_configs: [
- "libcore-platform-compat-config",
+ apexes: [
+ // Adds exportable dependencies of the API to the sdk,
+ // e.g. *classpath_fragments.
+ "com.android.art",
],
java_header_libs: [
diff --git a/cmdline/cmdline_types.h b/cmdline/cmdline_types.h
index dc2f8b7..b16f069 100644
--- a/cmdline/cmdline_types.h
+++ b/cmdline/cmdline_types.h
@@ -527,6 +527,8 @@
return gc::kCollectorTypeSS;
} else if (option == "CC") {
return gc::kCollectorTypeCC;
+ } else if (option == "CMC") {
+ return gc::kCollectorTypeCMC;
} else {
return gc::kCollectorTypeNone;
}
@@ -539,7 +541,7 @@
bool verify_pre_gc_heap_ = false;
bool verify_pre_sweeping_heap_ = kIsDebugBuild;
bool generational_cc = kEnableGenerationalCCByDefault;
- bool verify_post_gc_heap_ = false;
+ bool verify_post_gc_heap_ = kIsDebugBuild;
bool verify_pre_gc_rosalloc_ = kIsDebugBuild;
bool verify_pre_sweeping_rosalloc_ = false;
bool verify_post_gc_rosalloc_ = false;
diff --git a/cmdline/detail/cmdline_parse_argument_detail.h b/cmdline/detail/cmdline_parse_argument_detail.h
index de0a588..936d290 100644
--- a/cmdline/detail/cmdline_parse_argument_detail.h
+++ b/cmdline/detail/cmdline_parse_argument_detail.h
@@ -150,7 +150,7 @@
for (auto cname : names_) {
std::string_view name = cname;
if (using_blanks_) {
- name = name.substr(0, name.find("_"));
+ name = name.substr(0, name.find('_'));
}
auto& os = vios.Stream();
auto print_once = [&]() {
diff --git a/cmdline/token_range.h b/cmdline/token_range.h
index e28ead9..e917e1d 100644
--- a/cmdline/token_range.h
+++ b/cmdline/token_range.h
@@ -81,7 +81,7 @@
{}
// Non-copying constructor. Retain reference to existing list of tokens.
- TokenRange(std::shared_ptr<TokenList> token_list,
+ TokenRange(const std::shared_ptr<TokenList>& token_list,
TokenList::const_iterator it_begin,
TokenList::const_iterator it_end)
: token_list_(token_list),
@@ -98,7 +98,7 @@
TokenRange(TokenRange&&) = default;
// Non-copying constructor. Retains reference to an existing list of tokens, with offset.
- explicit TokenRange(std::shared_ptr<TokenList> token_list)
+ explicit TokenRange(const std::shared_ptr<TokenList>& token_list)
: token_list_(token_list),
begin_(token_list_->begin()),
end_(token_list_->end())
diff --git a/cmdline/unit.h b/cmdline/unit.h
index f73981f..0b5c344 100644
--- a/cmdline/unit.h
+++ b/cmdline/unit.h
@@ -21,13 +21,7 @@
// Used for arguments that simply indicate presence (e.g. "-help") without any values.
struct Unit {
- // Historical note: We specified a user-defined constructor to avoid
- // 'Conditional jump or move depends on uninitialised value(s)' errors
- // when running Valgrind.
- Unit() {}
- Unit(const Unit&) = default;
- ~Unit() {}
- bool operator==(Unit) const {
+ bool operator==(const Unit&) const {
return true;
}
};
diff --git a/compiler/Android.bp b/compiler/Android.bp
index de98fdb..8916fa1 100644
--- a/compiler/Android.bp
+++ b/compiler/Android.bp
@@ -465,8 +465,8 @@
],
shared_libs: [
- "libbacktrace",
"libnativeloader",
+ "libunwindstack",
],
target: {
diff --git a/compiler/driver/compiler_options.cc b/compiler/driver/compiler_options.cc
index 51cd999..a531bc9 100644
--- a/compiler/driver/compiler_options.cc
+++ b/compiler/driver/compiler_options.cc
@@ -23,6 +23,7 @@
#include "arch/instruction_set.h"
#include "arch/instruction_set_features.h"
+#include "art_method-inl.h"
#include "base/runtime_debug.h"
#include "base/string_view_cpp20.h"
#include "base/variant_map.h"
@@ -146,14 +147,39 @@
bool CompilerOptions::IsImageClass(const char* descriptor) const {
// Historical note: We used to hold the set indirectly and there was a distinction between an
- // empty set and a null, null meaning to include all classes. However, the distiction has been
+ // empty set and a null, null meaning to include all classes. However, the distinction has been
// removed; if we don't have a profile, we treat it as an empty set of classes. b/77340429
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();
+}
+
const VerificationResults* CompilerOptions::GetVerificationResults() const {
DCHECK(Runtime::Current()->IsAotCompiler());
return verification_results_;
}
+bool CompilerOptions::ShouldCompileWithClinitCheck(ArtMethod* method) const {
+ if (method != nullptr &&
+ Runtime::Current()->IsAotCompiler() &&
+ method->IsStatic() &&
+ !method->IsConstructor() &&
+ // Compiled code for native methods never do a clinit check, so we may put the resolution
+ // trampoline for native methods. This means that it's possible post zygote fork for the
+ // entry to be dirtied. We could resolve this by either:
+ // - Make these methods use the generic JNI entrypoint, but that's not
+ // desirable for a method that is in the profile.
+ // - Ensure the declaring class of such native methods are always in the
+ // preloaded-classes list.
+ // - Emit the clinit check in the compiled code of native methods.
+ !method->IsNative()) {
+ ScopedObjectAccess soa(Thread::Current());
+ ObjPtr<mirror::Class> cls = method->GetDeclaringClass<kWithoutReadBarrier>();
+ return cls->IsInBootImageAndNotInPreloadedClasses();
+ }
+ return false;
+}
+
} // namespace art
diff --git a/compiler/driver/compiler_options.h b/compiler/driver/compiler_options.h
index 1bffdb1..20f54bd 100644
--- a/compiler/driver/compiler_options.h
+++ b/compiler/driver/compiler_options.h
@@ -44,6 +44,7 @@
class Arm64RelativePatcherTest;
} // namespace linker
+class ArtMethod;
class DexFile;
enum class InstructionSet;
class InstructionSetFeatures;
@@ -300,6 +301,10 @@
bool IsImageClass(const char* descriptor) const;
+ // Returns whether the given `pretty_descriptor` is in the list of preloaded
+ // classes. `pretty_descriptor` should be the result of calling `PrettyDescriptor`.
+ bool IsPreloadedClass(const char* pretty_descriptor) const;
+
const VerificationResults* GetVerificationResults() const;
bool ParseCompilerOptions(const std::vector<std::string>& options,
@@ -383,6 +388,12 @@
return ContainsElement(GetDexFilesForOatFile(), dex_file);
}
+ // If this is a static non-constructor method in the boot classpath, and its class isn't
+ // initialized at compile-time, or won't be initialized by the zygote, add
+ // initialization checks at entry. This will avoid the need of trampolines
+ // which at runtime we will need to dirty after initialization.
+ bool ShouldCompileWithClinitCheck(ArtMethod* method) const;
+
private:
bool ParseDumpInitFailures(const std::string& option, std::string* error_msg);
bool ParseRegisterAllocationStrategy(const std::string& option, std::string* error_msg);
@@ -408,6 +419,10 @@
// Must not be empty for real boot image, only for tests pretending to compile boot image.
HashSet<std::string> image_classes_;
+ // Classes listed in the preloaded-classes file, used for boot image and
+ // boot image extension compilation.
+ HashSet<std::string> preloaded_classes_;
+
// Results of AOT verification.
const VerificationResults* verification_results_;
diff --git a/compiler/exception_test.cc b/compiler/exception_test.cc
index 495398b..a49c6c6 100644
--- a/compiler/exception_test.cc
+++ b/compiler/exception_test.cc
@@ -78,7 +78,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stack_maps(&allocator, kRuntimeISA);
- stack_maps.BeginMethod(4 * sizeof(void*), 0u, 0u, 0u);
+ stack_maps.BeginMethod(/* frame_size_in_bytes= */ 4 * sizeof(void*),
+ /* core_spill_mask= */ 0u,
+ /* fp_spill_mask= */ 0u,
+ /* num_dex_registers= */ 0u,
+ /* baseline= */ false,
+ /* debuggable= */ false);
stack_maps.BeginStackMapEntry(kDexPc, native_pc_offset);
stack_maps.EndStackMapEntry();
stack_maps.EndMethod(code_size);
@@ -187,14 +192,16 @@
fake_stack.push_back(0);
}
- fake_stack.push_back(method_g_->GetOatQuickMethodHeader(0)->ToNativeQuickPc(
+ OatQuickMethodHeader* header = OatQuickMethodHeader::FromEntryPoint(
+ method_g_->GetEntryPointFromQuickCompiledCode());
+ fake_stack.push_back(header->ToNativeQuickPc(
method_g_, kDexPc, /* is_for_catch_handler= */ false)); // return pc
// Create/push fake 16byte stack frame for method g
fake_stack.push_back(reinterpret_cast<uintptr_t>(method_g_));
fake_stack.push_back(0);
fake_stack.push_back(0);
- fake_stack.push_back(method_g_->GetOatQuickMethodHeader(0)->ToNativeQuickPc(
+ fake_stack.push_back(header->ToNativeQuickPc(
method_g_, kDexPc, /* is_for_catch_handler= */ false)); // return pc
// Create/push fake 16byte stack frame for method f
diff --git a/compiler/jit/jit_compiler.cc b/compiler/jit/jit_compiler.cc
index 7002636..e578d3b 100644
--- a/compiler/jit/jit_compiler.cc
+++ b/compiler/jit/jit_compiler.cc
@@ -85,7 +85,7 @@
if (StartsWith(option, "--instruction-set-variant=")) {
const char* str = option.c_str() + strlen("--instruction-set-variant=");
VLOG(compiler) << "JIT instruction set variant " << str;
- instruction_set_features = InstructionSetFeatures::FromVariant(
+ instruction_set_features = InstructionSetFeatures::FromVariantAndHwcap(
instruction_set, str, &error_msg);
if (instruction_set_features == nullptr) {
LOG(WARNING) << "Error parsing " << option << " message=" << error_msg;
diff --git a/compiler/jni/jni_cfi_test.cc b/compiler/jni/jni_cfi_test.cc
index 9e3bb86..368b87c 100644
--- a/compiler/jni/jni_cfi_test.cc
+++ b/compiler/jni/jni_cfi_test.cc
@@ -124,22 +124,31 @@
TestImpl(InstructionSet::isa, #isa, expected_asm, expected_cfi); \
}
+// We can't use compile-time macros for read-barrier as the introduction
+// of userfaultfd-GC has made it a runtime choice.
+#define TEST_ISA_ONLY_CC(isa) \
+ TEST_F(JNICFITest, isa) { \
+ if (kUseBakerReadBarrier && gUseReadBarrier) { \
+ std::vector<uint8_t> expected_asm(expected_asm_##isa, \
+ expected_asm_##isa + arraysize(expected_asm_##isa)); \
+ std::vector<uint8_t> expected_cfi(expected_cfi_##isa, \
+ expected_cfi_##isa + arraysize(expected_cfi_##isa)); \
+ TestImpl(InstructionSet::isa, #isa, expected_asm, expected_cfi); \
+ } \
+ }
+
#ifdef ART_ENABLE_CODEGEN_arm
// Run the tests for ARM only with Baker read barriers, as the
// expected generated code contains a Marking Register refresh
// instruction.
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
-TEST_ISA(kThumb2)
-#endif
+TEST_ISA_ONLY_CC(kThumb2)
#endif
#ifdef ART_ENABLE_CODEGEN_arm64
// Run the tests for ARM64 only with Baker read barriers, as the
// expected generated code contains a Marking Register refresh
// instruction.
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
-TEST_ISA(kArm64)
-#endif
+TEST_ISA_ONLY_CC(kArm64)
#endif
#ifdef ART_ENABLE_CODEGEN_x86
diff --git a/compiler/jni/jni_compiler_test.cc b/compiler/jni/jni_compiler_test.cc
index 0a1f017..d05324b 100644
--- a/compiler/jni/jni_compiler_test.cc
+++ b/compiler/jni/jni_compiler_test.cc
@@ -414,7 +414,7 @@
CHECK(!caller->IsCriticalNative());
CHECK(caller->IsSynchronized());
ObjPtr<mirror::Object> lock;
- if (self->GetManagedStack()->GetTopQuickFrameTag()) {
+ if (self->GetManagedStack()->GetTopQuickFrameGenericJniTag()) {
// Generic JNI.
lock = GetGenericJniSynchronizationObject(self, caller);
} else if (caller->IsStatic()) {
diff --git a/compiler/jni/quick/jni_compiler.cc b/compiler/jni/quick/jni_compiler.cc
index 6cb5021..c8be993 100644
--- a/compiler/jni/quick/jni_compiler.cc
+++ b/compiler/jni/quick/jni_compiler.cc
@@ -36,7 +36,9 @@
#include "dex/dex_file-inl.h"
#include "driver/compiler_options.h"
#include "entrypoints/quick/quick_entrypoints.h"
+#include "instrumentation.h"
#include "jni/jni_env_ext.h"
+#include "runtime.h"
#include "thread.h"
#include "utils/arm/managed_register_arm.h"
#include "utils/arm64/managed_register_arm64.h"
@@ -101,6 +103,20 @@
// i.e. if the method was annotated with @CriticalNative
const bool is_critical_native = (access_flags & kAccCriticalNative) != 0u;
+ bool needs_entry_exit_hooks =
+ compiler_options.GetDebuggable() && compiler_options.IsJitCompiler();
+ // We don't support JITing stubs for critical native methods in debuggable runtimes yet.
+ // TODO(mythria): Add support required for calling method entry / exit hooks from critical native
+ // methods.
+ DCHECK_IMPLIES(needs_entry_exit_hooks, !is_critical_native);
+
+ // When walking the stack the top frame doesn't have a pc associated with it. We then depend on
+ // the invariant that we don't have JITed code when AOT code is available. In debuggable runtimes
+ // this invariant doesn't hold. So we tag the SP for JITed code to indentify if we are executing
+ // JITed code or AOT code. Since tagging involves additional instructions we tag only in
+ // debuggable runtimes.
+ bool should_tag_sp = needs_entry_exit_hooks;
+
VLOG(jni) << "JniCompile: Method :: "
<< dex_file.PrettyMethod(method_idx, /* with signature */ true)
<< " :: access_flags = " << std::hex << access_flags << std::dec;
@@ -182,7 +198,7 @@
// Skip this for @CriticalNative because we're not passing a `jclass` to the native method.
std::unique_ptr<JNIMacroLabel> jclass_read_barrier_slow_path;
std::unique_ptr<JNIMacroLabel> jclass_read_barrier_return;
- if (kUseReadBarrier && is_static && LIKELY(!is_critical_native)) {
+ if (gUseReadBarrier && is_static && LIKELY(!is_critical_native)) {
jclass_read_barrier_slow_path = __ CreateLabel();
jclass_read_barrier_return = __ CreateLabel();
@@ -219,7 +235,22 @@
// because garbage collections are disabled within the execution of a
// @CriticalNative method.
if (LIKELY(!is_critical_native)) {
- __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>());
+ __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>(), should_tag_sp);
+ }
+
+ // 1.5. Call any method entry hooks if required.
+ // For critical native methods, we don't JIT stubs in debuggable runtimes (see
+ // OptimizingCompiler::JitCompile).
+ // TODO(mythria): Add support to call method entry / exit hooks for critical native methods too.
+ std::unique_ptr<JNIMacroLabel> method_entry_hook_slow_path;
+ std::unique_ptr<JNIMacroLabel> method_entry_hook_return;
+ if (UNLIKELY(needs_entry_exit_hooks)) {
+ uint64_t address = reinterpret_cast64<uint64_t>(Runtime::Current()->GetInstrumentation());
+ int offset = instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value();
+ method_entry_hook_slow_path = __ CreateLabel();
+ method_entry_hook_return = __ CreateLabel();
+ __ TestByteAndJumpIfNotZero(address + offset, method_entry_hook_slow_path.get());
+ __ Bind(method_entry_hook_return.get());
}
// 2. Lock the object (if synchronized) and transition out of Runnable (if normal native).
@@ -532,7 +563,21 @@
__ Bind(suspend_check_resume.get());
}
- // 7.5. Remove activation - need to restore callee save registers since the GC
+ // 7.5. Check if method exit hooks needs to be called
+ // For critical native methods, we don't JIT stubs in debuggable runtimes.
+ // TODO(mythria): Add support to call method entry / exit hooks for critical native methods too.
+ std::unique_ptr<JNIMacroLabel> method_exit_hook_slow_path;
+ std::unique_ptr<JNIMacroLabel> method_exit_hook_return;
+ if (UNLIKELY(needs_entry_exit_hooks)) {
+ uint64_t address = reinterpret_cast64<uint64_t>(Runtime::Current()->GetInstrumentation());
+ int offset = instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value();
+ method_exit_hook_slow_path = __ CreateLabel();
+ method_exit_hook_return = __ CreateLabel();
+ __ TestByteAndJumpIfNotZero(address + offset, method_exit_hook_slow_path.get());
+ __ Bind(method_exit_hook_return.get());
+ }
+
+ // 7.6. Remove activation - need to restore callee save registers since the GC
// may have changed them.
DCHECK_EQ(jni_asm->cfi().GetCurrentCFAOffset(), static_cast<int>(current_frame_size));
if (LIKELY(!is_critical_native) || !main_jni_conv->UseTailCall()) {
@@ -547,7 +592,7 @@
// 8.1. Read barrier slow path for the declaring class in the method for a static call.
// Skip this for @CriticalNative because we're not passing a `jclass` to the native method.
- if (kUseReadBarrier && is_static && !is_critical_native) {
+ if (gUseReadBarrier && is_static && !is_critical_native) {
__ Bind(jclass_read_barrier_slow_path.get());
// Construct slow path for read barrier:
@@ -605,7 +650,7 @@
if (reference_return) {
// Suspend check entry point overwrites top of managed stack and leaves it clobbered.
// We need to restore the top for subsequent runtime call to `JniDecodeReferenceResult()`.
- __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>());
+ __ StoreStackPointerToThread(Thread::TopOfManagedStackOffset<kPointerSize>(), should_tag_sp);
}
if (reference_return && main_out_arg_size != 0) {
__ IncreaseFrameSize(main_out_arg_size);
@@ -630,6 +675,24 @@
__ DeliverPendingException();
}
+ // 8.6. Method entry / exit hooks slow paths.
+ if (UNLIKELY(needs_entry_exit_hooks)) {
+ __ Bind(method_entry_hook_slow_path.get());
+ // Use Jni specific method entry hook that saves all the arguments. We have only saved the
+ // callee save registers at this point. So go through Jni specific stub that saves the rest
+ // of the live registers.
+ __ CallFromThread(QUICK_ENTRYPOINT_OFFSET(kPointerSize, pJniMethodEntryHook));
+ __ ExceptionPoll(exception_slow_path.get());
+ __ Jump(method_entry_hook_return.get());
+
+ __ Bind(method_exit_hook_slow_path.get());
+ // Method exit hooks is called just before tearing down the frame. So there are no live
+ // registers and we can directly call the method exit hook and don't need a Jni specific
+ // entrypoint.
+ __ CallFromThread(QUICK_ENTRYPOINT_OFFSET(kPointerSize, pMethodExitHook));
+ __ Jump(method_exit_hook_return.get());
+ }
+
// 9. Finalize code generation.
__ FinalizeCode();
size_t cs = __ CodeSize();
diff --git a/compiler/optimizing/bounds_check_elimination.cc b/compiler/optimizing/bounds_check_elimination.cc
index dad3c81..0b11a6f 100644
--- a/compiler/optimizing/bounds_check_elimination.cc
+++ b/compiler/optimizing/bounds_check_elimination.cc
@@ -1818,6 +1818,7 @@
HInstruction* condition,
bool is_null_check = false) {
HInstruction* suspend = loop->GetSuspendCheck();
+ DCHECK(suspend != nullptr);
block->InsertInstructionBefore(condition, block->GetLastInstruction());
DeoptimizationKind kind =
is_null_check ? DeoptimizationKind::kLoopNullBCE : DeoptimizationKind::kLoopBoundsBCE;
diff --git a/compiler/optimizing/cha_guard_optimization.cc b/compiler/optimizing/cha_guard_optimization.cc
index c6232ef..d231593 100644
--- a/compiler/optimizing/cha_guard_optimization.cc
+++ b/compiler/optimizing/cha_guard_optimization.cc
@@ -200,6 +200,7 @@
block->RemoveInstruction(deopt);
HInstruction* suspend = loop_info->GetSuspendCheck();
+ DCHECK(suspend != nullptr);
// Need a new deoptimize instruction that copies the environment
// of the suspend instruction for the loop.
HDeoptimize* deoptimize = new (GetGraph()->GetAllocator()) HDeoptimize(
diff --git a/compiler/optimizing/code_generator.cc b/compiler/optimizing/code_generator.cc
index 27eabaf..252e756 100644
--- a/compiler/optimizing/code_generator.cc
+++ b/compiler/optimizing/code_generator.cc
@@ -389,7 +389,8 @@
core_spill_mask_,
fpu_spill_mask_,
GetGraph()->GetNumberOfVRegs(),
- GetGraph()->IsCompilingBaseline());
+ GetGraph()->IsCompilingBaseline(),
+ GetGraph()->IsDebuggable());
size_t frame_start = GetAssembler()->CodeSize();
GenerateFrameEntry();
@@ -412,7 +413,13 @@
for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
HInstruction* current = it.Current();
if (current->HasEnvironment()) {
- // Create stackmap for HNativeDebugInfo or any instruction which calls native code.
+ // Catch StackMaps are dealt with later on in `RecordCatchBlockInfo`.
+ if (block->IsCatchBlock() && block->GetFirstInstruction() == current) {
+ DCHECK(current->IsNop());
+ continue;
+ }
+
+ // Create stackmap for HNop or any instruction which calls native code.
// Note that we need correct mapping for the native PC of the call instruction,
// so the runtime's stackmap is not sufficient since it is at PC after the call.
MaybeRecordNativeDebugInfo(current, block->GetDexPc());
@@ -1123,7 +1130,7 @@
for (HBasicBlock* block : graph.GetReversePostOrder()) {
if (block->IsLoopHeader()) {
HSuspendCheck* suspend_check = block->GetLoopInformation()->GetSuspendCheck();
- if (!suspend_check->GetEnvironment()->IsFromInlinedInvoke()) {
+ if (suspend_check != nullptr && !suspend_check->GetEnvironment()->IsFromInlinedInvoke()) {
loop_headers.push_back(suspend_check);
}
}
@@ -1333,53 +1340,29 @@
continue;
}
- uint32_t dex_pc = block->GetDexPc();
- uint32_t num_vregs = graph_->GetNumberOfVRegs();
- uint32_t native_pc = GetAddressOf(block);
+ // Get the outer dex_pc
+ uint32_t outer_dex_pc = block->GetDexPc();
+ DCHECK(block->GetFirstInstruction()->IsNop());
+ DCHECK(block->GetFirstInstruction()->AsNop()->NeedsEnvironment());
+ HEnvironment* const environment = block->GetFirstInstruction()->GetEnvironment();
+ DCHECK(environment != nullptr);
+ HEnvironment* outer_environment = environment;
+ while (outer_environment->GetParent() != nullptr) {
+ outer_environment = outer_environment->GetParent();
+ }
+ outer_dex_pc = outer_environment->GetDexPc();
- stack_map_stream->BeginStackMapEntry(dex_pc,
+ uint32_t native_pc = GetAddressOf(block);
+ stack_map_stream->BeginStackMapEntry(outer_dex_pc,
native_pc,
/* register_mask= */ 0,
/* sp_mask= */ nullptr,
StackMap::Kind::Catch);
- HInstruction* current_phi = block->GetFirstPhi();
- for (size_t vreg = 0; vreg < num_vregs; ++vreg) {
- while (current_phi != nullptr && current_phi->AsPhi()->GetRegNumber() < vreg) {
- HInstruction* next_phi = current_phi->GetNext();
- DCHECK(next_phi == nullptr ||
- current_phi->AsPhi()->GetRegNumber() <= next_phi->AsPhi()->GetRegNumber())
- << "Phis need to be sorted by vreg number to keep this a linear-time loop.";
- current_phi = next_phi;
- }
-
- if (current_phi == nullptr || current_phi->AsPhi()->GetRegNumber() != vreg) {
- stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kNone, 0);
- } else {
- Location location = current_phi->GetLocations()->Out();
- switch (location.GetKind()) {
- case Location::kStackSlot: {
- stack_map_stream->AddDexRegisterEntry(
- DexRegisterLocation::Kind::kInStack, location.GetStackIndex());
- break;
- }
- case Location::kDoubleStackSlot: {
- stack_map_stream->AddDexRegisterEntry(
- DexRegisterLocation::Kind::kInStack, location.GetStackIndex());
- stack_map_stream->AddDexRegisterEntry(
- DexRegisterLocation::Kind::kInStack, location.GetHighStackIndex(kVRegSize));
- ++vreg;
- DCHECK_LT(vreg, num_vregs);
- break;
- }
- default: {
- // All catch phis must be allocated to a stack slot.
- LOG(FATAL) << "Unexpected kind " << location.GetKind();
- UNREACHABLE();
- }
- }
- }
- }
+ EmitEnvironment(environment,
+ /* slow_path= */ nullptr,
+ /* needs_vreg_info= */ true,
+ /* is_for_catch_handler= */ true);
stack_map_stream->EndStackMapEntry();
}
@@ -1390,7 +1373,9 @@
code_generation_data_->AddSlowPath(slow_path);
}
-void CodeGenerator::EmitVRegInfo(HEnvironment* environment, SlowPathCode* slow_path) {
+void CodeGenerator::EmitVRegInfo(HEnvironment* environment,
+ SlowPathCode* slow_path,
+ bool is_for_catch_handler) {
StackMapStream* stack_map_stream = GetStackMapStream();
// Walk over the environment, and record the location of dex registers.
for (size_t i = 0, environment_size = environment->Size(); i < environment_size; ++i) {
@@ -1445,6 +1430,7 @@
}
case Location::kRegister : {
+ DCHECK(!is_for_catch_handler);
int id = location.reg();
if (slow_path != nullptr && slow_path->IsCoreRegisterSaved(id)) {
uint32_t offset = slow_path->GetStackOffsetOfCoreRegister(id);
@@ -1466,6 +1452,7 @@
}
case Location::kFpuRegister : {
+ DCHECK(!is_for_catch_handler);
int id = location.reg();
if (slow_path != nullptr && slow_path->IsFpuRegisterSaved(id)) {
uint32_t offset = slow_path->GetStackOffsetOfFpuRegister(id);
@@ -1487,6 +1474,7 @@
}
case Location::kFpuRegisterPair : {
+ DCHECK(!is_for_catch_handler);
int low = location.low();
int high = location.high();
if (slow_path != nullptr && slow_path->IsFpuRegisterSaved(low)) {
@@ -1508,6 +1496,7 @@
}
case Location::kRegisterPair : {
+ DCHECK(!is_for_catch_handler);
int low = location.low();
int high = location.high();
if (slow_path != nullptr && slow_path->IsCoreRegisterSaved(low)) {
@@ -1538,9 +1527,54 @@
}
}
+void CodeGenerator::EmitVRegInfoOnlyCatchPhis(HEnvironment* environment) {
+ StackMapStream* stack_map_stream = GetStackMapStream();
+ DCHECK(environment->GetHolder()->GetBlock()->IsCatchBlock());
+ DCHECK_EQ(environment->GetHolder()->GetBlock()->GetFirstInstruction(), environment->GetHolder());
+ HInstruction* current_phi = environment->GetHolder()->GetBlock()->GetFirstPhi();
+ for (size_t vreg = 0; vreg < environment->Size(); ++vreg) {
+ while (current_phi != nullptr && current_phi->AsPhi()->GetRegNumber() < vreg) {
+ HInstruction* next_phi = current_phi->GetNext();
+ DCHECK(next_phi == nullptr ||
+ current_phi->AsPhi()->GetRegNumber() <= next_phi->AsPhi()->GetRegNumber())
+ << "Phis need to be sorted by vreg number to keep this a linear-time loop.";
+ current_phi = next_phi;
+ }
+
+ if (current_phi == nullptr || current_phi->AsPhi()->GetRegNumber() != vreg) {
+ stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kNone, 0);
+ } else {
+ Location location = current_phi->GetLocations()->Out();
+ switch (location.GetKind()) {
+ case Location::kStackSlot: {
+ stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kInStack,
+ location.GetStackIndex());
+ break;
+ }
+ case Location::kDoubleStackSlot: {
+ stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kInStack,
+ location.GetStackIndex());
+ stack_map_stream->AddDexRegisterEntry(DexRegisterLocation::Kind::kInStack,
+ location.GetHighStackIndex(kVRegSize));
+ ++vreg;
+ DCHECK_LT(vreg, environment->Size());
+ break;
+ }
+ default: {
+ LOG(FATAL) << "All catch phis must be allocated to a stack slot. Unexpected kind "
+ << location.GetKind();
+ UNREACHABLE();
+ }
+ }
+ }
+ }
+}
+
void CodeGenerator::EmitEnvironment(HEnvironment* environment,
SlowPathCode* slow_path,
- bool needs_vreg_info) {
+ bool needs_vreg_info,
+ bool is_for_catch_handler,
+ bool innermost_environment) {
if (environment == nullptr) return;
StackMapStream* stack_map_stream = GetStackMapStream();
@@ -1548,7 +1582,11 @@
if (emit_inline_info) {
// We emit the parent environment first.
- EmitEnvironment(environment->GetParent(), slow_path, needs_vreg_info);
+ EmitEnvironment(environment->GetParent(),
+ slow_path,
+ needs_vreg_info,
+ is_for_catch_handler,
+ /* innermost_environment= */ false);
stack_map_stream->BeginInlineInfoEntry(environment->GetMethod(),
environment->GetDexPc(),
needs_vreg_info ? environment->Size() : 0,
@@ -1556,9 +1594,13 @@
this);
}
+ // If a dex register map is not required we just won't emit it.
if (needs_vreg_info) {
- // If a dex register map is not required we just won't emit it.
- EmitVRegInfo(environment, slow_path);
+ if (innermost_environment && is_for_catch_handler) {
+ EmitVRegInfoOnlyCatchPhis(environment);
+ } else {
+ EmitVRegInfo(environment, slow_path, is_for_catch_handler);
+ }
}
if (emit_inline_info) {
@@ -1671,7 +1713,7 @@
// When (non-Baker) read barriers are enabled, some instructions
// use a slow path to emit a read barrier, which does not trigger
// GC.
- (kEmitCompilerReadBarrier &&
+ (gUseReadBarrier &&
!kUseBakerReadBarrier &&
(instruction->IsInstanceFieldGet() ||
instruction->IsPredicatedInstanceFieldGet() ||
diff --git a/compiler/optimizing/code_generator.h b/compiler/optimizing/code_generator.h
index d81a7b5..6b85aaa 100644
--- a/compiler/optimizing/code_generator.h
+++ b/compiler/optimizing/code_generator.h
@@ -33,9 +33,11 @@
#include "graph_visualizer.h"
#include "locations.h"
#include "nodes.h"
+#include "oat_quick_method_header.h"
#include "optimizing_compiler_stats.h"
#include "read_barrier_option.h"
#include "stack.h"
+#include "subtype_check.h"
#include "utils/assembler.h"
#include "utils/label.h"
@@ -56,8 +58,16 @@
// Maximum value for a primitive long.
static int64_t constexpr kPrimLongMax = INT64_C(0x7fffffffffffffff);
-static constexpr ReadBarrierOption kCompilerReadBarrierOption =
- kEmitCompilerReadBarrier ? kWithReadBarrier : kWithoutReadBarrier;
+static const ReadBarrierOption gCompilerReadBarrierOption =
+ gUseReadBarrier ? kWithReadBarrier : kWithoutReadBarrier;
+
+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);
class Assembler;
class CodeGenerator;
@@ -460,7 +470,7 @@
// If the target class is in the boot image, it's non-moveable and it doesn't matter
// if we compare it with a from-space or to-space reference, the result is the same.
// It's OK to traverse a class hierarchy jumping between from-space and to-space.
- return kEmitCompilerReadBarrier && !instance_of->GetTargetClass()->IsInBootImage();
+ return gUseReadBarrier && !instance_of->GetTargetClass()->IsInBootImage();
}
static ReadBarrierOption ReadBarrierOptionForInstanceOf(HInstanceOf* instance_of) {
@@ -475,7 +485,7 @@
case TypeCheckKind::kArrayObjectCheck:
case TypeCheckKind::kInterfaceCheck: {
bool needs_read_barrier =
- kEmitCompilerReadBarrier && !check_cast->GetTargetClass()->IsInBootImage();
+ gUseReadBarrier && !check_cast->GetTargetClass()->IsInBootImage();
// We do not emit read barriers for HCheckCast, so we can get false negatives
// and the slow path shall re-check and simply return if the cast is actually OK.
return !needs_read_barrier;
@@ -678,7 +688,7 @@
return LocationSummary::kCallOnMainOnly;
case HLoadString::LoadKind::kJitTableAddress:
DCHECK(!load->NeedsEnvironment());
- return kEmitCompilerReadBarrier
+ return gUseReadBarrier
? LocationSummary::kCallOnSlowPath
: LocationSummary::kNoCall;
break;
@@ -836,8 +846,11 @@
void BlockIfInRegister(Location location, bool is_out = false) const;
void EmitEnvironment(HEnvironment* environment,
SlowPathCode* slow_path,
- bool needs_vreg_info = true);
- void EmitVRegInfo(HEnvironment* environment, SlowPathCode* slow_path);
+ bool needs_vreg_info = true,
+ bool is_for_catch_handler = false,
+ bool innermost_environment = true);
+ void EmitVRegInfo(HEnvironment* environment, SlowPathCode* slow_path, bool is_for_catch_handler);
+ void EmitVRegInfoOnlyCatchPhis(HEnvironment* environment);
static void PrepareCriticalNativeArgumentMoves(
HInvokeStaticOrDirect* invoke,
diff --git a/compiler/optimizing/code_generator_arm64.cc b/compiler/optimizing/code_generator_arm64.cc
index 2a0b481..17407a5 100644
--- a/compiler/optimizing/code_generator_arm64.cc
+++ b/compiler/optimizing/code_generator_arm64.cc
@@ -583,7 +583,7 @@
obj_(obj),
offset_(offset),
index_(index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// If `obj` is equal to `out` or `ref`, it means the initial object
// has been overwritten by (or after) the heap object reference load
// to be instrumented, e.g.:
@@ -762,7 +762,7 @@
public:
ReadBarrierForRootSlowPathARM64(HInstruction* instruction, Location out, Location root)
: SlowPathCodeARM64(instruction), out_(out), root_(root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
void EmitNativeCode(CodeGenerator* codegen) override {
@@ -1233,6 +1233,45 @@
void CodeGeneratorARM64::GenerateFrameEntry() {
MacroAssembler* masm = GetVIXLAssembler();
+
+ // Check if we need to generate the clinit check. We will jump to the
+ // resolution stub if the class is not initialized and the executing thread is
+ // not the thread initializing it.
+ // We do this before constructing the frame to get the correct stack trace if
+ // an exception is thrown.
+ if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+ UseScratchRegisterScope temps(masm);
+ vixl::aarch64::Label resolution;
+
+ Register temp1 = temps.AcquireW();
+ Register temp2 = temps.AcquireW();
+
+ // 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.
+ __ Ldr(temp1, MemOperand(kArtMethodRegister, ArtMethod::DeclaringClassOffset().Int32Value()));
+ __ Ldrb(temp2, HeapOperand(temp1, status_byte_offset));
+ __ Cmp(temp2, shifted_visibly_initialized_value);
+ __ B(hs, &frame_entry_label_);
+
+ // Check if we're initializing and the thread initializing is the one
+ // executing the code.
+ __ Cmp(temp2, shifted_initializing_value);
+ __ B(lo, &resolution);
+
+ __ Ldr(temp1, HeapOperand(temp1, mirror::Class::ClinitThreadIdOffset().Int32Value()));
+ __ Ldr(temp2, MemOperand(tr, Thread::TidOffset<kArm64PointerSize>().Int32Value()));
+ __ Cmp(temp1, temp2);
+ __ B(eq, &frame_entry_label_);
+ __ Bind(&resolution);
+
+ // Jump to the resolution stub.
+ ThreadOffset64 entrypoint_offset =
+ GetThreadOffset<kArm64PointerSize>(kQuickQuickResolutionTrampoline);
+ __ Ldr(temp1.X(), MemOperand(tr, entrypoint_offset.Int32Value()));
+ __ Br(temp1.X());
+ }
__ Bind(&frame_entry_label_);
bool do_overflow_check =
@@ -1904,11 +1943,6 @@
Register class_reg) {
UseScratchRegisterScope temps(GetVIXLAssembler());
Register temp = temps.AcquireW();
- constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
- const 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);
// CMP (immediate) is limited to imm12 or imm12<<12, so we would need to materialize
// the constant 0xf0000000 for comparison with the full 32-bit field. To reduce the code
@@ -1974,6 +2008,13 @@
void InstructionCodeGeneratorARM64::GenerateSuspendCheck(HSuspendCheck* instruction,
HBasicBlock* successor) {
+ if (instruction->IsNoOp()) {
+ if (successor != nullptr) {
+ __ B(codegen_->GetLabelOf(successor));
+ }
+ return;
+ }
+
if (codegen_->CanUseImplicitSuspendCheck()) {
__ Ldr(kImplicitSuspendCheckRegister, MemOperand(kImplicitSuspendCheckRegister));
codegen_->RecordPcInfo(instruction, instruction->GetDexPc());
@@ -2051,7 +2092,7 @@
bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
bool object_field_get_with_read_barrier =
- kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+ gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
object_field_get_with_read_barrier
@@ -2107,7 +2148,7 @@
MemOperand field =
HeapOperand(InputRegisterAt(instruction, receiver_input), field_info.GetFieldOffset());
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier &&
+ if (gUseReadBarrier && kUseBakerReadBarrier &&
load_type == DataType::Type::kReference) {
// Object FieldGet with Baker's read barrier case.
// /* HeapReference<Object> */ out = *(base + offset)
@@ -2549,7 +2590,7 @@
void LocationsBuilderARM64::VisitArrayGet(HArrayGet* instruction) {
bool object_array_get_with_read_barrier =
- kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+ gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
object_array_get_with_read_barrier
@@ -2605,10 +2646,10 @@
// does not support the HIntermediateAddress instruction.
DCHECK(!((type == DataType::Type::kReference) &&
instruction->GetArray()->IsIntermediateAddress() &&
- kEmitCompilerReadBarrier &&
+ gUseReadBarrier &&
!kUseBakerReadBarrier));
- if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (type == DataType::Type::kReference && gUseReadBarrier && kUseBakerReadBarrier) {
// Object ArrayGet with Baker's read barrier case.
// Note that a potential implicit null check is handled in the
// CodeGeneratorARM64::GenerateArrayLoadWithBakerReadBarrier call.
@@ -3845,12 +3886,12 @@
}
}
-void LocationsBuilderARM64::VisitNativeDebugInfo(HNativeDebugInfo* info) {
- new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderARM64::VisitNop(HNop* nop) {
+ new (GetGraph()->GetAllocator()) LocationSummary(nop);
}
-void InstructionCodeGeneratorARM64::VisitNativeDebugInfo(HNativeDebugInfo*) {
- // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorARM64::VisitNop(HNop*) {
+ // The environment recording already happened in CodeGenerator::Compile.
}
void CodeGeneratorARM64::IncreaseFrame(size_t adjustment) {
@@ -3898,7 +3939,7 @@
// Temp is used for read barrier.
static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
- if (kEmitCompilerReadBarrier &&
+ if (gUseReadBarrier &&
(kUseBakerReadBarrier ||
type_check_kind == TypeCheckKind::kAbstractClassCheck ||
type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -5313,7 +5354,7 @@
load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
load_kind == HLoadClass::LoadKind::kBssEntryPackage);
- const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+ const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
? LocationSummary::kCallOnSlowPath
: LocationSummary::kNoCall;
@@ -5327,7 +5368,7 @@
}
locations->SetOut(Location::RequiresRegister());
if (cls->GetLoadKind() == HLoadClass::LoadKind::kBssEntry) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the type resolution or initialization and marking to save everything we need.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -5354,7 +5395,7 @@
const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
? kWithoutReadBarrier
- : kCompilerReadBarrierOption;
+ : gCompilerReadBarrierOption;
bool generate_null_check = false;
switch (load_kind) {
case HLoadClass::LoadKind::kReferrersClass: {
@@ -5523,7 +5564,7 @@
} else {
locations->SetOut(Location::RequiresRegister());
if (load->GetLoadKind() == HLoadString::LoadKind::kBssEntry) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the pResolveString and marking to save everything we need.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -5577,7 +5618,7 @@
temp,
/* offset placeholder */ 0u,
ldr_label,
- kCompilerReadBarrierOption);
+ gCompilerReadBarrierOption);
SlowPathCodeARM64* slow_path =
new (codegen_->GetScopedAllocator()) LoadStringSlowPathARM64(load);
codegen_->AddSlowPath(slow_path);
@@ -5601,7 +5642,7 @@
out.X(),
/* offset= */ 0,
/* fixup_label= */ nullptr,
- kCompilerReadBarrierOption);
+ gCompilerReadBarrierOption);
return;
}
default:
@@ -6462,7 +6503,7 @@
DataType::Type type = DataType::Type::kReference;
Register out_reg = RegisterFrom(out, type);
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(out + offset)
@@ -6503,7 +6544,7 @@
Register out_reg = RegisterFrom(out, type);
Register obj_reg = RegisterFrom(obj, type);
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(obj + offset)
@@ -6538,7 +6579,7 @@
DCHECK(fixup_label == nullptr || offset == 0u);
Register root_reg = RegisterFrom(root, DataType::Type::kReference);
if (read_barrier_option == kWithReadBarrier) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Fast path implementation of art::ReadBarrier::BarrierForRoot when
// Baker's read barrier are used.
@@ -6604,7 +6645,7 @@
void CodeGeneratorARM64::GenerateIntrinsicCasMoveWithBakerReadBarrier(
vixl::aarch64::Register marked_old_value,
vixl::aarch64::Register old_value) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// Similar to the Baker RB path in GenerateGcRootFieldLoad(), with a MOV instead of LDR.
@@ -6626,7 +6667,7 @@
const vixl::aarch64::MemOperand& src,
bool needs_null_check,
bool use_load_acquire) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
@@ -6722,7 +6763,7 @@
uint32_t data_offset,
Location index,
bool needs_null_check) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
static_assert(
@@ -6800,7 +6841,7 @@
void CodeGeneratorARM64::MaybeGenerateMarkingRegisterCheck(int code, Location temp_loc) {
// The following condition is a compile-time one, so it does not have a run-time cost.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && kIsDebugBuild) {
+ if (kIsDebugBuild && gUseReadBarrier && kUseBakerReadBarrier) {
// The following condition is a run-time one; it is executed after the
// previous compile-time test, to avoid penalizing non-debug builds.
if (GetCompilerOptions().EmitRunTimeChecksInDebugMode()) {
@@ -6829,7 +6870,7 @@
Location obj,
uint32_t offset,
Location index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the reference load.
//
@@ -6854,7 +6895,7 @@
Location obj,
uint32_t offset,
Location index) {
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Baker's read barriers shall be handled by the fast path
// (CodeGeneratorARM64::GenerateReferenceLoadWithBakerReadBarrier).
DCHECK(!kUseBakerReadBarrier);
@@ -6869,7 +6910,7 @@
void CodeGeneratorARM64::GenerateReadBarrierForRootSlow(HInstruction* instruction,
Location out,
Location root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the GC root load.
//
diff --git a/compiler/optimizing/code_generator_arm64.h b/compiler/optimizing/code_generator_arm64.h
index f4d652c..c1984e3 100644
--- a/compiler/optimizing/code_generator_arm64.h
+++ b/compiler/optimizing/code_generator_arm64.h
@@ -92,7 +92,11 @@
vixl::aarch64::CPURegList(
tr,
// Reserve X20 as Marking Register when emitting Baker read barriers.
- ((kEmitCompilerReadBarrier && kUseBakerReadBarrier) ? mr : vixl::aarch64::NoCPUReg),
+ // TODO: We don't need to reserve marking-register for userfaultfd GC. But
+ // that would require some work in the assembler code as the right GC is
+ // chosen at load-time and not compile time.
+ ((gUseReadBarrier || gUseUserfaultfd) && kUseBakerReadBarrier
+ ? mr : vixl::aarch64::NoCPUReg),
kImplicitSuspendCheckRegister,
vixl::aarch64::lr);
@@ -111,7 +115,7 @@
const vixl::aarch64::CPURegList callee_saved_core_registers(
vixl::aarch64::CPURegister::kRegister,
vixl::aarch64::kXRegSize,
- ((kEmitCompilerReadBarrier && kUseBakerReadBarrier)
+ ((gUseReadBarrier && kUseBakerReadBarrier)
? vixl::aarch64::x21.GetCode()
: vixl::aarch64::x20.GetCode()),
vixl::aarch64::x30.GetCode());
diff --git a/compiler/optimizing/code_generator_arm_vixl.cc b/compiler/optimizing/code_generator_arm_vixl.cc
index 09fa598..0850e2f 100644
--- a/compiler/optimizing/code_generator_arm_vixl.cc
+++ b/compiler/optimizing/code_generator_arm_vixl.cc
@@ -744,7 +744,7 @@
obj_(obj),
offset_(offset),
index_(index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// If `obj` is equal to `out` or `ref`, it means the initial object
// has been overwritten by (or after) the heap object reference load
// to be instrumented, e.g.:
@@ -922,7 +922,7 @@
public:
ReadBarrierForRootSlowPathARMVIXL(HInstruction* instruction, Location out, Location root)
: SlowPathCodeARMVIXL(instruction), out_(out), root_(root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
void EmitNativeCode(CodeGenerator* codegen) override {
@@ -2101,7 +2101,10 @@
blocked_core_registers_[LR] = true;
blocked_core_registers_[PC] = true;
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ // TODO: We don't need to reserve marking-register for userfaultfd GC. But
+ // that would require some work in the assembler code as the right GC is
+ // chosen at load-time and not compile time.
+ if ((gUseReadBarrier || gUseUserfaultfd) && kUseBakerReadBarrier) {
// Reserve marking register.
blocked_core_registers_[MR] = true;
}
@@ -2234,6 +2237,52 @@
bool skip_overflow_check =
IsLeafMethod() && !FrameNeedsStackCheck(GetFrameSize(), InstructionSet::kArm);
DCHECK(GetCompilerOptions().GetImplicitStackOverflowChecks());
+
+ // Check if we need to generate the clinit check. We will jump to the
+ // resolution stub if the class is not initialized and the executing thread is
+ // not the thread initializing it.
+ // We do this before constructing the frame to get the correct stack trace if
+ // an exception is thrown.
+ if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+ UseScratchRegisterScope temps(GetVIXLAssembler());
+ vixl32::Label resolution;
+
+ // Check if we're visibly initialized.
+
+ vixl32::Register temp1 = temps.Acquire();
+ // Use r4 as other temporary register.
+ DCHECK(!blocked_core_registers_[R4]);
+ DCHECK(!kCoreCalleeSaves.Includes(r4));
+ vixl32::Register temp2 = r4;
+ for (vixl32::Register reg : kParameterCoreRegistersVIXL) {
+ DCHECK(!reg.Is(r4));
+ }
+
+ // 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);
+ __ B(cs, &frame_entry_label_);
+
+ // Check if we're initializing and the thread initializing is the one
+ // executing the code.
+ __ Cmp(temp2, shifted_initializing_value);
+ __ B(lo, &resolution);
+
+ __ Ldr(temp1, MemOperand(temp1, mirror::Class::ClinitThreadIdOffset().Int32Value()));
+ __ Ldr(temp2, MemOperand(tr, Thread::TidOffset<kArmPointerSize>().Int32Value()));
+ __ Cmp(temp1, temp2);
+ __ B(eq, &frame_entry_label_);
+ __ Bind(&resolution);
+
+ // Jump to the resolution stub.
+ ThreadOffset32 entrypoint_offset =
+ GetThreadOffset<kArmPointerSize>(kQuickQuickResolutionTrampoline);
+ __ Ldr(temp1, MemOperand(tr, entrypoint_offset.Int32Value()));
+ __ Bx(temp1);
+ }
+
__ Bind(&frame_entry_label_);
if (HasEmptyFrame()) {
@@ -3069,12 +3118,12 @@
}
}
-void LocationsBuilderARMVIXL::VisitNativeDebugInfo(HNativeDebugInfo* info) {
- new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderARMVIXL::VisitNop(HNop* nop) {
+ new (GetGraph()->GetAllocator()) LocationSummary(nop);
}
-void InstructionCodeGeneratorARMVIXL::VisitNativeDebugInfo(HNativeDebugInfo*) {
- // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorARMVIXL::VisitNop(HNop*) {
+ // The environment recording already happened in CodeGenerator::Compile.
}
void CodeGeneratorARMVIXL::IncreaseFrame(size_t adjustment) {
@@ -5911,7 +5960,7 @@
instruction->IsPredicatedInstanceFieldGet());
bool object_field_get_with_read_barrier =
- kEmitCompilerReadBarrier && (field_info.GetFieldType() == DataType::Type::kReference);
+ gUseReadBarrier && (field_info.GetFieldType() == DataType::Type::kReference);
bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
@@ -6082,7 +6131,7 @@
case DataType::Type::kReference: {
// /* HeapReference<Object> */ out = *(base + offset)
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
Location maybe_temp = (locations->GetTempCount() != 0) ? locations->GetTemp(0) : Location();
// Note that a potential implicit null check is handled in this
// CodeGeneratorARMVIXL::GenerateFieldLoadWithBakerReadBarrier call.
@@ -6386,7 +6435,7 @@
void LocationsBuilderARMVIXL::VisitArrayGet(HArrayGet* instruction) {
bool object_array_get_with_read_barrier =
- kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+ gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
object_array_get_with_read_barrier
@@ -6534,14 +6583,14 @@
// The read barrier instrumentation of object ArrayGet
// instructions does not support the HIntermediateAddress
// instruction.
- DCHECK(!(has_intermediate_address && kEmitCompilerReadBarrier));
+ DCHECK(!(has_intermediate_address && gUseReadBarrier));
static_assert(
sizeof(mirror::HeapReference<mirror::Object>) == sizeof(int32_t),
"art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
// /* HeapReference<Object> */ out =
// *(obj + data_offset + index * sizeof(HeapReference<Object>))
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Note that a potential implicit null check is handled in this
// CodeGeneratorARMVIXL::GenerateArrayLoadWithBakerReadBarrier call.
DCHECK(!instruction->CanDoImplicitNullCheckOn(instruction->InputAt(0)));
@@ -7459,7 +7508,7 @@
load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
load_kind == HLoadClass::LoadKind::kBssEntryPackage);
- const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+ const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
? LocationSummary::kCallOnSlowPath
: LocationSummary::kNoCall;
@@ -7473,7 +7522,7 @@
}
locations->SetOut(Location::RequiresRegister());
if (load_kind == HLoadClass::LoadKind::kBssEntry) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the type resolution or initialization and marking to save everything we need.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -7501,7 +7550,7 @@
const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
? kWithoutReadBarrier
- : kCompilerReadBarrierOption;
+ : gCompilerReadBarrierOption;
bool generate_null_check = false;
switch (load_kind) {
case HLoadClass::LoadKind::kReferrersClass: {
@@ -7622,12 +7671,7 @@
LoadClassSlowPathARMVIXL* slow_path, vixl32::Register class_reg) {
UseScratchRegisterScope temps(GetVIXLAssembler());
vixl32::Register temp = temps.Acquire();
- constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
- constexpr uint32_t shifted_visibly_initialized_value =
- enum_cast<uint32_t>(ClassStatus::kVisiblyInitialized) << status_lsb_position;
-
- const size_t status_offset = mirror::Class::StatusOffset().SizeValue();
- GetAssembler()->LoadFromOffset(kLoadWord, temp, class_reg, status_offset);
+ __ Ldrb(temp, MemOperand(class_reg, status_byte_offset));
__ Cmp(temp, shifted_visibly_initialized_value);
__ B(lo, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
@@ -7721,7 +7765,7 @@
} else {
locations->SetOut(Location::RequiresRegister());
if (load_kind == HLoadString::LoadKind::kBssEntry) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the pResolveString and marking to save everything we need, including temps.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -7760,7 +7804,7 @@
codegen_->EmitMovwMovtPlaceholder(labels, out);
// All aligned loads are implicitly atomic consume operations on ARM.
codegen_->GenerateGcRootFieldLoad(
- load, out_loc, out, /*offset=*/ 0, kCompilerReadBarrierOption);
+ load, out_loc, out, /*offset=*/ 0, gCompilerReadBarrierOption);
LoadStringSlowPathARMVIXL* slow_path =
new (codegen_->GetScopedAllocator()) LoadStringSlowPathARMVIXL(load);
codegen_->AddSlowPath(slow_path);
@@ -7781,7 +7825,7 @@
load->GetString()));
// /* GcRoot<mirror::String> */ out = *out
codegen_->GenerateGcRootFieldLoad(
- load, out_loc, out, /*offset=*/ 0, kCompilerReadBarrierOption);
+ load, out_loc, out, /*offset=*/ 0, gCompilerReadBarrierOption);
return;
}
default:
@@ -7838,7 +7882,7 @@
// Temp is used for read barrier.
static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
- if (kEmitCompilerReadBarrier &&
+ if (gUseReadBarrier &&
(kUseBakerReadBarrier ||
type_check_kind == TypeCheckKind::kAbstractClassCheck ||
type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -8773,7 +8817,7 @@
ReadBarrierOption read_barrier_option) {
vixl32::Register out_reg = RegisterFrom(out);
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
DCHECK(maybe_temp.IsRegister()) << maybe_temp;
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
@@ -8808,7 +8852,7 @@
vixl32::Register out_reg = RegisterFrom(out);
vixl32::Register obj_reg = RegisterFrom(obj);
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
DCHECK(maybe_temp.IsRegister()) << maybe_temp;
// Load with fast path based Baker's read barrier.
@@ -8837,7 +8881,7 @@
ReadBarrierOption read_barrier_option) {
vixl32::Register root_reg = RegisterFrom(root);
if (read_barrier_option == kWithReadBarrier) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Fast path implementation of art::ReadBarrier::BarrierForRoot when
// Baker's read barrier are used.
@@ -8901,7 +8945,7 @@
void CodeGeneratorARMVIXL::GenerateIntrinsicCasMoveWithBakerReadBarrier(
vixl::aarch32::Register marked_old_value,
vixl::aarch32::Register old_value) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// Similar to the Baker RB path in GenerateGcRootFieldLoad(), with a MOV instead of LDR.
@@ -8935,7 +8979,7 @@
vixl32::Register obj,
const vixl32::MemOperand& src,
bool needs_null_check) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// Query `art::Thread::Current()->GetIsGcMarking()` (stored in the
@@ -9028,7 +9072,7 @@
Location index,
Location temp,
bool needs_null_check) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
static_assert(
@@ -9094,7 +9138,7 @@
void CodeGeneratorARMVIXL::MaybeGenerateMarkingRegisterCheck(int code, Location temp_loc) {
// The following condition is a compile-time one, so it does not have a run-time cost.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && kIsDebugBuild) {
+ if (kIsDebugBuild && gUseReadBarrier && kUseBakerReadBarrier) {
// The following condition is a run-time one; it is executed after the
// previous compile-time test, to avoid penalizing non-debug builds.
if (GetCompilerOptions().EmitRunTimeChecksInDebugMode()) {
@@ -9124,7 +9168,7 @@
Location obj,
uint32_t offset,
Location index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the reference load.
//
@@ -9150,7 +9194,7 @@
Location obj,
uint32_t offset,
Location index) {
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Baker's read barriers shall be handled by the fast path
// (CodeGeneratorARMVIXL::GenerateReferenceLoadWithBakerReadBarrier).
DCHECK(!kUseBakerReadBarrier);
@@ -9165,7 +9209,7 @@
void CodeGeneratorARMVIXL::GenerateReadBarrierForRootSlow(HInstruction* instruction,
Location out,
Location root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the GC root load.
//
diff --git a/compiler/optimizing/code_generator_arm_vixl.h b/compiler/optimizing/code_generator_arm_vixl.h
index 790ad0f..fc5fc48 100644
--- a/compiler/optimizing/code_generator_arm_vixl.h
+++ b/compiler/optimizing/code_generator_arm_vixl.h
@@ -84,7 +84,7 @@
vixl::aarch32::r6,
vixl::aarch32::r7),
// Do not consider r8 as a callee-save register with Baker read barriers.
- ((kEmitCompilerReadBarrier && kUseBakerReadBarrier)
+ ((gUseReadBarrier && kUseBakerReadBarrier)
? vixl::aarch32::RegisterList()
: vixl::aarch32::RegisterList(vixl::aarch32::r8)),
vixl::aarch32::RegisterList(vixl::aarch32::r10,
@@ -602,7 +602,6 @@
struct PcRelativePatchInfo {
PcRelativePatchInfo(const DexFile* dex_file, uint32_t off_or_idx)
: target_dex_file(dex_file), offset_or_index(off_or_idx) { }
- PcRelativePatchInfo(PcRelativePatchInfo&& other) = default;
// Target dex file or null for .data.bmig.rel.ro patches.
const DexFile* target_dex_file;
diff --git a/compiler/optimizing/code_generator_vector_arm64_sve.cc b/compiler/optimizing/code_generator_vector_arm64_sve.cc
index 824b6c9..2d4921f 100644
--- a/compiler/optimizing/code_generator_vector_arm64_sve.cc
+++ b/compiler/optimizing/code_generator_vector_arm64_sve.cc
@@ -27,13 +27,10 @@
namespace arm64 {
using helpers::DRegisterFrom;
-using helpers::HeapOperand;
using helpers::InputRegisterAt;
using helpers::Int64FromLocation;
using helpers::LocationFrom;
using helpers::OutputRegister;
-using helpers::QRegisterFrom;
-using helpers::StackOperandFrom;
using helpers::SveStackOperandFrom;
using helpers::VRegisterFrom;
using helpers::ZRegisterFrom;
diff --git a/compiler/optimizing/code_generator_x86.cc b/compiler/optimizing/code_generator_x86.cc
index 8c6b802..ce27083 100644
--- a/compiler/optimizing/code_generator_x86.cc
+++ b/compiler/optimizing/code_generator_x86.cc
@@ -503,7 +503,7 @@
: SlowPathCode(instruction),
ref_(ref),
unpoison_ref_before_marking_(unpoison_ref_before_marking) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
const char* GetDescription() const override { return "ReadBarrierMarkSlowPathX86"; }
@@ -590,7 +590,7 @@
field_addr_(field_addr),
unpoison_ref_before_marking_(unpoison_ref_before_marking),
temp_(temp) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
const char* GetDescription() const override { return "ReadBarrierMarkAndUpdateFieldSlowPathX86"; }
@@ -744,7 +744,7 @@
obj_(obj),
offset_(offset),
index_(index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// If `obj` is equal to `out` or `ref`, it means the initial object
// has been overwritten by (or after) the heap object reference load
// to be instrumented, e.g.:
@@ -918,7 +918,7 @@
public:
ReadBarrierForRootSlowPathX86(HInstruction* instruction, Location out, Location root)
: SlowPathCode(instruction), out_(out), root_(root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
void EmitNativeCode(CodeGenerator* codegen) override {
@@ -1261,6 +1261,44 @@
void CodeGeneratorX86::GenerateFrameEntry() {
__ cfi().SetCurrentCFAOffset(kX86WordSize); // return address
+
+ // Check if we need to generate the clinit check. We will jump to the
+ // resolution stub if the class is not initialized and the executing thread is
+ // not the thread initializing it.
+ // We do this before constructing the frame to get the correct stack trace if
+ // an exception is thrown.
+ if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+ NearLabel continue_execution, resolution;
+ // We'll use EBP as temporary.
+ __ pushl(EBP);
+ // 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));
+ __ 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));
+ __ j(kBelow, &resolution);
+
+ __ movl(EBP, Address(EBP, mirror::Class::ClinitThreadIdOffset().Int32Value()));
+ __ fs()->cmpl(EBP, Address::Absolute(Thread::TidOffset<kX86PointerSize>().Int32Value()));
+ __ j(kEqual, &continue_execution);
+ __ Bind(&resolution);
+
+ __ popl(EBP);
+ // Jump to the resolution stub.
+ ThreadOffset32 entrypoint_offset =
+ GetThreadOffset<kX86PointerSize>(kQuickQuickResolutionTrampoline);
+ __ fs()->jmp(Address::Absolute(entrypoint_offset));
+
+ __ Bind(&continue_execution);
+ __ popl(EBP);
+ }
+
__ Bind(&frame_entry_label_);
bool skip_overflow_check =
IsLeafMethod() && !FrameNeedsStackCheck(GetFrameSize(), InstructionSet::kX86);
@@ -1619,7 +1657,7 @@
__ movsd(dst.AsFpuRegister<XmmRegister>(), src);
break;
case DataType::Type::kReference:
- DCHECK(!kEmitCompilerReadBarrier);
+ DCHECK(!gUseReadBarrier);
__ movl(dst.AsRegister<Register>(), src);
__ MaybeUnpoisonHeapReference(dst.AsRegister<Register>());
break;
@@ -2230,12 +2268,12 @@
}
}
-void LocationsBuilderX86::VisitNativeDebugInfo(HNativeDebugInfo* info) {
- new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderX86::VisitNop(HNop* nop) {
+ new (GetGraph()->GetAllocator()) LocationSummary(nop);
}
-void InstructionCodeGeneratorX86::VisitNativeDebugInfo(HNativeDebugInfo*) {
- // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorX86::VisitNop(HNop*) {
+ // The environment recording already happened in CodeGenerator::Compile.
}
void CodeGeneratorX86::IncreaseFrame(size_t adjustment) {
@@ -5731,11 +5769,11 @@
instruction->IsPredicatedInstanceFieldGet());
bool object_field_get_with_read_barrier =
- kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+ gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
- kEmitCompilerReadBarrier
+ gUseReadBarrier
? LocationSummary::kCallOnSlowPath
: LocationSummary::kNoCall);
if (object_field_get_with_read_barrier && kUseBakerReadBarrier) {
@@ -5793,7 +5831,7 @@
if (load_type == DataType::Type::kReference) {
// /* HeapReference<Object> */ out = *(base + offset)
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Note that a potential implicit null check is handled in this
// CodeGeneratorX86::GenerateFieldLoadWithBakerReadBarrier call.
codegen_->GenerateFieldLoadWithBakerReadBarrier(
@@ -6202,7 +6240,7 @@
void LocationsBuilderX86::VisitArrayGet(HArrayGet* instruction) {
bool object_array_get_with_read_barrier =
- kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+ gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
object_array_get_with_read_barrier
@@ -6244,7 +6282,7 @@
"art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
// /* HeapReference<Object> */ out =
// *(obj + data_offset + index * sizeof(HeapReference<Object>))
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Note that a potential implicit null check is handled in this
// CodeGeneratorX86::GenerateArrayLoadWithBakerReadBarrier call.
codegen_->GenerateArrayLoadWithBakerReadBarrier(
@@ -7057,7 +7095,7 @@
load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
load_kind == HLoadClass::LoadKind::kBssEntryPackage);
- const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+ const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
? LocationSummary::kCallOnSlowPath
: LocationSummary::kNoCall;
@@ -7071,7 +7109,7 @@
}
locations->SetOut(Location::RequiresRegister());
if (call_kind == LocationSummary::kCallOnSlowPath && cls->HasPcRelativeLoadKind()) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the type resolution and/or initialization to save everything.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -7109,7 +7147,7 @@
bool generate_null_check = false;
const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
? kWithoutReadBarrier
- : kCompilerReadBarrierOption;
+ : gCompilerReadBarrierOption;
switch (load_kind) {
case HLoadClass::LoadKind::kReferrersClass: {
DCHECK(!cls->CanCallRuntime());
@@ -7233,12 +7271,6 @@
void InstructionCodeGeneratorX86::GenerateClassInitializationCheck(
SlowPathCode* slow_path, Register class_reg) {
- constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
- const 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);
-
__ cmpb(Address(class_reg, status_byte_offset), Immediate(shifted_visibly_initialized_value));
__ j(kBelow, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
@@ -7296,7 +7328,7 @@
} else {
locations->SetOut(Location::RequiresRegister());
if (load_kind == HLoadString::LoadKind::kBssEntry) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the pResolveString to save everything.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -7345,7 +7377,7 @@
Address address = Address(method_address, CodeGeneratorX86::kPlaceholder32BitOffset);
Label* fixup_label = codegen_->NewStringBssEntryPatch(load);
// /* GcRoot<mirror::String> */ out = *address /* PC-relative */
- GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+ GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
// No need for memory fence, thanks to the x86 memory model.
SlowPathCode* slow_path = new (codegen_->GetScopedAllocator()) LoadStringSlowPathX86(load);
codegen_->AddSlowPath(slow_path);
@@ -7365,7 +7397,7 @@
Label* fixup_label = codegen_->NewJitRootStringPatch(
load->GetDexFile(), load->GetStringIndex(), load->GetString());
// /* GcRoot<mirror::String> */ out = *address
- GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+ GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
return;
}
default:
@@ -7416,7 +7448,7 @@
// Temp is used for read barrier.
static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
- if (kEmitCompilerReadBarrier &&
+ if (gUseReadBarrier &&
!kUseBakerReadBarrier &&
(type_check_kind == TypeCheckKind::kAbstractClassCheck ||
type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -8188,7 +8220,7 @@
ReadBarrierOption read_barrier_option) {
Register out_reg = out.AsRegister<Register>();
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(out + offset)
@@ -8222,7 +8254,7 @@
Register out_reg = out.AsRegister<Register>();
Register obj_reg = obj.AsRegister<Register>();
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(obj + offset)
@@ -8250,7 +8282,7 @@
ReadBarrierOption read_barrier_option) {
Register root_reg = root.AsRegister<Register>();
if (read_barrier_option == kWithReadBarrier) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Fast path implementation of art::ReadBarrier::BarrierForRoot when
// Baker's read barrier are used:
@@ -8314,7 +8346,7 @@
Register obj,
uint32_t offset,
bool needs_null_check) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// /* HeapReference<Object> */ ref = *(obj + offset)
@@ -8328,7 +8360,7 @@
uint32_t data_offset,
Location index,
bool needs_null_check) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
static_assert(
@@ -8347,7 +8379,7 @@
bool needs_null_check,
bool always_update_field,
Register* temp) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// In slow path based read barriers, the read barrier call is
@@ -8428,7 +8460,7 @@
Location obj,
uint32_t offset,
Location index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the reference load.
//
@@ -8455,7 +8487,7 @@
Location obj,
uint32_t offset,
Location index) {
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Baker's read barriers shall be handled by the fast path
// (CodeGeneratorX86::GenerateReferenceLoadWithBakerReadBarrier).
DCHECK(!kUseBakerReadBarrier);
@@ -8470,7 +8502,7 @@
void CodeGeneratorX86::GenerateReadBarrierForRootSlow(HInstruction* instruction,
Location out,
Location root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the GC root load.
//
diff --git a/compiler/optimizing/code_generator_x86_64.cc b/compiler/optimizing/code_generator_x86_64.cc
index 511917a..b1db993 100644
--- a/compiler/optimizing/code_generator_x86_64.cc
+++ b/compiler/optimizing/code_generator_x86_64.cc
@@ -510,7 +510,7 @@
: SlowPathCode(instruction),
ref_(ref),
unpoison_ref_before_marking_(unpoison_ref_before_marking) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
const char* GetDescription() const override { return "ReadBarrierMarkSlowPathX86_64"; }
@@ -601,7 +601,7 @@
unpoison_ref_before_marking_(unpoison_ref_before_marking),
temp1_(temp1),
temp2_(temp2) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
const char* GetDescription() const override {
@@ -761,7 +761,7 @@
obj_(obj),
offset_(offset),
index_(index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// If `obj` is equal to `out` or `ref`, it means the initial
// object has been overwritten by (or after) the heap object
// reference load to be instrumented, e.g.:
@@ -937,7 +937,7 @@
public:
ReadBarrierForRootSlowPathX86_64(HInstruction* instruction, Location out, Location root)
: SlowPathCode(instruction), out_(out), root_(root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
}
void EmitNativeCode(CodeGenerator* codegen) override {
@@ -1653,6 +1653,44 @@
void CodeGeneratorX86_64::GenerateFrameEntry() {
__ cfi().SetCurrentCFAOffset(kX86_64WordSize); // return address
+
+ // Check if we need to generate the clinit check. We will jump to the
+ // resolution stub if the class is not initialized and the executing thread is
+ // not the thread initializing it.
+ // We do this before constructing the frame to get the correct stack trace if
+ // an exception is thrown.
+ if (GetCompilerOptions().ShouldCompileWithClinitCheck(GetGraph()->GetArtMethod())) {
+ NearLabel resolution;
+ // 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(CpuRegister(TMP),
+ Address(CpuRegister(kMethodRegisterArgument),
+ ArtMethod::DeclaringClassOffset().Int32Value()));
+ __ cmpb(Address(CpuRegister(TMP), status_byte_offset),
+ Immediate(shifted_visibly_initialized_value));
+ __ 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));
+ __ j(kBelow, &resolution);
+
+ __ movl(CpuRegister(TMP),
+ Address(CpuRegister(TMP), mirror::Class::ClinitThreadIdOffset().Int32Value()));
+ __ gs()->cmpl(
+ CpuRegister(TMP),
+ Address::Absolute(Thread::TidOffset<kX86_64PointerSize>().Int32Value(), /*no_rip=*/ true));
+ __ j(kEqual, &frame_entry_label_);
+ __ Bind(&resolution);
+
+ // Jump to the resolution stub.
+ ThreadOffset64 entrypoint_offset =
+ GetThreadOffset<kX86_64PointerSize>(kQuickQuickResolutionTrampoline);
+ __ gs()->jmp(Address::Absolute(entrypoint_offset, /*no_rip=*/ true));
+ }
+
__ Bind(&frame_entry_label_);
bool skip_overflow_check = IsLeafMethod()
&& !FrameNeedsStackCheck(GetFrameSize(), InstructionSet::kX86_64);
@@ -2274,12 +2312,12 @@
}
}
-void LocationsBuilderX86_64::VisitNativeDebugInfo(HNativeDebugInfo* info) {
- new (GetGraph()->GetAllocator()) LocationSummary(info);
+void LocationsBuilderX86_64::VisitNop(HNop* nop) {
+ new (GetGraph()->GetAllocator()) LocationSummary(nop);
}
-void InstructionCodeGeneratorX86_64::VisitNativeDebugInfo(HNativeDebugInfo*) {
- // MaybeRecordNativeDebugInfo is already called implicitly in CodeGenerator::Compile.
+void InstructionCodeGeneratorX86_64::VisitNop(HNop*) {
+ // The environment recording already happened in CodeGenerator::Compile.
}
void CodeGeneratorX86_64::IncreaseFrame(size_t adjustment) {
@@ -5013,7 +5051,7 @@
instruction->IsPredicatedInstanceFieldGet());
bool object_field_get_with_read_barrier =
- kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+ gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
bool is_predicated = instruction->IsPredicatedInstanceFieldGet();
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
@@ -5064,7 +5102,7 @@
if (load_type == DataType::Type::kReference) {
// /* HeapReference<Object> */ out = *(base + offset)
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Note that a potential implicit null check is handled in this
// CodeGeneratorX86_64::GenerateFieldLoadWithBakerReadBarrier call.
codegen_->GenerateFieldLoadWithBakerReadBarrier(
@@ -5513,7 +5551,7 @@
void LocationsBuilderX86_64::VisitArrayGet(HArrayGet* instruction) {
bool object_array_get_with_read_barrier =
- kEmitCompilerReadBarrier && (instruction->GetType() == DataType::Type::kReference);
+ gUseReadBarrier && (instruction->GetType() == DataType::Type::kReference);
LocationSummary* locations =
new (GetGraph()->GetAllocator()) LocationSummary(instruction,
object_array_get_with_read_barrier
@@ -5551,7 +5589,7 @@
"art::mirror::HeapReference<art::mirror::Object> and int32_t have different sizes.");
// /* HeapReference<Object> */ out =
// *(obj + data_offset + index * sizeof(HeapReference<Object>))
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Note that a potential implicit null check is handled in this
// CodeGeneratorX86_64::GenerateArrayLoadWithBakerReadBarrier call.
codegen_->GenerateArrayLoadWithBakerReadBarrier(
@@ -6282,12 +6320,6 @@
void InstructionCodeGeneratorX86_64::GenerateClassInitializationCheck(
SlowPathCode* slow_path, CpuRegister class_reg) {
- constexpr size_t status_lsb_position = SubtypeCheckBits::BitStructSizeOf();
- const 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);
-
__ cmpb(Address(class_reg, status_byte_offset), Immediate(shifted_visibly_initialized_value));
__ j(kBelow, slow_path->GetEntryLabel());
__ Bind(slow_path->GetExitLabel());
@@ -6352,7 +6384,7 @@
load_kind == HLoadClass::LoadKind::kBssEntryPublic ||
load_kind == HLoadClass::LoadKind::kBssEntryPackage);
- const bool requires_read_barrier = kEmitCompilerReadBarrier && !cls->IsInBootImage();
+ const bool requires_read_barrier = gUseReadBarrier && !cls->IsInBootImage();
LocationSummary::CallKind call_kind = (cls->NeedsEnvironment() || requires_read_barrier)
? LocationSummary::kCallOnSlowPath
: LocationSummary::kNoCall;
@@ -6366,7 +6398,7 @@
}
locations->SetOut(Location::RequiresRegister());
if (load_kind == HLoadClass::LoadKind::kBssEntry) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the type resolution and/or initialization to save everything.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -6403,7 +6435,7 @@
const ReadBarrierOption read_barrier_option = cls->IsInBootImage()
? kWithoutReadBarrier
- : kCompilerReadBarrierOption;
+ : gCompilerReadBarrierOption;
bool generate_null_check = false;
switch (load_kind) {
case HLoadClass::LoadKind::kReferrersClass: {
@@ -6550,7 +6582,7 @@
} else {
locations->SetOut(Location::RequiresRegister());
if (load->GetLoadKind() == HLoadString::LoadKind::kBssEntry) {
- if (!kUseReadBarrier || kUseBakerReadBarrier) {
+ if (!gUseReadBarrier || kUseBakerReadBarrier) {
// Rely on the pResolveString to save everything.
locations->SetCustomSlowPathCallerSaves(OneRegInReferenceOutSaveEverythingCallerSaves());
} else {
@@ -6598,7 +6630,7 @@
/* no_rip= */ false);
Label* fixup_label = codegen_->NewStringBssEntryPatch(load);
// /* GcRoot<mirror::Class> */ out = *address /* PC-relative */
- GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+ GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
// No need for memory fence, thanks to the x86-64 memory model.
SlowPathCode* slow_path = new (codegen_->GetScopedAllocator()) LoadStringSlowPathX86_64(load);
codegen_->AddSlowPath(slow_path);
@@ -6619,7 +6651,7 @@
Label* fixup_label = codegen_->NewJitRootStringPatch(
load->GetDexFile(), load->GetStringIndex(), load->GetString());
// /* GcRoot<mirror::String> */ out = *address
- GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, kCompilerReadBarrierOption);
+ GenerateGcRootFieldLoad(load, out_loc, address, fixup_label, gCompilerReadBarrierOption);
return;
}
default:
@@ -6672,7 +6704,7 @@
// Temp is used for read barrier.
static size_t NumberOfInstanceOfTemps(TypeCheckKind type_check_kind) {
- if (kEmitCompilerReadBarrier &&
+ if (gUseReadBarrier &&
!kUseBakerReadBarrier &&
(type_check_kind == TypeCheckKind::kAbstractClassCheck ||
type_check_kind == TypeCheckKind::kClassHierarchyCheck ||
@@ -7426,7 +7458,7 @@
ReadBarrierOption read_barrier_option) {
CpuRegister out_reg = out.AsRegister<CpuRegister>();
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(out + offset)
@@ -7460,7 +7492,7 @@
CpuRegister out_reg = out.AsRegister<CpuRegister>();
CpuRegister obj_reg = obj.AsRegister<CpuRegister>();
if (read_barrier_option == kWithReadBarrier) {
- CHECK(kEmitCompilerReadBarrier);
+ CHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Load with fast path based Baker's read barrier.
// /* HeapReference<Object> */ out = *(obj + offset)
@@ -7488,7 +7520,7 @@
ReadBarrierOption read_barrier_option) {
CpuRegister root_reg = root.AsRegister<CpuRegister>();
if (read_barrier_option == kWithReadBarrier) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// Fast path implementation of art::ReadBarrier::BarrierForRoot when
// Baker's read barrier are used:
@@ -7552,7 +7584,7 @@
CpuRegister obj,
uint32_t offset,
bool needs_null_check) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// /* HeapReference<Object> */ ref = *(obj + offset)
@@ -7566,7 +7598,7 @@
uint32_t data_offset,
Location index,
bool needs_null_check) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
static_assert(
@@ -7586,7 +7618,7 @@
bool always_update_field,
CpuRegister* temp1,
CpuRegister* temp2) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
// In slow path based read barriers, the read barrier call is
@@ -7668,7 +7700,7 @@
Location obj,
uint32_t offset,
Location index) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the reference load.
//
@@ -7695,7 +7727,7 @@
Location obj,
uint32_t offset,
Location index) {
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Baker's read barriers shall be handled by the fast path
// (CodeGeneratorX86_64::GenerateReferenceLoadWithBakerReadBarrier).
DCHECK(!kUseBakerReadBarrier);
@@ -7710,7 +7742,7 @@
void CodeGeneratorX86_64::GenerateReadBarrierForRootSlow(HInstruction* instruction,
Location out,
Location root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
// Insert a slow path based read barrier *after* the GC root load.
//
diff --git a/compiler/optimizing/code_sinking.cc b/compiler/optimizing/code_sinking.cc
index 766bb01..930675b 100644
--- a/compiler/optimizing/code_sinking.cc
+++ b/compiler/optimizing/code_sinking.cc
@@ -271,10 +271,21 @@
}
}
for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
- HInstruction* user = use.GetUser()->GetHolder();
+ HEnvironment* env = use.GetUser();
+ HInstruction* user = env->GetHolder();
if (user->GetBlock() == target_block &&
(insert_pos == nullptr || user->StrictlyDominates(insert_pos))) {
- insert_pos = user;
+ if (target_block->IsCatchBlock() && target_block->GetFirstInstruction() == user) {
+ // We can sink the instructions past the environment setting Nop. If we do that, we have to
+ // remove said instruction from the environment. Since we know that we will be sinking the
+ // instruction to this block and there are no more instructions to consider, we can safely
+ // remove it from the environment now.
+ DCHECK(target_block->GetFirstInstruction()->IsNop());
+ env->RemoveAsUserOfInput(use.GetIndex());
+ env->SetRawEnvAt(use.GetIndex(), /*instruction=*/ nullptr);
+ } else {
+ insert_pos = user;
+ }
}
}
if (insert_pos == nullptr) {
diff --git a/compiler/optimizing/codegen_test_utils.h b/compiler/optimizing/codegen_test_utils.h
index 397e601..95cb548 100644
--- a/compiler/optimizing/codegen_test_utils.h
+++ b/compiler/optimizing/codegen_test_utils.h
@@ -45,7 +45,7 @@
namespace art {
-typedef CodeGenerator* (*CreateCodegenFn)(HGraph*, const CompilerOptions&);
+using CreateCodegenFn = CodeGenerator* (*)(HGraph*, const CompilerOptions&);
class CodegenTargetConfig {
public:
@@ -257,7 +257,7 @@
const void* code_ptr =
code_holder.MakeExecutable(allocator.GetMemory(), ArrayRef<const uint8_t>(), target_isa);
- typedef Expected (*fptr)();
+ using fptr = Expected (*)();
fptr f = reinterpret_cast<fptr>(reinterpret_cast<uintptr_t>(code_ptr));
if (target_isa == InstructionSet::kThumb2) {
// For thumb we need the bottom bit set.
diff --git a/compiler/optimizing/constant_folding.cc b/compiler/optimizing/constant_folding.cc
index 2031707..7fa2132 100644
--- a/compiler/optimizing/constant_folding.cc
+++ b/compiler/optimizing/constant_folding.cc
@@ -16,14 +16,19 @@
#include "constant_folding.h"
+#include <algorithm>
+
+#include "optimizing/data_type.h"
+#include "optimizing/nodes.h"
+
namespace art {
// This visitor tries to simplify instructions that can be evaluated
// as constants.
class HConstantFoldingVisitor : public HGraphDelegateVisitor {
public:
- explicit HConstantFoldingVisitor(HGraph* graph)
- : HGraphDelegateVisitor(graph) {}
+ explicit HConstantFoldingVisitor(HGraph* graph, OptimizingCompilerStats* stats)
+ : HGraphDelegateVisitor(graph, stats) {}
private:
void VisitBasicBlock(HBasicBlock* block) override;
@@ -33,6 +38,9 @@
void VisitTypeConversion(HTypeConversion* inst) override;
void VisitDivZeroCheck(HDivZeroCheck* inst) override;
+ void VisitIf(HIf* inst) override;
+
+ void PropagateValue(HBasicBlock* starting_block, HInstruction* variable, HConstant* constant);
DISALLOW_COPY_AND_ASSIGN(HConstantFoldingVisitor);
};
@@ -69,7 +77,7 @@
bool HConstantFolding::Run() {
- HConstantFoldingVisitor visitor(graph_);
+ HConstantFoldingVisitor visitor(graph_, stats_);
// Process basic blocks in reverse post-order in the dominator tree,
// so that an instruction turned into a constant, used as input of
// another instruction, may possibly be used to turn that second
@@ -130,6 +138,137 @@
}
}
+void HConstantFoldingVisitor::PropagateValue(HBasicBlock* starting_block,
+ HInstruction* variable,
+ HConstant* constant) {
+ const bool recording_stats = stats_ != nullptr;
+ size_t uses_before = 0;
+ size_t uses_after = 0;
+ if (recording_stats) {
+ uses_before = variable->GetUses().SizeSlow();
+ }
+
+ variable->ReplaceUsesDominatedBy(
+ starting_block->GetFirstInstruction(), constant, /* strictly_dominated= */ false);
+
+ if (recording_stats) {
+ uses_after = variable->GetUses().SizeSlow();
+ DCHECK_GE(uses_after, 1u) << "we must at least have the use in the if clause.";
+ DCHECK_GE(uses_before, uses_after);
+ MaybeRecordStat(stats_, MethodCompilationStat::kPropagatedIfValue, uses_before - uses_after);
+ }
+}
+
+void HConstantFoldingVisitor::VisitIf(HIf* inst) {
+ // Consistency check: the true and false successors do not dominate each other.
+ DCHECK(!inst->IfTrueSuccessor()->Dominates(inst->IfFalseSuccessor()) &&
+ !inst->IfFalseSuccessor()->Dominates(inst->IfTrueSuccessor()));
+
+ HInstruction* if_input = inst->InputAt(0);
+
+ // Already a constant.
+ if (if_input->IsConstant()) {
+ return;
+ }
+
+ // if (variable) {
+ // SSA `variable` guaranteed to be true
+ // } else {
+ // and here false
+ // }
+ PropagateValue(inst->IfTrueSuccessor(), if_input, GetGraph()->GetIntConstant(1));
+ PropagateValue(inst->IfFalseSuccessor(), if_input, GetGraph()->GetIntConstant(0));
+
+ // If the input is a condition, we can propagate the information of the condition itself.
+ if (!if_input->IsCondition()) {
+ return;
+ }
+ HCondition* condition = if_input->AsCondition();
+
+ // We want either `==` or `!=`, since we cannot make assumptions for other conditions e.g. `>`
+ if (!condition->IsEqual() && !condition->IsNotEqual()) {
+ return;
+ }
+
+ HInstruction* left = condition->GetLeft();
+ HInstruction* right = condition->GetRight();
+
+ // We want one of them to be a constant and not the other.
+ if (left->IsConstant() == right->IsConstant()) {
+ return;
+ }
+
+ // At this point we have something like:
+ // if (variable == constant) {
+ // SSA `variable` guaranteed to be equal to constant here
+ // } else {
+ // No guarantees can be made here (usually, see boolean case below).
+ // }
+ // Similarly with variable != constant, except that we can make guarantees in the else case.
+
+ HConstant* constant = left->IsConstant() ? left->AsConstant() : right->AsConstant();
+ HInstruction* variable = left->IsConstant() ? right : left;
+
+ // Don't deal with floats/doubles since they bring a lot of edge cases e.g.
+ // if (f == 0.0f) {
+ // // f is not really guaranteed to be 0.0f. It could be -0.0f, for example
+ // }
+ if (DataType::IsFloatingPointType(variable->GetType())) {
+ return;
+ }
+ DCHECK(!DataType::IsFloatingPointType(constant->GetType()));
+
+ // Sometimes we have an HCompare flowing into an Equals/NonEquals, which can act as a proxy. For
+ // example: `Equals(Compare(var, constant), 0)`. This is common for long, float, and double.
+ if (variable->IsCompare()) {
+ // We only care about equality comparisons so we skip if it is a less or greater comparison.
+ if (!constant->IsArithmeticZero()) {
+ return;
+ }
+
+ // Update left and right to be the ones from the HCompare.
+ left = variable->AsCompare()->GetLeft();
+ right = variable->AsCompare()->GetRight();
+
+ // Re-check that one of them to be a constant and not the other.
+ if (left->IsConstant() == right->IsConstant()) {
+ return;
+ }
+
+ constant = left->IsConstant() ? left->AsConstant() : right->AsConstant();
+ variable = left->IsConstant() ? right : left;
+
+ // Re-check floating point values.
+ if (DataType::IsFloatingPointType(variable->GetType())) {
+ return;
+ }
+ DCHECK(!DataType::IsFloatingPointType(constant->GetType()));
+ }
+
+ // From this block forward we want to replace the SSA value. We use `starting_block` and not the
+ // `if` block as we want to update one of the branches but not the other.
+ HBasicBlock* starting_block =
+ condition->IsEqual() ? inst->IfTrueSuccessor() : inst->IfFalseSuccessor();
+
+ PropagateValue(starting_block, variable, constant);
+
+ // Special case for booleans since they have only two values so we know what to propagate in the
+ // other branch. However, sometimes our boolean values are not compared to 0 or 1. In those cases
+ // we cannot make an assumption for the `else` branch.
+ if (variable->GetType() == DataType::Type::kBool &&
+ constant->IsIntConstant() &&
+ (constant->AsIntConstant()->IsTrue() || constant->AsIntConstant()->IsFalse())) {
+ HBasicBlock* other_starting_block =
+ condition->IsEqual() ? inst->IfFalseSuccessor() : inst->IfTrueSuccessor();
+ DCHECK_NE(other_starting_block, starting_block);
+
+ HConstant* other_constant = constant->AsIntConstant()->IsTrue() ?
+ GetGraph()->GetIntConstant(0) :
+ GetGraph()->GetIntConstant(1);
+ DCHECK_NE(other_constant, constant);
+ PropagateValue(other_starting_block, variable, other_constant);
+ }
+}
void InstructionWithAbsorbingInputSimplifier::VisitShift(HBinaryOperation* instruction) {
DCHECK(instruction->IsShl() || instruction->IsShr() || instruction->IsUShr());
diff --git a/compiler/optimizing/constant_folding.h b/compiler/optimizing/constant_folding.h
index 72bd95b..07388d2 100644
--- a/compiler/optimizing/constant_folding.h
+++ b/compiler/optimizing/constant_folding.h
@@ -19,6 +19,7 @@
#include "nodes.h"
#include "optimization.h"
+#include "optimizing/optimizing_compiler_stats.h"
namespace art {
@@ -39,7 +40,8 @@
*/
class HConstantFolding : public HOptimization {
public:
- HConstantFolding(HGraph* graph, const char* name) : HOptimization(graph, name) {}
+ HConstantFolding(HGraph* graph, OptimizingCompilerStats* stats, const char* name)
+ : HOptimization(graph, name, stats) {}
bool Run() override;
diff --git a/compiler/optimizing/constant_folding_test.cc b/compiler/optimizing/constant_folding_test.cc
index 74d9d3a..3bcdc1c 100644
--- a/compiler/optimizing/constant_folding_test.cc
+++ b/compiler/optimizing/constant_folding_test.cc
@@ -58,7 +58,7 @@
std::string actual_before = printer_before.str();
EXPECT_EQ(expected_before, actual_before);
- HConstantFolding(graph_, "constant_folding").Run();
+ HConstantFolding(graph_, /* stats= */ nullptr, "constant_folding").Run();
GraphChecker graph_checker_cf(graph_);
graph_checker_cf.Run();
ASSERT_TRUE(graph_checker_cf.IsValid());
diff --git a/compiler/optimizing/dead_code_elimination.cc b/compiler/optimizing/dead_code_elimination.cc
index d808f2c..ee868db 100644
--- a/compiler/optimizing/dead_code_elimination.cc
+++ b/compiler/optimizing/dead_code_elimination.cc
@@ -16,11 +16,13 @@
#include "dead_code_elimination.h"
+#include "android-base/logging.h"
#include "base/array_ref.h"
#include "base/bit_vector-inl.h"
#include "base/scoped_arena_allocator.h"
#include "base/scoped_arena_containers.h"
#include "base/stl_util.h"
+#include "optimizing/nodes.h"
#include "ssa_phi_elimination.h"
namespace art {
@@ -213,6 +215,9 @@
// | ...
// | instr_n
// | foo() // always throws
+// | instr_n+2
+// | ...
+// | instr_n+m
// \ goto B2
// \ /
// B2
@@ -234,7 +239,7 @@
// other optimization opportunities, such as code sinking.
bool HDeadCodeElimination::SimplifyAlwaysThrows() {
HBasicBlock* exit = graph_->GetExitBlock();
- if (exit == nullptr) {
+ if (!graph_->HasAlwaysThrowingInvokes() || exit == nullptr) {
return false;
}
@@ -260,28 +265,43 @@
continue;
}
- HInstruction* last = block->GetLastInstruction();
- HInstruction* prev = last->GetPrevious();
- if (prev == nullptr) {
- DCHECK_EQ(block->GetFirstInstruction(), block->GetLastInstruction());
- continue;
- }
-
- if (prev->AlwaysThrows() &&
- last->IsGoto() &&
+ if (block->GetLastInstruction()->IsGoto() &&
block->GetPhis().IsEmpty() &&
block->GetPredecessors().size() == 1u) {
HBasicBlock* pred = block->GetSinglePredecessor();
HBasicBlock* succ = block->GetSingleSuccessor();
- // Ensure no computations are merged through throwing block.
- // This does not prevent the optimization per se, but would
- // require an elaborate clean up of the SSA graph.
+ // Ensure no computations are merged through throwing block. This does not prevent the
+ // optimization per se, but would require an elaborate clean up of the SSA graph.
if (succ != exit &&
!block->Dominates(pred) &&
pred->Dominates(succ) &&
succ->GetPredecessors().size() > 1u &&
succ->GetPhis().IsEmpty()) {
- block->ReplaceSuccessor(succ, exit);
+ // We iterate to find the first instruction that always throws. If two instructions always
+ // throw, the first one will throw and the second one will never be reached.
+ HInstruction* throwing_instruction = nullptr;
+ for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
+ if (it.Current()->AlwaysThrows()) {
+ throwing_instruction = it.Current();
+ break;
+ }
+ }
+
+ if (throwing_instruction == nullptr) {
+ // No always-throwing instruction found. Continue with the rest of the blocks.
+ continue;
+ }
+
+ // We split the block at the throwing instruction, and the instructions after the throwing
+ // instructions will be disconnected from the graph after `block` points to the exit.
+ // `RemoveDeadBlocks` will take care of removing this new block and its instructions.
+ // Even though `SplitBefore` doesn't guarantee the graph to remain in SSA form, it is fine
+ // since we do not break it.
+ HBasicBlock* new_block = block->SplitBefore(throwing_instruction->GetNext(),
+ /* require_graph_not_in_ssa_form= */ false);
+ DCHECK_EQ(block->GetSingleSuccessor(), new_block);
+ block->ReplaceSuccessor(new_block, exit);
+
rerun_dominance_and_loop_analysis = true;
MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke);
// Perform a quick follow up optimization on object != null control dependences
diff --git a/compiler/optimizing/graph_checker.cc b/compiler/optimizing/graph_checker.cc
index d1769ce..1071151 100644
--- a/compiler/optimizing/graph_checker.cc
+++ b/compiler/optimizing/graph_checker.cc
@@ -159,6 +159,24 @@
}
}
+ // Make sure the first instruction of a catch block is always a Nop that emits an environment.
+ if (block->IsCatchBlock()) {
+ if (!block->GetFirstInstruction()->IsNop()) {
+ AddError(StringPrintf("Block %d doesn't have a Nop as its first instruction.",
+ current_block_->GetBlockId()));
+ } else {
+ HNop* nop = block->GetFirstInstruction()->AsNop();
+ if (!nop->NeedsEnvironment()) {
+ AddError(
+ StringPrintf("%s:%d is a Nop and the first instruction of block %d, but it doesn't "
+ "need an environment.",
+ nop->DebugName(),
+ nop->GetId(),
+ current_block_->GetBlockId()));
+ }
+ }
+ }
+
// Visit this block's list of phis.
for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
HInstruction* current = it.Current();
@@ -342,14 +360,15 @@
}
void GraphChecker::VisitLoadException(HLoadException* load) {
- // Ensure that LoadException is the first instruction in a catch block.
+ // Ensure that LoadException is the second instruction in a catch block. The first one should be a
+ // Nop (checked separately).
if (!load->GetBlock()->IsCatchBlock()) {
AddError(StringPrintf("%s:%d is in a non-catch block %d.",
load->DebugName(),
load->GetId(),
load->GetBlock()->GetBlockId()));
- } else if (load->GetBlock()->GetFirstInstruction() != load) {
- AddError(StringPrintf("%s:%d is not the first instruction in catch block %d.",
+ } else if (load->GetBlock()->GetFirstInstruction()->GetNext() != load) {
+ AddError(StringPrintf("%s:%d is not the second instruction in catch block %d.",
load->DebugName(),
load->GetId(),
load->GetBlock()->GetBlockId()));
@@ -513,17 +532,10 @@
instruction->GetId(),
current_block_->GetBlockId()));
} else if (instruction->CanThrowIntoCatchBlock()) {
- // Find the top-level environment. This corresponds to the environment of
- // the catch block since we do not inline methods with try/catch.
- HEnvironment* environment = instruction->GetEnvironment();
- while (environment->GetParent() != nullptr) {
- environment = environment->GetParent();
- }
-
- // Find all catch blocks and test that `instruction` has an environment
- // value for each one.
+ // Find all catch blocks and test that `instruction` has an environment value for each one.
const HTryBoundary& entry = instruction->GetBlock()->GetTryCatchInformation()->GetTryEntry();
for (HBasicBlock* catch_block : entry.GetExceptionHandlers()) {
+ const HEnvironment* environment = catch_block->GetFirstInstruction()->GetEnvironment();
for (HInstructionIterator phi_it(catch_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
HPhi* catch_phi = phi_it.Current()->AsPhi();
if (environment->GetInstructionAt(catch_phi->GetRegNumber()) == nullptr) {
@@ -674,13 +686,14 @@
loop_information->GetPreHeader()->GetSuccessors().size()));
}
- if (loop_information->GetSuspendCheck() == nullptr) {
- AddError(StringPrintf(
- "Loop with header %d does not have a suspend check.",
- loop_header->GetBlockId()));
+ if (!GetGraph()->SuspendChecksAreAllowedToNoOp() &&
+ loop_information->GetSuspendCheck() == nullptr) {
+ AddError(StringPrintf("Loop with header %d does not have a suspend check.",
+ loop_header->GetBlockId()));
}
- if (loop_information->GetSuspendCheck() != loop_header->GetFirstInstructionDisregardMoves()) {
+ if (!GetGraph()->SuspendChecksAreAllowedToNoOp() &&
+ loop_information->GetSuspendCheck() != loop_header->GetFirstInstructionDisregardMoves()) {
AddError(StringPrintf(
"Loop header %d does not have the loop suspend check as the first instruction.",
loop_header->GetBlockId()));
diff --git a/compiler/optimizing/induction_var_range.cc b/compiler/optimizing/induction_var_range.cc
index ad3d1a9..7f85483 100644
--- a/compiler/optimizing/induction_var_range.cc
+++ b/compiler/optimizing/induction_var_range.cc
@@ -17,6 +17,7 @@
#include "induction_var_range.h"
#include <limits>
+#include "optimizing/nodes.h"
namespace art {
@@ -1064,10 +1065,13 @@
case HInductionVarAnalysis::kLinear:
if (*stride_value > 0) {
lower = nullptr;
+ return GenerateLastValueLinear(
+ context, loop, info, trip, graph, block, /*is_min=*/false, upper);
} else {
upper = nullptr;
+ return GenerateLastValueLinear(
+ context, loop, info, trip, graph, block, /*is_min=*/true, lower);
}
- break;
case HInductionVarAnalysis::kPolynomial:
return GenerateLastValuePolynomial(context, loop, info, trip, graph, block, lower);
case HInductionVarAnalysis::kGeometric:
@@ -1113,6 +1117,54 @@
GenerateCode(context, loop, info, trip, graph, block, /*is_min=*/ false, upper);
}
+bool InductionVarRange::GenerateLastValueLinear(const HBasicBlock* context,
+ const HLoopInformation* loop,
+ HInductionVarAnalysis::InductionInfo* info,
+ HInductionVarAnalysis::InductionInfo* trip,
+ HGraph* graph,
+ HBasicBlock* block,
+ bool is_min,
+ /*out*/ HInstruction** result) const {
+ DataType::Type type = info->type;
+ // Avoid any narrowing linear induction or any type mismatch between the linear induction and the
+ // trip count expression.
+ if (HInductionVarAnalysis::IsNarrowingLinear(info) || trip->type != type) {
+ return false;
+ }
+
+ // Stride value must be a known constant that fits into int32.
+ int64_t stride_value = 0;
+ if (!IsConstant(context, loop, info->op_a, kExact, &stride_value) ||
+ !CanLongValueFitIntoInt(stride_value)) {
+ return false;
+ }
+
+ // We require `a` to be a constant value that didn't overflow.
+ const bool is_min_a = stride_value >= 0 ? is_min : !is_min;
+ Value val_a = GetVal(context, loop, trip, trip, is_min_a);
+ HInstruction* opb;
+ if (!IsConstantValue(val_a) ||
+ !GenerateCode(context, loop, info->op_b, trip, graph, block, is_min, &opb)) {
+ return false;
+ }
+
+ if (graph != nullptr) {
+ ArenaAllocator* allocator = graph->GetAllocator();
+ HInstruction* oper;
+ HInstruction* opa = graph->GetConstant(type, val_a.b_constant);
+ if (stride_value == 1) {
+ oper = new (allocator) HAdd(type, opa, opb);
+ } else if (stride_value == -1) {
+ oper = new (graph->GetAllocator()) HSub(type, opb, opa);
+ } else {
+ HInstruction* mul = new (allocator) HMul(type, graph->GetConstant(type, stride_value), opa);
+ oper = new (allocator) HAdd(type, Insert(block, mul), opb);
+ }
+ *result = Insert(block, oper);
+ }
+ return true;
+}
+
bool InductionVarRange::GenerateLastValuePolynomial(const HBasicBlock* context,
const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
diff --git a/compiler/optimizing/induction_var_range.h b/compiler/optimizing/induction_var_range.h
index 552837c..6555bc2 100644
--- a/compiler/optimizing/induction_var_range.h
+++ b/compiler/optimizing/induction_var_range.h
@@ -317,6 +317,15 @@
/*out*/ bool* needs_finite_test,
/*out*/ bool* needs_taken_test) const;
+ bool GenerateLastValueLinear(const HBasicBlock* context,
+ const HLoopInformation* loop,
+ HInductionVarAnalysis::InductionInfo* info,
+ HInductionVarAnalysis::InductionInfo* trip,
+ HGraph* graph,
+ HBasicBlock* block,
+ bool is_min,
+ /*out*/ HInstruction** result) const;
+
bool GenerateLastValuePolynomial(const HBasicBlock* context,
const HLoopInformation* loop,
HInductionVarAnalysis::InductionInfo* info,
diff --git a/compiler/optimizing/induction_var_range_test.cc b/compiler/optimizing/induction_var_range_test.cc
index 962123d..a83246c 100644
--- a/compiler/optimizing/induction_var_range_test.cc
+++ b/compiler/optimizing/induction_var_range_test.cc
@@ -1064,10 +1064,6 @@
HInstruction* last = range_.GenerateLastValue(phi, graph_, loop_preheader_);
ASSERT_TRUE(last->IsSub());
ExpectInt(1000, last->InputAt(0));
- ASSERT_TRUE(last->InputAt(1)->IsNeg());
- last = last->InputAt(1)->InputAt(0);
- ASSERT_TRUE(last->IsSub());
- ExpectInt(0, last->InputAt(0));
ExpectInt(1000, last->InputAt(1));
// Loop logic.
diff --git a/compiler/optimizing/inliner.cc b/compiler/optimizing/inliner.cc
index f73c0d3..cdd8fb2 100644
--- a/compiler/optimizing/inliner.cc
+++ b/compiler/optimizing/inliner.cc
@@ -141,7 +141,11 @@
}
bool did_inline = false;
- bool did_set_always_throws = false;
+ // The inliner is the only phase that sets invokes as `always throwing`, and since we only run the
+ // inliner once per graph this value should always be false at the beginning of the inlining
+ // phase. This is important since we use `HasAlwaysThrowingInvokes` to know whether the inliner
+ // phase performed a relevant change in the graph.
+ DCHECK(!graph_->HasAlwaysThrowingInvokes());
// Initialize the number of instructions for the method being compiled. Recursive calls
// to HInliner::Run have already updated the instruction count.
@@ -182,7 +186,7 @@
call->GetMethodReference().PrettyMethod(/* with_signature= */ false);
// Tests prevent inlining by having $noinline$ in their method names.
if (callee_name.find("$noinline$") == std::string::npos) {
- if (TryInline(call, &did_set_always_throws)) {
+ if (TryInline(call)) {
did_inline = true;
} else if (honor_inline_directives) {
bool should_have_inlined = (callee_name.find("$inline$") != std::string::npos);
@@ -192,7 +196,7 @@
} else {
DCHECK(!honor_inline_directives);
// Normal case: try to inline.
- if (TryInline(call, &did_set_always_throws)) {
+ if (TryInline(call)) {
did_inline = true;
}
}
@@ -201,7 +205,9 @@
}
}
- return did_inline || did_set_always_throws;
+ // We return true if we either inlined at least one method, or we marked one of our methods as
+ // always throwing.
+ return did_inline || graph_->HasAlwaysThrowingInvokes();
}
static bool IsMethodOrDeclaringClassFinal(ArtMethod* method)
@@ -436,7 +442,7 @@
return throw_seen;
}
-bool HInliner::TryInline(HInvoke* invoke_instruction, /*inout*/ bool* did_set_always_throws) {
+bool HInliner::TryInline(HInvoke* invoke_instruction) {
MaybeRecordStat(stats_, MethodCompilationStat::kTryInline);
// Don't bother to move further if we know the method is unresolved or the invocation is
@@ -487,11 +493,10 @@
} else {
invoke_to_analyze = invoke_instruction;
}
- // Set always throws property for non-inlined method call with single
- // target.
+ // Set always throws property for non-inlined method call with single target.
if (AlwaysThrows(actual_method)) {
- invoke_to_analyze->SetAlwaysThrows(true);
- *did_set_always_throws = true;
+ invoke_to_analyze->SetAlwaysThrows(/* always_throws= */ true);
+ graph_->SetHasAlwaysThrowingInvokes(/* value= */ true);
}
}
return result;
@@ -802,7 +807,6 @@
// Run type propagation to get the guard typed, and eventually propagate the
// type of the receiver.
ReferenceTypePropagation rtp_fixup(graph_,
- outer_compilation_unit_.GetClassLoader(),
outer_compilation_unit_.GetDexCache(),
/* is_first_run= */ false);
rtp_fixup.Run();
@@ -1024,7 +1028,6 @@
// Run type propagation to get the guards typed.
ReferenceTypePropagation rtp_fixup(graph_,
- outer_compilation_unit_.GetClassLoader(),
outer_compilation_unit_.GetDexCache(),
/* is_first_run= */ false);
rtp_fixup.Run();
@@ -1215,7 +1218,6 @@
// Run type propagation to get the guard typed.
ReferenceTypePropagation rtp_fixup(graph_,
- outer_compilation_unit_.GetClassLoader(),
outer_compilation_unit_.GetDexCache(),
/* is_first_run= */ false);
rtp_fixup.Run();
@@ -1232,7 +1234,6 @@
// Actual return value has a more specific type than the method's declared
// return type. Run RTP again on the outer graph to propagate it.
ReferenceTypePropagation(graph_,
- outer_compilation_unit_.GetClassLoader(),
outer_compilation_unit_.GetDexCache(),
/* is_first_run= */ false).Run();
}
@@ -1684,7 +1685,6 @@
Handle<mirror::DexCache> dex_cache =
graph_->GetHandleCache()->NewHandle(referrer->GetDexCache());
ReferenceTypePropagation rtp(graph_,
- outer_compilation_unit_.GetClassLoader(),
dex_cache,
/* is_first_run= */ false);
rtp.Visit(iget);
@@ -1807,7 +1807,6 @@
// are more specific than the declared ones, run RTP again on the inner graph.
if (run_rtp || ArgumentTypesMoreSpecific(invoke_instruction, resolved_method)) {
ReferenceTypePropagation(callee_graph,
- outer_compilation_unit_.GetClassLoader(),
dex_compilation_unit.GetDexCache(),
/* is_first_run= */ false).Run();
}
@@ -1821,8 +1820,9 @@
// If this function returns true, it will also set out_number_of_instructions to
// the number of instructions in the inlined body.
bool HInliner::CanInlineBody(const HGraph* callee_graph,
- const HBasicBlock* target_block,
+ HInvoke* invoke,
size_t* out_number_of_instructions) const {
+ const HBasicBlock* target_block = invoke->GetBlock();
ArtMethod* const resolved_method = callee_graph->GetArtMethod();
HBasicBlock* exit_block = callee_graph->GetExitBlock();
@@ -1864,6 +1864,11 @@
}
if (!has_one_return) {
+ // If we know that the method always throws with the particular parameters, set it as such. This
+ // is better than using the dex instructions as we have more information about this particular
+ // call.
+ invoke->SetAlwaysThrows(/* always_throws= */ true);
+ graph_->SetHasAlwaysThrowingInvokes(/* value= */ true);
LOG_FAIL(stats_, MethodCompilationStat::kNotInlinedAlwaysThrows)
<< "Method " << resolved_method->PrettyMethod()
<< " could not be inlined because it always throws";
@@ -2060,7 +2065,7 @@
RunOptimizations(callee_graph, code_item, dex_compilation_unit);
size_t number_of_instructions = 0;
- if (!CanInlineBody(callee_graph, invoke_instruction->GetBlock(), &number_of_instructions)) {
+ if (!CanInlineBody(callee_graph, invoke_instruction, &number_of_instructions)) {
return false;
}
@@ -2099,7 +2104,7 @@
// Note: if the outermost_graph_ is being compiled OSR, we should not run any
// optimization that could lead to a HDeoptimize. The following optimizations do not.
HDeadCodeElimination dce(callee_graph, inline_stats_, "dead_code_elimination$inliner");
- HConstantFolding fold(callee_graph, "constant_folding$inliner");
+ HConstantFolding fold(callee_graph, inline_stats_, "constant_folding$inliner");
InstructionSimplifier simplify(callee_graph, codegen_, inline_stats_);
HOptimization* optimizations[] = {
diff --git a/compiler/optimizing/inliner.h b/compiler/optimizing/inliner.h
index a2c2085..e33160e 100644
--- a/compiler/optimizing/inliner.h
+++ b/compiler/optimizing/inliner.h
@@ -70,9 +70,7 @@
kInlineCacheMissingTypes = 5
};
- // We set `did_set_always_throws` as true if we analyzed `invoke_instruction` and it always
- // throws.
- bool TryInline(HInvoke* invoke_instruction, /*inout*/ bool* did_set_always_throws);
+ bool TryInline(HInvoke* invoke_instruction);
// Try to inline `resolved_method` in place of `invoke_instruction`. `do_rtp` is whether
// reference type propagation can run after the inlining. If the inlining is successful, this
@@ -142,7 +140,7 @@
// This checks for instructions and constructs that we do not support
// inlining, such as inlining a throw instruction into a try block.
bool CanInlineBody(const HGraph* callee_graph,
- const HBasicBlock* target_block,
+ HInvoke* invoke,
size_t* out_number_of_instructions) const
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/compiler/optimizing/instruction_builder.cc b/compiler/optimizing/instruction_builder.cc
index e0bdd09..ba58c8d 100644
--- a/compiler/optimizing/instruction_builder.cc
+++ b/compiler/optimizing/instruction_builder.cc
@@ -343,6 +343,10 @@
// Suspend checks were inserted into loop headers during building of dominator tree.
DCHECK(block->GetFirstInstruction()->IsSuspendCheck());
return block->GetFirstInstruction() != block->GetLastInstruction();
+ } else if (block->IsCatchBlock()) {
+ // Nops were inserted into the beginning of catch blocks.
+ DCHECK(block->GetFirstInstruction()->IsNop());
+ return block->GetFirstInstruction() != block->GetLastInstruction();
} else {
return !block->GetInstructions().IsEmpty();
}
@@ -387,6 +391,11 @@
// This is slightly odd because the loop header might not be empty (TryBoundary).
// But we're still creating the environment with locals from the top of the block.
InsertInstructionAtTop(suspend_check);
+ } else if (current_block_->IsCatchBlock()) {
+ // We add an environment emitting instruction at the beginning of each catch block, in order
+ // to support try catch inlining.
+ // This is slightly odd because the catch block might not be empty (TryBoundary).
+ InsertInstructionAtTop(new (allocator_) HNop(block_dex_pc, /* needs_environment= */ true));
}
if (block_dex_pc == kNoDexPc || current_block_ != block_builder_->GetBlockAt(block_dex_pc)) {
@@ -414,7 +423,7 @@
}
if (native_debuggable && native_debug_info_locations->IsBitSet(dex_pc)) {
- AppendInstruction(new (allocator_) HNativeDebugInfo(dex_pc));
+ AppendInstruction(new (allocator_) HNop(dex_pc, /* needs_environment= */ true));
}
// Note: There may be no Thread for gtests.
diff --git a/compiler/optimizing/instruction_simplifier_shared.cc b/compiler/optimizing/instruction_simplifier_shared.cc
index dc60ba6..fb8b01b 100644
--- a/compiler/optimizing/instruction_simplifier_shared.cc
+++ b/compiler/optimizing/instruction_simplifier_shared.cc
@@ -244,7 +244,7 @@
// The access may require a runtime call or the original array pointer.
return false;
}
- if (kEmitCompilerReadBarrier &&
+ if (gUseReadBarrier &&
!kUseBakerReadBarrier &&
access->IsArrayGet() &&
access->GetType() == DataType::Type::kReference) {
diff --git a/compiler/optimizing/intrinsics.cc b/compiler/optimizing/intrinsics.cc
index f2d2b45..0feb92d 100644
--- a/compiler/optimizing/intrinsics.cc
+++ b/compiler/optimizing/intrinsics.cc
@@ -392,7 +392,7 @@
}
void IntrinsicVisitor::CreateReferenceRefersToLocations(HInvoke* invoke) {
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
// Unimplemented for non-Baker read barrier.
return;
}
diff --git a/compiler/optimizing/intrinsics_arm64.cc b/compiler/optimizing/intrinsics_arm64.cc
index 646f4f2..0ce082b 100644
--- a/compiler/optimizing/intrinsics_arm64.cc
+++ b/compiler/optimizing/intrinsics_arm64.cc
@@ -92,7 +92,7 @@
public:
ReadBarrierSystemArrayCopySlowPathARM64(HInstruction* instruction, Location tmp)
: SlowPathCodeARM64(instruction), tmp_(tmp) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
}
@@ -711,7 +711,7 @@
Location trg_loc = locations->Out();
Register trg = RegisterFrom(trg_loc, type);
- if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (type == DataType::Type::kReference && gUseReadBarrier && kUseBakerReadBarrier) {
// UnsafeGetObject/UnsafeGetObjectVolatile with Baker's read barrier case.
Register temp = WRegisterFrom(locations->GetTemp(0));
MacroAssembler* masm = codegen->GetVIXLAssembler();
@@ -754,7 +754,7 @@
}
static void CreateIntIntIntToIntLocations(ArenaAllocator* allocator, HInvoke* invoke) {
- bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+ bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
LocationSummary* locations =
new (allocator) LocationSummary(invoke,
can_call
@@ -1096,7 +1096,7 @@
}
static void CreateUnsafeCASLocations(ArenaAllocator* allocator, HInvoke* invoke) {
- const bool can_call = kEmitCompilerReadBarrier && IsUnsafeCASObject(invoke);
+ const bool can_call = gUseReadBarrier && IsUnsafeCASObject(invoke);
LocationSummary* locations =
new (allocator) LocationSummary(invoke,
can_call
@@ -1448,7 +1448,7 @@
vixl::aarch64::Label* exit_loop = &exit_loop_label;
vixl::aarch64::Label* cmp_failure = &exit_loop_label;
- if (kEmitCompilerReadBarrier && type == DataType::Type::kReference) {
+ if (gUseReadBarrier && type == DataType::Type::kReference) {
// We need to store the `old_value` in a non-scratch register to make sure
// the read barrier in the slow path does not clobber it.
old_value = WRegisterFrom(locations->GetTemp(0)); // The old value from main path.
@@ -1523,12 +1523,12 @@
}
void IntrinsicLocationsBuilderARM64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
CreateUnsafeCASLocations(allocator_, invoke);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// We need two non-scratch temporary registers for read barrier.
LocationSummary* locations = invoke->GetLocations();
if (kUseBakerReadBarrier) {
@@ -1578,7 +1578,7 @@
}
void IntrinsicCodeGeneratorARM64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
GenUnsafeCas(invoke, DataType::Type::kReference, codegen_);
}
@@ -2814,7 +2814,7 @@
void IntrinsicLocationsBuilderARM64::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -2866,7 +2866,7 @@
locations->AddTemp(Location::RequiresRegister());
locations->AddTemp(Location::RequiresRegister());
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Temporary register IP0, obtained from the VIXL scratch register
// pool, cannot be used in ReadBarrierSystemArrayCopySlowPathARM64
// (because that register is clobbered by ReadBarrierMarkRegX
@@ -2884,7 +2884,7 @@
void IntrinsicCodeGeneratorARM64::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
MacroAssembler* masm = GetVIXLAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -2991,7 +2991,7 @@
UseScratchRegisterScope temps(masm);
Location temp3_loc; // Used only for Baker read barrier.
Register temp3;
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
temp3_loc = locations->GetTemp(2);
temp3 = WRegisterFrom(temp3_loc);
} else {
@@ -3004,7 +3004,7 @@
// or the destination is Object[]. If none of these checks succeed, we go to the
// slow path.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
if (!optimizations.GetSourceIsNonPrimitiveArray()) {
// /* HeapReference<Class> */ temp1 = src->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
@@ -3165,7 +3165,7 @@
} else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
// Bail out if the source is not a non primitive array.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ temp1 = src->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
temp1_loc,
@@ -3215,7 +3215,7 @@
__ Cbz(WRegisterFrom(length), &done);
}
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// TODO: Also convert this intrinsic to the IsGcMarking strategy?
// SystemArrayCopy implementation for Baker read barriers (see
@@ -3451,7 +3451,7 @@
void IntrinsicLocationsBuilderARM64::VisitReferenceGetReferent(HInvoke* invoke) {
IntrinsicVisitor::CreateReferenceGetReferentLocations(invoke, codegen_);
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && invoke->GetLocations() != nullptr) {
+ if (gUseReadBarrier && kUseBakerReadBarrier && invoke->GetLocations() != nullptr) {
invoke->GetLocations()->AddTemp(Location::RequiresRegister());
}
}
@@ -3466,7 +3466,7 @@
SlowPathCodeARM64* slow_path = new (GetAllocator()) IntrinsicSlowPathARM64(invoke);
codegen_->AddSlowPath(slow_path);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Check self->GetWeakRefAccessEnabled().
UseScratchRegisterScope temps(masm);
Register temp = temps.AcquireW();
@@ -3493,7 +3493,7 @@
// Load the value from the field.
uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
out,
WRegisterFrom(obj),
@@ -3533,7 +3533,7 @@
__ Cmp(tmp, other);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
DCHECK(kUseBakerReadBarrier);
vixl::aarch64::Label calculate_result;
@@ -4629,7 +4629,7 @@
method.X(),
ArtField::DeclaringClassOffset().Int32Value(),
/*fixup_label=*/ nullptr,
- kCompilerReadBarrierOption);
+ gCompilerReadBarrierOption);
}
}
} else {
@@ -4683,7 +4683,7 @@
}
// Add a temporary for offset.
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
GetExpectedVarHandleCoordinatesCount(invoke) == 0u) { // For static fields.
// To preserve the offset value across the non-Baker read barrier slow path
// for loading the declaring class, use a fixed callee-save register.
@@ -4706,7 +4706,7 @@
return;
}
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
invoke->GetType() == DataType::Type::kReference &&
invoke->GetIntrinsic() != Intrinsics::kVarHandleGet &&
invoke->GetIntrinsic() != Intrinsics::kVarHandleGetOpaque) {
@@ -4746,7 +4746,7 @@
DCHECK(use_load_acquire || order == std::memory_order_relaxed);
// Load the value from the target location.
- if (type == DataType::Type::kReference && kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (type == DataType::Type::kReference && gUseReadBarrier && kUseBakerReadBarrier) {
// Piggy-back on the field load path using introspection for the Baker read barrier.
// The `target.offset` is a temporary, use it for field address.
Register tmp_ptr = target.offset.X();
@@ -4947,7 +4947,7 @@
uint32_t number_of_arguments = invoke->GetNumberOfArguments();
DataType::Type value_type = GetDataTypeFromShorty(invoke, number_of_arguments - 1u);
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
value_type == DataType::Type::kReference) {
// Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
// the passed reference and reloads it from the field. This breaks the read barriers
@@ -4961,7 +4961,7 @@
LocationSummary* locations = CreateVarHandleCommonLocations(invoke);
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
// We need callee-save registers for both the class object and offset instead of
// the temporaries reserved in CreateVarHandleCommonLocations().
static_assert(POPCOUNT(kArm64CalleeSaveRefSpills) >= 2u);
@@ -5002,7 +5002,7 @@
locations->AddTemp(Location::RequiresRegister());
}
}
- if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ if (gUseReadBarrier && value_type == DataType::Type::kReference) {
// Add a temporary for the `old_value_temp` in slow path.
locations->AddTemp(Location::RequiresRegister());
}
@@ -5068,7 +5068,7 @@
// except for references that need the offset for the read barrier.
UseScratchRegisterScope temps(masm);
Register tmp_ptr = target.offset.X();
- if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ if (gUseReadBarrier && value_type == DataType::Type::kReference) {
tmp_ptr = temps.AcquireX();
}
__ Add(tmp_ptr, target.object.X(), target.offset.X());
@@ -5151,7 +5151,7 @@
vixl::aarch64::Label* exit_loop = &exit_loop_label;
vixl::aarch64::Label* cmp_failure = &exit_loop_label;
- if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ if (gUseReadBarrier && value_type == DataType::Type::kReference) {
// The `old_value_temp` is used first for the marked `old_value` and then for the unmarked
// reloaded old value for subsequent CAS in the slow path. It cannot be a scratch register.
size_t expected_coordinates_count = GetExpectedVarHandleCoordinatesCount(invoke);
@@ -5296,7 +5296,7 @@
return;
}
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
invoke->GetType() == DataType::Type::kReference) {
// Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
// the passed reference and reloads it from the field, thus seeing the new value
@@ -5372,7 +5372,7 @@
// except for references that need the offset for the non-Baker read barrier.
UseScratchRegisterScope temps(masm);
Register tmp_ptr = target.offset.X();
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
value_type == DataType::Type::kReference) {
tmp_ptr = temps.AcquireX();
}
@@ -5402,7 +5402,7 @@
// the new value unless it is zero bit pattern (+0.0f or +0.0) and need another one
// in GenerateGetAndUpdate(). We have allocated a normal temporary to handle that.
old_value = CPURegisterFrom(locations->GetTemp(1u), load_store_type);
- } else if ((kEmitCompilerReadBarrier && kUseBakerReadBarrier) &&
+ } else if ((gUseReadBarrier && kUseBakerReadBarrier) &&
value_type == DataType::Type::kReference) {
// Load the old value initially to a scratch register.
// We shall move it to `out` later with a read barrier.
@@ -5450,7 +5450,7 @@
__ Sxtb(out.W(), old_value.W());
} else if (value_type == DataType::Type::kInt16) {
__ Sxth(out.W(), old_value.W());
- } else if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ } else if (gUseReadBarrier && value_type == DataType::Type::kReference) {
if (kUseBakerReadBarrier) {
codegen->GenerateIntrinsicCasMoveWithBakerReadBarrier(out.W(), old_value.W());
} else {
diff --git a/compiler/optimizing/intrinsics_arm_vixl.cc b/compiler/optimizing/intrinsics_arm_vixl.cc
index d850cad..da47fa6 100644
--- a/compiler/optimizing/intrinsics_arm_vixl.cc
+++ b/compiler/optimizing/intrinsics_arm_vixl.cc
@@ -120,7 +120,7 @@
public:
explicit ReadBarrierSystemArrayCopySlowPathARMVIXL(HInstruction* instruction)
: SlowPathCodeARMVIXL(instruction) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
}
@@ -1242,7 +1242,7 @@
void IntrinsicLocationsBuilderARMVIXL::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -1265,7 +1265,7 @@
if (length != nullptr && !assembler_->ShifterOperandCanAlwaysHold(length->GetValue())) {
locations->SetInAt(4, Location::RequiresRegister());
}
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Temporary register IP cannot be used in
// ReadBarrierSystemArrayCopySlowPathARM (because that register
// is clobbered by ReadBarrierMarkRegX entry points). Get an extra
@@ -1339,7 +1339,7 @@
void IntrinsicCodeGeneratorARMVIXL::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
ArmVIXLAssembler* assembler = GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -1453,7 +1453,7 @@
// or the destination is Object[]. If none of these checks succeed, we go to the
// slow path.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
if (!optimizations.GetSourceIsNonPrimitiveArray()) {
// /* HeapReference<Class> */ temp1 = src->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
@@ -1584,7 +1584,7 @@
} else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
// Bail out if the source is not a non primitive array.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ temp1 = src->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
invoke, temp1_loc, src, class_offset, temp2_loc, /* needs_null_check= */ false);
@@ -1621,7 +1621,7 @@
__ CompareAndBranchIfZero(RegisterFrom(length), &done, /* is_far_target= */ false);
}
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// TODO: Also convert this intrinsic to the IsGcMarking strategy?
// SystemArrayCopy implementation for Baker read barriers (see
@@ -2511,7 +2511,7 @@
SlowPathCodeARMVIXL* slow_path = new (GetAllocator()) IntrinsicSlowPathARMVIXL(invoke);
codegen_->AddSlowPath(slow_path);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Check self->GetWeakRefAccessEnabled().
UseScratchRegisterScope temps(assembler->GetVIXLAssembler());
vixl32::Register temp = temps.Acquire();
@@ -2539,7 +2539,7 @@
// Load the value from the field.
uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
out,
RegisterFrom(obj),
@@ -2587,7 +2587,7 @@
assembler->MaybeUnpoisonHeapReference(tmp);
codegen_->GenerateMemoryBarrier(MemBarrierKind::kLoadAny); // `referent` is volatile.
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
DCHECK(kUseBakerReadBarrier);
vixl32::Label calculate_result;
@@ -2613,7 +2613,7 @@
__ Bind(&calculate_result);
} else {
- DCHECK(!kEmitCompilerReadBarrier);
+ DCHECK(!gUseReadBarrier);
__ Sub(out, tmp, other);
}
@@ -2732,7 +2732,7 @@
}
break;
case DataType::Type::kReference:
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Piggy-back on the field load path using introspection for the Baker read barrier.
vixl32::Register temp = RegisterFrom(maybe_temp);
__ Add(temp, base, offset);
@@ -2777,7 +2777,7 @@
codegen->GenerateMemoryBarrier(
seq_cst_barrier ? MemBarrierKind::kAnyAny : MemBarrierKind::kLoadAny);
}
- if (type == DataType::Type::kReference && !(kEmitCompilerReadBarrier && kUseBakerReadBarrier)) {
+ if (type == DataType::Type::kReference && !(gUseReadBarrier && kUseBakerReadBarrier)) {
Location base_loc = LocationFrom(base);
Location index_loc = LocationFrom(offset);
codegen->MaybeGenerateReadBarrierSlow(invoke, out, out, base_loc, /* offset=*/ 0u, index_loc);
@@ -2802,7 +2802,7 @@
CodeGeneratorARMVIXL* codegen,
DataType::Type type,
bool atomic) {
- bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+ bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
ArenaAllocator* allocator = invoke->GetBlock()->GetGraph()->GetAllocator();
LocationSummary* locations =
new (allocator) LocationSummary(invoke,
@@ -2818,7 +2818,7 @@
locations->SetInAt(2, Location::RequiresRegister());
locations->SetOut(Location::RequiresRegister(),
(can_call ? Location::kOutputOverlap : Location::kNoOutputOverlap));
- if ((kEmitCompilerReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
+ if ((gUseReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
(type == DataType::Type::kInt64 && Use64BitExclusiveLoadStore(atomic, codegen))) {
// We need a temporary register for the read barrier marking slow
// path in CodeGeneratorARMVIXL::GenerateReferenceLoadWithBakerReadBarrier,
@@ -2837,7 +2837,7 @@
vixl32::Register offset = LowRegisterFrom(locations->InAt(2)); // Long offset, lo part only.
Location out = locations->Out();
Location maybe_temp = Location::NoLocation();
- if ((kEmitCompilerReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
+ if ((gUseReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) ||
(type == DataType::Type::kInt64 && Use64BitExclusiveLoadStore(atomic, codegen))) {
maybe_temp = locations->GetTemp(0);
}
@@ -3470,7 +3470,7 @@
// branch goes to the read barrier slow path that clobbers `success` anyway.
bool init_failure_for_cmp =
success.IsValid() &&
- !(kEmitCompilerReadBarrier && type == DataType::Type::kReference && expected.IsRegister());
+ !(gUseReadBarrier && type == DataType::Type::kReference && expected.IsRegister());
// Instruction scheduling: Loading a constant between LDREX* and using the loaded value
// is essentially free, so prepare the failure value here if we can.
bool init_failure_for_cmp_early =
@@ -3655,7 +3655,7 @@
};
static void CreateUnsafeCASLocations(ArenaAllocator* allocator, HInvoke* invoke) {
- const bool can_call = kEmitCompilerReadBarrier && IsUnsafeCASObject(invoke);
+ const bool can_call = gUseReadBarrier && IsUnsafeCASObject(invoke);
LocationSummary* locations =
new (allocator) LocationSummary(invoke,
can_call
@@ -3706,7 +3706,7 @@
vixl32::Label* exit_loop = &exit_loop_label;
vixl32::Label* cmp_failure = &exit_loop_label;
- if (kEmitCompilerReadBarrier && type == DataType::Type::kReference) {
+ if (gUseReadBarrier && type == DataType::Type::kReference) {
// If marking, check if the stored reference is a from-space reference to the same
// object as the to-space reference `expected`. If so, perform a custom CAS loop.
ReadBarrierCasSlowPathARMVIXL* slow_path =
@@ -3770,7 +3770,7 @@
}
void IntrinsicLocationsBuilderARMVIXL::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers (b/173104084).
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -3798,7 +3798,7 @@
}
void IntrinsicCodeGeneratorARMVIXL::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers (b/173104084).
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
GenUnsafeCas(invoke, DataType::Type::kReference, codegen_);
}
@@ -4351,7 +4351,7 @@
LocationFrom(target.object),
method,
ArtField::DeclaringClassOffset().Int32Value(),
- kCompilerReadBarrierOption);
+ gCompilerReadBarrierOption);
}
}
} else {
@@ -4403,7 +4403,7 @@
}
// Add a temporary for offset.
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
GetExpectedVarHandleCoordinatesCount(invoke) == 0u) { // For static fields.
// To preserve the offset value across the non-Baker read barrier slow path
// for loading the declaring class, use a fixed callee-save register.
@@ -4428,7 +4428,7 @@
return;
}
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
invoke->GetType() == DataType::Type::kReference &&
invoke->GetIntrinsic() != Intrinsics::kVarHandleGet &&
invoke->GetIntrinsic() != Intrinsics::kVarHandleGetOpaque) {
@@ -4476,7 +4476,7 @@
Location maybe_temp = Location::NoLocation();
Location maybe_temp2 = Location::NoLocation();
Location maybe_temp3 = Location::NoLocation();
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) {
+ if (gUseReadBarrier && kUseBakerReadBarrier && type == DataType::Type::kReference) {
// Reuse the offset temporary.
maybe_temp = LocationFrom(target.offset);
} else if (DataType::Is64BitType(type) && Use64BitExclusiveLoadStore(atomic, codegen)) {
@@ -4749,7 +4749,7 @@
uint32_t number_of_arguments = invoke->GetNumberOfArguments();
DataType::Type value_type = GetDataTypeFromShorty(invoke, number_of_arguments - 1u);
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
value_type == DataType::Type::kReference) {
// Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
// the passed reference and reloads it from the field. This breaks the read barriers
@@ -4763,7 +4763,7 @@
LocationSummary* locations = CreateVarHandleCommonLocations(invoke);
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
// We need callee-save registers for both the class object and offset instead of
// the temporaries reserved in CreateVarHandleCommonLocations().
static_assert(POPCOUNT(kArmCalleeSaveRefSpills) >= 2u);
@@ -4799,7 +4799,7 @@
locations->AddRegisterTemps(2u);
}
}
- if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ if (gUseReadBarrier && value_type == DataType::Type::kReference) {
// Add a temporary for store result, also used for the `old_value_temp` in slow path.
locations->AddTemp(Location::RequiresRegister());
}
@@ -4930,7 +4930,7 @@
vixl32::Label* exit_loop = &exit_loop_label;
vixl32::Label* cmp_failure = &exit_loop_label;
- if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ if (gUseReadBarrier && value_type == DataType::Type::kReference) {
// The `old_value_temp` is used first for the marked `old_value` and then for the unmarked
// reloaded old value for subsequent CAS in the slow path. This must not clobber `old_value`.
vixl32::Register old_value_temp = return_success ? RegisterFrom(out) : store_result;
@@ -5086,7 +5086,7 @@
return;
}
- if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
invoke->GetType() == DataType::Type::kReference) {
// Unsupported for non-Baker read barrier because the artReadBarrierSlow() ignores
// the passed reference and reloads it from the field, thus seeing the new value
@@ -5107,7 +5107,7 @@
// Add temps needed to do the GenerateGetAndUpdate() with core registers.
size_t temps_needed = (value_type == DataType::Type::kFloat64) ? 5u : 3u;
locations->AddRegisterTemps(temps_needed - locations->GetTempCount());
- } else if ((kEmitCompilerReadBarrier && !kUseBakerReadBarrier) &&
+ } else if ((gUseReadBarrier && !kUseBakerReadBarrier) &&
value_type == DataType::Type::kReference) {
// We need to preserve the declaring class (if present) and offset for read barrier
// slow paths, so we must use a separate temporary for the exclusive store result.
@@ -5213,7 +5213,7 @@
if (byte_swap) {
GenerateReverseBytes(assembler, DataType::Type::kInt32, arg, arg);
}
- } else if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ } else if (gUseReadBarrier && value_type == DataType::Type::kReference) {
if (kUseBakerReadBarrier) {
// Load the old value initially to a temporary register.
// We shall move it to `out` later with a read barrier.
@@ -5296,7 +5296,7 @@
} else {
__ Vmov(SRegisterFrom(out), RegisterFrom(old_value));
}
- } else if (kEmitCompilerReadBarrier && value_type == DataType::Type::kReference) {
+ } else if (gUseReadBarrier && value_type == DataType::Type::kReference) {
if (kUseBakerReadBarrier) {
codegen->GenerateIntrinsicCasMoveWithBakerReadBarrier(RegisterFrom(out),
RegisterFrom(old_value));
diff --git a/compiler/optimizing/intrinsics_x86.cc b/compiler/optimizing/intrinsics_x86.cc
index 7d90aae..0f6eb86 100644
--- a/compiler/optimizing/intrinsics_x86.cc
+++ b/compiler/optimizing/intrinsics_x86.cc
@@ -75,7 +75,7 @@
public:
explicit ReadBarrierSystemArrayCopySlowPathX86(HInstruction* instruction)
: SlowPathCode(instruction) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
}
@@ -1699,7 +1699,7 @@
case DataType::Type::kReference: {
Register output = output_loc.AsRegister<Register>();
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
if (kUseBakerReadBarrier) {
Address src(base, offset, ScaleFactor::TIMES_1, 0);
codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -1757,7 +1757,7 @@
HInvoke* invoke,
DataType::Type type,
bool is_volatile) {
- bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+ bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
LocationSummary* locations =
new (allocator) LocationSummary(invoke,
can_call
@@ -2103,7 +2103,7 @@
static void CreateIntIntIntIntIntToInt(ArenaAllocator* allocator,
DataType::Type type,
HInvoke* invoke) {
- const bool can_call = kEmitCompilerReadBarrier &&
+ const bool can_call = gUseReadBarrier &&
kUseBakerReadBarrier &&
IsUnsafeCASObject(invoke);
LocationSummary* locations =
@@ -2175,7 +2175,7 @@
void IntrinsicLocationsBuilderX86::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -2304,7 +2304,7 @@
DCHECK_EQ(expected, EAX);
DCHECK_NE(temp, temp2);
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Need to make sure the reference stored in the field is a to-space
// one before attempting the CAS or the CAS could fail incorrectly.
codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -2391,7 +2391,7 @@
if (type == DataType::Type::kReference) {
// The only read barrier implementation supporting the
// UnsafeCASObject intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
Register temp = locations->GetTemp(0).AsRegister<Register>();
Register temp2 = locations->GetTemp(1).AsRegister<Register>();
@@ -2413,7 +2413,7 @@
void IntrinsicCodeGeneratorX86::VisitUnsafeCASObject(HInvoke* invoke) {
// The only read barrier implementation supporting the
// UnsafeCASObject intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
GenCAS(DataType::Type::kReference, invoke, codegen_);
}
@@ -2443,7 +2443,7 @@
void IntrinsicCodeGeneratorX86::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
GenCAS(DataType::Type::kReference, invoke, codegen_);
}
@@ -2843,7 +2843,7 @@
void IntrinsicLocationsBuilderX86::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -2875,7 +2875,7 @@
void IntrinsicCodeGeneratorX86::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86Assembler* assembler = GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -2995,7 +2995,7 @@
// slow path.
if (!optimizations.GetSourceIsNonPrimitiveArray()) {
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ temp1 = src->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
invoke, temp1_loc, src, class_offset, /* needs_null_check= */ false);
@@ -3022,7 +3022,7 @@
__ j(kNotEqual, intrinsic_slow_path->GetEntryLabel());
}
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
if (length.Equals(Location::RegisterLocation(temp3))) {
// When Baker read barriers are enabled, register `temp3`,
// which in the present case contains the `length` parameter,
@@ -3120,7 +3120,7 @@
} else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
// Bail out if the source is not a non primitive array.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ temp1 = src->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
invoke, temp1_loc, src, class_offset, /* needs_null_check= */ false);
@@ -3151,7 +3151,7 @@
// Compute the base source address in `temp1`.
GenSystemArrayCopyBaseAddress(GetAssembler(), type, src, src_pos, temp1);
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// If it is needed (in the case of the fast-path loop), the base
// destination address is computed later, as `temp2` is used for
// intermediate computations.
@@ -3377,7 +3377,7 @@
SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathX86(invoke);
codegen_->AddSlowPath(slow_path);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Check self->GetWeakRefAccessEnabled().
ThreadOffset32 offset = Thread::WeakRefAccessEnabledOffset<kX86PointerSize>();
__ fs()->cmpl(Address::Absolute(offset),
@@ -3400,7 +3400,7 @@
// Load the value from the field.
uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
out,
obj.AsRegister<Register>(),
@@ -3442,7 +3442,7 @@
NearLabel end, return_true, return_false;
__ cmpl(out, other);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
DCHECK(kUseBakerReadBarrier);
__ j(kEqual, &return_true);
@@ -3781,7 +3781,7 @@
Location::RegisterLocation(temp),
Address(temp, declaring_class_offset),
/* fixup_label= */ nullptr,
- kCompilerReadBarrierOption);
+ gCompilerReadBarrierOption);
return temp;
}
@@ -3794,7 +3794,7 @@
static void CreateVarHandleGetLocations(HInvoke* invoke) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -3836,7 +3836,7 @@
static void GenerateVarHandleGet(HInvoke* invoke, CodeGeneratorX86* codegen) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -3860,7 +3860,7 @@
Address field_addr(ref, offset, TIMES_1, 0);
// Load the value from the field
- if (type == DataType::Type::kReference && kCompilerReadBarrierOption == kWithReadBarrier) {
+ if (type == DataType::Type::kReference && gCompilerReadBarrierOption == kWithReadBarrier) {
codegen->GenerateReferenceLoadWithBakerReadBarrier(
invoke, out, ref, field_addr, /* needs_null_check= */ false);
} else if (type == DataType::Type::kInt64 &&
@@ -3917,7 +3917,7 @@
static void CreateVarHandleSetLocations(HInvoke* invoke) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -3990,7 +3990,7 @@
static void GenerateVarHandleSet(HInvoke* invoke, CodeGeneratorX86* codegen) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -4087,7 +4087,7 @@
static void CreateVarHandleGetAndSetLocations(HInvoke* invoke) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -4135,7 +4135,7 @@
static void GenerateVarHandleGetAndSet(HInvoke* invoke, CodeGeneratorX86* codegen) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -4194,7 +4194,7 @@
__ movd(locations->Out().AsFpuRegister<XmmRegister>(), EAX);
break;
case DataType::Type::kReference: {
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Need to make sure the reference stored in the field is a to-space
// one before attempting the CAS or the CAS could fail incorrectly.
codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -4258,7 +4258,7 @@
static void CreateVarHandleCompareAndSetOrExchangeLocations(HInvoke* invoke) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -4322,7 +4322,7 @@
static void GenerateVarHandleCompareAndSetOrExchange(HInvoke* invoke, CodeGeneratorX86* codegen) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -4441,7 +4441,7 @@
static void CreateVarHandleGetAndAddLocations(HInvoke* invoke) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -4490,7 +4490,7 @@
static void GenerateVarHandleGetAndAdd(HInvoke* invoke, CodeGeneratorX86* codegen) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -4591,7 +4591,7 @@
static void CreateVarHandleGetAndBitwiseOpLocations(HInvoke* invoke) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -4659,7 +4659,7 @@
static void GenerateVarHandleGetAndBitwiseOp(HInvoke* invoke, CodeGeneratorX86* codegen) {
// The only read barrier implementation supporting the
// VarHandleGet intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
diff --git a/compiler/optimizing/intrinsics_x86_64.cc b/compiler/optimizing/intrinsics_x86_64.cc
index 3c31374..9921d90 100644
--- a/compiler/optimizing/intrinsics_x86_64.cc
+++ b/compiler/optimizing/intrinsics_x86_64.cc
@@ -71,7 +71,7 @@
public:
explicit ReadBarrierSystemArrayCopySlowPathX86_64(HInstruction* instruction)
: SlowPathCode(instruction) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
}
@@ -836,7 +836,7 @@
void IntrinsicLocationsBuilderX86_64::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -887,7 +887,7 @@
void IntrinsicCodeGeneratorX86_64::VisitSystemArrayCopy(HInvoke* invoke) {
// The only read barrier implementation supporting the
// SystemArrayCopy intrinsic is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86_64Assembler* assembler = GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -1002,7 +1002,7 @@
// slow path.
bool did_unpoison = false;
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ temp1 = dest->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
invoke, temp1_loc, dest, class_offset, /* needs_null_check= */ false);
@@ -1034,7 +1034,7 @@
if (!optimizations.GetDestinationIsNonPrimitiveArray()) {
// Bail out if the destination is not a non primitive array.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ TMP = temp1->component_type_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
invoke, TMP_loc, temp1, component_offset, /* needs_null_check= */ false);
@@ -1055,7 +1055,7 @@
if (!optimizations.GetSourceIsNonPrimitiveArray()) {
// Bail out if the source is not a non primitive array.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// For the same reason given earlier, `temp1` is not trashed by the
// read barrier emitted by GenerateFieldLoadWithBakerReadBarrier below.
// /* HeapReference<Class> */ TMP = temp2->component_type_
@@ -1081,7 +1081,7 @@
if (optimizations.GetDestinationIsTypedObjectArray()) {
NearLabel do_copy;
__ j(kEqual, &do_copy);
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ temp1 = temp1->component_type_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
invoke, temp1_loc, temp1, component_offset, /* needs_null_check= */ false);
@@ -1109,7 +1109,7 @@
} else if (!optimizations.GetSourceIsNonPrimitiveArray()) {
DCHECK(optimizations.GetDestinationIsNonPrimitiveArray());
// Bail out if the source is not a non primitive array.
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// /* HeapReference<Class> */ temp1 = src->klass_
codegen_->GenerateFieldLoadWithBakerReadBarrier(
invoke, temp1_loc, src, class_offset, /* needs_null_check= */ false);
@@ -1141,7 +1141,7 @@
GenSystemArrayCopyAddresses(
GetAssembler(), type, src, src_pos, dest, dest_pos, length, temp1, temp2, temp3);
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// SystemArrayCopy implementation for Baker read barriers (see
// also CodeGeneratorX86_64::GenerateReferenceLoadWithBakerReadBarrier):
//
@@ -1888,7 +1888,7 @@
break;
case DataType::Type::kReference: {
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
if (kUseBakerReadBarrier) {
Address src(base, offset, ScaleFactor::TIMES_1, 0);
codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -1930,7 +1930,7 @@
}
static void CreateIntIntIntToIntLocations(ArenaAllocator* allocator, HInvoke* invoke) {
- bool can_call = kEmitCompilerReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
+ bool can_call = gUseReadBarrier && UnsafeGetIntrinsicOnCallList(invoke->GetIntrinsic());
LocationSummary* locations =
new (allocator) LocationSummary(invoke,
can_call
@@ -2230,7 +2230,7 @@
static void CreateUnsafeCASLocations(ArenaAllocator* allocator,
DataType::Type type,
HInvoke* invoke) {
- const bool can_call = kEmitCompilerReadBarrier &&
+ const bool can_call = gUseReadBarrier &&
kUseBakerReadBarrier &&
IsUnsafeCASObject(invoke);
LocationSummary* locations =
@@ -2253,7 +2253,7 @@
// Need two temporaries for MarkGCCard.
locations->AddTemp(Location::RequiresRegister()); // Possibly used for reference poisoning too.
locations->AddTemp(Location::RequiresRegister());
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Need three temporaries for GenerateReferenceLoadWithBakerReadBarrier.
DCHECK(kUseBakerReadBarrier);
locations->AddTemp(Location::RequiresRegister());
@@ -2298,7 +2298,7 @@
void IntrinsicLocationsBuilderX86_64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return;
}
@@ -2438,7 +2438,7 @@
CpuRegister temp3,
bool is_cmpxchg) {
// The only supported read barrier implementation is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86_64Assembler* assembler = down_cast<X86_64Assembler*>(codegen->GetAssembler());
@@ -2447,7 +2447,7 @@
codegen->MarkGCCard(temp1, temp2, base, value, value_can_be_null);
Address field_addr(base, offset, TIMES_1, 0);
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// Need to make sure the reference stored in the field is a to-space
// one before attempting the CAS or the CAS could fail incorrectly.
codegen->GenerateReferenceLoadWithBakerReadBarrier(
@@ -2556,7 +2556,7 @@
CpuRegister new_value_reg = new_value.AsRegister<CpuRegister>();
CpuRegister temp1 = locations->GetTemp(temp1_index).AsRegister<CpuRegister>();
CpuRegister temp2 = locations->GetTemp(temp2_index).AsRegister<CpuRegister>();
- CpuRegister temp3 = kEmitCompilerReadBarrier
+ CpuRegister temp3 = gUseReadBarrier
? locations->GetTemp(temp3_index).AsRegister<CpuRegister>()
: CpuRegister(kNoRegister);
DCHECK(RegsAreAllDifferent({base, offset, temp1, temp2, temp3}));
@@ -2624,7 +2624,7 @@
void IntrinsicCodeGeneratorX86_64::VisitJdkUnsafeCompareAndSetObject(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers.
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
GenCAS(DataType::Type::kReference, invoke, codegen_);
}
@@ -3128,7 +3128,7 @@
SlowPathCode* slow_path = new (GetAllocator()) IntrinsicSlowPathX86_64(invoke);
codegen_->AddSlowPath(slow_path);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Check self->GetWeakRefAccessEnabled().
ThreadOffset64 offset = Thread::WeakRefAccessEnabledOffset<kX86_64PointerSize>();
__ gs()->cmpl(Address::Absolute(offset, /* no_rip= */ true),
@@ -3150,7 +3150,7 @@
// Load the value from the field.
uint32_t referent_offset = mirror::Reference::ReferentOffset().Uint32Value();
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
codegen_->GenerateFieldLoadWithBakerReadBarrier(invoke,
out,
obj.AsRegister<CpuRegister>(),
@@ -3191,7 +3191,7 @@
__ cmpl(out, other);
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
DCHECK(kUseBakerReadBarrier);
NearLabel calculate_result;
@@ -3771,7 +3771,7 @@
Location::RegisterLocation(target.object),
Address(method, ArtField::DeclaringClassOffset()),
/*fixup_label=*/ nullptr,
- kCompilerReadBarrierOption);
+ gCompilerReadBarrierOption);
}
}
} else {
@@ -3790,7 +3790,7 @@
static bool HasVarHandleIntrinsicImplementation(HInvoke* invoke) {
// The only supported read barrier implementation is the Baker-style read barriers.
- if (kEmitCompilerReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
return false;
}
@@ -3876,7 +3876,7 @@
Location out = locations->Out();
if (type == DataType::Type::kReference) {
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
DCHECK(kUseBakerReadBarrier);
codegen->GenerateReferenceLoadWithBakerReadBarrier(
invoke, out, CpuRegister(target.object), src, /* needs_null_check= */ false);
@@ -4070,7 +4070,7 @@
// Need two temporaries for MarkGCCard.
locations->AddTemp(Location::RequiresRegister());
locations->AddTemp(Location::RequiresRegister());
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Need three temporaries for GenerateReferenceLoadWithBakerReadBarrier.
DCHECK(kUseBakerReadBarrier);
locations->AddTemp(Location::RequiresRegister());
@@ -4085,7 +4085,7 @@
CodeGeneratorX86_64* codegen,
bool is_cmpxchg,
bool byte_swap = false) {
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86_64Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
@@ -4218,7 +4218,7 @@
// Need two temporaries for MarkGCCard.
locations->AddTemp(Location::RequiresRegister());
locations->AddTemp(Location::RequiresRegister());
- if (kEmitCompilerReadBarrier) {
+ if (gUseReadBarrier) {
// Need a third temporary for GenerateReferenceLoadWithBakerReadBarrier.
DCHECK(kUseBakerReadBarrier);
locations->AddTemp(Location::RequiresRegister());
@@ -4267,7 +4267,7 @@
CpuRegister temp2 = locations->GetTemp(temp_count - 2).AsRegister<CpuRegister>();
CpuRegister valreg = value.AsRegister<CpuRegister>();
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
codegen->GenerateReferenceLoadWithBakerReadBarrier(
invoke,
locations->GetTemp(temp_count - 3),
@@ -4647,7 +4647,7 @@
bool need_any_store_barrier,
bool need_any_any_barrier,
bool byte_swap = false) {
- DCHECK_IMPLIES(kEmitCompilerReadBarrier, kUseBakerReadBarrier);
+ DCHECK_IMPLIES(gUseReadBarrier, kUseBakerReadBarrier);
X86_64Assembler* assembler = codegen->GetAssembler();
LocationSummary* locations = invoke->GetLocations();
diff --git a/compiler/optimizing/load_store_elimination.cc b/compiler/optimizing/load_store_elimination.cc
index 9b8f07e..89d9fca 100644
--- a/compiler/optimizing/load_store_elimination.cc
+++ b/compiler/optimizing/load_store_elimination.cc
@@ -1137,6 +1137,14 @@
}
}
+ void VisitLoadMethodHandle(HLoadMethodHandle* load_method_handle) override {
+ HandleThrowingInstruction(load_method_handle);
+ }
+
+ void VisitLoadMethodType(HLoadMethodType* load_method_type) override {
+ HandleThrowingInstruction(load_method_type);
+ }
+
void VisitStringBuilderAppend(HStringBuilderAppend* sb_append) override {
HandleThrowingInstruction(sb_append);
}
@@ -1764,7 +1772,6 @@
if (type == DataType::Type::kReference) {
// Update reference type information. Pass invalid handles, these are not used for Phis.
ReferenceTypePropagation rtp_fixup(block->GetGraph(),
- Handle<mirror::ClassLoader>(),
Handle<mirror::DexCache>(),
/* is_first_run= */ false);
rtp_fixup.Visit(phi);
@@ -2352,7 +2359,6 @@
}
// Update reference type information. Pass invalid handles, these are not used for Phis.
ReferenceTypePropagation rtp_fixup(GetGraph(),
- Handle<mirror::ClassLoader>(),
Handle<mirror::DexCache>(),
/* is_first_run= */ false);
rtp_fixup.Visit(ArrayRef<HInstruction* const>(phis));
@@ -3013,7 +3019,6 @@
return;
}
ReferenceTypePropagation rtp_fixup(GetGraph(),
- Handle<mirror::ClassLoader>(),
Handle<mirror::DexCache>(),
/* is_first_run= */ false);
rtp_fixup.Visit(ArrayRef<HInstruction* const>(new_ref_phis_));
diff --git a/compiler/optimizing/loop_optimization.cc b/compiler/optimizing/loop_optimization.cc
index 2d7c208..604d3d2 100644
--- a/compiler/optimizing/loop_optimization.cc
+++ b/compiler/optimizing/loop_optimization.cc
@@ -681,6 +681,50 @@
}
//
+// This optimization applies to loops with plain simple operations
+// (I.e. no calls to java code or runtime) with a known small trip_count * instr_count
+// value.
+//
+bool HLoopOptimization::TryToRemoveSuspendCheckFromLoopHeader(LoopAnalysisInfo* analysis_info,
+ bool generate_code) {
+ if (!graph_->SuspendChecksAreAllowedToNoOp()) {
+ return false;
+ }
+
+ int64_t trip_count = analysis_info->GetTripCount();
+
+ if (trip_count == LoopAnalysisInfo::kUnknownTripCount) {
+ return false;
+ }
+
+ int64_t instruction_count = analysis_info->GetNumberOfInstructions();
+ int64_t total_instruction_count = trip_count * instruction_count;
+
+ // The inclusion of the HasInstructionsPreventingScalarOpts() prevents this
+ // optimization from being applied to loops that have calls.
+ bool can_optimize =
+ total_instruction_count <= HLoopOptimization::kMaxTotalInstRemoveSuspendCheck &&
+ !analysis_info->HasInstructionsPreventingScalarOpts();
+
+ if (!can_optimize) {
+ return false;
+ }
+
+ // If we should do the optimization, disable codegen for the SuspendCheck.
+ if (generate_code) {
+ HLoopInformation* loop_info = analysis_info->GetLoopInfo();
+ HBasicBlock* header = loop_info->GetHeader();
+ HSuspendCheck* instruction = header->GetLoopInformation()->GetSuspendCheck();
+ // As other optimizations depend on SuspendCheck
+ // (e.g: CHAGuardVisitor::HoistGuard), disable its codegen instead of
+ // removing the SuspendCheck instruction.
+ instruction->SetIsNoOp(true);
+ }
+
+ return true;
+}
+
+//
// Optimization.
//
@@ -824,7 +868,7 @@
}
bool HLoopOptimization::OptimizeInnerLoop(LoopNode* node) {
- return TryOptimizeInnerLoopFinite(node) || TryPeelingAndUnrolling(node);
+ return TryOptimizeInnerLoopFinite(node) || TryLoopScalarOpts(node);
}
//
@@ -928,7 +972,7 @@
return true;
}
-bool HLoopOptimization::TryPeelingAndUnrolling(LoopNode* node) {
+bool HLoopOptimization::TryLoopScalarOpts(LoopNode* node) {
HLoopInformation* loop_info = node->loop_info;
int64_t trip_count = LoopAnalysis::GetLoopTripCount(loop_info, &induction_range_);
LoopAnalysisInfo analysis_info(loop_info);
@@ -941,10 +985,16 @@
if (!TryFullUnrolling(&analysis_info, /*generate_code*/ false) &&
!TryPeelingForLoopInvariantExitsElimination(&analysis_info, /*generate_code*/ false) &&
- !TryUnrollingForBranchPenaltyReduction(&analysis_info, /*generate_code*/ false)) {
+ !TryUnrollingForBranchPenaltyReduction(&analysis_info, /*generate_code*/ false) &&
+ !TryToRemoveSuspendCheckFromLoopHeader(&analysis_info, /*generate_code*/ false)) {
return false;
}
+ // Try the suspend check removal even for non-clonable loops. Also this
+ // optimization doesn't interfere with other scalar loop optimizations so it can
+ // be done prior to them.
+ bool removed_suspend_check = TryToRemoveSuspendCheckFromLoopHeader(&analysis_info);
+
// Run 'IsLoopClonable' the last as it might be time-consuming.
if (!LoopClonerHelper::IsLoopClonable(loop_info)) {
return false;
@@ -952,7 +1002,7 @@
return TryFullUnrolling(&analysis_info) ||
TryPeelingForLoopInvariantExitsElimination(&analysis_info) ||
- TryUnrollingForBranchPenaltyReduction(&analysis_info);
+ TryUnrollingForBranchPenaltyReduction(&analysis_info) || removed_suspend_check;
}
//
diff --git a/compiler/optimizing/loop_optimization.h b/compiler/optimizing/loop_optimization.h
index b178616..0535c74 100644
--- a/compiler/optimizing/loop_optimization.h
+++ b/compiler/optimizing/loop_optimization.h
@@ -47,6 +47,11 @@
static constexpr const char* kLoopOptimizationPassName = "loop_optimization";
+ // The maximum number of total instructions (trip_count * instruction_count),
+ // where the optimization of removing SuspendChecks from the loop header could
+ // be performed.
+ static constexpr int64_t kMaxTotalInstRemoveSuspendCheck = 128;
+
private:
/**
* A single loop inside the loop hierarchy representation.
@@ -179,8 +184,19 @@
// should be actually applied.
bool TryFullUnrolling(LoopAnalysisInfo* analysis_info, bool generate_code = true);
- // Tries to apply scalar loop peeling and unrolling.
- bool TryPeelingAndUnrolling(LoopNode* node);
+ // Tries to remove SuspendCheck for plain loops with a low trip count. The
+ // SuspendCheck in the codegen makes sure that the thread can be interrupted
+ // during execution for GC. Not being able to do so might decrease the
+ // responsiveness of GC when a very long loop or a long recursion is being
+ // executed. However, for plain loops with a small trip count, the removal of
+ // SuspendCheck should not affect the GC's responsiveness by a large margin.
+ // Consequently, since the thread won't be interrupted for plain loops, it is
+ // assumed that the performance might increase by removing SuspendCheck.
+ bool TryToRemoveSuspendCheckFromLoopHeader(LoopAnalysisInfo* analysis_info,
+ bool generate_code = true);
+
+ // Tries to apply scalar loop optimizations.
+ bool TryLoopScalarOpts(LoopNode* node);
//
// Vectorization analysis and synthesis.
diff --git a/compiler/optimizing/nodes.cc b/compiler/optimizing/nodes.cc
index d35ed1c..19731ae 100644
--- a/compiler/optimizing/nodes.cc
+++ b/compiler/optimizing/nodes.cc
@@ -150,30 +150,54 @@
RemoveEnvironmentUses(instruction);
}
-void HGraph::RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visited) const {
+void HGraph::RemoveDeadBlocksInstructionsAsUsersAndDisconnect(const ArenaBitVector& visited) const {
for (size_t i = 0; i < blocks_.size(); ++i) {
if (!visited.IsBitSet(i)) {
HBasicBlock* block = blocks_[i];
if (block == nullptr) continue;
+
+ // Remove as user.
for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
RemoveAsUser(it.Current());
}
for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
RemoveAsUser(it.Current());
}
+
+ // Remove non-catch phi uses, and disconnect the block.
+ block->DisconnectFromSuccessors(&visited);
+ }
+ }
+}
+
+// This method assumes `insn` has been removed from all users with the exception of catch
+// phis because of missing exceptional edges in the graph. It removes the
+// instruction from catch phi uses, together with inputs of other catch phis in
+// the catch block at the same index, as these must be dead too.
+static void RemoveCatchPhiUsesOfDeadInstruction(HInstruction* insn) {
+ DCHECK(!insn->HasEnvironmentUses());
+ while (insn->HasNonEnvironmentUses()) {
+ const HUseListNode<HInstruction*>& use = insn->GetUses().front();
+ size_t use_index = use.GetIndex();
+ HBasicBlock* user_block = use.GetUser()->GetBlock();
+ DCHECK(use.GetUser()->IsPhi());
+ DCHECK(user_block->IsCatchBlock());
+ for (HInstructionIterator phi_it(user_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
+ phi_it.Current()->AsPhi()->RemoveInputAt(use_index);
}
}
}
void HGraph::RemoveDeadBlocks(const ArenaBitVector& visited) {
+ DCHECK(reverse_post_order_.empty()) << "We shouldn't have dominance information.";
for (size_t i = 0; i < blocks_.size(); ++i) {
if (!visited.IsBitSet(i)) {
HBasicBlock* block = blocks_[i];
if (block == nullptr) continue;
- // We only need to update the successor, which might be live.
- for (HBasicBlock* successor : block->GetSuccessors()) {
- successor->RemovePredecessor(block);
- }
+
+ // Remove all remaining uses (which should be only catch phi uses), and the instructions.
+ block->RemoveCatchPhiUsesAndInstruction(/* building_dominator_tree = */ true);
+
// Remove the block from the list of blocks, so that further analyses
// never see it.
blocks_[i] = nullptr;
@@ -200,7 +224,8 @@
// (2) Remove instructions and phis from blocks not visited during
// the initial DFS as users from other instructions, so that
// users can be safely removed before uses later.
- RemoveInstructionsAsUsersFromDeadBlocks(visited);
+ // Also disconnect the block from its successors, updating the successor's phis if needed.
+ RemoveDeadBlocksInstructionsAsUsersAndDisconnect(visited);
// (3) Remove blocks not visited during the initial DFS.
// Step (5) requires dead blocks to be removed from the
@@ -1459,6 +1484,10 @@
UNREACHABLE();
}
+bool HInstruction::Dominates(HInstruction* other_instruction) const {
+ return other_instruction == this || StrictlyDominates(other_instruction);
+}
+
bool HInstruction::StrictlyDominates(HInstruction* other_instruction) const {
if (other_instruction == this) {
// An instruction does not strictly dominate itself.
@@ -1518,14 +1547,19 @@
DCHECK(env_uses_.empty());
}
-void HInstruction::ReplaceUsesDominatedBy(HInstruction* dominator, HInstruction* replacement) {
+void HInstruction::ReplaceUsesDominatedBy(HInstruction* dominator,
+ HInstruction* replacement,
+ bool strictly_dominated) {
const HUseList<HInstruction*>& uses = GetUses();
for (auto it = uses.begin(), end = uses.end(); it != end; /* ++it below */) {
HInstruction* user = it->GetUser();
size_t index = it->GetIndex();
// Increment `it` now because `*it` may disappear thanks to user->ReplaceInput().
++it;
- if (dominator->StrictlyDominates(user)) {
+ const bool dominated =
+ strictly_dominated ? dominator->StrictlyDominates(user) : dominator->Dominates(user);
+
+ if (dominated) {
user->ReplaceInput(replacement, index);
} else if (user->IsPhi() && !user->AsPhi()->IsCatchPhi()) {
// If the input flows from a block dominated by `dominator`, we can replace it.
@@ -2108,8 +2142,9 @@
MoveBefore(insert_pos);
}
-HBasicBlock* HBasicBlock::SplitBefore(HInstruction* cursor) {
- DCHECK(!graph_->IsInSsaForm()) << "Support for SSA form not implemented.";
+HBasicBlock* HBasicBlock::SplitBefore(HInstruction* cursor, bool require_graph_not_in_ssa_form) {
+ DCHECK_IMPLIES(require_graph_not_in_ssa_form, !graph_->IsInSsaForm())
+ << "Support for SSA form not implemented.";
DCHECK_EQ(cursor->GetBlock(), this);
HBasicBlock* new_block =
@@ -2376,24 +2411,6 @@
}
}
-// Should be called on instructions in a dead block in post order. This method
-// assumes `insn` has been removed from all users with the exception of catch
-// phis because of missing exceptional edges in the graph. It removes the
-// instruction from catch phi uses, together with inputs of other catch phis in
-// the catch block at the same index, as these must be dead too.
-static void RemoveUsesOfDeadInstruction(HInstruction* insn) {
- DCHECK(!insn->HasEnvironmentUses());
- while (insn->HasNonEnvironmentUses()) {
- const HUseListNode<HInstruction*>& use = insn->GetUses().front();
- size_t use_index = use.GetIndex();
- HBasicBlock* user_block = use.GetUser()->GetBlock();
- DCHECK(use.GetUser()->IsPhi() && user_block->IsCatchBlock());
- for (HInstructionIterator phi_it(user_block->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
- phi_it.Current()->AsPhi()->RemoveInputAt(use_index);
- }
- }
-}
-
void HBasicBlock::DisconnectAndDelete() {
// Dominators must be removed after all the blocks they dominate. This way
// a loop header is removed last, a requirement for correct loop information
@@ -2418,52 +2435,14 @@
}
// (2) Disconnect the block from its successors and update their phis.
- for (HBasicBlock* successor : successors_) {
- // Delete this block from the list of predecessors.
- size_t this_index = successor->GetPredecessorIndexOf(this);
- successor->predecessors_.erase(successor->predecessors_.begin() + this_index);
-
- // Check that `successor` has other predecessors, otherwise `this` is the
- // dominator of `successor` which violates the order DCHECKed at the top.
- DCHECK(!successor->predecessors_.empty());
-
- // Remove this block's entries in the successor's phis. Skip exceptional
- // successors because catch phi inputs do not correspond to predecessor
- // blocks but throwing instructions. The inputs of the catch phis will be
- // updated in step (3).
- if (!successor->IsCatchBlock()) {
- if (successor->predecessors_.size() == 1u) {
- // The successor has just one predecessor left. Replace phis with the only
- // remaining input.
- for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
- HPhi* phi = phi_it.Current()->AsPhi();
- phi->ReplaceWith(phi->InputAt(1 - this_index));
- successor->RemovePhi(phi);
- }
- } else {
- for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
- phi_it.Current()->AsPhi()->RemoveInputAt(this_index);
- }
- }
- }
- }
- successors_.clear();
+ DisconnectFromSuccessors();
// (3) Remove instructions and phis. Instructions should have no remaining uses
// except in catch phis. If an instruction is used by a catch phi at `index`,
// remove `index`-th input of all phis in the catch block since they are
// guaranteed dead. Note that we may miss dead inputs this way but the
// graph will always remain consistent.
- for (HBackwardInstructionIterator it(GetInstructions()); !it.Done(); it.Advance()) {
- HInstruction* insn = it.Current();
- RemoveUsesOfDeadInstruction(insn);
- RemoveInstruction(insn);
- }
- for (HInstructionIterator it(GetPhis()); !it.Done(); it.Advance()) {
- HPhi* insn = it.Current()->AsPhi();
- RemoveUsesOfDeadInstruction(insn);
- RemovePhi(insn);
- }
+ RemoveCatchPhiUsesAndInstruction(/* building_dominator_tree = */ false);
// (4) Disconnect the block from its predecessors and update their
// control-flow instructions.
@@ -2537,6 +2516,70 @@
SetGraph(nullptr);
}
+void HBasicBlock::DisconnectFromSuccessors(const ArenaBitVector* visited) {
+ for (HBasicBlock* successor : successors_) {
+ // Delete this block from the list of predecessors.
+ size_t this_index = successor->GetPredecessorIndexOf(this);
+ successor->predecessors_.erase(successor->predecessors_.begin() + this_index);
+
+ if (visited != nullptr && !visited->IsBitSet(successor->GetBlockId())) {
+ // `successor` itself is dead. Therefore, there is no need to update its phis.
+ continue;
+ }
+
+ DCHECK(!successor->predecessors_.empty());
+
+ // Remove this block's entries in the successor's phis. Skips exceptional
+ // successors because catch phi inputs do not correspond to predecessor
+ // blocks but throwing instructions. They are removed in `RemoveCatchPhiUses`.
+ if (!successor->IsCatchBlock()) {
+ if (successor->predecessors_.size() == 1u) {
+ // The successor has just one predecessor left. Replace phis with the only
+ // remaining input.
+ for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
+ HPhi* phi = phi_it.Current()->AsPhi();
+ phi->ReplaceWith(phi->InputAt(1 - this_index));
+ successor->RemovePhi(phi);
+ }
+ } else {
+ for (HInstructionIterator phi_it(successor->GetPhis()); !phi_it.Done(); phi_it.Advance()) {
+ phi_it.Current()->AsPhi()->RemoveInputAt(this_index);
+ }
+ }
+ }
+ }
+ successors_.clear();
+}
+
+void HBasicBlock::RemoveCatchPhiUsesAndInstruction(bool building_dominator_tree) {
+ for (HBackwardInstructionIterator it(GetInstructions()); !it.Done(); it.Advance()) {
+ HInstruction* insn = it.Current();
+ RemoveCatchPhiUsesOfDeadInstruction(insn);
+
+ // If we are building the dominator tree, we removed all input records previously.
+ // `RemoveInstruction` will try to remove them again but that's not something we support and we
+ // will crash. We check here since we won't be checking that in RemoveInstruction.
+ if (building_dominator_tree) {
+ DCHECK(insn->GetUses().empty());
+ DCHECK(insn->GetEnvUses().empty());
+ }
+ RemoveInstruction(insn, /* ensure_safety= */ !building_dominator_tree);
+ }
+ for (HInstructionIterator it(GetPhis()); !it.Done(); it.Advance()) {
+ HPhi* insn = it.Current()->AsPhi();
+ RemoveCatchPhiUsesOfDeadInstruction(insn);
+
+ // If we are building the dominator tree, we removed all input records previously.
+ // `RemovePhi` will try to remove them again but that's not something we support and we
+ // will crash. We check here since we won't be checking that in RemovePhi.
+ if (building_dominator_tree) {
+ DCHECK(insn->GetUses().empty());
+ DCHECK(insn->GetEnvUses().empty());
+ }
+ RemovePhi(insn, /* ensure_safety= */ !building_dominator_tree);
+ }
+}
+
void HBasicBlock::MergeInstructionsWith(HBasicBlock* other) {
DCHECK(EndsWithControlFlowInstruction());
RemoveInstruction(GetLastInstruction());
@@ -2733,6 +2776,9 @@
if (HasSIMD()) {
outer_graph->SetHasSIMD(true);
}
+ if (HasAlwaysThrowingInvokes()) {
+ outer_graph->SetHasAlwaysThrowingInvokes(true);
+ }
HInstruction* return_value = nullptr;
if (GetBlocks().size() == 3) {
@@ -3047,6 +3093,7 @@
HSuspendCheck* suspend_check = new (allocator_) HSuspendCheck(header->GetDexPc());
new_header->AddInstruction(suspend_check);
new_body->AddInstruction(new (allocator_) HGoto());
+ DCHECK(loop->GetSuspendCheck() != nullptr);
suspend_check->CopyEnvironmentFromWithLoopPhiAdjustment(
loop->GetSuspendCheck()->GetEnvironment(), header);
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h
index 7a0059f..8db8c02 100644
--- a/compiler/optimizing/nodes.h
+++ b/compiler/optimizing/nodes.h
@@ -406,6 +406,7 @@
has_loops_(false),
has_irreducible_loops_(false),
has_direct_critical_native_call_(false),
+ has_always_throwing_invokes_(false),
dead_reference_safe_(dead_reference_safe),
debuggable_(debuggable),
current_instruction_id_(start_instruction_id),
@@ -678,6 +679,13 @@
return cha_single_implementation_list_;
}
+ // In case of OSR we intend to use SuspendChecks as an entry point to the
+ // function; for debuggable graphs we might deoptimize to interpreter from
+ // SuspendChecks. In these cases we should always generate code for them.
+ bool SuspendChecksAreAllowedToNoOp() const {
+ return !IsDebuggable() && !IsCompilingOsr();
+ }
+
void AddCHASingleImplementationDependency(ArtMethod* method) {
cha_single_implementation_list_.insert(method);
}
@@ -704,6 +712,9 @@
bool HasDirectCriticalNativeCall() const { return has_direct_critical_native_call_; }
void SetHasDirectCriticalNativeCall(bool value) { has_direct_critical_native_call_ = value; }
+ bool HasAlwaysThrowingInvokes() const { return has_always_throwing_invokes_; }
+ void SetHasAlwaysThrowingInvokes(bool value) { has_always_throwing_invokes_ = value; }
+
ArtMethod* GetArtMethod() const { return art_method_; }
void SetArtMethod(ArtMethod* method) { art_method_ = method; }
@@ -719,12 +730,12 @@
return ReferenceTypeInfo::Create(handle_cache_.GetObjectClassHandle(), /* is_exact= */ false);
}
- uint32_t GetNumberOfCHAGuards() { return number_of_cha_guards_; }
+ uint32_t GetNumberOfCHAGuards() const { return number_of_cha_guards_; }
void SetNumberOfCHAGuards(uint32_t num) { number_of_cha_guards_ = num; }
void IncrementNumberOfCHAGuards() { number_of_cha_guards_++; }
private:
- void RemoveInstructionsAsUsersFromDeadBlocks(const ArenaBitVector& visited) const;
+ void RemoveDeadBlocksInstructionsAsUsersAndDisconnect(const ArenaBitVector& visited) const;
void RemoveDeadBlocks(const ArenaBitVector& visited);
template <class InstructionType, typename ValueType>
@@ -826,6 +837,9 @@
// for @CriticalNative methods.
bool has_direct_critical_native_call_;
+ // Flag whether the graph contains invokes that always throw.
+ bool has_always_throwing_invokes_;
+
// Is the code known to be robust against eliminating dead references
// and the effects of early finalization? If false, dead reference variables
// are kept if they might be visible to the garbage collector.
@@ -1291,7 +1305,7 @@
// graph, create a Goto at the end of the former block and will create an edge
// between the blocks. It will not, however, update the reverse post order or
// loop and try/catch information.
- HBasicBlock* SplitBefore(HInstruction* cursor);
+ HBasicBlock* SplitBefore(HInstruction* cursor, bool require_graph_not_in_ssa_form = true);
// Split the block into two blocks just before `cursor`. Returns the newly
// created block. Note that this method just updates raw block information,
@@ -1332,6 +1346,20 @@
// are safely updated.
void DisconnectAndDelete();
+ // Disconnects `this` from all its successors and updates their phis, if the successors have them.
+ // If `visited` is provided, it will use the information to know if a successor is reachable and
+ // skip updating those phis.
+ void DisconnectFromSuccessors(const ArenaBitVector* visited = nullptr);
+
+ // Removes the catch phi uses of the instructions in `this`, and then remove the instruction
+ // itself. If `building_dominator_tree` is true, it will not remove the instruction as user, since
+ // we do it in a previous step. This is a special case for building up the dominator tree: we want
+ // to eliminate uses before inputs but we don't have domination information, so we remove all
+ // connections from input/uses first before removing any instruction.
+ // This method assumes the instructions have been removed from all users with the exception of
+ // catch phis because of missing exceptional edges in the graph.
+ void RemoveCatchPhiUsesAndInstruction(bool building_dominator_tree);
+
void AddInstruction(HInstruction* instruction);
// Insert `instruction` before/after an existing instruction `cursor`.
void InsertInstructionBefore(HInstruction* instruction, HInstruction* cursor);
@@ -1540,10 +1568,10 @@
M(Min, BinaryOperation) \
M(MonitorOperation, Instruction) \
M(Mul, BinaryOperation) \
- M(NativeDebugInfo, Instruction) \
M(Neg, UnaryOperation) \
M(NewArray, Instruction) \
M(NewInstance, Instruction) \
+ M(Nop, Instruction) \
M(Not, UnaryOperation) \
M(NotEqual, Condition) \
M(NullConstant, Instruction) \
@@ -2408,7 +2436,7 @@
!CanThrow() &&
!IsSuspendCheck() &&
!IsControlFlow() &&
- !IsNativeDebugInfo() &&
+ !IsNop() &&
!IsParameterValue() &&
// If we added an explicit barrier then we should keep it.
!IsMemoryBarrier() &&
@@ -2419,9 +2447,12 @@
return IsRemovable() && !HasUses();
}
- // Does this instruction strictly dominate `other_instruction`?
- // Returns false if this instruction and `other_instruction` are the same.
- // Aborts if this instruction and `other_instruction` are both phis.
+ // Does this instruction dominate `other_instruction`?
+ // Aborts if this instruction and `other_instruction` are different phis.
+ bool Dominates(HInstruction* other_instruction) const;
+
+ // Same but with `strictly dominates` i.e. returns false if this instruction and
+ // `other_instruction` are the same.
bool StrictlyDominates(HInstruction* other_instruction) const;
int GetId() const { return id_; }
@@ -2486,7 +2517,9 @@
void SetLocations(LocationSummary* locations) { locations_ = locations; }
void ReplaceWith(HInstruction* instruction);
- void ReplaceUsesDominatedBy(HInstruction* dominator, HInstruction* replacement);
+ void ReplaceUsesDominatedBy(HInstruction* dominator,
+ HInstruction* replacement,
+ bool strictly_dominated = true);
void ReplaceEnvUsesDominatedBy(HInstruction* dominator, HInstruction* replacement);
void ReplaceInput(HInstruction* replacement, size_t index);
@@ -6714,9 +6747,10 @@
class HSuspendCheck final : public HExpression<0> {
public:
- explicit HSuspendCheck(uint32_t dex_pc = kNoDexPc)
+ explicit HSuspendCheck(uint32_t dex_pc = kNoDexPc, bool is_no_op = false)
: HExpression(kSuspendCheck, SideEffects::CanTriggerGC(), dex_pc),
slow_path_(nullptr) {
+ SetPackedFlag<kFlagIsNoOp>(is_no_op);
}
bool IsClonable() const override { return true; }
@@ -6725,6 +6759,10 @@
return true;
}
+ void SetIsNoOp(bool is_no_op) { SetPackedFlag<kFlagIsNoOp>(is_no_op); }
+ bool IsNoOp() const { return GetPackedFlag<kFlagIsNoOp>(); }
+
+
void SetSlowPath(SlowPathCode* slow_path) { slow_path_ = slow_path; }
SlowPathCode* GetSlowPath() const { return slow_path_; }
@@ -6733,28 +6771,42 @@
protected:
DEFAULT_COPY_CONSTRUCTOR(SuspendCheck);
+ // True if the HSuspendCheck should not emit any code during codegen. It is
+ // not possible to simply remove this instruction to disable codegen, as
+ // other optimizations (e.g: CHAGuardVisitor::HoistGuard) depend on
+ // HSuspendCheck being present in every loop.
+ static constexpr size_t kFlagIsNoOp = kNumberOfGenericPackedBits;
+ static constexpr size_t kNumberOfSuspendCheckPackedBits = kFlagIsNoOp + 1;
+ static_assert(kNumberOfSuspendCheckPackedBits <= HInstruction::kMaxNumberOfPackedBits,
+ "Too many packed fields.");
+
private:
// Only used for code generation, in order to share the same slow path between back edges
// of a same loop.
SlowPathCode* slow_path_;
};
-// Pseudo-instruction which provides the native debugger with mapping information.
-// It ensures that we can generate line number and local variables at this point.
-class HNativeDebugInfo : public HExpression<0> {
+// Pseudo-instruction which doesn't generate any code.
+// If `emit_environment` is true, it can be used to generate an environment. It is used, for
+// example, to provide the native debugger with mapping information. It ensures that we can generate
+// line number and local variables at this point.
+class HNop : public HExpression<0> {
public:
- explicit HNativeDebugInfo(uint32_t dex_pc)
- : HExpression<0>(kNativeDebugInfo, SideEffects::None(), dex_pc) {
+ explicit HNop(uint32_t dex_pc, bool needs_environment)
+ : HExpression<0>(kNop, SideEffects::None(), dex_pc), needs_environment_(needs_environment) {
}
bool NeedsEnvironment() const override {
- return true;
+ return needs_environment_;
}
- DECLARE_INSTRUCTION(NativeDebugInfo);
+ DECLARE_INSTRUCTION(Nop);
protected:
- DEFAULT_COPY_CONSTRUCTOR(NativeDebugInfo);
+ DEFAULT_COPY_CONSTRUCTOR(Nop);
+
+ private:
+ bool needs_environment_;
};
/**
@@ -7222,6 +7274,10 @@
return SideEffects::CanTriggerGC();
}
+ bool CanThrow() const override { return true; }
+
+ bool NeedsEnvironment() const override { return true; }
+
DECLARE_INSTRUCTION(LoadMethodHandle);
protected:
@@ -7266,6 +7322,10 @@
return SideEffects::CanTriggerGC();
}
+ bool CanThrow() const override { return true; }
+
+ bool NeedsEnvironment() const override { return true; }
+
DECLARE_INSTRUCTION(LoadMethodType);
protected:
diff --git a/compiler/optimizing/optimization.cc b/compiler/optimizing/optimization.cc
index 2cac38b..7bf6dbb 100644
--- a/compiler/optimizing/optimization.cc
+++ b/compiler/optimizing/optimization.cc
@@ -221,7 +221,7 @@
// Regular passes.
//
case OptimizationPass::kConstantFolding:
- opt = new (allocator) HConstantFolding(graph, pass_name);
+ opt = new (allocator) HConstantFolding(graph, stats, pass_name);
break;
case OptimizationPass::kDeadCodeElimination:
opt = new (allocator) HDeadCodeElimination(graph, stats, pass_name);
diff --git a/compiler/optimizing/optimizing_cfi_test.cc b/compiler/optimizing/optimizing_cfi_test.cc
index bad540e..73e1fbe 100644
--- a/compiler/optimizing/optimizing_cfi_test.cc
+++ b/compiler/optimizing/optimizing_cfi_test.cc
@@ -167,9 +167,20 @@
// barrier configuration, and as such is removed from the set of
// callee-save registers in the ARM64 code generator of the Optimizing
// compiler.
-#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
-TEST_ISA(kArm64)
-#endif
+//
+// We can't use compile-time macros for read-barrier as the introduction
+// of userfaultfd-GC has made it a runtime choice.
+TEST_F(OptimizingCFITest, kArm64) {
+ if (kUseBakerReadBarrier && gUseReadBarrier) {
+ std::vector<uint8_t> expected_asm(
+ expected_asm_kArm64,
+ expected_asm_kArm64 + arraysize(expected_asm_kArm64));
+ std::vector<uint8_t> expected_cfi(
+ expected_cfi_kArm64,
+ expected_cfi_kArm64 + arraysize(expected_cfi_kArm64));
+ TestImpl(InstructionSet::kArm64, "kArm64", expected_asm, expected_cfi);
+ }
+}
#endif
#ifdef ART_ENABLE_CODEGEN_x86
diff --git a/compiler/optimizing/optimizing_compiler.cc b/compiler/optimizing/optimizing_compiler.cc
index 6eb3d01..97e6a7a 100644
--- a/compiler/optimizing/optimizing_compiler.cc
+++ b/compiler/optimizing/optimizing_compiler.cc
@@ -359,7 +359,6 @@
const DexCompilationUnit& dex_compilation_unit,
PassObserver* pass_observer) const;
- private:
// Create a 'CompiledMethod' for an optimized graph.
CompiledMethod* Emit(ArenaAllocator* allocator,
CodeVectorAllocator* code_allocator,
@@ -1115,17 +1114,18 @@
static ScopedArenaVector<uint8_t> CreateJniStackMap(ScopedArenaAllocator* allocator,
const JniCompiledMethod& jni_compiled_method,
- size_t code_size) {
+ size_t code_size,
+ bool debuggable) {
// StackMapStream is quite large, so allocate it using the ScopedArenaAllocator
// to stay clear of the frame size limit.
std::unique_ptr<StackMapStream> stack_map_stream(
new (allocator) StackMapStream(allocator, jni_compiled_method.GetInstructionSet()));
- stack_map_stream->BeginMethod(
- jni_compiled_method.GetFrameSize(),
- jni_compiled_method.GetCoreSpillMask(),
- jni_compiled_method.GetFpSpillMask(),
- /* num_dex_registers= */ 0,
- /* baseline= */ false);
+ stack_map_stream->BeginMethod(jni_compiled_method.GetFrameSize(),
+ jni_compiled_method.GetCoreSpillMask(),
+ jni_compiled_method.GetFpSpillMask(),
+ /* num_dex_registers= */ 0,
+ /* baseline= */ false,
+ debuggable);
stack_map_stream->EndMethod(code_size);
return stack_map_stream->Encode();
}
@@ -1187,8 +1187,11 @@
MaybeRecordStat(compilation_stats_.get(), MethodCompilationStat::kCompiledNativeStub);
ScopedArenaAllocator stack_map_allocator(&arena_stack); // Will hold the stack map.
- ScopedArenaVector<uint8_t> stack_map = CreateJniStackMap(
- &stack_map_allocator, jni_compiled_method, jni_compiled_method.GetCode().size());
+ ScopedArenaVector<uint8_t> stack_map =
+ CreateJniStackMap(&stack_map_allocator,
+ jni_compiled_method,
+ jni_compiled_method.GetCode().size(),
+ compiler_options.GetDebuggable() && compiler_options.IsJitCompiler());
return CompiledMethod::SwapAllocCompiledMethod(
GetCompiledMethodStorage(),
jni_compiled_method.GetInstructionSet(),
@@ -1233,6 +1236,14 @@
ArenaAllocator allocator(runtime->GetJitArenaPool());
if (UNLIKELY(method->IsNative())) {
+ // Use GenericJniTrampoline for critical native methods in debuggable runtimes. We don't
+ // support calling method entry / exit hooks for critical native methods yet.
+ // TODO(mythria): Add support for calling method entry / exit hooks in JITed stubs for critical
+ // native methods too.
+ if (runtime->IsJavaDebuggable() && method->IsCriticalNative()) {
+ DCHECK(compiler_options.IsJitCompiler());
+ return false;
+ }
JniCompiledMethod jni_compiled_method = ArtQuickJniCompileMethod(
compiler_options, access_flags, method_idx, *dex_file, &allocator);
std::vector<Handle<mirror::Object>> roots;
@@ -1241,8 +1252,11 @@
ArenaStack arena_stack(runtime->GetJitArenaPool());
// StackMapStream is large and it does not fit into this frame, so we need helper method.
ScopedArenaAllocator stack_map_allocator(&arena_stack); // Will hold the stack map.
- ScopedArenaVector<uint8_t> stack_map = CreateJniStackMap(
- &stack_map_allocator, jni_compiled_method, jni_compiled_method.GetCode().size());
+ ScopedArenaVector<uint8_t> stack_map =
+ CreateJniStackMap(&stack_map_allocator,
+ jni_compiled_method,
+ jni_compiled_method.GetCode().size(),
+ compiler_options.GetDebuggable() && compiler_options.IsJitCompiler());
ArrayRef<const uint8_t> reserved_code;
ArrayRef<const uint8_t> reserved_data;
diff --git a/compiler/optimizing/optimizing_compiler_stats.h b/compiler/optimizing/optimizing_compiler_stats.h
index d458e42..5e316ba 100644
--- a/compiler/optimizing/optimizing_compiler_stats.h
+++ b/compiler/optimizing/optimizing_compiler_stats.h
@@ -73,6 +73,7 @@
kLoopVectorizedIdiom,
kSelectGenerated,
kRemovedInstanceOf,
+ kPropagatedIfValue,
kInlinedInvokeVirtualOrInterface,
kInlinedLastInvokeVirtualOrInterface,
kImplicitNullCheckGenerated,
diff --git a/compiler/optimizing/optimizing_unit_test.h b/compiler/optimizing/optimizing_unit_test.h
index e836880..df21183 100644
--- a/compiler/optimizing/optimizing_unit_test.h
+++ b/compiler/optimizing/optimizing_unit_test.h
@@ -278,7 +278,7 @@
/* class_linker= */ nullptr,
graph->GetDexFile(),
code_item,
- /* class_def_index= */ DexFile::kDexNoIndex16,
+ /* class_def_idx= */ DexFile::kDexNoIndex16,
/* method_idx= */ dex::kDexNoIndex,
/* access_flags= */ 0u,
/* verified_method= */ nullptr,
@@ -559,7 +559,7 @@
class OptimizingUnitTest : public CommonArtTest, public OptimizingUnitTestHelper {};
// Naive string diff data type.
-typedef std::list<std::pair<std::string, std::string>> diff_t;
+using diff_t = std::list<std::pair<std::string, std::string>>;
// An alias for the empty string used to make it clear that a line is
// removed in a diff.
diff --git a/compiler/optimizing/reference_type_propagation.cc b/compiler/optimizing/reference_type_propagation.cc
index e6024b0..3bfacde 100644
--- a/compiler/optimizing/reference_type_propagation.cc
+++ b/compiler/optimizing/reference_type_propagation.cc
@@ -43,16 +43,12 @@
class ReferenceTypePropagation::RTPVisitor : public HGraphDelegateVisitor {
public:
- RTPVisitor(HGraph* graph,
- Handle<mirror::ClassLoader> class_loader,
- Handle<mirror::DexCache> hint_dex_cache,
- bool is_first_run)
- : HGraphDelegateVisitor(graph),
- class_loader_(class_loader),
- hint_dex_cache_(hint_dex_cache),
- allocator_(graph->GetArenaStack()),
- worklist_(allocator_.Adapter(kArenaAllocReferenceTypePropagation)),
- is_first_run_(is_first_run) {
+ RTPVisitor(HGraph* graph, Handle<mirror::DexCache> hint_dex_cache, bool is_first_run)
+ : HGraphDelegateVisitor(graph),
+ hint_dex_cache_(hint_dex_cache),
+ allocator_(graph->GetArenaStack()),
+ worklist_(allocator_.Adapter(kArenaAllocReferenceTypePropagation)),
+ is_first_run_(is_first_run) {
worklist_.reserve(kDefaultWorklistSize);
}
@@ -110,7 +106,6 @@
static constexpr size_t kDefaultWorklistSize = 8;
- Handle<mirror::ClassLoader> class_loader_;
Handle<mirror::DexCache> hint_dex_cache_;
// Use local allocator for allocating memory.
@@ -122,15 +117,10 @@
};
ReferenceTypePropagation::ReferenceTypePropagation(HGraph* graph,
- Handle<mirror::ClassLoader> class_loader,
Handle<mirror::DexCache> hint_dex_cache,
bool is_first_run,
const char* name)
- : HOptimization(graph, name),
- class_loader_(class_loader),
- hint_dex_cache_(hint_dex_cache),
- is_first_run_(is_first_run) {
-}
+ : HOptimization(graph, name), hint_dex_cache_(hint_dex_cache), is_first_run_(is_first_run) {}
void ReferenceTypePropagation::ValidateTypes() {
// TODO: move this to the graph checker. Note: There may be no Thread for gtests.
@@ -167,18 +157,12 @@
}
void ReferenceTypePropagation::Visit(HInstruction* instruction) {
- RTPVisitor visitor(graph_,
- class_loader_,
- hint_dex_cache_,
- is_first_run_);
+ RTPVisitor visitor(graph_, hint_dex_cache_, is_first_run_);
instruction->Accept(&visitor);
}
void ReferenceTypePropagation::Visit(ArrayRef<HInstruction* const> instructions) {
- RTPVisitor visitor(graph_,
- class_loader_,
- hint_dex_cache_,
- is_first_run_);
+ RTPVisitor visitor(graph_, hint_dex_cache_, is_first_run_);
for (HInstruction* instruction : instructions) {
if (instruction->IsPhi()) {
// Need to force phis to recalculate null-ness.
@@ -349,7 +333,7 @@
}
bool ReferenceTypePropagation::Run() {
- RTPVisitor visitor(graph_, class_loader_, hint_dex_cache_, is_first_run_);
+ RTPVisitor visitor(graph_, hint_dex_cache_, is_first_run_);
// To properly propagate type info we need to visit in the dominator-based order.
// Reverse post order guarantees a node's dominators are visited first.
@@ -583,7 +567,7 @@
ScopedObjectAccess soa(Thread::Current());
ObjPtr<mirror::DexCache> dex_cache = FindDexCacheWithHint(soa.Self(), dex_file, hint_dex_cache_);
ObjPtr<mirror::Class> klass = Runtime::Current()->GetClassLinker()->LookupResolvedType(
- type_idx, dex_cache, class_loader_.Get());
+ type_idx, dex_cache, dex_cache->GetClassLoader());
SetClassAsTypeInfo(instr, klass, is_exact);
}
diff --git a/compiler/optimizing/reference_type_propagation.h b/compiler/optimizing/reference_type_propagation.h
index 889a846..e2e178a 100644
--- a/compiler/optimizing/reference_type_propagation.h
+++ b/compiler/optimizing/reference_type_propagation.h
@@ -31,7 +31,6 @@
class ReferenceTypePropagation : public HOptimization {
public:
ReferenceTypePropagation(HGraph* graph,
- Handle<mirror::ClassLoader> class_loader,
Handle<mirror::DexCache> hint_dex_cache,
bool is_first_run,
const char* name = kReferenceTypePropagationPassName);
@@ -73,8 +72,6 @@
void ValidateTypes();
- Handle<mirror::ClassLoader> class_loader_;
-
// Note: hint_dex_cache_ is usually, but not necessarily, the dex cache associated with
// graph_->GetDexFile(). Since we may look up also in other dex files, it's used only
// as a hint, to reduce the number of calls to the costly ClassLinker::FindDexCache().
diff --git a/compiler/optimizing/reference_type_propagation_test.cc b/compiler/optimizing/reference_type_propagation_test.cc
index d1bcab0..839b1c6 100644
--- a/compiler/optimizing/reference_type_propagation_test.cc
+++ b/compiler/optimizing/reference_type_propagation_test.cc
@@ -47,11 +47,8 @@
void SetupPropagation(VariableSizedHandleScope* handles) {
graph_ = CreateGraph(handles);
- propagation_ = new (GetAllocator()) ReferenceTypePropagation(graph_,
- Handle<mirror::ClassLoader>(),
- Handle<mirror::DexCache>(),
- true,
- "test_prop");
+ propagation_ = new (GetAllocator())
+ ReferenceTypePropagation(graph_, Handle<mirror::DexCache>(), true, "test_prop");
}
// Relay method to merge type in reference type propagation.
diff --git a/compiler/optimizing/scheduler.cc b/compiler/optimizing/scheduler.cc
index 8f18ccf..d228aba 100644
--- a/compiler/optimizing/scheduler.cc
+++ b/compiler/optimizing/scheduler.cc
@@ -718,7 +718,7 @@
// HLoadException
// HMemoryBarrier
// HMonitorOperation
- // HNativeDebugInfo
+ // HNop
// HThrow
// HTryBoundary
// TODO: Some of the instructions above may be safe to schedule (maybe as
diff --git a/compiler/optimizing/scheduler_arm.cc b/compiler/optimizing/scheduler_arm.cc
index 965e1bd..25dd104 100644
--- a/compiler/optimizing/scheduler_arm.cc
+++ b/compiler/optimizing/scheduler_arm.cc
@@ -669,7 +669,7 @@
}
case DataType::Type::kReference: {
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
last_visited_latency_ = kArmLoadWithBakerReadBarrierLatency;
} else {
if (index->IsConstant()) {
@@ -937,7 +937,7 @@
break;
case DataType::Type::kReference:
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
last_visited_internal_latency_ = kArmMemoryLoadLatency + kArmIntegerOpLatency;
last_visited_latency_ = kArmMemoryLoadLatency;
} else {
diff --git a/compiler/optimizing/scheduler_arm.h b/compiler/optimizing/scheduler_arm.h
index d11222d..6aeea6d 100644
--- a/compiler/optimizing/scheduler_arm.h
+++ b/compiler/optimizing/scheduler_arm.h
@@ -22,9 +22,6 @@
namespace art {
namespace arm {
-// TODO: Replace CodeGeneratorARMType with CodeGeneratorARMVIXL everywhere?
-typedef CodeGeneratorARMVIXL CodeGeneratorARMType;
-
// AArch32 instruction latencies.
// We currently assume that all ARM CPUs share the same instruction latency list.
// The following latencies were tuned based on performance experiments and
@@ -52,7 +49,7 @@
class SchedulingLatencyVisitorARM : public SchedulingLatencyVisitor {
public:
explicit SchedulingLatencyVisitorARM(CodeGenerator* codegen)
- : codegen_(down_cast<CodeGeneratorARMType*>(codegen)) {}
+ : codegen_(down_cast<CodeGeneratorARMVIXL*>(codegen)) {}
// Default visitor for instructions not handled specifically below.
void VisitInstruction(HInstruction* ATTRIBUTE_UNUSED) override {
@@ -133,7 +130,7 @@
// The latency setting for each HInstruction depends on how CodeGenerator may generate code,
// latency visitors may query CodeGenerator for such information for accurate latency settings.
- CodeGeneratorARMType* codegen_;
+ CodeGeneratorARMVIXL* codegen_;
};
class HSchedulerARM : public HScheduler {
diff --git a/compiler/optimizing/select_generator.cc b/compiler/optimizing/select_generator.cc
index 5405382..f3b2af7 100644
--- a/compiler/optimizing/select_generator.cc
+++ b/compiler/optimizing/select_generator.cc
@@ -16,7 +16,7 @@
#include "select_generator.h"
-#include "base/scoped_arena_containers.h"
+#include "optimizing/nodes.h"
#include "reference_type_propagation.h"
namespace art {
@@ -90,135 +90,245 @@
return select_phi;
}
+bool HSelectGenerator::TryGenerateSelectSimpleDiamondPattern(
+ HBasicBlock* block, ScopedArenaSafeMap<HInstruction*, HSelect*>* cache) {
+ DCHECK(block->GetLastInstruction()->IsIf());
+ HIf* if_instruction = block->GetLastInstruction()->AsIf();
+ HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
+ HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
+ DCHECK_NE(true_block, false_block);
+
+ if (!IsSimpleBlock(true_block) ||
+ !IsSimpleBlock(false_block) ||
+ !BlocksMergeTogether(true_block, false_block)) {
+ return false;
+ }
+ HBasicBlock* merge_block = true_block->GetSingleSuccessor();
+
+ // If the branches are not empty, move instructions in front of the If.
+ // TODO(dbrazdil): This puts an instruction between If and its condition.
+ // Implement moving of conditions to first users if possible.
+ while (!true_block->IsSingleGoto() && !true_block->IsSingleReturn()) {
+ HInstruction* instr = true_block->GetFirstInstruction();
+ DCHECK(!instr->CanThrow());
+ instr->MoveBefore(if_instruction);
+ }
+ while (!false_block->IsSingleGoto() && !false_block->IsSingleReturn()) {
+ HInstruction* instr = false_block->GetFirstInstruction();
+ DCHECK(!instr->CanThrow());
+ instr->MoveBefore(if_instruction);
+ }
+ DCHECK(true_block->IsSingleGoto() || true_block->IsSingleReturn());
+ DCHECK(false_block->IsSingleGoto() || false_block->IsSingleReturn());
+
+ // Find the resulting true/false values.
+ size_t predecessor_index_true = merge_block->GetPredecessorIndexOf(true_block);
+ size_t predecessor_index_false = merge_block->GetPredecessorIndexOf(false_block);
+ DCHECK_NE(predecessor_index_true, predecessor_index_false);
+
+ bool both_successors_return = true_block->IsSingleReturn() && false_block->IsSingleReturn();
+ HPhi* phi = GetSingleChangedPhi(merge_block, predecessor_index_true, predecessor_index_false);
+
+ HInstruction* true_value = nullptr;
+ HInstruction* false_value = nullptr;
+ if (both_successors_return) {
+ true_value = true_block->GetFirstInstruction()->InputAt(0);
+ false_value = false_block->GetFirstInstruction()->InputAt(0);
+ } else if (phi != nullptr) {
+ true_value = phi->InputAt(predecessor_index_true);
+ false_value = phi->InputAt(predecessor_index_false);
+ } else {
+ return false;
+ }
+ DCHECK(both_successors_return || phi != nullptr);
+
+ // Create the Select instruction and insert it in front of the If.
+ HInstruction* condition = if_instruction->InputAt(0);
+ HSelect* select = new (graph_->GetAllocator()) HSelect(condition,
+ true_value,
+ false_value,
+ if_instruction->GetDexPc());
+ if (both_successors_return) {
+ if (true_value->GetType() == DataType::Type::kReference) {
+ DCHECK(false_value->GetType() == DataType::Type::kReference);
+ ReferenceTypePropagation::FixUpInstructionType(select, graph_->GetHandleCache());
+ }
+ } else if (phi->GetType() == DataType::Type::kReference) {
+ select->SetReferenceTypeInfo(phi->GetReferenceTypeInfo());
+ }
+ block->InsertInstructionBefore(select, if_instruction);
+
+ // Remove the true branch which removes the corresponding Phi
+ // input if needed. If left only with the false branch, the Phi is
+ // automatically removed.
+ if (both_successors_return) {
+ false_block->GetFirstInstruction()->ReplaceInput(select, 0);
+ } else {
+ phi->ReplaceInput(select, predecessor_index_false);
+ }
+
+ bool only_two_predecessors = (merge_block->GetPredecessors().size() == 2u);
+ true_block->DisconnectAndDelete();
+
+ // Merge remaining blocks which are now connected with Goto.
+ DCHECK_EQ(block->GetSingleSuccessor(), false_block);
+ block->MergeWith(false_block);
+ if (!both_successors_return && only_two_predecessors) {
+ DCHECK_EQ(only_two_predecessors, phi->GetBlock() == nullptr);
+ DCHECK_EQ(block->GetSingleSuccessor(), merge_block);
+ block->MergeWith(merge_block);
+ }
+
+ MaybeRecordStat(stats_, MethodCompilationStat::kSelectGenerated);
+
+ // Very simple way of finding common subexpressions in the generated HSelect statements
+ // (since this runs after GVN). Lookup by condition, and reuse latest one if possible
+ // (due to post order, latest select is most likely replacement). If needed, we could
+ // improve this by e.g. using the operands in the map as well.
+ auto it = cache->find(condition);
+ if (it == cache->end()) {
+ cache->Put(condition, select);
+ } else {
+ // Found cached value. See if latest can replace cached in the HIR.
+ HSelect* cached_select = it->second;
+ DCHECK_EQ(cached_select->GetCondition(), select->GetCondition());
+ if (cached_select->GetTrueValue() == select->GetTrueValue() &&
+ cached_select->GetFalseValue() == select->GetFalseValue() &&
+ select->StrictlyDominates(cached_select)) {
+ cached_select->ReplaceWith(select);
+ cached_select->GetBlock()->RemoveInstruction(cached_select);
+ }
+ it->second = select; // always cache latest
+ }
+
+ // No need to update dominance information, as we are simplifying
+ // a simple diamond shape, where the join block is merged with the
+ // entry block. Any following blocks would have had the join block
+ // as a dominator, and `MergeWith` handles changing that to the
+ // entry block
+ return true;
+}
+
+HBasicBlock* HSelectGenerator::TryFixupDoubleDiamondPattern(HBasicBlock* block) {
+ DCHECK(block->GetLastInstruction()->IsIf());
+ HIf* if_instruction = block->GetLastInstruction()->AsIf();
+ HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
+ HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
+ DCHECK_NE(true_block, false_block);
+
+ // One branch must be a single goto, and the other one the inner if.
+ if (true_block->IsSingleGoto() == false_block->IsSingleGoto()) {
+ return nullptr;
+ }
+
+ HBasicBlock* single_goto = true_block->IsSingleGoto() ? true_block : false_block;
+ HBasicBlock* inner_if_block = true_block->IsSingleGoto() ? false_block : true_block;
+
+ // The innner if branch has to be a block with just a comparison and an if.
+ if (!inner_if_block->EndsWithIf() ||
+ inner_if_block->GetLastInstruction()->AsIf()->InputAt(0) !=
+ inner_if_block->GetFirstInstruction() ||
+ inner_if_block->GetLastInstruction()->GetPrevious() !=
+ inner_if_block->GetFirstInstruction() ||
+ !inner_if_block->GetFirstInstruction()->IsCondition()) {
+ return nullptr;
+ }
+
+ HIf* inner_if_instruction = inner_if_block->GetLastInstruction()->AsIf();
+ HBasicBlock* inner_if_true_block = inner_if_instruction->IfTrueSuccessor();
+ HBasicBlock* inner_if_false_block = inner_if_instruction->IfFalseSuccessor();
+ if (!inner_if_true_block->IsSingleGoto() || !inner_if_false_block->IsSingleGoto()) {
+ return nullptr;
+ }
+
+ // One must merge into the outer condition and the other must not.
+ if (BlocksMergeTogether(single_goto, inner_if_true_block) ==
+ BlocksMergeTogether(single_goto, inner_if_false_block)) {
+ return nullptr;
+ }
+
+ // First merge merges the outer if with one of the inner if branches. The block must be a Phi and
+ // a Goto.
+ HBasicBlock* first_merge = single_goto->GetSingleSuccessor();
+ if (first_merge->GetNumberOfPredecessors() != 2 ||
+ first_merge->GetPhis().CountSize() != 1 ||
+ !first_merge->GetLastInstruction()->IsGoto() ||
+ first_merge->GetFirstInstruction() != first_merge->GetLastInstruction()) {
+ return nullptr;
+ }
+
+ HPhi* first_phi = first_merge->GetFirstPhi()->AsPhi();
+
+ // Second merge is first_merge and the remainder branch merging. It must be phi + goto, or phi +
+ // return. Depending on the first merge, we define the second merge.
+ HBasicBlock* merges_into_second_merge =
+ BlocksMergeTogether(single_goto, inner_if_true_block)
+ ? inner_if_false_block
+ : inner_if_true_block;
+ if (!BlocksMergeTogether(first_merge, merges_into_second_merge)) {
+ return nullptr;
+ }
+
+ HBasicBlock* second_merge = merges_into_second_merge->GetSingleSuccessor();
+ if (second_merge->GetNumberOfPredecessors() != 2 ||
+ second_merge->GetPhis().CountSize() != 1 ||
+ !(second_merge->GetLastInstruction()->IsGoto() ||
+ second_merge->GetLastInstruction()->IsReturn()) ||
+ second_merge->GetFirstInstruction() != second_merge->GetLastInstruction()) {
+ return nullptr;
+ }
+
+ size_t index = second_merge->GetPredecessorIndexOf(merges_into_second_merge);
+ HPhi* second_phi = second_merge->GetFirstPhi()->AsPhi();
+
+ // Merge the phis.
+ first_phi->AddInput(second_phi->InputAt(index));
+ merges_into_second_merge->ReplaceSuccessor(second_merge, first_merge);
+ second_phi->ReplaceWith(first_phi);
+ second_merge->RemovePhi(second_phi);
+
+ // Sort out the new domination before merging the blocks
+ DCHECK_EQ(second_merge->GetSinglePredecessor(), first_merge);
+ second_merge->GetDominator()->RemoveDominatedBlock(second_merge);
+ second_merge->SetDominator(first_merge);
+ first_merge->AddDominatedBlock(second_merge);
+ first_merge->MergeWith(second_merge);
+
+ return inner_if_block;
+}
+
bool HSelectGenerator::Run() {
- bool didSelect = false;
+ bool did_select = false;
// Select cache with local allocator.
ScopedArenaAllocator allocator(graph_->GetArenaStack());
- ScopedArenaSafeMap<HInstruction*, HSelect*> cache(
- std::less<HInstruction*>(), allocator.Adapter(kArenaAllocSelectGenerator));
+ ScopedArenaSafeMap<HInstruction*, HSelect*> cache(std::less<HInstruction*>(),
+ allocator.Adapter(kArenaAllocSelectGenerator));
// Iterate in post order in the unlikely case that removing one occurrence of
// the selection pattern empties a branch block of another occurrence.
for (HBasicBlock* block : graph_->GetPostOrder()) {
- if (!block->EndsWithIf()) continue;
-
- // Find elements of the diamond pattern.
- HIf* if_instruction = block->GetLastInstruction()->AsIf();
- HBasicBlock* true_block = if_instruction->IfTrueSuccessor();
- HBasicBlock* false_block = if_instruction->IfFalseSuccessor();
- DCHECK_NE(true_block, false_block);
-
- if (!IsSimpleBlock(true_block) ||
- !IsSimpleBlock(false_block) ||
- !BlocksMergeTogether(true_block, false_block)) {
+ if (!block->EndsWithIf()) {
continue;
}
- HBasicBlock* merge_block = true_block->GetSingleSuccessor();
- // If the branches are not empty, move instructions in front of the If.
- // TODO(dbrazdil): This puts an instruction between If and its condition.
- // Implement moving of conditions to first users if possible.
- while (!true_block->IsSingleGoto() && !true_block->IsSingleReturn()) {
- HInstruction* instr = true_block->GetFirstInstruction();
- DCHECK(!instr->CanThrow());
- instr->MoveBefore(if_instruction);
- }
- while (!false_block->IsSingleGoto() && !false_block->IsSingleReturn()) {
- HInstruction* instr = false_block->GetFirstInstruction();
- DCHECK(!instr->CanThrow());
- instr->MoveBefore(if_instruction);
- }
- DCHECK(true_block->IsSingleGoto() || true_block->IsSingleReturn());
- DCHECK(false_block->IsSingleGoto() || false_block->IsSingleReturn());
-
- // Find the resulting true/false values.
- size_t predecessor_index_true = merge_block->GetPredecessorIndexOf(true_block);
- size_t predecessor_index_false = merge_block->GetPredecessorIndexOf(false_block);
- DCHECK_NE(predecessor_index_true, predecessor_index_false);
-
- bool both_successors_return = true_block->IsSingleReturn() && false_block->IsSingleReturn();
- HPhi* phi = GetSingleChangedPhi(merge_block, predecessor_index_true, predecessor_index_false);
-
- HInstruction* true_value = nullptr;
- HInstruction* false_value = nullptr;
- if (both_successors_return) {
- true_value = true_block->GetFirstInstruction()->InputAt(0);
- false_value = false_block->GetFirstInstruction()->InputAt(0);
- } else if (phi != nullptr) {
- true_value = phi->InputAt(predecessor_index_true);
- false_value = phi->InputAt(predecessor_index_false);
+ if (TryGenerateSelectSimpleDiamondPattern(block, &cache)) {
+ did_select = true;
} else {
- continue;
- }
- DCHECK(both_successors_return || phi != nullptr);
-
- // Create the Select instruction and insert it in front of the If.
- HInstruction* condition = if_instruction->InputAt(0);
- HSelect* select = new (graph_->GetAllocator()) HSelect(condition,
- true_value,
- false_value,
- if_instruction->GetDexPc());
- if (both_successors_return) {
- if (true_value->GetType() == DataType::Type::kReference) {
- DCHECK(false_value->GetType() == DataType::Type::kReference);
- ReferenceTypePropagation::FixUpInstructionType(select, graph_->GetHandleCache());
+ // Try to fix up the odd version of the double diamond pattern. If we could do it, it means
+ // that we can generate two selects.
+ HBasicBlock* inner_if_block = TryFixupDoubleDiamondPattern(block);
+ if (inner_if_block != nullptr) {
+ // Generate the selects now since `inner_if_block` should be after `block` in PostOrder.
+ bool result = TryGenerateSelectSimpleDiamondPattern(inner_if_block, &cache);
+ DCHECK(result);
+ result = TryGenerateSelectSimpleDiamondPattern(block, &cache);
+ DCHECK(result);
+ did_select = true;
}
- } else if (phi->GetType() == DataType::Type::kReference) {
- select->SetReferenceTypeInfo(phi->GetReferenceTypeInfo());
}
- block->InsertInstructionBefore(select, if_instruction);
-
- // Remove the true branch which removes the corresponding Phi
- // input if needed. If left only with the false branch, the Phi is
- // automatically removed.
- if (both_successors_return) {
- false_block->GetFirstInstruction()->ReplaceInput(select, 0);
- } else {
- phi->ReplaceInput(select, predecessor_index_false);
- }
-
- bool only_two_predecessors = (merge_block->GetPredecessors().size() == 2u);
- true_block->DisconnectAndDelete();
-
- // Merge remaining blocks which are now connected with Goto.
- DCHECK_EQ(block->GetSingleSuccessor(), false_block);
- block->MergeWith(false_block);
- if (!both_successors_return && only_two_predecessors) {
- DCHECK_EQ(only_two_predecessors, phi->GetBlock() == nullptr);
- DCHECK_EQ(block->GetSingleSuccessor(), merge_block);
- block->MergeWith(merge_block);
- }
-
- MaybeRecordStat(stats_, MethodCompilationStat::kSelectGenerated);
-
- // Very simple way of finding common subexpressions in the generated HSelect statements
- // (since this runs after GVN). Lookup by condition, and reuse latest one if possible
- // (due to post order, latest select is most likely replacement). If needed, we could
- // improve this by e.g. using the operands in the map as well.
- auto it = cache.find(condition);
- if (it == cache.end()) {
- cache.Put(condition, select);
- } else {
- // Found cached value. See if latest can replace cached in the HIR.
- HSelect* cached = it->second;
- DCHECK_EQ(cached->GetCondition(), select->GetCondition());
- if (cached->GetTrueValue() == select->GetTrueValue() &&
- cached->GetFalseValue() == select->GetFalseValue() &&
- select->StrictlyDominates(cached)) {
- cached->ReplaceWith(select);
- cached->GetBlock()->RemoveInstruction(cached);
- }
- it->second = select; // always cache latest
- }
-
- // No need to update dominance information, as we are simplifying
- // a simple diamond shape, where the join block is merged with the
- // entry block. Any following blocks would have had the join block
- // as a dominator, and `MergeWith` handles changing that to the
- // entry block.
- didSelect = true;
}
- return didSelect;
+
+ return did_select;
}
} // namespace art
diff --git a/compiler/optimizing/select_generator.h b/compiler/optimizing/select_generator.h
index 30ac8a8..4f13917 100644
--- a/compiler/optimizing/select_generator.h
+++ b/compiler/optimizing/select_generator.h
@@ -57,7 +57,9 @@
#ifndef ART_COMPILER_OPTIMIZING_SELECT_GENERATOR_H_
#define ART_COMPILER_OPTIMIZING_SELECT_GENERATOR_H_
+#include "base/scoped_arena_containers.h"
#include "optimization.h"
+#include "optimizing/nodes.h"
namespace art {
@@ -72,6 +74,43 @@
static constexpr const char* kSelectGeneratorPassName = "select_generator";
private:
+ bool TryGenerateSelectSimpleDiamondPattern(HBasicBlock* block,
+ ScopedArenaSafeMap<HInstruction*, HSelect*>* cache);
+
+ // When generating code for nested ternary operators (e.g. `return (x > 100) ? 100 : ((x < -100) ?
+ // -100 : x);`), a dexer can generate a double diamond pattern but it is not a clear cut one due
+ // to the merging of the blocks. `TryFixupDoubleDiamondPattern` recognizes that pattern and fixes
+ // up the graph to have a clean double diamond that `TryGenerateSelectSimpleDiamondPattern` can
+ // use to generate selects.
+ //
+ // In ASCII, it turns:
+ //
+ // 1 (outer if)
+ // / \
+ // 2 3 (inner if)
+ // | / \
+ // | 4 5
+ // \/ |
+ // 6 |
+ // \ |
+ // 7
+ // |
+ // 8
+ // into:
+ // 1 (outer if)
+ // / \
+ // 2 3 (inner if)
+ // | / \
+ // | 4 5
+ // \/ /
+ // 6
+ // |
+ // 8
+ //
+ // In short, block 7 disappears and we merge 6 and 7. Now we have a diamond with {3,4,5,6}, and
+ // when that gets resolved we get another one with the outer if.
+ HBasicBlock* TryFixupDoubleDiamondPattern(HBasicBlock* block);
+
DISALLOW_COPY_AND_ASSIGN(HSelectGenerator);
};
diff --git a/compiler/optimizing/ssa_builder.cc b/compiler/optimizing/ssa_builder.cc
index 67ee83c..a36b74b 100644
--- a/compiler/optimizing/ssa_builder.cc
+++ b/compiler/optimizing/ssa_builder.cc
@@ -538,7 +538,6 @@
// Compute type of reference type instructions. The pass assumes that
// NullConstant has been fixed up.
ReferenceTypePropagation(graph_,
- class_loader_,
dex_cache_,
/* is_first_run= */ true).Run();
diff --git a/compiler/optimizing/stack_map_stream.cc b/compiler/optimizing/stack_map_stream.cc
index f55bbee..c13a355 100644
--- a/compiler/optimizing/stack_map_stream.cc
+++ b/compiler/optimizing/stack_map_stream.cc
@@ -49,7 +49,8 @@
size_t core_spill_mask,
size_t fp_spill_mask,
uint32_t num_dex_registers,
- bool baseline) {
+ bool baseline,
+ bool debuggable) {
DCHECK(!in_method_) << "Mismatched Begin/End calls";
in_method_ = true;
DCHECK_EQ(packed_frame_size_, 0u) << "BeginMethod was already called";
@@ -60,6 +61,7 @@
fp_spill_mask_ = fp_spill_mask;
num_dex_registers_ = num_dex_registers;
baseline_ = baseline;
+ debuggable_ = debuggable;
if (kVerifyStackMaps) {
dchecks_.emplace_back([=](const CodeInfo& code_info) {
@@ -367,6 +369,7 @@
uint32_t flags = (inline_infos_.size() > 0) ? CodeInfo::kHasInlineInfo : 0;
flags |= baseline_ ? CodeInfo::kIsBaseline : 0;
+ flags |= debuggable_ ? CodeInfo::kIsDebuggable : 0;
DCHECK_LE(flags, kVarintMax); // Ensure flags can be read directly as byte.
uint32_t bit_table_flags = 0;
ForEachBitTable([&bit_table_flags](size_t i, auto bit_table) {
diff --git a/compiler/optimizing/stack_map_stream.h b/compiler/optimizing/stack_map_stream.h
index 27145a1..1aaa6ae 100644
--- a/compiler/optimizing/stack_map_stream.h
+++ b/compiler/optimizing/stack_map_stream.h
@@ -64,7 +64,8 @@
size_t core_spill_mask,
size_t fp_spill_mask,
uint32_t num_dex_registers,
- bool baseline = false);
+ bool baseline,
+ bool debuggable);
void EndMethod(size_t code_size);
void BeginStackMapEntry(uint32_t dex_pc,
@@ -125,6 +126,7 @@
uint32_t fp_spill_mask_ = 0;
uint32_t num_dex_registers_ = 0;
bool baseline_;
+ bool debuggable_;
BitTableBuilder<StackMap> stack_maps_;
BitTableBuilder<RegisterMask> register_masks_;
BitmapTableBuilder stack_masks_;
diff --git a/compiler/optimizing/stack_map_test.cc b/compiler/optimizing/stack_map_test.cc
index f6a739e..23af1f7 100644
--- a/compiler/optimizing/stack_map_test.cc
+++ b/compiler/optimizing/stack_map_test.cc
@@ -52,7 +52,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 2);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 2,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArenaBitVector sp_mask(&allocator, 0, false);
size_t number_of_dex_registers = 2;
@@ -106,7 +111,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 2);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 2,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArtMethod art_method;
ArenaBitVector sp_mask1(&allocator, 0, true);
@@ -300,7 +310,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 2);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 2,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArtMethod art_method;
ArenaBitVector sp_mask1(&allocator, 0, true);
@@ -363,7 +378,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 2);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 2,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArenaBitVector sp_mask(&allocator, 0, false);
uint32_t number_of_dex_registers = 2;
@@ -411,7 +431,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 2);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 2,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArenaBitVector sp_mask(&allocator, 0, false);
uint32_t number_of_dex_registers = 2;
@@ -467,7 +492,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 1);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 1,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArenaBitVector sp_mask(&allocator, 0, false);
stream.BeginStackMapEntry(0, 64 * kPcAlign, 0x3, &sp_mask);
@@ -512,7 +542,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 2);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 2,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArtMethod art_method;
ArenaBitVector sp_mask1(&allocator, 0, true);
@@ -702,7 +737,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 0);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 0,
+ /* baseline= */ false,
+ /* debuggable= */ false);
ArenaBitVector sp_mask(&allocator, 0, true);
sp_mask.SetBit(1);
diff --git a/compiler/utils/arm/assembler_arm_vixl.cc b/compiler/utils/arm/assembler_arm_vixl.cc
index 77f5d70..0271db9 100644
--- a/compiler/utils/arm/assembler_arm_vixl.cc
+++ b/compiler/utils/arm/assembler_arm_vixl.cc
@@ -82,7 +82,7 @@
void ArmVIXLAssembler::GenerateMarkingRegisterCheck(vixl32::Register temp, int code) {
// The Marking Register is only used in the Baker read barrier configuration.
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
vixl32::Label mr_is_ok;
diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
index 6e6d40d..035e13d 100644
--- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
+++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.cc
@@ -155,7 +155,7 @@
// Pop LR to PC unless we need to emit some read barrier code just before returning.
bool emit_code_before_return =
- (kEmitCompilerReadBarrier && kUseBakerReadBarrier) &&
+ (gUseReadBarrier && kUseBakerReadBarrier) &&
(may_suspend || (kIsDebugBuild && emit_run_time_checks_in_debug_mode_));
if ((core_spill_mask & (1u << lr.GetCode())) != 0u && !emit_code_before_return) {
DCHECK_EQ(core_spill_mask & (1u << pc.GetCode()), 0u);
@@ -215,7 +215,9 @@
}
}
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ // Emit marking register refresh even with uffd-GC as we are still using the
+ // register due to nterp's dependency.
+ if ((gUseReadBarrier || gUseUserfaultfd) && kUseBakerReadBarrier) {
if (may_suspend) {
// The method may be suspended; refresh the Marking Register.
___ Ldr(mr, MemOperand(tr, Thread::IsGcMarkingOffset<kArmPointerSize>().Int32Value()));
@@ -428,8 +430,15 @@
asm_.StoreToOffset(kStoreWord, scratch, tr, thr_offs.Int32Value());
}
-void ArmVIXLJNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs) {
- asm_.StoreToOffset(kStoreWord, sp, tr, thr_offs.Int32Value());
+void ArmVIXLJNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) {
+ if (tag_sp) {
+ UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
+ vixl32::Register reg = temps.Acquire();
+ ___ Orr(reg, sp, 0x2);
+ asm_.StoreToOffset(kStoreWord, reg, tr, thr_offs.Int32Value());
+ } else {
+ asm_.StoreToOffset(kStoreWord, sp, tr, thr_offs.Int32Value());
+ }
}
void ArmVIXLJNIMacroAssembler::SignExtend(ManagedRegister mreg ATTRIBUTE_UNUSED,
@@ -1165,7 +1174,7 @@
UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
vixl32::Register test_reg;
DCHECK_EQ(Thread::IsGcMarkingSize(), 4u);
- DCHECK(kUseReadBarrier);
+ DCHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// TestGcMarking() is used in the JNI stub entry when the marking register is up to date.
if (kIsDebugBuild && emit_run_time_checks_in_debug_mode_) {
@@ -1213,6 +1222,14 @@
}
}
+void ArmVIXLJNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+ UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
+ vixl32::Register scratch = temps.Acquire();
+ ___ Mov(scratch, static_cast<uint32_t>(address));
+ ___ Ldrb(scratch, MemOperand(scratch, 0));
+ ___ CompareAndBranchIfNonZero(scratch, ArmVIXLJNIMacroLabel::Cast(label)->AsArm());
+}
+
void ArmVIXLJNIMacroAssembler::Bind(JNIMacroLabel* label) {
CHECK(label != nullptr);
___ Bind(ArmVIXLJNIMacroLabel::Cast(label)->AsArm());
diff --git a/compiler/utils/arm/jni_macro_assembler_arm_vixl.h b/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
index ed453ae..5965552 100644
--- a/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
+++ b/compiler/utils/arm/jni_macro_assembler_arm_vixl.h
@@ -70,7 +70,7 @@
void StoreStackOffsetToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset32 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
@@ -213,6 +213,8 @@
void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
// Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+ // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+ void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
// Code at this offset will serve as the target for the Jump call.
void Bind(JNIMacroLabel* label) override;
diff --git a/compiler/utils/arm64/assembler_arm64.cc b/compiler/utils/arm64/assembler_arm64.cc
index 6100ed9..df05838 100644
--- a/compiler/utils/arm64/assembler_arm64.cc
+++ b/compiler/utils/arm64/assembler_arm64.cc
@@ -188,7 +188,7 @@
void Arm64Assembler::GenerateMarkingRegisterCheck(Register temp, int code) {
// The Marking Register is only used in the Baker read barrier configuration.
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(kUseBakerReadBarrier);
vixl::aarch64::Register mr = reg_x(MR); // Marking Register.
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.cc b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
index 50ca468..4043a33 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.cc
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.cc
@@ -218,10 +218,13 @@
___ Str(scratch, MEM_OP(reg_x(TR), tr_offs.Int32Value()));
}
-void Arm64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 tr_offs) {
+void Arm64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 tr_offs, bool tag_sp) {
UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
Register scratch = temps.AcquireX();
___ Mov(scratch, reg_x(SP));
+ if (tag_sp) {
+ ___ Orr(scratch, scratch, 0x2);
+ }
___ Str(scratch, MEM_OP(reg_x(TR), tr_offs.Int32Value()));
}
@@ -989,7 +992,7 @@
UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
Register test_reg;
DCHECK_EQ(Thread::IsGcMarkingSize(), 4u);
- DCHECK(kUseReadBarrier);
+ DCHECK(gUseReadBarrier);
if (kUseBakerReadBarrier) {
// TestGcMarking() is used in the JNI stub entry when the marking register is up to date.
if (kIsDebugBuild && emit_run_time_checks_in_debug_mode_) {
@@ -1037,6 +1040,14 @@
}
}
+void Arm64JNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+ UseScratchRegisterScope temps(asm_.GetVIXLAssembler());
+ Register scratch = temps.AcquireX();
+ ___ Mov(scratch, address);
+ ___ Ldrb(scratch.W(), MEM_OP(scratch, 0));
+ ___ Cbnz(scratch.W(), Arm64JNIMacroLabel::Cast(label)->AsArm64());
+}
+
void Arm64JNIMacroAssembler::Bind(JNIMacroLabel* label) {
CHECK(label != nullptr);
___ Bind(Arm64JNIMacroLabel::Cast(label)->AsArm64());
@@ -1107,7 +1118,9 @@
asm_.UnspillRegisters(core_reg_list, frame_size - core_reg_size);
asm_.UnspillRegisters(fp_reg_list, frame_size - core_reg_size - fp_reg_size);
- if (kEmitCompilerReadBarrier && kUseBakerReadBarrier) {
+ // Emit marking register refresh even with uffd-GC as we are still using the
+ // register due to nterp's dependency.
+ if ((gUseReadBarrier || gUseUserfaultfd) && kUseBakerReadBarrier) {
vixl::aarch64::Register mr = reg_x(MR); // Marking Register.
vixl::aarch64::Register tr = reg_x(TR); // Thread Register.
diff --git a/compiler/utils/arm64/jni_macro_assembler_arm64.h b/compiler/utils/arm64/jni_macro_assembler_arm64.h
index 2c04184..9d3e821 100644
--- a/compiler/utils/arm64/jni_macro_assembler_arm64.h
+++ b/compiler/utils/arm64/jni_macro_assembler_arm64.h
@@ -72,7 +72,7 @@
void StoreRawPtr(FrameOffset dest, ManagedRegister src) override;
void StoreImmediateToFrame(FrameOffset dest, uint32_t imm) override;
void StoreStackOffsetToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset64 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
// Load routines.
@@ -197,6 +197,8 @@
void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
// Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+ // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+ void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
// Code at this offset will serve as the target for the Jump call.
void Bind(JNIMacroLabel* label) override;
diff --git a/compiler/utils/assembler_test.h b/compiler/utils/assembler_test.h
index bb22fe5..09146ea 100644
--- a/compiler/utils/assembler_test.h
+++ b/compiler/utils/assembler_test.h
@@ -59,7 +59,7 @@
return assembler_.get();
}
- typedef std::string (*TestFn)(AssemblerTest* assembler_test, Ass* assembler);
+ using TestFn = std::string (*)(AssemblerTest *, Ass *);
void DriverFn(TestFn f, const std::string& test_name) {
DriverWrapper(f(this, assembler_.get()), test_name);
@@ -259,7 +259,7 @@
std::string (AssemblerTest::*GetName1)(const Reg1&),
std::string (AssemblerTest::*GetName2)(const Reg2&),
std::string (AssemblerTest::*GetName3)(const Reg3&),
- std::string fmt,
+ const std::string& fmt,
int bias) {
std::string str;
std::vector<int64_t> imms = CreateImmediateValuesBits(abs(imm_bits), (imm_bits > 0));
diff --git a/compiler/utils/assembler_test_base.h b/compiler/utils/assembler_test_base.h
index bf73808..de19d66 100644
--- a/compiler/utils/assembler_test_base.h
+++ b/compiler/utils/assembler_test_base.h
@@ -59,7 +59,7 @@
// This is intended to be run as a test.
bool CheckTools() {
- for (auto cmd : { GetAssemblerCommand()[0], GetDisassemblerCommand()[0] }) {
+ for (const std::string& cmd : { GetAssemblerCommand()[0], GetDisassemblerCommand()[0] }) {
if (!OS::FileExists(cmd.c_str())) {
LOG(ERROR) << "Could not find " << cmd;
return false;
diff --git a/compiler/utils/assembler_thumb_test.cc b/compiler/utils/assembler_thumb_test.cc
index b2d4dcd..f867a05 100644
--- a/compiler/utils/assembler_thumb_test.cc
+++ b/compiler/utils/assembler_thumb_test.cc
@@ -159,7 +159,8 @@
__ StoreRef(FrameOffset(48), scratch_register);
__ StoreSpanning(FrameOffset(48), method_register, FrameOffset(48));
__ StoreStackOffsetToThread(ThreadOffset32(512), FrameOffset(4096));
- __ StoreStackPointerToThread(ThreadOffset32(512));
+ __ StoreStackPointerToThread(ThreadOffset32(512), false);
+ __ StoreStackPointerToThread(ThreadOffset32(512), true);
// Other
__ Call(method_register, FrameOffset(48));
diff --git a/compiler/utils/assembler_thumb_test_expected.cc.inc b/compiler/utils/assembler_thumb_test_expected.cc.inc
index b6c6025..ae84338 100644
--- a/compiler/utils/assembler_thumb_test_expected.cc.inc
+++ b/compiler/utils/assembler_thumb_test_expected.cc.inc
@@ -41,46 +41,46 @@
" 7e: 0d f5 80 5c add.w r12, sp, #4096\n"
" 82: c9 f8 00 c2 str.w r12, [r9, #512]\n"
" 86: c9 f8 00 d2 str.w sp, [r9, #512]\n"
- " 8a: d0 f8 30 e0 ldr.w lr, [r0, #48]\n"
- " 8e: f0 47 blx lr\n"
- " 90: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
- " 94: cd f8 30 c0 str.w r12, [sp, #48]\n"
- " 98: d9 f8 00 c2 ldr.w r12, [r9, #512]\n"
- " 9c: cd f8 2c c0 str.w r12, [sp, #44]\n"
- " a0: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
- " a4: cd f8 30 c0 str.w r12, [sp, #48]\n"
- " a8: 48 46 mov r0, r9\n"
- " aa: cd f8 30 90 str.w r9, [sp, #48]\n"
- " ae: 04 46 mov r4, r0\n"
- " b0: 0d f1 30 0c add.w r12, sp, #48\n"
- " b4: bb f1 00 0f cmp.w r11, #0\n"
- " b8: 18 bf it ne\n"
- " ba: e3 46 movne r11, r12\n"
- " bc: 0d f1 30 0b add.w r11, sp, #48\n"
- " c0: 5f ea 0b 00 movs.w r0, r11\n"
- " c4: 18 bf it ne\n"
- " c6: 0c a8 addne r0, sp, #48\n"
- " c8: dd f8 40 c0 ldr.w r12, [sp, #64]\n"
- " cc: bc f1 00 0f cmp.w r12, #0\n"
- " d0: 18 bf it ne\n"
- " d2: 0d f1 40 0c addne.w r12, sp, #64\n"
- " d6: cd f8 30 c0 str.w r12, [sp, #48]\n"
- " da: 5f ea 0b 00 movs.w r0, r11\n"
- " de: 18 bf it ne\n"
- " e0: 00 a8 addne r0, sp, #0\n"
- " e2: 0d f2 04 40 addw r0, sp, #1028\n"
- " e6: bb f1 00 0f cmp.w r11, #0\n"
- " ea: 08 bf it eq\n"
- " ec: 58 46 moveq r0, r11\n"
- " ee: 0d f2 04 4c addw r12, sp, #1028\n"
- " f2: bb f1 00 0f cmp.w r11, #0\n"
- " f6: 18 bf it ne\n"
- " f8: e3 46 movne r11, r12\n"
- " fa: d9 f8 94 c0 ldr.w r12, [r9, #148]\n"
- " fe: bc f1 00 0f cmp.w r12, #0\n"
- " 102: 71 d1 bne 0x1e8 @ imm = #226\n"
- " 104: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 108: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 8a: 4d f0 02 0c orr r12, sp, #2\n"
+ " 8e: c9 f8 00 c2 str.w r12, [r9, #512]\n"
+ " 92: d0 f8 30 e0 ldr.w lr, [r0, #48]\n"
+ " 96: f0 47 blx lr\n"
+ " 98: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
+ " 9c: cd f8 30 c0 str.w r12, [sp, #48]\n"
+ " a0: d9 f8 00 c2 ldr.w r12, [r9, #512]\n"
+ " a4: cd f8 2c c0 str.w r12, [sp, #44]\n"
+ " a8: dd f8 2c c0 ldr.w r12, [sp, #44]\n"
+ " ac: cd f8 30 c0 str.w r12, [sp, #48]\n"
+ " b0: 48 46 mov r0, r9\n"
+ " b2: cd f8 30 90 str.w r9, [sp, #48]\n"
+ " b6: 04 46 mov r4, r0\n"
+ " b8: 0d f1 30 0c add.w r12, sp, #48\n"
+ " bc: bb f1 00 0f cmp.w r11, #0\n"
+ " c0: 18 bf it ne\n"
+ " c2: e3 46 movne r11, r12\n"
+ " c4: 0d f1 30 0b add.w r11, sp, #48\n"
+ " c8: 5f ea 0b 00 movs.w r0, r11\n"
+ " cc: 18 bf it ne\n"
+ " ce: 0c a8 addne r0, sp, #48\n"
+ " d0: dd f8 40 c0 ldr.w r12, [sp, #64]\n"
+ " d4: bc f1 00 0f cmp.w r12, #0\n"
+ " d8: 18 bf it ne\n"
+ " da: 0d f1 40 0c addne.w r12, sp, #64\n"
+ " de: cd f8 30 c0 str.w r12, [sp, #48]\n"
+ " e2: 5f ea 0b 00 movs.w r0, r11\n"
+ " e6: 18 bf it ne\n"
+ " e8: 00 a8 addne r0, sp, #0\n"
+ " ea: 0d f2 04 40 addw r0, sp, #1028\n"
+ " ee: bb f1 00 0f cmp.w r11, #0\n"
+ " f2: 08 bf it eq\n"
+ " f4: 58 46 moveq r0, r11\n"
+ " f6: 0d f2 04 4c addw r12, sp, #1028\n"
+ " fa: bb f1 00 0f cmp.w r11, #0\n"
+ " fe: 18 bf it ne\n"
+ " 100: e3 46 movne r11, r12\n"
+ " 102: d9 f8 9c c0 ldr.w r12, [r9, #156]\n"
+ " 106: bc f1 00 0f cmp.w r12, #0\n"
+ " 10a: 71 d1 bne 0x1f0 @ imm = #226\n"
" 10c: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 110: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 114: cd f8 ff c7 str.w r12, [sp, #2047]\n"
@@ -135,26 +135,28 @@
" 1d8: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1dc: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1e0: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 1e4: 00 f0 02 b8 b.w 0x1ec @ imm = #4\n"
- " 1e8: 00 f0 1b b8 b.w 0x222 @ imm = #54\n"
- " 1ec: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 1f0: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 1e4: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 1e8: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 1ec: 00 f0 02 b8 b.w 0x1f4 @ imm = #4\n"
+ " 1f0: 00 f0 1b b8 b.w 0x22a @ imm = #54\n"
" 1f4: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1f8: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 1fc: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 200: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 204: cd f8 ff c7 str.w r12, [sp, #2047]\n"
" 208: cd f8 ff c7 str.w r12, [sp, #2047]\n"
- " 20c: 0d f5 80 5d add.w sp, sp, #4096\n"
- " 210: 08 b0 add sp, #32\n"
- " 212: 01 b0 add sp, #4\n"
- " 214: bd ec 10 8a vpop {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
- " 218: bd e8 e0 4d pop.w {r5, r6, r7, r8, r10, r11, lr}\n"
- " 21c: d9 f8 24 80 ldr.w r8, [r9, #36]\n"
- " 220: 70 47 bx lr\n"
- " 222: d9 f8 94 00 ldr.w r0, [r9, #148]\n"
- " 226: d9 f8 c8 e2 ldr.w lr, [r9, #712]\n"
- " 22a: f0 47 blx lr\n"
+ " 20c: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 210: cd f8 ff c7 str.w r12, [sp, #2047]\n"
+ " 214: 0d f5 80 5d add.w sp, sp, #4096\n"
+ " 218: 08 b0 add sp, #32\n"
+ " 21a: 01 b0 add sp, #4\n"
+ " 21c: bd ec 10 8a vpop {s16, s17, s18, s19, s20, s21, s22, s23, s24, s25, s26, s27, s28, s29, s30, s31}\n"
+ " 220: bd e8 e0 4d pop.w {r5, r6, r7, r8, r10, r11, lr}\n"
+ " 224: d9 f8 24 80 ldr.w r8, [r9, #36]\n"
+ " 228: 70 47 bx lr\n"
+ " 22a: d9 f8 9c 00 ldr.w r0, [r9, #156]\n"
+ " 22e: d9 f8 d4 e2 ldr.w lr, [r9, #724]\n"
+ " 232: f0 47 blx lr\n"
};
const char* const VixlLoadFromOffsetResults = {
diff --git a/compiler/utils/jni_macro_assembler.h b/compiler/utils/jni_macro_assembler.h
index 7022e3d..36de012 100644
--- a/compiler/utils/jni_macro_assembler.h
+++ b/compiler/utils/jni_macro_assembler.h
@@ -126,7 +126,11 @@
virtual void StoreStackOffsetToThread(ThreadOffset<kPointerSize> thr_offs,
FrameOffset fr_offs) = 0;
- virtual void StoreStackPointerToThread(ThreadOffset<kPointerSize> thr_offs) = 0;
+ // Stores stack pointer by tagging it if required so we can walk the stack. In debuggable runtimes
+ // we use tag to tell if we are using JITed code or AOT code. In non-debuggable runtimes we never
+ // use JITed code when AOT code is present. So checking for AOT code is sufficient to detect which
+ // code is being executed. We avoid tagging in non-debuggable runtimes to reduce instructions.
+ virtual void StoreStackPointerToThread(ThreadOffset<kPointerSize> thr_offs, bool tag_sp) = 0;
virtual void StoreSpanning(FrameOffset dest,
ManagedRegister src,
@@ -282,6 +286,8 @@
virtual void TestMarkBit(ManagedRegister ref,
JNIMacroLabel* label,
JNIMacroUnaryCondition cond) = 0;
+ // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+ virtual void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) = 0;
// Code at this offset will serve as the target for the Jump call.
virtual void Bind(JNIMacroLabel* label) = 0;
diff --git a/compiler/utils/jni_macro_assembler_test.h b/compiler/utils/jni_macro_assembler_test.h
index e77177e..62e8191 100644
--- a/compiler/utils/jni_macro_assembler_test.h
+++ b/compiler/utils/jni_macro_assembler_test.h
@@ -39,7 +39,7 @@
return assembler_.get();
}
- typedef std::string (*TestFn)(JNIMacroAssemblerTest* assembler_test, Ass* assembler);
+ using TestFn = std::string (*)(JNIMacroAssemblerTest *, Ass *);
void DriverFn(TestFn f, const std::string& test_name) {
DriverWrapper(f(this, assembler_.get()), test_name);
diff --git a/compiler/utils/x86/jni_macro_assembler_x86.cc b/compiler/utils/x86/jni_macro_assembler_x86.cc
index 685f5f1..e292c5b 100644
--- a/compiler/utils/x86/jni_macro_assembler_x86.cc
+++ b/compiler/utils/x86/jni_macro_assembler_x86.cc
@@ -187,8 +187,18 @@
__ fs()->movl(Address::Absolute(thr_offs), scratch);
}
-void X86JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs) {
- __ fs()->movl(Address::Absolute(thr_offs), ESP);
+void X86JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) {
+ if (tag_sp) {
+ // There is no free register, store contents onto stack and restore back later.
+ Register scratch = ECX;
+ __ movl(Address(ESP, -32), scratch);
+ __ movl(scratch, ESP);
+ __ orl(scratch, Immediate(0x2));
+ __ fs()->movl(Address::Absolute(thr_offs), scratch);
+ __ movl(scratch, Address(ESP, -32));
+ } else {
+ __ fs()->movl(Address::Absolute(thr_offs), ESP);
+ }
}
void X86JNIMacroAssembler::StoreSpanning(FrameOffset /*dst*/,
@@ -724,6 +734,12 @@
__ j(UnaryConditionToX86Condition(cond), X86JNIMacroLabel::Cast(label)->AsX86());
}
+
+void X86JNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+ __ cmpb(Address::Absolute(address), Immediate(0));
+ __ j(kNotZero, X86JNIMacroLabel::Cast(label)->AsX86());
+}
+
void X86JNIMacroAssembler::Bind(JNIMacroLabel* label) {
CHECK(label != nullptr);
__ Bind(X86JNIMacroLabel::Cast(label)->AsX86());
diff --git a/compiler/utils/x86/jni_macro_assembler_x86.h b/compiler/utils/x86/jni_macro_assembler_x86.h
index 29fccfd..571b213 100644
--- a/compiler/utils/x86/jni_macro_assembler_x86.h
+++ b/compiler/utils/x86/jni_macro_assembler_x86.h
@@ -66,7 +66,7 @@
void StoreStackOffsetToThread(ThreadOffset32 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset32 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset32 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
@@ -189,6 +189,8 @@
void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
// Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+ // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+ void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
// Code at this offset will serve as the target for the Jump call.
void Bind(JNIMacroLabel* label) override;
diff --git a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
index d5d1bba..8115911 100644
--- a/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
+++ b/compiler/utils/x86_64/jni_macro_assembler_x86_64.cc
@@ -217,8 +217,15 @@
__ gs()->movq(Address::Absolute(thr_offs, true), scratch);
}
-void X86_64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 thr_offs) {
- __ gs()->movq(Address::Absolute(thr_offs, true), CpuRegister(RSP));
+void X86_64JNIMacroAssembler::StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) {
+ if (tag_sp) {
+ CpuRegister reg = GetScratchRegister();
+ __ movq(reg, CpuRegister(RSP));
+ __ orq(reg, Immediate(0x2));
+ __ gs()->movq(Address::Absolute(thr_offs, true), reg);
+ } else {
+ __ gs()->movq(Address::Absolute(thr_offs, true), CpuRegister(RSP));
+ }
}
void X86_64JNIMacroAssembler::StoreSpanning(FrameOffset /*dst*/,
@@ -803,6 +810,13 @@
__ j(UnaryConditionToX86_64Condition(cond), X86_64JNIMacroLabel::Cast(label)->AsX86_64());
}
+void X86_64JNIMacroAssembler::TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) {
+ CpuRegister scratch = GetScratchRegister();
+ __ movq(scratch, Immediate(address));
+ __ cmpb(Address(scratch, 0), Immediate(0));
+ __ j(kNotZero, X86_64JNIMacroLabel::Cast(label)->AsX86_64());
+}
+
void X86_64JNIMacroAssembler::Bind(JNIMacroLabel* label) {
CHECK(label != nullptr);
__ Bind(X86_64JNIMacroLabel::Cast(label)->AsX86_64());
diff --git a/compiler/utils/x86_64/jni_macro_assembler_x86_64.h b/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
index e080f0b..04c6bfc 100644
--- a/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
+++ b/compiler/utils/x86_64/jni_macro_assembler_x86_64.h
@@ -67,7 +67,7 @@
void StoreStackOffsetToThread(ThreadOffset64 thr_offs, FrameOffset fr_offs) override;
- void StoreStackPointerToThread(ThreadOffset64 thr_offs) override;
+ void StoreStackPointerToThread(ThreadOffset64 thr_offs, bool tag_sp) override;
void StoreSpanning(FrameOffset dest, ManagedRegister src, FrameOffset in_off) override;
@@ -209,6 +209,8 @@
void TestGcMarking(JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
// Emit a conditional jump to the label by applying a unary condition test to object's mark bit.
void TestMarkBit(ManagedRegister ref, JNIMacroLabel* label, JNIMacroUnaryCondition cond) override;
+ // Emit a conditional jump to label if the loaded value from specified locations is not zero.
+ void TestByteAndJumpIfNotZero(uintptr_t address, JNIMacroLabel* label) override;
// Code at this offset will serve as the target for the Jump call.
void Bind(JNIMacroLabel* label) override;
diff --git a/dex2oat/Android.bp b/dex2oat/Android.bp
index 26cbd51..cd04dc1 100644
--- a/dex2oat/Android.bp
+++ b/dex2oat/Android.bp
@@ -459,6 +459,7 @@
name: "art_dex2oat_tests_defaults",
data: [
":art-gtest-jars-AbstractMethod",
+ ":art-gtest-jars-ArrayClassWithUnresolvedComponent",
":art-gtest-jars-DefaultMethods",
":art-gtest-jars-Dex2oatVdexPublicSdkDex",
":art-gtest-jars-Dex2oatVdexTestDex",
@@ -479,6 +480,7 @@
":art-gtest-jars-StaticLeafMethods",
":art-gtest-jars-Statics",
":art-gtest-jars-StringLiterals",
+ ":art-gtest-jars-SuperWithAccessChecks",
":art-gtest-jars-VerifierDeps",
":art-gtest-jars-VerifierDepsMulti",
":art-gtest-jars-VerifySoftFailDuringClinit",
@@ -599,6 +601,9 @@
":art-gtest-jars-Nested",
":generate-boot-image",
],
+ shared_libs: [
+ "libziparchive",
+ ],
test_config: "art_standalone_dex2oat_cts_tests.xml",
test_suites: ["cts"],
}
diff --git a/dex2oat/art_standalone_dex2oat_tests.xml b/dex2oat/art_standalone_dex2oat_tests.xml
index 32346f2..8fb120c 100644
--- a/dex2oat/art_standalone_dex2oat_tests.xml
+++ b/dex2oat/art_standalone_dex2oat_tests.xml
@@ -23,6 +23,8 @@
<target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
<option name="cleanup" value="true" />
<option name="push" value="art-gtest-jars-AbstractMethod.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-AbstractMethod.jar" />
+ <option name="push" value="art-gtest-jars-ArrayClassWithUnresolvedComponent.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-ArrayClassWithUnresolvedComponent.dex" />
+ <option name="push" value="art-gtest-jars-SuperWithAccessChecks.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-SuperWithAccessChecks.dex" />
<option name="push" value="art-gtest-jars-DefaultMethods.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-DefaultMethods.jar" />
<option name="push" value="art-gtest-jars-Dex2oatVdexPublicSdkDex.dex->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-Dex2oatVdexPublicSdkDex.dex" />
<option name="push" value="art-gtest-jars-Dex2oatVdexTestDex.jar->/data/local/tmp/art_standalone_dex2oat_tests/art-gtest-jars-Dex2oatVdexTestDex.jar" />
@@ -77,6 +79,8 @@
<option name="mainline-module-package-name" value="com.android.art" />
</object>
+ <!-- Skip on HWASan. TODO(b/230394041): Re-enable -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.SkipHWASanModuleController" />
<!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index 9e6103b..681811f 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -607,8 +607,13 @@
}
void ParseInstructionSetVariant(const std::string& option, ParserOptions* parser_options) {
- compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariant(
- compiler_options_->instruction_set_, option, &parser_options->error_msg);
+ if (kIsTargetBuild) {
+ compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariantAndHwcap(
+ compiler_options_->instruction_set_, option, &parser_options->error_msg);
+ } else {
+ compiler_options_->instruction_set_features_ = InstructionSetFeatures::FromVariant(
+ compiler_options_->instruction_set_, option, &parser_options->error_msg);
+ }
if (compiler_options_->instruction_set_features_ == nullptr) {
Usage("%s", parser_options->error_msg.c_str());
}
@@ -957,7 +962,7 @@
compiler_options_->GetNativeDebuggable());
key_value_store_->Put(OatHeader::kCompilerFilter,
CompilerFilter::NameOfFilter(compiler_options_->GetCompilerFilter()));
- key_value_store_->Put(OatHeader::kConcurrentCopying, kUseReadBarrier);
+ key_value_store_->Put(OatHeader::kConcurrentCopying, gUseReadBarrier);
if (invocation_file_.get() != -1) {
std::ostringstream oss;
for (int i = 0; i < argc; ++i) {
@@ -1187,7 +1192,7 @@
if (!parser_options->boot_image_filename.empty()) {
Usage("Option --boot-image and --force-jit-zygote cannot be specified together");
}
- parser_options->boot_image_filename = "boot.art:/nonx/boot-framework.art";
+ parser_options->boot_image_filename = GetJitZygoteBootImageLocation();
}
// If we have a profile, change the default compiler filter to speed-profile
@@ -2538,16 +2543,16 @@
}
bool PreparePreloadedClasses() {
- preloaded_classes_ = std::make_unique<HashSet<std::string>>();
if (!preloaded_classes_fds_.empty()) {
for (int fd : preloaded_classes_fds_) {
- if (!ReadCommentedInputFromFd(fd, nullptr, preloaded_classes_.get())) {
+ if (!ReadCommentedInputFromFd(fd, nullptr, &compiler_options_->preloaded_classes_)) {
return false;
}
}
} else {
for (const std::string& file : preloaded_classes_files_) {
- if (!ReadCommentedInputFromFile(file.c_str(), nullptr, preloaded_classes_.get())) {
+ if (!ReadCommentedInputFromFile(
+ file.c_str(), nullptr, &compiler_options_->preloaded_classes_)) {
return false;
}
}
@@ -2942,7 +2947,6 @@
const char* dirty_image_objects_filename_;
int dirty_image_objects_fd_;
std::unique_ptr<HashSet<std::string>> dirty_image_objects_;
- std::unique_ptr<HashSet<std::string>> preloaded_classes_;
std::unique_ptr<std::vector<std::string>> passes_to_run_;
bool is_host_;
std::string android_root_;
diff --git a/dex2oat/dex2oat_vdex_test.cc b/dex2oat/dex2oat_vdex_test.cc
index 895e9c2..df76d58 100644
--- a/dex2oat/dex2oat_vdex_test.cc
+++ b/dex2oat/dex2oat_vdex_test.cc
@@ -19,10 +19,8 @@
#include "common_runtime_test.h"
#include "dex2oat_environment_test.h"
-
#include "vdex_file.h"
#include "verifier/verifier_deps.h"
-#include "ziparchive/zip_writer.h"
namespace art {
@@ -113,23 +111,6 @@
return deps->GetVerifiedClasses(dex_file)[class_def_idx];
}
- void CreateDexMetadata(const std::string& vdex, const std::string& out_dm) {
- // Read the vdex bytes.
- std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex.c_str()));
- std::vector<uint8_t> data(vdex_file->GetLength());
- ASSERT_TRUE(vdex_file->ReadFully(data.data(), data.size()));
-
- // Zip the content.
- FILE* file = fopen(out_dm.c_str(), "wb");
- ZipWriter writer(file);
- writer.StartEntry("primary.vdex", ZipWriter::kAlign32);
- writer.WriteBytes(data.data(), data.size());
- writer.FinishEntry();
- writer.Finish();
- fflush(file);
- fclose(file);
- }
-
std::string GetFilename(const std::unique_ptr<const DexFile>& dex_file) {
const std::string& str = dex_file->GetLocation();
size_t idx = str.rfind('/');
diff --git a/dex2oat/driver/compiler_driver.cc b/dex2oat/driver/compiler_driver.cc
index d9509b0..5398412 100644
--- a/dex2oat/driver/compiler_driver.cc
+++ b/dex2oat/driver/compiler_driver.cc
@@ -1109,6 +1109,7 @@
HashSet<std::string>* image_classes)
REQUIRES_SHARED(Locks::mutator_lock_) {
DCHECK_EQ(self, Thread::Current());
+ DCHECK(klass->IsResolved());
Runtime* runtime = Runtime::Current();
gc::Heap* heap = runtime->GetHeap();
if (heap->ObjectIsInBootImageSpace(klass)) {
@@ -2232,6 +2233,19 @@
// Make sure the class initialization did not leave any local references.
self->GetJniEnv()->AssertLocalsEmpty();
}
+
+ if (!klass->IsVisiblyInitialized() &&
+ (is_boot_image || is_boot_image_extension) &&
+ !compiler_options.IsPreloadedClass(PrettyDescriptor(descriptor).c_str())) {
+ klass->SetInBootImageAndNotInPreloadedClasses();
+ }
+
+ if (compiler_options.CompileArtTest()) {
+ // For stress testing and unit-testing the clinit check in compiled code feature.
+ if (kIsDebugBuild || EndsWith(std::string_view(descriptor), "$NoPreloadHolder;")) {
+ klass->SetInBootImageAndNotInPreloadedClasses();
+ }
+ }
}
private:
diff --git a/dex2oat/linker/arm64/relative_patcher_arm64.cc b/dex2oat/linker/arm64/relative_patcher_arm64.cc
index 4028f75..5794040 100644
--- a/dex2oat/linker/arm64/relative_patcher_arm64.cc
+++ b/dex2oat/linker/arm64/relative_patcher_arm64.cc
@@ -251,7 +251,7 @@
} else {
if ((insn & 0xfffffc00) == 0x91000000) {
// ADD immediate, 64-bit with imm12 == 0 (unset).
- if (!kEmitCompilerReadBarrier) {
+ if (!gUseReadBarrier) {
DCHECK(patch.GetType() == LinkerPatch::Type::kIntrinsicReference ||
patch.GetType() == LinkerPatch::Type::kMethodRelative ||
patch.GetType() == LinkerPatch::Type::kTypeRelative ||
diff --git a/dex2oat/linker/code_info_table_deduper_test.cc b/dex2oat/linker/code_info_table_deduper_test.cc
index 8913b07..54b7dd5 100644
--- a/dex2oat/linker/code_info_table_deduper_test.cc
+++ b/dex2oat/linker/code_info_table_deduper_test.cc
@@ -35,7 +35,12 @@
ArenaStack arena_stack(&pool);
ScopedArenaAllocator allocator(&arena_stack);
StackMapStream stream(&allocator, kRuntimeISA);
- stream.BeginMethod(32, 0, 0, 2);
+ stream.BeginMethod(/* frame_size_in_bytes= */ 32,
+ /* core_spill_mask= */ 0,
+ /* fp_spill_mask= */ 0,
+ /* num_dex_registers= */ 2,
+ /* baseline= */ false,
+ /* debuggable= */ false);
stream.BeginStackMapEntry(0, 64 * kPcAlign);
stream.AddDexRegisterEntry(Kind::kInStack, 0);
diff --git a/dex2oat/linker/image_test.cc b/dex2oat/linker/image_test.cc
index 33d122b..82c87f5 100644
--- a/dex2oat/linker/image_test.cc
+++ b/dex2oat/linker/image_test.cc
@@ -176,5 +176,37 @@
/*image_classes_failing_aot_clinit=*/ {"LClassToInitialize;"});
}
+TEST_F(ImageTest, TestImageClassWithArrayClassWithUnresolvedComponent) {
+ CompilationHelper helper;
+ Compile(ImageHeader::kStorageModeUncompressed,
+ /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
+ helper,
+ "ArrayClassWithUnresolvedComponent",
+ /*image_classes=*/ {"LClassWithStatic;",
+ "LClassWithStaticConst;",
+ "[LClassWithMissingInterface;",
+ "[[LClassWithMissingInterface;",
+ "[LClassWithMissingSuper",
+ "[[LClassWithMissingSuper"},
+ /*image_classes_failing_aot_clinit=*/ {
+ "LClassWithStatic;",
+ "LClassWithStaticConst;"},
+ /*image_classes_failing_resolution=*/ {
+ "[LClassWithMissingInterface;",
+ "[[LClassWithMissingInterface;",
+ "[LClassWithMissingSuper",
+ "[[LClassWithMissingSuper"});
+}
+
+TEST_F(ImageTest, TestSuperWithAccessChecks) {
+ CompilationHelper helper;
+ Compile(ImageHeader::kStorageModeUncompressed,
+ /*max_image_block_size=*/std::numeric_limits<uint32_t>::max(),
+ helper,
+ "SuperWithAccessChecks",
+ /*image_classes=*/ {"LSubClass;", "LImplementsClass;"},
+ /*image_classes_failing_aot_clinit=*/ {"LSubClass;", "LImplementsClass;"});
+}
+
} // namespace linker
} // namespace art
diff --git a/dex2oat/linker/image_test.h b/dex2oat/linker/image_test.h
index 5c2d84c..52e53e6 100644
--- a/dex2oat/linker/image_test.h
+++ b/dex2oat/linker/image_test.h
@@ -63,6 +63,7 @@
struct CompilationHelper {
std::vector<std::string> dex_file_locations;
std::vector<ScratchFile> image_locations;
+ std::string extra_dex;
std::vector<std::unique_ptr<const DexFile>> extra_dex_files;
std::vector<ScratchFile> image_files;
std::vector<ScratchFile> oat_files;
@@ -86,7 +87,8 @@
/*out*/ CompilationHelper& out_helper,
const std::string& extra_dex = "",
const std::initializer_list<std::string>& image_classes = {},
- const std::initializer_list<std::string>& image_classes_failing_aot_clinit = {});
+ const std::initializer_list<std::string>& image_classes_failing_aot_clinit = {},
+ const std::initializer_list<std::string>& image_classes_failing_resolution = {});
void SetUpRuntimeOptions(RuntimeOptions* options) override {
CommonCompilerTest::SetUpRuntimeOptions(options);
@@ -149,18 +151,11 @@
inline void ImageTest::DoCompile(ImageHeader::StorageMode storage_mode,
/*out*/ CompilationHelper& out_helper) {
CompilerDriver* driver = compiler_driver_.get();
+ Runtime::Current()->AppendToBootClassPath(
+ out_helper.extra_dex, out_helper.extra_dex, out_helper.extra_dex_files);
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
std::vector<const DexFile*> class_path = class_linker->GetBootClassPath();
- for (const std::unique_ptr<const DexFile>& dex_file : out_helper.extra_dex_files) {
- {
- ScopedObjectAccess soa(Thread::Current());
- // Inject in boot class path so that the compiler driver can see it.
- class_linker->AppendToBootClassPath(soa.Self(), dex_file.get());
- }
- class_path.push_back(dex_file.get());
- }
-
// Enable write for dex2dex.
for (const DexFile* dex_file : class_path) {
out_helper.dex_file_locations.push_back(dex_file->GetLocation());
@@ -352,7 +347,8 @@
CompilationHelper& helper,
const std::string& extra_dex,
const std::initializer_list<std::string>& image_classes,
- const std::initializer_list<std::string>& image_classes_failing_aot_clinit) {
+ const std::initializer_list<std::string>& image_classes_failing_aot_clinit,
+ const std::initializer_list<std::string>& image_classes_failing_resolution) {
for (const std::string& image_class : image_classes_failing_aot_clinit) {
ASSERT_TRUE(ContainsElement(image_classes, image_class));
}
@@ -366,6 +362,7 @@
compiler_options_->SetMaxImageBlockSize(max_image_block_size);
image_classes_.clear();
if (!extra_dex.empty()) {
+ helper.extra_dex = extra_dex;
helper.extra_dex_files = OpenTestDexFiles(extra_dex.c_str());
}
DoCompile(storage_mode, helper);
@@ -375,12 +372,14 @@
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
for (const std::string& image_class : image_classes) {
ObjPtr<mirror::Class> klass =
- class_linker->FindSystemClass(Thread::Current(), image_class.c_str());
- EXPECT_TRUE(klass != nullptr);
- EXPECT_TRUE(klass->IsResolved());
- if (ContainsElement(image_classes_failing_aot_clinit, image_class)) {
+ class_linker->LookupClass(Thread::Current(), image_class.c_str(), nullptr);
+ if (ContainsElement(image_classes_failing_resolution, image_class)) {
+ EXPECT_TRUE(klass == nullptr || klass->IsErroneousUnresolved());
+ } else if (ContainsElement(image_classes_failing_aot_clinit, image_class)) {
+ ASSERT_TRUE(klass != nullptr) << image_class;
EXPECT_FALSE(klass->IsInitialized());
} else {
+ ASSERT_TRUE(klass != nullptr) << image_class;
EXPECT_TRUE(klass->IsInitialized());
}
}
diff --git a/dex2oat/linker/image_writer.cc b/dex2oat/linker/image_writer.cc
index 56fbe90..5c697fa 100644
--- a/dex2oat/linker/image_writer.cc
+++ b/dex2oat/linker/image_writer.cc
@@ -3356,7 +3356,7 @@
// The interpreter brige performs class initialization check if needed.
quick_code = GetOatAddress(StubType::kQuickToInterpreterBridge);
}
- } else if (needs_clinit_check) {
+ } else if (needs_clinit_check && !compiler_options_.ShouldCompileWithClinitCheck(method)) {
// If we do have code but the method needs a class initialization check before calling
// that code, install the resolution stub that will perform the check.
quick_code = GetOatAddress(StubType::kQuickResolutionTrampoline);
diff --git a/dex2oat/linker/oat_writer_test.cc b/dex2oat/linker/oat_writer_test.cc
index 6b2198d..06f5066 100644
--- a/dex2oat/linker/oat_writer_test.cc
+++ b/dex2oat/linker/oat_writer_test.cc
@@ -181,7 +181,7 @@
if (!oat_writer.WriteAndOpenDexFiles(
vdex_file,
verify,
- /*update_input_vdex=*/ false,
+ /*use_existing_vdex=*/ false,
copy,
&opened_dex_files_maps,
&opened_dex_files)) {
@@ -505,7 +505,7 @@
EXPECT_EQ(68U, sizeof(OatHeader));
EXPECT_EQ(4U, sizeof(OatMethodOffsets));
EXPECT_EQ(4U, sizeof(OatQuickMethodHeader));
- EXPECT_EQ(167 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
+ EXPECT_EQ(168 * static_cast<size_t>(GetInstructionSetPointerSize(kRuntimeISA)),
sizeof(QuickEntryPoints));
}
diff --git a/dex2oat/verifier_deps_test.cc b/dex2oat/verifier_deps_test.cc
index 708af04..27eadec 100644
--- a/dex2oat/verifier_deps_test.cc
+++ b/dex2oat/verifier_deps_test.cc
@@ -630,12 +630,5 @@
ASSERT_FALSE(buffer.empty());
}
-TEST_F(VerifierDepsTest, Assignable_Arrays) {
- ASSERT_TRUE(TestAssignabilityRecording(/* dst= */ "[LIface;",
- /* src= */ "[LMyClassExtendingInterface;"));
- ASSERT_FALSE(HasAssignable(
- "LIface;", "LMyClassExtendingInterface;"));
-}
-
} // namespace verifier
} // namespace art
diff --git a/dexlayout/dex_ir.h b/dexlayout/dex_ir.h
index 66ca84b..c819c67 100644
--- a/dexlayout/dex_ir.h
+++ b/dexlayout/dex_ir.h
@@ -20,7 +20,7 @@
#define ART_DEXLAYOUT_DEX_IR_H_
#include <stdint.h>
-
+#include <unordered_map>
#include <vector>
#include "base/iteration_range.h"
@@ -262,13 +262,21 @@
// Sort the vector by copying pointers over.
template <typename MapType>
void SortByMapOrder(const MapType& map) {
- auto it = map.begin();
CHECK_EQ(map.size(), Size());
+
+ // Move all pointers to a temporary map owning the pointers.
+ std::unordered_map<T*, ElementType> pointers_map;
+ pointers_map.reserve(Size());
+ for (std::unique_ptr<T>& element : collection_) {
+ pointers_map[element.get()] = std::move(element);
+ }
+
+ // Move back the pointers to the original vector according to the map order.
+ auto it = map.begin();
for (size_t i = 0; i < Size(); ++i) {
- // There are times when the array will temporarily contain the same pointer twice, doing the
- // release here sure there is no double free errors.
- collection_[i].release();
- collection_[i].reset(it->second);
+ auto element_it = pointers_map.find(it->second);
+ DCHECK(element_it != pointers_map.end());
+ collection_[i] = std::move(element_it->second);
++it;
}
}
diff --git a/dexoptanalyzer/Android.bp b/dexoptanalyzer/Android.bp
index 1e37b8d..66b0901 100644
--- a/dexoptanalyzer/Android.bp
+++ b/dexoptanalyzer/Android.bp
@@ -84,7 +84,8 @@
art_cc_defaults {
name: "art_dexoptanalyzer_tests_defaults",
shared_libs: [
- "libbacktrace",
+ "libunwindstack",
+ "libziparchive",
],
data: [
":art-gtest-jars-LinkageTest",
diff --git a/dexoptanalyzer/dexoptanalyzer.cc b/dexoptanalyzer/dexoptanalyzer.cc
index 0cc2cdb..ef43157 100644
--- a/dexoptanalyzer/dexoptanalyzer.cc
+++ b/dexoptanalyzer/dexoptanalyzer.cc
@@ -122,9 +122,6 @@
UsageError(" print a colon-separated list of its dex files to standard output. Dexopt");
UsageError(" needed analysis is not performed when this option is set.");
UsageError("");
- UsageError(" --validate-bcp: validates the boot class path files (.art, .oat, .vdex).");
- UsageError(" Requires --isa and --image options to locate artifacts.");
- UsageError("");
UsageError("Return code:");
UsageError(" To make it easier to integrate with the internal tools this command will make");
UsageError(" available its result (dexoptNeeded) as the exit/return code. i.e. it will not");
@@ -147,10 +144,7 @@
class DexoptAnalyzer final {
public:
- DexoptAnalyzer() :
- only_flatten_context_(false),
- only_validate_bcp_(false),
- downgrade_(false) {}
+ DexoptAnalyzer() : only_flatten_context_(false), downgrade_(false) {}
void ParseArgs(int argc, char **argv) {
original_argc = argc;
@@ -236,8 +230,6 @@
}
} else if (option == "--flatten-class-loader-context") {
only_flatten_context_ = true;
- } else if (option == "--validate-bcp") {
- only_validate_bcp_ = true;
} else {
Usage("Unknown argument '%s'", raw_option);
}
@@ -324,6 +316,7 @@
class_loader_context.get(),
/*load_executable=*/ false,
/*only_load_trusted_executable=*/ false,
+ /*runtime_options=*/ nullptr,
vdex_fd_,
oat_fd_,
zip_fd_);
@@ -362,82 +355,6 @@
}
}
- // Validates the boot classpath and boot classpath extensions by checking the image checksums,
- // the oat files and the vdex files.
- //
- // Returns `ReturnCode::kNoDexOptNeeded` when all the files are up-to-date,
- // `ReturnCode::kDex2OatFromScratch` if any of the files are missing or out-of-date, and
- // `ReturnCode::kErrorCannotCreateRuntime` if the files could not be tested due to problem
- // creating a runtime.
- ReturnCode ValidateBcp() const {
- using ImageSpace = gc::space::ImageSpace;
-
- if (!CreateRuntime()) {
- return ReturnCode::kErrorCannotCreateRuntime;
- }
- std::unique_ptr<Runtime> runtime(Runtime::Current());
-
- auto dex_files = ArrayRef<const DexFile* const>(runtime->GetClassLinker()->GetBootClassPath());
- auto boot_image_spaces = ArrayRef<ImageSpace* const>(runtime->GetHeap()->GetBootImageSpaces());
- const std::string checksums = ImageSpace::GetBootClassPathChecksums(boot_image_spaces,
- dex_files);
-
- std::string error_msg;
- const std::vector<std::string>& bcp = runtime->GetBootClassPath();
- const std::vector<std::string>& bcp_locations = runtime->GetBootClassPathLocations();
- const std::vector<int>& bcp_fds = runtime->GetBootClassPathFds();
- const std::vector<std::string>& image_locations = runtime->GetImageLocations();
- const std::string bcp_locations_path = android::base::Join(bcp_locations, ':');
- if (!ImageSpace::VerifyBootClassPathChecksums(checksums,
- bcp_locations_path,
- ArrayRef<const std::string>(image_locations),
- ArrayRef<const std::string>(bcp_locations),
- ArrayRef<const std::string>(bcp),
- ArrayRef<const int>(bcp_fds),
- runtime->GetInstructionSet(),
- &error_msg)) {
- LOG(INFO) << "Failed to verify boot class path checksums: " << error_msg;
- return ReturnCode::kDex2OatFromScratch;
- }
-
- const auto& image_spaces = runtime->GetHeap()->GetBootImageSpaces();
- size_t bcp_component_count = 0;
- for (const auto& image_space : image_spaces) {
- if (!image_space->GetImageHeader().IsValid()) {
- LOG(INFO) << "Image header is not valid: " << image_space->GetImageFilename();
- return ReturnCode::kDex2OatFromScratch;
- }
- const OatFile* oat_file = image_space->GetOatFile();
- if (oat_file == nullptr) {
- const std::string oat_path = ReplaceFileExtension(image_space->GetImageFilename(), "oat");
- LOG(INFO) << "Oat file missing: " << oat_path;
- return ReturnCode::kDex2OatFromScratch;
- }
- if (!oat_file->GetOatHeader().IsValid() ||
- !ImageSpace::ValidateOatFile(*oat_file, &error_msg)) {
- LOG(INFO) << "Oat file is not valid: " << oat_file->GetLocation() << " " << error_msg;
- return ReturnCode::kDex2OatFromScratch;
- }
- const VdexFile* vdex_file = oat_file->GetVdexFile();
- if (vdex_file == nullptr || !vdex_file->IsValid()) {
- LOG(INFO) << "Vdex file is not valid : " << oat_file->GetLocation();
- return ReturnCode::kDex2OatFromScratch;
- }
- bcp_component_count += image_space->GetComponentCount();
- }
-
- // If the number of components encountered in the image spaces does not match the number
- // of components expected from the boot classpath locations then something is missing.
- if (bcp_component_count != bcp_locations.size()) {
- for (size_t i = bcp_component_count; i < bcp_locations.size(); ++i) {
- LOG(INFO) << "Missing image file for " << bcp_locations[i];
- }
- return ReturnCode::kDex2OatFromScratch;
- }
-
- return ReturnCode::kNoDexOptNeeded;
- }
-
ReturnCode FlattenClassLoaderContext() const {
DCHECK(only_flatten_context_);
if (context_str_.empty()) {
@@ -449,15 +366,13 @@
Usage("Invalid --class-loader-context '%s'", context_str_.c_str());
}
- std::cout << context->FlattenDexPaths() << std::flush;
+ std::cout << android::base::Join(context->FlattenDexPaths(), ':') << std::flush;
return ReturnCode::kFlattenClassLoaderContextSuccess;
}
ReturnCode Run() const {
if (only_flatten_context_) {
return FlattenClassLoaderContext();
- } else if (only_validate_bcp_) {
- return ValidateBcp();
} else {
return GetDexOptNeeded();
}
@@ -469,7 +384,6 @@
CompilerFilter::Filter compiler_filter_;
std::string context_str_;
bool only_flatten_context_;
- bool only_validate_bcp_;
ProfileAnalysisResult profile_analysis_result_;
bool downgrade_;
std::string image_;
diff --git a/disassembler/Android.bp b/disassembler/Android.bp
index 4b55673..b7f758f 100644
--- a/disassembler/Android.bp
+++ b/disassembler/Android.bp
@@ -104,7 +104,6 @@
],
},
},
-
apex_available: [
"com.android.art",
"com.android.art.debug",
@@ -132,3 +131,38 @@
"com.android.art",
],
}
+
+art_cc_defaults {
+ name: "art_disassembler_tests_defaults",
+ codegen: {
+ arm64: {
+ srcs: ["disassembler_arm64_test.cc"],
+ },
+ },
+}
+
+// Version of ART gtest `art_disassembler_tests` bundled with the ART APEX on target.
+// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
+art_cc_test {
+ name: "art_disassembler_tests",
+ defaults: [
+ "art_gtest_defaults",
+ "art_disassembler_tests_defaults",
+ ],
+ static_libs: [
+ "libvixld",
+ ],
+}
+
+// Standalone version of ART gtest `art_disassembler_tests`,
+// not bundled with the ART APEX on target.
+art_cc_test {
+ name: "art_standalone_disassembler_tests",
+ defaults: [
+ "art_standalone_gtest_defaults",
+ "art_disassembler_tests_defaults",
+ ],
+ static_libs: [
+ "libvixl",
+ ],
+}
diff --git a/disassembler/disassembler_arm64.cc b/disassembler/disassembler_arm64.cc
index 0d51cfd..23472a8 100644
--- a/disassembler/disassembler_arm64.cc
+++ b/disassembler/disassembler_arm64.cc
@@ -18,6 +18,8 @@
#include <inttypes.h>
+#include <regex>
+
#include <sstream>
#include "android-base/logging.h"
@@ -58,9 +60,34 @@
Disassembler::AppendRegisterNameToOutput(instr, reg);
}
-void CustomDisassembler::VisitLoadLiteral(const Instruction* instr) {
- Disassembler::VisitLoadLiteral(instr);
+void CustomDisassembler::Visit(vixl::aarch64::Metadata* metadata, const Instruction* instr) {
+ vixl::aarch64::Disassembler::Visit(metadata, instr);
+ const std::string& form = (*metadata)["form"];
+ // These regexs are long, but it is an attempt to match the mapping entry keys in the
+ // #define DEFAULT_FORM_TO_VISITOR_MAP(VISITORCLASS) in the file
+ // external/vixl/src/aarch64/decoder-visitor-map-aarch64.h
+ // for the ::VisitLoadLiteralInstr, ::VisitLoadStoreUnsignedOffset or ::VisitUnconditionalBranch
+ // function addresess key values.
+ // N.B. the mapping are many to one.
+ if (std::regex_match(form, std::regex("(ldrsw|ldr|prfm)_(32|64|d|b|h|q|s)_loadlit"))) {
+ VisitLoadLiteralInstr(instr);
+ return;
+ }
+
+ if (std::regex_match(form, std::regex(
+ "(ldrb|ldrh|ldrsb|ldrsh|ldrsw|ldr|prfm|strb|strh|str)_(32|64|d|b|h|q|s)_ldst_pos"))) {
+ VisitLoadStoreUnsignedOffsetInstr(instr);
+ return;
+ }
+
+ if (std::regex_match(form, std::regex("(bl|b)_only_branch_imm"))) {
+ VisitUnconditionalBranchInstr(instr);
+ return;
+ }
+}
+
+void CustomDisassembler::VisitLoadLiteralInstr(const Instruction* instr) {
if (!read_literals_) {
return;
}
@@ -69,6 +96,7 @@
// avoid trying to fetch invalid literals (we can encounter this when
// interpreting raw data as instructions).
void* data_address = instr->GetLiteralAddress<void*>();
+
if (data_address < base_address_ || data_address >= end_address_) {
AppendToOutput(" (?)");
return;
@@ -97,17 +125,13 @@
}
}
-void CustomDisassembler::VisitLoadStoreUnsignedOffset(const Instruction* instr) {
- Disassembler::VisitLoadStoreUnsignedOffset(instr);
-
+void CustomDisassembler::VisitLoadStoreUnsignedOffsetInstr(const Instruction* instr) {
if (instr->GetRn() == TR) {
AppendThreadOfsetName(instr);
}
}
-void CustomDisassembler::VisitUnconditionalBranch(const Instruction* instr) {
- Disassembler::VisitUnconditionalBranch(instr);
-
+void CustomDisassembler::VisitUnconditionalBranchInstr(const Instruction* instr) {
if (instr->Mask(UnconditionalBranchMask) == BL) {
const Instruction* target = instr->GetImmPCOffsetTarget();
if (target >= base_address_ &&
diff --git a/disassembler/disassembler_arm64.h b/disassembler/disassembler_arm64.h
index a895dfe..d0443d2 100644
--- a/disassembler/disassembler_arm64.h
+++ b/disassembler/disassembler_arm64.h
@@ -47,16 +47,20 @@
void AppendRegisterNameToOutput(const vixl::aarch64::Instruction* instr,
const vixl::aarch64::CPURegister& reg) override;
- // Improve the disassembly of literal load instructions.
- void VisitLoadLiteral(const vixl::aarch64::Instruction* instr) override;
-
- // Improve the disassembly of thread offset.
- void VisitLoadStoreUnsignedOffset(const vixl::aarch64::Instruction* instr) override;
-
- // Improve the disassembly of branch to thunk jumping to pointer from thread entrypoint.
- void VisitUnconditionalBranch(const vixl::aarch64::Instruction* instr) override;
+ // Intercepts the instruction flow captured by the parent method,
+ // to specially instrument for particular instruction types.
+ void Visit(vixl::aarch64::Metadata* metadata, const vixl::aarch64::Instruction* instr) override;
private:
+ // Improve the disassembly of literal load instructions.
+ void VisitLoadLiteralInstr(const vixl::aarch64::Instruction* instr);
+
+ // Improve the disassembly of thread offset.
+ void VisitLoadStoreUnsignedOffsetInstr(const vixl::aarch64::Instruction* instr);
+
+ // Improve the disassembly of branch to thunk jumping to pointer from thread entrypoint.
+ void VisitUnconditionalBranchInstr(const vixl::aarch64::Instruction* instr);
+
void AppendThreadOfsetName(const vixl::aarch64::Instruction* instr);
// Indicate if the disassembler should read data loaded from literal pools.
diff --git a/disassembler/disassembler_arm64_test.cc b/disassembler/disassembler_arm64_test.cc
new file mode 100644
index 0000000..8279ed8
--- /dev/null
+++ b/disassembler/disassembler_arm64_test.cc
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2022 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 <inttypes.h>
+
+#include <regex>
+
+#include <sstream>
+
+#include "common_runtime_test.h"
+#include "disassembler_arm64.h"
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wshadow"
+#include "aarch64/disasm-aarch64.h"
+#include "aarch64/macro-assembler-aarch64.h"
+#pragma GCC diagnostic pop
+
+
+using namespace vixl::aarch64; // NOLINT(build/namespaces)
+
+namespace art {
+namespace arm64 {
+
+/**
+ * Fixture class for the ArtDisassemblerTest tests.
+ */
+class ArtDisassemblerTest : public CommonRuntimeTest {
+ public:
+ ArtDisassemblerTest() {
+ }
+
+ void SetupAssembly(uint64_t end_address) {
+ masm.GetCPUFeatures()->Combine(vixl::CPUFeatures::All());
+
+ disamOptions.reset(new DisassemblerOptions(/* absolute_addresses= */ true,
+ reinterpret_cast<uint8_t*>(0x0),
+ reinterpret_cast<uint8_t*>(end_address),
+ /* can_read_literals_= */ true,
+ &Thread::DumpThreadOffset<PointerSize::k64>));
+ disasm.reset(new CustomDisassembler(&*disamOptions));
+ decoder.AppendVisitor(disasm.get());
+ masm.SetGenerateSimulatorCode(false);
+ }
+
+ static constexpr size_t kMaxSizeGenerated = 1024;
+
+ template <typename LamdaType>
+ void ImplantInstruction(LamdaType fn) {
+ vixl::ExactAssemblyScope guard(&masm,
+ kMaxSizeGenerated,
+ vixl::ExactAssemblyScope::kMaximumSize);
+ fn();
+ }
+
+ // Appends an instruction to the existing buffer and then
+ // attempts to match the output of that instructions disassembly
+ // against a regex expression. Fails if no match is found.
+ template <typename LamdaType>
+ void CompareInstruction(LamdaType fn, const char* EXP) {
+ ImplantInstruction(fn);
+ masm.FinalizeCode();
+
+ // This gets the last instruction in the buffer.
+ // The end address of the buffer is at the end of the last instruction.
+ // sizeof(Instruction) is 1 byte as it in an empty class.
+ // Therefore we need to go back kInstructionSize * sizeof(Instruction) bytes
+ // in order to get to the start of the last instruction.
+ const Instruction* targetInstruction =
+ masm.GetBuffer()->GetEndAddress<Instruction*>()->
+ GetInstructionAtOffset(-static_cast<signed>(kInstructionSize));
+
+ decoder.Decode(targetInstruction);
+
+ const char* disassembly = disasm->GetOutput();
+
+ if (!std::regex_match(disassembly, std::regex(EXP))) {
+ const uint32_t encoding = static_cast<uint32_t>(targetInstruction->GetInstructionBits());
+
+ printf("\nEncoding: %08" PRIx32 "\nExpected: %s\nFound: %s\n",
+ encoding,
+ EXP,
+ disassembly);
+
+ ADD_FAILURE();
+ }
+ printf("----\n%s\n", disassembly);
+ }
+
+ std::unique_ptr<CustomDisassembler> disasm;
+ std::unique_ptr<DisassemblerOptions> disamOptions;
+ Decoder decoder;
+ MacroAssembler masm;
+};
+
+#define IMPLANT(fn) \
+ do { \
+ ImplantInstruction([&]() { this->masm.fn; }); \
+ } while (0)
+
+#define COMPARE(fn, output) \
+ do { \
+ CompareInstruction([&]() { this->masm.fn; }, (output)); \
+ } while (0)
+
+// These tests map onto the named per instruction instrumentation functions in:
+// ART/art/disassembler/disassembler_arm.cc
+// Context can be found in the logic conditional on incoming instruction types and sequences in the
+// ART disassembler. As of writing the functionality we are testing for that of additional
+// diagnostic info being appended to the end of the ART disassembly output.
+TEST_F(ArtDisassemblerTest, LoadLiteralVisitBadAddress) {
+ SetupAssembly(0xffffff);
+
+ // Check we append an erroneous hint "(?)" for literal load instructions with
+ // out of scope literal pool value addresses.
+ COMPARE(ldr(x0, vixl::aarch64::Assembler::ImmLLiteral(1000)),
+ "ldr x0, pc\\+128000 \\(addr -?0x[0-9a-fA-F]+\\) \\(\\?\\)");
+}
+
+TEST_F(ArtDisassemblerTest, LoadLiteralVisit) {
+ SetupAssembly(0xffffffffffffffff);
+
+ // Test that we do not append anything for ineligible instruction.
+ COMPARE(ldr(x0, MemOperand(x18, 0)), "ldr x0, \\[x18\\]$");
+
+ // Check we do append some extra info in the right text format for valid literal load instruction.
+ COMPARE(ldr(x0, vixl::aarch64::Assembler::ImmLLiteral(0)),
+ "ldr x0, pc\\+0 \\(addr -?0x[0-9a-f]+\\) \\(0x[0-9a-fA-F]+ / -?[0-9]+\\)");
+ COMPARE(ldr(d0, vixl::aarch64::Assembler::ImmLLiteral(0)),
+ "ldr d0, pc\\+0 \\(addr -?0x[0-9a-f]+\\) \\([0-9]+.[0-9]+e(\\+|-)[0-9]+\\)");
+}
+
+TEST_F(ArtDisassemblerTest, LoadStoreUnsignedOffsetVisit) {
+ SetupAssembly(0xffffffffffffffff);
+
+ // Test that we do not append anything for ineligible instruction.
+ COMPARE(ldr(x0, MemOperand(x18, 8)), "ldr x0, \\[x18, #8\\]$");
+ // Test that we do append the function name if the instruction is a load from the address
+ // stored in the TR register.
+ COMPARE(ldr(x0, MemOperand(x19, 8)), "ldr x0, \\[tr, #8\\] ; thin_lock_thread_id");
+}
+
+TEST_F(ArtDisassemblerTest, UnconditionalBranchNoAppendVisit) {
+ SetupAssembly(0xffffffffffffffff);
+
+ vixl::aarch64::Label destination;
+ masm.Bind(&destination);
+
+ IMPLANT(ldr(x16, MemOperand(x18, 0)));
+
+ // Test that we do not append anything for ineligible instruction.
+ COMPARE(bl(&destination),
+ "bl #-0x4 \\(addr -?0x[0-9a-f]+\\)$");
+}
+
+TEST_F(ArtDisassemblerTest, UnconditionalBranchVisit) {
+ SetupAssembly(0xffffffffffffffff);
+
+ vixl::aarch64::Label destination;
+ masm.Bind(&destination);
+
+ IMPLANT(ldr(x16, MemOperand(x19, 0)));
+ IMPLANT(br(x16));
+
+ // Test that we do append the function name if the instruction is a branch
+ // to a load that reads data from the address in the TR register, into the IPO register
+ // followed by a BR branching using the IPO register.
+ COMPARE(bl(&destination),
+ "bl #-0x8 \\(addr -?0x[0-9a-f]+\\) ; state_and_flags");
+}
+
+
+} // namespace arm64
+} // namespace art
diff --git a/libartbase/Android.bp b/libartbase/Android.bp
index f937523..5d78a36 100644
--- a/libartbase/Android.bp
+++ b/libartbase/Android.bp
@@ -67,9 +67,11 @@
"base/mem_map_unix.cc",
],
static_libs: [
+ "libcap",
// ZipArchive support, the order matters here to get all symbols.
"libziparchive",
],
+ whole_static_libs: ["libtinyxml2"],
shared_libs: [
"libz",
"liblog",
@@ -79,6 +81,9 @@
"libbase",
],
export_shared_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h.
+ export_static_lib_headers: [
+ "libcap",
+ ],
},
not_windows: {
srcs: [
@@ -88,6 +93,7 @@
static: {
cflags: ["-DART_STATIC_LIBARTBASE"],
},
+ whole_static_libs: ["libtinyxml2"],
shared_libs: [
"libziparchive",
"libz",
@@ -99,6 +105,14 @@
],
export_shared_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h.
},
+ linux_glibc: {
+ static_libs: [
+ "libcap",
+ ],
+ export_static_lib_headers: [
+ "libcap",
+ ],
+ },
windows: {
srcs: [
"base/mem_map_windows.cc",
@@ -112,6 +126,7 @@
// For common macros.
"libbase",
],
+ whole_static_libs: ["libtinyxml2"],
export_static_lib_headers: ["libbase"], // ART's macros.h depends on libbase's macros.h.
cflags: ["-Wno-thread-safety"],
@@ -134,6 +149,18 @@
"libz",
"libziparchive",
],
+ target: {
+ android: {
+ whole_static_libs: [
+ "libcap",
+ ],
+ },
+ linux_glibc: {
+ whole_static_libs: [
+ "libcap",
+ ],
+ },
+ },
}
cc_defaults {
@@ -228,7 +255,7 @@
],
shared_libs: [
"libbase",
- "libbacktrace",
+ "libunwindstack",
],
header_libs: [
"libnativehelper_header_only",
@@ -236,11 +263,13 @@
static: {
whole_static_libs: [
"libc++fs",
+ "libcap",
],
},
shared: {
static_libs: [
"libc++fs",
+ "libcap",
],
},
}
@@ -391,6 +420,8 @@
shared_libs: ["libbase"],
export_shared_lib_headers: ["libbase"],
+ whole_static_libs: ["libtinyxml2"],
+
apex_available: [
"com.android.art",
"com.android.art.debug",
diff --git a/libartbase/arch/instruction_set.cc b/libartbase/arch/instruction_set.cc
index 9ec66fe..10a9919 100644
--- a/libartbase/arch/instruction_set.cc
+++ b/libartbase/arch/instruction_set.cc
@@ -17,6 +17,8 @@
#include "instruction_set.h"
#include "android-base/logging.h"
+#include "android-base/properties.h"
+#include "android-base/stringprintf.h"
#include "base/bit_utils.h"
#include "base/globals.h"
@@ -91,6 +93,45 @@
UNREACHABLE();
}
+std::vector<InstructionSet> GetSupportedInstructionSets(std::string* error_msg) {
+ std::string zygote_kinds = android::base::GetProperty("ro.zygote", {});
+ if (zygote_kinds.empty()) {
+ *error_msg = "Unable to get Zygote kinds";
+ return {};
+ }
+
+ switch (kRuntimeISA) {
+ case InstructionSet::kArm:
+ case InstructionSet::kArm64:
+ if (zygote_kinds == "zygote64_32" || zygote_kinds == "zygote32_64") {
+ return {InstructionSet::kArm64, InstructionSet::kArm};
+ } else if (zygote_kinds == "zygote64") {
+ return {InstructionSet::kArm64};
+ } else if (zygote_kinds == "zygote32") {
+ return {InstructionSet::kArm};
+ } else {
+ *error_msg = android::base::StringPrintf("Unknown Zygote kinds '%s'", zygote_kinds.c_str());
+ return {};
+ }
+ case InstructionSet::kX86:
+ case InstructionSet::kX86_64:
+ if (zygote_kinds == "zygote64_32" || zygote_kinds == "zygote32_64") {
+ return {InstructionSet::kX86_64, InstructionSet::kX86};
+ } else if (zygote_kinds == "zygote64") {
+ return {InstructionSet::kX86_64};
+ } else if (zygote_kinds == "zygote32") {
+ return {InstructionSet::kX86};
+ } else {
+ *error_msg = android::base::StringPrintf("Unknown Zygote kinds '%s'", zygote_kinds.c_str());
+ return {};
+ }
+ default:
+ *error_msg = android::base::StringPrintf("Unknown runtime ISA '%s'",
+ GetInstructionSetString(kRuntimeISA));
+ return {};
+ }
+}
+
namespace instruction_set_details {
static_assert(IsAligned<kPageSize>(kArmStackOverflowReservedBytes), "ARM gap not page aligned");
diff --git a/libartbase/arch/instruction_set.h b/libartbase/arch/instruction_set.h
index faf881d..155b186 100644
--- a/libartbase/arch/instruction_set.h
+++ b/libartbase/arch/instruction_set.h
@@ -19,6 +19,7 @@
#include <iosfwd>
#include <string>
+#include <vector>
#include "base/enums.h"
#include "base/macros.h"
@@ -195,6 +196,9 @@
InstructionSetAbort(isa);
}
+// Returns the instruction sets supported by the device, or an empty list on failure.
+std::vector<InstructionSet> GetSupportedInstructionSets(std::string* error_msg);
+
namespace instruction_set_details {
#if !defined(ART_STACK_OVERFLOW_GAP_arm) || !defined(ART_STACK_OVERFLOW_GAP_arm64) || \
diff --git a/libartbase/base/arena_allocator.h b/libartbase/base/arena_allocator.h
index 12a44d5..bf7d932 100644
--- a/libartbase/base/arena_allocator.h
+++ b/libartbase/base/arena_allocator.h
@@ -289,7 +289,7 @@
return AllocWithMemoryToolAlign16(bytes, kind);
}
uintptr_t padding =
- ((reinterpret_cast<uintptr_t>(ptr_) + 15u) & 15u) - reinterpret_cast<uintptr_t>(ptr_);
+ RoundUp(reinterpret_cast<uintptr_t>(ptr_), 16) - reinterpret_cast<uintptr_t>(ptr_);
ArenaAllocatorStats::RecordAlloc(bytes, kind);
if (UNLIKELY(padding + bytes > static_cast<size_t>(end_ - ptr_))) {
static_assert(kArenaAlignment >= 16, "Expecting sufficient alignment for new Arena.");
diff --git a/libartbase/base/array_slice.h b/libartbase/base/array_slice.h
index a58ff44..067d9f2 100644
--- a/libartbase/base/array_slice.h
+++ b/libartbase/base/array_slice.h
@@ -65,9 +65,9 @@
lpa != nullptr ? lpa->size() : 0,
element_size) {}
ArraySlice(const ArraySlice<T>&) = default;
- ArraySlice(ArraySlice<T>&&) = default;
+ ArraySlice(ArraySlice<T>&&) noexcept = default;
ArraySlice<T>& operator=(const ArraySlice<T>&) = default;
- ArraySlice<T>& operator=(ArraySlice<T>&&) = default;
+ ArraySlice<T>& operator=(ArraySlice<T>&&) noexcept = default;
// Iterators.
iterator begin() { return iterator(&AtUnchecked(0), element_size_); }
diff --git a/libartbase/base/bit_memory_region.h b/libartbase/base/bit_memory_region.h
index c5224a5..baac2f5 100644
--- a/libartbase/base/bit_memory_region.h
+++ b/libartbase/base/bit_memory_region.h
@@ -324,8 +324,37 @@
size_t bit_size_ = 0;
};
-constexpr uint32_t kVarintBits = 4; // Minimum number of bits used for varint.
-constexpr uint32_t kVarintMax = 11; // Maximum value which is stored "inline".
+// Minimum number of bits used for varint. A varint represents either a value stored "inline" or
+// the number of bytes that are required to encode the value.
+constexpr uint32_t kVarintBits = 4;
+// Maximum value which is stored "inline". We use the rest of the values to encode the number of
+// bytes required to encode the value when the value is greater than kVarintMax.
+// We encode any value less than or equal to 11 inline. We use 12, 13, 14 and 15
+// to represent that the value is encoded in 1, 2, 3 and 4 bytes respectively.
+//
+// For example if we want to encode 1, 15, 16, 7, 11, 256:
+//
+// Low numbers (1, 7, 11) are encoded inline. 15 and 12 are set with 12 to show
+// we need to load one byte for each to have their real values (15 and 12), and
+// 256 is set with 13 to show we need to load two bytes. This is done to
+// compress the values in the bit array and keep the size down. Where the actual value
+// is read from depends on the use case.
+//
+// Values greater than kVarintMax could be encoded as a separate list referred
+// to as InterleavedVarints (see ReadInterleavedVarints / WriteInterleavedVarints).
+// This is used when there are fixed number of fields like CodeInfo headers.
+// In our example the interleaved encoding looks like below:
+//
+// Meaning: 1--- 15-- 12-- 7--- 11-- 256- 15------- 12------- 256----------------
+// Bits: 0001 1100 1100 0111 1011 1101 0000 1111 0000 1100 0000 0001 0000 0000
+//
+// In other cases the value is recorded just following the size encoding. This is
+// referred as consecutive encoding (See ReadVarint / WriteVarint). In our
+// example the consecutively encoded varints looks like below:
+//
+// Meaning: 1--- 15-- 15------- 12-- 12------- 7--- 11-- 256- 256----------------
+// Bits: 0001 1100 0000 1100 1100 0000 1100 0111 1011 1101 0000 0001 0000 0000
+constexpr uint32_t kVarintMax = 11;
class BitMemoryReader {
public:
diff --git a/libartbase/base/casts.h b/libartbase/base/casts.h
index c88f589..70a7035 100644
--- a/libartbase/base/casts.h
+++ b/libartbase/base/casts.h
@@ -57,17 +57,7 @@
// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because
// how do you know the pointer is really of type SubclassOfFoo? It
// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus,
-// when you downcast, you should use this macro. In debug mode, we
-// use dynamic_cast<> to double-check the downcast is legal (we die
-// if it's not). In normal mode, we do the efficient static_cast<>
-// instead. Thus, it's important to test in debug mode to make sure
-// the cast is legal!
-// This is the only place in the code we should use dynamic_cast<>.
-// In particular, you SHOULDN'T be using dynamic_cast<> in order to
-// do RTTI (eg code like this:
-// if (dynamic_cast<Subclass1>(foo)) HandleASubclass1Object(foo);
-// if (dynamic_cast<Subclass2>(foo)) HandleASubclass2Object(foo);
-// You should design the code some other way not to need this.
+// when you downcast, you should use this macro.
template<typename To, typename From> // use like this: down_cast<T*>(foo);
inline To down_cast(From* f) { // so we only accept pointers
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index c74f3de..c55954e 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -22,14 +22,17 @@
#include <ftw.h>
#include <libgen.h>
#include <stdlib.h>
+#include <sys/capability.h>
#include <unistd.h>
#include <cstdio>
#include <filesystem>
+#include <functional>
#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/process.h"
+#include "android-base/scopeguard.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "android-base/unique_fd.h"
@@ -41,6 +44,7 @@
#include "base/mutex.h"
#include "base/os.h"
#include "base/runtime_debug.h"
+#include "base/scoped_cap.h"
#include "base/stl_util.h"
#include "base/string_view_cpp20.h"
#include "base/testing.h"
@@ -145,6 +149,29 @@
CHECK_EQ(0, unlink_result);
}
+// Temporarily drops all root capabilities when the test is run as root. This is a noop otherwise.
+android::base::ScopeGuard<std::function<void()>> ScopedUnroot() {
+ ScopedCap old_cap(cap_get_proc());
+ CHECK_NE(old_cap.Get(), nullptr);
+ ScopedCap new_cap(cap_dup(old_cap.Get()));
+ CHECK_NE(new_cap.Get(), nullptr);
+ CHECK_EQ(cap_clear_flag(new_cap.Get(), CAP_EFFECTIVE), 0);
+ CHECK_EQ(cap_set_proc(new_cap.Get()), 0);
+ // `old_cap` is actually not shared with anyone else, but we have to wrap it with a `shared_ptr`
+ // because `std::function` requires captures to be copyable.
+ return android::base::make_scope_guard(
+ [old_cap = std::make_shared<ScopedCap>(std::move(old_cap))]() {
+ CHECK_EQ(cap_set_proc(old_cap->Get()), 0);
+ });
+}
+
+// Temporarily drops write permission on a file/directory.
+android::base::ScopeGuard<std::function<void()>> ScopedInaccessible(const std::string& path) {
+ std::filesystem::perms old_perms = std::filesystem::status(path).permissions();
+ std::filesystem::permissions(path, std::filesystem::perms::none);
+ return android::base::make_scope_guard([=]() { std::filesystem::permissions(path, old_perms); });
+}
+
std::string CommonArtTestImpl::GetAndroidBuildTop() {
CHECK(IsHost());
std::string android_build_top;
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index 6124ed9..fccb217 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -25,6 +25,7 @@
#include <vector>
#include "android-base/logging.h"
+#include "android-base/scopeguard.h"
#include "base/file_utils.h"
#include "base/globals.h"
#include "base/memory_tool.h"
@@ -122,6 +123,12 @@
DISALLOW_COPY_AND_ASSIGN(ScopedUnsetEnvironmentVariable);
};
+// Temporarily drops all root capabilities when the test is run as root. This is a noop otherwise.
+android::base::ScopeGuard<std::function<void()>> ScopedUnroot();
+
+// Temporarily drops all permissions on a file/directory.
+android::base::ScopeGuard<std::function<void()>> ScopedInaccessible(const std::string& path);
+
class CommonArtTestImpl {
public:
CommonArtTestImpl() = default;
diff --git a/libartbase/base/data_hash.h b/libartbase/base/data_hash.h
index 3399899..ccb8736 100644
--- a/libartbase/base/data_hash.h
+++ b/libartbase/base/data_hash.h
@@ -44,7 +44,7 @@
uint32_t hash = Murmur3Start();
const size_t nblocks = length_in_bytes / 4;
- typedef __attribute__((__aligned__(1))) uint32_t unaligned_uint32_t;
+ using unaligned_uint32_t __attribute__((__aligned__(1))) = uint32_t;
const unaligned_uint32_t* blocks = reinterpret_cast<const unaligned_uint32_t*>(data);
for (size_t i = 0; i != nblocks; ++i) {
hash = Murmur3Update(hash, blocks[i]);
diff --git a/libartbase/base/file_utils.cc b/libartbase/base/file_utils.cc
index 0b670e7..3c3583d 100644
--- a/libartbase/base/file_utils.cc
+++ b/libartbase/base/file_utils.cc
@@ -365,6 +365,12 @@
return GetDefaultBootImageLocation(android_root, /*deny_art_apex_data_files=*/false);
}
+std::string GetJitZygoteBootImageLocation() {
+ // Intentionally use a non-existing location so that the runtime will fail to find the boot image
+ // and JIT bootclasspath with the given profiles.
+ return "/nonx/boot.art!/apex/com.android.art/etc/boot-image.prof!/system/etc/boot-image.prof";
+}
+
static /*constinit*/ std::string_view dalvik_cache_sub_dir = "dalvik-cache";
void OverrideDalvikCacheSubDirectory(std::string sub_dir) {
diff --git a/libartbase/base/file_utils.h b/libartbase/base/file_utils.h
index c1c45bc..b9d999f 100644
--- a/libartbase/base/file_utils.h
+++ b/libartbase/base/file_utils.h
@@ -81,6 +81,9 @@
std::string GetDefaultBootImageLocation(const std::string& android_root,
bool deny_art_apex_data_files);
+// Returns the boot image location that forces the runtime to run in JIT Zygote mode.
+std::string GetJitZygoteBootImageLocation();
+
// Allows the name to be used for the dalvik cache directory (normally "dalvik-cache") to be
// overridden with a new value.
void OverrideDalvikCacheSubDirectory(std::string sub_dir);
diff --git a/libartbase/base/flags.h b/libartbase/base/flags.h
index d1e1ca6..4c38fba 100644
--- a/libartbase/base/flags.h
+++ b/libartbase/base/flags.h
@@ -236,8 +236,8 @@
//
// Flag<int> WriteMetricsToLog{"my-feature-test.flag", 42, FlagType::kDeviceConfig};
//
-// This creates a boolean flag that can be read through gFlags.WriteMetricsToLog(). The default
-// value is false. Note that the default value can be left unspecified, in which the value of the
+// This creates an integer flag that can be read through gFlags.WriteMetricsToLog(). The default
+// value is 42. Note that the default value can be left unspecified, in which case the value of the
// type's default constructor will be used.
//
// The flag can be set through the following generated means:
@@ -304,6 +304,12 @@
// Note that the actual write is still controlled by
// MetricsReportingMods and MetricsReportingNumMods.
Flag<std::string> MetricsWriteToFile{"metrics.write-to-file", "", FlagType::kCmdlineOnly};
+
+ // The output format for metrics. This is only used
+ // when writing metrics to a file; metrics written
+ // to logcat will be in human-readable text format.
+ // Supported values are "text" and "xml".
+ Flag<std::string> MetricsFormat{"metrics.format", "text", FlagType::kCmdlineOnly};
};
// This is the actual instance of all the flags.
diff --git a/libartbase/base/macros.h b/libartbase/base/macros.h
index eec73cb..13e87d7 100644
--- a/libartbase/base/macros.h
+++ b/libartbase/base/macros.h
@@ -75,6 +75,8 @@
#define FLATTEN __attribute__ ((flatten))
#endif
+#define NO_STACK_PROTECTOR __attribute__ ((no_stack_protector))
+
// clang doesn't like attributes on lambda functions. It would be nice to say:
// #define ALWAYS_INLINE_LAMBDA ALWAYS_INLINE
#define ALWAYS_INLINE_LAMBDA
diff --git a/libartbase/base/mem_map_windows.cc b/libartbase/base/mem_map_windows.cc
index 84e14ea..dfa75a1 100644
--- a/libartbase/base/mem_map_windows.cc
+++ b/libartbase/base/mem_map_windows.cc
@@ -31,7 +31,6 @@
namespace art {
-using android::base::MappedFile;
using android::base::StringPrintf;
static off_t allocation_granularity;
diff --git a/libartbase/base/metrics/metrics.h b/libartbase/base/metrics/metrics.h
index d6f2463..6d856da 100644
--- a/libartbase/base/metrics/metrics.h
+++ b/libartbase/base/metrics/metrics.h
@@ -30,6 +30,7 @@
#include "android-base/logging.h"
#include "base/bit_utils.h"
#include "base/time_utils.h"
+#include "tinyxml2.h"
#pragma clang diagnostic push
#pragma clang diagnostic error "-Wconversion"
@@ -55,7 +56,15 @@
METRIC(YoungGcThroughput, MetricsHistogram, 15, 0, 10'000) \
METRIC(FullGcThroughput, MetricsHistogram, 15, 0, 10'000) \
METRIC(YoungGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
- METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000)
+ METRIC(FullGcTracingThroughput, MetricsHistogram, 15, 0, 10'000) \
+ METRIC(GcWorldStopTime, MetricsCounter) \
+ METRIC(GcWorldStopCount, MetricsCounter) \
+ METRIC(YoungGcScannedBytes, MetricsCounter) \
+ METRIC(YoungGcFreedBytes, MetricsCounter) \
+ METRIC(YoungGcDuration, MetricsCounter) \
+ METRIC(FullGcScannedBytes, MetricsCounter) \
+ METRIC(FullGcFreedBytes, MetricsCounter) \
+ METRIC(FullGcDuration, MetricsCounter)
// A lot of the metrics implementation code is generated by passing one-off macros into ART_COUNTERS
// and ART_HISTOGRAMS. This means metrics.h and metrics.cc are very #define-heavy, which can be
@@ -115,7 +124,7 @@
#define REASON_NAME(kind, kind_name) \
case CompilationReason::kind: return kind_name;
#define REASON_FROM_NAME(kind, kind_name) \
- if (name == kind_name) { return CompilationReason::kind; }
+ if (name == (kind_name)) { return CompilationReason::kind; }
constexpr const char* CompilationReasonName(CompilationReason reason) {
switch (reason) {
@@ -129,7 +138,7 @@
}
#undef REASON_NAME
-#undef ReasonFromName
+#undef REASON_FROM_NAME
#define COMPILER_FILTER_REPORTING_LIST(V) \
V(kError, "error") /* Error (invalid value) condition */ \
@@ -156,7 +165,7 @@
#define FILTER_NAME(kind, kind_name) \
case CompilerFilterReporting::kind: return kind_name;
#define FILTER_FROM_NAME(kind, kind_name) \
- if (name == kind_name) { return CompilerFilterReporting::kind; }
+ if (name == (kind_name)) { return CompilerFilterReporting::kind; }
constexpr const char* CompilerFilterReportingName(CompilerFilterReporting filter) {
switch (filter) {
@@ -433,12 +442,80 @@
friend class ArtMetrics;
};
-// A backend that writes metrics in a human-readable format to a string.
+// Base class for formatting metrics into different formats
+// (human-readable text, JSON, etc.)
+class MetricsFormatter {
+ public:
+ virtual ~MetricsFormatter() = default;
+
+ virtual void FormatBeginReport(uint64_t timestamp_since_start_ms,
+ const std::optional<SessionData>& session_data) = 0;
+ virtual void FormatEndReport() = 0;
+ virtual void FormatReportCounter(DatumId counter_type, uint64_t value) = 0;
+ virtual void FormatReportHistogram(DatumId histogram_type,
+ int64_t low_value,
+ int64_t high_value,
+ const std::vector<uint32_t>& buckets) = 0;
+ virtual std::string GetAndResetBuffer() = 0;
+
+ protected:
+ const std::string version = "1.0";
+};
+
+// Formatter outputting metrics in human-readable text format
+class TextFormatter : public MetricsFormatter {
+ public:
+ TextFormatter() = default;
+
+ void FormatBeginReport(uint64_t timestamp_millis,
+ const std::optional<SessionData>& session_data) override;
+
+ void FormatReportCounter(DatumId counter_type, uint64_t value) override;
+
+ void FormatReportHistogram(DatumId histogram_type,
+ int64_t low_value,
+ int64_t high_value,
+ const std::vector<uint32_t>& buckets) override;
+
+ void FormatEndReport() override;
+
+ std::string GetAndResetBuffer() override;
+
+ private:
+ std::ostringstream os_;
+};
+
+// Formatter outputting metrics in XML format
+class XmlFormatter : public MetricsFormatter {
+ public:
+ XmlFormatter() = default;
+
+ void FormatBeginReport(uint64_t timestamp_millis,
+ const std::optional<SessionData>& session_data) override;
+
+ void FormatReportCounter(DatumId counter_type, uint64_t value) override;
+
+ void FormatReportHistogram(DatumId histogram_type,
+ int64_t low_value,
+ int64_t high_value,
+ const std::vector<uint32_t>& buckets) override;
+
+ void FormatEndReport() override;
+
+ std::string GetAndResetBuffer() override;
+
+ private:
+ tinyxml2::XMLDocument document_;
+};
+
+// A backend that writes metrics to a string.
+// The format of the metrics' output is delegated
+// to the MetricsFormatter class.
//
// This is used as a base for LogBackend and FileBackend.
class StringBackend : public MetricsBackend {
public:
- StringBackend();
+ explicit StringBackend(std::unique_ptr<MetricsFormatter> formatter);
void BeginOrUpdateSession(const SessionData& session_data) override;
@@ -456,14 +533,15 @@
std::string GetAndResetBuffer();
private:
- std::ostringstream os_;
+ std::unique_ptr<MetricsFormatter> formatter_;
std::optional<SessionData> session_data_;
};
// A backend that writes metrics in human-readable format to the log (i.e. logcat).
class LogBackend : public StringBackend {
public:
- explicit LogBackend(android::base::LogSeverity level);
+ explicit LogBackend(std::unique_ptr<MetricsFormatter> formatter,
+ android::base::LogSeverity level);
void BeginReport(uint64_t timestamp_millis) override;
void EndReport() override;
@@ -473,12 +551,10 @@
};
// A backend that writes metrics to a file.
-//
-// These are currently written in the same human-readable format used by StringBackend and
-// LogBackend, but we will probably want a more machine-readable format in the future.
class FileBackend : public StringBackend {
public:
- explicit FileBackend(const std::string& filename);
+ explicit FileBackend(std::unique_ptr<MetricsFormatter> formatter,
+ const std::string& filename);
void BeginReport(uint64_t timestamp_millis) override;
void EndReport() override;
diff --git a/libartbase/base/metrics/metrics_common.cc b/libartbase/base/metrics/metrics_common.cc
index f09987b..025f5eb 100644
--- a/libartbase/base/metrics/metrics_common.cc
+++ b/libartbase/base/metrics/metrics_common.cc
@@ -76,7 +76,7 @@
}
void ArtMetrics::DumpForSigQuit(std::ostream& os) const {
- StringBackend backend;
+ StringBackend backend(std::make_unique<TextFormatter>());
ReportAllMetrics(&backend);
os << backend.GetAndResetBuffer();
}
@@ -88,13 +88,12 @@
#undef ART_METRIC
}
-StringBackend::StringBackend() {}
+StringBackend::StringBackend(std::unique_ptr<MetricsFormatter> formatter)
+ : formatter_(std::move(formatter))
+{}
std::string StringBackend::GetAndResetBuffer() {
- std::string result = os_.str();
- os_.clear();
- os_.str("");
- return result;
+ return formatter_->GetAndResetBuffer();
}
void StringBackend::BeginOrUpdateSession(const SessionData& session_data) {
@@ -102,32 +101,51 @@
}
void StringBackend::BeginReport(uint64_t timestamp_since_start_ms) {
- os_ << "\n*** ART internal metrics ***\n";
- os_ << " Metadata:\n";
- os_ << " timestamp_since_start_ms: " << timestamp_since_start_ms << "\n";
- if (session_data_.has_value()) {
- os_ << " session_id: " << session_data_->session_id << "\n";
- os_ << " uid: " << session_data_->uid << "\n";
- os_ << " compilation_reason: " << CompilationReasonName(session_data_->compilation_reason)
- << "\n";
- os_ << " compiler_filter: " << CompilerFilterReportingName(session_data_->compiler_filter)
- << "\n";
- }
- os_ << " Metrics:\n";
+ formatter_->FormatBeginReport(timestamp_since_start_ms, session_data_);
}
-void StringBackend::EndReport() { os_ << "*** Done dumping ART internal metrics ***\n"; }
+void StringBackend::EndReport() {
+ formatter_->FormatEndReport();
+}
void StringBackend::ReportCounter(DatumId counter_type, uint64_t value) {
- os_ << " " << DatumName(counter_type) << ": count = " << value << "\n";
+ formatter_->FormatReportCounter(counter_type, value);
}
void StringBackend::ReportHistogram(DatumId histogram_type,
int64_t minimum_value_,
int64_t maximum_value_,
const std::vector<uint32_t>& buckets) {
- os_ << " " << DatumName(histogram_type) << ": range = " << minimum_value_ << "..." << maximum_value_;
- if (buckets.size() > 0) {
+ formatter_->FormatReportHistogram(histogram_type, minimum_value_, maximum_value_, buckets);
+}
+
+void TextFormatter::FormatBeginReport(uint64_t timestamp_since_start_ms,
+ const std::optional<SessionData>& session_data) {
+ os_ << "\n*** ART internal metrics ***\n";
+ os_ << " Metadata:\n";
+ os_ << " timestamp_since_start_ms: " << timestamp_since_start_ms << "\n";
+ if (session_data.has_value()) {
+ os_ << " session_id: " << session_data->session_id << "\n";
+ os_ << " uid: " << session_data->uid << "\n";
+ os_ << " compilation_reason: " << CompilationReasonName(session_data->compilation_reason)
+ << "\n";
+ os_ << " compiler_filter: " << CompilerFilterReportingName(session_data->compiler_filter)
+ << "\n";
+ }
+ os_ << " Metrics:\n";
+}
+
+void TextFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) {
+ os_ << " " << DatumName(counter_type) << ": count = " << value << "\n";
+}
+
+void TextFormatter::FormatReportHistogram(DatumId histogram_type,
+ int64_t minimum_value_,
+ int64_t maximum_value_,
+ const std::vector<uint32_t>& buckets) {
+ os_ << " " << DatumName(histogram_type) << ": range = "
+ << minimum_value_ << "..." << maximum_value_;
+ if (!buckets.empty()) {
os_ << ", buckets: ";
bool first = true;
for (const auto& count : buckets) {
@@ -143,22 +161,100 @@
}
}
-LogBackend::LogBackend(android::base::LogSeverity level) : level_{level} {}
+void TextFormatter::FormatEndReport() {
+ os_ << "*** Done dumping ART internal metrics ***\n";
+}
+
+std::string TextFormatter::GetAndResetBuffer() {
+ std::string result = os_.str();
+ os_.clear();
+ os_.str("");
+ return result;
+}
+
+void XmlFormatter::FormatBeginReport(uint64_t timestamp_millis,
+ const std::optional<SessionData>& session_data) {
+ tinyxml2::XMLElement* art_runtime_metrics = document_.NewElement("art_runtime_metrics");
+ document_.InsertEndChild(art_runtime_metrics);
+
+ art_runtime_metrics->InsertNewChildElement("version")->SetText(version.data());
+
+ tinyxml2::XMLElement* metadata = art_runtime_metrics->InsertNewChildElement("metadata");
+ metadata->InsertNewChildElement("timestamp_since_start_ms")->SetText(timestamp_millis);
+
+ if (session_data.has_value()) {
+ metadata->InsertNewChildElement("session_id")->SetText(session_data->session_id);
+ metadata->InsertNewChildElement("uid")->SetText(session_data->uid);
+ metadata
+ ->InsertNewChildElement("compilation_reason")
+ ->SetText(CompilationReasonName(session_data->compilation_reason));
+ metadata
+ ->InsertNewChildElement("compiler_filter")
+ ->SetText(CompilerFilterReportingName(session_data->compiler_filter));
+ }
+
+ art_runtime_metrics->InsertNewChildElement("metrics");
+}
+
+void XmlFormatter::FormatReportCounter(DatumId counter_type, uint64_t value) {
+ tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics");
+
+ tinyxml2::XMLElement* counter = metrics->InsertNewChildElement(DatumName(counter_type).data());
+ counter->InsertNewChildElement("counter_type")->SetText("count");
+ counter->InsertNewChildElement("value")->SetText(value);
+}
+
+void XmlFormatter::FormatReportHistogram(DatumId histogram_type,
+ int64_t low_value,
+ int64_t high_value,
+ const std::vector<uint32_t>& buckets) {
+ tinyxml2::XMLElement* metrics = document_.RootElement()->FirstChildElement("metrics");
+
+ tinyxml2::XMLElement* histogram =
+ metrics->InsertNewChildElement(DatumName(histogram_type).data());
+ histogram->InsertNewChildElement("counter_type")->SetText("histogram");
+ histogram->InsertNewChildElement("minimum_value")->SetText(low_value);
+ histogram->InsertNewChildElement("maximum_value")->SetText(high_value);
+
+ tinyxml2::XMLElement* buckets_element = histogram->InsertNewChildElement("buckets");
+ for (const auto& count : buckets) {
+ buckets_element->InsertNewChildElement("bucket")->SetText(count);
+ }
+}
+
+void XmlFormatter::FormatEndReport() {}
+
+std::string XmlFormatter::GetAndResetBuffer() {
+ tinyxml2::XMLPrinter printer(/*file=*/nullptr, /*compact=*/true);
+ document_.Print(&printer);
+ std::string result = printer.CStr();
+ document_.Clear();
+
+ return result;
+}
+
+LogBackend::LogBackend(std::unique_ptr<MetricsFormatter> formatter,
+ android::base::LogSeverity level)
+ : StringBackend{std::move(formatter)}, level_{level}
+{}
void LogBackend::BeginReport(uint64_t timestamp_since_start_ms) {
- GetAndResetBuffer();
+ StringBackend::GetAndResetBuffer();
StringBackend::BeginReport(timestamp_since_start_ms);
}
void LogBackend::EndReport() {
StringBackend::EndReport();
- LOG_STREAM(level_) << GetAndResetBuffer();
+ LOG_STREAM(level_) << StringBackend::GetAndResetBuffer();
}
-FileBackend::FileBackend(const std::string& filename) : filename_{filename} {}
+FileBackend::FileBackend(std::unique_ptr<MetricsFormatter> formatter,
+ const std::string& filename)
+ : StringBackend{std::move(formatter)}, filename_{filename}
+{}
void FileBackend::BeginReport(uint64_t timestamp_since_start_ms) {
- GetAndResetBuffer();
+ StringBackend::GetAndResetBuffer();
StringBackend::BeginReport(timestamp_since_start_ms);
}
@@ -170,7 +266,7 @@
if (file.get() == nullptr) {
LOG(WARNING) << "Could open metrics file '" << filename_ << "': " << error_message;
} else {
- if (!android::base::WriteStringToFd(GetAndResetBuffer(), file.get()->Fd())) {
+ if (!android::base::WriteStringToFd(StringBackend::GetAndResetBuffer(), file.get()->Fd())) {
PLOG(WARNING) << "Error writing metrics to file";
}
}
diff --git a/libartbase/base/metrics/metrics_test.cc b/libartbase/base/metrics/metrics_test.cc
index ed6f88d..fd7124b 100644
--- a/libartbase/base/metrics/metrics_test.cc
+++ b/libartbase/base/metrics/metrics_test.cc
@@ -16,6 +16,7 @@
#include "metrics.h"
+#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "metrics_test.h"
@@ -248,7 +249,7 @@
// Makes sure all defined metrics are included when dumping through StreamBackend.
TEST_F(MetricsTest, StreamBackendDumpAllMetrics) {
ArtMetrics metrics;
- StringBackend backend;
+ StringBackend backend(std::make_unique<TextFormatter>());
metrics.ReportAllMetrics(&backend);
@@ -305,6 +306,266 @@
metrics.ReportAllMetrics(&zero_backend);
}
+TEST(TextFormatterTest, ReportMetrics_WithBuckets) {
+ TextFormatter text_formatter;
+ SessionData session_data {
+ .session_id = 1000,
+ .uid = 50,
+ .compilation_reason = CompilationReason::kInstall,
+ .compiler_filter = CompilerFilterReporting::kSpeed,
+ };
+
+ text_formatter.FormatBeginReport(200, session_data);
+ text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u);
+ text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime,
+ 50,
+ 200,
+ {2, 4, 7, 1});
+ text_formatter.FormatEndReport();
+
+ const std::string result = text_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "\n*** ART internal metrics ***\n"
+ " Metadata:\n"
+ " timestamp_since_start_ms: 200\n"
+ " session_id: 1000\n"
+ " uid: 50\n"
+ " compilation_reason: install\n"
+ " compiler_filter: speed\n"
+ " Metrics:\n"
+ " FullGcCount: count = 1\n"
+ " FullGcCollectionTime: range = 50...200, buckets: 2,4,7,1\n"
+ "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(TextFormatterTest, ReportMetrics_NoBuckets) {
+ TextFormatter text_formatter;
+ SessionData session_data {
+ .session_id = 500,
+ .uid = 15,
+ .compilation_reason = CompilationReason::kCmdLine,
+ .compiler_filter = CompilerFilterReporting::kExtract,
+ };
+
+ text_formatter.FormatBeginReport(400, session_data);
+ text_formatter.FormatReportHistogram(DatumId::kFullGcCollectionTime, 10, 20, {});
+ text_formatter.FormatEndReport();
+
+ std::string result = text_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "\n*** ART internal metrics ***\n"
+ " Metadata:\n"
+ " timestamp_since_start_ms: 400\n"
+ " session_id: 500\n"
+ " uid: 15\n"
+ " compilation_reason: cmdline\n"
+ " compiler_filter: extract\n"
+ " Metrics:\n"
+ " FullGcCollectionTime: range = 10...20, no buckets\n"
+ "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(TextFormatterTest, BeginReport_NoSessionData) {
+ TextFormatter text_formatter;
+ std::optional<SessionData> empty_session_data;
+
+ text_formatter.FormatBeginReport(100, empty_session_data);
+ text_formatter.FormatEndReport();
+
+ std::string result = text_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "\n*** ART internal metrics ***\n"
+ " Metadata:\n"
+ " timestamp_since_start_ms: 100\n"
+ " Metrics:\n"
+ "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(TextFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) {
+ TextFormatter text_formatter;
+ std::optional<SessionData> empty_session_data;
+
+ text_formatter.FormatBeginReport(200, empty_session_data);
+ text_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u);
+ text_formatter.FormatEndReport();
+
+ std::string result = text_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "\n*** ART internal metrics ***\n"
+ " Metadata:\n"
+ " timestamp_since_start_ms: 200\n"
+ " Metrics:\n"
+ " FullGcCount: count = 1\n"
+ "*** Done dumping ART internal metrics ***\n");
+
+ text_formatter.FormatBeginReport(300, empty_session_data);
+ text_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u);
+ text_formatter.FormatEndReport();
+
+ result = text_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "\n*** ART internal metrics ***\n"
+ " Metadata:\n"
+ " timestamp_since_start_ms: 300\n"
+ " Metrics:\n"
+ " FullGcCount: count = 5\n"
+ "*** Done dumping ART internal metrics ***\n");
+}
+
+TEST(XmlFormatterTest, ReportMetrics_WithBuckets) {
+ XmlFormatter xml_formatter;
+ SessionData session_data {
+ .session_id = 123,
+ .uid = 456,
+ .compilation_reason = CompilationReason::kFirstBoot,
+ .compiler_filter = CompilerFilterReporting::kSpace,
+ };
+
+ xml_formatter.FormatBeginReport(250, session_data);
+ xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u);
+ xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime,
+ 300,
+ 600,
+ {1, 5, 3});
+ xml_formatter.FormatEndReport();
+
+ const std::string result = xml_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "<art_runtime_metrics>"
+ "<version>1.0</version>"
+ "<metadata>"
+ "<timestamp_since_start_ms>250</timestamp_since_start_ms>"
+ "<session_id>123</session_id>"
+ "<uid>456</uid>"
+ "<compilation_reason>first-boot</compilation_reason>"
+ "<compiler_filter>space</compiler_filter>"
+ "</metadata>"
+ "<metrics>"
+ "<YoungGcCount>"
+ "<counter_type>count</counter_type>"
+ "<value>3</value>"
+ "</YoungGcCount>"
+ "<YoungGcCollectionTime>"
+ "<counter_type>histogram</counter_type>"
+ "<minimum_value>300</minimum_value>"
+ "<maximum_value>600</maximum_value>"
+ "<buckets>"
+ "<bucket>1</bucket>"
+ "<bucket>5</bucket>"
+ "<bucket>3</bucket>"
+ "</buckets>"
+ "</YoungGcCollectionTime>"
+ "</metrics>"
+ "</art_runtime_metrics>");
+}
+
+TEST(XmlFormatterTest, ReportMetrics_NoBuckets) {
+ XmlFormatter xml_formatter;
+ SessionData session_data {
+ .session_id = 234,
+ .uid = 345,
+ .compilation_reason = CompilationReason::kFirstBoot,
+ .compiler_filter = CompilerFilterReporting::kSpace,
+ };
+
+ xml_formatter.FormatBeginReport(160, session_data);
+ xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 4u);
+ xml_formatter.FormatReportHistogram(DatumId::kYoungGcCollectionTime, 20, 40, {});
+ xml_formatter.FormatEndReport();
+
+ const std::string result = xml_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "<art_runtime_metrics>"
+ "<version>1.0</version>"
+ "<metadata>"
+ "<timestamp_since_start_ms>160</timestamp_since_start_ms>"
+ "<session_id>234</session_id>"
+ "<uid>345</uid>"
+ "<compilation_reason>first-boot</compilation_reason>"
+ "<compiler_filter>space</compiler_filter>"
+ "</metadata>"
+ "<metrics>"
+ "<YoungGcCount>"
+ "<counter_type>count</counter_type>"
+ "<value>4</value>"
+ "</YoungGcCount>"
+ "<YoungGcCollectionTime>"
+ "<counter_type>histogram</counter_type>"
+ "<minimum_value>20</minimum_value>"
+ "<maximum_value>40</maximum_value>"
+ "<buckets/>"
+ "</YoungGcCollectionTime>"
+ "</metrics>"
+ "</art_runtime_metrics>");
+}
+
+TEST(XmlFormatterTest, BeginReport_NoSessionData) {
+ XmlFormatter xml_formatter;
+ std::optional<SessionData> empty_session_data;
+
+ xml_formatter.FormatBeginReport(100, empty_session_data);
+ xml_formatter.FormatReportCounter(DatumId::kYoungGcCount, 3u);
+ xml_formatter.FormatEndReport();
+
+ std::string result = xml_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "<art_runtime_metrics>"
+ "<version>1.0</version>"
+ "<metadata>"
+ "<timestamp_since_start_ms>100</timestamp_since_start_ms>"
+ "</metadata>"
+ "<metrics>"
+ "<YoungGcCount>"
+ "<counter_type>count</counter_type>"
+ "<value>3</value>"
+ "</YoungGcCount>"
+ "</metrics>"
+ "</art_runtime_metrics>");
+}
+
+TEST(XmlFormatterTest, GetAndResetBuffer_ActuallyResetsBuffer) {
+ XmlFormatter xml_formatter;
+ std::optional<SessionData> empty_session_data;
+
+ xml_formatter.FormatBeginReport(200, empty_session_data);
+ xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 1u);
+ xml_formatter.FormatEndReport();
+
+ std::string result = xml_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "<art_runtime_metrics>"
+ "<version>1.0</version>"
+ "<metadata>"
+ "<timestamp_since_start_ms>200</timestamp_since_start_ms>"
+ "</metadata>"
+ "<metrics>"
+ "<FullGcCount>"
+ "<counter_type>count</counter_type>"
+ "<value>1</value>"
+ "</FullGcCount>"
+ "</metrics>"
+ "</art_runtime_metrics>");
+
+ xml_formatter.FormatBeginReport(300, empty_session_data);
+ xml_formatter.FormatReportCounter(DatumId::kFullGcCount, 5u);
+ xml_formatter.FormatEndReport();
+
+ result = xml_formatter.GetAndResetBuffer();
+ ASSERT_EQ(result,
+ "<art_runtime_metrics>"
+ "<version>1.0</version>"
+ "<metadata>"
+ "<timestamp_since_start_ms>300</timestamp_since_start_ms>"
+ "</metadata>"
+ "<metrics>"
+ "<FullGcCount>"
+ "<counter_type>count</counter_type>"
+ "<value>5</value>"
+ "</FullGcCount>"
+ "</metrics>"
+ "</art_runtime_metrics>");
+}
+
TEST(CompilerFilterReportingTest, FromName) {
ASSERT_EQ(CompilerFilterReportingFromName("error"),
CompilerFilterReporting::kError);
@@ -400,6 +661,8 @@
CompilationReason::kCmdLine);
ASSERT_EQ(CompilationReasonFromName("error"),
CompilationReason::kError);
+ ASSERT_EQ(CompilationReasonFromName("vdex"),
+ CompilationReason::kVdex);
}
TEST(CompilerReason, Name) {
@@ -439,6 +702,8 @@
"cmdline");
ASSERT_EQ(CompilationReasonName(CompilationReason::kError),
"error");
+ ASSERT_EQ(CompilationReasonName(CompilationReason::kVdex),
+ "vdex");
}
} // namespace metrics
} // namespace art
diff --git a/libartbase/base/safe_copy.cc b/libartbase/base/safe_copy.cc
index ad75aa7..7b0b895 100644
--- a/libartbase/base/safe_copy.cc
+++ b/libartbase/base/safe_copy.cc
@@ -56,10 +56,10 @@
}
src_iovs[iovecs_used].iov_base = const_cast<char*>(cur);
- if (!IsAlignedParam(cur, PAGE_SIZE)) {
- src_iovs[iovecs_used].iov_len = AlignUp(cur, PAGE_SIZE) - cur;
+ if (!IsAlignedParam(cur, kPageSize)) {
+ src_iovs[iovecs_used].iov_len = AlignUp(cur, kPageSize) - cur;
} else {
- src_iovs[iovecs_used].iov_len = PAGE_SIZE;
+ src_iovs[iovecs_used].iov_len = kPageSize;
}
src_iovs[iovecs_used].iov_len = std::min(src_iovs[iovecs_used].iov_len, len);
diff --git a/libartbase/base/safe_copy_test.cc b/libartbase/base/safe_copy_test.cc
index 9f7d409..01ed7cd 100644
--- a/libartbase/base/safe_copy_test.cc
+++ b/libartbase/base/safe_copy_test.cc
@@ -31,7 +31,7 @@
#if defined(__linux__)
TEST(SafeCopyTest, smoke) {
- DCHECK_EQ(kPageSize, static_cast<decltype(kPageSize)>(PAGE_SIZE));
+ DCHECK_EQ(kPageSize, static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
// Map four pages, mark the second one as PROT_NONE, unmap the last one.
void* map = mmap(nullptr, kPageSize * 4, PROT_READ | PROT_WRITE,
@@ -79,7 +79,7 @@
}
TEST(SafeCopyTest, alignment) {
- DCHECK_EQ(kPageSize, static_cast<decltype(kPageSize)>(PAGE_SIZE));
+ DCHECK_EQ(kPageSize, static_cast<size_t>(sysconf(_SC_PAGE_SIZE)));
// Copy the middle of a mapping to the end of another one.
void* src_map = mmap(nullptr, kPageSize * 3, PROT_READ | PROT_WRITE,
diff --git a/libartbase/base/scoped_cap.h b/libartbase/base/scoped_cap.h
new file mode 100644
index 0000000..c369821
--- /dev/null
+++ b/libartbase/base/scoped_cap.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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_SCOPED_CAP_H_
+#define ART_LIBARTBASE_BASE_SCOPED_CAP_H_
+
+#include <sys/capability.h>
+
+#include <utility>
+
+#include "android-base/logging.h"
+
+namespace art {
+
+// A wrapper of `cap_t` that automatically calls `cap_free`.
+class ScopedCap {
+ public:
+ explicit ScopedCap(cap_t cap) : cap_(cap) {}
+
+ ScopedCap(const ScopedCap&) = delete;
+ ScopedCap& operator=(const ScopedCap&) = delete;
+ ScopedCap(ScopedCap&& other) noexcept : cap_(std::exchange(other.cap_, nullptr)) {}
+
+ ~ScopedCap() {
+ if (cap_ != nullptr) {
+ if (cap_free(cap_) != 0) {
+ PLOG(ERROR) << "Failed to call cap_free";
+ }
+ }
+ }
+
+ cap_t Get() const { return cap_; }
+
+ private:
+ cap_t cap_;
+};
+
+} // namespace art
+
+#endif // ART_LIBARTBASE_BASE_SCOPED_CAP_H_
diff --git a/libartbase/base/scoped_flock.cc b/libartbase/base/scoped_flock.cc
index b16a45a..3f44e25 100644
--- a/libartbase/base/scoped_flock.cc
+++ b/libartbase/base/scoped_flock.cc
@@ -27,7 +27,7 @@
namespace art {
-using android::base::StringPrintf;
+using android::base::StringPrintf; // NOLINT - StringPrintf is actually used
/* static */ ScopedFlock LockedFile::Open(const char* filename, std::string* error_msg) {
return Open(filename, O_CREAT | O_RDWR, true, error_msg);
diff --git a/libartbase/base/sdk_version.h b/libartbase/base/sdk_version.h
index 07c3c2c..d39aa95 100644
--- a/libartbase/base/sdk_version.h
+++ b/libartbase/base/sdk_version.h
@@ -36,7 +36,8 @@
kQ = 29u,
kR = 30u,
kS = 31u,
- kT = 32u,
+ kS_V2 = 32u,
+ kT = 33u,
kMax = std::numeric_limits<uint32_t>::max(),
};
diff --git a/libartbase/base/stride_iterator.h b/libartbase/base/stride_iterator.h
index 67c0d38..6a7e4be 100644
--- a/libartbase/base/stride_iterator.h
+++ b/libartbase/base/stride_iterator.h
@@ -30,9 +30,9 @@
typename std::iterator<std::random_access_iterator_tag, T>::difference_type;
StrideIterator(const StrideIterator&) = default;
- StrideIterator(StrideIterator&&) = default;
+ StrideIterator(StrideIterator&&) noexcept = default;
StrideIterator& operator=(const StrideIterator&) = default;
- StrideIterator& operator=(StrideIterator&&) = default;
+ StrideIterator& operator=(StrideIterator&&) noexcept = default;
StrideIterator(T* ptr, size_t stride)
: ptr_(reinterpret_cast<uintptr_t>(ptr)),
diff --git a/libartbase/base/utils.cc b/libartbase/base/utils.cc
index ba62f30..e0c37d8 100644
--- a/libartbase/base/utils.cc
+++ b/libartbase/base/utils.cc
@@ -50,7 +50,6 @@
#if defined(__linux__)
#include <linux/unistd.h>
#include <sys/syscall.h>
-#include <sys/utsname.h>
#endif
#if defined(_WIN32)
@@ -64,7 +63,7 @@
namespace art {
-using android::base::ReadFileToString;
+using android::base::ReadFileToString; // NOLINT - ReadFileToString is actually used
using android::base::StringPrintf;
#if defined(__arm__)
@@ -91,7 +90,7 @@
CHECK_LT(start, limit);
CHECK_EQ(RoundDown(start, kPageSize), RoundDown(limit - 1, kPageSize)) << "range spans pages";
// Declare a volatile variable so the compiler does not elide reads from the page being touched.
- volatile uint8_t v = 0;
+ [[maybe_unused]] volatile uint8_t v = 0;
for (size_t i = 0; i < attempts; ++i) {
// Touch page to maximize chance page is resident.
v = *reinterpret_cast<uint8_t*>(start);
@@ -158,6 +157,17 @@
#endif
+#if defined(__linux__)
+bool IsKernelVersionAtLeast(int reqd_major, int reqd_minor) {
+ struct utsname uts;
+ int major, minor;
+ CHECK_EQ(uname(&uts), 0);
+ CHECK_EQ(strcmp(uts.sysname, "Linux"), 0);
+ CHECK_EQ(sscanf(uts.release, "%d.%d:", &major, &minor), 2);
+ return major > reqd_major || (major == reqd_major && minor >= reqd_minor);
+}
+#endif
+
bool CacheOperationsMaySegFault() {
#if defined(__linux__) && defined(__aarch64__)
// Avoid issue on older ARM64 kernels where data cache operations could be classified as writes
@@ -167,18 +177,10 @@
//
// This behaviour means we should avoid the dual view JIT on the device. This is just
// an issue when running tests on devices that have an old kernel.
- static constexpr int kRequiredMajor = 3;
- static constexpr int kRequiredMinor = 12;
- struct utsname uts;
- int major, minor;
- if (uname(&uts) != 0 ||
- strcmp(uts.sysname, "Linux") != 0 ||
- sscanf(uts.release, "%d.%d", &major, &minor) != 2 ||
- (major < kRequiredMajor || (major == kRequiredMajor && minor < kRequiredMinor))) {
- return true;
- }
-#endif
+ return !IsKernelVersionAtLeast(3, 12);
+#else
return false;
+#endif
}
uint32_t GetTid() {
diff --git a/libartbase/base/utils.h b/libartbase/base/utils.h
index 0e8231a..90eb2da 100644
--- a/libartbase/base/utils.h
+++ b/libartbase/base/utils.h
@@ -31,6 +31,10 @@
#include "globals.h"
#include "macros.h"
+#if defined(__linux__)
+#include <sys/utsname.h>
+#endif
+
namespace art {
static inline uint32_t PointerToLowMemUInt32(const void* p) {
@@ -125,6 +129,10 @@
// Flush CPU caches. Returns true on success, false if flush failed.
WARN_UNUSED bool FlushCpuCaches(void* begin, void* end);
+#if defined(__linux__)
+bool IsKernelVersionAtLeast(int reqd_major, int reqd_minor);
+#endif
+
// On some old kernels, a cache operation may segfault.
WARN_UNUSED bool CacheOperationsMaySegFault();
diff --git a/libartpalette/apex/palette.cc b/libartpalette/apex/palette.cc
index 75a3878..9a50683 100644
--- a/libartpalette/apex/palette.cc
+++ b/libartpalette/apex/palette.cc
@@ -110,6 +110,8 @@
extern "C" {
+// Methods in version 1 API, corresponding to SDK level 31.
+
palette_status_t PaletteSchedSetPriority(int32_t tid, int32_t java_priority) {
PaletteSchedSetPriorityMethod m = PaletteLoader::Instance().GetPaletteSchedSetPriorityMethod();
return m(tid, java_priority);
@@ -218,6 +220,8 @@
return m(env);
}
+// Methods in version 2 API, corresponding to SDK level 33.
+
palette_status_t PaletteReportLockContention(JNIEnv* env,
int32_t wait_ms,
const char* filename,
diff --git a/libartpalette/include/palette/palette_method_list.h b/libartpalette/include/palette/palette_method_list.h
index 066f24f..6db389c 100644
--- a/libartpalette/include/palette/palette_method_list.h
+++ b/libartpalette/include/palette/palette_method_list.h
@@ -23,8 +23,8 @@
#include "jni.h"
-// Methods in version 1 API
#define PALETTE_METHOD_LIST(M) \
+ /* Methods in version 1 API, corresponding to SDK level 31. */ \
M(PaletteSchedSetPriority, int32_t tid, int32_t java_priority) \
M(PaletteSchedGetPriority, int32_t tid, /*out*/int32_t* java_priority) \
M(PaletteWriteCrashThreadStacks, const char* stacks, size_t stacks_len) \
@@ -53,6 +53,7 @@
M(PaletteShouldReportJniInvocations, bool*) \
M(PaletteNotifyBeginJniInvocation, JNIEnv* env) \
M(PaletteNotifyEndJniInvocation, JNIEnv* env) \
+ /* Methods in version 2 API, corresponding to SDK level 33. */ \
M(PaletteReportLockContention, JNIEnv* env, \
int32_t wait_ms, \
const char* filename, \
diff --git a/libartpalette/include/palette/palette_types.h b/libartpalette/include/palette/palette_types.h
index 905a341..3c02544 100644
--- a/libartpalette/include/palette/palette_types.h
+++ b/libartpalette/include/palette/palette_types.h
@@ -23,7 +23,7 @@
extern "C" {
#endif // __cplusplus
-typedef int32_t palette_status_t;
+using palette_status_t = int32_t;
// Palette function return value when the function completed successfully.
#define PALETTE_STATUS_OK ((palette_status_t) 0)
diff --git a/libartpalette/libartpalette.map.txt b/libartpalette/libartpalette.map.txt
index 6401010..be5d9b4 100644
--- a/libartpalette/libartpalette.map.txt
+++ b/libartpalette/libartpalette.map.txt
@@ -14,12 +14,12 @@
# limitations under the License.
#
-LIBARTPALETTE_1 {
+LIBARTPALETTE_1 { # introduced=31
global:
# --- VERSION 01 API ---
PaletteSchedSetPriority; # apex
PaletteSchedGetPriority; # apex
- PaletteWriteCrashThreadStacks; #apex
+ PaletteWriteCrashThreadStacks; # apex
PaletteTraceEnabled; # apex
PaletteTraceBegin; # apex
PaletteTraceEnd; # apex
@@ -27,16 +27,21 @@
PaletteAshmemCreateRegion; # apex
PaletteAshmemSetProtRegion; # apex
PaletteCreateOdrefreshStagingDirectory; # apex
- PaletteShouldReportDex2oatCompilation; #apex
- PaletteNotifyStartDex2oatCompilation; #apex
- PaletteNotifyEndDex2oatCompilation; #apex
- PaletteNotifyDexFileLoaded; #apex
- PaletteNotifyOatFileLoaded; #apex
- PaletteShouldReportJniInvocations; #apex
- PaletteNotifyBeginJniInvocation; #apex
- PaletteNotifyEndJniInvocation; #apex
- PaletteReportLockContention; #apex
+ PaletteShouldReportDex2oatCompilation; # apex
+ PaletteNotifyStartDex2oatCompilation; # apex
+ PaletteNotifyEndDex2oatCompilation; # apex
+ PaletteNotifyDexFileLoaded; # apex
+ PaletteNotifyOatFileLoaded; # apex
+ PaletteShouldReportJniInvocations; # apex
+ PaletteNotifyBeginJniInvocation; # apex
+ PaletteNotifyEndJniInvocation; # apex
local:
*;
};
+
+LIBARTPALETTE_2 { # introduced=33
+ global:
+ # --- VERSION 02 API ---
+ PaletteReportLockContention; # apex
+} LIBARTPALETTE_1;
diff --git a/libartpalette/system/palette_fake.cc b/libartpalette/system/palette_fake.cc
index bbf8f2d..e1e05c9 100644
--- a/libartpalette/system/palette_fake.cc
+++ b/libartpalette/system/palette_fake.cc
@@ -25,6 +25,8 @@
#include "palette_system.h"
+// Methods in version 1 API, corresponding to SDK level 31.
+
// Cached thread priority for testing. No thread priorities are ever affected.
static std::mutex g_tid_priority_map_mutex;
static std::map<int32_t, int32_t> g_tid_priority_map;
@@ -129,6 +131,8 @@
return PALETTE_STATUS_OK;
}
+// Methods in version 2 API, corresponding to SDK level 33.
+
palette_status_t PaletteReportLockContention(JNIEnv* env ATTRIBUTE_UNUSED,
int32_t wait_ms ATTRIBUTE_UNUSED,
const char* filename ATTRIBUTE_UNUSED,
diff --git a/libartservice/Android.bp b/libartservice/Android.bp
deleted file mode 100644
index 985a2eb..0000000
--- a/libartservice/Android.bp
+++ /dev/null
@@ -1,143 +0,0 @@
-// Copyright (C) 2021 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 {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "art_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["art_license"],
-}
-
-cc_library {
- // This native library contains JNI support code for the ART Service Java
- // Language library.
-
- name: "libartservice",
- defaults: ["art_defaults"],
- host_supported: true,
- srcs: [
- "service/native/service.cc",
- ],
- export_include_dirs: ["."],
- apex_available: [
- "com.android.art",
- "com.android.art.debug",
- ],
- shared_libs: [
- "libbase",
- ],
- export_shared_lib_headers: ["libbase"],
-}
-
-// Provides the API and implementation of the ART Service class that will be
-// loaded by the System Server.
-java_sdk_library {
- // This target is named 'service-art' to conform to the naming conventions
- // for JAR files in the System Server.
- name: "service-art",
- defaults: ["framework-system-server-module-defaults"],
-
- permitted_packages: ["com.android.server.art"],
-
- visibility: [
- "//art:__subpackages__",
- "//frameworks/base/services/core",
- ],
-
- impl_library_visibility: [
- "//art/libartservice/tests",
- ],
-
- stubs_library_visibility: ["//visibility:public"],
- stubs_source_visibility: ["//visibility:private"],
-
- apex_available: [
- "com.android.art",
- "com.android.art.debug",
- ],
- sdk_version: "core_platform",
- min_sdk_version: "31",
-
- public: {
- // Override the setting of "module_current" from the defaults as that is
- // not available in all manifests where this needs to be built.
- sdk_version: "core_current",
- },
-
- system_server: {
- // Override the setting of "module_current" from the defaults as that is
- // not available in all manifests where this needs to be built.
- sdk_version: "core_current",
- },
-
- // The API elements are the ones annotated with
- // libcore.api.CorePlatformApi(status=libcore.api.CorePlatformApi.Status.STABLE)
- droiddoc_options: [
- "--show-single-annotation libcore.api.CorePlatformApi\\(status=libcore.api.CorePlatformApi.Status.STABLE\\)",
- ],
-
- // Temporarily disable compatibility with previous released APIs.
- // TODO - remove once prototype has stabilized
- // running "m update-api" will give instructions on what to do next
- unsafe_ignore_missing_latest_api: true,
-
- // This cannot be accessed by apps using <uses-library> in their manifest.
- shared_library: false,
- // TODO(b/188773212): force dex compilation for inclusion in bootclasspath_fragment.
- compile_dex: true,
-
- srcs: [
- "service/java/com/android/server/art/ArtManagerLocal.java",
- ],
-
- libs: [
- "art.module.api.annotations.for.system.modules",
- ],
-
- plugins: ["java_api_finder"],
- dist_group: "android",
-}
-
-art_cc_defaults {
- name: "art_libartservice_tests_defaults",
- srcs: [
- "service/native/service_test.cc",
- ],
- shared_libs: [
- "libbase",
- "libartservice",
- ],
-}
-
-// Version of ART gtest `art_libartservice_tests` bundled with the ART APEX on target.
-// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
-art_cc_test {
- name: "art_libartservice_tests",
- defaults: [
- "art_gtest_defaults",
- "art_libartservice_tests_defaults",
- ],
-}
-
-// Standalone version of ART gtest `art_libartservice_tests`, not bundled with the ART APEX on
-// target.
-art_cc_test {
- name: "art_standalone_libartservice_tests",
- defaults: [
- "art_standalone_gtest_defaults",
- "art_libartservice_tests_defaults",
- ],
-}
diff --git a/libartservice/api/current.txt b/libartservice/api/current.txt
deleted file mode 100644
index c7844e0..0000000
--- a/libartservice/api/current.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-// Signature format: 2.0
-package com.android.server.art {
-
- public final class ArtManagerLocal {
- ctor public ArtManagerLocal();
- }
-
-}
-
diff --git a/libartservice/service/Android.bp b/libartservice/service/Android.bp
new file mode 100644
index 0000000..8805430
--- /dev/null
+++ b/libartservice/service/Android.bp
@@ -0,0 +1,138 @@
+// Copyright (C) 2021 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 {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// This native library contains JNI support code for the ART Service Java
+// Language library.
+cc_defaults {
+ name: "libartservice_defaults",
+ defaults: ["art_defaults"],
+ host_supported: true,
+ srcs: [
+ "native/service.cc",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+cc_library {
+ name: "libartservice",
+ defaults: ["libartservice_defaults"],
+ apex_available: [
+ "com.android.art",
+ "com.android.art.debug",
+ ],
+ shared_libs: [
+ ],
+}
+
+cc_library {
+ name: "libartserviced",
+ defaults: [
+ "libartservice_defaults",
+ "art_debug_defaults",
+ ],
+ apex_available: [
+ "com.android.art",
+ "com.android.art.debug",
+ ],
+ shared_libs: [
+ ],
+}
+
+// Provides the API and implementation of the ART Service class that will be
+// loaded by the System Server.
+java_sdk_library {
+ // This target is named 'service-art' to conform to the naming conventions
+ // for JAR files in the System Server.
+ name: "service-art",
+ defaults: ["framework-system-server-module-defaults"],
+ permitted_packages: ["com.android.server.art"],
+ visibility: [
+ "//art:__subpackages__",
+ "//frameworks/base/services/core",
+ ],
+ apex_available: [
+ "com.android.art",
+ "com.android.art.debug",
+ ],
+ sdk_version: "system_server_current",
+ min_sdk_version: "31",
+ srcs: [
+ "java/**/*.java",
+ ],
+ static_libs: [
+ ],
+ plugins: ["java_api_finder"],
+ jarjar_rules: "jarjar-rules.txt",
+}
+
+art_cc_defaults {
+ name: "art_libartservice_tests_defaults",
+ defaults: ["libartservice_defaults"],
+ srcs: [
+ "native/service_test.cc",
+ ],
+}
+
+// Version of ART gtest `art_libartservice_tests` bundled with the ART APEX on target.
+// TODO(b/192274705): Remove this module when the migration to standalone ART gtests is complete.
+art_cc_test {
+ name: "art_libartservice_tests",
+ defaults: [
+ "art_gtest_defaults",
+ "art_libartservice_tests_defaults",
+ ],
+}
+
+// Standalone version of ART gtest `art_libartservice_tests`, not bundled with the ART APEX on
+// target.
+art_cc_test {
+ name: "art_standalone_libartservice_tests",
+ defaults: [
+ "art_standalone_gtest_defaults",
+ "art_libartservice_tests_defaults",
+ ],
+}
+
+android_test {
+ name: "ArtServiceTests",
+
+ // Include all test java files.
+ srcs: [
+ "javatests/**/*.java",
+ ],
+
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.runner",
+ "mockito-target-minus-junit4",
+ "service-art.impl",
+ ],
+
+ sdk_version: "system_server_current",
+ min_sdk_version: "31",
+
+ test_suites: ["general-tests"],
+}
diff --git a/libartservice/service/AndroidManifest.xml b/libartservice/service/AndroidManifest.xml
new file mode 100644
index 0000000..921bde9
--- /dev/null
+++ b/libartservice/service/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.server.art.tests">
+
+ <application android:label="ArtServiceTests">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.server.art.tests"
+ android:label="Tests for ART Serices" />
+</manifest>
diff --git a/libartservice/api/removed.txt b/libartservice/service/api/current.txt
similarity index 100%
copy from libartservice/api/removed.txt
copy to libartservice/service/api/current.txt
diff --git a/libartservice/api/removed.txt b/libartservice/service/api/removed.txt
similarity index 100%
rename from libartservice/api/removed.txt
rename to libartservice/service/api/removed.txt
diff --git a/libartservice/api/system-server-current.txt b/libartservice/service/api/system-server-current.txt
similarity index 100%
rename from libartservice/api/system-server-current.txt
rename to libartservice/service/api/system-server-current.txt
diff --git a/libartservice/api/system-server-removed.txt b/libartservice/service/api/system-server-removed.txt
similarity index 100%
rename from libartservice/api/system-server-removed.txt
rename to libartservice/service/api/system-server-removed.txt
diff --git a/libartservice/service/jarjar-rules.txt b/libartservice/service/jarjar-rules.txt
new file mode 100644
index 0000000..c7d39e6
--- /dev/null
+++ b/libartservice/service/jarjar-rules.txt
@@ -0,0 +1,2 @@
+# Repackages static libraries to make them private to ART Services.
+rule com.android.modules.utils.** com.android.server.art.jarjar.@0
diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index aac4b25..64aec7b 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -16,15 +16,16 @@
package com.android.server.art;
-import libcore.api.CorePlatformApi;
+import android.annotation.SystemApi;
/**
- * This class provides a system API for functionality provided by the ART
- * module.
+ * This class provides a system API for functionality provided by the ART module.
+ *
+ * @hide
*/
-@libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE)
+@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
public final class ArtManagerLocal {
- static final String LOG_TAG = "ArtService";
+ private static final String TAG = "ArtService";
public ArtManagerLocal() {}
}
diff --git a/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
similarity index 68%
rename from libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java
rename to libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index 515849b..a27dfa5 100644
--- a/libartservice/tests/src/com/android/server/art/ArtManagerLocalTests.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -16,22 +16,29 @@
package com.android.server.art;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
-import androidx.test.runner.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
import com.android.server.art.ArtManagerLocal;
-import junit.framework.TestCase;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
-public class ArtManagerLocalTests extends TestCase {
+@SmallTest
+@RunWith(MockitoJUnitRunner.class)
+public class ArtManagerLocalTest {
private ArtManagerLocal mArtManagerLocal;
- public void setup() {
+ @Before
+ public void setUp() {
mArtManagerLocal = new ArtManagerLocal();
}
+ @Test
public void testScaffolding() {
- assertTrue(true);
+ assertThat(true).isTrue();
}
-}
\ No newline at end of file
+}
diff --git a/libdexfile/Android.bp b/libdexfile/Android.bp
index fdc57d0..c6f18b6 100644
--- a/libdexfile/Android.bp
+++ b/libdexfile/Android.bp
@@ -293,7 +293,7 @@
],
header_libs: ["jni_headers"],
shared_libs: [
- "libbacktrace",
+ "libunwindstack",
"libziparchive",
],
}
@@ -398,19 +398,6 @@
"art_libdexfile_external_tests_defaults",
],
- // Support multilib variants (using different suffix per sub-architecture), which is needed on
- // build targets with secondary architectures, as the CTS test suite packaging logic flattens
- // all test artifacts into a single `testcases` directory.
- compile_multilib: "both",
- multilib: {
- lib32: {
- suffix: "32",
- },
- lib64: {
- suffix: "64",
- },
- },
-
test_config_template: ":art-gtests-target-standalone-cts-template",
test_suites: ["cts"], // For backed-by API coverage.
}
@@ -495,20 +482,6 @@
"art_standalone_test_defaults",
"art_libdexfile_support_tests_defaults",
],
-
- // Support multilib variants (using different suffix per sub-architecture), which is needed on
- // build targets with secondary architectures, as the MTS test suite packaging logic flattens
- // all test artifacts into a single `testcases` directory.
- compile_multilib: "both",
- multilib: {
- lib32: {
- suffix: "32",
- },
- lib64: {
- suffix: "64",
- },
- },
-
test_suites: [
"mts-art",
],
diff --git a/libdexfile/dex/dex_file.h b/libdexfile/dex/dex_file.h
index cc5d2fd..37a601d 100644
--- a/libdexfile/dex/dex_file.h
+++ b/libdexfile/dex/dex_file.h
@@ -570,7 +570,7 @@
};
// Callback for "new locals table entry".
- typedef void (*DexDebugNewLocalCb)(void* context, const LocalInfo& entry);
+ using DexDebugNewLocalCb = void (*)(void* context, const LocalInfo& entry);
const dex::AnnotationsDirectoryItem* GetAnnotationsDirectory(const dex::ClassDef& class_def)
const {
diff --git a/libdexfile/dex/dex_file_types.h b/libdexfile/dex/dex_file_types.h
index ecc0482..bf67187 100644
--- a/libdexfile/dex/dex_file_types.h
+++ b/libdexfile/dex/dex_file_types.h
@@ -17,6 +17,7 @@
#ifndef ART_LIBDEXFILE_DEX_DEX_FILE_TYPES_H_
#define ART_LIBDEXFILE_DEX_DEX_FILE_TYPES_H_
+#include <functional>
#include <iosfwd>
#include <limits>
#include <utility>
diff --git a/libdexfile/dex/dex_instruction.h b/libdexfile/dex/dex_instruction.h
index 7e43f75..ff6fcf7 100644
--- a/libdexfile/dex/dex_instruction.h
+++ b/libdexfile/dex/dex_instruction.h
@@ -22,8 +22,8 @@
#include "base/globals.h"
#include "base/macros.h"
-typedef uint8_t uint4_t;
-typedef int8_t int4_t;
+using uint4_t = uint8_t;
+using int4_t = int8_t;
namespace art {
diff --git a/libdexfile/dex/modifiers.h b/libdexfile/dex/modifiers.h
index 72949b0..a17545c 100644
--- a/libdexfile/dex/modifiers.h
+++ b/libdexfile/dex/modifiers.h
@@ -56,6 +56,9 @@
// Used by a class to denote that this class and any objects with this as a
// 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.
+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.
// We need copies of the original method because the method may end up in different
diff --git a/libdexfile/external/include/art_api/dex_file_external.h b/libdexfile/external/include/art_api/dex_file_external.h
index 360be92..d9db200 100644
--- a/libdexfile/external/include/art_api/dex_file_external.h
+++ b/libdexfile/external/include/art_api/dex_file_external.h
@@ -28,10 +28,10 @@
// may only be added here. C++ users should use dex_file_support.h instead.
struct ADexFile;
-typedef struct ADexFile ADexFile;
+typedef struct ADexFile ADexFile; // NOLINT
struct ADexFile_Method;
-typedef struct ADexFile_Method ADexFile_Method;
+typedef struct ADexFile_Method ADexFile_Method; // NOLINT
enum ADexFile_Error : uint32_t {
ADEXFILE_ERROR_OK = 0,
@@ -39,10 +39,11 @@
ADEXFILE_ERROR_INVALID_HEADER = 2,
ADEXFILE_ERROR_NOT_ENOUGH_DATA = 3,
};
-typedef enum ADexFile_Error ADexFile_Error;
+typedef enum ADexFile_Error ADexFile_Error; // NOLINT
// Callback used to return information about a dex method.
// The method information is valid only during the callback.
+// NOLINTNEXTLINE
typedef void ADexFile_MethodCallback(void* _Nullable callback_data,
const ADexFile_Method* _Nonnull method);
diff --git a/libelffile/elf/elf_debug_reader.h b/libelffile/elf/elf_debug_reader.h
index 266c638..6e01989 100644
--- a/libelffile/elf/elf_debug_reader.h
+++ b/libelffile/elf/elf_debug_reader.h
@@ -35,11 +35,11 @@
class ElfDebugReader {
public:
// Note that the input buffer might be misaligned.
- typedef typename ElfTypes::Ehdr ALIGNED(1) Elf_Ehdr;
- typedef typename ElfTypes::Phdr ALIGNED(1) Elf_Phdr;
- typedef typename ElfTypes::Shdr ALIGNED(1) Elf_Shdr;
- typedef typename ElfTypes::Sym ALIGNED(1) Elf_Sym;
- typedef typename ElfTypes::Addr ALIGNED(1) Elf_Addr;
+ using Elf_Ehdr ALIGNED(1) = typename ElfTypes::Ehdr;
+ using Elf_Phdr ALIGNED(1) = typename ElfTypes::Phdr;
+ using Elf_Shdr ALIGNED(1) = typename ElfTypes::Shdr;
+ using Elf_Sym ALIGNED(1) = typename ElfTypes::Sym;
+ using Elf_Addr ALIGNED(1) = typename ElfTypes::Addr;
// Call Frame Information.
struct CFI {
diff --git a/libnativebridge/include/nativebridge/native_bridge.h b/libnativebridge/include/nativebridge/native_bridge.h
index 2199bab..5904c0f 100644
--- a/libnativebridge/include/nativebridge/native_bridge.h
+++ b/libnativebridge/include/nativebridge/native_bridge.h
@@ -40,7 +40,7 @@
// Function pointer type for sigaction. This is mostly the signature of a signal handler, except
// for the return type. The runtime needs to know whether the signal was handled or should be given
// to the chain.
-typedef bool (*NativeBridgeSignalHandlerFn)(int, siginfo_t*, void*);
+typedef bool (*NativeBridgeSignalHandlerFn)(int, siginfo_t*, void*); // NOLINT
// Open the native bridge, if any. Should be called by Runtime::Init(). A null library filename
// signals that we do not want to load a native bridge.
diff --git a/libnativebridge/tests/Android.bp b/libnativebridge/tests/Android.bp
index 57d26c0..603f97a 100644
--- a/libnativebridge/tests/Android.bp
+++ b/libnativebridge/tests/Android.bp
@@ -180,20 +180,6 @@
],
header_libs: ["libbase_headers"],
- // Support multilib variants (using different suffix per sub-architecture),
- // which is needed on build targets with secondary architectures, as the CTS
- // test suite packaging logic flattens all test artifacts into a single
- // `testcases` directory.
- compile_multilib: "both",
- multilib: {
- lib32: {
- suffix: "32",
- },
- lib64: {
- suffix: "64",
- },
- },
-
test_config_template: ":art-gtests-target-standalone-cts-template",
test_suites: [
"cts",
diff --git a/libnativeloader/Android.bp b/libnativeloader/Android.bp
index e14aea2..f1ef32d 100644
--- a/libnativeloader/Android.bp
+++ b/libnativeloader/Android.bp
@@ -64,7 +64,7 @@
"libdl_android",
],
static_libs: [
- "PlatformProperties",
+ "libPlatformProperties",
],
},
},
@@ -164,19 +164,6 @@
"libnativeloader",
],
- // Support multilib variants (using different suffix per sub-architecture), which is needed on
- // build targets with secondary architectures, as the CTS test suite packaging logic flattens
- // all test artifacts into a single `testcases` directory.
- compile_multilib: "both",
- multilib: {
- lib32: {
- suffix: "32",
- },
- lib64: {
- suffix: "64",
- },
- },
-
// Added to CTS for API coverage of libnativeloader which is backed by the
// ART module.
test_config_template: ":art-gtests-target-standalone-cts-template",
diff --git a/libnativeloader/library_namespaces.cpp b/libnativeloader/library_namespaces.cpp
index f31c430..96d4dde 100644
--- a/libnativeloader/library_namespaces.cpp
+++ b/libnativeloader/library_namespaces.cpp
@@ -231,51 +231,38 @@
std::string system_exposed_libraries = default_public_libraries();
std::string namespace_name = kClassloaderNamespaceName;
ApkOrigin unbundled_app_origin = APK_ORIGIN_DEFAULT;
- if ((apk_origin == APK_ORIGIN_VENDOR ||
- (apk_origin == APK_ORIGIN_PRODUCT &&
- is_product_vndk_version_defined())) &&
- !is_shared) {
- unbundled_app_origin = apk_origin;
- // For vendor / product apks, give access to the vendor / product lib even though
- // they are treated as unbundled; the libs and apks are still bundled
- // together in the vendor / product partition.
- const char* origin_partition;
- const char* origin_lib_path;
- const char* llndk_libraries;
+ const char* apk_origin_msg = "other apk"; // Only for debug logging.
- switch (apk_origin) {
- case APK_ORIGIN_VENDOR:
- origin_partition = "vendor";
- origin_lib_path = kVendorLibPath;
- llndk_libraries = llndk_libraries_vendor().c_str();
- break;
- case APK_ORIGIN_PRODUCT:
- origin_partition = "product";
- origin_lib_path = kProductLibPath;
- llndk_libraries = llndk_libraries_product().c_str();
- break;
- default:
- origin_partition = "unknown";
- origin_lib_path = "";
- llndk_libraries = "";
- }
- library_path = library_path + ":" + origin_lib_path;
- permitted_path = permitted_path + ":" + origin_lib_path;
+ if (!is_shared) {
+ if (apk_origin == APK_ORIGIN_VENDOR) {
+ unbundled_app_origin = APK_ORIGIN_VENDOR;
+ apk_origin_msg = "unbundled vendor apk";
- // Also give access to LLNDK libraries since they are available to vendor or product
- system_exposed_libraries = system_exposed_libraries + ":" + llndk_libraries;
+ // For vendor apks, give access to the vendor libs even though they are
+ // treated as unbundled; the libs and apks are still bundled together in the
+ // vendor partition.
+ library_path = library_path + ':' + kVendorLibPath;
+ permitted_path = permitted_path + ':' + kVendorLibPath;
- // Different name is useful for debugging
- namespace_name = kVendorClassloaderNamespaceName;
- ALOGD("classloader namespace configured for unbundled %s apk. library_path=%s",
- origin_partition, library_path.c_str());
- } else {
- auto libs = filter_public_libraries(target_sdk_version, uses_libraries,
- extended_public_libraries());
- // extended public libraries are NOT available to vendor apks, otherwise it
- // would be system->vendor violation.
- if (!libs.empty()) {
- system_exposed_libraries = system_exposed_libraries + ':' + libs;
+ // Also give access to LLNDK libraries since they are available to vendor.
+ system_exposed_libraries = system_exposed_libraries + ':' + llndk_libraries_vendor();
+
+ // Different name is useful for debugging
+ namespace_name = kVendorClassloaderNamespaceName;
+ } else if (apk_origin == APK_ORIGIN_PRODUCT && is_product_vndk_version_defined()) {
+ unbundled_app_origin = APK_ORIGIN_PRODUCT;
+ apk_origin_msg = "unbundled product apk";
+
+ // Like for vendor apks, give access to the product libs since they are
+ // bundled together in the same partition.
+ library_path = library_path + ':' + kProductLibPath;
+ permitted_path = permitted_path + ':' + kProductLibPath;
+
+ // Also give access to LLNDK libraries since they are available to product.
+ system_exposed_libraries = system_exposed_libraries + ':' + llndk_libraries_product();
+
+ // Different name is useful for debugging
+ namespace_name = kVendorClassloaderNamespaceName;
}
}
@@ -285,6 +272,32 @@
namespace_name = namespace_name + kSharedNamespaceSuffix;
}
+ ALOGD(
+ "Configuring %s for %s %s. target_sdk_version=%u, uses_libraries=%s, library_path=%s, "
+ "permitted_path=%s",
+ namespace_name.c_str(),
+ apk_origin_msg,
+ dex_path.c_str(),
+ static_cast<unsigned>(target_sdk_version),
+ android::base::Join(uses_libraries, ':').c_str(),
+ library_path.c_str(),
+ permitted_path.c_str());
+
+ if (unbundled_app_origin != APK_ORIGIN_VENDOR) {
+ // Extended public libraries are NOT available to unbundled vendor apks, but
+ // they are to other apps, including those in system, system_ext, and
+ // product partitions. The reason is that when GSI is used, the system
+ // partition may get replaced, and then vendor apps may fail. It's fine for
+ // product (and system_ext) apps, because those partitions aren't mounted in
+ // GSI tests.
+ auto libs =
+ filter_public_libraries(target_sdk_version, uses_libraries, extended_public_libraries());
+ if (!libs.empty()) {
+ ALOGD("Extending system_exposed_libraries: %s", libs.c_str());
+ system_exposed_libraries = system_exposed_libraries + ':' + libs;
+ }
+ }
+
// Create the app namespace
NativeLoaderNamespace* parent_ns = FindParentNamespaceByClassLoader(env, class_loader);
// Heuristic: the first classloader with non-empty library_path is assumed to
diff --git a/libnativeloader/native_loader_test.cpp b/libnativeloader/native_loader_test.cpp
index 9648713..7bf4cb7 100644
--- a/libnativeloader/native_loader_test.cpp
+++ b/libnativeloader/native_loader_test.cpp
@@ -33,7 +33,7 @@
using ::testing::Eq;
using ::testing::NotNull;
using ::testing::StrEq;
-using internal::ConfigEntry;
+using internal::ConfigEntry; // NOLINT - ConfigEntry is actually used
using internal::ParseApexLibrariesConfig;
using internal::ParseConfig;
@@ -68,7 +68,7 @@
void SetExpectations() {
std::vector<std::string> default_public_libs =
android::base::Split(preloadable_public_libraries(), ":");
- for (auto l : default_public_libs) {
+ for (const std::string& l : default_public_libs) {
EXPECT_CALL(*mock,
mock_dlopen_ext(false, StrEq(l.c_str()), RTLD_NOW | RTLD_NODELETE, NotNull()))
.WillOnce(Return(any_nonnull));
@@ -167,13 +167,16 @@
/////////////////////////////////////////////////////////////////
-std::string default_public_and_extended_libraries() {
- std::string public_libs = default_public_libraries();
- std::string ext_libs = extended_public_libraries();
+std::string append_extended_libraries(const std::string& libs) {
+ const std::string& ext_libs = extended_public_libraries();
if (!ext_libs.empty()) {
- public_libs = public_libs + ":" + ext_libs;
+ return libs + ":" + ext_libs;
}
- return public_libs;
+ return libs;
+}
+
+std::string default_public_and_extended_libraries() {
+ return append_extended_libraries(default_public_libraries());
}
class NativeLoaderTest_Create : public NativeLoaderTest {
@@ -380,7 +383,7 @@
expected_permitted_path =
expected_permitted_path + ":/product/" LIB_DIR ":/system/product/" LIB_DIR;
expected_shared_libs_to_platform_ns =
- default_public_libraries() + ":" + llndk_libraries_product();
+ append_extended_libraries(default_public_libraries() + ":" + llndk_libraries_product());
expected_link_with_vndk_product_ns = true;
}
SetExpectations();
diff --git a/libnativeloader/native_loader_test.h b/libnativeloader/native_loader_test.h
index 5c51f00..30b2f84 100644
--- a/libnativeloader/native_loader_test.h
+++ b/libnativeloader/native_loader_test.h
@@ -42,7 +42,7 @@
// Instead of having two set of mock APIs for the two, define only one set with an additional
// argument 'bool bridged' to identify the context (i.e., called for libdl_android or
// libnativebridge).
- typedef char* mock_namespace_handle;
+ using mock_namespace_handle = char*;
virtual bool mock_init_anonymous_namespace(bool bridged, const char* sonames,
const char* search_paths) = 0;
virtual mock_namespace_handle mock_create_namespace(
diff --git a/libnativeloader/public_libraries.cpp b/libnativeloader/public_libraries.cpp
index ffebe0b..ac5acc0 100644
--- a/libnativeloader/public_libraries.cpp
+++ b/libnativeloader/public_libraries.cpp
@@ -46,7 +46,6 @@
using internal::ConfigEntry;
using internal::ParseConfig;
using internal::ParseApexLibrariesConfig;
-using std::literals::string_literals::operator""s;
namespace {
@@ -117,7 +116,7 @@
if (android::base::ConsumePrefix(&fn, kExtendedPublicLibrariesFilePrefix) &&
android::base::ConsumeSuffix(&fn, kExtendedPublicLibrariesFileSuffix)) {
const std::string company_name(fn);
- const std::string config_file_path = dirname + "/"s + filename;
+ const std::string config_file_path = std::string(dirname) + std::string("/") + filename;
LOG_ALWAYS_FATAL_IF(
company_name.empty(),
"Error extracting company name from public native library list file path \"%s\"",
diff --git a/libnativeloader/test/Android.bp b/libnativeloader/test/Android.bp
index fb9ae0d..dd5e969 100644
--- a/libnativeloader/test/Android.bp
+++ b/libnativeloader/test/Android.bp
@@ -24,57 +24,88 @@
}
cc_library {
- name: "libfoo.oem1",
- srcs: ["test.cpp"],
- cflags: ["-DLIBNAME=\"libfoo.oem1.so\""],
- shared_libs: [
- "libbase",
+ name: "libnativeloader_testlib",
+ srcs: [],
+ stl: "none",
+}
+
+// This app is just an intermediate container to be able to include the .so
+// library as a java resource in the host test. It's not actually installed or
+// started.
+android_test_helper_app {
+ name: "library_container_app",
+ defaults: ["art_module_source_build_java_defaults"],
+ manifest: "library_container_app_manifest.xml",
+ compile_multilib: "both",
+ jni_libs: ["libnativeloader_testlib"],
+}
+
+java_defaults {
+ name: "loadlibrarytest_app_defaults",
+ defaults: ["art_module_source_build_java_defaults"],
+
+ // TODO(mast): Use old target SDK to avoid filtering on uses_library lists.
+ // Figure out what we need to do to make <uses-native-library> work in the
+ // test apps so we can use that instead.
+ sdk_version: "30",
+
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.ext.truth",
+ "androidx.test.rules",
],
}
-cc_library {
- name: "libbar.oem1",
- srcs: ["test.cpp"],
- cflags: ["-DLIBNAME=\"libbar.oem1.so\""],
- shared_libs: [
- "libbase",
- ],
+android_test_helper_app {
+ name: "loadlibrarytest_system_priv_app",
+ defaults: ["loadlibrarytest_app_defaults"],
+ manifest: "loadlibrarytest_system_priv_app_manifest.xml",
+ // /system/priv-app currently reuses the same test as /system/app.
+ srcs: ["src/android/test/app/SystemAppTest.java"],
}
-cc_library {
- name: "libfoo.oem2",
- srcs: ["test.cpp"],
- cflags: ["-DLIBNAME=\"libfoo.oem2.so\""],
- shared_libs: [
- "libbase",
- ],
+android_test_helper_app {
+ name: "loadlibrarytest_system_app",
+ defaults: ["loadlibrarytest_app_defaults"],
+ manifest: "loadlibrarytest_system_app_manifest.xml",
+ srcs: ["src/android/test/app/SystemAppTest.java"],
}
-cc_library {
- name: "libbar.oem2",
- srcs: ["test.cpp"],
- cflags: ["-DLIBNAME=\"libbar.oem2.so\""],
- shared_libs: [
- "libbase",
- ],
+android_test_helper_app {
+ name: "loadlibrarytest_system_ext_app",
+ defaults: ["loadlibrarytest_app_defaults"],
+ manifest: "loadlibrarytest_system_ext_app_manifest.xml",
+ // /system_ext should behave the same as /system, so use the same test class there.
+ srcs: ["src/android/test/app/SystemAppTest.java"],
}
-cc_library {
- name: "libfoo.product1",
- srcs: ["test.cpp"],
- cflags: ["-DLIBNAME=\"libfoo.product1.so\""],
- product_specific: true,
- shared_libs: [
- "libbase",
- ],
+android_test_helper_app {
+ name: "loadlibrarytest_product_app",
+ defaults: ["loadlibrarytest_app_defaults"],
+ manifest: "loadlibrarytest_product_app_manifest.xml",
+ srcs: ["src/android/test/app/ProductAppTest.java"],
}
-cc_library {
- name: "libbar.product1",
- srcs: ["test.cpp"],
- cflags: ["-DLIBNAME=\"libbar.product1.so\""],
- product_specific: true,
- shared_libs: [
- "libbase",
+android_test_helper_app {
+ name: "loadlibrarytest_vendor_app",
+ defaults: ["loadlibrarytest_app_defaults"],
+ manifest: "loadlibrarytest_vendor_app_manifest.xml",
+ srcs: ["src/android/test/app/VendorAppTest.java"],
+}
+
+java_test_host {
+ name: "libnativeloader_e2e_tests",
+ defaults: ["art_module_source_build_java_defaults"],
+ srcs: ["src/android/test/hostside/*.java"],
+ libs: ["tradefed"],
+ java_resources: [
+ ":library_container_app",
+ ":loadlibrarytest_system_priv_app",
+ ":loadlibrarytest_system_app",
+ ":loadlibrarytest_system_ext_app",
+ ":loadlibrarytest_product_app",
+ ":loadlibrarytest_vendor_app",
],
+ test_config: "libnativeloader_e2e_tests.xml",
+ test_suites: ["general-tests"],
}
diff --git a/libnativeloader/test/Android.mk b/libnativeloader/test/Android.mk
deleted file mode 100644
index 95fa68a..0000000
--- a/libnativeloader/test/Android.mk
+++ /dev/null
@@ -1,72 +0,0 @@
-#
-# Copyright (C) 2017 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.
-#
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := public.libraries-oem1.txt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES:= $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := public.libraries-oem2.txt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES:= $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := public.libraries-product1.txt
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_SRC_FILES:= $(LOCAL_MODULE)
-LOCAL_MODULE_CLASS := ETC
-LOCAL_MODULE_PATH := $(TARGET_OUT_PRODUCT_ETC)
-include $(BUILD_PREBUILT)
-
-include $(CLEAR_VARS)
-LOCAL_PACKAGE_NAME := oemlibrarytest-system
-LOCAL_MODULE_TAGS := tests
-LOCAL_MANIFEST_FILE := system/AndroidManifest.xml
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := current
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_MODULE_PATH := $(TARGET_OUT_APPS)
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_PACKAGE_NAME := oemlibrarytest-vendor
-LOCAL_MODULE_TAGS := tests
-LOCAL_MANIFEST_FILE := vendor/AndroidManifest.xml
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_SDK_VERSION := current
-LOCAL_PROGUARD_ENABLED := disabled
-LOCAL_MODULE_PATH := $(TARGET_OUT_VENDOR_APPS)
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-include $(BUILD_PACKAGE)
diff --git a/libnativeloader/test/libnativeloader_e2e_tests.xml b/libnativeloader/test/libnativeloader_e2e_tests.xml
new file mode 100644
index 0000000..b1333b0
--- /dev/null
+++ b/libnativeloader/test/libnativeloader_e2e_tests.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2021 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.
+-->
+<configuration description="Config for libnativeloader e2e test cases">
+ <option name="test-suite-tag" value="libnativeloader_e2e_tests" />
+ <option name="test-suite-tag" value="apct" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="jar" value="libnativeloader_e2e_tests.jar" />
+ </test>
+
+ <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/libnativeloader/test/test.cpp b/libnativeloader/test/library_container_app_manifest.xml
similarity index 71%
rename from libnativeloader/test/test.cpp
rename to libnativeloader/test/library_container_app_manifest.xml
index b166928..20030de 100644
--- a/libnativeloader/test/test.cpp
+++ b/libnativeloader/test/library_container_app_manifest.xml
@@ -1,5 +1,6 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 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.
@@ -12,10 +13,8 @@
* 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.
- */
-#define LOG_TAG "oemlib"
-#include <android-base/logging.h>
+ -->
-static __attribute__((constructor)) void test_lib_init() {
- LOG(DEBUG) << LIBNAME << " loaded";
-}
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.test.app.container">
+</manifest>
diff --git a/libnativeloader/test/vendor/AndroidManifest.xml b/libnativeloader/test/loadlibrarytest_product_app_manifest.xml
similarity index 66%
copy from libnativeloader/test/vendor/AndroidManifest.xml
copy to libnativeloader/test/loadlibrarytest_product_app_manifest.xml
index c4c1a9c..a791795 100644
--- a/libnativeloader/test/vendor/AndroidManifest.xml
+++ b/libnativeloader/test/loadlibrarytest_product_app_manifest.xml
@@ -16,16 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.app.vendor">
-
- <application>
- <activity android:name="android.test.app.TestActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
+ package="android.test.app.product">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.app.product" />
</manifest>
diff --git a/libnativeloader/test/vendor/AndroidManifest.xml b/libnativeloader/test/loadlibrarytest_system_app_manifest.xml
similarity index 66%
copy from libnativeloader/test/vendor/AndroidManifest.xml
copy to libnativeloader/test/loadlibrarytest_system_app_manifest.xml
index c4c1a9c..3cbd054 100644
--- a/libnativeloader/test/vendor/AndroidManifest.xml
+++ b/libnativeloader/test/loadlibrarytest_system_app_manifest.xml
@@ -16,16 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.app.vendor">
-
- <application>
- <activity android:name="android.test.app.TestActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
+ package="android.test.app.system">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.app.system" />
</manifest>
diff --git a/libnativeloader/test/vendor/AndroidManifest.xml b/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml
similarity index 66%
rename from libnativeloader/test/vendor/AndroidManifest.xml
rename to libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml
index c4c1a9c..83ca779 100644
--- a/libnativeloader/test/vendor/AndroidManifest.xml
+++ b/libnativeloader/test/loadlibrarytest_system_ext_app_manifest.xml
@@ -16,16 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.app.vendor">
-
- <application>
- <activity android:name="android.test.app.TestActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
+ package="android.test.app.system_ext">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.app.system_ext" />
</manifest>
diff --git a/libnativeloader/test/vendor/AndroidManifest.xml b/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml
similarity index 66%
copy from libnativeloader/test/vendor/AndroidManifest.xml
copy to libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml
index c4c1a9c..0ad1aa5 100644
--- a/libnativeloader/test/vendor/AndroidManifest.xml
+++ b/libnativeloader/test/loadlibrarytest_system_priv_app_manifest.xml
@@ -16,16 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.app.vendor">
-
- <application>
- <activity android:name="android.test.app.TestActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
+ package="android.test.app.system_priv">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.app.system_priv" />
</manifest>
diff --git a/libnativeloader/test/vendor/AndroidManifest.xml b/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml
similarity index 66%
copy from libnativeloader/test/vendor/AndroidManifest.xml
copy to libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml
index c4c1a9c..b7073c6 100644
--- a/libnativeloader/test/vendor/AndroidManifest.xml
+++ b/libnativeloader/test/loadlibrarytest_vendor_app_manifest.xml
@@ -16,16 +16,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.app.vendor">
-
- <application>
- <activity android:name="android.test.app.TestActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
+ package="android.test.app.vendor">
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="android.test.app.vendor" />
</manifest>
diff --git a/libnativeloader/test/public.libraries-oem1.txt b/libnativeloader/test/public.libraries-oem1.txt
deleted file mode 100644
index f9433e2..0000000
--- a/libnativeloader/test/public.libraries-oem1.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-libfoo.oem1.so
-libbar.oem1.so
diff --git a/libnativeloader/test/public.libraries-oem2.txt b/libnativeloader/test/public.libraries-oem2.txt
deleted file mode 100644
index de6bdb0..0000000
--- a/libnativeloader/test/public.libraries-oem2.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-libfoo.oem2.so
-libbar.oem2.so
diff --git a/libnativeloader/test/public.libraries-product1.txt b/libnativeloader/test/public.libraries-product1.txt
deleted file mode 100644
index 358154c..0000000
--- a/libnativeloader/test/public.libraries-product1.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-libfoo.product1.so
-libbar.product1.so
diff --git a/libnativeloader/test/runtest.sh b/libnativeloader/test/runtest.sh
deleted file mode 100755
index 40beb5b..0000000
--- a/libnativeloader/test/runtest.sh
+++ /dev/null
@@ -1,11 +0,0 @@
-#!/bin/bash
-adb root
-adb remount
-adb sync
-adb shell stop
-adb shell start
-sleep 5 # wait until device reboots
-adb logcat -c;
-adb shell am start -n android.test.app.system/android.test.app.TestActivity
-adb shell am start -n android.test.app.vendor/android.test.app.TestActivity
-adb logcat | grep android.test.app
diff --git a/libnativeloader/test/src/android/test/app/ProductAppTest.java b/libnativeloader/test/src/android/test/app/ProductAppTest.java
new file mode 100644
index 0000000..243e2c7
--- /dev/null
+++ b/libnativeloader/test/src/android/test/app/ProductAppTest.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.test.app;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ProductAppTest {
+ @Test
+ public void testLoadLibraries() {
+ System.loadLibrary("foo.oem1");
+ System.loadLibrary("bar.oem1");
+ System.loadLibrary("foo.oem2");
+ System.loadLibrary("bar.oem2");
+ System.loadLibrary("foo.product1");
+ System.loadLibrary("bar.product1");
+ }
+}
diff --git a/libnativeloader/test/src/android/test/app/SystemAppTest.java b/libnativeloader/test/src/android/test/app/SystemAppTest.java
new file mode 100644
index 0000000..dd680f5
--- /dev/null
+++ b/libnativeloader/test/src/android/test/app/SystemAppTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 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.test.app;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+// These tests are run in both /system and /system_ext.
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SystemAppTest {
+ @Test
+ public void testLoadLibraries() {
+ System.loadLibrary("foo.oem1");
+ System.loadLibrary("bar.oem1");
+ System.loadLibrary("foo.oem2");
+ System.loadLibrary("bar.oem2");
+ System.loadLibrary("foo.product1");
+ System.loadLibrary("bar.product1");
+ }
+}
diff --git a/libnativeloader/test/src/android/test/app/TestActivity.java b/libnativeloader/test/src/android/test/app/TestActivity.java
deleted file mode 100644
index a7a455d..0000000
--- a/libnativeloader/test/src/android/test/app/TestActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2018 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.test.app;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestActivity extends Activity {
-
- @Override
- public void onCreate(Bundle icicle) {
- super.onCreate(icicle);
- tryLoadingLib("foo.oem1");
- tryLoadingLib("bar.oem1");
- tryLoadingLib("foo.oem2");
- tryLoadingLib("bar.oem2");
- tryLoadingLib("foo.product1");
- tryLoadingLib("bar.product1");
- }
-
- private void tryLoadingLib(String name) {
- try {
- System.loadLibrary(name);
- Log.d(getPackageName(), "library " + name + " is successfully loaded");
- } catch (UnsatisfiedLinkError e) {
- Log.d(getPackageName(), "failed to load libarary " + name, e);
- }
- }
-}
diff --git a/libnativeloader/test/src/android/test/app/VendorAppTest.java b/libnativeloader/test/src/android/test/app/VendorAppTest.java
new file mode 100644
index 0000000..a830d06
--- /dev/null
+++ b/libnativeloader/test/src/android/test/app/VendorAppTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.test.app;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VendorAppTest {
+ @Test
+ public void testLoadLibraries() {
+ assertLinkerNamespaceError("foo.oem1");
+ assertLinkerNamespaceError("bar.oem1");
+ assertLinkerNamespaceError("foo.oem2");
+ assertLinkerNamespaceError("bar.oem2");
+ System.loadLibrary("foo.product1");
+ System.loadLibrary("bar.product1");
+ }
+
+ private void assertLinkerNamespaceError(String libraryName) {
+ Throwable t =
+ assertThrows(UnsatisfiedLinkError.class, () -> System.loadLibrary(libraryName));
+ assertThat(t.getMessage())
+ .containsMatch("dlopen failed: .* is not accessible for the namespace");
+ }
+}
diff --git a/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java
new file mode 100644
index 0000000..7e2d9de
--- /dev/null
+++ b/libnativeloader/test/src/android/test/hostside/LibnativeloaderTest.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2022 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.test.hostside;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import com.android.tradefed.util.CommandResult;
+import com.google.common.io.ByteStreams;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test libnativeloader behavior for apps and libs in various partitions by overlaying them over
+ * the system partitions. Requires root.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class LibnativeloaderTest extends BaseHostJUnit4Test {
+ private static final String TAG = "LibnativeloaderTest";
+ private static final String CLEANUP_PATHS_KEY = TAG + ":CLEANUP_PATHS";
+ private static final String LOG_FILE_NAME = "TestActivity.log";
+
+ @BeforeClassWithInfo
+ public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
+ DeviceContext ctx = new DeviceContext(testInfo.getContext(), testInfo.getDevice());
+
+ // A soft reboot is slow, so do setup for all tests and reboot once.
+
+ ctx.mDevice.remountSystemWritable();
+ try (ZipFile libApk = openLibContainerApk()) {
+ ctx.pushSystemOemLibs(libApk);
+ ctx.pushProductLibs(libApk);
+ }
+
+ // "Install" apps in various partitions through plain adb push. We need them in these
+ // locations to test library loading restrictions, so we cannot use
+ // ITestDevice.installPackage for it since it only installs in /data.
+
+ // For testSystemPrivApp
+ ctx.pushApk("loadlibrarytest_system_priv_app", "/system/priv-app");
+
+ // For testSystemApp
+ ctx.pushApk("loadlibrarytest_system_app", "/system/app");
+
+ // For testSystemExtApp
+ ctx.pushApk("loadlibrarytest_system_ext_app", "/system_ext/app");
+
+ // For testProductApp
+ ctx.pushApk("loadlibrarytest_product_app", "/product/app");
+
+ // For testVendorApp
+ ctx.pushApk("loadlibrarytest_vendor_app", "/vendor/app");
+
+ ctx.softReboot();
+
+ testInfo.properties().put(CLEANUP_PATHS_KEY, ctx.mCleanup.getPathList());
+ }
+
+ @AfterClassWithInfo
+ public static void afterClassWithDevice(TestInformation testInfo) throws Exception {
+ String cleanupPathList = testInfo.properties().get(CLEANUP_PATHS_KEY);
+ CleanupPaths cleanup = new CleanupPaths(testInfo.getDevice(), cleanupPathList);
+ cleanup.cleanup();
+ }
+
+ @Test
+ public void testSystemPrivApp() throws Exception {
+ // There's currently no difference in the tests between /system/priv-app and /system/app, so
+ // let's reuse the same one.
+ runDeviceTests("android.test.app.system_priv", "android.test.app.SystemAppTest");
+ }
+
+ @Test
+ public void testSystemApp() throws Exception {
+ runDeviceTests("android.test.app.system", "android.test.app.SystemAppTest");
+ }
+
+ @Test
+ public void testSystemExtApp() throws Exception {
+ // /system_ext should behave the same as /system, so run the same test class there.
+ runDeviceTests("android.test.app.system_ext", "android.test.app.SystemAppTest");
+ }
+
+ @Test
+ public void testProductApp() throws Exception {
+ runDeviceTests("android.test.app.product", "android.test.app.ProductAppTest");
+ }
+
+ @Test
+ public void testVendorApp() throws Exception {
+ runDeviceTests("android.test.app.vendor", "android.test.app.VendorAppTest");
+ }
+
+ // Utility class that keeps track of a set of paths the need to be deleted after testing.
+ private static class CleanupPaths {
+ private ITestDevice mDevice;
+ private List<String> mCleanupPaths;
+
+ CleanupPaths(ITestDevice device) {
+ mDevice = device;
+ mCleanupPaths = new ArrayList<String>();
+ }
+
+ CleanupPaths(ITestDevice device, String pathList) {
+ mDevice = device;
+ mCleanupPaths = Arrays.asList(pathList.split(":"));
+ }
+
+ String getPathList() { return String.join(":", mCleanupPaths); }
+
+ // Adds the given path, or its topmost nonexisting parent directory, to the list of paths to
+ // clean up.
+ void addPath(String devicePath) throws DeviceNotAvailableException {
+ File path = new File(devicePath);
+ while (true) {
+ File parentPath = path.getParentFile();
+ if (parentPath == null || mDevice.doesFileExist(parentPath.toString())) {
+ break;
+ }
+ path = parentPath;
+ }
+ String nonExistingPath = path.toString();
+ if (!mCleanupPaths.contains(nonExistingPath)) {
+ mCleanupPaths.add(nonExistingPath);
+ }
+ }
+
+ void cleanup() throws DeviceNotAvailableException {
+ // Clean up in reverse order in case several pushed files were in the same nonexisting
+ // directory.
+ for (int i = mCleanupPaths.size() - 1; i >= 0; --i) {
+ mDevice.deleteFile(mCleanupPaths.get(i));
+ }
+ }
+ }
+
+ // Class for code that needs an ITestDevice. It is instantiated both in tests and in
+ // (Before|After)ClassWithInfo.
+ private static class DeviceContext implements AutoCloseable {
+ IInvocationContext mContext;
+ ITestDevice mDevice;
+ CleanupPaths mCleanup;
+ private String mTestArch;
+
+ DeviceContext(IInvocationContext context, ITestDevice device) {
+ mContext = context;
+ mDevice = device;
+ mCleanup = new CleanupPaths(mDevice);
+ }
+
+ public void close() throws DeviceNotAvailableException { mCleanup.cleanup(); }
+
+ void pushSystemOemLibs(ZipFile libApk) throws Exception {
+ pushNativeTestLib(libApk, "/system/${LIB}/libfoo.oem1.so");
+ pushNativeTestLib(libApk, "/system/${LIB}/libbar.oem1.so");
+ pushString("libfoo.oem1.so\n"
+ + "libbar.oem1.so\n",
+ "/system/etc/public.libraries-oem1.txt");
+
+ pushNativeTestLib(libApk, "/system/${LIB}/libfoo.oem2.so");
+ pushNativeTestLib(libApk, "/system/${LIB}/libbar.oem2.so");
+ pushString("libfoo.oem2.so\n"
+ + "libbar.oem2.so\n",
+ "/system/etc/public.libraries-oem2.txt");
+ }
+
+ void pushProductLibs(ZipFile libApk) throws Exception {
+ pushNativeTestLib(libApk, "/product/${LIB}/libfoo.product1.so");
+ pushNativeTestLib(libApk, "/product/${LIB}/libbar.product1.so");
+ pushString("libfoo.product1.so\n"
+ + "libbar.product1.so\n",
+ "/product/etc/public.libraries-product1.txt");
+ }
+
+ void softReboot() throws DeviceNotAvailableException {
+ assertCommandSucceeds("setprop dev.bootcomplete 0");
+ assertCommandSucceeds("stop");
+ assertCommandSucceeds("start");
+ mDevice.waitForDeviceAvailable();
+ }
+
+ String getTestArch() throws DeviceNotAvailableException {
+ if (mTestArch == null) {
+ IAbi abi = mContext.getConfigurationDescriptor().getAbi();
+ mTestArch = abi != null ? abi.getName()
+ : assertCommandSucceeds("getprop ro.bionic.arch");
+ }
+ return mTestArch;
+ }
+
+ // Pushes the given file contents to the device at the given destination path. destPath is
+ // assumed to have no risk of overlapping with existing files, and is deleted in tearDown(),
+ // along with any directory levels that had to be created.
+ void pushString(String fileContents, String destPath) throws DeviceNotAvailableException {
+ mCleanup.addPath(destPath);
+ assertThat(mDevice.pushString(fileContents, destPath)).isTrue();
+ }
+
+ // Like pushString, but extracts a Java resource and pushes that.
+ void pushResource(String resourceName, String destPath) throws Exception {
+ File hostTempFile = extractResourceToTempFile(resourceName);
+ mCleanup.addPath(destPath);
+ assertThat(mDevice.pushFile(hostTempFile, destPath)).isTrue();
+ }
+
+ void pushApk(String apkBaseName, String destPath) throws Exception {
+ pushResource("/" + apkBaseName + ".apk",
+ destPath + "/" + apkBaseName + "/" + apkBaseName + ".apk");
+ }
+
+ // Like pushString, but extracts libnativeloader_testlib.so from the library_container_app
+ // APK and pushes it to destPath. "${LIB}" is replaced with "lib" or "lib64" as appropriate.
+ void pushNativeTestLib(ZipFile libApk, String destPath) throws Exception {
+ String libApkPath = "lib/" + getTestArch() + "/libnativeloader_testlib.so";
+ ZipEntry entry = libApk.getEntry(libApkPath);
+ assertWithMessage("Failed to find " + libApkPath + " in library_container_app.apk")
+ .that(entry)
+ .isNotNull();
+
+ File libraryTempFile;
+ try (InputStream inStream = libApk.getInputStream(entry)) {
+ libraryTempFile = writeStreamToTempFile("libnativeloader_testlib.so", inStream);
+ }
+
+ String libDir = getTestArch().contains("64") ? "lib64" : "lib";
+ destPath = destPath.replace("${LIB}", libDir);
+
+ mCleanup.addPath(destPath);
+ assertThat(mDevice.pushFile(libraryTempFile, destPath)).isTrue();
+ }
+
+ String assertCommandSucceeds(String command) throws DeviceNotAvailableException {
+ CommandResult result = mDevice.executeShellV2Command(command);
+ assertWithMessage(result.toString()).that(result.getExitCode()).isEqualTo(0);
+ // Remove trailing \n's.
+ return result.getStdout().trim();
+ }
+ }
+
+ static private ZipFile openLibContainerApk() throws Exception {
+ return new ZipFile(extractResourceToTempFile("/library_container_app.apk"));
+ }
+
+ static private File extractResourceToTempFile(String resourceName) throws Exception {
+ assertThat(resourceName).startsWith("/");
+ try (InputStream inStream = LibnativeloaderTest.class.getResourceAsStream(resourceName)) {
+ assertWithMessage("Failed to extract resource " + resourceName)
+ .that(inStream)
+ .isNotNull();
+ return writeStreamToTempFile(resourceName.substring(1), inStream);
+ }
+ }
+
+ static private File writeStreamToTempFile(String tempFileBaseName, InputStream inStream)
+ throws Exception {
+ File hostTempFile = File.createTempFile(tempFileBaseName, null);
+ try (FileOutputStream outStream = new FileOutputStream(hostTempFile)) {
+ ByteStreams.copy(inStream, outStream);
+ }
+ return hostTempFile;
+ }
+}
diff --git a/libnativeloader/test/system/AndroidManifest.xml b/libnativeloader/test/system/AndroidManifest.xml
deleted file mode 100644
index c304889..0000000
--- a/libnativeloader/test/system/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- * Copyright (C) 2018 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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.test.app.system">
-
- <application>
- <activity android:name="android.test.app.TestActivity" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
-
-</manifest>
-
diff --git a/libprofile/profile/profile_compilation_info.cc b/libprofile/profile/profile_compilation_info.cc
index f135805..83db0a0 100644
--- a/libprofile/profile/profile_compilation_info.cc
+++ b/libprofile/profile/profile_compilation_info.cc
@@ -2387,7 +2387,8 @@
}
bool ProfileCompilationInfo::UpdateProfileKeys(
- const std::vector<std::unique_ptr<const DexFile>>& dex_files) {
+ const std::vector<std::unique_ptr<const DexFile>>& dex_files, /*out*/ bool* updated) {
+ *updated = false;
for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {
for (const std::unique_ptr<DexFileData>& dex_data : info_) {
if (dex_data->checksum == dex_file->GetLocationChecksum() &&
@@ -2407,6 +2408,7 @@
// form the old key.
dex_data->profile_key = MigrateAnnotationInfo(new_profile_key, dex_data->profile_key);
profile_key_map_.Put(dex_data->profile_key, dex_data->profile_index);
+ *updated = true;
}
}
}
diff --git a/libprofile/profile/profile_compilation_info.h b/libprofile/profile/profile_compilation_info.h
index 4366078..76cbf9a 100644
--- a/libprofile/profile/profile_compilation_info.h
+++ b/libprofile/profile/profile_compilation_info.h
@@ -645,7 +645,10 @@
//
// If the new profile key would collide with an existing key (for a different dex)
// the method returns false. Otherwise it returns true.
- bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files);
+ //
+ // `updated` is set to true if any profile key has been updated by this method.
+ bool UpdateProfileKeys(const std::vector<std::unique_ptr<const DexFile>>& dex_files,
+ /*out*/ bool* updated);
// Checks if the profile is empty.
bool IsEmpty() const;
diff --git a/libprofile/profile/profile_compilation_info_test.cc b/libprofile/profile/profile_compilation_info_test.cc
index 8c9d0df..2ee34f2 100644
--- a/libprofile/profile/profile_compilation_info_test.cc
+++ b/libprofile/profile/profile_compilation_info_test.cc
@@ -40,17 +40,23 @@
CommonArtTest::SetUp();
allocator_.reset(new ArenaAllocator(&pool_));
- dex1 = BuildDex("location1", /*checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 101);
- dex2 = BuildDex("location2", /*checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 102);
- dex3 = BuildDex("location3", /*checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 103);
- dex4 = BuildDex("location4", /*checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 104);
+ dex1 = BuildDex("location1", /*location_checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 101);
+ dex2 = BuildDex("location2", /*location_checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 102);
+ dex3 = BuildDex("location3", /*location_checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 103);
+ dex4 = BuildDex("location4", /*location_checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 104);
- dex1_checksum_missmatch =
- BuildDex("location1", /*checksum=*/ 12, "LUnique1;", /*num_method_ids=*/ 101);
- dex1_renamed =
- BuildDex("location1-renamed", /*checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 101);
- dex2_renamed =
- BuildDex("location2-renamed", /*checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 102);
+ dex1_checksum_missmatch = BuildDex("location1",
+ /*location_checksum=*/ 12,
+ "LUnique1;",
+ /*num_method_ids=*/ 101);
+ dex1_renamed = BuildDex("location1-renamed",
+ /*location_checksum=*/ 1,
+ "LUnique1;",
+ /*num_method_ids=*/ 101);
+ dex2_renamed = BuildDex("location2-renamed",
+ /*location_checksum=*/ 2,
+ "LUnique2;",
+ /*num_method_ids=*/ 102);
}
protected:
@@ -350,10 +356,16 @@
TEST_F(ProfileCompilationInfoTest, SaveMaxMethods) {
ScratchFile profile;
- const DexFile* dex_max1 = BuildDex(
- "location-max1", /*checksum=*/ 5, "LUniqueMax1;", kMaxMethodIds, kMaxClassIds);
- const DexFile* dex_max2 = BuildDex(
- "location-max2", /*checksum=*/ 6, "LUniqueMax2;", kMaxMethodIds, kMaxClassIds);
+ const DexFile* dex_max1 = BuildDex("location-max1",
+ /*location_checksum=*/ 5,
+ "LUniqueMax1;",
+ kMaxMethodIds,
+ kMaxClassIds);
+ const DexFile* dex_max2 = BuildDex("location-max2",
+ /*location_checksum=*/ 6,
+ "LUniqueMax2;",
+ kMaxMethodIds,
+ kMaxClassIds);
ProfileCompilationInfo saved_info;
@@ -733,11 +745,11 @@
// Save a few methods.
for (uint16_t i = 0; i < std::numeric_limits<ProfileIndexType>::max(); i++) {
std::string location = std::to_string(i);
- const DexFile* dex = BuildDex(location, /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+ const DexFile* dex = BuildDex(location, /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
ASSERT_TRUE(AddMethod(&info, dex, /*method_idx=*/ 0));
}
// Add an extra dex file.
- const DexFile* dex = BuildDex("-1", /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+ const DexFile* dex = BuildDex("-1", /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
ASSERT_FALSE(AddMethod(&info, dex, /*method_idx=*/ 0));
}
@@ -746,11 +758,11 @@
// Save a few methods.
for (uint16_t i = 0; i < std::numeric_limits<ProfileIndexType>::max(); i++) {
std::string location = std::to_string(i);
- const DexFile* dex = BuildDex(location, /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+ const DexFile* dex = BuildDex(location, /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
ASSERT_TRUE(AddMethod(&info, dex, /*method_idx=*/ 0));
}
// Add an extra dex file.
- const DexFile* dex = BuildDex("-1", /*checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
+ const DexFile* dex = BuildDex("-1", /*location_checksum=*/ 1, "LC;", /*num_method_ids=*/ 1);
ASSERT_FALSE(AddMethod(&info, dex, /*method_idx=*/ 0));
}
@@ -944,7 +956,9 @@
AddMethod(&info, dex2, /*method_idx=*/ 0);
// Update the profile keys based on the original dex files
- ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+ bool updated = false;
+ ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
+ ASSERT_TRUE(updated);
// Verify that we find the methods when searched with the original dex files.
for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -970,7 +984,9 @@
AddMethod(&info, dex2, /*method_idx=*/ 0, Hotness::kFlagHot, annotation);
// Update the profile keys based on the original dex files
- ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+ bool updated = false;
+ ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
+ ASSERT_TRUE(updated);
// Verify that we find the methods when searched with the original dex files.
for (const std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -993,7 +1009,9 @@
AddMethod(&info, dex2, /*method_idx=*/ 0);
// Update the profile keys based on the original dex files.
- ASSERT_TRUE(info.UpdateProfileKeys(dex_files));
+ bool updated = false;
+ ASSERT_TRUE(info.UpdateProfileKeys(dex_files, &updated));
+ ASSERT_FALSE(updated);
// Verify that we did not perform any update and that we cannot find anything with the new
// location.
@@ -1025,7 +1043,9 @@
// This will cause the rename to fail because an existing entry would already have that name.
AddMethod(&info, dex1_renamed, /*method_idx=*/ 0);
- ASSERT_FALSE(info.UpdateProfileKeys(dex_files));
+ bool updated = false;
+ ASSERT_FALSE(info.UpdateProfileKeys(dex_files, &updated));
+ ASSERT_FALSE(updated);
// Release the ownership as this is held by the test class;
for (std::unique_ptr<const DexFile>& dex : dex_files) {
@@ -1167,12 +1187,12 @@
ScratchFile profile;
const DexFile* dex1_1000 = BuildDex("location1_1000",
- /*checksum=*/ 7,
+ /*location_checksum=*/ 7,
"LC1_1000;",
/*num_method_ids=*/ 1u,
/*num_class_ids=*/ 1000u);
const DexFile* dex2_1000 = BuildDex("location2_1000",
- /*checksum=*/ 8,
+ /*location_checksum=*/ 8,
"LC2_1000;",
/*num_method_ids=*/ 1u,
/*num_class_ids=*/ 1000u);
diff --git a/oatdump/art_standalone_oatdump_tests.xml b/oatdump/art_standalone_oatdump_tests.xml
index bcd94ed..664ac6c 100644
--- a/oatdump/art_standalone_oatdump_tests.xml
+++ b/oatdump/art_standalone_oatdump_tests.xml
@@ -54,7 +54,8 @@
<!-- ART Mainline Module (external (AOSP) version). -->
<option name="mainline-module-package-name" value="com.android.art" />
</object>
-
+ <!-- Skip on HWASan. TODO(b/230394041): Re-enable -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.SkipHWASanModuleController" />
<!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
<object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
</configuration>
diff --git a/odrefresh/Android.bp b/odrefresh/Android.bp
index 8fee83a..55a1fde 100644
--- a/odrefresh/Android.bp
+++ b/odrefresh/Android.bp
@@ -47,7 +47,6 @@
],
static_libs: [
"libc++fs",
- "libtinyxml2",
],
tidy: true,
tidy_disabled_srcs: [":art-apex-cache-info"],
diff --git a/odrefresh/odr_compilation_log.cc b/odrefresh/odr_compilation_log.cc
index 9c50817..0c8dda8 100644
--- a/odrefresh/odr_compilation_log.cc
+++ b/odrefresh/odr_compilation_log.cc
@@ -185,6 +185,12 @@
return true;
}
+ // The backoff time is for avoiding too many failed attempts. It should not be applied if the last
+ // compilation was successful.
+ if (entries_.back().exit_code == ExitCode::kCompilationSuccess) {
+ return true;
+ }
+
if (trigger == OdrMetrics::Trigger::kApexVersionMismatch ||
trigger == OdrMetrics::Trigger::kDexFilesChanged) {
// Things have changed since the last run.
diff --git a/odrefresh/odr_compilation_log_test.cc b/odrefresh/odr_compilation_log_test.cc
index 46cea79..f28d849 100644
--- a/odrefresh/odr_compilation_log_test.cc
+++ b/odrefresh/odr_compilation_log_test.cc
@@ -109,7 +109,7 @@
/*apex_version=*/1,
/*last_update_millis=*/762,
OdrMetrics::Trigger::kApexVersionMismatch,
- ExitCode::kCompilationSuccess);
+ ExitCode::kCompilationFailed);
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kApexVersionMismatch));
ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kDexFilesChanged));
ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown));
@@ -180,8 +180,8 @@
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
ExitCode::kCompilationSuccess);
- ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
- ASSERT_FALSE(
+ ASSERT_TRUE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
+ ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 4));
ASSERT_TRUE(
ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time + kSecondsPerDay / 2));
@@ -382,7 +382,7 @@
kLastUpdateMillis,
OdrMetrics::Trigger::kApexVersionMismatch,
start_time,
- ExitCode::kCompilationSuccess);
+ ExitCode::kCompilationFailed);
ASSERT_FALSE(ocl.ShouldAttemptCompile(OdrMetrics::Trigger::kUnknown, start_time));
}
}
diff --git a/odrefresh/odr_config.h b/odrefresh/odr_config.h
index 0475466..7a88a1c 100644
--- a/odrefresh/odr_config.h
+++ b/odrefresh/odr_config.h
@@ -83,6 +83,7 @@
InstructionSet isa_;
std::string program_name_;
std::string system_server_classpath_;
+ std::string boot_image_compiler_filter_;
std::string system_server_compiler_filter_;
ZygoteKind zygote_kind_;
std::string boot_classpath_;
@@ -168,6 +169,9 @@
const std::string& GetSystemServerClasspath() const {
return system_server_classpath_;
}
+ const std::string& GetBootImageCompilerFilter() const {
+ return boot_image_compiler_filter_;
+ }
const std::string& GetSystemServerCompilerFilter() const {
return system_server_compiler_filter_;
}
@@ -204,6 +208,9 @@
system_server_classpath_ = classpath;
}
+ void SetBootImageCompilerFilter(const std::string& filter) {
+ boot_image_compiler_filter_ = filter;
+ }
void SetSystemServerCompilerFilter(const std::string& filter) {
system_server_compiler_filter_ = filter;
}
diff --git a/odrefresh/odr_fs_utils.cc b/odrefresh/odr_fs_utils.cc
index 22cc1b6..3ed8021 100644
--- a/odrefresh/odr_fs_utils.cc
+++ b/odrefresh/odr_fs_utils.cc
@@ -68,9 +68,12 @@
}
if (rmdir(dir_path.c_str()) != 0) {
- LOG(ERROR) << "Failed to delete '" << dir_path << "'";
- return false;
+ // It's possible that we are not able to remove the directory itself. For example, when
+ // odrefresh is running in CompOS, the staging dir is prepared beforehand passed to the VM as an
+ // FD. In this case, just log and ignore the error. It's okay to keep the directory.
+ LOG(WARNING) << "Failed to delete '" << dir_path << "'";
}
+
return true;
}
diff --git a/odrefresh/odr_metrics.cc b/odrefresh/odr_metrics.cc
index 4bddb17..a2ce51f 100644
--- a/odrefresh/odr_metrics.cc
+++ b/odrefresh/odr_metrics.cc
@@ -64,16 +64,16 @@
}
}
-void OdrMetrics::SetCompilationTime(int32_t seconds) {
+void OdrMetrics::SetCompilationTime(int32_t millis) {
switch (stage_) {
case Stage::kPrimaryBootClasspath:
- primary_bcp_compilation_seconds_ = seconds;
+ primary_bcp_compilation_millis_ = millis;
break;
case Stage::kSecondaryBootClasspath:
- secondary_bcp_compilation_seconds_ = seconds;
+ secondary_bcp_compilation_millis_ = millis;
break;
case Stage::kSystemServerClasspath:
- system_server_compilation_seconds_ = seconds;
+ system_server_compilation_millis_ = millis;
break;
case Stage::kCheck:
case Stage::kComplete:
@@ -119,28 +119,31 @@
if (!trigger_.has_value()) {
return false;
}
+ record->odrefresh_metrics_version = kOdrefreshMetricsVersion;
record->art_apex_version = art_apex_version_;
record->trigger = static_cast<uint32_t>(trigger_.value());
record->stage_reached = static_cast<uint32_t>(stage_);
record->status = static_cast<uint32_t>(status_);
- record->primary_bcp_compilation_seconds = primary_bcp_compilation_seconds_;
- record->secondary_bcp_compilation_seconds = secondary_bcp_compilation_seconds_;
- record->system_server_compilation_seconds = system_server_compilation_seconds_;
record->cache_space_free_start_mib = cache_space_free_start_mib_;
record->cache_space_free_end_mib = cache_space_free_end_mib_;
+ record->primary_bcp_compilation_millis = primary_bcp_compilation_millis_;
+ record->secondary_bcp_compilation_millis = secondary_bcp_compilation_millis_;
+ record->system_server_compilation_millis = system_server_compilation_millis_;
return true;
}
void OdrMetrics::WriteToFile(const std::string& path, const OdrMetrics* metrics) {
- OdrMetricsRecord record;
+ OdrMetricsRecord record{};
if (!metrics->ToRecord(&record)) {
LOG(ERROR) << "Attempting to report metrics without a compilation trigger.";
return;
}
- // Preserve order from frameworks/proto_logging/stats/atoms.proto in metrics file written.
- std::ofstream ofs(path);
- ofs << record;
+ const android::base::Result<void>& result = record.WriteToFile(path);
+ if (!result.ok()) {
+ LOG(ERROR) << "Failed to report metrics to file: " << path
+ << ", error: " << result.error().message();
+ }
}
} // namespace odrefresh
diff --git a/odrefresh/odr_metrics.h b/odrefresh/odr_metrics.h
index cd80bef..7ebc148 100644
--- a/odrefresh/odr_metrics.h
+++ b/odrefresh/odr_metrics.h
@@ -126,7 +126,7 @@
static int32_t GetFreeSpaceMiB(const std::string& path);
static void WriteToFile(const std::string& path, const OdrMetrics* metrics);
- void SetCompilationTime(int32_t seconds);
+ void SetCompilationTime(int32_t millis);
const std::string cache_directory_;
const std::string metrics_file_;
@@ -137,11 +137,11 @@
Stage stage_ = Stage::kUnknown;
Status status_ = Status::kUnknown;
- int32_t primary_bcp_compilation_seconds_ = 0;
- int32_t secondary_bcp_compilation_seconds_ = 0;
- int32_t system_server_compilation_seconds_ = 0;
int32_t cache_space_free_start_mib_ = 0;
int32_t cache_space_free_end_mib_ = 0;
+ int32_t primary_bcp_compilation_millis_ = 0;
+ int32_t secondary_bcp_compilation_millis_ = 0;
+ int32_t system_server_compilation_millis_ = 0;
friend class ScopedOdrCompilationTimer;
};
@@ -155,8 +155,8 @@
~ScopedOdrCompilationTimer() {
auto elapsed_time = std::chrono::steady_clock::now() - start_;
- auto elapsed_seconds = std::chrono::duration_cast<std::chrono::seconds>(elapsed_time);
- metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_seconds.count()));
+ auto elapsed_millis = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed_time);
+ metrics_.SetCompilationTime(static_cast<int32_t>(elapsed_millis.count()));
}
private:
diff --git a/odrefresh/odr_metrics_record.cc b/odrefresh/odr_metrics_record.cc
index fc135d3..b8c2f80 100644
--- a/odrefresh/odr_metrics_record.cc
+++ b/odrefresh/odr_metrics_record.cc
@@ -14,59 +14,110 @@
* limitations under the License.
*/
+#include "android-base/logging.h"
#include "odr_metrics_record.h"
+#include "tinyxml2.h"
#include <iosfwd>
-#include <istream>
-#include <ostream>
-#include <streambuf>
#include <string>
namespace art {
namespace odrefresh {
-std::istream& operator>>(std::istream& is, OdrMetricsRecord& record) {
- // Block I/O related exceptions
- auto saved_exceptions = is.exceptions();
- is.exceptions(std::ios_base::iostate {});
+namespace {
+android::base::Result<int64_t> ReadInt64(tinyxml2::XMLElement* parent, const char* name) {
+ tinyxml2::XMLElement* element = parent->FirstChildElement(name);
+ if (element == nullptr) {
+ return Errorf("Expected Odrefresh metric {} not found", name);
+ }
- // The order here matches the field order of MetricsRecord.
- is >> record.art_apex_version >> std::ws;
- is >> record.trigger >> std::ws;
- is >> record.stage_reached >> std::ws;
- is >> record.status >> std::ws;
- is >> record.primary_bcp_compilation_seconds >> std::ws;
- is >> record.secondary_bcp_compilation_seconds >> std::ws;
- is >> record.system_server_compilation_seconds >> std::ws;
- is >> record.cache_space_free_start_mib >> std::ws;
- is >> record.cache_space_free_end_mib >> std::ws;
-
- // Restore I/O related exceptions
- is.exceptions(saved_exceptions);
- return is;
+ int64_t metric;
+ tinyxml2::XMLError result = element->QueryInt64Text(&metric);
+ if (result == tinyxml2::XML_SUCCESS) {
+ return metric;
+ } else {
+ return Errorf("Odrefresh metric {} is not an int64", name);
+ }
}
-std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record) {
- static const char kSpace = ' ';
+android::base::Result<int32_t> ReadInt32(tinyxml2::XMLElement* parent, const char* name) {
+ tinyxml2::XMLElement* element = parent->FirstChildElement(name);
+ if (element == nullptr) {
+ return Errorf("Expected Odrefresh metric {} not found", name);
+ }
- // Block I/O related exceptions
- auto saved_exceptions = os.exceptions();
- os.exceptions(std::ios_base::iostate {});
+ int32_t metric;
+ tinyxml2::XMLError result = element->QueryIntText(&metric);
+ if (result == tinyxml2::XML_SUCCESS) {
+ return metric;
+ } else {
+ return Errorf("Odrefresh metric {} is not an int32", name);
+ }
+}
+
+template <typename T>
+void AddMetric(tinyxml2::XMLElement* parent, const char* name, const T& value) {
+ parent->InsertNewChildElement(name)->SetText(value);
+}
+} // namespace
+
+android::base::Result<void> OdrMetricsRecord::ReadFromFile(const std::string& filename) {
+ tinyxml2::XMLDocument xml_document;
+ tinyxml2::XMLError result = xml_document.LoadFile(filename.data());
+ if (result != tinyxml2::XML_SUCCESS) {
+ return android::base::Error() << xml_document.ErrorStr();
+ }
+
+ tinyxml2::XMLElement* metrics = xml_document.FirstChildElement("odrefresh_metrics");
+ if (metrics == nullptr) {
+ return Errorf("odrefresh_metrics element not found in {}", filename);
+ }
+
+ odrefresh_metrics_version = OR_RETURN(ReadInt32(metrics, "odrefresh_metrics_version"));
+ if (odrefresh_metrics_version != kOdrefreshMetricsVersion) {
+ return Errorf("odrefresh_metrics_version {} is different than expected ({})",
+ odrefresh_metrics_version,
+ kOdrefreshMetricsVersion);
+ }
+
+ art_apex_version = OR_RETURN(ReadInt64(metrics, "art_apex_version"));
+ trigger = OR_RETURN(ReadInt32(metrics, "trigger"));
+ stage_reached = OR_RETURN(ReadInt32(metrics, "stage_reached"));
+ status = OR_RETURN(ReadInt32(metrics, "status"));
+ cache_space_free_start_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_start_mib"));
+ cache_space_free_end_mib = OR_RETURN(ReadInt32(metrics, "cache_space_free_end_mib"));
+ primary_bcp_compilation_millis = OR_RETURN(
+ ReadInt32(metrics, "primary_bcp_compilation_millis"));
+ secondary_bcp_compilation_millis = OR_RETURN(
+ ReadInt32(metrics, "secondary_bcp_compilation_millis"));
+ system_server_compilation_millis = OR_RETURN(
+ ReadInt32(metrics, "system_server_compilation_millis"));
+ return {};
+}
+
+android::base::Result<void> OdrMetricsRecord::WriteToFile(const std::string& filename) const {
+ tinyxml2::XMLDocument xml_document;
+ tinyxml2::XMLElement* metrics = xml_document.NewElement("odrefresh_metrics");
+ xml_document.InsertEndChild(metrics);
// The order here matches the field order of MetricsRecord.
- os << record.art_apex_version << kSpace;
- os << record.trigger << kSpace;
- os << record.stage_reached << kSpace;
- os << record.status << kSpace;
- os << record.primary_bcp_compilation_seconds << kSpace;
- os << record.secondary_bcp_compilation_seconds << kSpace;
- os << record.system_server_compilation_seconds << kSpace;
- os << record.cache_space_free_start_mib << kSpace;
- os << record.cache_space_free_end_mib << std::endl;
+ AddMetric(metrics, "odrefresh_metrics_version", odrefresh_metrics_version);
+ AddMetric(metrics, "art_apex_version", art_apex_version);
+ AddMetric(metrics, "trigger", trigger);
+ AddMetric(metrics, "stage_reached", stage_reached);
+ AddMetric(metrics, "status", status);
+ AddMetric(metrics, "cache_space_free_start_mib", cache_space_free_start_mib);
+ AddMetric(metrics, "cache_space_free_end_mib", cache_space_free_end_mib);
+ AddMetric(metrics, "primary_bcp_compilation_millis", primary_bcp_compilation_millis);
+ AddMetric(metrics, "secondary_bcp_compilation_millis", secondary_bcp_compilation_millis);
+ AddMetric(metrics, "system_server_compilation_millis", system_server_compilation_millis);
- // Restore I/O related exceptions
- os.exceptions(saved_exceptions);
- return os;
+ tinyxml2::XMLError result = xml_document.SaveFile(filename.data(), /*compact=*/true);
+ if (result == tinyxml2::XML_SUCCESS) {
+ return {};
+ } else {
+ return android::base::Error() << xml_document.ErrorStr();
+ }
}
} // namespace odrefresh
diff --git a/odrefresh/odr_metrics_record.h b/odrefresh/odr_metrics_record.h
index 9dd51a6..fa95337 100644
--- a/odrefresh/odr_metrics_record.h
+++ b/odrefresh/odr_metrics_record.h
@@ -20,43 +20,42 @@
#include <cstdint>
#include <iosfwd> // For forward-declaration of std::string.
+#include "android-base/result.h"
+#include "tinyxml2.h"
+
namespace art {
namespace odrefresh {
// Default location for storing metrics from odrefresh.
-constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.txt";
+constexpr const char* kOdrefreshMetricsFile = "/data/misc/odrefresh/odrefresh-metrics.xml";
+
+// Initial OdrefreshMetrics version
+static constexpr int32_t kOdrefreshMetricsVersion = 2;
// MetricsRecord is a simpler container for Odrefresh metric values reported to statsd. The order
// and types of fields here mirror definition of `OdrefreshReported` in
// frameworks/proto_logging/stats/atoms.proto.
struct OdrMetricsRecord {
+ int32_t odrefresh_metrics_version;
int64_t art_apex_version;
int32_t trigger;
int32_t stage_reached;
int32_t status;
- int32_t primary_bcp_compilation_seconds;
- int32_t secondary_bcp_compilation_seconds;
- int32_t system_server_compilation_seconds;
int32_t cache_space_free_start_mib;
int32_t cache_space_free_end_mib;
+ int32_t primary_bcp_compilation_millis;
+ int32_t secondary_bcp_compilation_millis;
+ int32_t system_server_compilation_millis;
+
+ // Reads a `MetricsRecord` from an XML file.
+ // Returns an error if the XML document was not found or parsed correctly.
+ android::base::Result<void> ReadFromFile(const std::string& filename);
+
+ // Writes a `MetricsRecord` to an XML file.
+ // Returns an error if the XML document was not saved correctly.
+ android::base::Result<void> WriteToFile(const std::string& filename) const;
};
-// Read a `MetricsRecord` from an `istream`.
-//
-// This method blocks istream related exceptions, the caller should check `is.fail()` is false after
-// calling.
-//
-// Returns `is`.
-std::istream& operator>>(std::istream& is, OdrMetricsRecord& record);
-
-// Write a `MetricsRecord` to an `ostream`.
-//
-// This method blocks ostream related exceptions, the caller should check `os.fail()` is false after
-// calling.
-//
-// Returns `os`
-std::ostream& operator<<(std::ostream& os, const OdrMetricsRecord& record);
-
} // namespace odrefresh
} // namespace art
diff --git a/odrefresh/odr_metrics_record_test.cc b/odrefresh/odr_metrics_record_test.cc
index dd739d6..fbb5b99 100644
--- a/odrefresh/odr_metrics_record_test.cc
+++ b/odrefresh/odr_metrics_record_test.cc
@@ -20,6 +20,8 @@
#include <fstream>
+#include "android-base/result-gmock.h"
+#include "android-base/stringprintf.h"
#include "base/common_art_test.h"
namespace art {
@@ -27,86 +29,124 @@
class OdrMetricsRecordTest : public CommonArtTest {};
+using android::base::testing::Ok;
+using android::base::testing::HasError;
+using android::base::testing::WithMessage;
+
TEST_F(OdrMetricsRecordTest, HappyPath) {
- const OdrMetricsRecord expected {
+ const OdrMetricsRecord expected{
+ .odrefresh_metrics_version = art::odrefresh::kOdrefreshMetricsVersion,
.art_apex_version = 0x01233456'789abcde,
.trigger = 0x01020304,
.stage_reached = 0x11121314,
.status = 0x21222324,
- .primary_bcp_compilation_seconds = 0x31323334,
- .secondary_bcp_compilation_seconds = 0x41424344,
- .system_server_compilation_seconds = 0x51525354,
.cache_space_free_start_mib = 0x61626364,
- .cache_space_free_end_mib = 0x71727374
+ .cache_space_free_end_mib = 0x71727374,
+ .primary_bcp_compilation_millis = 0x31323334,
+ .secondary_bcp_compilation_millis = 0x41424344,
+ .system_server_compilation_millis = 0x51525354
};
ScratchDir dir(/*keep_files=*/false);
- std::string file_path = dir.GetPath() + "/metrics-record.txt";
-
- {
- std::ofstream ofs(file_path);
- ofs << expected;
- ASSERT_FALSE(ofs.fail());
- ofs.close();
- }
+ std::string file_path = dir.GetPath() + "/metrics-record.xml";
+ ASSERT_THAT(expected.WriteToFile(file_path), Ok());
OdrMetricsRecord actual {};
- {
- std::ifstream ifs(file_path);
- ifs >> actual;
- ASSERT_TRUE(ifs.eof());
- }
+ ASSERT_THAT(actual.ReadFromFile(file_path), Ok());
+ ASSERT_EQ(expected.odrefresh_metrics_version, actual.odrefresh_metrics_version);
ASSERT_EQ(expected.art_apex_version, actual.art_apex_version);
ASSERT_EQ(expected.trigger, actual.trigger);
ASSERT_EQ(expected.stage_reached, actual.stage_reached);
ASSERT_EQ(expected.status, actual.status);
- ASSERT_EQ(expected.primary_bcp_compilation_seconds, actual.primary_bcp_compilation_seconds);
- ASSERT_EQ(expected.secondary_bcp_compilation_seconds, actual.secondary_bcp_compilation_seconds);
- ASSERT_EQ(expected.system_server_compilation_seconds, actual.system_server_compilation_seconds);
ASSERT_EQ(expected.cache_space_free_start_mib, actual.cache_space_free_start_mib);
ASSERT_EQ(expected.cache_space_free_end_mib, actual.cache_space_free_end_mib);
+ ASSERT_EQ(expected.primary_bcp_compilation_millis, actual.primary_bcp_compilation_millis);
+ ASSERT_EQ(expected.secondary_bcp_compilation_millis, actual.secondary_bcp_compilation_millis);
+ ASSERT_EQ(expected.system_server_compilation_millis, actual.system_server_compilation_millis);
ASSERT_EQ(0, memcmp(&expected, &actual, sizeof(expected)));
}
TEST_F(OdrMetricsRecordTest, EmptyInput) {
ScratchDir dir(/*keep_files=*/false);
- std::string file_path = dir.GetPath() + "/metrics-record.txt";
+ std::string file_path = dir.GetPath() + "/metrics-record.xml";
- std::ifstream ifs(file_path);
- OdrMetricsRecord record;
- ifs >> record;
-
- ASSERT_TRUE(ifs.fail());
- ASSERT_TRUE(!ifs);
+ OdrMetricsRecord record{};
+ ASSERT_THAT(record.ReadFromFile(file_path), testing::Not(Ok()));
}
-TEST_F(OdrMetricsRecordTest, ClosedInput) {
+TEST_F(OdrMetricsRecordTest, UnexpectedInput) {
ScratchDir dir(/*keep_files=*/false);
- std::string file_path = dir.GetPath() + "/metrics-record.txt";
-
- std::ifstream ifs(file_path);
- ifs.close();
-
- OdrMetricsRecord record;
- ifs >> record;
-
- ASSERT_TRUE(ifs.fail());
- ASSERT_TRUE(!ifs);
-}
-
-TEST_F(OdrMetricsRecordTest, ClosedOutput) {
- ScratchDir dir(/*keep_files=*/false);
- std::string file_path = dir.GetPath() + "/metrics-record.txt";
+ std::string file_path = dir.GetPath() + "/metrics-record.xml";
std::ofstream ofs(file_path);
+ ofs << "<not_odrefresh_metrics></not_odrefresh_metrics>";
ofs.close();
- OdrMetricsRecord record {};
- ofs << record;
+ OdrMetricsRecord record{};
+ ASSERT_THAT(
+ record.ReadFromFile(file_path),
+ HasError(WithMessage("odrefresh_metrics element not found in " + file_path)));
+}
- ASSERT_TRUE(ofs.fail());
- ASSERT_TRUE(!ofs.good());
+TEST_F(OdrMetricsRecordTest, ExpectedElementNotFound) {
+ ScratchDir dir(/*keep_files=*/false);
+ std::string file_path = dir.GetPath() + "/metrics-record.xml";
+
+ std::ofstream ofs(file_path);
+ ofs << "<odrefresh_metrics>";
+ ofs << "<not_valid_metric>25</not_valid_metric>";
+ ofs << "</odrefresh_metrics>";
+ ofs.close();
+
+ OdrMetricsRecord record{};
+ ASSERT_THAT(
+ record.ReadFromFile(file_path),
+ HasError(WithMessage("Expected Odrefresh metric odrefresh_metrics_version not found")));
+}
+
+TEST_F(OdrMetricsRecordTest, UnexpectedOdrefreshMetricsVersion) {
+ ScratchDir dir(/*keep_files=*/false);
+ std::string file_path = dir.GetPath() + "/metrics-record.xml";
+
+ std::ofstream ofs(file_path);
+ ofs << "<odrefresh_metrics>";
+ ofs << "<odrefresh_metrics_version>0</odrefresh_metrics_version>";
+ ofs << "</odrefresh_metrics>";
+ ofs.close();
+
+ OdrMetricsRecord record{};
+ std::string expected_error = android::base::StringPrintf(
+ "odrefresh_metrics_version 0 is different than expected (%d)",
+ kOdrefreshMetricsVersion);
+ ASSERT_THAT(record.ReadFromFile(file_path),
+ HasError(WithMessage(expected_error)));
+}
+
+TEST_F(OdrMetricsRecordTest, UnexpectedType) {
+ ScratchDir dir(/*keep_files=*/false);
+ std::string file_path = dir.GetPath() + "/metrics-record.xml";
+
+ std::ofstream ofs(file_path);
+ ofs << "<odrefresh_metrics>";
+ ofs << "<odrefresh_metrics_version>" << kOdrefreshMetricsVersion
+ << "</odrefresh_metrics_version>";
+ ofs << "<art_apex_version>81966764218039518</art_apex_version>";
+ ofs << "<trigger>16909060</trigger>";
+ ofs << "<stage_reached>286397204</stage_reached>";
+ ofs << "<status>abcd</status>"; // It should be an int32.
+ ofs << "<cache_space_free_start_mib>1633837924</cache_space_free_start_mib>";
+ ofs << "<cache_space_free_end_mib>1903326068</cache_space_free_end_mib>";
+ ofs << "<primary_bcp_compilation_millis>825373492</primary_bcp_compilation_millis>";
+ ofs << "<secondary_bcp_compilation_millis>1094861636</secondary_bcp_compilation_millis>";
+ ofs << "<system_server_compilation_millis>1364349780</system_server_compilation_millis>";
+ ofs << "</odrefresh_metrics>";
+ ofs.close();
+
+ OdrMetricsRecord record{};
+ ASSERT_THAT(
+ record.ReadFromFile(file_path),
+ HasError(WithMessage("Odrefresh metric status is not an int32")));
}
} // namespace odrefresh
diff --git a/odrefresh/odr_metrics_test.cc b/odrefresh/odr_metrics_test.cc
index 4519f00..f222caa 100644
--- a/odrefresh/odr_metrics_test.cc
+++ b/odrefresh/odr_metrics_test.cc
@@ -24,6 +24,7 @@
#include <memory>
#include <string>
+#include "android-base/result-gmock.h"
#include "base/common_art_test.h"
namespace art {
@@ -35,7 +36,7 @@
CommonArtTest::SetUp();
scratch_dir_ = std::make_unique<ScratchDir>();
- metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.txt";
+ metrics_file_path_ = scratch_dir_->GetPath() + "/metrics.xml";
cache_directory_ = scratch_dir_->GetPath() + "/dir";
mkdir(cache_directory_.c_str(), S_IRWXU);
}
@@ -158,10 +159,10 @@
EXPECT_TRUE(metrics.ToRecord(&record));
EXPECT_EQ(OdrMetrics::Stage::kPrimaryBootClasspath,
enum_cast<OdrMetrics::Stage>(record.stage_reached));
- EXPECT_NE(0, record.primary_bcp_compilation_seconds);
- EXPECT_GT(10, record.primary_bcp_compilation_seconds);
- EXPECT_EQ(0, record.secondary_bcp_compilation_seconds);
- EXPECT_EQ(0, record.system_server_compilation_seconds);
+ EXPECT_NE(0, record.primary_bcp_compilation_millis);
+ EXPECT_GT(10'000, record.primary_bcp_compilation_millis);
+ EXPECT_EQ(0, record.secondary_bcp_compilation_millis);
+ EXPECT_EQ(0, record.system_server_compilation_millis);
// Secondary boot classpath compilation time.
{
@@ -172,10 +173,10 @@
EXPECT_TRUE(metrics.ToRecord(&record));
EXPECT_EQ(OdrMetrics::Stage::kSecondaryBootClasspath,
enum_cast<OdrMetrics::Stage>(record.stage_reached));
- EXPECT_NE(0, record.primary_bcp_compilation_seconds);
- EXPECT_NE(0, record.secondary_bcp_compilation_seconds);
- EXPECT_GT(10, record.secondary_bcp_compilation_seconds);
- EXPECT_EQ(0, record.system_server_compilation_seconds);
+ EXPECT_NE(0, record.primary_bcp_compilation_millis);
+ EXPECT_NE(0, record.secondary_bcp_compilation_millis);
+ EXPECT_GT(10'000, record.secondary_bcp_compilation_millis);
+ EXPECT_EQ(0, record.system_server_compilation_millis);
// system_server classpath compilation time.
{
@@ -186,10 +187,10 @@
EXPECT_TRUE(metrics.ToRecord(&record));
EXPECT_EQ(OdrMetrics::Stage::kSystemServerClasspath,
enum_cast<OdrMetrics::Stage>(record.stage_reached));
- EXPECT_NE(0, record.primary_bcp_compilation_seconds);
- EXPECT_NE(0, record.secondary_bcp_compilation_seconds);
- EXPECT_NE(0, record.system_server_compilation_seconds);
- EXPECT_GT(10, record.system_server_compilation_seconds);
+ EXPECT_NE(0, record.primary_bcp_compilation_millis);
+ EXPECT_NE(0, record.secondary_bcp_compilation_millis);
+ EXPECT_NE(0, record.system_server_compilation_millis);
+ EXPECT_GT(10'000, record.system_server_compilation_millis);
}
TEST_F(OdrMetricsTest, CacheSpaceValuesAreUpdated) {
@@ -207,11 +208,8 @@
EXPECT_EQ(0, snap.cache_space_free_end_mib);
}
- OdrMetricsRecord on_disk;
- std::ifstream ifs(GetMetricsFilePath());
- EXPECT_TRUE(ifs);
- ifs >> on_disk;
- EXPECT_TRUE(ifs);
+ OdrMetricsRecord on_disk{};
+ EXPECT_THAT(on_disk.ReadFromFile(GetMetricsFilePath()), android::base::testing::Ok());
EXPECT_EQ(snap.cache_space_free_start_mib, on_disk.cache_space_free_start_mib);
EXPECT_NE(0, on_disk.cache_space_free_end_mib);
}
diff --git a/odrefresh/odr_statslog_android.cc b/odrefresh/odr_statslog_android.cc
index 7db348e..ee173d4 100644
--- a/odrefresh/odr_statslog_android.cc
+++ b/odrefresh/odr_statslog_android.cc
@@ -103,16 +103,11 @@
bool ReadValues(const char* metrics_file,
/*out*/ OdrMetricsRecord* record,
/*out*/ std::string* error_msg) {
- std::ifstream ifs(metrics_file);
- if (!ifs) {
- *error_msg = android::base::StringPrintf(
- "metrics file '%s' could not be opened: %s", metrics_file, strerror(errno));
- return false;
- }
-
- ifs >> *record;
- if (!ifs) {
- *error_msg = "file parsing error.";
+ const android::base::Result<void>& result = record->ReadFromFile(metrics_file);
+ if (!result.ok()) {
+ *error_msg = android::base::StringPrintf("Unable to open or parse metrics file %s (error: %s)",
+ metrics_file,
+ result.error().message().data());
return false;
}
@@ -151,16 +146,20 @@
// Write values to statsd. The order of values passed is the same as the order of the
// fields in OdrMetricsRecord.
- int bytes_written = art::metrics::statsd::stats_write(metrics::statsd::ODREFRESH_REPORTED,
- record.art_apex_version,
- record.trigger,
- record.stage_reached,
- record.status,
- record.primary_bcp_compilation_seconds,
- record.secondary_bcp_compilation_seconds,
- record.system_server_compilation_seconds,
- record.cache_space_free_start_mib,
- record.cache_space_free_end_mib);
+ int bytes_written = art::metrics::statsd::stats_write(
+ metrics::statsd::ODREFRESH_REPORTED,
+ record.art_apex_version,
+ record.trigger,
+ record.stage_reached,
+ record.status,
+ record.primary_bcp_compilation_millis / 1000,
+ record.secondary_bcp_compilation_millis / 1000,
+ record.system_server_compilation_millis / 1000,
+ record.cache_space_free_start_mib,
+ record.cache_space_free_end_mib,
+ record.primary_bcp_compilation_millis,
+ record.secondary_bcp_compilation_millis,
+ record.system_server_compilation_millis);
if (bytes_written <= 0) {
*error_msg = android::base::StringPrintf("stats_write returned %d", bytes_written);
return false;
diff --git a/odrefresh/odrefresh.cc b/odrefresh/odrefresh.cc
index f829bb8..ba32b0a 100644
--- a/odrefresh/odrefresh.cc
+++ b/odrefresh/odrefresh.cc
@@ -101,10 +101,10 @@
constexpr const char* kCacheInfoFile = "cache-info.xml";
// Maximum execution time for odrefresh from start to end.
-constexpr time_t kMaximumExecutionSeconds = 300;
+constexpr time_t kMaximumExecutionSeconds = 480;
// Maximum execution time for any child process spawned.
-constexpr time_t kMaxChildProcessSeconds = 90;
+constexpr time_t kMaxChildProcessSeconds = 120;
constexpr mode_t kFileMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
@@ -407,7 +407,8 @@
args.emplace_back(Concatenate({"--instruction-set=", isa_str}));
}
-void AddDex2OatProfileAndCompilerFilter(
+// Returns true if any profile has been added.
+bool AddDex2OatProfile(
/*inout*/ std::vector<std::string>& args,
/*inout*/ std::vector<std::unique_ptr<File>>& output_files,
const std::vector<std::string>& profile_paths) {
@@ -420,12 +421,7 @@
has_any_profile = true;
}
}
-
- if (has_any_profile) {
- args.emplace_back("--compiler-filter=speed-profile");
- } else {
- args.emplace_back("--compiler-filter=speed");
- }
+ return has_any_profile;
}
bool AddBootClasspathFds(/*inout*/ std::vector<std::string>& args,
@@ -1444,8 +1440,18 @@
std::vector<std::unique_ptr<File>> readonly_files_raii;
const std::string art_boot_profile_file = GetArtRoot() + "/etc/boot-image.prof";
const std::string framework_boot_profile_file = GetAndroidRoot() + "/etc/boot-image.prof";
- AddDex2OatProfileAndCompilerFilter(args, readonly_files_raii,
- {art_boot_profile_file, framework_boot_profile_file});
+ bool has_any_profile = AddDex2OatProfile(
+ args, readonly_files_raii, {art_boot_profile_file, framework_boot_profile_file});
+ if (!has_any_profile) {
+ *error_msg = "Missing boot image profile";
+ return false;
+ }
+ const std::string& compiler_filter = config_.GetBootImageCompilerFilter();
+ if (!compiler_filter.empty()) {
+ args.emplace_back("--compiler-filter=" + compiler_filter);
+ } else {
+ args.emplace_back("--compiler-filter=speed-profile");
+ }
// Compile as a single image for fewer files and slightly less memory overhead.
args.emplace_back("--single-image");
@@ -1606,11 +1612,14 @@
const std::string jar_name(android::base::Basename(jar));
const std::string profile = Concatenate({GetAndroidRoot(), "/framework/", jar_name, ".prof"});
- std::string compiler_filter = config_.GetSystemServerCompilerFilter();
- if (compiler_filter == "speed-profile") {
- AddDex2OatProfileAndCompilerFilter(args, readonly_files_raii, {profile});
- } else {
+ bool has_any_profile = AddDex2OatProfile(args, readonly_files_raii, {profile});
+ const std::string& compiler_filter = config_.GetSystemServerCompilerFilter();
+ if (!compiler_filter.empty()) {
args.emplace_back("--compiler-filter=" + compiler_filter);
+ } else if (has_any_profile) {
+ args.emplace_back("--compiler-filter=speed-profile");
+ } else {
+ args.emplace_back("--compiler-filter=speed");
}
const std::string image_location = GetSystemServerImagePath(/*on_system=*/false, jar);
diff --git a/odrefresh/odrefresh_main.cc b/odrefresh/odrefresh_main.cc
index 58ef28f..6505c51 100644
--- a/odrefresh/odrefresh_main.cc
+++ b/odrefresh/odrefresh_main.cc
@@ -144,6 +144,8 @@
config->SetArtifactDirectory(GetApexDataDalvikCacheDirectory(art::InstructionSet::kNone));
} else if (ArgumentMatches(arg, "--zygote-arch=", &value)) {
zygote = value;
+ } else if (ArgumentMatches(arg, "--boot-image-compiler-filter=", &value)) {
+ config->SetBootImageCompilerFilter(value);
} else if (ArgumentMatches(arg, "--system-server-compiler-filter=", &value)) {
config->SetSystemServerCompilerFilter(value);
} else if (ArgumentMatches(arg, "--staging-dir=", &value)) {
@@ -172,7 +174,7 @@
config->SetZygoteKind(zygote_kind);
if (config->GetSystemServerCompilerFilter().empty()) {
- std::string filter = GetProperty("dalvik.vm.systemservercompilerfilter", "speed");
+ std::string filter = GetProperty("dalvik.vm.systemservercompilerfilter", "");
config->SetSystemServerCompilerFilter(filter);
}
@@ -232,6 +234,9 @@
UsageMsg("--staging-dir=<DIR> Write temporary artifacts to <DIR> rather than");
UsageMsg(" .../staging");
UsageMsg("--zygote-arch=<STRING> Zygote kind that overrides ro.zygote");
+ UsageMsg("--boot-image-compiler-filter=<STRING>");
+ UsageMsg(" Compiler filter for the boot image. Default: ");
+ UsageMsg(" speed-profile");
UsageMsg("--system-server-compiler-filter=<STRING>");
UsageMsg(" Compiler filter that overrides");
UsageMsg(" dalvik.vm.systemservercompilerfilter");
diff --git a/odrefresh/odrefresh_test.cc b/odrefresh/odrefresh_test.cc
index ae7cc78..d28c0d8 100644
--- a/odrefresh/odrefresh_test.cc
+++ b/odrefresh/odrefresh_test.cc
@@ -71,14 +71,14 @@
public:
// A workaround to avoid MOCK_METHOD on a method with an `std::string*` parameter, which will lead
// to a conflict between gmock and android-base/logging.h (b/132668253).
- int ExecAndReturnCode(std::vector<std::string>& arg_vector,
- time_t,
+ int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+ int,
bool*,
std::string*) const override {
return DoExecAndReturnCode(arg_vector);
}
- MOCK_METHOD(int, DoExecAndReturnCode, (std::vector<std::string> & arg_vector), (const));
+ MOCK_METHOD(int, DoExecAndReturnCode, (const std::vector<std::string>& arg_vector), (const));
};
// Matches a flag that starts with `flag` and is a colon-separated list that contains an element
@@ -202,7 +202,7 @@
config_.SetStandaloneSystemServerJars(Concatenate({services_foo_jar_, ":", services_bar_jar_}));
config_.SetIsa(InstructionSet::kX86_64);
config_.SetZygoteKind(ZygoteKind::kZygote64_32);
- config_.SetSystemServerCompilerFilter("speed"); // specify a default
+ config_.SetSystemServerCompilerFilter("");
config_.SetArtifactDirectory(dalvik_cache_dir_);
std::string staging_dir = dalvik_cache_dir_ + "/staging";
@@ -255,11 +255,41 @@
Contains(Concatenate({"--dex-file=", framework_jar_})),
Contains(FlagContains("--dex-fd=", FdOf(core_oj_jar_))),
Contains(FlagContains("--dex-fd=", FdOf(framework_jar_))),
- Contains(FlagContains("--profile-file-fd=", FdOf(art_profile_))),
- Contains(FlagContains("--profile-file-fd=", FdOf(framework_profile_))),
Contains(Concatenate({"--oat-location=", dalvik_cache_dir_, "/x86_64/boot.oat"})),
- Contains(HasSubstr("--base=")),
- Contains("--compiler-filter=speed-profile"))))
+ Contains(HasSubstr("--base=")))))
+ .WillOnce(Return(0));
+
+ EXPECT_EQ(odrefresh_->Compile(*metrics_,
+ CompilationOptions{
+ .compile_boot_classpath_for_isas = {InstructionSet::kX86_64},
+ }),
+ ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, BootClasspathJarsWithExplicitCompilerFilter) {
+ config_.SetBootImageCompilerFilter("speed");
+
+ // Profiles should still be passed.
+ EXPECT_CALL(*mock_exec_utils_,
+ DoExecAndReturnCode(
+ AllOf(Contains(FlagContains("--profile-file-fd=", FdOf(art_profile_))),
+ Contains(FlagContains("--profile-file-fd=", FdOf(framework_profile_))),
+ Contains("--compiler-filter=speed"))))
+ .WillOnce(Return(0));
+
+ EXPECT_EQ(odrefresh_->Compile(*metrics_,
+ CompilationOptions{
+ .compile_boot_classpath_for_isas = {InstructionSet::kX86_64},
+ }),
+ ExitCode::kCompilationSuccess);
+}
+
+TEST_F(OdRefreshTest, BootClasspathJarsWithDefaultCompilerFilter) {
+ EXPECT_CALL(*mock_exec_utils_,
+ DoExecAndReturnCode(
+ AllOf(Contains(FlagContains("--profile-file-fd=", FdOf(art_profile_))),
+ Contains(FlagContains("--profile-file-fd=", FdOf(framework_profile_))),
+ Contains("--compiler-filter=speed-profile"))))
.WillOnce(Return(0));
EXPECT_EQ(odrefresh_->Compile(*metrics_,
@@ -362,9 +392,11 @@
ExitCode::kCompilationSuccess);
}
-TEST_F(OdRefreshTest, CompileSetsCompilerFilterToSpeed) {
- // Test setup: use "speed" compiler filter.
- config_.SetSystemServerCompilerFilter("speed");
+// Test setup: The compiler filter is explicitly set to "speed-profile". Use it regardless of
+// whether the profile exists or not. Dex2oat will fall back to "verify" if the profile doesn't
+// exist.
+TEST_F(OdRefreshTest, CompileSetsCompilerFilterWithExplicitValue) {
+ config_.SetSystemServerCompilerFilter("speed-profile");
// Uninteresting calls.
EXPECT_CALL(
@@ -376,13 +408,12 @@
*mock_exec_utils_,
DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
Not(Contains(HasSubstr("--profile-file-fd="))),
- Contains("--compiler-filter=speed"))))
+ Contains("--compiler-filter=speed-profile"))))
.WillOnce(Return(0));
- EXPECT_CALL(
- *mock_exec_utils_,
- DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
- Not(Contains(HasSubstr("--profile-file-fd="))),
- Contains("--compiler-filter=speed"))))
+ EXPECT_CALL(*mock_exec_utils_,
+ DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
+ Contains(HasSubstr("--profile-file-fd=")),
+ Contains("--compiler-filter=speed-profile"))))
.WillOnce(Return(0));
EXPECT_EQ(
odrefresh_->Compile(*metrics_,
@@ -392,11 +423,9 @@
ExitCode::kCompilationSuccess);
}
-TEST_F(OdRefreshTest, CompileSetsCompilerFilterToSpeedProfile) {
- // Test setup: with "speed-profile" compiler filter in the request, only apply if there is a
- // profile, otherwise fallback to speed.
- config_.SetSystemServerCompilerFilter("speed-profile");
-
+// Test setup: The compiler filter is not explicitly set. Use "speed-profile" if there is a profile,
+// otherwise fall back to "speed".
+TEST_F(OdRefreshTest, CompileSetsCompilerFilterWithDefaultValue) {
// Uninteresting calls.
EXPECT_CALL(
*mock_exec_utils_, DoExecAndReturnCode(_))
@@ -424,36 +453,6 @@
ExitCode::kCompilationSuccess);
}
-TEST_F(OdRefreshTest, CompileSetsCompilerFilterToVerify) {
- // Test setup: use "speed" compiler filter.
- config_.SetSystemServerCompilerFilter("verify");
-
- // Uninteresting calls.
- EXPECT_CALL(
- *mock_exec_utils_, DoExecAndReturnCode(_))
- .Times(odrefresh_->AllSystemServerJars().size() - 2)
- .WillRepeatedly(Return(0));
-
- EXPECT_CALL(
- *mock_exec_utils_,
- DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", location_provider_jar_})),
- Not(Contains(HasSubstr("--profile-file-fd="))),
- Contains("--compiler-filter=verify"))))
- .WillOnce(Return(0));
- EXPECT_CALL(
- *mock_exec_utils_,
- DoExecAndReturnCode(AllOf(Contains(Concatenate({"--dex-file=", services_jar_})),
- Not(Contains(HasSubstr("--profile-file-fd="))),
- Contains("--compiler-filter=verify"))))
- .WillOnce(Return(0));
- EXPECT_EQ(
- odrefresh_->Compile(*metrics_,
- CompilationOptions{
- .system_server_jars_to_compile = odrefresh_->AllSystemServerJars(),
- }),
- ExitCode::kCompilationSuccess);
-}
-
TEST_F(OdRefreshTest, OutputFilesAndIsa) {
EXPECT_CALL(
*mock_exec_utils_,
diff --git a/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc
index 8c1f773..da6fc85 100644
--- a/openjdkjvm/OpenjdkJvm.cc
+++ b/openjdkjvm/OpenjdkJvm.cc
@@ -32,6 +32,8 @@
/*
* Services that OpenJDK expects the VM to provide.
*/
+
+#include <assert.h>
#include <dlfcn.h>
#include <limits.h>
#include <stdio.h>
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 083ba6d..cdda09b 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -141,7 +141,7 @@
explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
JvmtiDeleter(JvmtiDeleter&) = default;
- JvmtiDeleter(JvmtiDeleter&&) = default;
+ JvmtiDeleter(JvmtiDeleter&&) noexcept = default;
JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
void operator()(T* ptr) const {
@@ -161,7 +161,7 @@
explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
JvmtiDeleter(JvmtiDeleter&) = default;
- JvmtiDeleter(JvmtiDeleter&&) = default;
+ JvmtiDeleter(JvmtiDeleter&&) noexcept = default;
JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
template <typename U>
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
index 312a797..03042ec 100644
--- a/openjdkjvmti/deopt_manager.cc
+++ b/openjdkjvmti/deopt_manager.cc
@@ -61,16 +61,13 @@
namespace openjdkjvmti {
-// TODO We should make this much more selective in the future so we only return true when we
+// We could make this much more selective in the future so we only return true when we
// actually care about the method at this time (ie active frames had locals changed). For now we
-// just assume that if anything has changed any frame's locals we care about all methods. If nothing
-// has we only care about methods with active breakpoints on them. In the future we should probably
-// rewrite all of this to instead do this at the ShadowFrame or thread granularity.
-bool JvmtiMethodInspectionCallback::IsMethodBeingInspected(art::ArtMethod* method) {
- // In non-java-debuggable runtimes the breakpoint check would miss if we have breakpoints on
- // methods that are inlined. Since these features are best effort in non-java-debuggable
- // runtimes it is OK to be less precise. For debuggable runtimes, inlining is disabled.
- return manager_->HaveLocalsChanged() || manager_->MethodHasBreakpoints(method);
+// just assume that if anything has changed any frame's locals we care about all methods. This only
+// impacts whether we are able to OSR or not so maybe not really important to maintain frame
+// specific information.
+bool JvmtiMethodInspectionCallback::HaveLocalsChanged() {
+ return manager_->HaveLocalsChanged();
}
DeoptManager::DeoptManager()
diff --git a/openjdkjvmti/deopt_manager.h b/openjdkjvmti/deopt_manager.h
index c0a788b..9278bf1 100644
--- a/openjdkjvmti/deopt_manager.h
+++ b/openjdkjvmti/deopt_manager.h
@@ -57,8 +57,7 @@
public:
explicit JvmtiMethodInspectionCallback(DeoptManager* manager) : manager_(manager) {}
- bool IsMethodBeingInspected(art::ArtMethod* method)
- override REQUIRES_SHARED(art::Locks::mutator_lock_);
+ bool HaveLocalsChanged() override REQUIRES_SHARED(art::Locks::mutator_lock_);
private:
DeoptManager* manager_;
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index a6425af..e23beec 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -741,7 +741,6 @@
// Call-back for when a method is popped due to an exception throw. A method will either cause a
// MethodExited call-back or a MethodUnwind call-back when its activation is removed.
void MethodUnwind(art::Thread* self,
- art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
art::ArtMethod* method,
uint32_t dex_pc ATTRIBUTE_UNUSED)
REQUIRES_SHARED(art::Locks::mutator_lock_) override {
@@ -1259,7 +1258,7 @@
if (m.IsNative() || m.IsProxyMethod()) {
continue;
} else if (!runtime_->GetClassLinker()->IsQuickToInterpreterBridge(code) &&
- !runtime_->IsAsyncDeoptimizeable(reinterpret_cast<uintptr_t>(code))) {
+ !runtime_->IsAsyncDeoptimizeable(&m, reinterpret_cast<uintptr_t>(code))) {
runtime_->GetInstrumentation()->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
}
}
diff --git a/openjdkjvmti/jvmti_allocator.h b/openjdkjvmti/jvmti_allocator.h
index bd4c85b..4adf769 100644
--- a/openjdkjvmti/jvmti_allocator.h
+++ b/openjdkjvmti/jvmti_allocator.h
@@ -46,13 +46,13 @@
template <>
class JvmtiAllocator<void> {
public:
- typedef void value_type;
- typedef void* pointer;
- typedef const void* const_pointer;
+ using value_type = void;
+ using pointer = void*;
+ using const_pointer = const void*;
template <typename U>
struct rebind {
- typedef JvmtiAllocator<U> other;
+ using other = JvmtiAllocator<U>;
};
explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
@@ -79,17 +79,17 @@
template <typename T>
class JvmtiAllocator {
public:
- typedef T value_type;
- typedef T* pointer;
- typedef T& reference;
- typedef const T* const_pointer;
- typedef const T& const_reference;
- typedef size_t size_type;
- typedef ptrdiff_t difference_type;
+ using value_type = T;
+ using pointer = T*;
+ using reference = T&;
+ using const_pointer = const T*;
+ using const_reference = const T&;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
template <typename U>
struct rebind {
- typedef JvmtiAllocator<U> other;
+ using other = JvmtiAllocator<U>;
};
explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
diff --git a/openjdkjvmti/jvmti_weak_table-inl.h b/openjdkjvmti/jvmti_weak_table-inl.h
index 5b28e45..17578d2 100644
--- a/openjdkjvmti/jvmti_weak_table-inl.h
+++ b/openjdkjvmti/jvmti_weak_table-inl.h
@@ -114,7 +114,7 @@
return true;
}
- if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+ if (art::gUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
// Under concurrent GC, there is a window between moving objects and sweeping of system
// weaks in which mutators are active. We may receive a to-space object pointer in obj,
// but still have from-space pointers in the table. Explicitly update the table once.
@@ -156,7 +156,7 @@
return true;
}
- if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+ if (art::gUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
// Under concurrent GC, there is a window between moving objects and sweeping of system
// weaks in which mutators are active. We may receive a to-space object pointer in obj,
// but still have from-space pointers in the table. Explicitly update the table once.
diff --git a/openjdkjvmti/jvmti_weak_table.h b/openjdkjvmti/jvmti_weak_table.h
index ea0d023..afa2d1d 100644
--- a/openjdkjvmti/jvmti_weak_table.h
+++ b/openjdkjvmti/jvmti_weak_table.h
@@ -152,7 +152,7 @@
// Performance optimization: To avoid multiple table updates, ensure that during GC we
// only update once. See the comment on the implementation of GetTagSlowPath.
- if (art::kUseReadBarrier &&
+ if (art::gUseReadBarrier &&
self != nullptr &&
self->GetIsGcMarking() &&
!update_since_last_sweep_) {
diff --git a/openjdkjvmti/ti_class_loader.cc b/openjdkjvmti/ti_class_loader.cc
index d0a6634..cf82534 100644
--- a/openjdkjvmti/ti_class_loader.cc
+++ b/openjdkjvmti/ti_class_loader.cc
@@ -66,7 +66,8 @@
art::ScopedObjectAccessUnchecked soa(self);
art::StackHandleScope<3> hs(self);
if (art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
- art::Runtime::Current()->GetClassLinker()->AppendToBootClassPath(self, dex_file);
+ art::Runtime::Current()->AppendToBootClassPath(
+ dex_file->GetLocation(), dex_file->GetLocation(), {dex_file});
return true;
}
art::Handle<art::mirror::Object> java_dex_file_obj(
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 15cb6de..2521ff3 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -512,8 +512,15 @@
art::MemMap Redefiner::MoveDataToMemMap(const std::string& original_location,
art::ArrayRef<const unsigned char> data,
std::string* error_msg) {
+ std::string modified_location = StringPrintf("%s-transformed", original_location.c_str());
+ // A dangling multi-dex location appended to bootclasspath can cause inaccuracy in oat file
+ // validation. For simplicity, just convert it to a normal location.
+ size_t pos = modified_location.find(art::DexFileLoader::kMultiDexSeparator);
+ if (pos != std::string::npos) {
+ modified_location[pos] = '-';
+ }
art::MemMap map = art::MemMap::MapAnonymous(
- StringPrintf("%s-transformed", original_location.c_str()).c_str(),
+ modified_location.c_str(),
data.size(),
PROT_READ|PROT_WRITE,
/*low_4gb=*/ false,
@@ -1423,8 +1430,9 @@
RedefinitionDataIter(const RedefinitionDataIter&) = default;
RedefinitionDataIter(RedefinitionDataIter&&) = default;
- RedefinitionDataIter& operator=(const RedefinitionDataIter&) = default;
- RedefinitionDataIter& operator=(RedefinitionDataIter&&) = default;
+ // Assignments are deleted because holder_ is a reference.
+ RedefinitionDataIter& operator=(const RedefinitionDataIter&) = delete;
+ RedefinitionDataIter& operator=(RedefinitionDataIter&&) = delete;
bool operator==(const RedefinitionDataIter& other) const
REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -1651,10 +1659,12 @@
art::MutableHandle<art::mirror::LongArray> old_cookie(
hs.NewHandle<art::mirror::LongArray>(nullptr));
bool has_older_cookie = false;
- // See if we already have a cookie that a previous redefinition got from the same classloader.
+ // See if we already have a cookie that a previous redefinition got from the same classloader
+ // and the same JavaDex file.
for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
- if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
- // Since every instance of this classloader should have the same cookie associated with it we
+ if (old_data.GetSourceClassLoader() == source_class_loader.Get() &&
+ old_data.GetJavaDexFile() == dex_file_obj.Get()) {
+ // Since every instance of this JavaDex file should have the same cookie associated with it we
// can stop looking here.
has_older_cookie = true;
old_cookie.Assign(old_data.GetNewDexFileCookie());
@@ -1679,12 +1689,13 @@
// Save the cookie.
cur_data->SetNewDexFileCookie(new_cookie.Get());
- // If there are other copies of this same classloader we need to make sure that we all have the
- // same cookie.
+ // If there are other copies of the same classloader and the same JavaDex file we need to
+ // make sure that we all have the same cookie.
if (has_older_cookie) {
for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
// We will let the GC take care of the cookie we allocated for this one.
- if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
+ if (old_data.GetSourceClassLoader() == source_class_loader.Get() &&
+ old_data.GetJavaDexFile() == dex_file_obj.Get()) {
old_data.SetNewDexFileCookie(new_cookie.Get());
}
}
@@ -2477,7 +2488,9 @@
art::ClassLinker* cl = runtime_->GetClassLinker();
if (data.GetSourceClassLoader() == nullptr) {
// AppendToBootClassPath includes dex file registration.
- cl->AppendToBootClassPath(&data.GetRedefinition().GetDexFile(), data.GetNewDexCache());
+ const art::DexFile& dex_file = data.GetRedefinition().GetDexFile();
+ runtime_->AppendToBootClassPath(
+ dex_file.GetLocation(), dex_file.GetLocation(), {{&dex_file, data.GetNewDexCache()}});
} else {
cl->RegisterExistingDexCache(data.GetNewDexCache(), data.GetSourceClassLoader());
}
@@ -2910,6 +2923,27 @@
// be undone. This replaces the mirror::Class in 'holder' as well. It's magic!
HeapExtensions::ReplaceReferences(driver_->self_, map);
+ // Undo the replacement of old_class with new_class for the methods / fields on the old_class.
+ // It is hard to ensure that we don't replace the declaring class of the old class field / methods
+ // isn't impacted by ReplaceReferences. It is just simpler to undo the replacement here.
+ std::for_each(
+ old_classes_vec.cbegin(),
+ old_classes_vec.cend(),
+ [](art::ObjPtr<art::mirror::Class> orig) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ orig->VisitMethods(
+ [&](art::ArtMethod* method) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ if (method->IsCopied()) {
+ // Copied methods have interfaces as their declaring class.
+ return;
+ }
+ method->SetDeclaringClass(orig);
+ },
+ art::kRuntimePointerSize);
+ orig->VisitFields([&](art::ArtField* field) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+ field->SetDeclaringClass(orig);
+ });
+ });
+
// Save the old class so that the JIT gc doesn't get confused by it being collected before the
// jit code. This is also needed to keep the dex-caches of any obsolete methods live.
for (auto [new_class, old_class] :
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 85b1070..cdd6627 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -126,7 +126,7 @@
~ClassRedefinition() NO_THREAD_SAFETY_ANALYSIS;
// Move assignment so we can sort these in a vector.
- ClassRedefinition& operator=(ClassRedefinition&& other) {
+ ClassRedefinition& operator=(ClassRedefinition&& other) noexcept {
driver_ = other.driver_;
klass_ = other.klass_;
dex_file_ = std::move(other.dex_file_);
@@ -138,7 +138,7 @@
}
// Move constructor so we can put these into a vector.
- ClassRedefinition(ClassRedefinition&& other)
+ ClassRedefinition(ClassRedefinition&& other) noexcept
: driver_(other.driver_),
klass_(other.klass_),
dex_file_(std::move(other.dex_file_)),
diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
index 526836e..5c1677f 100644
--- a/openjdkjvmti/ti_search.cc
+++ b/openjdkjvmti/ti_search.cc
@@ -247,12 +247,8 @@
return ERR(ILLEGAL_ARGUMENT);
}
- art::ScopedObjectAccess soa(art::Thread::Current());
- for (std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
- current->GetClassLinker()->AppendToBootClassPath(art::Thread::Current(), dex_file.release());
- }
-
- return ERR(NONE);
+ current->AddExtraBootDexFiles(segment, segment, std::move(dex_files));
+ return OK;
}
jvmtiError SearchUtil::AddToDexClassLoaderInMemory(jvmtiEnv* jvmti_env,
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
index f31759e..7fb789d 100644
--- a/openjdkjvmti/ti_thread.cc
+++ b/openjdkjvmti/ti_thread.cc
@@ -131,6 +131,7 @@
if (name != "JDWP" && name != "Signal Catcher" && name != "perfetto_hprof_listener" &&
name != art::metrics::MetricsReporter::kBackgroundThreadName &&
!android::base::StartsWith(name, "Jit thread pool") &&
+ !android::base::StartsWith(name, "Heap thread pool worker thread") &&
!android::base::StartsWith(name, "Runtime worker thread")) {
LOG(FATAL) << "Unexpected thread before start: " << name << " id: "
<< self->GetThreadId();
diff --git a/perfetto_hprof/Android.bp b/perfetto_hprof/Android.bp
index a81a4fa..7db4be4 100644
--- a/perfetto_hprof/Android.bp
+++ b/perfetto_hprof/Android.bp
@@ -68,6 +68,12 @@
header_libs: [
"libnativehelper_header_only",
],
+ // FIXME: Workaround LTO build breakage
+ // http://b/241700157
+ lto: {
+ never: true,
+ },
+
}
art_cc_library {
diff --git a/profman/Android.bp b/profman/Android.bp
index b231499..8e76bc2 100644
--- a/profman/Android.bp
+++ b/profman/Android.bp
@@ -32,6 +32,7 @@
"profman.cc",
"profile_assistant.cc",
],
+ header_libs: ["profman_headers"],
target: {
android: {
@@ -160,11 +161,31 @@
},
}
+cc_library_headers {
+ name: "profman_headers",
+ defaults: ["art_defaults"],
+ export_include_dirs: ["include"],
+ host_supported: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ windows: {
+ enabled: true,
+ },
+ },
+ apex_available: [
+ "com.android.art",
+ "com.android.art.debug",
+ ],
+}
+
art_cc_defaults {
name: "art_profman_tests_defaults",
data: [
":art-gtest-jars-ProfileTestMultiDex",
],
+ header_libs: ["profman_headers"],
tidy_timeout_srcs: ["profile_assistant_test.cc"],
srcs: ["profile_assistant_test.cc"],
}
diff --git a/profman/include/profman/profman_result.h b/profman/include/profman/profman_result.h
new file mode 100644
index 0000000..2e962e8
--- /dev/null
+++ b/profman/include/profman/profman_result.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
+#define ART_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
+
+namespace art {
+
+class ProfmanResult {
+ public:
+ static constexpr int kErrorUsage = 100;
+
+ // The return codes of processing profiles (running profman in normal mode).
+ //
+ // Note that installd consumes the returns codes with its own copy of these values
+ // (frameworks/native/cmds/installd/dexopt.cpp).
+ enum ProcessingResult {
+ kSuccess = 0, // Generic success code for non-analysis runs.
+ kCompile = 1,
+ kSkipCompilationSmallDelta = 2,
+ kErrorBadProfiles = 3,
+ kErrorIO = 4,
+ kErrorCannotLock = 5,
+ kErrorDifferentVersions = 6,
+ kSkipCompilationEmptyProfiles = 7,
+ };
+
+ // The return codes of running profman with `--copy-and-update-profile-key`.
+ enum CopyAndUpdateResult {
+ kCopyAndUpdateSuccess = 0,
+ kCopyAndUpdateNoUpdate = 21,
+ kCopyAndUpdateErrorFailedToUpdateProfile = 22,
+ kCopyAndUpdateErrorFailedToSaveProfile = 23,
+ kCopyAndUpdateErrorFailedToLoadProfile = 24,
+ };
+};
+
+} // namespace art
+
+#endif // ART_PROFMAN_INCLUDE_PROFMAN_PROFMAN_RESULT_H_
diff --git a/profman/profile_assistant.cc b/profman/profile_assistant.cc
index d098738..abbde2d 100644
--- a/profman/profile_assistant.cc
+++ b/profman/profile_assistant.cc
@@ -18,6 +18,7 @@
#include "base/os.h"
#include "base/unix_file/fd_file.h"
+#include "profman/profman_result.h"
namespace art {
@@ -26,25 +27,22 @@
static constexpr const uint32_t kMinNewMethodsForCompilation = 100;
static constexpr const uint32_t kMinNewClassesForCompilation = 50;
-
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
- const std::vector<ScopedFlock>& profile_files,
- const ScopedFlock& reference_profile_file,
- const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
- const Options& options) {
- DCHECK(!profile_files.empty());
-
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfilesInternal(
+ const std::vector<ScopedFlock>& profile_files,
+ const ScopedFlock& reference_profile_file,
+ const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
+ const Options& options) {
ProfileCompilationInfo info(options.IsBootImageMerge());
// Load the reference profile.
if (!info.Load(reference_profile_file->Fd(), /*merge_classes=*/ true, filter_fn)) {
LOG(WARNING) << "Could not load reference profile file";
- return kErrorBadProfiles;
+ return ProfmanResult::kErrorBadProfiles;
}
if (options.IsBootImageMerge() && !info.IsForBootImage()) {
LOG(WARNING) << "Requested merge for boot image profile but the reference profile is regular.";
- return kErrorBadProfiles;
+ return ProfmanResult::kErrorBadProfiles;
}
// Store the current state of the reference profile before merging with the current profiles.
@@ -65,21 +63,21 @@
// TODO: Do we really need to use a different error code for version mismatch?
ProfileCompilationInfo wrong_info(!options.IsBootImageMerge());
if (wrong_info.Load(profile_files[i]->Fd(), /*merge_classes=*/ true, filter_fn)) {
- return kErrorDifferentVersions;
+ return ProfmanResult::kErrorDifferentVersions;
}
- return kErrorBadProfiles;
+ return ProfmanResult::kErrorBadProfiles;
}
if (!info.MergeWith(cur_info)) {
LOG(WARNING) << "Could not merge profile file at index " << i;
- return kErrorBadProfiles;
+ return ProfmanResult::kErrorBadProfiles;
}
}
// If we perform a forced merge do not analyze the difference between profiles.
if (!options.IsForceMerge()) {
if (info.IsEmpty()) {
- return kSkipCompilationEmptyProfiles;
+ return ProfmanResult::kSkipCompilationEmptyProfiles;
}
uint32_t min_change_in_methods_for_compilation = std::max(
(options.GetMinNewMethodsPercentChangeForCompilation() * number_of_methods) / 100,
@@ -91,21 +89,21 @@
if (((info.GetNumberOfMethods() - number_of_methods) < min_change_in_methods_for_compilation) &&
((info.GetNumberOfResolvedClasses() - number_of_classes)
< min_change_in_classes_for_compilation)) {
- return kSkipCompilationSmallDelta;
+ return ProfmanResult::kSkipCompilationSmallDelta;
}
}
// We were successful in merging all profile information. Update the reference profile.
if (!reference_profile_file->ClearContent()) {
PLOG(WARNING) << "Could not clear reference profile file";
- return kErrorIO;
+ return ProfmanResult::kErrorIO;
}
if (!info.Save(reference_profile_file->Fd())) {
LOG(WARNING) << "Could not save reference profile file";
- return kErrorIO;
+ return ProfmanResult::kErrorIO;
}
- return options.IsForceMerge() ? kSuccess : kCompile;
+ return options.IsForceMerge() ? ProfmanResult::kSuccess : ProfmanResult::kCompile;
}
class ScopedFlockList {
@@ -144,7 +142,7 @@
std::vector<ScopedFlock> flocks_;
};
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfiles(
const std::vector<int>& profile_files_fd,
int reference_profile_file_fd,
const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
@@ -155,7 +153,7 @@
ScopedFlockList profile_files(profile_files_fd.size());
if (!profile_files.Init(profile_files_fd, &error)) {
LOG(WARNING) << "Could not lock profile files: " << error;
- return kErrorCannotLock;
+ return ProfmanResult::kErrorCannotLock;
}
// The reference_profile_file is opened in read/write mode because it's
@@ -166,7 +164,7 @@
&error);
if (reference_profile_file.get() == nullptr) {
LOG(WARNING) << "Could not lock reference profiled files: " << error;
- return kErrorCannotLock;
+ return ProfmanResult::kErrorCannotLock;
}
return ProcessProfilesInternal(profile_files.Get(),
@@ -175,7 +173,7 @@
options);
}
-ProfileAssistant::ProcessingResult ProfileAssistant::ProcessProfiles(
+ProfmanResult::ProcessingResult ProfileAssistant::ProcessProfiles(
const std::vector<std::string>& profile_files,
const std::string& reference_profile_file,
const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
@@ -185,14 +183,14 @@
ScopedFlockList profile_files_list(profile_files.size());
if (!profile_files_list.Init(profile_files, &error)) {
LOG(WARNING) << "Could not lock profile files: " << error;
- return kErrorCannotLock;
+ return ProfmanResult::kErrorCannotLock;
}
ScopedFlock locked_reference_profile_file = LockedFile::Open(
reference_profile_file.c_str(), O_RDWR, /* block= */ true, &error);
if (locked_reference_profile_file.get() == nullptr) {
LOG(WARNING) << "Could not lock reference profile files: " << error;
- return kErrorCannotLock;
+ return ProfmanResult::kErrorCannotLock;
}
return ProcessProfilesInternal(profile_files_list.Get(),
diff --git a/profman/profile_assistant.h b/profman/profile_assistant.h
index 0ef4f88..6b7a7a6 100644
--- a/profman/profile_assistant.h
+++ b/profman/profile_assistant.h
@@ -22,24 +22,12 @@
#include "base/scoped_flock.h"
#include "profile/profile_compilation_info.h"
+#include "profman/profman_result.h"
namespace art {
class ProfileAssistant {
public:
- // These also serve as return codes of profman and are processed by installd
- // (frameworks/native/cmds/installd/commands.cpp)
- enum ProcessingResult {
- kSuccess = 0, // Generic success code for non-analysis runs.
- kCompile = 1,
- kSkipCompilationSmallDelta = 2,
- kErrorBadProfiles = 3,
- kErrorIO = 4,
- kErrorCannotLock = 5,
- kErrorDifferentVersions = 6,
- kSkipCompilationEmptyProfiles = 7,
- };
-
class Options {
public:
static constexpr bool kForceMergeDefault = false;
@@ -101,14 +89,14 @@
// this case no file will be updated. A variation of this code is
// kSkipCompilationEmptyProfiles which indicates that all the profiles are empty.
// This allow the caller to make fine grain decisions on the compilation strategy.
- static ProcessingResult ProcessProfiles(
+ static ProfmanResult::ProcessingResult ProcessProfiles(
const std::vector<std::string>& profile_files,
const std::string& reference_profile_file,
const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn
= ProfileCompilationInfo::ProfileFilterFnAcceptAll,
const Options& options = Options());
- static ProcessingResult ProcessProfiles(
+ static ProfmanResult::ProcessingResult ProcessProfiles(
const std::vector<int>& profile_files_fd_,
int reference_profile_file_fd,
const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn
@@ -116,7 +104,7 @@
const Options& options = Options());
private:
- static ProcessingResult ProcessProfilesInternal(
+ static ProfmanResult::ProcessingResult ProcessProfilesInternal(
const std::vector<ScopedFlock>& profile_files,
const ScopedFlock& reference_profile_file,
const ProfileCompilationInfo::ProfileLoadFilterFn& filter_fn,
diff --git a/profman/profile_assistant_test.cc b/profman/profile_assistant_test.cc
index f446c09..4fc8143 100644
--- a/profman/profile_assistant_test.cc
+++ b/profman/profile_assistant_test.cc
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-#include <gtest/gtest.h>
+#include "profile_assistant.h"
+
#include <sstream>
#include <string>
@@ -31,12 +32,13 @@
#include "dex/dex_instruction_iterator.h"
#include "dex/type_reference.h"
#include "exec_utils.h"
+#include "gtest/gtest.h"
#include "linear_alloc.h"
#include "mirror/class-inl.h"
#include "obj_ptr-inl.h"
#include "profile/profile_compilation_info.h"
#include "profile/profile_test_helper.h"
-#include "profile_assistant.h"
+#include "profman/profman_result.h"
#include "scoped_thread_state_change-inl.h"
namespace art {
@@ -50,13 +52,13 @@
void PostRuntimeCreate() override {
allocator_.reset(new ArenaAllocator(Runtime::Current()->GetArenaPool()));
- dex1 = BuildDex("location1", /*checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 10001);
- dex2 = BuildDex("location2", /*checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 10002);
- dex3 = BuildDex("location3", /*checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 10003);
- dex4 = BuildDex("location4", /*checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 10004);
+ dex1 = BuildDex("location1", /*location_checksum=*/ 1, "LUnique1;", /*num_method_ids=*/ 10001);
+ dex2 = BuildDex("location2", /*location_checksum=*/ 2, "LUnique2;", /*num_method_ids=*/ 10002);
+ dex3 = BuildDex("location3", /*location_checksum=*/ 3, "LUnique3;", /*num_method_ids=*/ 10003);
+ dex4 = BuildDex("location4", /*location_checksum=*/ 4, "LUnique4;", /*num_method_ids=*/ 10004);
dex1_checksum_missmatch =
- BuildDex("location1", /*checksum=*/ 12, "LUnique1;", /*num_method_ids=*/ 10001);
+ BuildDex("location1", /*location_checksum=*/ 12, "LUnique1;", /*num_method_ids=*/ 10001);
}
protected:
@@ -267,7 +269,7 @@
bool CreateAndDump(const std::string& input_file_contents,
std::string* output_file_contents,
- std::optional<const std::string> target = std::nullopt) {
+ const std::optional<const std::string>& target = std::nullopt) {
ScratchFile profile_file;
EXPECT_TRUE(CreateProfile(input_file_contents,
profile_file.GetFilename(),
@@ -442,10 +444,16 @@
const std::vector<const std::string>& extra_args =
std::vector<const std::string>()) {
uint16_t max_classes = std::max(classes_in_cur_profile, classes_in_ref_profile);
- const DexFile* dex1_x = BuildDex(
- "location1_x", /*checksum=*/ 0x101, "LUnique1_x;", /*num_method_ids=*/ 0, max_classes);
- const DexFile* dex2_x = BuildDex(
- "location2_x", /*checksum=*/ 0x102, "LUnique2_x;", /*num_method_ids=*/ 0, max_classes);
+ const DexFile* dex1_x = BuildDex("location1_x",
+ /*location_checksum=*/ 0x101,
+ "LUnique1_x;",
+ /*num_method_ids=*/ 0,
+ max_classes);
+ const DexFile* dex2_x = BuildDex("location2_x",
+ /*location_checksum=*/ 0x102,
+ "LUnique2_x;",
+ /*num_method_ids=*/ 0,
+ max_classes);
ScratchFile profile;
ScratchFile reference_profile;
@@ -486,8 +494,7 @@
SetupProfile(dex3, dex4, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
// We should advise compilation.
- ASSERT_EQ(ProfileAssistant::kCompile,
- ProcessProfiles(profile_fds, reference_profile_fd));
+ ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
// The resulting compilation info must be equal to the merge of the inputs.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -506,15 +513,15 @@
TEST_F(ProfileAssistantTest, AdviseCompilationEmptyReferencesBecauseOfClasses) {
const uint16_t kNumberOfClassesToEnableCompilation = 100;
const DexFile* dex1_100 = BuildDex("location1_100",
- /*checksum=*/ 101,
+ /*location_checksum=*/ 101,
"LUnique1_100;",
/*num_method_ids=*/ 0,
- /*num_type_ids=*/ 100);
+ /*num_class_ids=*/ 100);
const DexFile* dex2_100 = BuildDex("location2_100",
- /*checksum=*/ 102,
+ /*location_checksum=*/ 102,
"LUnique2_100;",
/*num_method_ids=*/ 0,
- /*num_type_ids=*/ 100);
+ /*num_class_ids=*/ 100);
ScratchFile profile1;
ScratchFile reference_profile;
@@ -527,8 +534,7 @@
SetupProfile(dex1_100, dex2_100, 0, kNumberOfClassesToEnableCompilation, profile1, &info1);
// We should advise compilation.
- ASSERT_EQ(ProfileAssistant::kCompile,
- ProcessProfiles(profile_fds, reference_profile_fd));
+ ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
// The resulting compilation info must be equal to the merge of the inputs.
ProfileCompilationInfo result;
ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -566,8 +572,7 @@
&reference_info, kNumberOfMethodsToEnableCompilation / 2);
// We should advise compilation.
- ASSERT_EQ(ProfileAssistant::kCompile,
- ProcessProfiles(profile_fds, reference_profile_fd));
+ ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
// The resulting compilation info must be equal to the merge of the inputs
ProfileCompilationInfo result;
@@ -600,7 +605,7 @@
SetupProfile(dex3, dex4, /*number_of_methods=*/ 0, /*number_of_classes*/ 0, profile2, &info2);
// We should not advise compilation.
- ASSERT_EQ(ProfileAssistant::kSkipCompilationEmptyProfiles,
+ ASSERT_EQ(ProfmanResult::kSkipCompilationEmptyProfiles,
ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
@@ -637,7 +642,7 @@
SetupProfile(dex3, dex4, kNumberOfMethodsToSkipCompilation, 0, profile2, &info2);
// We should not advise compilation.
- ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+ ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
@@ -663,10 +668,9 @@
std::vector<const std::string> extra_args({"--min-new-methods-percent-change=2"});
// We should not advise compilation.
- ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
- CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
- kNumberOfMethodsInRefProfile,
- extra_args));
+ ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
+ CheckCompilationMethodPercentChange(
+ kNumberOfMethodsInCurProfile, kNumberOfMethodsInRefProfile, extra_args));
}
TEST_F(ProfileAssistantTest, ShouldAdviseCompilationMethodPercentage) {
@@ -675,10 +679,9 @@
std::vector<const std::string> extra_args({"--min-new-methods-percent-change=2"});
// We should advise compilation.
- ASSERT_EQ(ProfileAssistant::kCompile,
- CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
- kNumberOfMethodsInRefProfile,
- extra_args));
+ ASSERT_EQ(ProfmanResult::kCompile,
+ CheckCompilationMethodPercentChange(
+ kNumberOfMethodsInCurProfile, kNumberOfMethodsInRefProfile, extra_args));
}
TEST_F(ProfileAssistantTest, DoNotAdviseCompilationMethodPercentageWithNewMin) {
@@ -686,7 +689,7 @@
const uint16_t kNumberOfMethodsInCurProfile = 6200; // Threshold is 20%.
// We should not advise compilation.
- ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+ ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
CheckCompilationMethodPercentChange(kNumberOfMethodsInCurProfile,
kNumberOfMethodsInRefProfile));
}
@@ -697,10 +700,9 @@
std::vector<const std::string> extra_args({"--min-new-classes-percent-change=2"});
// We should not advise compilation.
- ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
- CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
- kNumberOfClassesInRefProfile,
- extra_args));
+ ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
+ CheckCompilationClassPercentChange(
+ kNumberOfClassesInCurProfile, kNumberOfClassesInRefProfile, extra_args));
}
TEST_F(ProfileAssistantTest, ShouldAdviseCompilationClassPercentage) {
@@ -709,10 +711,9 @@
std::vector<const std::string> extra_args({"--min-new-classes-percent-change=2"});
// We should advise compilation.
- ASSERT_EQ(ProfileAssistant::kCompile,
- CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
- kNumberOfClassesInRefProfile,
- extra_args));
+ ASSERT_EQ(ProfmanResult::kCompile,
+ CheckCompilationClassPercentChange(
+ kNumberOfClassesInCurProfile, kNumberOfClassesInRefProfile, extra_args));
}
TEST_F(ProfileAssistantTest, DoNotAdviseCompilationClassPercentageWithNewMin) {
@@ -720,7 +721,7 @@
const uint16_t kNumberOfClassesInCurProfile = 6200; // Threshold is 20%.
// We should not advise compilation.
- ASSERT_EQ(ProfileAssistant::kSkipCompilationSmallDelta,
+ ASSERT_EQ(ProfmanResult::kSkipCompilationSmallDelta,
CheckCompilationClassPercentChange(kNumberOfClassesInCurProfile,
kNumberOfClassesInRefProfile));
}
@@ -744,8 +745,7 @@
dex1_checksum_missmatch, dex2, kNumberOfMethodsToEnableCompilation, 0, profile2, &info2);
// We should fail processing.
- ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
- ProcessProfiles(profile_fds, reference_profile_fd));
+ ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
CheckProfileInfo(profile1, info1);
@@ -776,8 +776,7 @@
&reference_info);
// We should not advise compilation.
- ASSERT_EQ(ProfileAssistant::kErrorBadProfiles,
- ProcessProfiles(profile_fds, reference_profile_fd));
+ ASSERT_EQ(ProfmanResult::kErrorBadProfiles, ProcessProfiles(profile_fds, reference_profile_fd));
// The information from profiles must remain the same.
CheckProfileInfo(profile1, info1);
@@ -971,7 +970,7 @@
/*for_boot_image=*/ true));
ProfileCompilationInfo bootProfile(/*for_boot_image=*/ true);
- bootProfile.Load(profile.GetFilename(), /*clear_if_invalid=*/ true);
+ EXPECT_TRUE(bootProfile.Load(profile.GetFilename(), /*clear_if_invalid=*/ false));
// Generate the boot profile.
ScratchFile out_profile;
@@ -1065,10 +1064,10 @@
core_dex,
/*for_boot_image=*/ true));
- ProfileCompilationInfo boot_profile1;
- ProfileCompilationInfo boot_profile2;
- boot_profile1.Load(profile1.GetFilename(), /*for_boot_image=*/ true);
- boot_profile2.Load(profile2.GetFilename(), /*for_boot_image=*/ true);
+ ProfileCompilationInfo boot_profile1(/*for_boot_image=*/ true);
+ ProfileCompilationInfo boot_profile2(/*for_boot_image=*/ true);
+ EXPECT_TRUE(boot_profile1.Load(profile1.GetFilename(), /*clear_if_invalid=*/ false));
+ EXPECT_TRUE(boot_profile2.Load(profile2.GetFilename(), /*clear_if_invalid=*/ false));
// Generate the boot profile.
ScratchFile out_profile;
@@ -1525,8 +1524,7 @@
&reference_info, kNumberOfMethodsToEnableCompilation / 2, /*reverse_dex_write_order=*/true);
// We should advise compilation.
- ASSERT_EQ(ProfileAssistant::kCompile,
- ProcessProfiles(profile_fds, reference_profile_fd));
+ ASSERT_EQ(ProfmanResult::kCompile, ProcessProfiles(profile_fds, reference_profile_fd));
// The resulting compilation info must be equal to the merge of the inputs.
ProfileCompilationInfo result;
@@ -1787,7 +1785,7 @@
argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
std::string error;
- EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfileAssistant::kCompile) << error;
+ EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCompile) << error;
// Verify that we can load the result.
@@ -1820,6 +1818,169 @@
ASSERT_TRUE(expected.Equals(result));
}
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfile) {
+ ScratchFile reference_profile;
+
+ // Use a real dex file to generate profile test data.
+ std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex");
+ const DexFile& d1 = *dex_files[0];
+ const DexFile& d2 = *dex_files[0];
+
+ // The reference profile info will contain the methods with indices 0-100.
+ ProfileCompilationInfo reference_info;
+ SetupProfile(&d1,
+ &d2,
+ /*number_of_methods=*/ 100,
+ /*number_of_classes=*/ 0,
+ reference_profile,
+ &reference_info);
+
+ std::string content_before;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+ // Run profman and pass the dex file with --apk-fd.
+ android::base::unique_fd apk_fd(
+ // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+ open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+ ASSERT_GE(apk_fd.get(), 0);
+
+ std::string profman_cmd = GetProfmanCmd();
+ std::vector<std::string> argv_str;
+ argv_str.push_back(profman_cmd);
+ argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+ argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+ // Must return kSkipCompilationSmallDelta.
+ std::string error;
+ EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationSmallDelta)
+ << error;
+
+ // Verify that the content has not changed.
+ std::string content_after;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+ EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfilePassByFilename) {
+ ScratchFile reference_profile;
+
+ // Use a real dex file to generate profile test data.
+ std::vector<std::unique_ptr<const DexFile>> dex_files = OpenTestDexFiles("ProfileTestMultiDex");
+ const DexFile& d1 = *dex_files[0];
+ const DexFile& d2 = *dex_files[0];
+
+ // The reference profile info will contain the methods with indices 0-100.
+ ProfileCompilationInfo reference_info;
+ SetupProfile(&d1,
+ &d2,
+ /*number_of_methods=*/100,
+ /*number_of_classes=*/0,
+ reference_profile,
+ &reference_info);
+
+ std::string content_before;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+ // Run profman and pass the dex file with --apk-fd.
+ android::base::unique_fd apk_fd(
+ // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+ open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+ ASSERT_GE(apk_fd.get(), 0);
+
+ std::string profman_cmd = GetProfmanCmd();
+ std::vector<std::string> argv_str;
+ argv_str.push_back(profman_cmd);
+ argv_str.push_back("--reference-profile-file=" + reference_profile.GetFilename());
+ argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+ // Must return kSkipCompilationSmallDelta.
+ std::string error;
+ EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationSmallDelta)
+ << error;
+
+ // Verify that the content has not changed.
+ std::string content_after;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+ EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfileEmptyReferenceProfile) {
+ ScratchFile reference_profile;
+
+ // The reference profile info will only contain the header.
+ ProfileCompilationInfo reference_info;
+ SetupProfile(/*dex_file1=*/ nullptr,
+ /*dex_file2=*/ nullptr,
+ /*number_of_methods=*/ 0,
+ /*number_of_classes=*/ 0,
+ reference_profile,
+ &reference_info);
+
+ std::string content_before;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+ // Run profman and pass the dex file with --apk-fd.
+ android::base::unique_fd apk_fd(
+ // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+ open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+ ASSERT_GE(apk_fd.get(), 0);
+
+ std::string profman_cmd = GetProfmanCmd();
+ std::vector<std::string> argv_str;
+ argv_str.push_back(profman_cmd);
+ argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+ argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+ // Must return kSkipCompilationEmptyProfiles.
+ std::string error;
+ EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationEmptyProfiles)
+ << error;
+
+ // Verify that the content has not changed.
+ std::string content_after;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+ EXPECT_EQ(content_before, content_after);
+}
+
+TEST_F(ProfileAssistantTest, MergeProfilesNoProfileEmptyReferenceProfileAfterFiltering) {
+ ScratchFile reference_profile;
+
+ // Use fake dex files to generate profile test data.
+ // All the methods will be filtered out during the profman invocation.
+ ProfileCompilationInfo reference_info;
+ SetupProfile(dex1,
+ dex2,
+ /*number_of_methods=*/ 100,
+ /*number_of_classes=*/ 0,
+ reference_profile,
+ &reference_info);
+
+ std::string content_before;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_before));
+
+ // Run profman and pass the real dex file with --apk-fd.
+ android::base::unique_fd apk_fd(
+ // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+ open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+ ASSERT_GE(apk_fd.get(), 0);
+
+ std::string profman_cmd = GetProfmanCmd();
+ std::vector<std::string> argv_str;
+ argv_str.push_back(profman_cmd);
+ argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+ argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+
+ // Must return kSkipCompilationEmptyProfiles.
+ std::string error;
+ EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSkipCompilationEmptyProfiles)
+ << error;
+
+ // Verify that the content has not changed.
+ std::string content_after;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &content_after));
+ EXPECT_EQ(content_before, content_after);
+}
+
TEST_F(ProfileAssistantTest, CopyAndUpdateProfileKey) {
ScratchFile profile1;
ScratchFile reference_profile;
@@ -1846,7 +2007,8 @@
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd(
- open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY)); // NOLINT
+ // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+ open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
std::string profman_cmd = GetProfmanCmd();
@@ -1858,7 +2020,8 @@
argv_str.push_back("--copy-and-update-profile-key");
std::string error;
- ASSERT_EQ(ExecAndReturnCode(argv_str, &error), 0) << error;
+ // Must return kCopyAndUpdateSuccess.
+ ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateSuccess) << error;
// Verify that we can load the result.
ProfileCompilationInfo result;
@@ -1874,6 +2037,46 @@
}
}
+TEST_F(ProfileAssistantTest, CopyAndUpdateProfileKeyNoUpdate) {
+ ScratchFile profile1;
+ ScratchFile reference_profile;
+
+ // Use fake dex files to generate profile test data.
+ ProfileCompilationInfo info1;
+ SetupProfile(dex1,
+ dex2,
+ /*number_of_methods=*/ 100,
+ /*number_of_classes=*/ 0,
+ profile1,
+ &info1);
+
+ std::string input_content;
+ ASSERT_TRUE(android::base::ReadFileToString(profile1.GetFilename(), &input_content));
+
+ // Run profman and pass the real dex file with --apk-fd. It won't match any entry in the profile.
+ android::base::unique_fd apk_fd(
+ // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+ open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
+ ASSERT_GE(apk_fd.get(), 0);
+
+ std::string profman_cmd = GetProfmanCmd();
+ std::vector<std::string> argv_str;
+ argv_str.push_back(profman_cmd);
+ argv_str.push_back("--profile-file-fd=" + std::to_string(profile1.GetFd()));
+ argv_str.push_back("--reference-profile-file-fd=" + std::to_string(reference_profile.GetFd()));
+ argv_str.push_back("--apk-fd=" + std::to_string(apk_fd.get()));
+ argv_str.push_back("--copy-and-update-profile-key");
+ std::string error;
+
+ // Must return kCopyAndUpdateNoUpdate.
+ ASSERT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kCopyAndUpdateNoUpdate) << error;
+
+ // Verify that the content is the same.
+ std::string output_content;
+ ASSERT_TRUE(android::base::ReadFileToString(reference_profile.GetFilename(), &output_content));
+ EXPECT_EQ(input_content, output_content);
+}
+
TEST_F(ProfileAssistantTest, BootImageMerge) {
ScratchFile profile;
ScratchFile reference_profile;
@@ -1900,7 +2103,7 @@
int return_code = ProcessProfiles(profile_fds, reference_profile_fd, extra_args);
- ASSERT_EQ(return_code, ProfileAssistant::kSuccess);
+ ASSERT_EQ(return_code, ProfmanResult::kSuccess);
// Verify the result: it should be equal to info2 since info1 is a regular profile
// and should be ignored.
@@ -1918,15 +2121,15 @@
const uint16_t kNumberOfClassesInCurProfile = 6110; // Threshold is 2%.
const DexFile* dex1_7000 = BuildDex("location1_7000",
- /*checksum=*/ 7001,
+ /*location_checksum=*/ 7001,
"LUnique1_7000;",
/*num_method_ids=*/ 0,
- /*num_type_ids=*/ 7000);
+ /*num_class_ids=*/ 7000);
const DexFile* dex2_7000 = BuildDex("location2_7000",
- /*checksum=*/ 7002,
+ /*location_checksum=*/ 7002,
"LUnique2_7000;",
/*num_method_ids=*/ 0,
- /*num_type_ids=*/ 7000);
+ /*num_class_ids=*/ 7000);
ScratchFile profile;
ScratchFile reference_profile;
@@ -1942,7 +2145,7 @@
std::vector<const std::string> extra_args({"--force-merge"});
int return_code = ProcessProfiles(profile_fds, reference_profile_fd, extra_args);
- ASSERT_EQ(return_code, ProfileAssistant::kSuccess);
+ ASSERT_EQ(return_code, ProfmanResult::kSuccess);
// Check that the result is the aggregation.
ProfileCompilationInfo result;
@@ -1974,7 +2177,8 @@
// Run profman and pass the dex file with --apk-fd.
android::base::unique_fd apk_fd(
- open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY)); // NOLINT
+ // NOLINTNEXTLINE - Profman needs file to be opened after fork() and exec()
+ open(GetTestDexFileName("ProfileTestMultiDex").c_str(), O_RDONLY));
ASSERT_GE(apk_fd.get(), 0);
std::string profman_cmd = GetProfmanCmd();
@@ -1987,7 +2191,7 @@
argv_str.push_back("--boot-image-merge");
std::string error;
- EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfileAssistant::kSuccess) << error;
+ EXPECT_EQ(ExecAndReturnCode(argv_str, &error), ProfmanResult::kSuccess) << error;
// Verify that we can load the result and that it equals to what we saved.
ProfileCompilationInfo result(/*for_boot_image=*/ true);
@@ -2009,17 +2213,16 @@
int reference_profile_fd = GetFd(profile2);
std::vector<const std::string> boot_image_args({"--boot-image-merge"});
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, boot_image_args),
- ProfileAssistant::kErrorDifferentVersions);
- ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd),
- ProfileAssistant::kErrorBadProfiles);
+ ProfmanResult::kErrorDifferentVersions);
+ ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd), ProfmanResult::kErrorBadProfiles);
// Reverse the order of the profiles to verify we get the same behaviour.
profile_fds[0] = GetFd(profile2);
reference_profile_fd = GetFd(profile1);
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, boot_image_args),
- ProfileAssistant::kErrorBadProfiles);
+ ProfmanResult::kErrorBadProfiles);
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd),
- ProfileAssistant::kErrorDifferentVersions);
+ ProfmanResult::kErrorDifferentVersions);
}
// Under default behaviour we will abort if we cannot load a profile during a merge
@@ -2042,7 +2245,7 @@
// With force-merge we should merge successfully.
std::vector<const std::string> extra_args({"--force-merge", "--boot-image-merge"});
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, extra_args),
- ProfileAssistant::kSuccess);
+ ProfmanResult::kSuccess);
ProfileCompilationInfo result(/*for_boot_image=*/ true);
ASSERT_TRUE(result.Load(reference_profile_fd));
@@ -2051,7 +2254,7 @@
// Without force-merge we should fail.
std::vector<const std::string> extra_args2({"--boot-image-merge"});
ASSERT_EQ(ProcessProfiles(profile_fds, reference_profile_fd, extra_args2),
- ProfileAssistant::kErrorBadProfiles);
+ ProfmanResult::kErrorBadProfiles);
}
} // namespace art
diff --git a/profman/profman.cc b/profman/profman.cc
index 1968468..8e2f3b1 100644
--- a/profman/profman.cc
+++ b/profman/profman.cc
@@ -36,7 +36,6 @@
#include "android-base/parsebool.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
-
#include "base/array_ref.h"
#include "base/dumpable.h"
#include "base/logging.h" // For InitLogging.
@@ -64,6 +63,7 @@
#include "profile/profile_boot_info.h"
#include "profile/profile_compilation_info.h"
#include "profile_assistant.h"
+#include "profman/profman_result.h"
namespace art {
@@ -118,7 +118,10 @@
UsageError("");
UsageError(" --profile-file=<filename>: specify profiler output file to use for compilation.");
UsageError(" Can be specified multiple time, in which case the data from the different");
- UsageError(" profiles will be aggregated.");
+ UsageError(" profiles will be aggregated. Can also be specified zero times, in which case");
+ UsageError(" profman will still analyze the reference profile against the given --apk and");
+ UsageError(" return exit code based on whether the reference profile is empty and whether");
+ UsageError(" an error occurs, but no merge will happen.");
UsageError("");
UsageError(" --profile-file-fd=<number>: same as --profile-file but accepts a file descriptor.");
UsageError(" Cannot be used together with --profile-file.");
@@ -126,8 +129,8 @@
UsageError(" --reference-profile-file=<filename>: specify a reference profile.");
UsageError(" The data in this file will be compared with the data obtained by merging");
UsageError(" all the files specified with --profile-file or --profile-file-fd.");
- UsageError(" If the exit code is EXIT_COMPILE then all --profile-file will be merged into");
- UsageError(" --reference-profile-file. ");
+ UsageError(" If the exit code is ProfmanResult::kCompile then all --profile-file will be");
+ UsageError(" merged into --reference-profile-file. ");
UsageError("");
UsageError(" --reference-profile-file-fd=<number>: same as --reference-profile-file but");
UsageError(" accepts a file descriptor. Cannot be used together with");
@@ -194,7 +197,7 @@
UsageError(" the min percent of new classes to trigger a compilation.");
UsageError("");
- exit(EXIT_FAILURE);
+ exit(ProfmanResult::kErrorUsage);
}
// Note: make sure you update the Usage if you change these values.
@@ -501,11 +504,10 @@
}
};
- ProfileAssistant::ProcessingResult ProcessProfiles() {
- // Validate that at least one profile file was passed, as well as a reference profile.
- if (profile_files_.empty() && profile_files_fd_.empty()) {
- Usage("No profile files specified.");
- }
+ ProfmanResult::ProcessingResult ProcessProfiles() {
+ // Validate that a reference profile was passed, at the very least. It's okay that profiles are
+ // missing, in which case profman will still analyze the reference profile (to check whether
+ // it's empty), but no merge will happen.
if (reference_profile_file_.empty() && !FdIsValid(reference_profile_file_fd_)) {
Usage("No reference profile file specified.");
}
@@ -518,7 +520,7 @@
// Check if we have any apks which we should use to filter the profile data.
std::set<ProfileFilterKey> profile_filter_keys;
if (!GetProfileFilterKeyFromApks(&profile_filter_keys)) {
- return ProfileAssistant::kErrorIO;
+ return ProfmanResult::kErrorIO;
}
// Build the profile filter function. If the set of keys is empty it means we
@@ -536,9 +538,9 @@
}
};
- ProfileAssistant::ProcessingResult result;
+ ProfmanResult::ProcessingResult result;
- if (profile_files_.empty()) {
+ if (reference_profile_file_.empty()) {
// The file doesn't need to be flushed here (ProcessProfiles will do it)
// so don't check the usage.
File file(reference_profile_file_fd_, false);
@@ -1854,7 +1856,7 @@
return copy_and_update_profile_key_;
}
- int32_t CopyAndUpdateProfileKey() {
+ ProfmanResult::CopyAndUpdateResult CopyAndUpdateProfileKey() {
// Validate that at least one profile file was passed, as well as a reference profile.
if (!(profile_files_.size() == 1 ^ profile_files_fd_.size() == 1)) {
Usage("Only one profile file should be specified.");
@@ -1867,10 +1869,6 @@
Usage("No apk files specified");
}
- static constexpr int32_t kErrorFailedToUpdateProfile = -1;
- static constexpr int32_t kErrorFailedToSaveProfile = -2;
- static constexpr int32_t kErrorFailedToLoadProfile = -3;
-
bool use_fds = profile_files_fd_.size() == 1;
ProfileCompilationInfo profile;
@@ -1882,15 +1880,19 @@
// Open the dex files to look up classes and methods.
std::vector<std::unique_ptr<const DexFile>> dex_files;
OpenApkFilesFromLocations(&dex_files);
- if (!profile.UpdateProfileKeys(dex_files)) {
- return kErrorFailedToUpdateProfile;
+ bool updated = false;
+ if (!profile.UpdateProfileKeys(dex_files, &updated)) {
+ return ProfmanResult::kCopyAndUpdateErrorFailedToUpdateProfile;
}
bool result = use_fds
? profile.Save(reference_profile_file_fd_)
: profile.Save(reference_profile_file_, /*bytes_written=*/ nullptr);
- return result ? 0 : kErrorFailedToSaveProfile;
+ if (!result) {
+ return ProfmanResult::kCopyAndUpdateErrorFailedToSaveProfile;
+ }
+ return updated ? ProfmanResult::kCopyAndUpdateSuccess : ProfmanResult::kCopyAndUpdateNoUpdate;
} else {
- return kErrorFailedToLoadProfile;
+ return ProfmanResult::kCopyAndUpdateErrorFailedToLoadProfile;
}
}
@@ -1950,7 +1952,7 @@
return ics.Dump(os);
}
-// See ProfileAssistant::ProcessingResult for return codes.
+// See ProfmanResult for return codes.
static int profman(int argc, char** argv) {
ProfMan profman;
diff --git a/runtime/Android.bp b/runtime/Android.bp
index ba7f6cc..d42848f 100644
--- a/runtime/Android.bp
+++ b/runtime/Android.bp
@@ -130,7 +130,7 @@
"exec_utils.cc",
"fault_handler.cc",
"gc/allocation_record.cc",
- "gc/allocator/dlmalloc.cc",
+ "gc/allocator/art-dlmalloc.cc",
"gc/allocator/rosalloc.cc",
"gc/accounting/bitmap.cc",
"gc/accounting/card_table.cc",
@@ -142,6 +142,7 @@
"gc/collector/garbage_collector.cc",
"gc/collector/immune_region.cc",
"gc/collector/immune_spaces.cc",
+ "gc/collector/mark_compact.cc",
"gc/collector/mark_sweep.cc",
"gc/collector/partial_mark_sweep.cc",
"gc/collector/semi_space.cc",
@@ -173,7 +174,6 @@
"interpreter/interpreter.cc",
"interpreter/interpreter_cache.cc",
"interpreter/interpreter_common.cc",
- "interpreter/interpreter_intrinsics.cc",
"interpreter/interpreter_switch_impl0.cc",
"interpreter/interpreter_switch_impl1.cc",
"interpreter/interpreter_switch_impl2.cc",
@@ -209,6 +209,7 @@
"mirror/method_handles_lookup.cc",
"mirror/method_type.cc",
"mirror/object.cc",
+ "mirror/stack_frame_info.cc",
"mirror/stack_trace_element.cc",
"mirror/string.cc",
"mirror/throwable.cc",
@@ -225,6 +226,7 @@
"native/dalvik_system_ZygoteHooks.cc",
"native/java_lang_Class.cc",
"native/java_lang_Object.cc",
+ "native/java_lang_StackStreamFactory.cc",
"native/java_lang_String.cc",
"native/java_lang_StringFactory.cc",
"native/java_lang_System.cc",
@@ -254,6 +256,7 @@
"oat.cc",
"oat_file.cc",
"oat_file_assistant.cc",
+ "oat_file_assistant_context.cc",
"oat_file_manager.cc",
"oat_quick_method_header.cc",
"object_lock.cc",
@@ -415,7 +418,6 @@
],
static_libs: [
"libstatslog_art",
- "libtinyxml2",
],
generated_sources: [
"apex-info-list-tinyxml",
@@ -456,17 +458,20 @@
header_libs: [
"art_cmdlineparser_headers",
"cpp-define-generator-definitions",
+ "dlmalloc",
"jni_platform_headers",
"libart_headers",
"libnativehelper_header_only",
],
- export_header_lib_headers: ["libart_headers"],
+ export_header_lib_headers: [
+ "dlmalloc",
+ "libart_headers",
+ ],
whole_static_libs: [
"libcpu_features",
],
shared_libs: [
"libartpalette",
- "libbacktrace",
"libbase", // For common macros.
"liblog",
"liblz4",
@@ -496,7 +501,6 @@
name: "libart_static_base_defaults",
whole_static_libs: [
"libartpalette",
- "libbacktrace",
"libbase",
"liblog",
"liblz4",
@@ -679,6 +683,7 @@
],
shared_libs: [
"libbase",
+ "libziparchive",
],
static_libs: [
"libprocinfo",
@@ -848,7 +853,11 @@
"verifier/reg_type_test.cc",
],
shared_libs: [
- "libbacktrace",
+ "libunwindstack",
+ "libziparchive",
+ ],
+ static_libs: [
+ "libgmock",
],
header_libs: [
"art_cmdlineparser_headers", // For parsed_options_test.
diff --git a/runtime/alloc_instrumentation.md b/runtime/alloc_instrumentation.md
index 513bbe3..66e9a6a 100644
--- a/runtime/alloc_instrumentation.md
+++ b/runtime/alloc_instrumentation.md
@@ -17,7 +17,7 @@
- These in turn are called by `SetStatsEnabled()`, `SetAllocationListener()`, et al, which
require the mutator lock is not held.
-- With a started runtime, `SetEntrypointsInstrumented()` calls `ScopedSupendAll(`) before updating
+- With a started runtime, `SetEntrypointsInstrumented()` calls `ScopedSuspendAll(`) before updating
the function table.
Mutual exclusion in the dispatch table is thus ensured by the fact that it is only updated while
diff --git a/runtime/app_info.cc b/runtime/app_info.cc
index c72951e..2dbbbf6 100644
--- a/runtime/app_info.cc
+++ b/runtime/app_info.cc
@@ -93,6 +93,12 @@
<< "\nodex_status=" << odex_status;
}
+bool AppInfo::HasRegisteredAppInfo() {
+ MutexLock mu(Thread::Current(), update_mutex_);
+
+ return package_name_.has_value();
+}
+
void AppInfo::GetPrimaryApkOptimizationStatus(
std::string* out_compiler_filter,
std::string* out_compilation_reason) {
@@ -110,6 +116,13 @@
*out_compilation_reason = kUnknownValue;
}
+AppInfo::CodeType AppInfo::GetRegisteredCodeType(const std::string& code_path) {
+ MutexLock mu(Thread::Current(), update_mutex_);
+
+ const auto it = registered_code_locations_.find(code_path);
+ return it != registered_code_locations_.end() ? it->second.code_type : CodeType::kUnknown;
+}
+
std::ostream& operator<<(std::ostream& os, AppInfo& rhs) {
MutexLock mu(Thread::Current(), rhs.update_mutex_);
diff --git a/runtime/app_info.h b/runtime/app_info.h
index 68f2c58..43e2ef3 100644
--- a/runtime/app_info.h
+++ b/runtime/app_info.h
@@ -77,6 +77,13 @@
void GetPrimaryApkOptimizationStatus(std::string* out_compiler_filter,
std::string* out_compilation_reason);
+ // Whether we've received a call to RegisterAppInfo.
+ bool HasRegisteredAppInfo();
+
+ // The registered code type for a given code path. Note that this will
+ // be kUnknown until an explicit registration for that path has been made.
+ CodeType GetRegisteredCodeType(const std::string& code_path);
+
private:
// Encapsulates optimization information about a particular code location.
struct CodeLocationInfo {
diff --git a/runtime/app_info_test.cc b/runtime/app_info_test.cc
index 4a365de..51dd42f 100644
--- a/runtime/app_info_test.cc
+++ b/runtime/app_info_test.cc
@@ -24,12 +24,17 @@
TEST(AppInfoTest, RegisterAppInfo) {
AppInfo app_info;
+ EXPECT_FALSE(app_info.HasRegisteredAppInfo());
+ EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kUnknown);
+
app_info.RegisterAppInfo(
"package_name",
std::vector<std::string>({"code_location"}),
"",
"",
AppInfo::CodeType::kPrimaryApk);
+ EXPECT_TRUE(app_info.HasRegisteredAppInfo());
+ EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
std::string filter;
std::string reason;
@@ -48,11 +53,13 @@
"",
"",
AppInfo::CodeType::kPrimaryApk);
+ EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
app_info.RegisterOdexStatus(
"code_location",
"filter",
"reason",
"odex_status");
+ EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
std::string filter;
std::string reason;
@@ -69,17 +76,22 @@
"filter",
"reason",
"odex_status");
+ EXPECT_FALSE(app_info.HasRegisteredAppInfo());
app_info.RegisterOdexStatus(
"code_location2",
"filter2",
"reason2",
"odex_status");
+ EXPECT_FALSE(app_info.HasRegisteredAppInfo());
app_info.RegisterAppInfo(
"package_name",
std::vector<std::string>({"code_location"}),
"",
"",
AppInfo::CodeType::kPrimaryApk);
+ EXPECT_TRUE(app_info.HasRegisteredAppInfo());
+ EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kPrimaryApk);
+ EXPECT_EQ(app_info.GetRegisteredCodeType("code_location2"), AppInfo::CodeType::kUnknown);
std::string filter;
std::string reason;
@@ -110,7 +122,7 @@
"filter",
"reason",
"odex_status");
-
+ EXPECT_EQ(app_info.GetRegisteredCodeType("code_location"), AppInfo::CodeType::kSplitApk);
// The optimization status is unknown since we don't have primary apks.
app_info.GetPrimaryApkOptimizationStatus(&filter, &reason);
diff --git a/runtime/arch/arm/asm_support_arm.S b/runtime/arch/arm/asm_support_arm.S
index 23d82ba..68afc24 100644
--- a/runtime/arch/arm/asm_support_arm.S
+++ b/runtime/arch/arm/asm_support_arm.S
@@ -315,10 +315,6 @@
DELIVER_PENDING_EXCEPTION
.endm
-.macro RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
- RETURN_OR_DELIVER_PENDING_EXCEPTION_REG r1
-.endm
-
.macro RETURN_OR_DELIVER_PENDING_EXCEPTION
ldr ip, [rSELF, #THREAD_EXCEPTION_OFFSET] @ Get exception field.
cmp ip, #0
diff --git a/runtime/arch/arm/asm_support_arm.h b/runtime/arch/arm/asm_support_arm.h
index aff055e..a3d46c2 100644
--- a/runtime/arch/arm/asm_support_arm.h
+++ b/runtime/arch/arm/asm_support_arm.h
@@ -25,6 +25,8 @@
#define FRAME_SIZE_SAVE_EVERYTHING 192
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_R0_OFFSET \
+ (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
// The offset from the art_quick_read_barrier_mark_introspection (used for field
// loads with 32-bit LDR) to the entrypoint for field loads with 16-bit LDR,
@@ -43,22 +45,22 @@
// The offset of the reference load LDR from the return address in LR for field loads.
#ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET -8
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET (-8)
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET (-4)
#else
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET -4
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET -2
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_WIDE_OFFSET (-4)
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_NARROW_OFFSET (-2)
#endif
// The offset of the reference load LDR from the return address in LR for array loads.
#ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-8)
#else
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-4)
#endif
// The offset of the reference load LDR from the return address in LR for GC root loads.
-#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_OFFSET -8
-#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_OFFSET -6
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_WIDE_OFFSET (-8)
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_NARROW_OFFSET (-6)
// The offset of the MOV from the return address in LR for intrinsic CAS.
-#define BAKER_MARK_INTROSPECTION_INTRINSIC_CAS_MOV_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_INTRINSIC_CAS_MOV_OFFSET (-8)
#endif // ART_RUNTIME_ARCH_ARM_ASM_SUPPORT_ARM_H_
diff --git a/runtime/arch/arm/entrypoints_init_arm.cc b/runtime/arch/arm/entrypoints_init_arm.cc
index b0b0064..555babe 100644
--- a/runtime/arch/arm/entrypoints_init_arm.cc
+++ b/runtime/arch/arm/entrypoints_init_arm.cc
@@ -91,7 +91,7 @@
qpoints->SetReadBarrierMarkReg10(is_active ? art_quick_read_barrier_mark_reg10 : nullptr);
qpoints->SetReadBarrierMarkReg11(is_active ? art_quick_read_barrier_mark_reg11 : nullptr);
- if (kUseReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
// For the alignment check, strip the Thumb mode bit.
DCHECK_ALIGNED(reinterpret_cast<intptr_t>(art_quick_read_barrier_mark_introspection) - 1u,
256u);
diff --git a/runtime/arch/arm/jni_entrypoints_arm.S b/runtime/arch/arm/jni_entrypoints_arm.S
index fc57df7..d91882c 100644
--- a/runtime/arch/arm/jni_entrypoints_arm.S
+++ b/runtime/arch/arm/jni_entrypoints_arm.S
@@ -99,7 +99,7 @@
// Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
// for @FastNative or @CriticalNative.
ldr ip, [r0, #THREAD_TOP_QUICK_FRAME_OFFSET] // uintptr_t tagged_quick_frame
- bic ip, #1 // ArtMethod** sp
+ bic ip, #TAGGED_JNI_SP_MASK // ArtMethod** sp
ldr ip, [ip] // ArtMethod* method
ldr ip, [ip, #ART_METHOD_ACCESS_FLAGS_OFFSET] // uint32_t access_flags
tst ip, #(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE)
@@ -327,6 +327,11 @@
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, rSELF
/*
+ * Trampoline to `artJniMethodEntryHook()` that preserves all managed arguments.
+ */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_entry_hook, artJniMethodEntryHook, rSELF
+
+ /*
* Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
*/
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_monitored_method_start, artJniMonitoredMethodStart, rSELF
diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S
index d6f129b..bd8149e 100644
--- a/runtime/arch/arm/quick_entrypoints_arm.S
+++ b/runtime/arch/arm/quick_entrypoints_arm.S
@@ -134,16 +134,50 @@
.cfi_adjust_cfa_offset -52
.endm
-.macro RETURN_IF_RESULT_IS_ZERO
- cbnz r0, 1f @ result non-zero branch over
- bx lr @ return
+.macro RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ ldr r1, [rSELF, # THREAD_EXCEPTION_OFFSET] // Get exception field.
+ cbnz r1, 1f
+ DEOPT_OR_RETURN r1 // Check if deopt is required
1:
+ DELIVER_PENDING_EXCEPTION
.endm
-.macro RETURN_IF_RESULT_IS_NON_ZERO
- cbz r0, 1f @ result zero branch over
- bx lr @ return
-1:
+.macro DEOPT_OR_RETURN temp, is_ref = 0
+ ldr \temp, [rSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+ cbnz \temp, 2f
+ bx lr
+2:
+ SETUP_SAVE_EVERYTHING_FRAME \temp
+ mov r2, \is_ref // pass if result is a reference
+ mov r1, r0 // pass the result
+ mov r0, rSELF // Thread::Current
+ bl artDeoptimizeIfNeeded
+ .cfi_remember_state
+ RESTORE_SAVE_EVERYTHING_FRAME
+ REFRESH_MARKING_REGISTER
+ bx lr
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+.endm
+
+.macro DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_R0 temp, is_ref
+ ldr \temp, [rSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+ cbnz \temp, 2f
+ .cfi_remember_state
+ RESTORE_SAVE_EVERYTHING_FRAME_KEEP_R0
+ REFRESH_MARKING_REGISTER
+ bx lr
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+ str r0, [sp, SAVE_EVERYTHING_FRAME_R0_OFFSET] // update result in the frame
+ mov r2, \is_ref // pass if result is a reference
+ mov r1, r0 // pass the result
+ mov r0, rSELF // Thread::Current
+ bl artDeoptimizeIfNeeded
+ .cfi_remember_state
+ RESTORE_SAVE_EVERYTHING_FRAME
+ REFRESH_MARKING_REGISTER
+ bx lr
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
.endm
.macro NO_ARG_RUNTIME_EXCEPTION c_name, cxx_name
@@ -183,12 +217,16 @@
.endm
.macro RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
- RETURN_IF_RESULT_IS_ZERO
+ cbnz r0, 1f @ result non-zero branch over
+ DEOPT_OR_RETURN r1
+1:
DELIVER_PENDING_EXCEPTION
.endm
-.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
- RETURN_IF_RESULT_IS_NON_ZERO
+.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+ cbz r0, 1f @ result zero branch over
+ DEOPT_OR_RETURN r1, /*is_ref=*/1
+1:
DELIVER_PENDING_EXCEPTION
.endm
@@ -517,8 +555,7 @@
bl artLockObjectFromCode @ (Object* obj, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_ZERO
- DELIVER_PENDING_EXCEPTION
+ RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
END art_quick_lock_object_no_inline
/*
@@ -548,8 +585,7 @@
bl artUnlockObjectFromCode @ (Object* obj, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_ZERO
- DELIVER_PENDING_EXCEPTION
+ RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
END art_quick_unlock_object_no_inline
/*
@@ -601,13 +637,33 @@
.cfi_rel_offset \rReg, \offset
.endm
- /*
- * Macro to insert read barrier, only used in art_quick_aput_obj.
- * rObj and rDest are registers, offset is a defined literal such as MIRROR_OBJECT_CLASS_OFFSET.
- * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
- */
-.macro READ_BARRIER rDest, rObj, offset
+ // Helper macros for `art_quick_aput_obj`.
#ifdef USE_READ_BARRIER
+#ifdef USE_BAKER_READ_BARRIER
+.macro BAKER_RB_CHECK_GRAY_BIT_AND_LOAD rDest, rObj, offset, gray_slow_path_label
+ ldr ip, [\rObj, #MIRROR_OBJECT_LOCK_WORD_OFFSET]
+ tst ip, #LOCK_WORD_READ_BARRIER_STATE_MASK_SHIFTED
+ bne \gray_slow_path_label
+ // False dependency to avoid needing load/load fence.
+ add \rObj, \rObj, ip, lsr #32
+ ldr \rDest, [\rObj, #\offset]
+ UNPOISON_HEAP_REF \rDest
+.endm
+
+.macro BAKER_RB_LOAD_AND_MARK rDest, rObj, offset, mark_function
+ ldr \rDest, [\rObj, #\offset]
+ UNPOISON_HEAP_REF \rDest
+ str lr, [sp, #-8]! @ Save LR with correct stack alignment.
+ .cfi_rel_offset lr, 0
+ .cfi_adjust_cfa_offset 8
+ bl \mark_function
+ ldr lr, [sp], #8 @ Restore LR.
+ .cfi_restore lr
+ .cfi_adjust_cfa_offset -8
+.endm
+#else // USE_BAKER_READ_BARRIER
+ .extern artReadBarrierSlow
+.macro READ_BARRIER_SLOW rDest, rObj, offset
push {r0-r3, ip, lr} @ 6 words for saved registers (used in art_quick_aput_obj)
.cfi_adjust_cfa_offset 24
.cfi_rel_offset r0, 0
@@ -640,30 +696,36 @@
pop {lr} @ restore lr
.cfi_adjust_cfa_offset -4
.cfi_restore lr
-#else
- ldr \rDest, [\rObj, #\offset]
- UNPOISON_HEAP_REF \rDest
-#endif // USE_READ_BARRIER
.endm
+#endif // USE_BAKER_READ_BARRIER
+#endif // USE_READ_BARRIER
-#ifdef USE_READ_BARRIER
- .extern artReadBarrierSlow
-#endif
.hidden art_quick_aput_obj
ENTRY art_quick_aput_obj
-#ifdef USE_READ_BARRIER
- @ The offset to .Ldo_aput_null is too large to use cbz due to expansion from READ_BARRIER macro.
+#if defined(USE_READ_BARRIER) && !defined(USE_BAKER_READ_BARRIER)
+ @ The offset to .Ldo_aput_null is too large to use cbz due to expansion from `READ_BARRIER_SLOW`.
tst r2, r2
- beq .Ldo_aput_null
-#else
- cbz r2, .Ldo_aput_null
+ beq .Laput_obj_null
+ READ_BARRIER_SLOW r3, r0, MIRROR_OBJECT_CLASS_OFFSET
+ READ_BARRIER_SLOW r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
+ READ_BARRIER_SLOW r4, r2, MIRROR_OBJECT_CLASS_OFFSET
+#else // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
+ cbz r2, .Laput_obj_null
+#ifdef USE_READ_BARRIER
+ cmp rMR, #0
+ bne .Laput_obj_gc_marking
#endif // USE_READ_BARRIER
- READ_BARRIER r3, r0, MIRROR_OBJECT_CLASS_OFFSET
- READ_BARRIER ip, r2, MIRROR_OBJECT_CLASS_OFFSET
- READ_BARRIER r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
- cmp r3, ip @ value's type == array's component type - trivial assignability
- bne .Lcheck_assignability
-.Ldo_aput:
+ ldr r3, [r0, #MIRROR_OBJECT_CLASS_OFFSET]
+ UNPOISON_HEAP_REF r3
+ // R4 is a scratch register in managed ARM ABI.
+ ldr r4, [r2, #MIRROR_OBJECT_CLASS_OFFSET]
+ UNPOISON_HEAP_REF r4
+ ldr r3, [r3, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET]
+ UNPOISON_HEAP_REF r3
+#endif // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
+ cmp r3, r4 @ value's type == array's component type - trivial assignability
+ bne .Laput_obj_check_assignability
+.Laput_obj_store:
add r3, r0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
POISON_HEAP_REF r2
str r2, [r3, r1, lsl #2]
@@ -671,26 +733,22 @@
lsr r0, r0, #CARD_TABLE_CARD_SHIFT
strb r3, [r3, r0]
blx lr
-.Ldo_aput_null:
+
+.Laput_obj_null:
add r3, r0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
str r2, [r3, r1, lsl #2]
blx lr
-.Lcheck_assignability:
+
+.Laput_obj_check_assignability:
push {r0-r2, lr} @ save arguments
.cfi_adjust_cfa_offset 16
- .cfi_rel_offset r0, 0
- .cfi_rel_offset r1, 4
- .cfi_rel_offset r2, 8
.cfi_rel_offset lr, 12
- mov r1, ip
+ mov r1, r4
mov r0, r3
bl artIsAssignableFromCode
cbz r0, .Lthrow_array_store_exception
.cfi_remember_state
pop {r0-r2, lr}
- .cfi_restore r0
- .cfi_restore r1
- .cfi_restore r2
.cfi_restore lr
.cfi_adjust_cfa_offset -16
add r3, r0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
@@ -700,19 +758,52 @@
lsr r0, r0, #CARD_TABLE_CARD_SHIFT
strb r3, [r3, r0]
blx lr
+
.Lthrow_array_store_exception:
CFI_RESTORE_STATE_AND_DEF_CFA sp, 16
pop {r0-r2, lr}
- .cfi_restore r0
- .cfi_restore r1
- .cfi_restore r2
.cfi_restore lr
.cfi_adjust_cfa_offset -16
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+ .cfi_remember_state
+#endif // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
SETUP_SAVE_ALL_CALLEE_SAVES_FRAME r3
mov r1, r2
- mov r2, rSELF @ pass Thread::Current
+ mov r2, rSELF @ Pass Thread::Current.
bl artThrowArrayStoreException @ (Class*, Class*, Thread*)
- bkpt @ unreached
+ bkpt @ Unreachable.
+
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, 0
+.Laput_obj_gc_marking:
+ BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+ r3, r0, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_array_class
+.Laput_obj_mark_array_class_continue:
+ BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+ r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, .Laput_obj_mark_array_element
+.Laput_obj_mark_array_element_continue:
+ BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+ r4, r2, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_object_class
+.Laput_obj_mark_object_class_continue:
+
+ cmp r3, r4 @ value's type == array's component type - trivial assignability
+ // All registers are set up for correctly `.Laput_obj_check_assignability`.
+ bne .Laput_obj_check_assignability
+ b .Laput_obj_store
+
+.Laput_obj_mark_array_class:
+ BAKER_RB_LOAD_AND_MARK r3, r0, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg03
+ b .Laput_obj_mark_array_class_continue
+
+.Laput_obj_mark_array_element:
+ BAKER_RB_LOAD_AND_MARK \
+ r3, r3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, art_quick_read_barrier_mark_reg03
+ b .Laput_obj_mark_array_element_continue
+
+.Laput_obj_mark_object_class:
+ BAKER_RB_LOAD_AND_MARK r4, r2, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg04
+ b .Laput_obj_mark_object_class_continue
+#endif // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
END art_quick_aput_obj
// Macro to facilitate adding new allocation entrypoints.
@@ -782,11 +873,8 @@
mov r1, rSELF @ pass Thread::Current
bl \entrypoint @ (uint32_t index, Thread*)
cbz r0, 1f @ If result is null, deliver the OOME.
- .cfi_remember_state
- RESTORE_SAVE_EVERYTHING_FRAME_KEEP_R0
- REFRESH_MARKING_REGISTER
- bx lr
- CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+ str r0, [sp, #136] @ store result in the frame
+ DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_R0 r1, /* is_ref= */ 1
1:
DELIVER_PENDING_EXCEPTION_FRAME_READY
END \name
@@ -809,12 +897,12 @@
/*
* Called by managed code to resolve a static field and load a non-wide value.
*/
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
/*
* Called by managed code to resolve a static field and load a 64-bit primitive value.
*/
@@ -827,7 +915,7 @@
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
cbnz r2, 1f @ success if no exception pending
- bx lr @ return on success
+ DEOPT_OR_RETURN r2 @ check if deopt is required or return
1:
DELIVER_PENDING_EXCEPTION
END art_quick_get64_static
@@ -835,12 +923,12 @@
/*
* Called by managed code to resolve an instance field and load a non-wide value.
*/
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_R1
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
/*
* Called by managed code to resolve an instance field and load a 64-bit primitive value.
*/
@@ -853,7 +941,7 @@
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
cbnz r2, 1f @ success if no exception pending
- bx lr @ return on success
+ DEOPT_OR_RETURN r2 @ check if deopt is required or return
1:
DELIVER_PENDING_EXCEPTION
END art_quick_get64_instance
@@ -888,8 +976,7 @@
.cfi_adjust_cfa_offset -16
RESTORE_SAVE_REFS_ONLY_FRAME @ TODO: we can clearly save an add here
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_ZERO
- DELIVER_PENDING_EXCEPTION
+ RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
END art_quick_set64_instance
.extern artSet64StaticFromCompiledCode
@@ -903,8 +990,7 @@
.cfi_adjust_cfa_offset -16
RESTORE_SAVE_REFS_ONLY_FRAME @ TODO: we can clearly save an add here
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_ZERO
- DELIVER_PENDING_EXCEPTION
+ RETURN_IF_RESULT_IS_ZERO_OR_DELIVER
END art_quick_set64_static
// Generate the allocation entrypoints for each allocator.
@@ -1037,7 +1123,7 @@
bl \cxx_name @ (mirror::Class* cls, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END \c_name
.endm
@@ -1096,10 +1182,6 @@
str r1, [rSELF, #THREAD_LOCAL_OBJECTS_OFFSET]
POISON_HEAP_REF r0
str r0, [r2, #MIRROR_OBJECT_CLASS_OFFSET] // Store the class pointer.
- // Fence. This is "ish" not "ishst" so
- // that the code after this allocation
- // site will see the right values in
- // the fields of the class.
mov r0, r2
// No barrier. The class is already observably initialized (otherwise the fast
// path size check above would fail) and new-instance allocations are protected
@@ -1122,7 +1204,7 @@
bl \entrypoint // (mirror::Class* klass, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END \name
.endm
@@ -1162,10 +1244,6 @@
POISON_HEAP_REF r0
str r0, [r3, #MIRROR_OBJECT_CLASS_OFFSET] // Store the class pointer.
str r1, [r3, #MIRROR_ARRAY_LENGTH_OFFSET] // Store the array length.
- // Fence. This is "ish" not "ishst" so
- // that the code after this allocation
- // site will see the right values in
- // the fields of the class.
mov r0, r3
// new-array is special. The class is loaded and immediately goes to the Initialized state
// before it is published. Therefore the only fence needed is for the publication of the object.
@@ -1195,7 +1273,7 @@
bl \entrypoint
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END \name
.endm
@@ -1448,16 +1526,26 @@
.cfi_remember_state
.cfi_def_cfa_register sp
+ // store into fpr, for when it's a fpr return...
+ vmov d0, r0, r1
+
+ LOAD_RUNTIME_INSTANCE r2
+ ldr r2, [r2, #INSTRUMENTATION_STUBS_INSTALLED_OFFSET_FROM_RUNTIME_INSTANCE]
+ cbnz r2, .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
+
// Tear down the callee-save frame. Skip arg registers.
add sp, #FRAME_SIZE_SAVE_REFS_AND_ARGS-FRAME_SIZE_SAVE_REFS_ONLY
.cfi_adjust_cfa_offset -(FRAME_SIZE_SAVE_REFS_AND_ARGS-FRAME_SIZE_SAVE_REFS_ONLY)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- // store into fpr, for when it's a fpr return...
- vmov d0, r0, r1
bx lr // ret
+.Lcall_method_exit_hook:
+ bl art_quick_method_exit_hook
+ b .Lcall_method_exit_hook_done
+
// Undo the unwinding information from above since it doesn't apply below.
CFI_RESTORE_STATE_AND_DEF_CFA r10, FRAME_SIZE_SAVE_REFS_AND_ARGS
.Lexception_in_native:
@@ -1883,7 +1971,7 @@
bl artStringBuilderAppend @ (uint32_t, const unit32_t*, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END art_quick_string_builder_append
/*
@@ -1895,13 +1983,9 @@
*
* If `reg` is different from `r0`, the generated function follows a
* non-standard runtime calling convention:
- * - register `reg` is used to pass the (sole) argument of this
- * function (instead of R0);
- * - register `reg` is used to return the result of this function
- * (instead of R0);
- * - R0 is treated like a normal (non-argument) caller-save register;
- * - everything else is the same as in the standard runtime calling
- * convention (e.g. standard callee-save registers are preserved).
+ * - register `reg` (which may be different from R0) is used to pass the (sole) argument,
+ * - register `reg` (which may be different from R0) is used to return the result,
+ * - all other registers are callee-save (the values they hold are preserved).
*/
.macro READ_BARRIER_MARK_REG name, reg
ENTRY \name
@@ -2496,14 +2580,8 @@
mov r0, rSELF @ pass Thread::Current
blx artMethodExitHook @ (Thread*, ArtMethod*, gpr_res*, fpr_res*)
- .cfi_remember_state
- cbnz r0, .Ldo_deliver_instrumentation_exception_exit @ Deliver exception
-
// Normal return.
RESTORE_SAVE_EVERYTHING_FRAME
REFRESH_MARKING_REGISTER
blx lr
-.Ldo_deliver_instrumentation_exception_exit:
- CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
- DELIVER_PENDING_EXCEPTION_FRAME_READY
END art_quick_method_exit_hook
diff --git a/runtime/arch/arm/quick_entrypoints_cc_arm.cc b/runtime/arch/arm/quick_entrypoints_cc_arm.cc
index 987b459..d7fef6f 100644
--- a/runtime/arch/arm/quick_entrypoints_cc_arm.cc
+++ b/runtime/arch/arm/quick_entrypoints_cc_arm.cc
@@ -25,6 +25,7 @@
uint32_t*);
template <bool kIsStatic>
+NO_STACK_PROTECTOR
static void quick_invoke_reg_setup(ArtMethod* method, uint32_t* args, uint32_t args_size,
Thread* self, JValue* result, const char* shorty) {
// Note: We do not follow aapcs ABI in quick code for both softfp and hardfp.
@@ -96,6 +97,7 @@
// Called by art::ArtMethod::Invoke to do entry into a non-static method.
// TODO: migrate into an assembly implementation as with ARM64.
+NO_STACK_PROTECTOR
extern "C" void art_quick_invoke_stub(ArtMethod* method, uint32_t* args, uint32_t args_size,
Thread* self, JValue* result, const char* shorty) {
quick_invoke_reg_setup<false>(method, args, args_size, self, result, shorty);
@@ -103,6 +105,7 @@
// Called by art::ArtMethod::Invoke to do entry into a static method.
// TODO: migrate into an assembly implementation as with ARM64.
+NO_STACK_PROTECTOR
extern "C" void art_quick_invoke_static_stub(ArtMethod* method, uint32_t* args,
uint32_t args_size, Thread* self, JValue* result,
const char* shorty) {
diff --git a/runtime/arch/arm64/asm_support_arm64.h b/runtime/arch/arm64/asm_support_arm64.h
index 887ee02..983fe3a 100644
--- a/runtime/arch/arm64/asm_support_arm64.h
+++ b/runtime/arch/arm64/asm_support_arm64.h
@@ -19,6 +19,7 @@
#include "asm_support.h"
+// TODO(mythria): Change these to use constants from callee_save_frame_arm64.h
#define CALLEE_SAVES_SIZE (12 * 8 + 8 * 8)
// +8 for the ArtMethod, +8 for alignment.
#define FRAME_SIZE_SAVE_ALL_CALLEE_SAVES (CALLEE_SAVES_SIZE + 16)
@@ -27,6 +28,8 @@
#define FRAME_SIZE_SAVE_EVERYTHING 512
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_X0_OFFSET \
+ (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
// The offset from art_quick_read_barrier_mark_introspection to the array switch cases,
// i.e. art_quick_read_barrier_mark_introspection_arrays.
@@ -37,17 +40,17 @@
// The offset of the reference load LDR from the return address in LR for field loads.
#ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET (-8)
#else
-#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_FIELD_LDR_OFFSET (-4)
#endif
// The offset of the reference load LDR from the return address in LR for array loads.
#ifdef USE_HEAP_POISONING
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-8)
#else
-#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET -4
+#define BAKER_MARK_INTROSPECTION_ARRAY_LDR_OFFSET (-4)
#endif
// The offset of the reference load LDR from the return address in LR for GC root loads.
-#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET -8
+#define BAKER_MARK_INTROSPECTION_GC_ROOT_LDR_OFFSET (-8)
#endif // ART_RUNTIME_ARCH_ARM64_ASM_SUPPORT_ARM64_H_
diff --git a/runtime/arch/arm64/instruction_set_features_arm64.cc b/runtime/arch/arm64/instruction_set_features_arm64.cc
index ad082ae..93400d9 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64.cc
+++ b/runtime/arch/arm64/instruction_set_features_arm64.cc
@@ -171,6 +171,18 @@
has_sve));
}
+Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::IntersectWithHwcap() const {
+ Arm64FeaturesUniquePtr hwcaps = Arm64InstructionSetFeatures::FromHwcap();
+ return Arm64FeaturesUniquePtr(new Arm64InstructionSetFeatures(
+ fix_cortex_a53_835769_,
+ fix_cortex_a53_843419_,
+ has_crc_ && hwcaps->has_crc_,
+ has_lse_ && hwcaps->has_lse_,
+ has_fp16_ && hwcaps->has_fp16_,
+ has_dotprod_ && hwcaps->has_dotprod_,
+ has_sve_ && hwcaps->has_sve_));
+}
+
Arm64FeaturesUniquePtr Arm64InstructionSetFeatures::FromBitmap(uint32_t bitmap) {
bool is_a53 = (bitmap & kA53Bitfield) != 0;
bool has_crc = (bitmap & kCRCBitField) != 0;
diff --git a/runtime/arch/arm64/instruction_set_features_arm64.h b/runtime/arch/arm64/instruction_set_features_arm64.h
index eb98c01..8f0013a 100644
--- a/runtime/arch/arm64/instruction_set_features_arm64.h
+++ b/runtime/arch/arm64/instruction_set_features_arm64.h
@@ -53,6 +53,10 @@
// Use external cpu_features library.
static Arm64FeaturesUniquePtr FromCpuFeatures();
+ // Return a new set of instruction set features, intersecting `this` features
+ // with hardware capabilities.
+ Arm64FeaturesUniquePtr IntersectWithHwcap() const;
+
bool Equals(const InstructionSetFeatures* other) const override;
// Note that newer CPUs do not have a53 erratum 835769 and 843419,
diff --git a/runtime/arch/arm64/jni_entrypoints_arm64.S b/runtime/arch/arm64/jni_entrypoints_arm64.S
index 463767c..9612a7b 100644
--- a/runtime/arch/arm64/jni_entrypoints_arm64.S
+++ b/runtime/arch/arm64/jni_entrypoints_arm64.S
@@ -103,7 +103,7 @@
// Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
// for @FastNative or @CriticalNative.
ldr xIP0, [x0, #THREAD_TOP_QUICK_FRAME_OFFSET] // uintptr_t tagged_quick_frame
- bic xIP0, xIP0, #1 // ArtMethod** sp
+ bic xIP0, xIP0, #TAGGED_JNI_SP_MASK // ArtMethod** sp
ldr xIP0, [xIP0] // ArtMethod* method
ldr xIP0, [xIP0, #ART_METHOD_ACCESS_FLAGS_OFFSET] // uint32_t access_flags
mov xIP1, #(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE)
@@ -366,6 +366,11 @@
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, xSELF
/*
+ * Trampoline to `artJniMethodEntryHook` that preserves all managed arguments.
+ */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_entry_hook, artJniMethodEntryHook, xSELF
+
+ /*
* Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
*/
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_monitored_method_start, artJniMonitoredMethodStart, xSELF
diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S
index d8c91e1..a35206f 100644
--- a/runtime/arch/arm64/quick_entrypoints_arm64.S
+++ b/runtime/arch/arm64/quick_entrypoints_arm64.S
@@ -194,26 +194,56 @@
RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
.endm
-.macro RETURN_IF_RESULT_IS_ZERO
- cbnz x0, 1f // result non-zero branch over
- ret // return
-1:
+.macro RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ ldr x1, [xSELF, # THREAD_EXCEPTION_OFFSET] // Get exception field.
+ cbnz x1, 1f
+ DEOPT_OR_RETURN x1 // Check if deopt is required
+1: // deliver exception on current thread
+ DELIVER_PENDING_EXCEPTION
.endm
-.macro RETURN_IF_RESULT_IS_NON_ZERO
- cbz x0, 1f // result zero branch over
- ret // return
-1:
+.macro DEOPT_OR_RETURN temp, is_ref = 0
+ ldr \temp, [xSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+ cbnz \temp, 2f
+ ret
+2:
+ SETUP_SAVE_EVERYTHING_FRAME
+ mov x2, \is_ref // pass if result is a reference
+ mov x1, x0 // pass the result
+ mov x0, xSELF // Thread::Current
+ bl artDeoptimizeIfNeeded
+ .cfi_remember_state
+ RESTORE_SAVE_EVERYTHING_FRAME
+ REFRESH_MARKING_REGISTER
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
.endm
-// Same as above with x1. This is helpful in stubs that want to avoid clobbering another register.
-.macro RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
- RETURN_OR_DELIVER_PENDING_EXCEPTION_REG x1
+.macro DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_X0 temp, is_ref
+ ldr \temp, [xSELF, #THREAD_DEOPT_CHECK_REQUIRED_OFFSET]
+ cbnz \temp, 2f
+ .cfi_remember_state
+ RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
+ REFRESH_MARKING_REGISTER
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+ str x0, [sp, #SAVE_EVERYTHING_FRAME_X0_OFFSET] // update result in the frame
+ mov x2, \is_ref // pass if result is a reference
+ mov x1, x0 // pass the result
+ mov x0, xSELF // Thread::Current
+ bl artDeoptimizeIfNeeded
+ .cfi_remember_state
+ RESTORE_SAVE_EVERYTHING_FRAME
+ REFRESH_MARKING_REGISTER
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
.endm
+
.macro RETURN_IF_W0_IS_ZERO_OR_DELIVER
cbnz w0, 1f // result non-zero branch over
- ret // return
+ DEOPT_OR_RETURN x1
1:
DELIVER_PENDING_EXCEPTION
.endm
@@ -999,25 +1029,30 @@
.cfi_restore \xReg2
.endm
- /*
- * Macro to insert read barrier, only used in art_quick_aput_obj.
- * xDest, wDest and xObj are registers, offset is a defined literal such as
- * MIRROR_OBJECT_CLASS_OFFSET. Dest needs both x and w versions of the same register to handle
- * name mismatch between instructions. This macro uses the lower 32b of register when possible.
- * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
- */
-.macro READ_BARRIER xDest, wDest, xObj, xTemp, wTemp, offset, number
+ // Helper macros for `art_quick_aput_obj`.
#ifdef USE_READ_BARRIER
-# ifdef USE_BAKER_READ_BARRIER
- ldr \wTemp, [\xObj, #MIRROR_OBJECT_LOCK_WORD_OFFSET]
- tbnz \wTemp, #LOCK_WORD_READ_BARRIER_STATE_SHIFT, .Lrb_slowpath\number
+#ifdef USE_BAKER_READ_BARRIER
+.macro BAKER_RB_CHECK_GRAY_BIT_AND_LOAD wDest, xObj, offset, gray_slow_path_label
+ ldr wIP0, [\xObj, #MIRROR_OBJECT_LOCK_WORD_OFFSET]
+ tbnz wIP0, #LOCK_WORD_READ_BARRIER_STATE_SHIFT, \gray_slow_path_label
// False dependency to avoid needing load/load fence.
- add \xObj, \xObj, \xTemp, lsr #32
- ldr \wDest, [\xObj, #\offset] // Heap reference = 32b. This also zero-extends to \xDest.
+ add \xObj, \xObj, xIP0, lsr #32
+ ldr \wDest, [\xObj, #\offset] // Heap reference = 32b; zero-extends to xN.
UNPOISON_HEAP_REF \wDest
- b .Lrb_exit\number
-# endif // USE_BAKER_READ_BARRIER
-.Lrb_slowpath\number:
+.endm
+
+.macro BAKER_RB_LOAD_AND_MARK wDest, xObj, offset, mark_function
+ ldr \wDest, [\xObj, #\offset] // Heap reference = 32b; zero-extends to xN.
+ UNPOISON_HEAP_REF \wDest
+ // Save LR in a register preserved by `art_quick_read_barrier_mark_regNN`
+ // and unused by the `art_quick_aput_obj`.
+ mov x5, lr
+ bl \mark_function
+ mov lr, x5 // Restore LR.
+.endm
+#else // USE_BAKER_READ_BARRIER
+ .extern artReadBarrierSlow
+.macro READ_BARRIER_SLOW xDest, wDest, xObj, offset
// Store registers used in art_quick_aput_obj (x0-x4, LR), stack is 16B aligned.
SAVE_TWO_REGS_INCREASE_FRAME x0, x1, 48
SAVE_TWO_REGS x2, x3, 16
@@ -1042,41 +1077,44 @@
POP_REG_NE x4, 32, \xDest
RESTORE_REG xLR, 40
DECREASE_FRAME 48
-.Lrb_exit\number:
-#else
- ldr \wDest, [\xObj, #\offset] // Heap reference = 32b. This also zero-extends to \xDest.
- UNPOISON_HEAP_REF \wDest
-#endif // USE_READ_BARRIER
.endm
+#endif // USE_BAKER_READ_BARRIER
+#endif // USE_READ_BARRIER
-#ifdef USE_READ_BARRIER
- .extern artReadBarrierSlow
-#endif
ENTRY art_quick_aput_obj
- cbz x2, .Ldo_aput_null
- READ_BARRIER x3, w3, x0, x3, w3, MIRROR_OBJECT_CLASS_OFFSET, 0 // Heap reference = 32b
- // This also zero-extends to x3
- READ_BARRIER x3, w3, x3, x4, w4, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, 1 // Heap reference = 32b
- // This also zero-extends to x3
- READ_BARRIER x4, w4, x2, x4, w4, MIRROR_OBJECT_CLASS_OFFSET, 2 // Heap reference = 32b
- // This also zero-extends to x4
+ cbz x2, .Laput_obj_null
+#if defined(USE_READ_BARRIER) && !defined(USE_BAKER_READ_BARRIER)
+ READ_BARRIER_SLOW x3, w3, x0, MIRROR_OBJECT_CLASS_OFFSET
+ READ_BARRIER_SLOW x3, w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET
+ READ_BARRIER_SLOW x4, w4, x2, MIRROR_OBJECT_CLASS_OFFSET
+#else // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
+#ifdef USE_READ_BARRIER
+ cbnz wMR, .Laput_obj_gc_marking
+#endif // USE_READ_BARRIER
+ ldr w3, [x0, #MIRROR_OBJECT_CLASS_OFFSET] // Heap reference = 32b; zero-extends to x3.
+ UNPOISON_HEAP_REF w3
+ ldr w3, [x3, #MIRROR_CLASS_COMPONENT_TYPE_OFFSET] // Heap reference = 32b; zero-extends to x3.
+ UNPOISON_HEAP_REF w3
+ ldr w4, [x2, #MIRROR_OBJECT_CLASS_OFFSET] // Heap reference = 32b; zero-extends to x4.
+ UNPOISON_HEAP_REF w4
+#endif // !defined(USE_READ_BARRIER) || defined(USE_BAKER_READ_BARRIER)
cmp w3, w4 // value's type == array's component type - trivial assignability
- bne .Lcheck_assignability
-.Ldo_aput:
+ bne .Laput_obj_check_assignability
+.Laput_obj_store:
add x3, x0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
- // "Compress" = do nothing
POISON_HEAP_REF w2
- str w2, [x3, x1, lsl #2] // Heap reference = 32b
+ str w2, [x3, x1, lsl #2] // Heap reference = 32b.
ldr x3, [xSELF, #THREAD_CARD_TABLE_OFFSET]
lsr x0, x0, #CARD_TABLE_CARD_SHIFT
strb w3, [x3, x0]
ret
-.Ldo_aput_null:
+
+.Laput_obj_null:
add x3, x0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
- // "Compress" = do nothing
- str w2, [x3, x1, lsl #2] // Heap reference = 32b
+ str w2, [x3, x1, lsl #2] // Heap reference = 32b.
ret
-.Lcheck_assignability:
+
+.Laput_obj_check_assignability:
// Store arguments and link register
SAVE_TWO_REGS_INCREASE_FRAME x0, x1, 32
SAVE_TWO_REGS x2, xLR, 16
@@ -1087,7 +1125,7 @@
bl artIsAssignableFromCode
// Check for exception
- cbz x0, .Lthrow_array_store_exception
+ cbz x0, .Laput_obj_throw_array_store_exception
// Restore
.cfi_remember_state
@@ -1095,23 +1133,56 @@
RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32
add x3, x0, #MIRROR_OBJECT_ARRAY_DATA_OFFSET
- // "Compress" = do nothing
POISON_HEAP_REF w2
- str w2, [x3, x1, lsl #2] // Heap reference = 32b
+ str w2, [x3, x1, lsl #2] // Heap reference = 32b.
ldr x3, [xSELF, #THREAD_CARD_TABLE_OFFSET]
lsr x0, x0, #CARD_TABLE_CARD_SHIFT
strb w3, [x3, x0]
ret
CFI_RESTORE_STATE_AND_DEF_CFA sp, 32
-.Lthrow_array_store_exception:
+
+.Laput_obj_throw_array_store_exception:
RESTORE_TWO_REGS x2, xLR, 16
RESTORE_TWO_REGS_DECREASE_FRAME x0, x1, 32
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+ .cfi_remember_state
+#endif // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
SETUP_SAVE_ALL_CALLEE_SAVES_FRAME
mov x1, x2 // Pass value.
mov x2, xSELF // Pass Thread::Current.
bl artThrowArrayStoreException // (Object*, Object*, Thread*).
- brk 0 // Unreached.
+ brk 0 // Unreachable.
+
+#if defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
+ CFI_RESTORE_STATE_AND_DEF_CFA sp, 0
+.Laput_obj_gc_marking:
+ BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+ w3, x0, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_array_class
+.Laput_obj_mark_array_class_continue:
+ BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+ w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, .Laput_obj_mark_array_element
+.Laput_obj_mark_array_element_continue:
+ BAKER_RB_CHECK_GRAY_BIT_AND_LOAD \
+ w4, x2, MIRROR_OBJECT_CLASS_OFFSET, .Laput_obj_mark_object_class
+.Laput_obj_mark_object_class_continue:
+ cmp w3, w4 // value's type == array's component type - trivial assignability
+ bne .Laput_obj_check_assignability
+ b .Laput_obj_store
+
+.Laput_obj_mark_array_class:
+ BAKER_RB_LOAD_AND_MARK w3, x0, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg03
+ b .Laput_obj_mark_array_class_continue
+
+.Laput_obj_mark_array_element:
+ BAKER_RB_LOAD_AND_MARK \
+ w3, x3, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, art_quick_read_barrier_mark_reg03
+ b .Laput_obj_mark_array_element_continue
+
+.Laput_obj_mark_object_class:
+ BAKER_RB_LOAD_AND_MARK w4, x2, MIRROR_OBJECT_CLASS_OFFSET, art_quick_read_barrier_mark_reg04
+ b .Laput_obj_mark_object_class_continue
+#endif // defined(USE_READ_BARRIER) && defined(USE_BAKER_READ_BARRIER)
END art_quick_aput_obj
// Macro to facilitate adding new allocation entrypoints.
@@ -1214,11 +1285,7 @@
mov x1, xSELF // pass Thread::Current
bl \entrypoint // (int32_t index, Thread* self)
cbz w0, 1f // If result is null, deliver the OOME.
- .cfi_remember_state
- RESTORE_SAVE_EVERYTHING_FRAME_KEEP_X0
- REFRESH_MARKING_REGISTER
- ret // return
- CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
+ DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_X0 x1, /* is_ref= */ 1
1:
DELIVER_PENDING_EXCEPTION_FRAME_READY
END \name
@@ -1228,13 +1295,14 @@
ONE_ARG_SAVE_EVERYTHING_DOWNCALL \name, \entrypoint, RUNTIME_SAVE_EVERYTHING_FOR_CLINIT_METHOD_OFFSET
.endm
-.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
- cbz w0, 1f // result zero branch over
- ret // return
+.macro RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+ cbz w0, 1f // result zero branch over
+ DEOPT_OR_RETURN x1, /*is_ref=*/1 // check for deopt or return
1:
DELIVER_PENDING_EXCEPTION
.endm
+
/*
* Entry from managed code that calls artHandleFillArrayDataFromCode and delivers exception on
* failure.
@@ -1256,21 +1324,21 @@
// Note: Functions `art{Get,Set}<Kind>{Static,Instance}FromCompiledCode` are
// defined with a macro in runtime/entrypoints/quick/quick_field_entrypoints.cc.
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION_X1
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_W0_IS_ZERO_OR_DELIVER
@@ -1410,7 +1478,7 @@
bl \cxx_name
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END \c_name
.endm
@@ -1439,10 +1507,6 @@
str x5, [xSELF, #THREAD_LOCAL_OBJECTS_OFFSET]
POISON_HEAP_REF w0
str w0, [x4, #MIRROR_OBJECT_CLASS_OFFSET] // Store the class pointer.
- // Fence. This is "ish" not "ishst" so
- // that the code after this allocation
- // site will see the right values in
- // the fields of the class.
mov x0, x4
// No barrier. The class is already observably initialized (otherwise the fast
// path size check above would fail) and new-instance allocations are protected
@@ -1465,7 +1529,7 @@
bl \entrypoint // (mirror::Class*, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END \name
.endm
@@ -1510,7 +1574,6 @@
POISON_HEAP_REF \wClass
str \wClass, [x0, #MIRROR_OBJECT_CLASS_OFFSET] // Store the class pointer.
str \wCount, [x0, #MIRROR_ARRAY_LENGTH_OFFSET] // Store the array length.
- // Fence.
// new-array is special. The class is loaded and immediately goes to the Initialized state
// before it is published. Therefore the only fence needed is for the publication of the object.
// See ClassLinker::CreateArrayClass() for more details.
@@ -1539,7 +1602,7 @@
bl \entrypoint
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END \name
.endm
@@ -1837,6 +1900,11 @@
.cfi_remember_state
.cfi_def_cfa_register sp
+ LOAD_RUNTIME_INSTANCE x1
+ ldrb w1, [x1, #INSTRUMENTATION_STUBS_INSTALLED_OFFSET_FROM_RUNTIME_INSTANCE]
+ cbnz w1, .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
+
// Tear down the callee-save frame.
RESTORE_SAVE_REFS_AND_ARGS_FRAME
REFRESH_MARKING_REGISTER
@@ -1845,6 +1913,11 @@
fmov d0, x0
ret
+.Lcall_method_exit_hook:
+ fmov d0, x0
+ bl art_quick_method_exit_hook
+ b .Lcall_method_exit_hook_done
+
// Undo the unwinding information from above since it doesn't apply below.
CFI_RESTORE_STATE_AND_DEF_CFA x28, FRAME_SIZE_SAVE_REFS_AND_ARGS
.Lexception_in_native:
@@ -2102,7 +2175,7 @@
bl artStringBuilderAppend // (uint32_t, const unit32_t*, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME
REFRESH_MARKING_REGISTER
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
END art_quick_string_builder_append
/*
@@ -2111,15 +2184,10 @@
* `wreg` (corresponding to X register `xreg`), saving and restoring
* all caller-save registers.
*
- * If `wreg` is different from `w0`, the generated function follows a
- * non-standard runtime calling convention:
- * - register `wreg` is used to pass the (sole) argument of this
- * function (instead of W0);
- * - register `wreg` is used to return the result of this function
- * (instead of W0);
- * - W0 is treated like a normal (non-argument) caller-save register;
- * - everything else is the same as in the standard runtime calling
- * convention (e.g. standard callee-save registers are preserved).
+ * The generated function follows a non-standard runtime calling convention:
+ * - register `reg` (which may be different from W0) is used to pass the (sole) argument,
+ * - register `reg` (which may be different from W0) is used to return the result,
+ * - all other registers are callee-save (the values they hold are preserved).
*/
.macro READ_BARRIER_MARK_REG name, wreg, xreg
ENTRY \name
@@ -2615,15 +2683,9 @@
mov x0, xSELF // Thread::Current
bl artMethodExitHook // (Thread*, ArtMethod*, gpr_res*, fpr_res*)
- .cfi_remember_state
- cbnz x0, .Ldo_deliver_instrumentation_exception_exit // Handle exception
-
// Normal return.
RESTORE_SAVE_EVERYTHING_FRAME
REFRESH_MARKING_REGISTER
ret
-.Ldo_deliver_instrumentation_exception_exit:
- CFI_RESTORE_STATE_AND_DEF_CFA sp, FRAME_SIZE_SAVE_EVERYTHING
- DELIVER_PENDING_EXCEPTION_FRAME_READY
END art_quick_method_exit_hook
diff --git a/runtime/arch/instruction_set_features.cc b/runtime/arch/instruction_set_features.cc
index ec1e340..7a1e6b0 100644
--- a/runtime/arch/instruction_set_features.cc
+++ b/runtime/arch/instruction_set_features.cc
@@ -53,6 +53,33 @@
UNREACHABLE();
}
+std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromVariantAndHwcap(
+ InstructionSet isa, const std::string& variant, std::string* error_msg) {
+ auto variant_features = FromVariant(isa, variant, error_msg);
+ if (variant_features == nullptr) {
+ return nullptr;
+ }
+ // Pixel3a is wrongly reporting itself as cortex-a75, so validate the features
+ // with hwcaps.
+ // Note that when cross-compiling on device (using dex2oat32 for compiling
+ // arm64), the hwcaps will report that no feature is supported. This is
+ // currently our best approach to be safe/correct. Maybe using the
+ // cpu_features library could fix this issue.
+ if (isa == InstructionSet::kArm64) {
+ auto new_features = down_cast<const Arm64InstructionSetFeatures*>(variant_features.get())
+ ->IntersectWithHwcap();
+ if (!variant_features->Equals(new_features.get())) {
+ LOG(WARNING) << "Mismatch between instruction set variant of device ("
+ << *variant_features
+ << ") and features returned by the hardware (" << *new_features << ")";
+ }
+ return new_features;
+ } else {
+ // TODO: Implement this validation on all architectures.
+ return variant_features;
+ }
+}
+
std::unique_ptr<const InstructionSetFeatures> InstructionSetFeatures::FromBitmap(InstructionSet isa,
uint32_t bitmap) {
std::unique_ptr<const InstructionSetFeatures> result;
diff --git a/runtime/arch/instruction_set_features.h b/runtime/arch/instruction_set_features.h
index b80d36f..cee8c5d 100644
--- a/runtime/arch/instruction_set_features.h
+++ b/runtime/arch/instruction_set_features.h
@@ -39,6 +39,12 @@
const std::string& variant,
std::string* error_msg);
+ // Process a CPU variant string for the given ISA and make sure the features advertised
+ // are supported by the hardware. This is needed for Pixel3a which wrongly
+ // reports itself as cortex-a75.
+ static std::unique_ptr<const InstructionSetFeatures> FromVariantAndHwcap(
+ InstructionSet isa, const std::string& variant, std::string* error_msg);
+
// Parse a bitmap for the given isa and create an InstructionSetFeatures.
static std::unique_ptr<const InstructionSetFeatures> FromBitmap(InstructionSet isa,
uint32_t bitmap);
diff --git a/runtime/arch/quick_alloc_entrypoints.S b/runtime/arch/quick_alloc_entrypoints.S
index 32888ed..5d4b24b 100644
--- a/runtime/arch/quick_alloc_entrypoints.S
+++ b/runtime/arch/quick_alloc_entrypoints.S
@@ -16,27 +16,27 @@
.macro GENERATE_ALLOC_ENTRYPOINTS c_suffix, cxx_suffix
// Called by managed code to allocate an object of a resolved class.
-ONE_ARG_DOWNCALL art_quick_alloc_object_resolved\c_suffix, artAllocObjectFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_object_resolved\c_suffix, artAllocObjectFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
// Called by managed code to allocate an object of an initialized class.
-ONE_ARG_DOWNCALL art_quick_alloc_object_initialized\c_suffix, artAllocObjectFromCodeInitialized\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_object_initialized\c_suffix, artAllocObjectFromCodeInitialized\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
// Called by managed code to allocate an object when the caller doesn't know whether it has access
// to the created type.
-ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks\c_suffix, artAllocObjectFromCodeWithChecks\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks\c_suffix, artAllocObjectFromCodeWithChecks\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
// Called by managed code to allocate a string if it could not be removed by any optimizations
-ONE_ARG_DOWNCALL art_quick_alloc_string_object\c_suffix, artAllocStringObject\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_string_object\c_suffix, artAllocStringObject\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
// Called by managed code to allocate an array of a resolve class.
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
// Called by managed code to allocate a string from bytes
-FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes\c_suffix, artAllocStringFromBytesFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes\c_suffix, artAllocStringFromBytesFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
// Called by managed code to allocate a string from chars
-THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars\c_suffix, artAllocStringFromCharsFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars\c_suffix, artAllocStringFromCharsFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
// Called by managed code to allocate a string from string
-ONE_ARG_DOWNCALL art_quick_alloc_string_from_string\c_suffix, artAllocStringFromStringFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ONE_ARG_DOWNCALL art_quick_alloc_string_from_string\c_suffix, artAllocStringFromStringFromCode\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
-TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
+TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64\c_suffix, artAllocArrayFromCodeResolved\cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
.endm
.macro GENERATE_ALL_ALLOC_ENTRYPOINTS
@@ -58,29 +58,29 @@
// GENERATE_ALL_ALLOC_ENTRYPOINTS for selectively implementing allocation fast paths in
// hand-written assembly.
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(c_suffix, cxx_suffix) \
- ONE_ARG_DOWNCALL art_quick_alloc_object_resolved ## c_suffix, artAllocObjectFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ ONE_ARG_DOWNCALL art_quick_alloc_object_resolved ## c_suffix, artAllocObjectFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_INITIALIZED(c_suffix, cxx_suffix) \
- ONE_ARG_DOWNCALL art_quick_alloc_object_initialized ## c_suffix, artAllocObjectFromCodeInitialized ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ ONE_ARG_DOWNCALL art_quick_alloc_object_initialized ## c_suffix, artAllocObjectFromCodeInitialized ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_WITH_ACCESS_CHECK(c_suffix, cxx_suffix) \
- ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks ## c_suffix, artAllocObjectFromCodeWithChecks ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ ONE_ARG_DOWNCALL art_quick_alloc_object_with_checks ## c_suffix, artAllocObjectFromCodeWithChecks ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_OBJECT(c_suffix, cxx_suffix) \
- ONE_ARG_DOWNCALL art_quick_alloc_string_object ## c_suffix, artAllocStringObject ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ ONE_ARG_DOWNCALL art_quick_alloc_string_object ## c_suffix, artAllocStringObject ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_BYTES(c_suffix, cxx_suffix) \
- FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes ## c_suffix, artAllocStringFromBytesFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ FOUR_ARG_DOWNCALL art_quick_alloc_string_from_bytes ## c_suffix, artAllocStringFromBytesFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_CHARS(c_suffix, cxx_suffix) \
- THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars ## c_suffix, artAllocStringFromCharsFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ THREE_ARG_DOWNCALL art_quick_alloc_string_from_chars ## c_suffix, artAllocStringFromCharsFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_STRING_FROM_STRING(c_suffix, cxx_suffix) \
- ONE_ARG_DOWNCALL art_quick_alloc_string_from_string ## c_suffix, artAllocStringFromStringFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ ONE_ARG_DOWNCALL art_quick_alloc_string_from_string ## c_suffix, artAllocStringFromStringFromCode ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED(c_suffix, cxx_suffix) \
- TWO_ARG_DOWNCALL art_quick_alloc_array_resolved ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ TWO_ARG_DOWNCALL art_quick_alloc_array_resolved ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED8(c_suffix, cxx_suffix) \
- TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ TWO_ARG_DOWNCALL art_quick_alloc_array_resolved8 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED16(c_suffix, cxx_suffix) \
- TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ TWO_ARG_DOWNCALL art_quick_alloc_array_resolved16 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED32(c_suffix, cxx_suffix) \
- TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ TWO_ARG_DOWNCALL art_quick_alloc_array_resolved32 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
#define GENERATE_ALLOC_ENTRYPOINTS_ALLOC_ARRAY_RESOLVED64(c_suffix, cxx_suffix) \
- TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER
+ TWO_ARG_DOWNCALL art_quick_alloc_array_resolved64 ## c_suffix, artAllocArrayFromCodeResolved ## cxx_suffix, RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER
.macro GENERATE_ALLOC_ENTRYPOINTS_FOR_REGION_TLAB_ALLOCATOR
// This is to be separately defined for each architecture to allow a hand-written assembly fast path.
diff --git a/runtime/arch/x86/asm_support_x86.h b/runtime/arch/x86/asm_support_x86.h
index 737d736..f688933 100644
--- a/runtime/arch/x86/asm_support_x86.h
+++ b/runtime/arch/x86/asm_support_x86.h
@@ -25,5 +25,7 @@
#define FRAME_SIZE_SAVE_EVERYTHING (48 + 64)
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_EAX_OFFSET \
+ (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
#endif // ART_RUNTIME_ARCH_X86_ASM_SUPPORT_X86_H_
diff --git a/runtime/arch/x86/jni_entrypoints_x86.S b/runtime/arch/x86/jni_entrypoints_x86.S
index d827509..c7cf856 100644
--- a/runtime/arch/x86/jni_entrypoints_x86.S
+++ b/runtime/arch/x86/jni_entrypoints_x86.S
@@ -98,7 +98,7 @@
// for @FastNative or @CriticalNative.
movl (%esp), %eax // Thread* self
movl THREAD_TOP_QUICK_FRAME_OFFSET(%eax), %eax // uintptr_t tagged_quick_frame
- andl LITERAL(0xfffffffe), %eax // ArtMethod** sp
+ andl LITERAL(TAGGED_JNI_SP_MASK_TOGGLED32), %eax // ArtMethod** sp
movl (%eax), %eax // ArtMethod* method
testl LITERAL(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE), \
ART_METHOD_ACCESS_FLAGS_OFFSET(%eax)
@@ -286,6 +286,12 @@
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, fs:THREAD_SELF_OFFSET
/*
+ * Trampoline to `artJniMethodEntryHook` that preserves all managed arguments.
+ */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
+ art_jni_method_entry_hook, artJniMethodEntryHook, fs:THREAD_SELF_OFFSET
+
+ /*
* Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
*/
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S
index 7f1311c..bc61be5 100644
--- a/runtime/arch/x86/quick_entrypoints_x86.S
+++ b/runtime/arch/x86/quick_entrypoints_x86.S
@@ -794,12 +794,9 @@
call CALLVAR(cxx_name) // cxx_name(arg1, Thread*)
addl MACRO_LITERAL(16), %esp // pop arguments
CFI_ADJUST_CFA_OFFSET(-16)
- testl %eax, %eax // If result is null, deliver the OOME.
+ testl %eax, %eax // If result is null deliver pending exception
jz 1f
- CFI_REMEMBER_STATE
- RESTORE_SAVE_EVERYTHING_FRAME_KEEP_EAX // restore frame up to return address
- ret // return
- CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+ DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_EAX ebx, /* is_ref= */1 // Check for deopt
1:
DELIVER_PENDING_EXCEPTION_FRAME_READY
END_FUNCTION VAR(c_name)
@@ -809,18 +806,72 @@
ONE_ARG_SAVE_EVERYTHING_DOWNCALL \c_name, \cxx_name, RUNTIME_SAVE_EVERYTHING_FOR_CLINIT_METHOD_OFFSET
END_MACRO
-MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER)
- testl %eax, %eax // eax == 0 ?
- jz 1f // if eax == 0 goto 1
- ret // return
-1: // deliver exception on current thread
+MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER)
+ testl %eax, %eax // eax == 0 ?
+ jz 1f // if eax == 0 goto 1
+ DEOPT_OR_RETURN ebx, /*is_ref=*/1 // check if deopt is required
+1: // deliver exception on current thread
DELIVER_PENDING_EXCEPTION
END_MACRO
+MACRO0(RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION)
+ cmpl MACRO_LITERAL(0),%fs:THREAD_EXCEPTION_OFFSET // exception field == 0 ?
+ jne 1f // if exception field != 0 goto 1
+ DEOPT_OR_RETURN ebx // check if deopt is required
+1: // deliver exception on current thread
+ DELIVER_PENDING_EXCEPTION
+END_MACRO
+
+MACRO2(DEOPT_OR_RETURN, temp, is_ref = 0)
+ cmpl LITERAL(0), %fs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+ jne 2f
+ ret
+2:
+ SETUP_SAVE_EVERYTHING_FRAME \temp
+ subl MACRO_LITERAL(4), %esp // alignment padding
+ CFI_ADJUST_CFA_OFFSET(4)
+ pushl MACRO_LITERAL(\is_ref) // is_ref
+ CFI_ADJUST_CFA_OFFSET(4)
+ PUSH_ARG eax // result
+ pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current
+ CFI_ADJUST_CFA_OFFSET(4)
+ call SYMBOL(artDeoptimizeIfNeeded)
+ addl LITERAL(16), %esp // pop arguments
+ CFI_REMEMBER_STATE
+ RESTORE_SAVE_EVERYTHING_FRAME
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+MACRO2(DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_EAX, temp, is_ref = 0)
+ cmpl LITERAL(0), %fs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+ jne 2f
+ CFI_REMEMBER_STATE
+ RESTORE_SAVE_EVERYTHING_FRAME_KEEP_EAX
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+ movl %eax, SAVE_EVERYTHING_FRAME_EAX_OFFSET(%esp) // update eax in the frame
+ subl MACRO_LITERAL(4), %esp // alignment padding
+ CFI_ADJUST_CFA_OFFSET(4)
+ pushl MACRO_LITERAL(\is_ref) // is_ref
+ CFI_ADJUST_CFA_OFFSET(4)
+ PUSH_ARG eax // result
+ pushl %fs:THREAD_SELF_OFFSET // Pass Thread::Current
+ CFI_ADJUST_CFA_OFFSET(4)
+ call SYMBOL(artDeoptimizeIfNeeded)
+ addl LITERAL(16), %esp // pop arguments
+ CFI_REMEMBER_STATE
+ RESTORE_SAVE_EVERYTHING_FRAME
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+
MACRO0(RETURN_IF_EAX_ZERO)
testl %eax, %eax // eax == 0 ?
jnz 1f // if eax != 0 goto 1
- ret // return
+ DEOPT_OR_RETURN ebx // check if deopt is needed
1: // deliver exception on current thread
DELIVER_PENDING_EXCEPTION
END_MACRO
@@ -927,7 +978,7 @@
addl LITERAL(16), %esp // pop arguments
CFI_ADJUST_CFA_OFFSET(-16)
RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deliver exception
END_FUNCTION VAR(c_name)
END_MACRO
@@ -974,7 +1025,7 @@
addl LITERAL(16), %esp
CFI_ADJUST_CFA_OFFSET(-16)
RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deliver exception
END_MACRO
MACRO2(ART_QUICK_ALLOC_OBJECT_TLAB, c_name, cxx_name)
@@ -1107,7 +1158,7 @@
addl LITERAL(16), %esp // pop arguments
CFI_ADJUST_CFA_OFFSET(-16)
RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deliver exception
END_FUNCTION VAR(c_entrypoint)
END_MACRO
@@ -1254,126 +1305,102 @@
.endif
END_MACRO
- /*
- * Macro to insert read barrier, only used in art_quick_aput_obj.
- * obj_reg and dest_reg are registers, offset is a defined literal such as
- * MIRROR_OBJECT_CLASS_OFFSET.
- * pop_eax is a boolean flag, indicating if eax is popped after the call.
- * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
- */
-MACRO4(READ_BARRIER, obj_reg, offset, dest_reg, pop_eax)
-#ifdef USE_READ_BARRIER
- PUSH eax // save registers used in art_quick_aput_obj
- PUSH ebx
- PUSH edx
- PUSH ecx
- // Outgoing argument set up
- pushl MACRO_LITERAL((RAW_VAR(offset))) // pass offset, double parentheses are necessary
- CFI_ADJUST_CFA_OFFSET(4)
- PUSH RAW_VAR(obj_reg) // pass obj_reg
- PUSH eax // pass ref, just pass eax for now since parameter ref is unused
- call SYMBOL(artReadBarrierSlow) // artReadBarrierSlow(ref, obj_reg, offset)
- // No need to unpoison return value in eax, artReadBarrierSlow() would do the unpoisoning.
- .ifnc RAW_VAR(dest_reg), eax
- movl %eax, REG_VAR(dest_reg) // save loaded ref in dest_reg
- .endif
- addl MACRO_LITERAL(12), %esp // pop arguments
- CFI_ADJUST_CFA_OFFSET(-12)
- POP_REG_NE ecx, RAW_VAR(dest_reg) // Restore args except dest_reg
- POP_REG_NE edx, RAW_VAR(dest_reg)
- POP_REG_NE ebx, RAW_VAR(dest_reg)
- .ifc RAW_VAR(pop_eax), true
- POP_REG_NE eax, RAW_VAR(dest_reg)
- .endif
-#else
- movl RAW_VAR(offset)(REG_VAR(obj_reg)), REG_VAR(dest_reg)
- UNPOISON_HEAP_REF RAW_VAR(dest_reg)
-#endif // USE_READ_BARRIER
-END_MACRO
-
DEFINE_FUNCTION art_quick_aput_obj
test %edx, %edx // store of null
- jz .Ldo_aput_null
- READ_BARRIER eax, MIRROR_OBJECT_CLASS_OFFSET, ebx, true
- READ_BARRIER ebx, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, ebx, true
- // value's type == array's component type - trivial assignability
-#if defined(USE_READ_BARRIER)
- READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, false
- cmpl %eax, %ebx
- POP eax // restore eax from the push in the beginning of READ_BARRIER macro
- // This asymmetric push/pop saves a push of eax and maintains stack alignment.
-#elif defined(USE_HEAP_POISONING)
- PUSH eax // save eax
- movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
- UNPOISON_HEAP_REF eax
- cmpl %eax, %ebx
- POP eax // restore eax
-#else
- cmpl MIRROR_OBJECT_CLASS_OFFSET(%edx), %ebx
-#endif
- jne .Lcheck_assignability
-.Ldo_aput:
+ jz .Laput_obj_null
+ movl MIRROR_OBJECT_CLASS_OFFSET(%eax), %ebx
+ UNPOISON_HEAP_REF ebx
+#ifdef USE_READ_BARRIER
+ cmpl LITERAL(0), %fs:THREAD_IS_GC_MARKING_OFFSET
+ jnz .Laput_obj_gc_marking
+#endif // USE_READ_BARRIER
+ movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%ebx), %ebx
+ cmpl MIRROR_OBJECT_CLASS_OFFSET(%edx), %ebx // Both poisoned if heap poisoning is enabled.
+ jne .Laput_obj_check_assignability
+.Laput_obj_store:
POISON_HEAP_REF edx
movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4)
movl %fs:THREAD_CARD_TABLE_OFFSET, %edx
shrl LITERAL(CARD_TABLE_CARD_SHIFT), %eax
movb %dl, (%edx, %eax)
ret
-.Ldo_aput_null:
+
+.Laput_obj_null:
movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4)
ret
-.Lcheck_assignability:
- PUSH eax // save arguments
- PUSH ecx
- PUSH edx
-#if defined(USE_READ_BARRIER)
- subl LITERAL(4), %esp // alignment padding
- CFI_ADJUST_CFA_OFFSET(4)
- READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, true
- subl LITERAL(4), %esp // alignment padding
- CFI_ADJUST_CFA_OFFSET(4)
- PUSH eax // pass arg2 - type of the value to be stored
-#elif defined(USE_HEAP_POISONING)
- subl LITERAL(8), %esp // alignment padding
- CFI_ADJUST_CFA_OFFSET(8)
+
+.Laput_obj_check_assignability:
+ UNPOISON_HEAP_REF ebx // Unpoison array component type if poisoning is enabled.
+ PUSH_ARG eax // Save `art_quick_aput_obj()` arguments.
+ PUSH_ARG ecx
+ PUSH_ARG edx
+ INCREASE_FRAME 8 // Alignment padding.
+ // Pass arg2 - type of the value to be stored.
+#if defined(USE_HEAP_POISONING)
movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
UNPOISON_HEAP_REF eax
- PUSH eax // pass arg2 - type of the value to be stored
+ PUSH_ARG eax
#else
- subl LITERAL(8), %esp // alignment padding
- CFI_ADJUST_CFA_OFFSET(8)
- pushl MIRROR_OBJECT_CLASS_OFFSET(%edx) // pass arg2 - type of the value to be stored
+ pushl MIRROR_OBJECT_CLASS_OFFSET(%edx)
CFI_ADJUST_CFA_OFFSET(4)
#endif
- PUSH ebx // pass arg1 - component type of the array
+.Laput_obj_check_assignability_call:
+ PUSH_ARG ebx // Pass arg1 - component type of the array.
call SYMBOL(artIsAssignableFromCode) // (Class* a, Class* b)
- addl LITERAL(16), %esp // pop arguments
- CFI_ADJUST_CFA_OFFSET(-16)
+ DECREASE_FRAME 16 // Pop `artIsAssignableFromCode()` arguments
testl %eax, %eax
+ POP_ARG edx // Pop `art_quick_aput_obj()` arguments; flags unaffected.
+ POP_ARG ecx
+ POP_ARG eax
jz .Lthrow_array_store_exception
- POP edx
- POP ecx
- POP eax
POISON_HEAP_REF edx
- movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4) // do the aput
+ movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%eax, %ecx, 4) // Do the aput.
movl %fs:THREAD_CARD_TABLE_OFFSET, %edx
shrl LITERAL(CARD_TABLE_CARD_SHIFT), %eax
movb %dl, (%edx, %eax)
ret
- CFI_ADJUST_CFA_OFFSET(12) // 3 POP after the jz for unwinding.
+
.Lthrow_array_store_exception:
- POP edx
- POP ecx
- POP eax
- SETUP_SAVE_ALL_CALLEE_SAVES_FRAME ebx // save all registers as basis for long jump context
- // Outgoing argument set up
- PUSH eax // alignment padding
- pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current()
- CFI_ADJUST_CFA_OFFSET(4)
- PUSH edx // pass arg2 - value
- PUSH eax // pass arg1 - array
+#ifdef USE_READ_BARRIER
+ CFI_REMEMBER_STATE
+#endif // USE_READ_BARRIER
+ SETUP_SAVE_ALL_CALLEE_SAVES_FRAME ebx // Save all registers as basis for long jump context.
+ // Outgoing argument set up.
+ PUSH_ARG eax // Alignment padding.
+ PUSH_ARG fs:THREAD_SELF_OFFSET // Pass Thread::Current()
+ PUSH_ARG edx // Pass arg2 - value.
+ PUSH_ARG eax // Pass arg1 - array.
call SYMBOL(artThrowArrayStoreException) // (array, value, Thread*)
UNREACHABLE
+
+#ifdef USE_READ_BARRIER
+ CFI_RESTORE_STATE_AND_DEF_CFA esp, 4
+.Laput_obj_gc_marking:
+ PUSH_ARG eax // Save `art_quick_aput_obj()` arguments.
+ PUSH_ARG ecx // We need to align stack for `art_quick_read_barrier_mark_regNN`
+ PUSH_ARG edx // and use a register (EAX) as a temporary for the object class.
+ call SYMBOL(art_quick_read_barrier_mark_reg03) // Mark EBX.
+ movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%ebx), %ebx
+ UNPOISON_HEAP_REF ebx
+ call SYMBOL(art_quick_read_barrier_mark_reg03) // Mark EBX.
+ movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %eax
+ UNPOISON_HEAP_REF eax
+ call SYMBOL(art_quick_read_barrier_mark_reg00) // Mark EAX.
+ cmpl %eax, %ebx
+ jne .Laput_obj_check_assignability_gc_marking
+ POP_ARG edx // Restore `art_quick_aput_obj()` arguments.
+ POP_ARG ecx
+ POP_ARG eax
+ jmp .Laput_obj_store
+
+.Laput_obj_check_assignability_gc_marking:
+ // Prepare arguments in line with `.Laput_obj_check_assignability_call` and jump there.
+ // (EAX, ECX and EDX were already saved in the right stack slots.)
+ INCREASE_FRAME 8 // Alignment padding.
+ PUSH_ARG eax // Pass arg2 - type of the value to be stored.
+ // The arg1 shall be pushed at `.Laput_obj_check_assignability_call`.
+ jmp .Laput_obj_check_assignability_call
+#endif // USE_READ_BARRIER
END_FUNCTION art_quick_aput_obj
DEFINE_FUNCTION art_quick_memcpy
@@ -1501,21 +1528,21 @@
// Note: Functions `art{Get,Set}<Kind>{Static,Instance}FromCompiledCode` are
// defined with a macro in runtime/entrypoints/quick/quick_field_entrypoints.cc.
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_EAX_ZERO
TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_EAX_ZERO
@@ -1616,7 +1643,7 @@
movl %eax, %edi // remember code pointer in EDI
addl LITERAL(16), %esp // pop arguments
CFI_ADJUST_CFA_OFFSET(-16)
- test %eax, %eax // if code pointer is null goto deliver pending exception
+ test %eax, %eax // if code pointer is null goto deliver the OOME.
jz 1f
RESTORE_SAVE_REFS_AND_ARGS_FRAME_AND_JUMP
1:
@@ -1686,6 +1713,16 @@
CFI_REMEMBER_STATE
CFI_DEF_CFA_REGISTER(esp)
+ // Quick expects the return value to be in xmm0.
+ movd %eax, %xmm0
+ movd %edx, %xmm1
+ punpckldq %xmm1, %xmm0
+
+ LOAD_RUNTIME_INSTANCE ebx
+ cmpb MACRO_LITERAL(0), INSTRUMENTATION_STUBS_INSTALLED_OFFSET_FROM_RUNTIME_INSTANCE(%ebx)
+ jne .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
+
// Tear down the callee-save frame.
// Remove space for FPR args and EAX
addl LITERAL(4 + 4 * 8), %esp
@@ -1698,12 +1735,12 @@
POP ebp // Restore callee saves
POP esi
POP edi
- // Quick expects the return value to be in xmm0.
- movd %eax, %xmm0
- movd %edx, %xmm1
- punpckldq %xmm1, %xmm0
ret
+.Lcall_method_exit_hook:
+ call art_quick_method_exit_hook
+ jmp .Lcall_method_exit_hook_done
+
// Undo the unwinding information from above since it doesn't apply below.
CFI_RESTORE_STATE_AND_DEF_CFA ebp, 64
.Lexception_in_native:
@@ -1974,34 +2011,29 @@
SETUP_SAVE_REFS_ONLY_FRAME ebx // save ref containing registers for GC
// Outgoing argument set up
leal FRAME_SIZE_SAVE_REFS_ONLY + __SIZEOF_POINTER__(%esp), %edi // prepare args
- push %eax // push padding
+ push %eax // push padding
CFI_ADJUST_CFA_OFFSET(4)
- pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current()
+ pushl %fs:THREAD_SELF_OFFSET // pass Thread::Current()
CFI_ADJUST_CFA_OFFSET(4)
- push %edi // pass args
+ push %edi // pass args
CFI_ADJUST_CFA_OFFSET(4)
- push %eax // pass format
+ push %eax // pass format
CFI_ADJUST_CFA_OFFSET(4)
- call SYMBOL(artStringBuilderAppend) // (uint32_t, const unit32_t*, Thread*)
- addl MACRO_LITERAL(16), %esp // pop arguments
+ call SYMBOL(artStringBuilderAppend) // (uint32_t, const unit32_t*, Thread*)
+ addl MACRO_LITERAL(16), %esp // pop arguments
CFI_ADJUST_CFA_OFFSET(-16)
- RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deliver exception
END_FUNCTION art_quick_string_builder_append
// Create a function `name` calling the ReadBarrier::Mark routine,
// getting its argument and returning its result through register
// `reg`, saving and restoring all caller-save registers.
//
-// If `reg` is different from `eax`, the generated function follows a
-// non-standard runtime calling convention:
-// - register `reg` is used to pass the (sole) argument of this function
-// (instead of EAX);
-// - register `reg` is used to return the result of this function
-// (instead of EAX);
-// - EAX is treated like a normal (non-argument) caller-save register;
-// - everything else is the same as in the standard runtime calling
-// convention (e.g. standard callee-save registers are preserved).
+// The generated function follows a non-standard runtime calling convention:
+// - register `reg` (which may differ from EAX) is used to pass the (sole) argument,
+// - register `reg` (which may differ from EAX) is used to return the result,
+// - all other registers are callee-save (the values they hold are preserved).
MACRO2(READ_BARRIER_MARK_REG, name, reg)
DEFINE_FUNCTION VAR(name)
// Null check so that we can load the lock word.
@@ -2354,16 +2386,9 @@
addl LITERAL(32), %esp // Pop arguments and grp_result.
CFI_ADJUST_CFA_OFFSET(-32)
- cmpl LITERAL(1), %eax // Check if we returned error.
- CFI_REMEMBER_STATE
- je .Ldo_deliver_instrumentation_exception_exit
-
// Normal return.
RESTORE_SAVE_EVERYTHING_FRAME
ret
-.Ldo_deliver_instrumentation_exception_exit:
- CFI_RESTORE_STATE_AND_DEF_CFA esp, FRAME_SIZE_SAVE_EVERYTHING
- DELIVER_PENDING_EXCEPTION_FRAME_READY
END_FUNCTION art_quick_method_exit_hook
diff --git a/runtime/arch/x86_64/asm_support_x86_64.h b/runtime/arch/x86_64/asm_support_x86_64.h
index 51befbe..e389c78 100644
--- a/runtime/arch/x86_64/asm_support_x86_64.h
+++ b/runtime/arch/x86_64/asm_support_x86_64.h
@@ -25,5 +25,7 @@
#define FRAME_SIZE_SAVE_EVERYTHING (144 + 16*8)
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_CLINIT FRAME_SIZE_SAVE_EVERYTHING
#define FRAME_SIZE_SAVE_EVERYTHING_FOR_SUSPEND_CHECK FRAME_SIZE_SAVE_EVERYTHING
+#define SAVE_EVERYTHING_FRAME_RAX_OFFSET \
+ (FRAME_SIZE_SAVE_EVERYTHING - CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS * POINTER_SIZE)
#endif // ART_RUNTIME_ARCH_X86_64_ASM_SUPPORT_X86_64_H_
diff --git a/runtime/arch/x86_64/jni_entrypoints_x86_64.S b/runtime/arch/x86_64/jni_entrypoints_x86_64.S
index 0d5fa3f..55f01b7 100644
--- a/runtime/arch/x86_64/jni_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/jni_entrypoints_x86_64.S
@@ -118,7 +118,7 @@
// Call artFindNativeMethod() for normal native and artFindNativeMethodRunnable()
// for @FastNative or @CriticalNative.
movq THREAD_TOP_QUICK_FRAME_OFFSET(%rdi), %rax // uintptr_t tagged_quick_frame
- andq LITERAL(0xfffffffffffffffe), %rax // ArtMethod** sp
+ andq LITERAL(TAGGED_JNI_SP_MASK_TOGGLED64), %rax // ArtMethod** sp
movq (%rax), %rax // ArtMethod* method
testl LITERAL(ACCESS_FLAGS_METHOD_IS_FAST_NATIVE | ACCESS_FLAGS_METHOD_IS_CRITICAL_NATIVE), \
ART_METHOD_ACCESS_FLAGS_OFFSET(%rax)
@@ -400,6 +400,12 @@
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE art_jni_method_start, artJniMethodStart, gs:THREAD_SELF_OFFSET
/*
+ * Trampoline to `artJniMethodEntryHook` that preserves all managed arguments.
+ */
+JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
+ art_jni_method_entry_hook, artJniMethodEntryHook, gs:THREAD_SELF_OFFSET
+
+ /*
* Trampoline to `artJniMonitoredMethodStart()` that preserves all managed arguments.
*/
JNI_SAVE_MANAGED_ARGS_TRAMPOLINE \
diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
index 673696c..0d79d00 100644
--- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S
+++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S
@@ -731,12 +731,9 @@
movl %eax, %edi // pass the index of the constant as arg0
movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current()
call CALLVAR(cxx_name) // cxx_name(arg0, Thread*)
- testl %eax, %eax // If result is null, deliver the OOME.
+ testl %eax, %eax // If result is null, deliver pending exception.
jz 1f
- CFI_REMEMBER_STATE
- RESTORE_SAVE_EVERYTHING_FRAME_KEEP_RAX // restore frame up to return address
- ret
- CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+ DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_RAX /*is_ref=*/1
1:
DELIVER_PENDING_EXCEPTION_FRAME_READY
END_FUNCTION VAR(c_name)
@@ -746,18 +743,65 @@
ONE_ARG_SAVE_EVERYTHING_DOWNCALL \c_name, \cxx_name, RUNTIME_SAVE_EVERYTHING_FOR_CLINIT_METHOD_OFFSET
END_MACRO
-MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER)
+MACRO0(RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER)
testq %rax, %rax // rax == 0 ?
jz 1f // if rax == 0 goto 1
- ret // return
+ DEOPT_OR_RETURN /*is_ref=*/1 // Check if deopt is required
1: // deliver exception on current thread
DELIVER_PENDING_EXCEPTION
END_MACRO
+
+MACRO0(RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION)
+ movq %gs:THREAD_EXCEPTION_OFFSET, %rcx // get exception field
+ testq %rcx, %rcx // rcx == 0 ?
+ jnz 1f // if rcx != 0 goto 1
+ DEOPT_OR_RETURN // Check if deopt is required
+1: // deliver exception on current thread
+ DELIVER_PENDING_EXCEPTION
+END_MACRO
+
+MACRO1(DEOPT_OR_RETURN, is_ref = 0)
+ cmpl LITERAL(0), %gs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+ jne 2f
+ ret
+2:
+ SETUP_SAVE_EVERYTHING_FRAME
+ movq LITERAL(\is_ref), %rdx // pass if result is a reference
+ movq %rax, %rsi // pass the result
+ movq %gs:THREAD_SELF_OFFSET, %rdi // pass Thread::Current
+ call SYMBOL(artDeoptimizeIfNeeded)
+ CFI_REMEMBER_STATE
+ RESTORE_SAVE_EVERYTHING_FRAME
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+MACRO1(DEOPT_OR_RESTORE_SAVE_EVERYTHING_FRAME_AND_RETURN_RAX, is_ref = 0)
+ cmpl LITERAL(0), %gs:THREAD_DEOPT_CHECK_REQUIRED_OFFSET
+ jne 2f
+ CFI_REMEMBER_STATE
+ RESTORE_SAVE_EVERYTHING_FRAME_KEEP_RAX
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+2:
+ movq %rax, SAVE_EVERYTHING_FRAME_RAX_OFFSET(%rsp) // update result in the frame
+ movq LITERAL(\is_ref), %rdx // pass if result is a reference
+ movq %rax, %rsi // pass the result
+ movq %gs:THREAD_SELF_OFFSET, %rdi // pass Thread::Current
+ call SYMBOL(artDeoptimizeIfNeeded)
+ CFI_REMEMBER_STATE
+ RESTORE_SAVE_EVERYTHING_FRAME
+ ret
+ CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
+END_MACRO
+
+
+
MACRO0(RETURN_IF_EAX_ZERO)
testl %eax, %eax // eax == 0 ?
jnz 1f // if eax != 0 goto 1
- ret // return
+ DEOPT_OR_RETURN // Check if we need a deopt
1: // deliver exception on current thread
DELIVER_PENDING_EXCEPTION
END_MACRO
@@ -859,7 +903,7 @@
movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current()
call CALLVAR(cxx_name) // cxx_name(arg0, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deliver exception
END_FUNCTION VAR(c_name)
END_MACRO
@@ -931,7 +975,7 @@
movq %gs:THREAD_SELF_OFFSET, %rsi // pass Thread::Current()
call CALLVAR(cxx_name) // cxx_name(arg0, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deliver exception
END_MACRO
// A hand-written override for GENERATE_ALLOC_ENTRYPOINTS_ALLOC_OBJECT_RESOLVED(_tlab, TLAB). May be
@@ -1019,7 +1063,7 @@
movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current()
call CALLVAR(cxx_name) // cxx_name(arg0, arg1, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deliver exception
END_FUNCTION VAR(c_entrypoint)
END_MACRO
@@ -1163,134 +1207,89 @@
.endif
END_MACRO
- /*
- * Macro to insert read barrier, used in art_quick_aput_obj.
- * obj_reg and dest_reg{32|64} are registers, offset is a defined literal such as
- * MIRROR_OBJECT_CLASS_OFFSET. dest_reg needs two versions to handle the mismatch between
- * 64b PUSH/POP and 32b argument.
- * TODO: When read barrier has a fast path, add heap unpoisoning support for the fast path.
- *
- * As with art_quick_aput_obj function, the 64b versions are in comments.
- */
-MACRO4(READ_BARRIER, obj_reg, offset, dest_reg32, dest_reg64)
-#ifdef USE_READ_BARRIER
- PUSH rax // save registers that might be used
- PUSH rdi
- PUSH rsi
- PUSH rdx
- PUSH rcx
- SETUP_FP_CALLEE_SAVE_FRAME
- // Outgoing argument set up
- // movl REG_VAR(ref_reg32), %edi // pass ref, no-op for now since parameter ref is unused
- // // movq REG_VAR(ref_reg64), %rdi
- movl REG_VAR(obj_reg), %esi // pass obj_reg
- // movq REG_VAR(obj_reg), %rsi
- movl MACRO_LITERAL((RAW_VAR(offset))), %edx // pass offset, double parentheses are necessary
- // movq MACRO_LITERAL((RAW_VAR(offset))), %rdx
- call SYMBOL(artReadBarrierSlow) // artReadBarrierSlow(ref, obj_reg, offset)
- // No need to unpoison return value in rax, artReadBarrierSlow() would do the unpoisoning.
- .ifnc RAW_VAR(dest_reg32), eax
- // .ifnc RAW_VAR(dest_reg64), rax
- movl %eax, REG_VAR(dest_reg32) // save loaded ref in dest_reg
- // movq %rax, REG_VAR(dest_reg64)
- .endif
- RESTORE_FP_CALLEE_SAVE_FRAME
- POP_REG_NE rcx, RAW_VAR(dest_reg64) // Restore registers except dest_reg
- POP_REG_NE rdx, RAW_VAR(dest_reg64)
- POP_REG_NE rsi, RAW_VAR(dest_reg64)
- POP_REG_NE rdi, RAW_VAR(dest_reg64)
- POP_REG_NE rax, RAW_VAR(dest_reg64)
-#else
- movl RAW_VAR(offset)(REG_VAR(obj_reg)), REG_VAR(dest_reg32)
- // movq RAW_VAR(offset)(REG_VAR(obj_reg)), REG_VAR(dest_reg64)
- UNPOISON_HEAP_REF RAW_VAR(dest_reg32) // UNPOISON_HEAP_REF only takes a 32b register
-#endif // USE_READ_BARRIER
-END_MACRO
-
DEFINE_FUNCTION art_quick_aput_obj
- testl %edx, %edx // store of null
-// test %rdx, %rdx
- jz .Ldo_aput_null
- READ_BARRIER edi, MIRROR_OBJECT_CLASS_OFFSET, ecx, rcx
- // READ_BARRIER rdi, MIRROR_OBJECT_CLASS_OFFSET, ecx, rcx
- READ_BARRIER ecx, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, ecx, rcx
- // READ_BARRIER rcx, MIRROR_CLASS_COMPONENT_TYPE_OFFSET, ecx, rcx
-#if defined(USE_HEAP_POISONING) || defined(USE_READ_BARRIER)
- READ_BARRIER edx, MIRROR_OBJECT_CLASS_OFFSET, eax, rax // rax is free.
- // READ_BARRIER rdx, MIRROR_OBJECT_CLASS_OFFSET, eax, rax
- cmpl %eax, %ecx // value's type == array's component type - trivial assignability
-#else
- cmpl MIRROR_OBJECT_CLASS_OFFSET(%edx), %ecx // value's type == array's component type - trivial assignability
-// cmpq MIRROR_CLASS_OFFSET(%rdx), %rcx
-#endif
- jne .Lcheck_assignability
-.Ldo_aput:
+ test %edx, %edx // store of null
+ jz .Laput_obj_null
+ movl MIRROR_OBJECT_CLASS_OFFSET(%rdi), %ecx
+ UNPOISON_HEAP_REF ecx
+#ifdef USE_READ_BARRIER
+ cmpl LITERAL(0), %gs:THREAD_IS_GC_MARKING_OFFSET
+ jnz .Laput_obj_gc_marking
+#endif // USE_READ_BARRIER
+ movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%rcx), %ecx
+ cmpl MIRROR_OBJECT_CLASS_OFFSET(%rdx), %ecx // Both poisoned if heap poisoning is enabled.
+ jne .Laput_obj_check_assignability
+.Laput_obj_store:
POISON_HEAP_REF edx
- movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%edi, %esi, 4)
-// movq %rdx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
+ movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
movq %gs:THREAD_CARD_TABLE_OFFSET, %rdx
shrl LITERAL(CARD_TABLE_CARD_SHIFT), %edi
-// shrl LITERAL(CARD_TABLE_CARD_SHIFT), %rdi
- movb %dl, (%rdx, %rdi) // Note: this assumes that top 32b of %rdi are zero
+ movb %dl, (%rdx, %rdi)
ret
-.Ldo_aput_null:
- movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%edi, %esi, 4)
-// movq %rdx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
+
+.Laput_obj_null:
+ movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
ret
-.Lcheck_assignability:
- // Save arguments.
- PUSH rdi
- PUSH rsi
- PUSH rdx
+
+.Laput_obj_check_assignability:
+ UNPOISON_HEAP_REF ecx // Unpoison array component type if poisoning is enabled.
+ PUSH_ARG rdi // Save arguments.
+ PUSH_ARG rsi
+ PUSH_ARG rdx
+ movl MIRROR_OBJECT_CLASS_OFFSET(%rdx), %esi // Pass arg2 = value's class.
+ UNPOISON_HEAP_REF esi
+.Laput_obj_check_assignability_call:
+ movl %ecx, %edi // Pass arg1 = array's component type.
SETUP_FP_CALLEE_SAVE_FRAME
-
-#if defined(USE_HEAP_POISONING) || defined(USE_READ_BARRIER)
- // The load of MIRROR_OBJECT_CLASS_OFFSET(%edx) is redundant, eax still holds the value.
- movl %eax, %esi // Pass arg2 = value's class.
- // movq %rax, %rsi
-#else
- // "Uncompress" = do nothing, as already zero-extended on load.
- movl MIRROR_OBJECT_CLASS_OFFSET(%edx), %esi // Pass arg2 = value's class.
-#endif
- movq %rcx, %rdi // Pass arg1 = array's component type.
-
call SYMBOL(artIsAssignableFromCode) // (Class* a, Class* b)
-
- // Exception?
- testq %rax, %rax
- jz .Lthrow_array_store_exception
-
- RESTORE_FP_CALLEE_SAVE_FRAME
- // Restore arguments.
- POP rdx
- POP rsi
- POP rdi
-
+ RESTORE_FP_CALLEE_SAVE_FRAME // Resore FP registers.
+ POP_ARG rdx // Restore arguments.
+ POP_ARG rsi
+ POP_ARG rdi
+ testq %rax, %rax // Check for exception.
+ jz .Laput_obj_throw_array_store_exception
POISON_HEAP_REF edx
- movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%edi, %esi, 4)
-// movq %rdx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
+ movl %edx, MIRROR_OBJECT_ARRAY_DATA_OFFSET(%rdi, %rsi, 4)
movq %gs:THREAD_CARD_TABLE_OFFSET, %rdx
shrl LITERAL(CARD_TABLE_CARD_SHIFT), %edi
-// shrl LITERAL(CARD_TABLE_CARD_SHIFT), %rdi
- movb %dl, (%rdx, %rdi) // Note: this assumes that top 32b of %rdi are zero
-// movb %dl, (%rdx, %rdi)
+ movb %dl, (%rdx, %rdi)
ret
- CFI_ADJUST_CFA_OFFSET(24 + 4 * 8) // Reset unwind info so following code unwinds.
-.Lthrow_array_store_exception:
- RESTORE_FP_CALLEE_SAVE_FRAME
- // Restore arguments.
- POP rdx
- POP rsi
- POP rdi
+.Laput_obj_throw_array_store_exception:
+#ifdef USE_READ_BARRIER
+ CFI_REMEMBER_STATE
+#endif // USE_READ_BARRIER
SETUP_SAVE_ALL_CALLEE_SAVES_FRAME // Save all registers as basis for long jump context.
-
// Outgoing argument set up.
movq %rdx, %rsi // Pass arg 2 = value.
movq %gs:THREAD_SELF_OFFSET, %rdx // Pass arg 3 = Thread::Current().
// Pass arg 1 = array.
call SYMBOL(artThrowArrayStoreException) // (array, value, Thread*)
UNREACHABLE
+
+#ifdef USE_READ_BARRIER
+ CFI_RESTORE_STATE_AND_DEF_CFA esp, 4
+.Laput_obj_gc_marking:
+ // We need to align stack for `art_quick_read_barrier_mark_regNN`.
+ INCREASE_FRAME 8 // Stack alignment.
+ call SYMBOL(art_quick_read_barrier_mark_reg01) // Mark ECX
+ movl MIRROR_CLASS_COMPONENT_TYPE_OFFSET(%rcx), %ecx
+ UNPOISON_HEAP_REF ecx
+ call SYMBOL(art_quick_read_barrier_mark_reg01) // Mark ECX
+ movl MIRROR_OBJECT_CLASS_OFFSET(%rdx), %eax
+ UNPOISON_HEAP_REF eax
+ call SYMBOL(art_quick_read_barrier_mark_reg00) // Mark EAX
+ DECREASE_FRAME 8 // Remove stack alignment.
+ cmpl %eax, %ecx
+ je .Laput_obj_store
+ // Prepare arguments in line with `.Laput_obj_check_assignability_call` and jump there.
+ PUSH_ARG rdi // Save arguments.
+ PUSH_ARG rsi
+ PUSH_ARG rdx
+ movl %eax, %esi // Pass arg2 - type of the value to be stored.
+ // The arg1 shall be moved at `.Ldo_assignability_check_call`.
+ jmp .Laput_obj_check_assignability_call
+#endif // USE_READ_BARRIER
END_FUNCTION art_quick_aput_obj
// TODO: This is quite silly on X86_64 now.
@@ -1324,27 +1323,27 @@
THREE_ARG_REF_DOWNCALL art_quick_set64_instance, artSet64InstanceFromCompiledCode, RETURN_IF_EAX_ZERO
THREE_ARG_REF_DOWNCALL art_quick_set_obj_instance, artSetObjInstanceFromCompiledCode, RETURN_IF_EAX_ZERO
-TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_byte_instance, artGetByteInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_boolean_instance, artGetBooleanInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_short_instance, artGetShortInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_char_instance, artGetCharInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get32_instance, artGet32InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get64_instance, artGet64InstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_get_obj_instance, artGetObjInstanceFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
TWO_ARG_REF_DOWNCALL art_quick_set8_static, artSet8StaticFromCompiledCode, RETURN_IF_EAX_ZERO
TWO_ARG_REF_DOWNCALL art_quick_set16_static, artSet16StaticFromCompiledCode, RETURN_IF_EAX_ZERO
TWO_ARG_REF_DOWNCALL art_quick_set32_static, artSet32StaticFromCompiledCode, RETURN_IF_EAX_ZERO
-TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+TWO_ARG_REF_DOWNCALL art_quick_set64_static, artSet64StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
TWO_ARG_REF_DOWNCALL art_quick_set_obj_static, artSetObjStaticFromCompiledCode, RETURN_IF_EAX_ZERO
-ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
-ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_byte_static, artGetByteStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_boolean_static, artGetBooleanStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_short_static, artGetShortStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_char_static, artGetCharStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get32_static, artGet32StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get64_static, artGet64StaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
+ONE_ARG_REF_DOWNCALL art_quick_get_obj_static, artGetObjStaticFromCompiledCode, RETURN_OR_DEOPT_OR_DELIVER_PENDING_EXCEPTION
DEFINE_FUNCTION art_quick_proxy_invoke_handler
SETUP_SAVE_REFS_AND_ARGS_FRAME_WITH_METHOD_IN_RDI
@@ -1575,6 +1574,14 @@
CFI_REMEMBER_STATE
CFI_DEF_CFA_REGISTER(rsp)
+ // store into fpr, for when it's a fpr return...
+ movq %rax, %xmm0
+
+ LOAD_RUNTIME_INSTANCE rcx
+ cmpb MACRO_LITERAL(0), INSTRUMENTATION_STUBS_INSTALLED_OFFSET_FROM_RUNTIME_INSTANCE(%rcx)
+ jne .Lcall_method_exit_hook
+.Lcall_method_exit_hook_done:
+
// Tear down the callee-save frame.
// Load FPRs.
// movq %xmm0, 16(%rsp) // doesn't make sense!!!
@@ -1604,10 +1611,12 @@
POP r13 // Callee save.
POP r14 // Callee save.
POP r15 // Callee save.
- // store into fpr, for when it's a fpr return...
- movq %rax, %xmm0
ret
+.Lcall_method_exit_hook:
+ call art_quick_method_exit_hook
+ jmp .Lcall_method_exit_hook_done
+
// Undo the unwinding information from above since it doesn't apply below.
CFI_RESTORE_STATE_AND_DEF_CFA rbp, 208
.Lexception_in_native:
@@ -1846,7 +1855,7 @@
movq %gs:THREAD_SELF_OFFSET, %rdx // pass Thread::Current()
call artStringBuilderAppend // (uint32_t, const unit32_t*, Thread*)
RESTORE_SAVE_REFS_ONLY_FRAME // restore frame up to return address
- RETURN_IF_RESULT_IS_NON_ZERO_OR_DELIVER // return or deliver exception
+ RETURN_IF_RESULT_IS_NON_ZERO_OR_DEOPT_OR_DELIVER // return or deopt or deliver exception
END_FUNCTION art_quick_string_builder_append
// Create a function `name` calling the ReadBarrier::Mark routine,
@@ -1855,16 +1864,9 @@
//
// The generated function follows a non-standard runtime calling
// convention:
-// - register `reg` (which may be different from RDI) is used to pass
-// the (sole) argument of this function;
-// - register `reg` (which may be different from RAX) is used to return
-// the result of this function (instead of RAX);
-// - if `reg` is different from `rdi`, RDI is treated like a normal
-// (non-argument) caller-save register;
-// - if `reg` is different from `rax`, RAX is treated like a normal
-// (non-result) caller-save register;
-// - everything else is the same as in the standard runtime calling
-// convention (e.g. standard callee-save registers are preserved).
+// - register `reg` (which may be different from RDI) is used to pass the (sole) argument,
+// - register `reg` (which may be different from RAX) is used to return the result,
+// - all other registers are callee-save (the values they hold are preserved).
MACRO2(READ_BARRIER_MARK_REG, name, reg)
DEFINE_FUNCTION VAR(name)
// Null check so that we can load the lock word.
@@ -2172,7 +2174,6 @@
END_FUNCTION art_quick_method_entry_hook
// On entry, method is at the bottom of the stack.
-// and r8 has should_deopt_frame value.
DEFINE_FUNCTION art_quick_method_exit_hook
SETUP_SAVE_EVERYTHING_FRAME
@@ -2183,14 +2184,7 @@
movq %gs:THREAD_SELF_OFFSET, %rdi // Thread::Current
call SYMBOL(artMethodExitHook) // (Thread*, SP, gpr_res*, fpr_res*)
- cmpq LITERAL(1), %rax
- CFI_REMEMBER_STATE
- je .Ldo_deliver_instrumentation_exception_exit
-
// Normal return.
RESTORE_SAVE_EVERYTHING_FRAME
ret
-.Ldo_deliver_instrumentation_exception_exit:
- CFI_RESTORE_STATE_AND_DEF_CFA rsp, FRAME_SIZE_SAVE_EVERYTHING
- DELIVER_PENDING_EXCEPTION_FRAME_READY
END_FUNCTION art_quick_method_entry_hook
diff --git a/runtime/art_method-inl.h b/runtime/art_method-inl.h
index 844a0ff..b071714 100644
--- a/runtime/art_method-inl.h
+++ b/runtime/art_method-inl.h
@@ -388,17 +388,19 @@
return (GetAccessFlags() & kAccSingleImplementation) != 0;
}
-template<ReadBarrierOption kReadBarrierOption, typename RootVisitorType>
+template<ReadBarrierOption kReadBarrierOption, bool kVisitProxyMethod, typename RootVisitorType>
void ArtMethod::VisitRoots(RootVisitorType& visitor, PointerSize pointer_size) {
if (LIKELY(!declaring_class_.IsNull())) {
visitor.VisitRoot(declaring_class_.AddressWithoutBarrier());
- ObjPtr<mirror::Class> klass = declaring_class_.Read<kReadBarrierOption>();
- if (UNLIKELY(klass->IsProxyClass())) {
- // For normal methods, dex cache shortcuts will be visited through the declaring class.
- // However, for proxies we need to keep the interface method alive, so we visit its roots.
- ArtMethod* interface_method = GetInterfaceMethodForProxyUnchecked(pointer_size);
- DCHECK(interface_method != nullptr);
- interface_method->VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+ if (kVisitProxyMethod) {
+ ObjPtr<mirror::Class> klass = declaring_class_.Read<kReadBarrierOption>();
+ if (UNLIKELY(klass->IsProxyClass())) {
+ // For normal methods, dex cache shortcuts will be visited through the declaring class.
+ // However, for proxies we need to keep the interface method alive, so we visit its roots.
+ ArtMethod* interface_method = GetInterfaceMethodForProxyUnchecked(pointer_size);
+ DCHECK(interface_method != nullptr);
+ interface_method->VisitRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
+ }
}
}
}
diff --git a/runtime/art_method.cc b/runtime/art_method.cc
index f6f8b5f..71f08e7 100644
--- a/runtime/art_method.cc
+++ b/runtime/art_method.cc
@@ -150,10 +150,34 @@
return dex_file->GetIndexForClassDef(*class_def);
}
-void ArtMethod::ThrowInvocationTimeError() {
+void ArtMethod::ThrowInvocationTimeError(ObjPtr<mirror::Object> receiver) {
DCHECK(!IsInvokable());
if (IsDefaultConflicting()) {
ThrowIncompatibleClassChangeErrorForMethodConflict(this);
+ } else if (GetDeclaringClass()->IsInterface() && receiver != nullptr) {
+ // If this was an interface call, check whether there is a method in the
+ // superclass chain that isn't public. In this situation, we should throw an
+ // IllegalAccessError.
+ DCHECK(IsAbstract());
+ ObjPtr<mirror::Class> current = receiver->GetClass();
+ while (current != nullptr) {
+ for (ArtMethod& method : current->GetDeclaredMethodsSlice(kRuntimePointerSize)) {
+ ArtMethod* np_method = method.GetInterfaceMethodIfProxy(kRuntimePointerSize);
+ if (!np_method->IsStatic() &&
+ np_method->GetNameView() == GetNameView() &&
+ np_method->GetSignature() == GetSignature()) {
+ if (!np_method->IsPublic()) {
+ ThrowIllegalAccessErrorForImplementingMethod(receiver->GetClass(), np_method, this);
+ return;
+ } else if (np_method->IsAbstract()) {
+ ThrowAbstractMethodError(this);
+ return;
+ }
+ }
+ }
+ current = current->GetSuperClass();
+ }
+ ThrowAbstractMethodError(this);
} else {
DCHECK(IsAbstract());
ThrowAbstractMethodError(this);
@@ -310,6 +334,7 @@
return found_dex_pc;
}
+NO_STACK_PROTECTOR
void ArtMethod::Invoke(Thread* self, uint32_t* args, uint32_t args_size, JValue* result,
const char* shorty) {
if (UNLIKELY(__builtin_frame_address(0) < self->GetStackEnd())) {
@@ -551,6 +576,12 @@
return nullptr;
}
+ // We should not reach here with a pc of 0. pc can be 0 for downcalls when walking the stack.
+ // For native methods this case is handled by the caller by checking the quick frame tag. See
+ // StackVisitor::WalkStack for more details. For non-native methods pc can be 0 only for runtime
+ // methods or proxy invoke handlers which are handled earlier.
+ DCHECK_NE(pc, 0u) << "PC 0 for " << PrettyMethod();
+
// Check whether the current entry point contains this pc.
if (!class_linker->IsQuickGenericJniStub(existing_entry_point) &&
!class_linker->IsQuickResolutionStub(existing_entry_point) &&
@@ -592,21 +623,17 @@
OatFile::OatMethod oat_method =
FindOatMethodFor(this, class_linker->GetImagePointerSize(), &found);
if (!found) {
- if (IsNative()) {
- // We are running the GenericJNI stub. The entrypoint may point
- // to different entrypoints or to a JIT-compiled JNI stub.
- DCHECK(class_linker->IsQuickGenericJniStub(existing_entry_point) ||
- class_linker->IsQuickResolutionStub(existing_entry_point) ||
- existing_entry_point == GetQuickInstrumentationEntryPoint() ||
- (jit != nullptr && jit->GetCodeCache()->ContainsPc(existing_entry_point)))
- << " entrypoint: " << existing_entry_point
- << " size: " << OatQuickMethodHeader::FromEntryPoint(existing_entry_point)->GetCodeSize()
- << " pc: " << reinterpret_cast<const void*>(pc);
- return nullptr;
- }
- // Only for unit tests.
- // TODO(ngeoffray): Update these tests to pass the right pc?
- return OatQuickMethodHeader::FromEntryPoint(existing_entry_point);
+ CHECK(IsNative());
+ // We are running the GenericJNI stub. The entrypoint may point
+ // to different entrypoints or to a JIT-compiled JNI stub.
+ DCHECK(class_linker->IsQuickGenericJniStub(existing_entry_point) ||
+ class_linker->IsQuickResolutionStub(existing_entry_point) ||
+ existing_entry_point == GetQuickInstrumentationEntryPoint() ||
+ (jit != nullptr && jit->GetCodeCache()->ContainsPc(existing_entry_point)))
+ << " entrypoint: " << existing_entry_point
+ << " size: " << OatQuickMethodHeader::FromEntryPoint(existing_entry_point)->GetCodeSize()
+ << " pc: " << reinterpret_cast<const void*>(pc);
+ return nullptr;
}
const void* oat_entry_point = oat_method.GetQuickCode();
if (oat_entry_point == nullptr || class_linker->IsQuickGenericJniStub(oat_entry_point)) {
@@ -615,10 +642,13 @@
}
OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(oat_entry_point);
- if (pc == 0) {
- // This is a downcall, it can only happen for a native method.
- DCHECK(IsNative());
- return method_header;
+ // We could have existing Oat code for native methods but we may not use it if the runtime is java
+ // debuggable or when profiling boot class path. There is no easy way to check if the pc
+ // corresponds to QuickGenericJniStub. Since we have eliminated all the other cases, if the pc
+ // doesn't correspond to the AOT code then we must be running QuickGenericJniStub.
+ if (IsNative() && !method_header->Contains(pc)) {
+ DCHECK_NE(pc, 0u) << "PC 0 for " << PrettyMethod();
+ return nullptr;
}
DCHECK(method_header->Contains(pc))
@@ -728,16 +758,16 @@
// the entry point to the JIT code, but this would require taking the JIT code cache
// lock to notify it, which we do not want at this level.
Runtime* runtime = Runtime::Current();
+ const void* entry_point = GetEntryPointFromQuickCompiledCodePtrSize(image_pointer_size);
if (runtime->UseJitCompilation()) {
- if (runtime->GetJit()->GetCodeCache()->ContainsPc(GetEntryPointFromQuickCompiledCode())) {
+ if (runtime->GetJit()->GetCodeCache()->ContainsPc(entry_point)) {
SetEntryPointFromQuickCompiledCodePtrSize(
src->IsNative() ? GetQuickGenericJniStub() : GetQuickToInterpreterBridge(),
image_pointer_size);
}
}
- if (interpreter::IsNterpSupported() &&
- (GetEntryPointFromQuickCompiledCodePtrSize(image_pointer_size) ==
- interpreter::GetNterpEntryPoint())) {
+ 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);
diff --git a/runtime/art_method.h b/runtime/art_method.h
index c2de718..a07d696 100644
--- a/runtime/art_method.h
+++ b/runtime/art_method.h
@@ -259,9 +259,7 @@
}
void SetMemorySharedMethod() REQUIRES_SHARED(Locks::mutator_lock_) {
- // Disable until we make sure critical code is AOTed.
- static constexpr bool kEnabledMemorySharedMethod = false;
- if (kEnabledMemorySharedMethod && !IsIntrinsic() && !IsAbstract()) {
+ if (!IsIntrinsic() && !IsAbstract()) {
AddAccessFlags(kAccMemorySharedMethod);
SetHotCounter();
}
@@ -424,8 +422,10 @@
bool CheckIncompatibleClassChange(InvokeType type) REQUIRES_SHARED(Locks::mutator_lock_);
// Throws the error that would result from trying to invoke this method (i.e.
- // IncompatibleClassChangeError or AbstractMethodError). Only call if !IsInvokable();
- void ThrowInvocationTimeError() REQUIRES_SHARED(Locks::mutator_lock_);
+ // IncompatibleClassChangeError, AbstractMethodError, or IllegalAccessError).
+ // Only call if !IsInvokable();
+ void ThrowInvocationTimeError(ObjPtr<mirror::Object> receiver)
+ REQUIRES_SHARED(Locks::mutator_lock_);
uint16_t GetMethodIndex() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -635,7 +635,9 @@
REQUIRES_SHARED(Locks::mutator_lock_);
// NO_THREAD_SAFETY_ANALYSIS since we don't know what the callback requires.
- template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename RootVisitorType>
+ template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+ bool kVisitProxyMethod = true,
+ typename RootVisitorType>
void VisitRoots(RootVisitorType& visitor, PointerSize pointer_size) NO_THREAD_SAFETY_ANALYSIS;
const DexFile* GetDexFile() REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/art_standalone_runtime_tests.xml b/runtime/art_standalone_runtime_tests.xml
index 5200529..8f89d84 100644
--- a/runtime/art_standalone_runtime_tests.xml
+++ b/runtime/art_standalone_runtime_tests.xml
@@ -93,7 +93,7 @@
<option name="exclude-filter" value="HiddenApiTest.DexDomain_SystemSystemExtFrameworkDir" />
<option name="exclude-filter" value="HiddenApiTest.DexDomain_SystemSystemExtFrameworkDir_MultiDex" />
<option name="exclude-filter" value="JniInternalTest.CallVarArgMethodBadPrimitive" />
- <option name="exclude-filter" value="OatFileAssistantTest.SystemFrameworkDir" />
+ <option name="exclude-filter" value="OatFileAssistantBaseTest.SystemFrameworkDir" />
<option name="exclude-filter" value="StubTest.Fields16" />
<option name="exclude-filter" value="StubTest.Fields32" />
<option name="exclude-filter" value="StubTest.Fields64" />
@@ -107,10 +107,10 @@
them fail to dynamically link to the expected (64-bit) libraries.
TODO(b/204649079): Investigate these failures and re-enable these tests. -->
- <option name="exclude-filter" value="ExecUtilsTest.EnvSnapshotDeletionsAreNotVisible" />
- <option name="exclude-filter" value="ExecUtilsTest.ExecNoTimeout" />
- <option name="exclude-filter" value="ExecUtilsTest.ExecSuccess" />
- <option name="exclude-filter" value="ExecUtilsTest.ExecTimeout" />
+ <option name="exclude-filter" value="*ExecUtilsTest.EnvSnapshotDeletionsAreNotVisible*" />
+ <option name="exclude-filter" value="*ExecUtilsTest.ExecNoTimeout*" />
+ <option name="exclude-filter" value="*ExecUtilsTest.ExecSuccess*" />
+ <option name="exclude-filter" value="*ExecUtilsTest.ExecTimeout*" />
</test>
<!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
diff --git a/runtime/backtrace_helper.h b/runtime/backtrace_helper.h
index a74d0e0..9be2550 100644
--- a/runtime/backtrace_helper.h
+++ b/runtime/backtrace_helper.h
@@ -26,7 +26,7 @@
namespace art {
-// Using libbacktrace
+// Using libunwindstack
class BacktraceCollector {
public:
BacktraceCollector(uintptr_t* out_frames, size_t max_depth, size_t skip_count)
diff --git a/runtime/base/locks.h b/runtime/base/locks.h
index 829adff..c15e5de 100644
--- a/runtime/base/locks.h
+++ b/runtime/base/locks.h
@@ -68,12 +68,12 @@
// Can be held while GC related work is done, and thus must be above kMarkSweepMarkStackLock
kThreadWaitLock,
kCHALock,
- kJitCodeCacheLock,
kRosAllocGlobalLock,
kRosAllocBracketLock,
kRosAllocBulkFreeLock,
kAllocSpaceLock,
kTaggingLockLevel,
+ kJitCodeCacheLock,
kTransactionLogLock,
kCustomTlsLock,
kJniFunctionTableLock,
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 5709333..01d7e73 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -59,18 +59,19 @@
};
#if ART_USE_FUTEXES
+// Compute a relative timespec as *result_ts = lhs - rhs.
+// Return false (and produce an invalid *result_ts) if lhs < rhs.
static bool ComputeRelativeTimeSpec(timespec* result_ts, const timespec& lhs, const timespec& rhs) {
const int32_t one_sec = 1000 * 1000 * 1000; // one second in nanoseconds.
+ static_assert(std::is_signed<decltype(result_ts->tv_sec)>::value); // Signed on Linux.
result_ts->tv_sec = lhs.tv_sec - rhs.tv_sec;
result_ts->tv_nsec = lhs.tv_nsec - rhs.tv_nsec;
if (result_ts->tv_nsec < 0) {
result_ts->tv_sec--;
result_ts->tv_nsec += one_sec;
- } else if (result_ts->tv_nsec > one_sec) {
- result_ts->tv_sec++;
- result_ts->tv_nsec -= one_sec;
}
- return result_ts->tv_sec < 0;
+ DCHECK(result_ts->tv_nsec >= 0 && result_ts->tv_nsec < one_sec);
+ return result_ts->tv_sec >= 0;
}
#endif
@@ -852,7 +853,7 @@
timespec now_abs_ts;
InitTimeSpec(true, CLOCK_MONOTONIC, 0, 0, &now_abs_ts);
timespec rel_ts;
- if (ComputeRelativeTimeSpec(&rel_ts, end_abs_ts, now_abs_ts)) {
+ if (!ComputeRelativeTimeSpec(&rel_ts, end_abs_ts, now_abs_ts)) {
return false; // Timed out.
}
ScopedContentionRecorder scr(this, SafeGetTid(self), GetExclusiveOwnerTid());
@@ -869,6 +870,7 @@
// EAGAIN and EINTR both indicate a spurious failure,
// recompute the relative time out from now and try again.
// We don't use TEMP_FAILURE_RETRY so we can recompute rel_ts;
+ num_contenders_.fetch_sub(1); // Unlikely to matter.
PLOG(FATAL) << "timed futex wait failed for " << name_;
}
}
diff --git a/runtime/check_reference_map_visitor.h b/runtime/check_reference_map_visitor.h
index a7c3e45..d03139b 100644
--- a/runtime/check_reference_map_visitor.h
+++ b/runtime/check_reference_map_visitor.h
@@ -91,7 +91,7 @@
CodeItemDataAccessor accessor(m->DexInstructionData());
uint16_t number_of_dex_registers = accessor.RegistersSize();
- if (!Runtime::Current()->IsAsyncDeoptimizeable(GetCurrentQuickFramePc())) {
+ if (!Runtime::Current()->IsAsyncDeoptimizeable(GetOuterMethod(), GetCurrentQuickFramePc())) {
// We can only guarantee dex register info presence for debuggable methods.
return;
}
diff --git a/runtime/class_linker-inl.h b/runtime/class_linker-inl.h
index 02b2778..b79f3f5 100644
--- a/runtime/class_linker-inl.h
+++ b/runtime/class_linker-inl.h
@@ -24,6 +24,7 @@
#include "art_method-inl.h"
#include "base/mutex.h"
#include "class_linker.h"
+#include "class_table-inl.h"
#include "dex/dex_file.h"
#include "dex/dex_file_structs.h"
#include "gc_root-inl.h"
@@ -592,6 +593,11 @@
return resolved;
}
+template <typename Visitor>
+inline void ClassLinker::VisitBootClasses(Visitor* visitor) {
+ boot_class_table_->Visit(*visitor);
+}
+
template <class Visitor>
inline void ClassLinker::VisitClassTables(const Visitor& visitor) {
Thread* const self = Thread::Current();
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index c8dbc75..dc67dca 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -37,6 +37,7 @@
#include "art_method-inl.h"
#include "barrier.h"
#include "base/arena_allocator.h"
+#include "base/arena_bit_vector.h"
#include "base/casts.h"
#include "base/file_utils.h"
#include "base/hash_map.h"
@@ -2115,7 +2116,7 @@
const bool tracing_enabled = Trace::IsTracingEnabled();
Thread* const self = Thread::Current();
WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
// We do not track new roots for CC.
DCHECK_EQ(0, flags & (kVisitRootFlagNewRoots |
kVisitRootFlagClearRootLog |
@@ -2151,7 +2152,7 @@
root.VisitRoot(visitor, RootInfo(kRootVMInternal));
}
}
- } else if (!kUseReadBarrier && (flags & kVisitRootFlagNewRoots) != 0) {
+ } else if (!gUseReadBarrier && (flags & kVisitRootFlagNewRoots) != 0) {
for (auto& root : new_class_roots_) {
ObjPtr<mirror::Class> old_ref = root.Read<kWithoutReadBarrier>();
root.VisitRoot(visitor, RootInfo(kRootStickyClass));
@@ -2172,13 +2173,13 @@
}
}
}
- if (!kUseReadBarrier && (flags & kVisitRootFlagClearRootLog) != 0) {
+ if (!gUseReadBarrier && (flags & kVisitRootFlagClearRootLog) != 0) {
new_class_roots_.clear();
new_bss_roots_boot_oat_files_.clear();
}
- if (!kUseReadBarrier && (flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
+ if (!gUseReadBarrier && (flags & kVisitRootFlagStartLoggingNewRoots) != 0) {
log_new_roots_ = true;
- } else if (!kUseReadBarrier && (flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
+ } else if (!gUseReadBarrier && (flags & kVisitRootFlagStopLoggingNewRoots) != 0) {
log_new_roots_ = false;
}
// We deliberately ignore the class roots in the image since we
@@ -3389,11 +3390,9 @@
}
instrumentation::Instrumentation* instrumentation = runtime->GetInstrumentation();
- // Link the code of methods skipped by LinkCode.
for (size_t method_index = 0; method_index < num_direct_methods; ++method_index) {
ArtMethod* method = klass->GetDirectMethod(method_index, pointer_size);
- if (!method->IsStatic()) {
- // Only update static methods.
+ if (!NeedsClinitCheckBeforeCall(method)) {
continue;
}
instrumentation->UpdateMethodsCode(method, instrumentation->GetCodeForInvoke(method));
@@ -4144,10 +4143,11 @@
class_loader)));
if (component_type == nullptr) {
DCHECK(self->IsExceptionPending());
- // We need to accept erroneous classes as component types.
+ // We need to accept erroneous classes as component types. Under AOT, we
+ // don't accept them as we cannot encode the erroneous class in an image.
const size_t component_hash = ComputeModifiedUtf8Hash(descriptor + 1);
component_type.Assign(LookupClass(self, descriptor + 1, component_hash, class_loader.Get()));
- if (component_type == nullptr) {
+ if (component_type == nullptr || Runtime::Current()->IsAotCompiler()) {
DCHECK(self->IsExceptionPending());
return nullptr;
} else {
@@ -5088,11 +5088,19 @@
CHECK_EQ(prototype, method->GetInterfaceMethodIfProxy(image_pointer_size_));
}
-bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass, bool can_init_statics,
+bool ClassLinker::CanWeInitializeClass(ObjPtr<mirror::Class> klass,
+ bool can_init_statics,
bool can_init_parents) {
if (can_init_statics && can_init_parents) {
return true;
}
+ DCHECK(Runtime::Current()->IsAotCompiler());
+
+ // We currently don't support initializing at AOT time classes that need access
+ // checks.
+ if (klass->IsVerifiedNeedsAccessChecks()) {
+ return false;
+ }
if (!can_init_statics) {
// Check if there's a class initializer.
ArtMethod* clinit = klass->FindClassInitializer(image_pointer_size_);
@@ -7859,20 +7867,6 @@
return true;
}
-NO_INLINE
-static void ThrowIllegalAccessErrorForImplementingMethod(ObjPtr<mirror::Class> klass,
- ArtMethod* vtable_method,
- ArtMethod* interface_method)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- DCHECK(!vtable_method->IsAbstract());
- DCHECK(!vtable_method->IsPublic());
- ThrowIllegalAccessError(
- klass,
- "Method '%s' implementing interface method '%s' is not public",
- vtable_method->PrettyMethod().c_str(),
- interface_method->PrettyMethod().c_str());
-}
-
template <PointerSize kPointerSize>
ObjPtr<mirror::PointerArray> ClassLinker::LinkMethodsHelper<kPointerSize>::AllocPointerArray(
Thread* self, size_t length) {
@@ -7961,55 +7955,17 @@
static constexpr double kMinLoadFactor = 0.3;
static constexpr double kMaxLoadFactor = 0.5;
static constexpr size_t kMaxStackBuferSize = 256;
- const size_t super_vtable_buffer_size = super_vtable_length * 3;
const size_t declared_virtuals_buffer_size = num_virtual_methods * 3;
- const size_t total_buffer_size = super_vtable_buffer_size + declared_virtuals_buffer_size;
- uint32_t* super_vtable_buffer_ptr = (total_buffer_size <= kMaxStackBuferSize)
- ? reinterpret_cast<uint32_t*>(alloca(total_buffer_size * sizeof(uint32_t)))
- : allocator_.AllocArray<uint32_t>(total_buffer_size);
- uint32_t* declared_virtuals_buffer_ptr = super_vtable_buffer_ptr + super_vtable_buffer_size;
- VTableSignatureSet super_vtable_signatures(
- kMinLoadFactor,
- kMaxLoadFactor,
- VTableSignatureHash(super_vtable_accessor),
- VTableSignatureEqual(super_vtable_accessor),
- super_vtable_buffer_ptr,
- super_vtable_buffer_size,
- allocator_.Adapter());
- ArrayRef<uint32_t> same_signature_vtable_lists;
- // Insert the first `mirror::Object::kVTableLength` indexes with pre-calculated hashes.
- DCHECK_GE(super_vtable_length, mirror::Object::kVTableLength);
- for (uint32_t i = 0; i != mirror::Object::kVTableLength; ++i) {
- size_t hash = class_linker_->object_virtual_method_hashes_[i];
- // There are no duplicate signatures in `java.lang.Object`, so use `HashSet<>::PutWithHash()`.
- // This avoids equality comparison for the three `java.lang.Object.wait()` overloads.
- super_vtable_signatures.PutWithHash(i, hash);
- }
- // Insert the remaining indexes, check for duplicate signatures.
- if (super_vtable_length > mirror::Object::kVTableLength) {
- for (size_t i = mirror::Object::kVTableLength; i < super_vtable_length; ++i) {
- // Use `super_vtable_accessor` for getting the method for hash calculation.
- // Letting `HashSet<>::insert()` use the internal accessor copy in the hash
- // function prevents the compiler from optimizing this properly because the
- // compiler cannot prove that the accessor copy is immutable.
- size_t hash = ComputeMethodHash(super_vtable_accessor.GetVTableEntry(i));
- auto [it, inserted] = super_vtable_signatures.InsertWithHash(i, hash);
- if (UNLIKELY(!inserted)) {
- if (same_signature_vtable_lists.empty()) {
- same_signature_vtable_lists = ArrayRef<uint32_t>(
- allocator_.AllocArray<uint32_t>(super_vtable_length), super_vtable_length);
- std::fill_n(same_signature_vtable_lists.data(), super_vtable_length, dex::kDexNoIndex);
- same_signature_vtable_lists_ = same_signature_vtable_lists;
- }
- DCHECK_LT(*it, i);
- same_signature_vtable_lists[i] = *it;
- *it = i;
- }
- }
- }
+ const size_t super_vtable_buffer_size = super_vtable_length * 3;
+ const size_t bit_vector_size = BitVector::BitsToWords(num_virtual_methods);
+ const size_t total_size =
+ declared_virtuals_buffer_size + super_vtable_buffer_size + bit_vector_size;
- // For each declared virtual method, look for a superclass virtual method
- // to override and assign a new vtable index if no method was overridden.
+ uint32_t* declared_virtuals_buffer_ptr = (total_size <= kMaxStackBuferSize)
+ ? reinterpret_cast<uint32_t*>(alloca(total_size * sizeof(uint32_t)))
+ : allocator_.AllocArray<uint32_t>(total_size);
+ uint32_t* bit_vector_buffer_ptr = declared_virtuals_buffer_ptr + declared_virtuals_buffer_size;
+
DeclaredVirtualSignatureSet declared_virtual_signatures(
kMinLoadFactor,
kMaxLoadFactor,
@@ -8018,8 +7974,24 @@
declared_virtuals_buffer_ptr,
declared_virtuals_buffer_size,
allocator_.Adapter());
+
+ ArrayRef<uint32_t> same_signature_vtable_lists;
const bool is_proxy_class = klass->IsProxyClass();
size_t vtable_length = super_vtable_length;
+
+ // Record which declared methods are overriding a super method.
+ BitVector initialized_methods(/* expandable= */ false,
+ Allocator::GetNoopAllocator(),
+ bit_vector_size,
+ bit_vector_buffer_ptr);
+
+ // Note: our sets hash on the method name, and therefore we pay a high
+ // performance price when a class has many overloads.
+ //
+ // We populate a set of declared signatures instead of signatures from the
+ // super vtable (which is only lazy populated in case of interface overriding,
+ // see below). This makes sure that we pay the performance price only on that
+ // class, and not on its subclasses (except in the case of interface overriding, see below).
for (size_t i = 0; i != num_virtual_methods; ++i) {
ArtMethod* virtual_method = klass->GetVirtualMethodDuringLinking(i, kPointerSize);
DCHECK(!virtual_method->IsStatic()) << virtual_method->PrettyMethod();
@@ -8028,59 +8000,79 @@
: virtual_method;
size_t hash = ComputeMethodHash(signature_method);
declared_virtual_signatures.PutWithHash(i, hash);
- auto it = super_vtable_signatures.FindWithHash(signature_method, hash);
- if (it != super_vtable_signatures.end()) {
- size_t super_index = *it;
- DCHECK_LT(super_index, super_vtable_length);
- ArtMethod* super_method = super_vtable_accessor.GetVTableEntry(super_index);
- // Historical note: Before Android 4.1, an inaccessible package-private
- // superclass method would have been incorrectly overridden.
- bool overrides = klass->CanAccessMember(super_method->GetDeclaringClass(),
- super_method->GetAccessFlags());
- if (overrides && super_method->IsFinal()) {
- sants.reset();
- ThrowLinkageError(klass, "Method %s overrides final method in class %s",
- virtual_method->PrettyMethod().c_str(),
- super_method->GetDeclaringClassDescriptor());
- return 0u;
- }
- if (UNLIKELY(!same_signature_vtable_lists.empty())) {
- // We may override more than one method according to JLS, see b/211854716 .
- // We record the highest overridden vtable index here so that we can walk
- // the list to find other overridden methods when constructing the vtable.
- // However, we walk all the methods to check for final method overriding.
- size_t current_index = super_index;
- while (same_signature_vtable_lists[current_index] != dex::kDexNoIndex) {
- DCHECK_LT(same_signature_vtable_lists[current_index], current_index);
- current_index = same_signature_vtable_lists[current_index];
- ArtMethod* current_method = super_vtable_accessor.GetVTableEntry(current_index);
- if (klass->CanAccessMember(current_method->GetDeclaringClass(),
- current_method->GetAccessFlags())) {
- if (current_method->IsFinal()) {
- sants.reset();
- ThrowLinkageError(klass, "Method %s overrides final method in class %s",
- virtual_method->PrettyMethod().c_str(),
- current_method->GetDeclaringClassDescriptor());
- return 0u;
- }
- if (!overrides) {
- overrides = true;
- super_index = current_index;
- super_method = current_method;
- }
- }
- }
- }
- if (overrides) {
- virtual_method->SetMethodIndex(super_index);
- continue;
- }
- }
- // The method does not override any method from superclass, so it needs a new vtable index.
- virtual_method->SetMethodIndex(vtable_length);
- ++vtable_length;
}
+ // Loop through each super vtable method and see if they are overridden by a method we added to
+ // the hash table.
+ for (size_t j = 0; j < super_vtable_length; ++j) {
+ // Search the hash table to see if we are overridden by any method.
+ ArtMethod* super_method = super_vtable_accessor.GetVTableEntry(j);
+ if (!klass->CanAccessMember(super_method->GetDeclaringClass(),
+ super_method->GetAccessFlags())) {
+ // Continue on to the next method since this one is package private and cannot be overridden.
+ // Before Android 4.1, the package-private method super_method might have been incorrectly
+ // overridden.
+ continue;
+ }
+ size_t hash = (j < mirror::Object::kVTableLength)
+ ? class_linker_->object_virtual_method_hashes_[j]
+ : ComputeMethodHash(super_method);
+ auto it = declared_virtual_signatures.FindWithHash(super_method, hash);
+ if (it == declared_virtual_signatures.end()) {
+ continue;
+ }
+ ArtMethod* virtual_method = klass->GetVirtualMethodDuringLinking(*it, kPointerSize);
+ if (super_method->IsFinal()) {
+ sants.reset();
+ ThrowLinkageError(klass, "Method %s overrides final method in class %s",
+ virtual_method->PrettyMethod().c_str(),
+ super_method->GetDeclaringClassDescriptor());
+ return 0u;
+ }
+ if (initialized_methods.IsBitSet(*it)) {
+ // The method is overriding more than one method.
+ // We record that information in a linked list to later set the method in the vtable
+ // locations that are not the method index.
+ if (same_signature_vtable_lists.empty()) {
+ same_signature_vtable_lists = ArrayRef<uint32_t>(
+ allocator_.AllocArray<uint32_t>(super_vtable_length), super_vtable_length);
+ std::fill_n(same_signature_vtable_lists.data(), super_vtable_length, dex::kDexNoIndex);
+ same_signature_vtable_lists_ = same_signature_vtable_lists;
+ }
+ same_signature_vtable_lists[j] = virtual_method->GetMethodIndexDuringLinking();
+ } else {
+ initialized_methods.SetBit(*it);
+ }
+
+ // We arbitrarily set to the largest index. This is also expected when
+ // iterating over the `same_signature_vtable_lists_`.
+ virtual_method->SetMethodIndex(j);
+ }
+
+ // Add the non-overridden methods at the end.
+ for (size_t i = 0; i < num_virtual_methods; ++i) {
+ if (!initialized_methods.IsBitSet(i)) {
+ ArtMethod* local_method = klass->GetVirtualMethodDuringLinking(i, kPointerSize);
+ local_method->SetMethodIndex(vtable_length);
+ vtable_length++;
+ }
+ }
+
+ // A lazily constructed super vtable set, which we only populate in the less
+ // common sittuation of a superclass implementing a method declared in an
+ // interface this class inherits.
+ // We still try to allocate the set on the stack as using the arena will have
+ // a larger cost.
+ uint32_t* super_vtable_buffer_ptr = bit_vector_buffer_ptr + bit_vector_size;
+ VTableSignatureSet super_vtable_signatures(
+ kMinLoadFactor,
+ kMaxLoadFactor,
+ VTableSignatureHash(super_vtable_accessor),
+ VTableSignatureEqual(super_vtable_accessor),
+ super_vtable_buffer_ptr,
+ super_vtable_buffer_size,
+ allocator_.Adapter());
+
// Assign vtable indexes for interface methods in new interfaces and store them
// in implementation method arrays. These shall be replaced by actual method
// pointers later. We do not need to do this for superclass interfaces as we can
@@ -8099,42 +8091,39 @@
ArtMethod* interface_method = iface->GetVirtualMethod(j, kPointerSize);
size_t hash = ComputeMethodHash(interface_method);
ArtMethod* vtable_method = nullptr;
- bool found = false;
auto it1 = declared_virtual_signatures.FindWithHash(interface_method, hash);
if (it1 != declared_virtual_signatures.end()) {
- vtable_method = klass->GetVirtualMethodDuringLinking(*it1, kPointerSize);
- found = true;
+ ArtMethod* found_method = klass->GetVirtualMethodDuringLinking(*it1, kPointerSize);
+ // For interface overriding, we only look at public methods.
+ if (found_method->IsPublic()) {
+ vtable_method = found_method;
+ }
} else {
+ // This situation should be rare (a superclass implements a method
+ // declared in an interface this class is inheriting). Only in this case
+ // do we lazily populate the super_vtable_signatures.
+ if (super_vtable_signatures.empty()) {
+ for (size_t k = 0; k < super_vtable_length; ++k) {
+ ArtMethod* super_method = super_vtable_accessor.GetVTableEntry(k);
+ if (!super_method->IsPublic()) {
+ // For interface overriding, we only look at public methods.
+ continue;
+ }
+ size_t super_hash = (k < mirror::Object::kVTableLength)
+ ? class_linker_->object_virtual_method_hashes_[k]
+ : ComputeMethodHash(super_method);
+ auto [it, inserted] = super_vtable_signatures.InsertWithHash(k, super_hash);
+ DCHECK(inserted || super_vtable_accessor.GetVTableEntry(*it) == super_method);
+ }
+ }
auto it2 = super_vtable_signatures.FindWithHash(interface_method, hash);
if (it2 != super_vtable_signatures.end()) {
- // If there are multiple vtable methods with the same signature, the one with
- // the highest vtable index is not nessarily the one in most-derived class.
- // Find the most-derived method. See b/211854716 .
vtable_method = super_vtable_accessor.GetVTableEntry(*it2);
- if (UNLIKELY(!same_signature_vtable_lists.empty())) {
- size_t current_index = *it2;
- while (same_signature_vtable_lists[current_index] != dex::kDexNoIndex) {
- DCHECK_LT(same_signature_vtable_lists[current_index], current_index);
- current_index = same_signature_vtable_lists[current_index];
- ArtMethod* current_method = super_vtable_accessor.GetVTableEntry(current_index);
- ObjPtr<mirror::Class> current_class = current_method->GetDeclaringClass();
- if (current_class->IsSubClass(vtable_method->GetDeclaringClass())) {
- vtable_method = current_method;
- }
- }
- }
- found = true;
}
}
+
uint32_t vtable_index = vtable_length;
- if (found) {
- DCHECK(vtable_method != nullptr);
- if (!vtable_method->IsAbstract() && !vtable_method->IsPublic()) {
- // FIXME: Delay the exception until we actually try to call the method. b/211854716
- sants.reset();
- ThrowIllegalAccessErrorForImplementingMethod(klass, vtable_method, interface_method);
- return 0u;
- }
+ if (vtable_method != nullptr) {
vtable_index = vtable_method->GetMethodIndexDuringLinking();
if (!vtable_method->IsOverridableByDefaultMethod()) {
method_array->SetElementPtrSize(j, vtable_index, kPointerSize);
@@ -8144,7 +8133,7 @@
auto [it, inserted] = copied_method_records_.InsertWithHash(
CopiedMethodRecord(interface_method, vtable_index), hash);
- if (found) {
+ if (vtable_method != nullptr) {
DCHECK_EQ(vtable_index, it->GetMethodIndex());
} else if (inserted) {
DCHECK_EQ(vtable_index, it->GetMethodIndex());
@@ -8484,17 +8473,16 @@
uint32_t vtable_index = virtual_method.GetMethodIndexDuringLinking();
vtable->SetElementPtrSize(vtable_index, &virtual_method, kPointerSize);
if (UNLIKELY(vtable_index < same_signature_vtable_lists.size())) {
- // We may override more than one method according to JLS, see b/211854716 .
- // If we do, arbitrarily update the method index to the lowest overridden vtable index.
+ // We may override more than one method according to JLS, see b/211854716.
while (same_signature_vtable_lists[vtable_index] != dex::kDexNoIndex) {
DCHECK_LT(same_signature_vtable_lists[vtable_index], vtable_index);
vtable_index = same_signature_vtable_lists[vtable_index];
- ArtMethod* current_method = super_class->GetVTableEntry(vtable_index, kPointerSize);
- if (klass->CanAccessMember(current_method->GetDeclaringClass(),
- current_method->GetAccessFlags())) {
+ vtable->SetElementPtrSize(vtable_index, &virtual_method, kPointerSize);
+ if (kIsDebugBuild) {
+ ArtMethod* current_method = super_class->GetVTableEntry(vtable_index, kPointerSize);
+ DCHECK(klass->CanAccessMember(current_method->GetDeclaringClass(),
+ current_method->GetAccessFlags()));
DCHECK(!current_method->IsFinal());
- vtable->SetElementPtrSize(vtable_index, &virtual_method, kPointerSize);
- virtual_method.SetMethodIndex(vtable_index);
}
}
}
@@ -8606,7 +8594,8 @@
};
// We use the following order of field types for assigning offsets.
-// Some fields can be shuffled forward to fill gaps, see `ClassLinker::LinkFields()`.
+// Some fields can be shuffled forward to fill gaps, see
+// `ClassLinker::LinkFieldsHelper::LinkFields()`.
enum class ClassLinker::LinkFieldsHelper::FieldTypeOrder : uint16_t {
kReference = 0u,
kLong,
@@ -10196,6 +10185,18 @@
}
}
+void ClassLinker::VisitDexCaches(DexCacheVisitor* visitor) const {
+ Thread* const self = Thread::Current();
+ for (const auto& it : dex_caches_) {
+ // Need to use DecodeJObject so that we get null for cleared JNI weak globals.
+ ObjPtr<mirror::DexCache> dex_cache = ObjPtr<mirror::DexCache>::DownCast(
+ self->DecodeJObject(it.second.weak_root));
+ if (dex_cache != nullptr) {
+ visitor->Visit(dex_cache);
+ }
+ }
+}
+
void ClassLinker::VisitAllocators(AllocatorVisitor* visitor) const {
for (const ClassLoaderData& data : class_loaders_) {
LinearAlloc* alloc = data.allocator;
diff --git a/runtime/class_linker.h b/runtime/class_linker.h
index 3b20c75b..eb97140 100644
--- a/runtime/class_linker.h
+++ b/runtime/class_linker.h
@@ -36,6 +36,7 @@
#include "dex/dex_file_types.h"
#include "gc_root.h"
#include "handle.h"
+#include "interpreter/mterp/nterp.h"
#include "jni.h"
#include "mirror/class.h"
#include "mirror/object.h"
@@ -127,6 +128,13 @@
REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) = 0;
};
+class DexCacheVisitor {
+ public:
+ virtual ~DexCacheVisitor() {}
+ virtual void Visit(ObjPtr<mirror::DexCache> dex_cache)
+ REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_) = 0;
+};
+
template <typename Func>
class ClassLoaderFuncVisitor final : public ClassLoaderVisitor {
public:
@@ -168,6 +176,7 @@
// Add boot class path dex files that were not included in the boot image.
// ClassLinker takes ownership of these dex files.
+ // DO NOT use directly. Use `Runtime::AddExtraBootDexFiles`.
void AddExtraBootDexFiles(Thread* self,
std::vector<std::unique_ptr<const DexFile>>&& additional_dex_files)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -478,6 +487,11 @@
REQUIRES(!Locks::classlinker_classes_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Visits only the classes in the boot class path.
+ template <typename Visitor>
+ inline void VisitBootClasses(Visitor* visitor)
+ REQUIRES_SHARED(Locks::classlinker_classes_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Less efficient variant of VisitClasses that copies the class_table_ into secondary storage
// so that it can visit individual classes without holding the doesn't hold the
// Locks::classlinker_classes_lock_. As the Locks::classlinker_classes_lock_ isn't held this code
@@ -608,6 +622,11 @@
return nterp_trampoline_ == entry_point;
}
+ bool IsNterpEntryPoint(const void* entry_point) const {
+ return entry_point == interpreter::GetNterpEntryPoint() ||
+ entry_point == interpreter::GetNterpWithClinitEntryPoint();
+ }
+
const void* GetQuickToInterpreterBridgeTrampoline() const {
return quick_to_interpreter_bridge_trampoline_;
}
@@ -762,10 +781,12 @@
REQUIRES_SHARED(Locks::mutator_lock_)
NO_THREAD_SAFETY_ANALYSIS;
+ // DO NOT use directly. Use `Runtime::AppendToBootClassPath`.
void AppendToBootClassPath(Thread* self, const DexFile* dex_file)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_);
+ // DO NOT use directly. Use `Runtime::AppendToBootClassPath`.
void AppendToBootClassPath(const DexFile* dex_file, ObjPtr<mirror::DexCache> dex_cache)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_);
@@ -774,6 +795,10 @@
void VisitClassLoaders(ClassLoaderVisitor* visitor) const
REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_);
+ // Visit all of the dex caches in the class linker.
+ void VisitDexCaches(DexCacheVisitor* visitor) const
+ REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_);
+
// Checks that a class and its superclass from another class loader have the same virtual methods.
bool ValidateSuperClassDescriptors(Handle<mirror::Class> klass)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/class_linker_test.cc b/runtime/class_linker_test.cc
index 010b384..666f86e 100644
--- a/runtime/class_linker_test.cc
+++ b/runtime/class_linker_test.cc
@@ -52,6 +52,7 @@
#include "mirror/object_array-inl.h"
#include "mirror/proxy.h"
#include "mirror/reference.h"
+#include "mirror/stack_frame_info.h"
#include "mirror/stack_trace_element.h"
#include "mirror/string-inl.h"
#include "mirror/var_handle.h"
@@ -598,6 +599,7 @@
struct ClassExtOffsets : public CheckOffsets<mirror::ClassExt> {
ClassExtOffsets() : CheckOffsets<mirror::ClassExt>(false, "Ldalvik/system/ClassExt;") {
+ addOffset(OFFSETOF_MEMBER(mirror::ClassExt, class_value_map_), "classValueMap");
addOffset(OFFSETOF_MEMBER(mirror::ClassExt, erroneous_state_error_), "erroneousStateError");
addOffset(OFFSETOF_MEMBER(mirror::ClassExt, instance_jfield_ids_), "instanceJfieldIDs");
addOffset(OFFSETOF_MEMBER(mirror::ClassExt, jmethod_ids_), "jmethodIDs");
@@ -640,6 +642,20 @@
}
};
+struct StackFrameInfoOffsets : public CheckOffsets<mirror::StackFrameInfo> {
+ StackFrameInfoOffsets() : CheckOffsets<mirror::StackFrameInfo>(
+ false, "Ljava/lang/StackFrameInfo;") {
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, bci_), "bci");
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, declaring_class_), "declaringClass");
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, file_name_), "fileName");
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, line_number_), "lineNumber");
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, method_name_), "methodName");
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, method_type_), "methodType");
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, retain_class_ref_), "retainClassRef");
+ addOffset(OFFSETOF_MEMBER(mirror::StackFrameInfo, ste_), "ste");
+ }
+};
+
struct ClassLoaderOffsets : public CheckOffsets<mirror::ClassLoader> {
ClassLoaderOffsets() : CheckOffsets<mirror::ClassLoader>(false, "Ljava/lang/ClassLoader;") {
addOffset(OFFSETOF_MEMBER(mirror::ClassLoader, allocator_), "allocator");
@@ -830,7 +846,7 @@
// C++ fields must exactly match the fields in the Java classes. If this fails,
// reorder the fields in the C++ class. Managed class fields are ordered by
-// ClassLinker::LinkFields.
+// ClassLinker::LinkFieldsHelper::LinkFields.
TEST_F(ClassLinkerTest, ValidateFieldOrderOfJavaCppUnionClasses) {
ScopedObjectAccess soa(Thread::Current());
EXPECT_TRUE(ObjectOffsets().Check());
@@ -858,6 +874,7 @@
EXPECT_TRUE(ArrayElementVarHandleOffsets().Check());
EXPECT_TRUE(ByteArrayViewVarHandleOffsets().Check());
EXPECT_TRUE(ByteBufferViewVarHandleOffsets().Check());
+ EXPECT_TRUE(StackFrameInfoOffsets().Check());
}
TEST_F(ClassLinkerTest, FindClassNonexistent) {
diff --git a/runtime/class_loader_context.cc b/runtime/class_loader_context.cc
index 2419b7b..31c310e 100644
--- a/runtime/class_loader_context.cc
+++ b/runtime/class_loader_context.cc
@@ -950,12 +950,13 @@
return result;
}
-std::string ClassLoaderContext::FlattenDexPaths() const {
+std::vector<std::string> ClassLoaderContext::FlattenDexPaths() const {
+ std::vector<std::string> result;
+
if (class_loader_chain_ == nullptr) {
- return "";
+ return result;
}
- std::vector<std::string> result;
std::vector<ClassLoaderInfo*> work_list;
work_list.push_back(class_loader_chain_.get());
while (!work_list.empty()) {
@@ -966,7 +967,7 @@
}
AddToWorkList(info, work_list);
}
- return FlattenClasspath(result);
+ return result;
}
const char* ClassLoaderContext::GetClassLoaderTypeName(ClassLoaderType type) {
diff --git a/runtime/class_loader_context.h b/runtime/class_loader_context.h
index eceea00..ccc5c73 100644
--- a/runtime/class_loader_context.h
+++ b/runtime/class_loader_context.h
@@ -155,9 +155,8 @@
// Should only be called if OpenDexFiles() returned true.
std::vector<const DexFile*> FlattenOpenedDexFiles() const;
- // Return a colon-separated list of dex file locations from this class loader
- // context after flattening.
- std::string FlattenDexPaths() const;
+ // Return a list of dex file locations from this class loader context after flattening.
+ std::vector<std::string> FlattenDexPaths() const;
// Verifies that the current context is identical to the context encoded as `context_spec`.
// Identical means:
diff --git a/runtime/class_loader_utils.h b/runtime/class_loader_utils.h
index 934c92b..c777370 100644
--- a/runtime/class_loader_utils.h
+++ b/runtime/class_loader_utils.h
@@ -177,7 +177,7 @@
VisitClassLoaderDexFiles<decltype(helper), void*>(soa,
class_loader,
helper,
- /* default= */ nullptr);
+ /* defaultReturn= */ nullptr);
}
} // namespace art
diff --git a/runtime/class_table-inl.h b/runtime/class_table-inl.h
index 071376c..67eeb55 100644
--- a/runtime/class_table-inl.h
+++ b/runtime/class_table-inl.h
@@ -104,6 +104,43 @@
}
}
+template <typename Visitor>
+class ClassTable::TableSlot::ClassAndRootVisitor {
+ public:
+ explicit ClassAndRootVisitor(Visitor& visitor) : visitor_(visitor) {}
+
+ void VisitRoot(mirror::CompressedReference<mirror::Object>* klass) const
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!klass->IsNull());
+ // Visit roots in the klass object
+ visitor_(klass->AsMirrorPtr());
+ // Visit the GC-root holding klass' reference
+ visitor_.VisitRoot(klass);
+ }
+
+ private:
+ Visitor& visitor_;
+};
+
+template <typename Visitor>
+void ClassTable::VisitClassesAndRoots(Visitor& visitor) {
+ TableSlot::ClassAndRootVisitor class_visitor(visitor);
+ ReaderMutexLock mu(Thread::Current(), lock_);
+ for (ClassSet& class_set : classes_) {
+ for (TableSlot& table_slot : class_set) {
+ table_slot.VisitRoot(class_visitor);
+ }
+ }
+ for (GcRoot<mirror::Object>& root : strong_roots_) {
+ visitor.VisitRoot(root.AddressWithoutBarrier());
+ }
+ for (const OatFile* oat_file : oat_files_) {
+ for (GcRoot<mirror::Object>& root : oat_file->GetBssGcRoots()) {
+ visitor.VisitRootIfNonNull(root.AddressWithoutBarrier());
+ }
+ }
+}
+
template <ReadBarrierOption kReadBarrierOption, typename Visitor>
bool ClassTable::Visit(Visitor& visitor) {
ReaderMutexLock mu(Thread::Current(), lock_);
diff --git a/runtime/class_table.h b/runtime/class_table.h
index 212a7d6..123c069 100644
--- a/runtime/class_table.h
+++ b/runtime/class_table.h
@@ -85,6 +85,9 @@
template<typename Visitor>
void VisitRoot(const Visitor& visitor) const NO_THREAD_SAFETY_ANALYSIS;
+ template<typename Visitor>
+ class ClassAndRootVisitor;
+
private:
// Extract a raw pointer from an address.
static ObjPtr<mirror::Class> ExtractPtr(uint32_t data)
@@ -185,6 +188,12 @@
REQUIRES(!lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
+ template<class Visitor>
+ void VisitClassesAndRoots(Visitor& visitor)
+ NO_THREAD_SAFETY_ANALYSIS
+ REQUIRES(!lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// Stops visit if the visitor returns false.
template <ReadBarrierOption kReadBarrierOption = kWithReadBarrier, typename Visitor>
bool Visit(Visitor& visitor)
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index a48d860..cd39686 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -166,9 +166,6 @@
WellKnownClasses::Init(Thread::Current()->GetJniEnv());
InitializeIntrinsics();
- // Create the heap thread pool so that the GC runs in parallel for tests. Normally, the thread
- // pool is created by the runtime.
- runtime_->GetHeap()->CreateThreadPool();
runtime_->GetHeap()->VerifyHeap(); // Check for heap corruption before the test
// Reduce timinig-dependent flakiness in OOME behavior (eg StubTest.AllocObject).
runtime_->GetHeap()->SetMinIntervalHomogeneousSpaceCompactionByOom(0U);
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 9fa9c5d..e136073 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -305,7 +305,7 @@
}
#define TEST_DISABLED_WITHOUT_BAKER_READ_BARRIERS() \
- if (!kEmitCompilerReadBarrier || !kUseBakerReadBarrier) { \
+ if (!gUseReadBarrier || !kUseBakerReadBarrier) { \
printf("WARNING: TEST DISABLED FOR GC WITHOUT BAKER READ BARRIER\n"); \
return; \
}
@@ -317,7 +317,7 @@
}
#define TEST_DISABLED_FOR_MEMORY_TOOL_WITH_HEAP_POISONING_WITHOUT_READ_BARRIERS() \
- if (kRunningOnMemoryTool && kPoisonHeapReferences && !kEmitCompilerReadBarrier) { \
+ if (kRunningOnMemoryTool && kPoisonHeapReferences && !gUseReadBarrier) { \
printf("WARNING: TEST DISABLED FOR MEMORY TOOL WITH HEAP POISONING WITHOUT READ BARRIERS\n"); \
return; \
}
diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc
index 17a0a8a..03fd423 100644
--- a/runtime/common_throws.cc
+++ b/runtime/common_throws.cc
@@ -152,7 +152,6 @@
// ClassCastException
void ThrowClassCastException(ObjPtr<mirror::Class> dest_type, ObjPtr<mirror::Class> src_type) {
- DumpB77342775DebugData(dest_type, src_type);
ThrowException("Ljava/lang/ClassCastException;", nullptr,
StringPrintf("%s cannot be cast to %s",
mirror::Class::PrettyDescriptor(src_type).c_str(),
@@ -238,6 +237,19 @@
va_end(args);
}
+void ThrowIllegalAccessErrorForImplementingMethod(ObjPtr<mirror::Class> klass,
+ ArtMethod* implementation_method,
+ ArtMethod* interface_method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(!implementation_method->IsAbstract());
+ DCHECK(!implementation_method->IsPublic());
+ ThrowIllegalAccessError(
+ klass,
+ "Method '%s' implementing interface method '%s' is not public",
+ implementation_method->PrettyMethod().c_str(),
+ interface_method->PrettyMethod().c_str());
+}
+
// IllegalAccessException
void ThrowIllegalAccessException(const char* msg) {
@@ -281,7 +293,6 @@
<< "' does not implement interface '"
<< mirror::Class::PrettyDescriptor(interface_method->GetDeclaringClass())
<< "' in call to '" << ArtMethod::PrettyMethod(interface_method) << "'";
- DumpB77342775DebugData(interface_method->GetDeclaringClass(), this_object->GetClass());
ThrowException("Ljava/lang/IncompatibleClassChangeError;",
referrer != nullptr ? referrer->GetDeclaringClass() : nullptr,
msg.str().c_str());
@@ -437,7 +448,7 @@
}
static bool IsValidReadBarrierImplicitCheck(uintptr_t addr) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
uint32_t monitor_offset = mirror::Object::MonitorOffset().Uint32Value();
if (kUseBakerReadBarrier &&
(kRuntimeISA == InstructionSet::kX86 || kRuntimeISA == InstructionSet::kX86_64)) {
@@ -472,7 +483,7 @@
}
case Instruction::IGET_OBJECT:
- if (kEmitCompilerReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
+ if (gUseReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
return true;
}
FALLTHROUGH_INTENDED;
@@ -496,7 +507,7 @@
}
case Instruction::AGET_OBJECT:
- if (kEmitCompilerReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
+ if (gUseReadBarrier && IsValidReadBarrierImplicitCheck(addr)) {
return true;
}
FALLTHROUGH_INTENDED;
diff --git a/runtime/common_throws.h b/runtime/common_throws.h
index 843c455..d9620df 100644
--- a/runtime/common_throws.h
+++ b/runtime/common_throws.h
@@ -111,6 +111,11 @@
__attribute__((__format__(__printf__, 2, 3)))
REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
+void ThrowIllegalAccessErrorForImplementingMethod(ObjPtr<mirror::Class> klass,
+ ArtMethod* implementation_method,
+ ArtMethod* interface_method)
+ REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
+
// IllegalAccessException
void ThrowIllegalAccessException(const char* msg)
diff --git a/runtime/debug_print.cc b/runtime/debug_print.cc
index cde4d86..9c38cce 100644
--- a/runtime/debug_print.cc
+++ b/runtime/debug_print.cc
@@ -129,60 +129,4 @@
return oss.str();
}
-void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::Class> src_class) {
- std::string target_descriptor_storage;
- const char* target_descriptor = target_class->GetDescriptor(&target_descriptor_storage);
- const char kCheckedPrefix[] = "Lorg/apache/http/";
- // Avoid spam for other packages. (That spam would break some ART run-tests for example.)
- if (strncmp(target_descriptor, kCheckedPrefix, sizeof(kCheckedPrefix) - 1) != 0) {
- return;
- }
- auto matcher = [target_descriptor, target_class](ObjPtr<mirror::Class> klass)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (klass->DescriptorEquals(target_descriptor)) {
- LOG(ERROR) << " descriptor match in "
- << DescribeLoaders(klass->GetClassLoader(), target_descriptor)
- << " match? " << std::boolalpha << (klass == target_class);
- }
- };
-
- std::string source_descriptor_storage;
- const char* source_descriptor = src_class->GetDescriptor(&source_descriptor_storage);
-
- LOG(ERROR) << "Maybe bug 77342775, looking for " << target_descriptor
- << " " << target_class.Ptr() << "[" << DescribeSpace(target_class) << "]"
- << " defined in " << target_class->GetDexFile().GetLocation()
- << "/" << static_cast<const void*>(&target_class->GetDexFile())
- << "\n with loader: " << DescribeLoaders(target_class->GetClassLoader(), target_descriptor);
- if (target_class->IsInterface()) {
- ObjPtr<mirror::IfTable> iftable = src_class->GetIfTable();
- CHECK(iftable != nullptr);
- size_t ifcount = iftable->Count();
- LOG(ERROR) << " in interface table for " << source_descriptor
- << " " << src_class.Ptr() << "[" << DescribeSpace(src_class) << "]"
- << " defined in " << src_class->GetDexFile().GetLocation()
- << "/" << static_cast<const void*>(&src_class->GetDexFile())
- << " ifcount=" << ifcount
- << "\n with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor);
- for (size_t i = 0; i != ifcount; ++i) {
- ObjPtr<mirror::Class> iface = iftable->GetInterface(i);
- CHECK(iface != nullptr);
- LOG(ERROR) << " iface #" << i << ": " << iface->PrettyDescriptor();
- matcher(iface);
- }
- } else {
- LOG(ERROR) << " in superclass chain for " << source_descriptor
- << " " << src_class.Ptr() << "[" << DescribeSpace(src_class) << "]"
- << " defined in " << src_class->GetDexFile().GetLocation()
- << "/" << static_cast<const void*>(&src_class->GetDexFile())
- << "\n with loader " << DescribeLoaders(src_class->GetClassLoader(), source_descriptor);
- for (ObjPtr<mirror::Class> klass = src_class;
- klass != nullptr;
- klass = klass->GetSuperClass()) {
- LOG(ERROR) << " - " << klass->PrettyDescriptor();
- matcher(klass);
- }
- }
-}
-
} // namespace art
diff --git a/runtime/debug_print.h b/runtime/debug_print.h
index e2990d4..7c68402 100644
--- a/runtime/debug_print.h
+++ b/runtime/debug_print.h
@@ -29,9 +29,6 @@
std::string DescribeLoaders(ObjPtr<mirror::ClassLoader> loader, const char* class_descriptor)
REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
-void DumpB77342775DebugData(ObjPtr<mirror::Class> target_class, ObjPtr<mirror::Class> src_class)
- REQUIRES_SHARED(Locks::mutator_lock_) COLD_ATTR;
-
} // namespace art
#endif // ART_RUNTIME_DEBUG_PRINT_H_
diff --git a/runtime/dex/dex_file_annotations.cc b/runtime/dex/dex_file_annotations.cc
index 5a409f0..3dd8ae1 100644
--- a/runtime/dex/dex_file_annotations.cc
+++ b/runtime/dex/dex_file_annotations.cc
@@ -841,6 +841,38 @@
return annotation_value.value_.GetL();
}
+template<typename T>
+static inline ObjPtr<mirror::ObjectArray<T>> GetAnnotationArrayValue(
+ Handle<mirror::Class> klass,
+ const char* annotation_name,
+ const char* value_name)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ClassData data(klass);
+ const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
+ if (annotation_set == nullptr) {
+ return nullptr;
+ }
+ const AnnotationItem* annotation_item =
+ SearchAnnotationSet(data.GetDexFile(), annotation_set, annotation_name,
+ DexFile::kDexVisibilitySystem);
+ if (annotation_item == nullptr) {
+ return nullptr;
+ }
+ StackHandleScope<1> hs(Thread::Current());
+ Handle<mirror::Class> class_array_class =
+ hs.NewHandle(GetClassRoot<mirror::ObjectArray<T>>());
+ DCHECK(class_array_class != nullptr);
+ ObjPtr<mirror::Object> obj = GetAnnotationValue(data,
+ annotation_item,
+ value_name,
+ class_array_class,
+ DexFile::kDexAnnotationArray);
+ if (obj == nullptr) {
+ return nullptr;
+ }
+ return obj->AsObjectArray<T>();
+}
+
static ObjPtr<mirror::ObjectArray<mirror::String>> GetSignatureValue(
const ClassData& klass,
const AnnotationSetItem* annotation_set)
@@ -1478,28 +1510,9 @@
}
ObjPtr<mirror::ObjectArray<mirror::Class>> GetDeclaredClasses(Handle<mirror::Class> klass) {
- ClassData data(klass);
- const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
- if (annotation_set == nullptr) {
- return nullptr;
- }
- const AnnotationItem* annotation_item =
- SearchAnnotationSet(data.GetDexFile(), annotation_set, "Ldalvik/annotation/MemberClasses;",
- DexFile::kDexVisibilitySystem);
- if (annotation_item == nullptr) {
- return nullptr;
- }
- StackHandleScope<1> hs(Thread::Current());
- Handle<mirror::Class> class_array_class =
- hs.NewHandle(GetClassRoot<mirror::ObjectArray<mirror::Class>>());
- DCHECK(class_array_class != nullptr);
- ObjPtr<mirror::Object> obj =
- GetAnnotationValue(data, annotation_item, "value", class_array_class,
- DexFile::kDexAnnotationArray);
- if (obj == nullptr) {
- return nullptr;
- }
- return obj->AsObjectArray<mirror::Class>();
+ return GetAnnotationArrayValue<mirror::Class>(klass,
+ "Ldalvik/annotation/MemberClasses;",
+ "value");
}
ObjPtr<mirror::Class> GetDeclaringClass(Handle<mirror::Class> klass) {
@@ -1714,6 +1727,46 @@
return data.GetDexFile().StringDataByIdx(index);
}
+ObjPtr<mirror::Class> GetNestHost(Handle<mirror::Class> klass) {
+ ClassData data(klass);
+ const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
+ if (annotation_set == nullptr) {
+ return nullptr;
+ }
+ const AnnotationItem* annotation_item =
+ SearchAnnotationSet(data.GetDexFile(), annotation_set, "Ldalvik/annotation/NestHost;",
+ DexFile::kDexVisibilitySystem);
+ if (annotation_item == nullptr) {
+ return nullptr;
+ }
+ ObjPtr<mirror::Object> obj = GetAnnotationValue(data,
+ annotation_item,
+ "host",
+ ScopedNullHandle<mirror::Class>(),
+ DexFile::kDexAnnotationType);
+ if (obj == nullptr) {
+ return nullptr;
+ }
+ if (!obj->IsClass()) {
+ // TypeNotPresentException, throw the NoClassDefFoundError.
+ Thread::Current()->SetException(obj->AsThrowable()->GetCause());
+ return nullptr;
+ }
+ return obj->AsClass();
+}
+
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetNestMembers(Handle<mirror::Class> klass) {
+ return GetAnnotationArrayValue<mirror::Class>(klass,
+ "Ldalvik/annotation/NestMembers;",
+ "classes");
+}
+
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetPermittedSubclasses(Handle<mirror::Class> klass) {
+ return GetAnnotationArrayValue<mirror::Class>(klass,
+ "Ldalvik/annotation/PermittedSubclasses;",
+ "value");
+}
+
bool IsClassAnnotationPresent(Handle<mirror::Class> klass, Handle<mirror::Class> annotation_class) {
ClassData data(klass);
const AnnotationSetItem* annotation_set = FindAnnotationSetForClass(data);
diff --git a/runtime/dex/dex_file_annotations.h b/runtime/dex/dex_file_annotations.h
index 3ef67e5..75951a7 100644
--- a/runtime/dex/dex_file_annotations.h
+++ b/runtime/dex/dex_file_annotations.h
@@ -136,6 +136,12 @@
Handle<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
const char* GetSourceDebugExtension(Handle<mirror::Class> klass)
REQUIRES_SHARED(Locks::mutator_lock_);
+ObjPtr<mirror::Class> GetNestHost(Handle<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetNestMembers(Handle<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ObjPtr<mirror::ObjectArray<mirror::Class>> GetPermittedSubclasses(Handle<mirror::Class> klass)
+ REQUIRES_SHARED(Locks::mutator_lock_);
bool IsClassAnnotationPresent(Handle<mirror::Class> klass,
Handle<mirror::Class> annotation_class)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/dex2oat_environment_test.h b/runtime/dex2oat_environment_test.h
index 964b7f3..166464f 100644
--- a/runtime/dex2oat_environment_test.h
+++ b/runtime/dex2oat_environment_test.h
@@ -21,8 +21,6 @@
#include <string>
#include <vector>
-#include <gtest/gtest.h>
-
#include "base/file_utils.h"
#include "base/os.h"
#include "base/stl_util.h"
@@ -34,8 +32,10 @@
#include "exec_utils.h"
#include "gc/heap.h"
#include "gc/space/image_space.h"
+#include "gtest/gtest.h"
#include "oat_file_assistant.h"
#include "runtime.h"
+#include "ziparchive/zip_writer.h"
namespace art {
@@ -233,6 +233,23 @@
return res.status_code;
}
+ void CreateDexMetadata(const std::string& vdex, const std::string& out_dm) {
+ // Read the vdex bytes.
+ std::unique_ptr<File> vdex_file(OS::OpenFileForReading(vdex.c_str()));
+ std::vector<uint8_t> data(vdex_file->GetLength());
+ ASSERT_TRUE(vdex_file->ReadFully(data.data(), data.size()));
+
+ // Zip the content.
+ FILE* file = fopen(out_dm.c_str(), "wb");
+ ZipWriter writer(file);
+ writer.StartEntry("primary.vdex", ZipWriter::kAlign32);
+ writer.WriteBytes(data.data(), data.size());
+ writer.FinishEntry();
+ writer.Finish();
+ fflush(file);
+ fclose(file);
+ }
+
private:
std::string scratch_dir_;
std::string odex_oat_dir_;
diff --git a/runtime/dexopt_test.cc b/runtime/dexopt_test.cc
index eb5b149..1f44a67 100644
--- a/runtime/dexopt_test.cc
+++ b/runtime/dexopt_test.cc
@@ -14,14 +14,17 @@
* limitations under the License.
*/
-#include <string>
-#include <vector>
+#include "dexopt_test.h"
#include <gtest/gtest.h>
#include <procinfo/process_map.h>
+#include <string>
+#include <vector>
+
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
+#include "arch/instruction_set.h"
#include "base/file_utils.h"
#include "base/mem_map.h"
#include "common_runtime_test.h"
@@ -29,10 +32,10 @@
#include "dex/art_dex_file_loader.h"
#include "dex/dex_file_loader.h"
#include "dex2oat_environment_test.h"
-#include "dexopt_test.h"
#include "gc/space/image_space.h"
#include "hidden_api.h"
#include "oat.h"
+#include "oat_file_assistant.h"
#include "profile/profile_compilation_info.h"
namespace art {
@@ -163,22 +166,13 @@
EXPECT_EQ(filter, odex_file->GetCompilerFilter());
if (CompilerFilter::DependsOnImageChecksum(filter)) {
- const OatHeader& oat_header = odex_file->GetOatHeader();
- const char* oat_bcp = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathKey);
- ASSERT_TRUE(oat_bcp != nullptr);
- ASSERT_EQ(oat_bcp, android::base::Join(Runtime::Current()->GetBootClassPathLocations(), ':'));
- const char* checksums = oat_header.GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
- ASSERT_TRUE(checksums != nullptr);
+ std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::Create(/*spec=*/"");
+ OatFileAssistant oat_file_assistant(dex_location.c_str(),
+ kRuntimeISA,
+ context.get(),
+ /*load_executable=*/false);
- bool match = gc::space::ImageSpace::VerifyBootClassPathChecksums(
- checksums,
- oat_bcp,
- ArrayRef<const std::string>(&image_location, 1),
- ArrayRef<const std::string>(Runtime::Current()->GetBootClassPathLocations()),
- ArrayRef<const std::string>(Runtime::Current()->GetBootClassPath()),
- ArrayRef<const int>(Runtime::Current()->GetBootClassPathFds()),
- kRuntimeISA,
- &error_msg);
+ bool match = oat_file_assistant.ValidateBootClassPathChecksums(*odex_file);
ASSERT_EQ(!with_alternate_image, match) << error_msg;
}
}
diff --git a/runtime/entrypoints/entrypoint_utils-inl.h b/runtime/entrypoints/entrypoint_utils-inl.h
index 4ee1013..91c2663 100644
--- a/runtime/entrypoints/entrypoint_utils-inl.h
+++ b/runtime/entrypoints/entrypoint_utils-inl.h
@@ -486,6 +486,123 @@
#undef EXPLICIT_FIND_FIELD_FROM_CODE_TYPED_TEMPLATE_DECL
#undef EXPLICIT_FIND_FIELD_FROM_CODE_TEMPLATE_DECL
+static inline bool IsStringInit(const DexFile* dex_file, uint32_t method_idx)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const dex::MethodId& method_id = dex_file->GetMethodId(method_idx);
+ const char* class_name = dex_file->StringByTypeIdx(method_id.class_idx_);
+ const char* method_name = dex_file->GetMethodName(method_id);
+ // Instead of calling ResolveMethod() which has suspend point and can trigger
+ // GC, look up the method symbolically.
+ // Compare method's class name and method name against string init.
+ // It's ok since it's not allowed to create your own java/lang/String.
+ // TODO: verify that assumption.
+ if ((strcmp(class_name, "Ljava/lang/String;") == 0) &&
+ (strcmp(method_name, "<init>") == 0)) {
+ return true;
+ }
+ return false;
+}
+
+static inline bool IsStringInit(const Instruction& instr, ArtMethod* caller)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (instr.Opcode() == Instruction::INVOKE_DIRECT ||
+ instr.Opcode() == Instruction::INVOKE_DIRECT_RANGE) {
+ uint16_t callee_method_idx = (instr.Opcode() == Instruction::INVOKE_DIRECT_RANGE) ?
+ instr.VRegB_3rc() : instr.VRegB_35c();
+ return IsStringInit(caller->GetDexFile(), callee_method_idx);
+ }
+ return false;
+}
+
+extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, const uint16_t* dex_pc_ptr);
+
+template <InvokeType type>
+ArtMethod* FindMethodToCall(Thread* self,
+ ArtMethod* caller,
+ ObjPtr<mirror::Object>* this_object,
+ const Instruction& inst,
+ /*out*/ bool* string_init)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
+
+ // Try to find the method in thread-local cache.
+ size_t tls_value = 0u;
+ if (!self->GetInterpreterCache()->Get(self, &inst, &tls_value)) {
+ DCHECK(!self->IsExceptionPending());
+ // NterpGetMethod can suspend, so save this_object.
+ StackHandleScope<1> hs(self);
+ HandleWrapperObjPtr<mirror::Object> h_this(hs.NewHandleWrapper(this_object));
+ tls_value = NterpGetMethod(self, caller, reinterpret_cast<const uint16_t*>(&inst));
+ if (self->IsExceptionPending()) {
+ return nullptr;
+ }
+ }
+
+ if (type != kStatic && UNLIKELY((*this_object) == nullptr)) {
+ if (UNLIKELY(IsStringInit(inst, caller))) {
+ // Hack for String init:
+ //
+ // We assume that the input of String.<init> in verified code is always
+ // an uninitialized reference. If it is a null constant, it must have been
+ // optimized out by the compiler and we arrive here after deoptimization.
+ // Do not throw NullPointerException.
+ } else {
+ // Maintain interpreter-like semantics where NullPointerException is thrown
+ // after potential NoSuchMethodError from class linker.
+ const uint32_t method_idx = inst.VRegB();
+ ThrowNullPointerExceptionForMethodAccess(method_idx, type);
+ return nullptr;
+ }
+ }
+
+ static constexpr size_t kStringInitMethodFlag = 0b1;
+ static constexpr size_t kInvokeInterfaceOnObjectMethodFlag = 0b1;
+ static constexpr size_t kMethodMask = ~0b11;
+
+ ArtMethod* called_method = nullptr;
+ switch (type) {
+ case kDirect:
+ case kSuper:
+ case kStatic:
+ // Note: for the interpreter, the String.<init> special casing for invocation is handled
+ // in DoCallCommon.
+ *string_init = ((tls_value & kStringInitMethodFlag) != 0);
+ DCHECK_EQ(*string_init, IsStringInit(inst, caller));
+ called_method = reinterpret_cast<ArtMethod*>(tls_value & kMethodMask);
+ break;
+ case kInterface:
+ if ((tls_value & kInvokeInterfaceOnObjectMethodFlag) != 0) {
+ // invokeinterface on a j.l.Object method.
+ uint16_t method_index = tls_value >> 16;
+ called_method = (*this_object)->GetClass()->GetVTableEntry(method_index, pointer_size);
+ } else {
+ ArtMethod* interface_method = reinterpret_cast<ArtMethod*>(tls_value & kMethodMask);
+ called_method = (*this_object)->GetClass()->GetImt(pointer_size)->Get(
+ interface_method->GetImtIndex(), pointer_size);
+ if (called_method->IsRuntimeMethod()) {
+ called_method = (*this_object)->GetClass()->FindVirtualMethodForInterface(
+ interface_method, pointer_size);
+ if (UNLIKELY(called_method == nullptr)) {
+ ThrowIncompatibleClassChangeErrorClassForInterfaceDispatch(
+ interface_method, *this_object, caller);
+ return nullptr;
+ }
+ }
+ }
+ break;
+ case kVirtual:
+ called_method = (*this_object)->GetClass()->GetVTableEntry(tls_value, pointer_size);
+ break;
+ }
+
+ if (UNLIKELY(!called_method->IsInvokable())) {
+ called_method->ThrowInvocationTimeError((type == kStatic) ? nullptr : *this_object);
+ return nullptr;
+ }
+ DCHECK(!called_method->IsRuntimeMethod()) << called_method->PrettyMethod();
+ return called_method;
+}
+
template<bool access_check>
ALWAYS_INLINE ArtMethod* FindSuperMethodToCall(uint32_t method_idx,
ArtMethod* resolved_method,
@@ -546,130 +663,6 @@
return super_class->GetVTableEntry(vtable_index, linker->GetImagePointerSize());
}
-// Follow virtual/interface indirections if applicable.
-// Will throw null-pointer exception the if the object is null.
-template<InvokeType type, bool access_check>
-ALWAYS_INLINE ArtMethod* FindMethodToCall(uint32_t method_idx,
- ArtMethod* resolved_method,
- ObjPtr<mirror::Object>* this_object,
- ArtMethod* referrer,
- Thread* self)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- // Null pointer check.
- if (UNLIKELY(*this_object == nullptr && type != kStatic)) {
- if (UNLIKELY(resolved_method->GetDeclaringClass()->IsStringClass() &&
- resolved_method->IsConstructor())) {
- // Hack for String init:
- //
- // We assume that the input of String.<init> in verified code is always
- // an unitialized reference. If it is a null constant, it must have been
- // optimized out by the compiler. Do not throw NullPointerException.
- } else {
- // Maintain interpreter-like semantics where NullPointerException is thrown
- // after potential NoSuchMethodError from class linker.
- ThrowNullPointerExceptionForMethodAccess(method_idx, type);
- return nullptr; // Failure.
- }
- }
- switch (type) {
- case kStatic:
- case kDirect:
- return resolved_method;
- case kVirtual: {
- ObjPtr<mirror::Class> klass = (*this_object)->GetClass();
- uint16_t vtable_index = resolved_method->GetMethodIndex();
- if (access_check &&
- (!klass->HasVTable() ||
- vtable_index >= static_cast<uint32_t>(klass->GetVTableLength()))) {
- // Behavior to agree with that of the verifier.
- ThrowNoSuchMethodError(type, resolved_method->GetDeclaringClass(),
- resolved_method->GetName(), resolved_method->GetSignature());
- return nullptr; // Failure.
- }
- DCHECK(klass->HasVTable()) << klass->PrettyClass();
- return klass->GetVTableEntry(vtable_index, class_linker->GetImagePointerSize());
- }
- case kSuper: {
- return FindSuperMethodToCall<access_check>(method_idx, resolved_method, referrer, self);
- }
- case kInterface: {
- size_t imt_index = resolved_method->GetImtIndex();
- PointerSize pointer_size = class_linker->GetImagePointerSize();
- ObjPtr<mirror::Class> klass = (*this_object)->GetClass();
- ArtMethod* imt_method = klass->GetImt(pointer_size)->Get(imt_index, pointer_size);
- if (!imt_method->IsRuntimeMethod()) {
- if (kIsDebugBuild) {
- ArtMethod* method = klass->FindVirtualMethodForInterface(
- resolved_method, class_linker->GetImagePointerSize());
- CHECK_EQ(imt_method, method) << ArtMethod::PrettyMethod(resolved_method) << " / "
- << imt_method->PrettyMethod() << " / "
- << ArtMethod::PrettyMethod(method) << " / "
- << klass->PrettyClass();
- }
- return imt_method;
- } else {
- ArtMethod* interface_method = klass->FindVirtualMethodForInterface(
- resolved_method, class_linker->GetImagePointerSize());
- if (UNLIKELY(interface_method == nullptr)) {
- ThrowIncompatibleClassChangeErrorClassForInterfaceDispatch(resolved_method,
- *this_object, referrer);
- return nullptr; // Failure.
- }
- return interface_method;
- }
- }
- default:
- LOG(FATAL) << "Unknown invoke type " << type;
- return nullptr; // Failure.
- }
-}
-
-template<InvokeType type, bool access_check>
-inline ArtMethod* FindMethodFromCode(uint32_t method_idx,
- ObjPtr<mirror::Object>* this_object,
- ArtMethod* referrer,
- Thread* self) {
- ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- constexpr ClassLinker::ResolveMode resolve_mode =
- access_check ? ClassLinker::ResolveMode::kCheckICCEAndIAE
- : ClassLinker::ResolveMode::kNoChecks;
- ArtMethod* resolved_method;
- if (type == kStatic) {
- resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, referrer, type);
- } else {
- StackHandleScope<1> hs(self);
- HandleWrapperObjPtr<mirror::Object> h_this(hs.NewHandleWrapper(this_object));
- resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, referrer, type);
- }
- if (UNLIKELY(resolved_method == nullptr)) {
- DCHECK(self->IsExceptionPending()); // Throw exception and unwind.
- return nullptr; // Failure.
- }
- return FindMethodToCall<type, access_check>(
- method_idx, resolved_method, this_object, referrer, self);
-}
-
-// Explicit template declarations of FindMethodFromCode for all invoke types.
-#define EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL(_type, _access_check) \
- template REQUIRES_SHARED(Locks::mutator_lock_) ALWAYS_INLINE \
- ArtMethod* FindMethodFromCode<_type, _access_check>(uint32_t method_idx, \
- ObjPtr<mirror::Object>* this_object, \
- ArtMethod* referrer, \
- Thread* self)
-#define EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(_type) \
- EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL(_type, false); \
- EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL(_type, true)
-
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kStatic);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kDirect);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kVirtual);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kSuper);
-EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL(kInterface);
-
-#undef EXPLICIT_FIND_METHOD_FROM_CODE_TYPED_TEMPLATE_DECL
-#undef EXPLICIT_FIND_METHOD_FROM_CODE_TEMPLATE_DECL
-
inline ObjPtr<mirror::Class> ResolveVerifyAndClinit(dex::TypeIndex type_idx,
ArtMethod* referrer,
Thread* self,
diff --git a/runtime/entrypoints/entrypoint_utils.h b/runtime/entrypoints/entrypoint_utils.h
index 8b6fc69..ae56875 100644
--- a/runtime/entrypoints/entrypoint_utils.h
+++ b/runtime/entrypoints/entrypoint_utils.h
@@ -143,11 +143,12 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Roles::uninterruptible_);
-template<InvokeType type, bool access_check>
-inline ArtMethod* FindMethodFromCode(uint32_t method_idx,
- ObjPtr<mirror::Object>* this_object,
- ArtMethod* referrer,
- Thread* self)
+template<InvokeType type>
+inline ArtMethod* FindMethodToCall(Thread* self,
+ ArtMethod* referrer,
+ ObjPtr<mirror::Object>* this_object,
+ const Instruction& inst,
+ /*out*/ bool* string_init)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Roles::uninterruptible_);
diff --git a/runtime/entrypoints/jni/jni_entrypoints.cc b/runtime/entrypoints/jni/jni_entrypoints.cc
index c78b604..80eb89f 100644
--- a/runtime/entrypoints/jni/jni_entrypoints.cc
+++ b/runtime/entrypoints/jni/jni_entrypoints.cc
@@ -87,11 +87,11 @@
}
// Replace the runtime method on the stack with the target method.
- DCHECK(!self->GetManagedStack()->GetTopQuickFrameTag());
+ DCHECK(!self->GetManagedStack()->GetTopQuickFrameGenericJniTag());
ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrameKnownNotTagged();
DCHECK(*sp == Runtime::Current()->GetCalleeSaveMethod(CalleeSaveType::kSaveRefsAndArgs));
*sp = target_method;
- self->SetTopOfStackTagged(sp); // Fake GenericJNI frame.
+ self->SetTopOfStackGenericJniTagged(sp); // Fake GenericJNI frame.
// Continue with the target method.
method = target_method;
diff --git a/runtime/entrypoints/quick/quick_default_externs.h b/runtime/entrypoints/quick/quick_default_externs.h
index f8856d8..cb3caac 100644
--- a/runtime/entrypoints/quick/quick_default_externs.h
+++ b/runtime/entrypoints/quick/quick_default_externs.h
@@ -122,6 +122,7 @@
extern "C" void art_jni_monitored_method_start();
extern "C" void art_jni_method_end();
extern "C" void art_jni_monitored_method_end();
+extern "C" void art_jni_method_entry_hook();
// JNI lock/unlock entrypoints. Note: Custom calling convention.
extern "C" void art_jni_lock_object(art::mirror::Object*);
diff --git a/runtime/entrypoints/quick/quick_default_init_entrypoints.h b/runtime/entrypoints/quick/quick_default_init_entrypoints.h
index 939feee..ea07788 100644
--- a/runtime/entrypoints/quick/quick_default_init_entrypoints.h
+++ b/runtime/entrypoints/quick/quick_default_init_entrypoints.h
@@ -79,6 +79,7 @@
qpoints->SetQuickGenericJniTrampoline(art_quick_generic_jni_trampoline);
qpoints->SetJniDecodeReferenceResult(JniDecodeReferenceResult);
qpoints->SetJniReadBarrier(art_jni_read_barrier);
+ qpoints->SetJniMethodEntryHook(art_jni_method_entry_hook);
// Locks
if (UNLIKELY(VLOG_IS_ON(systrace_lock_logging))) {
diff --git a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
index 60a5875..76bee21 100644
--- a/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_dexcache_entrypoints.cc
@@ -23,6 +23,7 @@
#include "dex/dex_file_types.h"
#include "entrypoints/entrypoint_utils-inl.h"
#include "gc/heap.h"
+#include "jvalue-inl.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
#include "mirror/object-inl.h"
diff --git a/runtime/entrypoints/quick/quick_entrypoints.h b/runtime/entrypoints/quick/quick_entrypoints.h
index 7af1a0b..0e73c63 100644
--- a/runtime/entrypoints/quick/quick_entrypoints.h
+++ b/runtime/entrypoints/quick/quick_entrypoints.h
@@ -67,6 +67,7 @@
// JNI entrypoints when monitoring entry/exit.
extern "C" void artJniMonitoredMethodStart(Thread* self) UNLOCK_FUNCTION(Locks::mutator_lock_);
extern "C" void artJniMonitoredMethodEnd(Thread* self) SHARED_LOCK_FUNCTION(Locks::mutator_lock_);
+extern "C" void artJniMethodEntryHook(Thread* self);
// StringAppend pattern entrypoint.
extern "C" mirror::String* artStringBuilderAppend(uint32_t format,
diff --git a/runtime/entrypoints/quick/quick_entrypoints_list.h b/runtime/entrypoints/quick/quick_entrypoints_list.h
index dffaa4b..4534bba 100644
--- a/runtime/entrypoints/quick/quick_entrypoints_list.h
+++ b/runtime/entrypoints/quick/quick_entrypoints_list.h
@@ -78,6 +78,7 @@
V(JniLockObject, void, mirror::Object*) \
V(JniUnlockObject, void, mirror::Object*) \
V(QuickGenericJniTrampoline, void, ArtMethod*) \
+ V(JniMethodEntryHook, void) \
\
V(LockObject, void, mirror::Object*) \
V(UnlockObject, void, mirror::Object*) \
diff --git a/runtime/entrypoints/quick/quick_field_entrypoints.cc b/runtime/entrypoints/quick/quick_field_entrypoints.cc
index d32aa39..81a9e21 100644
--- a/runtime/entrypoints/quick/quick_field_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_field_entrypoints.cc
@@ -175,7 +175,7 @@
return -1; \
} \
} \
- if (!referrer->SkipAccessChecks() && IsObject && new_value != 0) { \
+ if (!referrer->SkipAccessChecks() && (IsObject) && new_value != 0) { \
StackArtFieldHandleScope<1> rhs(self); \
ReflectiveHandle<ArtField> field_handle(rhs.NewHandle(field)); \
if (field->ResolveType().IsNull()) { \
@@ -223,7 +223,7 @@
return -1; \
} \
} \
- if (!referrer->SkipAccessChecks() && IsObject && new_value != 0) { \
+ if (!referrer->SkipAccessChecks() && (IsObject) && new_value != 0) { \
StackArtFieldHandleScope<1> rhs(self); \
ReflectiveHandle<ArtField> field_handle(rhs.NewHandle(field)); \
if (field->ResolveType().IsNull()) { \
@@ -435,7 +435,7 @@
}
extern "C" mirror::Object* artReadBarrierMark(mirror::Object* obj) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
return ReadBarrier::Mark(obj);
}
@@ -443,14 +443,12 @@
mirror::Object* obj,
uint32_t offset) {
// Used only in connection with non-volatile loads.
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
uint8_t* raw_addr = reinterpret_cast<uint8_t*>(obj) + offset;
mirror::HeapReference<mirror::Object>* ref_addr =
reinterpret_cast<mirror::HeapReference<mirror::Object>*>(raw_addr);
- constexpr ReadBarrierOption kReadBarrierOption =
- kUseReadBarrier ? kWithReadBarrier : kWithoutReadBarrier;
mirror::Object* result =
- ReadBarrier::Barrier<mirror::Object, /* kIsVolatile= */ false, kReadBarrierOption>(
+ ReadBarrier::Barrier<mirror::Object, /* kIsVolatile= */ false, kWithReadBarrier>(
obj,
MemberOffset(offset),
ref_addr);
@@ -458,7 +456,7 @@
}
extern "C" mirror::Object* artReadBarrierForRootSlow(GcRoot<mirror::Object>* root) {
- DCHECK(kEmitCompilerReadBarrier);
+ DCHECK(gUseReadBarrier);
return root->Read();
}
diff --git a/runtime/entrypoints/quick/quick_jni_entrypoints.cc b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
index ab13bd9..fafa3c7 100644
--- a/runtime/entrypoints/quick/quick_jni_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
@@ -38,11 +38,16 @@
namespace art {
+extern "C" int artMethodExitHook(Thread* self,
+ ArtMethod* method,
+ uint64_t* gpr_result,
+ uint64_t* fpr_result);
+
static_assert(sizeof(IRTSegmentState) == sizeof(uint32_t), "IRTSegmentState size unexpected");
static_assert(std::is_trivial<IRTSegmentState>::value, "IRTSegmentState not trivial");
extern "C" void artJniReadBarrier(ArtMethod* method) {
- DCHECK(kUseReadBarrier);
+ DCHECK(gUseReadBarrier);
mirror::CompressedReference<mirror::Object>* declaring_class =
method->GetDeclaringClassAddressWithoutBarrier();
if (kUseBakerReadBarrier) {
@@ -174,11 +179,11 @@
artJniUnlockObject(lock.Ptr(), self);
}
char return_shorty_char = called->GetShorty()[0];
+ uint64_t ret;
if (return_shorty_char == 'L') {
- uint64_t ret = reinterpret_cast<uint64_t>(
+ ret = reinterpret_cast<uint64_t>(
UNLIKELY(self->IsExceptionPending()) ? nullptr : JniDecodeReferenceResult(result.l, self));
PopLocalReferences(saved_local_ref_cookie, self);
- return ret;
} else {
if (LIKELY(!critical_native)) {
PopLocalReferences(saved_local_ref_cookie, self);
@@ -188,32 +193,43 @@
if (kRuntimeISA == InstructionSet::kX86) {
// Convert back the result to float.
double d = bit_cast<double, uint64_t>(result_f);
- return bit_cast<uint32_t, float>(static_cast<float>(d));
+ ret = bit_cast<uint32_t, float>(static_cast<float>(d));
} else {
- return result_f;
+ ret = result_f;
}
}
+ break;
case 'D':
- return result_f;
+ ret = result_f;
+ break;
case 'Z':
- return result.z;
+ ret = result.z;
+ break;
case 'B':
- return result.b;
+ ret = result.b;
+ break;
case 'C':
- return result.c;
+ ret = result.c;
+ break;
case 'S':
- return result.s;
+ ret = result.s;
+ break;
case 'I':
- return result.i;
+ ret = result.i;
+ break;
case 'J':
- return result.j;
+ ret = result.j;
+ break;
case 'V':
- return 0;
+ ret = 0;
+ break;
default:
LOG(FATAL) << "Unexpected return shorty character " << return_shorty_char;
UNREACHABLE();
}
}
+
+ return ret;
}
extern "C" void artJniMonitoredMethodStart(Thread* self) {
diff --git a/runtime/entrypoints/quick/quick_thread_entrypoints.cc b/runtime/entrypoints/quick/quick_thread_entrypoints.cc
index 93422cf..5dca58a 100644
--- a/runtime/entrypoints/quick/quick_thread_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_thread_entrypoints.cc
@@ -21,16 +21,46 @@
namespace art {
+extern "C" void artDeoptimizeIfNeeded(Thread* self, uintptr_t result, bool is_ref)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ DCHECK(!self->IsExceptionPending());
+
+ ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+ DCHECK(sp != nullptr && (*sp)->IsRuntimeMethod());
+
+ DeoptimizationMethodType type = instr->GetDeoptimizationMethodType(*sp);
+ JValue jvalue;
+ jvalue.SetJ(result);
+ instr->DeoptimizeIfNeeded(self, sp, type, jvalue, is_ref);
+}
+
extern "C" void artTestSuspendFromCode(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
// Called when there is a pending checkpoint or suspend request.
ScopedQuickEntrypointChecks sqec(self);
self->CheckSuspend();
+
+ // We could have other dex instructions at the same dex pc as suspend and we need to execute
+ // those instructions. So we should start executing from the current dex pc.
+ ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+ JValue result;
+ result.SetJ(0);
+ Runtime::Current()->GetInstrumentation()->DeoptimizeIfNeeded(
+ self, sp, DeoptimizationMethodType::kKeepDexPc, result, /* is_ref= */ false);
}
extern "C" void artImplicitSuspendFromCode(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_) {
// Called when there is a pending checkpoint or suspend request.
ScopedQuickEntrypointChecks sqec(self);
self->CheckSuspend(/*implicit=*/ true);
+
+ // We could have other dex instructions at the same dex pc as suspend and we need to execute
+ // those instructions. So we should start executing from the current dex pc.
+ ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+ JValue result;
+ result.SetJ(0);
+ Runtime::Current()->GetInstrumentation()->DeoptimizeIfNeeded(
+ self, sp, DeoptimizationMethodType::kKeepDexPc, result, /* is_ref= */ false);
}
extern "C" void artCompileOptimized(ArtMethod* method, Thread* self)
diff --git a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
index b6ece4a..9e2100738 100644
--- a/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_trampoline_entrypoints.cc
@@ -224,13 +224,8 @@
#endif
public:
- // Special handling for proxy methods. Proxy methods are instance methods so the
- // 'this' object is the 1st argument. They also have the same frame layout as the
- // kRefAndArgs runtime method. Since 'this' is a reference, it is located in the
- // 1st GPR.
- static StackReference<mirror::Object>* GetProxyThisObjectReference(ArtMethod** sp)
+ static StackReference<mirror::Object>* GetThisObjectReference(ArtMethod** sp)
REQUIRES_SHARED(Locks::mutator_lock_) {
- CHECK((*sp)->IsProxyMethod());
CHECK_GT(kNumQuickGprArgs, 0u);
constexpr uint32_t kThisGprIndex = 0u; // 'this' is in the 1st GPR.
size_t this_arg_offset = kQuickCalleeSaveFrame_RefAndArgs_Gpr1Offset +
@@ -529,7 +524,8 @@
// allows to use the QuickArgumentVisitor constants without moving all the code in its own module.
extern "C" mirror::Object* artQuickGetProxyThisObject(ArtMethod** sp)
REQUIRES_SHARED(Locks::mutator_lock_) {
- return QuickArgumentVisitor::GetProxyThisObjectReference(sp)->AsMirrorPtr();
+ DCHECK((*sp)->IsProxyMethod());
+ return QuickArgumentVisitor::GetThisObjectReference(sp)->AsMirrorPtr();
}
// Visits arguments on the stack placing them into the shadow frame.
@@ -647,6 +643,7 @@
method_type);
}
+NO_STACK_PROTECTOR
extern "C" uint64_t artQuickToInterpreterBridge(ArtMethod* method, Thread* self, ArtMethod** sp)
REQUIRES_SHARED(Locks::mutator_lock_) {
// Ensure we don't get thread suspension until the object arguments are safely in the shadow
@@ -654,7 +651,10 @@
ScopedQuickEntrypointChecks sqec(self);
if (UNLIKELY(!method->IsInvokable())) {
- method->ThrowInvocationTimeError();
+ method->ThrowInvocationTimeError(
+ method->IsStatic()
+ ? nullptr
+ : QuickArgumentVisitor::GetThisObjectReference(sp)->AsMirrorPtr());
return 0;
}
@@ -713,41 +713,34 @@
// Pop transition.
self->PopManagedStackFragment(fragment);
- // Request a stack deoptimization if needed
- ArtMethod* caller = QuickArgumentVisitor::GetCallingMethod(sp);
- uintptr_t caller_pc = QuickArgumentVisitor::GetCallingPc(sp);
+ // Check if caller needs to be deoptimized for instrumentation reasons.
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
// If caller_pc is the instrumentation exit stub, the stub will check to see if deoptimization
// should be done and it knows the real return pc. NB If the upcall is null we don't need to do
// anything. This can happen during shutdown or early startup.
- if (UNLIKELY(
- caller != nullptr &&
- caller_pc != reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc()) &&
- (self->IsForceInterpreter() || Dbg::IsForcedInterpreterNeededForUpcall(self, caller)))) {
- if (!Runtime::Current()->IsAsyncDeoptimizeable(caller_pc)) {
- LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
- << caller->PrettyMethod();
- } else {
- VLOG(deopt) << "Forcing deoptimization on return from method " << method->PrettyMethod()
- << " to " << caller->PrettyMethod()
- << (force_frame_pop ? " for frame-pop" : "");
- DCHECK_IMPLIES(force_frame_pop, result.GetJ() == 0)
- << "Force frame pop should have no result.";
- if (force_frame_pop && self->GetException() != nullptr) {
- LOG(WARNING) << "Suppressing exception for instruction-retry: "
- << self->GetException()->Dump();
- }
- // Push the context of the deoptimization stack so we can restore the return value and the
- // exception before executing the deoptimized frames.
- self->PushDeoptimizationContext(
- result,
- shorty[0] == 'L' || shorty[0] == '[', /* class or array */
- force_frame_pop ? nullptr : self->GetException(),
- /* from_code= */ false,
- DeoptimizationMethodType::kDefault);
-
- // Set special exception to cause deoptimization.
- self->SetException(Thread::GetDeoptimizationException());
+ if (UNLIKELY(instr->ShouldDeoptimizeCaller(self, sp))) {
+ ArtMethod* caller = QuickArgumentVisitor::GetOuterMethod(sp);
+ uintptr_t caller_pc = QuickArgumentVisitor::GetCallingPc(sp);
+ DCHECK(Runtime::Current()->IsAsyncDeoptimizeable(caller, caller_pc));
+ DCHECK(caller != nullptr);
+ VLOG(deopt) << "Forcing deoptimization on return from method " << method->PrettyMethod()
+ << " to " << caller->PrettyMethod() << (force_frame_pop ? " for frame-pop" : "");
+ DCHECK(!force_frame_pop || result.GetJ() == 0) << "Force frame pop should have no result.";
+ if (force_frame_pop && self->GetException() != nullptr) {
+ LOG(WARNING) << "Suppressing exception for instruction-retry: "
+ << self->GetException()->Dump();
}
+ DCHECK(self->GetException() != Thread::GetDeoptimizationException());
+ // Push the context of the deoptimization stack so we can restore the return value and the
+ // exception before executing the deoptimized frames.
+ self->PushDeoptimizationContext(result,
+ shorty[0] == 'L' || shorty[0] == '[', /* class or array */
+ force_frame_pop ? nullptr : self->GetException(),
+ /* from_code= */ false,
+ DeoptimizationMethodType::kDefault);
+
+ // Set special exception to cause deoptimization.
+ self->SetException(Thread::GetDeoptimizationException());
}
// No need to restore the args since the method has already been run by the interpreter.
@@ -862,7 +855,6 @@
instr->MethodEnterEvent(soa.Self(), proxy_method);
if (soa.Self()->IsExceptionPending()) {
instr->MethodUnwindEvent(self,
- soa.Decode<mirror::Object>(rcvr_jobj),
proxy_method,
0);
return 0;
@@ -872,7 +864,6 @@
if (soa.Self()->IsExceptionPending()) {
if (instr->HasMethodUnwindListeners()) {
instr->MethodUnwindEvent(self,
- soa.Decode<mirror::Object>(rcvr_jobj),
proxy_method,
0);
}
@@ -1037,7 +1028,7 @@
<< "Proxy method " << method->PrettyMethod()
<< " (declaring class: " << method->GetDeclaringClass()->PrettyClass() << ")"
<< " should not hit instrumentation entrypoint.";
- DCHECK(!instrumentation->IsDeoptimized(method));
+ DCHECK(!instrumentation->IsDeoptimized(method)) << method->PrettyMethod();
// This will get the entry point either from the oat file, the JIT or the appropriate bridge
// method if none of those can be found.
result = instrumentation->GetCodeForInvoke(method);
@@ -1065,6 +1056,7 @@
}
}
+ DCHECK(!method->IsRuntimeMethod());
instrumentation->PushInstrumentationStackFrame(self,
is_static ? nullptr : h_object.Get(),
method,
@@ -1379,6 +1371,11 @@
success = linker->EnsureInitialized(soa.Self(), h_called_class, true, true);
}
if (success) {
+ // When the clinit check is at entry of the AOT/nterp code, we do the clinit check
+ // before doing the suspend check. To ensure the code sees the latest
+ // version of the class (the code doesn't do a read barrier to reduce
+ // size), do a suspend check now.
+ self->CheckSuspend();
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
// Check if we need instrumented code here. Since resolution stubs could suspend, it is
// possible that we instrumented the entry points after we started executing the resolution
@@ -1944,7 +1941,7 @@
// The declaring class must be marked.
auto* declaring_class = reinterpret_cast<mirror::CompressedReference<mirror::Class>*>(
method->GetDeclaringClassAddressWithoutBarrier());
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
artJniReadBarrier(method);
}
sm_.AdvancePointer(declaring_class);
@@ -2091,7 +2088,7 @@
}
// Fix up managed-stack things in Thread. After this we can walk the stack.
- self->SetTopOfStackTagged(managed_sp);
+ self->SetTopOfStackGenericJniTagged(managed_sp);
self->VerifyStack();
@@ -2117,6 +2114,14 @@
}
}
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ if (UNLIKELY(instr->AreExitStubsInstalled() && Runtime::Current()->IsJavaDebuggable())) {
+ instr->MethodEnterEvent(self, called);
+ if (self->IsExceptionPending()) {
+ return nullptr;
+ }
+ }
+
// Skip calling `artJniMethodStart()` for @CriticalNative and @FastNative.
if (LIKELY(normal_native)) {
// Start JNI.
@@ -2185,7 +2190,7 @@
// anything that requires a mutator lock before that would cause problems as GC may have the
// exclusive mutator lock and may be moving objects, etc.
ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
- DCHECK(self->GetManagedStack()->GetTopQuickFrameTag());
+ DCHECK(self->GetManagedStack()->GetTopQuickFrameGenericJniTag());
uint32_t* sp32 = reinterpret_cast<uint32_t*>(sp);
ArtMethod* called = *sp;
uint32_t cookie = *(sp32 - 1);
@@ -2274,12 +2279,18 @@
uint32_t shorty_len;
const char* shorty = dex_file->GetMethodShorty(dex_file->GetMethodId(method_idx), &shorty_len);
{
- // Remember the args in case a GC happens in FindMethodFromCode.
+ // Remember the args in case a GC happens in FindMethodToCall.
ScopedObjectAccessUnchecked soa(self->GetJniEnv());
RememberForGcArgumentVisitor visitor(sp, type == kStatic, shorty, shorty_len, &soa);
visitor.VisitArguments();
- method = FindMethodFromCode<type, /*access_check=*/true>(
- method_idx, &this_object, caller_method, self);
+
+ uint32_t dex_pc = QuickArgumentVisitor::GetCallingDexPc(sp);
+ CodeItemInstructionAccessor accessor(caller_method->DexInstructions());
+ CHECK_LT(dex_pc, accessor.InsnsSizeInCodeUnits());
+ const Instruction& instr = accessor.InstructionAt(dex_pc);
+ bool string_init = false;
+ method = FindMethodToCall<type>(self, caller_method, &this_object, instr, &string_init);
+
visitor.FixupReferences();
}
@@ -2589,6 +2600,10 @@
// Pop transition record.
self->PopManagedStackFragment(fragment);
+ bool is_ref = (shorty[0] == 'L');
+ Runtime::Current()->GetInstrumentation()->PushDeoptContextIfNeeded(
+ self, DeoptimizationMethodType::kDefault, is_ref, result);
+
return result.GetJ();
}
@@ -2647,9 +2662,20 @@
// Pop transition record.
self->PopManagedStackFragment(fragment);
+ bool is_ref = (shorty[0] == 'L');
+ Runtime::Current()->GetInstrumentation()->PushDeoptContextIfNeeded(
+ self, DeoptimizationMethodType::kDefault, is_ref, result);
+
return result.GetJ();
}
+extern "C" void artJniMethodEntryHook(Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ ArtMethod* method = *self->GetManagedStack()->GetTopQuickFrame();
+ instr->MethodEnterEvent(self, method);
+}
+
extern "C" void artMethodEntryHook(ArtMethod* method, Thread* self, ArtMethod** sp ATTRIBUTE_UNUSED)
REQUIRES_SHARED(Locks::mutator_lock_) {
instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
@@ -2662,11 +2688,19 @@
}
}
-extern "C" int artMethodExitHook(Thread* self,
+extern "C" void artMethodExitHook(Thread* self,
ArtMethod* method,
uint64_t* gpr_result,
uint64_t* fpr_result)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ // For GenericJniTrampolines we call artMethodExitHook even for non debuggable runtimes though we
+ // still install instrumentation stubs. So just return early here so we don't call method exit
+ // twice. In all other cases (JITed JNI stubs / JITed code) we only call this for debuggable
+ // runtimes.
+ if (!Runtime::Current()->IsJavaDebuggable()) {
+ return;
+ }
+
DCHECK_EQ(reinterpret_cast<uintptr_t>(self), reinterpret_cast<uintptr_t>(Thread::Current()));
CHECK(gpr_result != nullptr);
CHECK(fpr_result != nullptr);
@@ -2677,7 +2711,7 @@
instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
DCHECK(instr->AreExitStubsInstalled());
bool is_ref;
- JValue return_value = instr->GetReturnValue(self, method, &is_ref, gpr_result, fpr_result);
+ JValue return_value = instr->GetReturnValue(method, &is_ref, gpr_result, fpr_result);
bool deoptimize = false;
{
StackHandleScope<1> hs(self);
@@ -2692,11 +2726,12 @@
// back to an upcall.
NthCallerVisitor visitor(self, 1, /*include_runtime_and_upcalls=*/false);
visitor.WalkStack(true);
- deoptimize = instr->ShouldDeoptimizeMethod(self, visitor);
+ deoptimize = instr->ShouldDeoptimizeCaller(self, visitor);
// If we need a deoptimization MethodExitEvent will be called by the interpreter when it
- // re-executes the return instruction.
- if (!deoptimize) {
+ // re-executes the return instruction. For native methods we have to process method exit
+ // events here since deoptimization just removes the native frame.
+ if (!deoptimize || method->IsNative()) {
instr->MethodExitEvent(self,
method,
/* frame= */ {},
@@ -2711,17 +2746,22 @@
}
if (self->IsExceptionPending() || self->ObserveAsyncException()) {
- return 1;
+ // The exception was thrown from the method exit callback. We should not call method unwind
+ // callbacks for this case.
+ self->QuickDeliverException(/* is_method_exit_exception= */ true);
+ UNREACHABLE();
}
if (deoptimize) {
DeoptimizationMethodType deopt_method_type = instr->GetDeoptimizationMethodType(method);
- self->PushDeoptimizationContext(return_value, is_ref, nullptr, false, deopt_method_type);
+ self->PushDeoptimizationContext(return_value,
+ is_ref,
+ self->GetException(),
+ false,
+ deopt_method_type);
artDeoptimize(self);
UNREACHABLE();
}
-
- return 0;
}
} // namespace art
diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc
index 240ecbd..2cd58db 100644
--- a/runtime/entrypoints_order_test.cc
+++ b/runtime/entrypoints_order_test.cc
@@ -225,7 +225,9 @@
pJniUnlockObject, sizeof(void*));
EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pJniUnlockObject,
pQuickGenericJniTrampoline, sizeof(void*));
- EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pQuickGenericJniTrampoline, pLockObject, sizeof(void*));
+ EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pQuickGenericJniTrampoline,
+ pJniMethodEntryHook, sizeof(void*));
+ EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pJniMethodEntryHook, pLockObject, sizeof(void*));
EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pLockObject, pUnlockObject, sizeof(void*));
EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pUnlockObject, pCmpgDouble, sizeof(void*));
EXPECT_OFFSET_DIFFNP(QuickEntryPoints, pCmpgDouble, pCmpgFloat, sizeof(void*));
diff --git a/runtime/exec_utils.cc b/runtime/exec_utils.cc
index 463d458..6e2a5b4 100644
--- a/runtime/exec_utils.cc
+++ b/runtime/exec_utils.cc
@@ -16,22 +16,32 @@
#include "exec_utils.h"
+#include <poll.h>
#include <sys/types.h>
#include <sys/wait.h>
+#include <unistd.h>
+
+#include "base/macros.h"
+
+#ifdef __BIONIC__
+#include <sys/pidfd.h>
+#endif
+
+#include <cstdint>
#include <string>
#include <vector>
+#include "android-base/scopeguard.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
-
#include "runtime.h"
namespace art {
-using android::base::StringPrintf;
-
namespace {
+using ::android::base::StringPrintf;
+
std::string ToCommandLine(const std::vector<std::string>& args) {
return android::base::Join(args, ' ');
}
@@ -40,7 +50,7 @@
// 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.
-pid_t ExecWithoutWait(std::vector<std::string>& arg_vector) {
+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();
std::vector<char*> args;
@@ -65,110 +75,124 @@
} else {
execve(program, &args[0], envp);
}
- PLOG(ERROR) << "Failed to execve(" << ToCommandLine(arg_vector) << ")";
- // _exit to avoid atexit handlers in child.
- _exit(1);
+ // This should be regarded as a crash rather than a normal return.
+ PLOG(FATAL) << "Failed to execute (" << ToCommandLine(arg_vector) << ")";
+ UNREACHABLE();
+ } else if (pid == -1) {
+ *error_msg = StringPrintf("Failed to execute (%s) because fork failed: %s",
+ ToCommandLine(arg_vector).c_str(),
+ strerror(errno));
+ return -1;
} else {
return pid;
}
}
-} // namespace
-
-int ExecAndReturnCode(std::vector<std::string>& arg_vector, std::string* error_msg) {
- pid_t pid = ExecWithoutWait(arg_vector);
- if (pid == -1) {
- *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s",
- ToCommandLine(arg_vector).c_str(), strerror(errno));
- return -1;
- }
-
- // wait for subprocess to finish
+int WaitChild(pid_t pid, const std::vector<std::string>& arg_vector, std::string* error_msg) {
int status = -1;
pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
if (got_pid != pid) {
- *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: "
- "wanted %d, got %d: %s",
- ToCommandLine(arg_vector).c_str(), pid, got_pid, strerror(errno));
+ *error_msg =
+ StringPrintf("Failed to execute (%s) because waitpid failed: wanted %d, got %d: %s",
+ ToCommandLine(arg_vector).c_str(),
+ pid,
+ got_pid,
+ strerror(errno));
return -1;
}
- if (WIFEXITED(status)) {
- return WEXITSTATUS(status);
+ if (!WIFEXITED(status)) {
+ *error_msg =
+ StringPrintf("Failed to execute (%s) because the child process is terminated by signal %d",
+ ToCommandLine(arg_vector).c_str(),
+ WTERMSIG(status));
+ return -1;
}
- return -1;
+ return WEXITSTATUS(status);
}
-int ExecAndReturnCode(std::vector<std::string>& arg_vector,
- time_t timeout_secs,
+int WaitChildWithTimeout(pid_t pid,
+ const std::vector<std::string>& arg_vector,
+ int timeout_sec,
+ bool* timed_out,
+ std::string* error_msg) {
+ auto cleanup = android::base::make_scope_guard([&]() {
+ kill(pid, SIGKILL);
+ std::string ignored_error_msg;
+ WaitChild(pid, arg_vector, &ignored_error_msg);
+ });
+
+#ifdef __BIONIC__
+ int pidfd = pidfd_open(pid, /*flags=*/0);
+#else
+ // There is no glibc wrapper for pidfd_open.
+ constexpr int SYS_pidfd_open = 434;
+ int pidfd = syscall(SYS_pidfd_open, pid, /*flags=*/0);
+#endif
+ if (pidfd < 0) {
+ *error_msg = StringPrintf("pidfd_open failed for pid %d: %s", pid, strerror(errno));
+ return -1;
+ }
+
+ struct pollfd pfd;
+ pfd.fd = pidfd;
+ pfd.events = POLLIN;
+ int poll_ret = TEMP_FAILURE_RETRY(poll(&pfd, /*nfds=*/1, timeout_sec * 1000));
+
+ close(pidfd);
+
+ if (poll_ret < 0) {
+ *error_msg = StringPrintf("poll failed for pid %d: %s", pid, strerror(errno));
+ return -1;
+ }
+ if (poll_ret == 0) {
+ *timed_out = true;
+ *error_msg = StringPrintf("Child process %d timed out after %ds. Killing it", pid, timeout_sec);
+ return -1;
+ }
+
+ cleanup.Disable();
+ return WaitChild(pid, arg_vector, error_msg);
+}
+
+} // namespace
+
+int ExecAndReturnCode(const std::vector<std::string>& arg_vector, std::string* error_msg) {
+ // Start subprocess.
+ pid_t pid = ExecWithoutWait(arg_vector, error_msg);
+ if (pid == -1) {
+ return -1;
+ }
+
+ // Wait for subprocess to finish.
+ return WaitChild(pid, arg_vector, error_msg);
+}
+
+int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+ int timeout_sec,
bool* timed_out,
std::string* error_msg) {
*timed_out = false;
// Start subprocess.
- pid_t pid = ExecWithoutWait(arg_vector);
+ pid_t pid = ExecWithoutWait(arg_vector, error_msg);
if (pid == -1) {
- *error_msg = StringPrintf("Failed to execv(%s) because fork failed: %s",
- ToCommandLine(arg_vector).c_str(), strerror(errno));
return -1;
}
- // Add SIGCHLD to the signal set.
- sigset_t child_mask, original_mask;
- sigemptyset(&child_mask);
- sigaddset(&child_mask, SIGCHLD);
- if (sigprocmask(SIG_BLOCK, &child_mask, &original_mask) == -1) {
- *error_msg = StringPrintf("Failed to set sigprocmask(): %s", strerror(errno));
- return -1;
- }
-
- // Wait for a SIGCHLD notification.
- errno = 0;
- timespec ts = {timeout_secs, 0};
- int wait_result = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
- int wait_errno = errno;
-
- // Restore the original signal set.
- if (sigprocmask(SIG_SETMASK, &original_mask, nullptr) == -1) {
- *error_msg = StringPrintf("Fail to restore sigprocmask(): %s", strerror(errno));
- if (wait_result == 0) {
- return -1;
- }
- }
-
- // Having restored the signal set, see if we need to terminate the subprocess.
- if (wait_result == -1) {
- if (wait_errno == EAGAIN) {
- *error_msg = "Timed out.";
- *timed_out = true;
- } else {
- *error_msg = StringPrintf("Failed to sigtimedwait(): %s", strerror(errno));
- }
- if (kill(pid, SIGKILL) != 0) {
- PLOG(ERROR) << "Failed to kill() subprocess: ";
- }
- }
-
// Wait for subprocess to finish.
- int status = -1;
- pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
- if (got_pid != pid) {
- *error_msg = StringPrintf("Failed after fork for execv(%s) because waitpid failed: "
- "wanted %d, got %d: %s",
- ToCommandLine(arg_vector).c_str(), pid, got_pid, strerror(errno));
- return -1;
- }
- if (WIFEXITED(status)) {
- return WEXITSTATUS(status);
- }
- return -1;
+ return WaitChildWithTimeout(pid, arg_vector, timeout_sec, timed_out, error_msg);
}
-
-bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
+bool Exec(const std::vector<std::string>& arg_vector, std::string* error_msg) {
int status = ExecAndReturnCode(arg_vector, error_msg);
- if (status != 0) {
- *error_msg = StringPrintf("Failed execv(%s) because non-0 exit status",
- ToCommandLine(arg_vector).c_str());
+ if (status < 0) {
+ // Internal error. The error message is already set.
+ return false;
+ }
+ if (status > 0) {
+ *error_msg =
+ StringPrintf("Failed to execute (%s) because the child process returns non-zero exit code",
+ ToCommandLine(arg_vector).c_str());
return false;
}
return true;
diff --git a/runtime/exec_utils.h b/runtime/exec_utils.h
index 7ce0a9c..ff90ebd 100644
--- a/runtime/exec_utils.h
+++ b/runtime/exec_utils.h
@@ -29,13 +29,13 @@
// of the runtime (Runtime::Current()) was started. If no instance of the runtime was started, it
// will use the current environment settings.
-bool Exec(std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg);
-int ExecAndReturnCode(std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg);
+bool Exec(const std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg);
+int ExecAndReturnCode(const std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg);
// Execute the command specified in `argv_vector` in a subprocess with a timeout.
// Returns the process exit code on success, -1 otherwise.
-int ExecAndReturnCode(std::vector<std::string>& arg_vector,
- time_t timeout_secs,
+int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+ int timeout_sec,
/*out*/ bool* timed_out,
/*out*/ std::string* error_msg);
@@ -44,20 +44,21 @@
public:
virtual ~ExecUtils() = default;
- virtual bool Exec(std::vector<std::string>& arg_vector, /*out*/ std::string* error_msg) const {
+ virtual bool Exec(const std::vector<std::string>& arg_vector,
+ /*out*/ std::string* error_msg) const {
return art::Exec(arg_vector, error_msg);
}
- virtual int ExecAndReturnCode(std::vector<std::string>& arg_vector,
+ virtual int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
/*out*/ std::string* error_msg) const {
return art::ExecAndReturnCode(arg_vector, error_msg);
}
- virtual int ExecAndReturnCode(std::vector<std::string>& arg_vector,
- time_t timeout_secs,
+ virtual int ExecAndReturnCode(const std::vector<std::string>& arg_vector,
+ int timeout_sec,
/*out*/ bool* timed_out,
/*out*/ std::string* error_msg) const {
- return art::ExecAndReturnCode(arg_vector, timeout_secs, timed_out, error_msg);
+ return art::ExecAndReturnCode(arg_vector, timeout_sec, timed_out, error_msg);
}
};
diff --git a/runtime/exec_utils_test.cc b/runtime/exec_utils_test.cc
index dc789aa..aa53739 100644
--- a/runtime/exec_utils_test.cc
+++ b/runtime/exec_utils_test.cc
@@ -16,16 +16,34 @@
#include "exec_utils.h"
+#include <unistd.h>
+
#include "android-base/stringprintf.h"
#include "base/file_utils.h"
#include "base/memory_tool.h"
#include "common_runtime_test.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
namespace art {
std::string PrettyArguments(const char* signature);
std::string PrettyReturnType(const char* signature);
+bool IsPidfdSupported() {
+#ifdef __BIONIC__
+ return true;
+#else
+ constexpr int SYS_pidfd_open = 434;
+ int pidfd = syscall(SYS_pidfd_open, getpid(), /*flags=*/0);
+ if (pidfd < 0) {
+ return false;
+ }
+ close(pidfd);
+ return true;
+#endif
+}
+
class ExecUtilsTest : public CommonRuntimeTest {};
TEST_F(ExecUtilsTest, ExecSuccess) {
@@ -44,9 +62,6 @@
}
TEST_F(ExecUtilsTest, ExecError) {
- // This will lead to error messages in the log.
- ScopedLogSeverity sls(LogSeverity::FATAL);
-
std::vector<std::string> command;
command.push_back("bogus");
std::string error_msg;
@@ -115,6 +130,10 @@
}
TEST_F(ExecUtilsTest, ExecTimeout) {
+ if (!IsPidfdSupported()) {
+ GTEST_SKIP() << "pidfd not supported";
+ }
+
static constexpr int kSleepSeconds = 5;
static constexpr int kWaitSeconds = 1;
std::vector<std::string> command = SleepCommand(kSleepSeconds);
@@ -125,6 +144,10 @@
}
TEST_F(ExecUtilsTest, ExecNoTimeout) {
+ if (!IsPidfdSupported()) {
+ GTEST_SKIP() << "pidfd not supported";
+ }
+
static constexpr int kSleepSeconds = 1;
static constexpr int kWaitSeconds = 5;
std::vector<std::string> command = SleepCommand(kSleepSeconds);
@@ -134,4 +157,15 @@
EXPECT_FALSE(timed_out);
}
+TEST_F(ExecUtilsTest, ExecTimeoutNotSupported) {
+ if (IsPidfdSupported()) {
+ GTEST_SKIP() << "pidfd supported";
+ }
+
+ std::string error_msg;
+ bool timed_out;
+ ASSERT_EQ(ExecAndReturnCode({"command"}, /*timeout_sec=*/0, &timed_out, &error_msg), -1);
+ EXPECT_THAT(error_msg, testing::HasSubstr("pidfd_open failed for pid"));
+}
+
} // namespace art
diff --git a/runtime/gc/accounting/atomic_stack.h b/runtime/gc/accounting/atomic_stack.h
index 5e6bd88..a90a319 100644
--- a/runtime/gc/accounting/atomic_stack.h
+++ b/runtime/gc/accounting/atomic_stack.h
@@ -130,6 +130,35 @@
}
}
+ // Bump the back index by the given number of slots. Returns false if this
+ // operation will overflow the stack. New elements should be written
+ // to [*start_address, *end_address).
+ bool BumpBack(size_t num_slots,
+ StackReference<T>** start_address,
+ StackReference<T>** end_address)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (kIsDebugBuild) {
+ debug_is_sorted_ = false;
+ }
+ const int32_t index = back_index_.load(std::memory_order_relaxed);
+ const int32_t new_index = index + num_slots;
+ if (UNLIKELY(static_cast<size_t>(new_index) >= growth_limit_)) {
+ // Stack overflow.
+ return false;
+ }
+ back_index_.store(new_index, std::memory_order_relaxed);
+ *start_address = begin_ + index;
+ *end_address = begin_ + new_index;
+ if (kIsDebugBuild) {
+ // Check the memory is zero.
+ for (int32_t i = index; i < new_index; i++) {
+ DCHECK_EQ(begin_[i].AsMirrorPtr(), static_cast<T*>(nullptr))
+ << "i=" << i << " index=" << index << " new_index=" << new_index;
+ }
+ }
+ return true;
+ }
+
void PushBack(T* value) REQUIRES_SHARED(Locks::mutator_lock_) {
if (kIsDebugBuild) {
debug_is_sorted_ = false;
@@ -144,8 +173,16 @@
DCHECK_GT(back_index_.load(std::memory_order_relaxed),
front_index_.load(std::memory_order_relaxed));
// Decrement the back index non atomically.
- back_index_.store(back_index_.load(std::memory_order_relaxed) - 1, std::memory_order_relaxed);
- return begin_[back_index_.load(std::memory_order_relaxed)].AsMirrorPtr();
+ const int32_t index = back_index_.load(std::memory_order_relaxed) - 1;
+ back_index_.store(index, std::memory_order_relaxed);
+ T* ret = begin_[index].AsMirrorPtr();
+ // In debug builds we expect the stack elements to be null, which may not
+ // always be the case if the stack is being reused without resetting it
+ // in-between.
+ if (kIsDebugBuild) {
+ begin_[index].Clear();
+ }
+ return ret;
}
// Take an item from the front of the stack.
diff --git a/runtime/gc/accounting/bitmap.cc b/runtime/gc/accounting/bitmap.cc
index 37646b3..bd10958 100644
--- a/runtime/gc/accounting/bitmap.cc
+++ b/runtime/gc/accounting/bitmap.cc
@@ -21,6 +21,7 @@
#include "base/bit_utils.h"
#include "base/mem_map.h"
#include "card_table.h"
+#include "gc/collector/mark_compact.h"
#include "jit/jit_memory_region.h"
namespace art {
@@ -98,6 +99,7 @@
template class MemoryRangeBitmap<CardTable::kCardSize>;
template class MemoryRangeBitmap<jit::kJitCodeAccountingBytes>;
+template class MemoryRangeBitmap<collector::MarkCompact::kAlignment>;
} // namespace accounting
} // namespace gc
diff --git a/runtime/gc/accounting/bitmap.h b/runtime/gc/accounting/bitmap.h
index 68f2d04..b050442 100644
--- a/runtime/gc/accounting/bitmap.h
+++ b/runtime/gc/accounting/bitmap.h
@@ -81,7 +81,7 @@
void CopyFrom(Bitmap* source_bitmap);
// Starting address of our internal storage.
- uintptr_t* Begin() {
+ uintptr_t* Begin() const {
return bitmap_begin_;
}
@@ -98,7 +98,7 @@
std::string Dump() const;
protected:
- static constexpr size_t kBitsPerBitmapWord = sizeof(uintptr_t) * kBitsPerByte;
+ static constexpr size_t kBitsPerBitmapWord = kBitsPerIntPtrT;
Bitmap(MemMap&& mem_map, size_t bitmap_size);
~Bitmap();
@@ -109,7 +109,9 @@
template<bool kSetBit>
ALWAYS_INLINE bool ModifyBit(uintptr_t bit_index);
- // Backing storage for bitmap.
+ // Backing storage for bitmap. This is interpreted as an array of
+ // kBitsPerBitmapWord-sized integers, with bits assigned in each word little
+ // endian first.
MemMap mem_map_;
// This bitmap itself, word sized for efficiency in scanning.
@@ -122,7 +124,7 @@
DISALLOW_IMPLICIT_CONSTRUCTORS(Bitmap);
};
-// One bit per kAlignment in range (start, end]
+// One bit per kAlignment in range [start, end)
template<size_t kAlignment>
class MemoryRangeBitmap : public Bitmap {
public:
@@ -138,7 +140,7 @@
// End of the memory range that the bitmap covers.
ALWAYS_INLINE uintptr_t CoverEnd() const {
- return cover_end_;
+ return cover_begin_ + kAlignment * BitmapSize();
}
// Return the address associated with a bit index.
@@ -148,41 +150,53 @@
return addr;
}
- // Return the bit index associated with an address .
- ALWAYS_INLINE uintptr_t BitIndexFromAddr(uintptr_t addr) const {
- DCHECK(HasAddress(addr)) << CoverBegin() << " <= " << addr << " < " << CoverEnd();
+ ALWAYS_INLINE uintptr_t BitIndexFromAddrUnchecked(uintptr_t addr) const {
return (addr - CoverBegin()) / kAlignment;
}
+ // Return the bit index associated with an address .
+ ALWAYS_INLINE uintptr_t BitIndexFromAddr(uintptr_t addr) const {
+ uintptr_t result = BitIndexFromAddrUnchecked(addr);
+ DCHECK(result < BitmapSize()) << CoverBegin() << " <= " << addr << " < " << CoverEnd();
+ return result;
+ }
+
ALWAYS_INLINE bool HasAddress(const uintptr_t addr) const {
- return cover_begin_ <= addr && addr < cover_end_;
+ // Don't use BitIndexFromAddr() here as the addr passed to this function
+ // could be outside the range. If addr < cover_begin_, then the result
+ // underflows to some very large value past the end of the bitmap.
+ // Therefore, all operations are unsigned here.
+ bool ret = (addr - CoverBegin()) / kAlignment < BitmapSize();
+ if (ret) {
+ DCHECK(CoverBegin() <= addr && addr < CoverEnd())
+ << CoverBegin() << " <= " << addr << " < " << CoverEnd();
+ }
+ return ret;
}
ALWAYS_INLINE bool Set(uintptr_t addr) {
return SetBit(BitIndexFromAddr(addr));
}
- ALWAYS_INLINE bool Clear(size_t addr) {
+ ALWAYS_INLINE bool Clear(uintptr_t addr) {
return ClearBit(BitIndexFromAddr(addr));
}
- ALWAYS_INLINE bool Test(size_t addr) const {
+ ALWAYS_INLINE bool Test(uintptr_t addr) const {
return TestBit(BitIndexFromAddr(addr));
}
// Returns true if the object was previously set.
- ALWAYS_INLINE bool AtomicTestAndSet(size_t addr) {
+ ALWAYS_INLINE bool AtomicTestAndSet(uintptr_t addr) {
return AtomicTestAndSetBit(BitIndexFromAddr(addr));
}
private:
MemoryRangeBitmap(MemMap&& mem_map, uintptr_t begin, size_t num_bits)
: Bitmap(std::move(mem_map), num_bits),
- cover_begin_(begin),
- cover_end_(begin + kAlignment * num_bits) {}
+ cover_begin_(begin) {}
uintptr_t const cover_begin_;
- uintptr_t const cover_end_;
DISALLOW_IMPLICIT_CONSTRUCTORS(MemoryRangeBitmap);
};
diff --git a/runtime/gc/accounting/mod_union_table.cc b/runtime/gc/accounting/mod_union_table.cc
index b4026fc..4a84799 100644
--- a/runtime/gc/accounting/mod_union_table.cc
+++ b/runtime/gc/accounting/mod_union_table.cc
@@ -388,6 +388,11 @@
void ModUnionTableReferenceCache::VisitObjects(ObjectCallback callback, void* arg) {
CardTable* const card_table = heap_->GetCardTable();
ContinuousSpaceBitmap* live_bitmap = space_->GetLiveBitmap();
+ // Use an unordered_set for constant time search of card in the second loop.
+ // We don't want to change cleared_cards_ to unordered so that traversals are
+ // sequential in address order.
+ // TODO: Optimize this.
+ std::unordered_set<const uint8_t*> card_lookup_map;
for (uint8_t* card : cleared_cards_) {
uintptr_t start = reinterpret_cast<uintptr_t>(card_table->AddrFromCard(card));
uintptr_t end = start + CardTable::kCardSize;
@@ -396,10 +401,13 @@
[callback, arg](mirror::Object* obj) {
callback(obj, arg);
});
+ card_lookup_map.insert(card);
}
- // This may visit the same card twice, TODO avoid this.
for (const auto& pair : references_) {
const uint8_t* card = pair.first;
+ if (card_lookup_map.find(card) != card_lookup_map.end()) {
+ continue;
+ }
uintptr_t start = reinterpret_cast<uintptr_t>(card_table->AddrFromCard(card));
uintptr_t end = start + CardTable::kCardSize;
live_bitmap->VisitMarkedRange(start,
diff --git a/runtime/gc/accounting/space_bitmap-inl.h b/runtime/gc/accounting/space_bitmap-inl.h
index d460e00..e7825e6 100644
--- a/runtime/gc/accounting/space_bitmap-inl.h
+++ b/runtime/gc/accounting/space_bitmap-inl.h
@@ -64,7 +64,44 @@
}
template<size_t kAlignment>
-template<typename Visitor>
+inline mirror::Object* SpaceBitmap<kAlignment>::FindPrecedingObject(uintptr_t visit_begin,
+ uintptr_t visit_end) const {
+ // Covers [visit_end, visit_begin].
+ visit_end = std::max(heap_begin_, visit_end);
+ DCHECK_LE(visit_end, visit_begin);
+ DCHECK_LT(visit_begin, HeapLimit());
+
+ const uintptr_t offset_start = visit_begin - heap_begin_;
+ const uintptr_t offset_end = visit_end - heap_begin_;
+ uintptr_t index_start = OffsetToIndex(offset_start);
+ const uintptr_t index_end = OffsetToIndex(offset_end);
+
+ // Start with the right edge
+ uintptr_t word = bitmap_begin_[index_start].load(std::memory_order_relaxed);
+ // visit_begin could be the first word of the object we are looking for.
+ const uintptr_t right_edge_mask = OffsetToMask(offset_start);
+ word &= right_edge_mask | (right_edge_mask - 1);
+ while (index_start > index_end) {
+ if (word != 0) {
+ const uintptr_t ptr_base = IndexToOffset(index_start) + heap_begin_;
+ size_t pos_leading_set_bit = kBitsPerIntPtrT - CLZ(word) - 1;
+ return reinterpret_cast<mirror::Object*>(ptr_base + pos_leading_set_bit * kAlignment);
+ }
+ word = bitmap_begin_[--index_start].load(std::memory_order_relaxed);
+ }
+
+ word &= ~(OffsetToMask(offset_end) - 1);
+ if (word != 0) {
+ const uintptr_t ptr_base = IndexToOffset(index_end) + heap_begin_;
+ size_t pos_leading_set_bit = kBitsPerIntPtrT - CLZ(word) - 1;
+ return reinterpret_cast<mirror::Object*>(ptr_base + pos_leading_set_bit * kAlignment);
+ } else {
+ return nullptr;
+ }
+}
+
+template<size_t kAlignment>
+template<bool kVisitOnce, typename Visitor>
inline void SpaceBitmap<kAlignment>::VisitMarkedRange(uintptr_t visit_begin,
uintptr_t visit_end,
Visitor&& visitor) const {
@@ -114,6 +151,9 @@
const size_t shift = CTZ(left_edge);
mirror::Object* obj = reinterpret_cast<mirror::Object*>(ptr_base + shift * kAlignment);
visitor(obj);
+ if (kVisitOnce) {
+ return;
+ }
left_edge ^= (static_cast<uintptr_t>(1)) << shift;
} while (left_edge != 0);
}
@@ -128,6 +168,9 @@
const size_t shift = CTZ(w);
mirror::Object* obj = reinterpret_cast<mirror::Object*>(ptr_base + shift * kAlignment);
visitor(obj);
+ if (kVisitOnce) {
+ return;
+ }
w ^= (static_cast<uintptr_t>(1)) << shift;
} while (w != 0);
}
@@ -155,6 +198,9 @@
const size_t shift = CTZ(right_edge);
mirror::Object* obj = reinterpret_cast<mirror::Object*>(ptr_base + shift * kAlignment);
visitor(obj);
+ if (kVisitOnce) {
+ return;
+ }
right_edge ^= (static_cast<uintptr_t>(1)) << shift;
} while (right_edge != 0);
}
diff --git a/runtime/gc/accounting/space_bitmap.cc b/runtime/gc/accounting/space_bitmap.cc
index 3c5688d..a0458d2 100644
--- a/runtime/gc/accounting/space_bitmap.cc
+++ b/runtime/gc/accounting/space_bitmap.cc
@@ -16,6 +16,9 @@
#include "space_bitmap-inl.h"
+#include <iomanip>
+#include <sstream>
+
#include "android-base/stringprintf.h"
#include "art_field-inl.h"
@@ -113,6 +116,37 @@
reinterpret_cast<void*>(HeapLimit()));
}
+template <size_t kAlignment>
+std::string SpaceBitmap<kAlignment>::DumpMemAround(mirror::Object* obj) const {
+ uintptr_t addr = reinterpret_cast<uintptr_t>(obj);
+ DCHECK_GE(addr, heap_begin_);
+ DCHECK(HasAddress(obj)) << obj;
+ const uintptr_t offset = addr - heap_begin_;
+ const size_t index = OffsetToIndex(offset);
+ const uintptr_t mask = OffsetToMask(offset);
+ size_t num_entries = bitmap_size_ / sizeof(uintptr_t);
+ DCHECK_LT(index, num_entries) << " bitmap_size_ = " << bitmap_size_;
+ Atomic<uintptr_t>* atomic_entry = &bitmap_begin_[index];
+ uintptr_t prev = 0;
+ uintptr_t next = 0;
+ if (index > 0) {
+ prev = (atomic_entry - 1)->load(std::memory_order_relaxed);
+ }
+ uintptr_t curr = atomic_entry->load(std::memory_order_relaxed);
+ if (index < num_entries - 1) {
+ next = (atomic_entry + 1)->load(std::memory_order_relaxed);
+ }
+ std::ostringstream oss;
+ oss << " offset: " << offset
+ << " index: " << index
+ << " mask: " << std::hex << std::setfill('0') << std::setw(16) << mask
+ << " words {" << std::hex << std::setfill('0') << std::setw(16) << prev
+ << ", " << std::hex << std::setfill('0') << std::setw(16) << curr
+ << ", " << std::hex <<std::setfill('0') << std::setw(16) << next
+ << "}";
+ return oss.str();
+}
+
template<size_t kAlignment>
void SpaceBitmap<kAlignment>::Clear() {
if (bitmap_begin_ != nullptr) {
diff --git a/runtime/gc/accounting/space_bitmap.h b/runtime/gc/accounting/space_bitmap.h
index 0d8ffa0..c87b31e 100644
--- a/runtime/gc/accounting/space_bitmap.h
+++ b/runtime/gc/accounting/space_bitmap.h
@@ -40,8 +40,8 @@
template<size_t kAlignment>
class SpaceBitmap {
public:
- typedef void ScanCallback(mirror::Object* obj, void* finger, void* arg);
- typedef void SweepCallback(size_t ptr_count, mirror::Object** ptrs, void* arg);
+ using ScanCallback = void(mirror::Object* obj, void* finger, void* arg);
+ using SweepCallback = void(size_t ptr_count, mirror::Object** ptrs, void* arg);
// Initialize a space bitmap so that it points to a bitmap large enough to cover a heap at
// heap_begin of heap_capacity bytes, where objects are guaranteed to be kAlignment-aligned.
@@ -131,10 +131,15 @@
}
}
- // Visit the live objects in the range [visit_begin, visit_end).
+ // Find first object while scanning bitmap backwards from visit_begin -> visit_end.
+ // Covers [visit_end, visit_begin] range.
+ mirror::Object* FindPrecedingObject(uintptr_t visit_begin, uintptr_t visit_end = 0) const;
+
+ // Visit the live objects in the range [visit_begin, visit_end). If kVisitOnce
+ // is true, then only the first live object will be visited.
// TODO: Use lock annotations when clang is fixed.
// REQUIRES(Locks::heap_bitmap_lock_) REQUIRES_SHARED(Locks::mutator_lock_);
- template <typename Visitor>
+ template <bool kVisitOnce = false, typename Visitor>
void VisitMarkedRange(uintptr_t visit_begin, uintptr_t visit_end, Visitor&& visitor) const
NO_THREAD_SAFETY_ANALYSIS;
@@ -202,6 +207,9 @@
std::string Dump() const;
+ // Dump three bitmap words around obj.
+ std::string DumpMemAround(mirror::Object* obj) const;
+
// Helper function for computing bitmap size based on a 64 bit capacity.
static size_t ComputeBitmapSize(uint64_t capacity);
static size_t ComputeHeapSize(uint64_t bitmap_bytes);
diff --git a/runtime/gc/allocation_record.cc b/runtime/gc/allocation_record.cc
index 7bcf375..9586e9d 100644
--- a/runtime/gc/allocation_record.cc
+++ b/runtime/gc/allocation_record.cc
@@ -59,6 +59,13 @@
}
void AllocRecordObjectMap::VisitRoots(RootVisitor* visitor) {
+ gc::Heap* const heap = Runtime::Current()->GetHeap();
+ // When we are compacting in userfaultfd GC, the class GC-roots are already
+ // updated in SweepAllocationRecords()->SweepClassObject().
+ if (heap->CurrentCollectorType() == gc::CollectorType::kCollectorTypeCMC
+ && heap->MarkCompactCollector()->IsCompacting(Thread::Current())) {
+ return;
+ }
CHECK_LE(recent_record_max_, alloc_record_max_);
BufferedRootVisitor<kDefaultBufferedRootCount> buffered_visitor(visitor, RootInfo(kRootDebugger));
size_t count = recent_record_max_;
@@ -92,7 +99,10 @@
mirror::Object* new_object = visitor->IsMarked(old_object);
DCHECK(new_object != nullptr);
if (UNLIKELY(old_object != new_object)) {
- klass = GcRoot<mirror::Class>(new_object->AsClass());
+ // We can't use AsClass() as it uses IsClass in a DCHECK, which expects
+ // the class' contents to be there. This is not the case in userfaultfd
+ // GC.
+ klass = GcRoot<mirror::Class>(ObjPtr<mirror::Class>::DownCast(new_object));
}
}
}
@@ -131,13 +141,13 @@
}
void AllocRecordObjectMap::AllowNewAllocationRecords() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
allow_new_record_ = true;
new_record_condition_.Broadcast(Thread::Current());
}
void AllocRecordObjectMap::DisallowNewAllocationRecords() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
allow_new_record_ = false;
}
@@ -230,8 +240,8 @@
// Since nobody seemed to really notice or care it might not be worth the trouble.
// Wait for GC's sweeping to complete and allow new records.
- while (UNLIKELY((!kUseReadBarrier && !allow_new_record_) ||
- (kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
+ while (UNLIKELY((!gUseReadBarrier && !allow_new_record_) ||
+ (gUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
// Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
// presence of threads blocking for weak ref access.
self->CheckEmptyCheckpointFromWeakRefAccess(Locks::alloc_tracker_lock_);
diff --git a/runtime/gc/allocator/dlmalloc.cc b/runtime/gc/allocator/art-dlmalloc.cc
similarity index 95%
rename from runtime/gc/allocator/dlmalloc.cc
rename to runtime/gc/allocator/art-dlmalloc.cc
index 79d4fbf..de0c85a 100644
--- a/runtime/gc/allocator/dlmalloc.cc
+++ b/runtime/gc/allocator/art-dlmalloc.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "dlmalloc.h"
+#include "art-dlmalloc.h"
#include <android-base/logging.h>
@@ -39,8 +39,8 @@
#pragma GCC diagnostic ignored "-Wstrict-aliasing"
#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
#pragma GCC diagnostic ignored "-Wexpansion-to-defined"
-#include "../../../external/dlmalloc/malloc.c"
-// Note: malloc.c uses a DEBUG define to drive debug code. This interferes with the DEBUG severity
+#include "dlmalloc.c" // NOLINT
+// Note: dlmalloc.c uses a DEBUG define to drive debug code. This interferes with the DEBUG severity
// of libbase, so undefine it now.
#undef DEBUG
#pragma GCC diagnostic pop
diff --git a/runtime/gc/allocator/dlmalloc.h b/runtime/gc/allocator/art-dlmalloc.h
similarity index 91%
rename from runtime/gc/allocator/dlmalloc.h
rename to runtime/gc/allocator/art-dlmalloc.h
index b12691a..296de72 100644
--- a/runtime/gc/allocator/dlmalloc.h
+++ b/runtime/gc/allocator/art-dlmalloc.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef ART_RUNTIME_GC_ALLOCATOR_DLMALLOC_H_
-#define ART_RUNTIME_GC_ALLOCATOR_DLMALLOC_H_
+#ifndef ART_RUNTIME_GC_ALLOCATOR_ART_DLMALLOC_H_
+#define ART_RUNTIME_GC_ALLOCATOR_ART_DLMALLOC_H_
#include <cstdint>
@@ -33,7 +33,7 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wredundant-decls"
#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
-#include "../../external/dlmalloc/malloc.h"
+#include "dlmalloc.h"
#pragma GCC diagnostic pop
// Callback for dlmalloc_inspect_all or mspace_inspect_all that will madvise(2) unused
@@ -58,4 +58,4 @@
} // namespace gc
} // namespace art
-#endif // ART_RUNTIME_GC_ALLOCATOR_DLMALLOC_H_
+#endif // ART_RUNTIME_GC_ALLOCATOR_ART_DLMALLOC_H_
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 0de62fe..f3c61e3 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -164,6 +164,9 @@
gc_tracing_throughput_hist_ = metrics->YoungGcTracingThroughput();
gc_throughput_avg_ = metrics->YoungGcThroughputAvg();
gc_tracing_throughput_avg_ = metrics->YoungGcTracingThroughputAvg();
+ gc_scanned_bytes_ = metrics->YoungGcScannedBytes();
+ gc_freed_bytes_ = metrics->YoungGcFreedBytes();
+ gc_duration_ = metrics->YoungGcDuration();
} else {
gc_time_histogram_ = metrics->FullGcCollectionTime();
metrics_gc_count_ = metrics->FullGcCount();
@@ -171,6 +174,9 @@
gc_tracing_throughput_hist_ = metrics->FullGcTracingThroughput();
gc_throughput_avg_ = metrics->FullGcThroughputAvg();
gc_tracing_throughput_avg_ = metrics->FullGcTracingThroughputAvg();
+ gc_scanned_bytes_ = metrics->FullGcScannedBytes();
+ gc_freed_bytes_ = metrics->FullGcFreedBytes();
+ gc_duration_ = metrics->FullGcDuration();
}
}
diff --git a/runtime/gc/collector/garbage_collector.cc b/runtime/gc/collector/garbage_collector.cc
index 80b3982..4efe48c 100644
--- a/runtime/gc/collector/garbage_collector.cc
+++ b/runtime/gc/collector/garbage_collector.cc
@@ -76,6 +76,9 @@
gc_tracing_throughput_hist_(nullptr),
gc_throughput_avg_(nullptr),
gc_tracing_throughput_avg_(nullptr),
+ gc_scanned_bytes_(nullptr),
+ gc_freed_bytes_(nullptr),
+ gc_duration_(nullptr),
cumulative_timings_(name),
pause_histogram_lock_("pause histogram lock", kDefaultMutexLevel, true),
is_transaction_active_(false),
@@ -189,15 +192,18 @@
RegisterPause(duration_ns);
}
total_time_ns_ += duration_ns;
- uint64_t total_pause_time = 0;
+ uint64_t total_pause_time_ns = 0;
for (uint64_t pause_time : current_iteration->GetPauseTimes()) {
MutexLock mu(self, pause_histogram_lock_);
pause_histogram_.AdjustAndAddValue(pause_time);
- total_pause_time += pause_time;
+ total_pause_time_ns += pause_time;
}
metrics::ArtMetrics* metrics = runtime->GetMetrics();
// Report STW pause time in microseconds.
- metrics->WorldStopTimeDuringGCAvg()->Add(total_pause_time / 1'000);
+ const uint64_t total_pause_time_us = total_pause_time_ns / 1'000;
+ metrics->WorldStopTimeDuringGCAvg()->Add(total_pause_time_us);
+ metrics->GcWorldStopTime()->Add(total_pause_time_us);
+ metrics->GcWorldStopCount()->AddOne();
// Report total collection time of all GCs put together.
metrics->TotalGcCollectionTime()->Add(NsToMs(duration_ns));
if (are_metrics_initialized_) {
@@ -216,6 +222,10 @@
throughput = current_iteration->GetEstimatedThroughput() / MB;
gc_throughput_histogram_->Add(throughput);
gc_throughput_avg_->Add(throughput);
+
+ gc_scanned_bytes_->Add(current_iteration->GetScannedBytes());
+ gc_freed_bytes_->Add(current_iteration->GetFreedBytes());
+ gc_duration_->Add(NsToMs(current_iteration->GetDurationNs()));
}
is_transaction_active_ = false;
}
diff --git a/runtime/gc/collector/garbage_collector.h b/runtime/gc/collector/garbage_collector.h
index d439914..d11aea3 100644
--- a/runtime/gc/collector/garbage_collector.h
+++ b/runtime/gc/collector/garbage_collector.h
@@ -166,6 +166,9 @@
metrics::MetricsBase<int64_t>* gc_tracing_throughput_hist_;
metrics::MetricsBase<uint64_t>* gc_throughput_avg_;
metrics::MetricsBase<uint64_t>* gc_tracing_throughput_avg_;
+ metrics::MetricsBase<uint64_t>* gc_scanned_bytes_;
+ metrics::MetricsBase<uint64_t>* gc_freed_bytes_;
+ metrics::MetricsBase<uint64_t>* gc_duration_;
uint64_t total_thread_cpu_time_ns_;
uint64_t total_time_ns_;
uint64_t total_freed_objects_;
diff --git a/runtime/gc/collector/immune_spaces_test.cc b/runtime/gc/collector/immune_spaces_test.cc
index a0ea60d..3d27a93 100644
--- a/runtime/gc/collector/immune_spaces_test.cc
+++ b/runtime/gc/collector/immune_spaces_test.cc
@@ -46,7 +46,7 @@
MemMap&& oat_map)
: ImageSpace("FakeImageSpace",
/*image_location=*/"",
- /*profile_file=*/{},
+ /*profile_files=*/{},
std::move(map),
std::move(live_bitmap),
map.End()),
diff --git a/runtime/gc/collector/mark_compact-inl.h b/runtime/gc/collector/mark_compact-inl.h
new file mode 100644
index 0000000..8fdf0f3
--- /dev/null
+++ b/runtime/gc/collector/mark_compact-inl.h
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2021 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_GC_COLLECTOR_MARK_COMPACT_INL_H_
+#define ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_INL_H_
+
+#include "mark_compact.h"
+
+#include "mirror/object-inl.h"
+
+namespace art {
+namespace gc {
+namespace collector {
+
+template <size_t kAlignment>
+inline uintptr_t MarkCompact::LiveWordsBitmap<kAlignment>::SetLiveWords(uintptr_t begin,
+ size_t size) {
+ const uintptr_t begin_bit_idx = MemRangeBitmap::BitIndexFromAddr(begin);
+ DCHECK(!Bitmap::TestBit(begin_bit_idx));
+ uintptr_t end = begin + size;
+ // We have to use the unchecked version of BitIndexFromAddr() as 'end' could
+ // be outside the range. Do explicit check here.
+ DCHECK_LE(end, MemRangeBitmap::CoverEnd());
+ const uintptr_t end_bit_idx = MemRangeBitmap::BitIndexFromAddrUnchecked(end);
+ uintptr_t* bm_address = Bitmap::Begin() + Bitmap::BitIndexToWordIndex(begin_bit_idx);
+ uintptr_t* const end_bm_address = Bitmap::Begin() + Bitmap::BitIndexToWordIndex(end_bit_idx);
+ uintptr_t mask = Bitmap::BitIndexToMask(begin_bit_idx);
+ // Bits that needs to be set in the first word, if it's not also the last word
+ mask = ~(mask - 1);
+ // loop over all the words, except the last one.
+ // TODO: optimize by using memset. Sometimes this function may get called with
+ // large ranges.
+ for (; bm_address < end_bm_address; bm_address++) {
+ *bm_address |= mask;
+ // This needs to be set only once as we are setting all bits in the
+ // subsequent iterations. Hopefully, the compiler will optimize it.
+ mask = ~0;
+ }
+ // Take care of the last word. If we had only one word, then mask != ~0.
+ const uintptr_t end_mask = Bitmap::BitIndexToMask(end_bit_idx);
+ *bm_address |= mask & (end_mask - 1);
+ return begin_bit_idx;
+}
+
+template <size_t kAlignment> template <typename Visitor>
+inline void MarkCompact::LiveWordsBitmap<kAlignment>::VisitLiveStrides(uintptr_t begin_bit_idx,
+ uint8_t* end,
+ const size_t bytes,
+ Visitor&& visitor) const {
+ DCHECK(IsAligned<kAlignment>(end));
+ // We have to use the unchecked version of BitIndexFromAddr() as 'end' could
+ // be outside the range. Do explicit check here.
+ DCHECK_LE(reinterpret_cast<uintptr_t>(end), MemRangeBitmap::CoverEnd());
+ const uintptr_t end_bit_idx =
+ MemRangeBitmap::BitIndexFromAddrUnchecked(reinterpret_cast<uintptr_t>(end));
+ DCHECK_LT(begin_bit_idx, end_bit_idx);
+ uintptr_t begin_word_idx = Bitmap::BitIndexToWordIndex(begin_bit_idx);
+ const uintptr_t end_word_idx = Bitmap::BitIndexToWordIndex(end_bit_idx);
+ DCHECK(Bitmap::TestBit(begin_bit_idx));
+ size_t stride_size = 0;
+ size_t idx_in_word = 0;
+ size_t num_heap_words = bytes / kAlignment;
+ uintptr_t live_stride_start_idx;
+ uintptr_t word = Bitmap::Begin()[begin_word_idx];
+
+ // Setup the first word.
+ word &= ~(Bitmap::BitIndexToMask(begin_bit_idx) - 1);
+ begin_bit_idx = RoundDown(begin_bit_idx, Bitmap::kBitsPerBitmapWord);
+
+ do {
+ if (UNLIKELY(begin_word_idx == end_word_idx)) {
+ word &= Bitmap::BitIndexToMask(end_bit_idx) - 1;
+ }
+ if (~word == 0) {
+ // All bits in the word are marked.
+ if (stride_size == 0) {
+ live_stride_start_idx = begin_bit_idx;
+ }
+ stride_size += Bitmap::kBitsPerBitmapWord;
+ if (num_heap_words <= stride_size) {
+ break;
+ }
+ } else {
+ while (word != 0) {
+ // discard 0s
+ size_t shift = CTZ(word);
+ idx_in_word += shift;
+ word >>= shift;
+ if (stride_size > 0) {
+ if (shift > 0) {
+ if (num_heap_words <= stride_size) {
+ break;
+ }
+ visitor(live_stride_start_idx, stride_size, /*is_last*/ false);
+ num_heap_words -= stride_size;
+ live_stride_start_idx = begin_bit_idx + idx_in_word;
+ stride_size = 0;
+ }
+ } else {
+ live_stride_start_idx = begin_bit_idx + idx_in_word;
+ }
+ // consume 1s
+ shift = CTZ(~word);
+ DCHECK_NE(shift, 0u);
+ word >>= shift;
+ idx_in_word += shift;
+ stride_size += shift;
+ }
+ // If the whole word == 0 or the higher bits are 0s, then we exit out of
+ // the above loop without completely consuming the word, so call visitor,
+ // if needed.
+ if (idx_in_word < Bitmap::kBitsPerBitmapWord && stride_size > 0) {
+ if (num_heap_words <= stride_size) {
+ break;
+ }
+ visitor(live_stride_start_idx, stride_size, /*is_last*/ false);
+ num_heap_words -= stride_size;
+ stride_size = 0;
+ }
+ idx_in_word = 0;
+ }
+ begin_bit_idx += Bitmap::kBitsPerBitmapWord;
+ begin_word_idx++;
+ if (UNLIKELY(begin_word_idx > end_word_idx)) {
+ num_heap_words = std::min(stride_size, num_heap_words);
+ break;
+ }
+ word = Bitmap::Begin()[begin_word_idx];
+ } while (true);
+
+ if (stride_size > 0) {
+ visitor(live_stride_start_idx, num_heap_words, /*is_last*/ true);
+ }
+}
+
+template <size_t kAlignment>
+inline
+uint32_t MarkCompact::LiveWordsBitmap<kAlignment>::FindNthLiveWordOffset(size_t chunk_idx,
+ uint32_t n) const {
+ DCHECK_LT(n, kBitsPerVectorWord);
+ const size_t index = chunk_idx * kBitmapWordsPerVectorWord;
+ for (uint32_t i = 0; i < kBitmapWordsPerVectorWord; i++) {
+ uintptr_t word = Bitmap::Begin()[index + i];
+ if (~word == 0) {
+ if (n < Bitmap::kBitsPerBitmapWord) {
+ return i * Bitmap::kBitsPerBitmapWord + n;
+ }
+ n -= Bitmap::kBitsPerBitmapWord;
+ } else {
+ uint32_t j = 0;
+ while (word != 0) {
+ // count contiguous 0s
+ uint32_t shift = CTZ(word);
+ word >>= shift;
+ j += shift;
+ // count contiguous 1s
+ shift = CTZ(~word);
+ DCHECK_NE(shift, 0u);
+ if (shift > n) {
+ return i * Bitmap::kBitsPerBitmapWord + j + n;
+ }
+ n -= shift;
+ word >>= shift;
+ j += shift;
+ }
+ }
+ }
+ UNREACHABLE();
+}
+
+inline void MarkCompact::UpdateRef(mirror::Object* obj, MemberOffset offset) {
+ mirror::Object* old_ref = obj->GetFieldObject<
+ mirror::Object, kVerifyNone, kWithoutReadBarrier, /*kIsVolatile*/false>(offset);
+ if (kIsDebugBuild) {
+ if (live_words_bitmap_->HasAddress(old_ref)
+ && reinterpret_cast<uint8_t*>(old_ref) < black_allocations_begin_
+ && !moving_space_bitmap_->Test(old_ref)) {
+ mirror::Object* from_ref = GetFromSpaceAddr(old_ref);
+ std::ostringstream oss;
+ heap_->DumpSpaces(oss);
+ MemMap::DumpMaps(oss, /* terse= */ true);
+ LOG(FATAL) << "Not marked in the bitmap ref=" << old_ref
+ << " from_ref=" << from_ref
+ << " offset=" << offset
+ << " obj=" << obj
+ << " obj-validity=" << IsValidObject(obj)
+ << " from-space=" << static_cast<void*>(from_space_begin_)
+ << " bitmap= " << moving_space_bitmap_->DumpMemAround(old_ref)
+ << " from_ref "
+ << heap_->GetVerification()->DumpRAMAroundAddress(
+ reinterpret_cast<uintptr_t>(from_ref), 128)
+ << " obj "
+ << heap_->GetVerification()->DumpRAMAroundAddress(
+ reinterpret_cast<uintptr_t>(obj), 128)
+ << " old_ref " << heap_->GetVerification()->DumpRAMAroundAddress(
+ reinterpret_cast<uintptr_t>(old_ref), 128)
+ << " maps\n" << oss.str();
+ }
+ }
+ mirror::Object* new_ref = PostCompactAddress(old_ref);
+ if (new_ref != old_ref) {
+ obj->SetFieldObjectWithoutWriteBarrier<
+ /*kTransactionActive*/false, /*kCheckTransaction*/false, kVerifyNone, /*kIsVolatile*/false>(
+ offset,
+ new_ref);
+ }
+}
+
+inline bool MarkCompact::VerifyRootSingleUpdate(void* root,
+ mirror::Object* old_ref,
+ const RootInfo& info) {
+ void* stack_end = stack_end_;
+ void* stack_addr = stack_addr_;
+ if (!live_words_bitmap_->HasAddress(old_ref)) {
+ return false;
+ }
+ if (UNLIKELY(stack_end == nullptr)) {
+ pthread_attr_t attr;
+ size_t stack_size;
+ pthread_getattr_np(pthread_self(), &attr);
+ pthread_attr_getstack(&attr, &stack_addr, &stack_size);
+ pthread_attr_destroy(&attr);
+ stack_end = reinterpret_cast<char*>(stack_addr) + stack_size;
+ }
+ if (root < stack_addr || root > stack_end) {
+ auto ret = updated_roots_.insert(root);
+ DCHECK(ret.second) << "root=" << root
+ << " old_ref=" << old_ref
+ << " stack_addr=" << stack_addr
+ << " stack_end=" << stack_end;
+ }
+ DCHECK(reinterpret_cast<uint8_t*>(old_ref) >= black_allocations_begin_
+ || live_words_bitmap_->Test(old_ref))
+ << "ref=" << old_ref
+ << " <" << mirror::Object::PrettyTypeOf(old_ref)
+ << "> RootInfo [" << info << "]";
+ return true;
+}
+
+inline void MarkCompact::UpdateRoot(mirror::CompressedReference<mirror::Object>* root,
+ const RootInfo& info) {
+ DCHECK(!root->IsNull());
+ mirror::Object* old_ref = root->AsMirrorPtr();
+ if (!kIsDebugBuild || VerifyRootSingleUpdate(root, old_ref, info)) {
+ mirror::Object* new_ref = PostCompactAddress(old_ref);
+ if (old_ref != new_ref) {
+ root->Assign(new_ref);
+ }
+ }
+}
+
+inline void MarkCompact::UpdateRoot(mirror::Object** root, const RootInfo& info) {
+ mirror::Object* old_ref = *root;
+ if (!kIsDebugBuild || VerifyRootSingleUpdate(root, old_ref, info)) {
+ mirror::Object* new_ref = PostCompactAddress(old_ref);
+ if (old_ref != new_ref) {
+ *root = new_ref;
+ }
+ }
+}
+
+template <size_t kAlignment>
+inline size_t MarkCompact::LiveWordsBitmap<kAlignment>::CountLiveWordsUpto(size_t bit_idx) const {
+ const size_t word_offset = Bitmap::BitIndexToWordIndex(bit_idx);
+ uintptr_t word;
+ size_t ret = 0;
+ // This is needed only if we decide to make chunks 128-bit but still
+ // choose to use 64-bit word for bitmap. Ideally we should use 128-bit
+ // SIMD instructions to compute popcount.
+ if (kBitmapWordsPerVectorWord > 1) {
+ for (size_t i = RoundDown(word_offset, kBitmapWordsPerVectorWord); i < word_offset; i++) {
+ word = Bitmap::Begin()[i];
+ ret += POPCOUNT(word);
+ }
+ }
+ word = Bitmap::Begin()[word_offset];
+ const uintptr_t mask = Bitmap::BitIndexToMask(bit_idx);
+ DCHECK_NE(word & mask, 0u)
+ << " word_offset:" << word_offset
+ << " bit_idx:" << bit_idx
+ << " bit_idx_in_word:" << (bit_idx % Bitmap::kBitsPerBitmapWord)
+ << std::hex << " word: 0x" << word
+ << " mask: 0x" << mask << std::dec;
+ ret += POPCOUNT(word & (mask - 1));
+ return ret;
+}
+
+inline mirror::Object* MarkCompact::PostCompactBlackObjAddr(mirror::Object* old_ref) const {
+ return reinterpret_cast<mirror::Object*>(reinterpret_cast<uint8_t*>(old_ref)
+ - black_objs_slide_diff_);
+}
+
+inline mirror::Object* MarkCompact::PostCompactOldObjAddr(mirror::Object* old_ref) const {
+ const uintptr_t begin = live_words_bitmap_->Begin();
+ const uintptr_t addr_offset = reinterpret_cast<uintptr_t>(old_ref) - begin;
+ const size_t vec_idx = addr_offset / kOffsetChunkSize;
+ const size_t live_bytes_in_bitmap_word =
+ live_words_bitmap_->CountLiveWordsUpto(addr_offset / kAlignment) * kAlignment;
+ return reinterpret_cast<mirror::Object*>(begin
+ + chunk_info_vec_[vec_idx]
+ + live_bytes_in_bitmap_word);
+}
+
+inline mirror::Object* MarkCompact::PostCompactAddressUnchecked(mirror::Object* old_ref) const {
+ if (reinterpret_cast<uint8_t*>(old_ref) >= black_allocations_begin_) {
+ return PostCompactBlackObjAddr(old_ref);
+ }
+ if (kIsDebugBuild) {
+ mirror::Object* from_ref = GetFromSpaceAddr(old_ref);
+ DCHECK(live_words_bitmap_->Test(old_ref))
+ << "ref=" << old_ref;
+ if (!moving_space_bitmap_->Test(old_ref)) {
+ std::ostringstream oss;
+ Runtime::Current()->GetHeap()->DumpSpaces(oss);
+ MemMap::DumpMaps(oss, /* terse= */ true);
+ LOG(FATAL) << "ref=" << old_ref
+ << " from_ref=" << from_ref
+ << " from-space=" << static_cast<void*>(from_space_begin_)
+ << " bitmap= " << moving_space_bitmap_->DumpMemAround(old_ref)
+ << heap_->GetVerification()->DumpRAMAroundAddress(
+ reinterpret_cast<uintptr_t>(from_ref), 128)
+ << " maps\n" << oss.str();
+ }
+ }
+ return PostCompactOldObjAddr(old_ref);
+}
+
+inline mirror::Object* MarkCompact::PostCompactAddress(mirror::Object* old_ref) const {
+ // TODO: To further speedup the check, maybe we should consider caching heap
+ // start/end in this object.
+ if (LIKELY(live_words_bitmap_->HasAddress(old_ref))) {
+ return PostCompactAddressUnchecked(old_ref);
+ }
+ return old_ref;
+}
+
+} // namespace collector
+} // namespace gc
+} // namespace art
+
+#endif // ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_INL_H_
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
new file mode 100644
index 0000000..3b6dffb
--- /dev/null
+++ b/runtime/gc/collector/mark_compact.cc
@@ -0,0 +1,2589 @@
+/*
+ * Copyright 2021 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 "mark_compact-inl.h"
+
+#include "base/quasi_atomic.h"
+#include "base/systrace.h"
+#include "base/utils.h"
+#include "gc/accounting/mod_union_table-inl.h"
+#include "gc/reference_processor.h"
+#include "gc/space/bump_pointer_space.h"
+#include "gc/task_processor.h"
+#include "gc/verification-inl.h"
+#include "jit/jit_code_cache.h"
+#include "mirror/object-refvisitor-inl.h"
+#include "read_barrier_config.h"
+#include "scoped_thread_state_change-inl.h"
+#include "sigchain.h"
+#include "thread_list.h"
+
+#include <linux/userfaultfd.h>
+#include <poll.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <fstream>
+#include <numeric>
+
+#ifndef __BIONIC__
+#ifndef MREMAP_DONTUNMAP
+#define MREMAP_DONTUNMAP 4
+#endif
+#ifndef __NR_userfaultfd
+#if defined(__x86_64__)
+#define __NR_userfaultfd 323
+#elif defined(__i386__)
+#define __NR_userfaultfd 374
+#elif defined(__aarch64__)
+#define __NR_userfaultfd 282
+#elif defined(__arm__)
+#define __NR_userfaultfd 388
+#else
+#error "__NR_userfaultfd undefined"
+#endif
+#endif // __NR_userfaultfd
+#endif // __BIONIC__
+
+namespace art {
+
+// We require MREMAP_DONTUNMAP functionality of the mremap syscall, which was
+// introduced in 5.13 kernel version. Check for that on host. Checking
+// on target is not required as MREMAP_DONTUNMAP and userfaultfd were enabled
+// together.
+// Concurrent compaction termination logic depends on the kernel having
+// the fault-retry feature (allowing repeated faults on the same page), which was
+// introduced in 5.7. On target this feature is backported on all the kernels where
+// userfaultfd is enabled.
+#ifdef ART_TARGET
+static constexpr bool gHaveMremapDontunmap = true;
+static constexpr bool gKernelHasFaultRetry = true;
+#else
+static const bool gHaveMremapDontunmap = IsKernelVersionAtLeast(5, 13);
+static const bool gKernelHasFaultRetry = IsKernelVersionAtLeast(5, 7);
+#endif
+
+#ifndef ART_FORCE_USE_READ_BARRIER
+static bool ShouldUseUserfaultfd() {
+#if !defined(__linux__)
+ return false;
+#endif
+ int fd = syscall(__NR_userfaultfd, O_CLOEXEC | UFFD_USER_MODE_ONLY);
+#ifndef ART_TARGET
+ // On host we may not have the kernel patches that restrict userfaultfd to
+ // user mode. But that is not a security concern as we are on host.
+ // Therefore, attempt one more time without UFFD_USER_MODE_ONLY.
+ if (fd == -1 && errno == EINVAL) {
+ fd = syscall(__NR_userfaultfd, O_CLOEXEC);
+ }
+#endif
+ if (fd >= 0) {
+ close(fd);
+ return true;
+ } else {
+ return false;
+ }
+}
+#endif
+
+// The other cases are defined as a constexpr in runtime/read_barrier_config.h
+#ifndef ART_FORCE_USE_READ_BARRIER
+const bool gUseReadBarrier = (kUseBakerReadBarrier || kUseTableLookupReadBarrier)
+ && !ShouldUseUserfaultfd();
+#ifdef ART_DEFAULT_GC_TYPE_IS_CMC
+const bool gUseUserfaultfd = !gUseReadBarrier;
+#endif
+#endif
+
+namespace gc {
+namespace collector {
+
+// Turn off kCheckLocks when profiling the GC as it slows down the GC
+// significantly.
+static constexpr bool kCheckLocks = kDebugLocking;
+static constexpr bool kVerifyRootsMarked = kIsDebugBuild;
+
+bool MarkCompact::CreateUserfaultfd(bool post_fork) {
+ if (post_fork || uffd_ == -1) {
+ // Don't use O_NONBLOCK as we rely on read waiting on uffd_ if there isn't
+ // any read event available. We don't use poll.
+ if (gKernelHasFaultRetry) {
+ uffd_ = syscall(__NR_userfaultfd, O_CLOEXEC | UFFD_USER_MODE_ONLY);
+#ifndef ART_TARGET
+ // On host we may not have the kernel patches that restrict userfaultfd to
+ // user mode. But that is not a security concern as we are on host.
+ // Therefore, attempt one more time without UFFD_USER_MODE_ONLY.
+ if (UNLIKELY(uffd_ == -1 && errno == EINVAL)) {
+ uffd_ = syscall(__NR_userfaultfd, O_CLOEXEC);
+ }
+#endif
+ if (UNLIKELY(uffd_ == -1)) {
+ uffd_ = kFallbackMode;
+ LOG(WARNING) << "Userfaultfd isn't supported (reason: " << strerror(errno)
+ << ") and therefore falling back to stop-the-world compaction.";
+ } else {
+ DCHECK_GE(uffd_, 0);
+ // Get/update the features that we want in userfaultfd
+ struct uffdio_api api = {.api = UFFD_API, .features = 0};
+ CHECK_EQ(ioctl(uffd_, UFFDIO_API, &api), 0)
+ << "ioctl_userfaultfd: API: " << strerror(errno);
+ }
+ } else {
+ // Without fault-retry feature in the kernel we can't terminate concurrent
+ // compaction. So fallback to stop-the-world compaction.
+ uffd_ = kFallbackMode;
+ }
+ }
+ uffd_initialized_ = !post_fork || uffd_ == kFallbackMode;
+ return uffd_ >= 0;
+}
+
+template <size_t kAlignment>
+MarkCompact::LiveWordsBitmap<kAlignment>* MarkCompact::LiveWordsBitmap<kAlignment>::Create(
+ uintptr_t begin, uintptr_t end) {
+ return static_cast<LiveWordsBitmap<kAlignment>*>(
+ MemRangeBitmap::Create("Concurrent Mark Compact live words bitmap", begin, end));
+}
+
+MarkCompact::MarkCompact(Heap* heap)
+ : GarbageCollector(heap, "concurrent mark compact"),
+ gc_barrier_(0),
+ mark_stack_lock_("mark compact mark stack lock", kMarkSweepMarkStackLock),
+ bump_pointer_space_(heap->GetBumpPointerSpace()),
+ uffd_(-1),
+ thread_pool_counter_(0),
+ compacting_(false),
+ uffd_initialized_(false) {
+ // TODO: Depending on how the bump-pointer space move is implemented. If we
+ // switch between two virtual memories each time, then we will have to
+ // initialize live_words_bitmap_ accordingly.
+ live_words_bitmap_.reset(LiveWordsBitmap<kAlignment>::Create(
+ reinterpret_cast<uintptr_t>(bump_pointer_space_->Begin()),
+ reinterpret_cast<uintptr_t>(bump_pointer_space_->Limit())));
+
+ // Create one MemMap for all the data structures
+ size_t chunk_info_vec_size = bump_pointer_space_->Capacity() / kOffsetChunkSize;
+ size_t nr_moving_pages = bump_pointer_space_->Capacity() / kPageSize;
+ size_t nr_non_moving_pages = heap->GetNonMovingSpace()->Capacity() / kPageSize;
+
+ std::string err_msg;
+ info_map_ = MemMap::MapAnonymous("Concurrent mark-compact chunk-info vector",
+ chunk_info_vec_size * sizeof(uint32_t)
+ + nr_non_moving_pages * sizeof(ObjReference)
+ + nr_moving_pages * sizeof(ObjReference)
+ + nr_moving_pages * sizeof(uint32_t),
+ PROT_READ | PROT_WRITE,
+ /*low_4gb=*/ false,
+ &err_msg);
+ if (UNLIKELY(!info_map_.IsValid())) {
+ LOG(ERROR) << "Failed to allocate concurrent mark-compact chunk-info vector: " << err_msg;
+ } else {
+ uint8_t* p = info_map_.Begin();
+ chunk_info_vec_ = reinterpret_cast<uint32_t*>(p);
+ vector_length_ = chunk_info_vec_size;
+
+ p += chunk_info_vec_size * sizeof(uint32_t);
+ first_objs_non_moving_space_ = reinterpret_cast<ObjReference*>(p);
+
+ p += nr_non_moving_pages * sizeof(ObjReference);
+ first_objs_moving_space_ = reinterpret_cast<ObjReference*>(p);
+
+ p += nr_moving_pages * sizeof(ObjReference);
+ pre_compact_offset_moving_space_ = reinterpret_cast<uint32_t*>(p);
+ }
+
+ from_space_map_ = MemMap::MapAnonymous("Concurrent mark-compact from-space",
+ bump_pointer_space_->Capacity(),
+ PROT_NONE,
+ /*low_4gb=*/ kObjPtrPoisoning,
+ &err_msg);
+ if (UNLIKELY(!from_space_map_.IsValid())) {
+ LOG(ERROR) << "Failed to allocate concurrent mark-compact from-space" << err_msg;
+ } else {
+ from_space_begin_ = from_space_map_.Begin();
+ }
+
+ // poisoning requires 32-bit pointers and therefore compaction buffers on
+ // the stack can't be used. We also use the first page-sized buffer for the
+ // purpose of terminating concurrent compaction.
+ const size_t num_pages = 1 + std::max(heap_->GetParallelGCThreadCount(),
+ heap_->GetConcGCThreadCount());
+ compaction_buffers_map_ = MemMap::MapAnonymous("Concurrent mark-compact compaction buffers",
+ kPageSize * (kObjPtrPoisoning ? num_pages : 1),
+ PROT_READ | PROT_WRITE,
+ /*low_4gb=*/ kObjPtrPoisoning,
+ &err_msg);
+ if (UNLIKELY(!compaction_buffers_map_.IsValid())) {
+ LOG(ERROR) << "Failed to allocate concurrent mark-compact compaction buffers" << err_msg;
+ }
+ conc_compaction_termination_page_ = compaction_buffers_map_.Begin();
+ if (kObjPtrPoisoning) {
+ // Touch the page deliberately to avoid userfaults on it. We madvise it in
+ // CompactionPhase() before using it to terminate concurrent compaction.
+ CHECK_EQ(*conc_compaction_termination_page_, 0);
+ }
+}
+
+void MarkCompact::BindAndResetBitmaps() {
+ // TODO: We need to hold heap_bitmap_lock_ only for populating immune_spaces.
+ // The card-table and mod-union-table processing can be done without it. So
+ // change the logic below. Note that the bitmap clearing would require the
+ // lock.
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ accounting::CardTable* const card_table = heap_->GetCardTable();
+ // Mark all of the spaces we never collect as immune.
+ for (const auto& space : GetHeap()->GetContinuousSpaces()) {
+ if (space->GetGcRetentionPolicy() == space::kGcRetentionPolicyNeverCollect ||
+ space->GetGcRetentionPolicy() == space::kGcRetentionPolicyFullCollect) {
+ CHECK(space->IsZygoteSpace() || space->IsImageSpace());
+ immune_spaces_.AddSpace(space);
+ accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+ if (table != nullptr) {
+ table->ProcessCards();
+ } else {
+ // Keep cards aged if we don't have a mod-union table since we may need
+ // to scan them in future GCs. This case is for app images.
+ // TODO: We could probably scan the objects right here to avoid doing
+ // another scan through the card-table.
+ card_table->ModifyCardsAtomic(
+ space->Begin(),
+ space->End(),
+ [](uint8_t card) {
+ return (card == gc::accounting::CardTable::kCardClean)
+ ? card
+ : gc::accounting::CardTable::kCardAged;
+ },
+ /* card modified visitor */ VoidFunctor());
+ }
+ } else {
+ CHECK(!space->IsZygoteSpace());
+ CHECK(!space->IsImageSpace());
+ // The card-table corresponding to bump-pointer and non-moving space can
+ // be cleared, because we are going to traverse all the reachable objects
+ // in these spaces. This card-table will eventually be used to track
+ // mutations while concurrent marking is going on.
+ card_table->ClearCardRange(space->Begin(), space->Limit());
+ if (space == bump_pointer_space_) {
+ // It is OK to clear the bitmap with mutators running since the only
+ // place it is read is VisitObjects which has exclusion with this GC.
+ moving_space_bitmap_ = bump_pointer_space_->GetMarkBitmap();
+ moving_space_bitmap_->Clear();
+ } else {
+ CHECK(space == heap_->GetNonMovingSpace());
+ non_moving_space_ = space;
+ non_moving_space_bitmap_ = space->GetMarkBitmap();
+ }
+ }
+ }
+}
+
+void MarkCompact::InitializePhase() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ mark_stack_ = heap_->GetMarkStack();
+ CHECK(mark_stack_->IsEmpty());
+ immune_spaces_.Reset();
+ moving_first_objs_count_ = 0;
+ non_moving_first_objs_count_ = 0;
+ black_page_count_ = 0;
+ freed_objects_ = 0;
+ from_space_slide_diff_ = from_space_begin_ - bump_pointer_space_->Begin();
+ black_allocations_begin_ = bump_pointer_space_->Limit();
+ compacting_ = false;
+}
+
+void MarkCompact::RunPhases() {
+ Thread* self = Thread::Current();
+ thread_running_gc_ = self;
+ InitializePhase();
+ GetHeap()->PreGcVerification(this);
+ {
+ ReaderMutexLock mu(self, *Locks::mutator_lock_);
+ MarkingPhase();
+ }
+ {
+ ScopedPause pause(this);
+ MarkingPause();
+ if (kIsDebugBuild) {
+ bump_pointer_space_->AssertAllThreadLocalBuffersAreRevoked();
+ }
+ }
+ // To increase likelihood of black allocations. For testing purposes only.
+ if (kIsDebugBuild && heap_->GetTaskProcessor()->GetRunningThread() == thread_running_gc_) {
+ usleep(500'000);
+ }
+ {
+ ReaderMutexLock mu(self, *Locks::mutator_lock_);
+ ReclaimPhase();
+ PrepareForCompaction();
+ }
+ if (uffd_ != kFallbackMode) {
+ heap_->GetThreadPool()->WaitForWorkersToBeCreated();
+ }
+ {
+ heap_->ThreadFlipBegin(self);
+ {
+ ScopedPause pause(this);
+ PreCompactionPhase();
+ }
+ heap_->ThreadFlipEnd(self);
+ }
+
+ if (uffd_ >= 0) {
+ ReaderMutexLock mu(self, *Locks::mutator_lock_);
+ CompactionPhase();
+ }
+
+ FinishPhase();
+ thread_running_gc_ = nullptr;
+ GetHeap()->PostGcVerification(this);
+}
+
+void MarkCompact::InitMovingSpaceFirstObjects(const size_t vec_len) {
+ // Find the first live word first.
+ size_t to_space_page_idx = 0;
+ uint32_t offset_in_chunk_word;
+ uint32_t offset;
+ mirror::Object* obj;
+ const uintptr_t heap_begin = moving_space_bitmap_->HeapBegin();
+
+ size_t chunk_idx;
+ // Find the first live word in the space
+ for (chunk_idx = 0; chunk_info_vec_[chunk_idx] == 0; chunk_idx++) {
+ if (chunk_idx > vec_len) {
+ // We don't have any live data on the moving-space.
+ return;
+ }
+ }
+ // Use live-words bitmap to find the first word
+ offset_in_chunk_word = live_words_bitmap_->FindNthLiveWordOffset(chunk_idx, /*n*/ 0);
+ offset = chunk_idx * kBitsPerVectorWord + offset_in_chunk_word;
+ DCHECK(live_words_bitmap_->Test(offset)) << "offset=" << offset
+ << " chunk_idx=" << chunk_idx
+ << " N=0"
+ << " offset_in_word=" << offset_in_chunk_word
+ << " word=" << std::hex
+ << live_words_bitmap_->GetWord(chunk_idx);
+ // The first object doesn't require using FindPrecedingObject().
+ obj = reinterpret_cast<mirror::Object*>(heap_begin + offset * kAlignment);
+ // TODO: add a check to validate the object.
+
+ pre_compact_offset_moving_space_[to_space_page_idx] = offset;
+ first_objs_moving_space_[to_space_page_idx].Assign(obj);
+ to_space_page_idx++;
+
+ uint32_t page_live_bytes = 0;
+ while (true) {
+ for (; page_live_bytes <= kPageSize; chunk_idx++) {
+ if (chunk_idx > vec_len) {
+ moving_first_objs_count_ = to_space_page_idx;
+ return;
+ }
+ page_live_bytes += chunk_info_vec_[chunk_idx];
+ }
+ chunk_idx--;
+ page_live_bytes -= kPageSize;
+ DCHECK_LE(page_live_bytes, kOffsetChunkSize);
+ DCHECK_LE(page_live_bytes, chunk_info_vec_[chunk_idx])
+ << " chunk_idx=" << chunk_idx
+ << " to_space_page_idx=" << to_space_page_idx
+ << " vec_len=" << vec_len;
+ DCHECK(IsAligned<kAlignment>(chunk_info_vec_[chunk_idx] - page_live_bytes));
+ offset_in_chunk_word =
+ live_words_bitmap_->FindNthLiveWordOffset(
+ chunk_idx, (chunk_info_vec_[chunk_idx] - page_live_bytes) / kAlignment);
+ offset = chunk_idx * kBitsPerVectorWord + offset_in_chunk_word;
+ DCHECK(live_words_bitmap_->Test(offset))
+ << "offset=" << offset
+ << " chunk_idx=" << chunk_idx
+ << " N=" << ((chunk_info_vec_[chunk_idx] - page_live_bytes) / kAlignment)
+ << " offset_in_word=" << offset_in_chunk_word
+ << " word=" << std::hex << live_words_bitmap_->GetWord(chunk_idx);
+ // TODO: Can we optimize this for large objects? If we are continuing a
+ // large object that spans multiple pages, then we may be able to do without
+ // calling FindPrecedingObject().
+ //
+ // Find the object which encapsulates offset in it, which could be
+ // starting at offset itself.
+ obj = moving_space_bitmap_->FindPrecedingObject(heap_begin + offset * kAlignment);
+ // TODO: add a check to validate the object.
+ pre_compact_offset_moving_space_[to_space_page_idx] = offset;
+ first_objs_moving_space_[to_space_page_idx].Assign(obj);
+ to_space_page_idx++;
+ chunk_idx++;
+ }
+}
+
+void MarkCompact::InitNonMovingSpaceFirstObjects() {
+ accounting::ContinuousSpaceBitmap* bitmap = non_moving_space_->GetLiveBitmap();
+ uintptr_t begin = reinterpret_cast<uintptr_t>(non_moving_space_->Begin());
+ const uintptr_t end = reinterpret_cast<uintptr_t>(non_moving_space_->End());
+ mirror::Object* prev_obj;
+ size_t page_idx;
+ {
+ // Find first live object
+ mirror::Object* obj = nullptr;
+ bitmap->VisitMarkedRange</*kVisitOnce*/ true>(begin,
+ end,
+ [&obj] (mirror::Object* o) {
+ obj = o;
+ });
+ if (obj == nullptr) {
+ // There are no live objects in the non-moving space
+ return;
+ }
+ page_idx = (reinterpret_cast<uintptr_t>(obj) - begin) / kPageSize;
+ first_objs_non_moving_space_[page_idx++].Assign(obj);
+ prev_obj = obj;
+ }
+ // TODO: check obj is valid
+ uintptr_t prev_obj_end = reinterpret_cast<uintptr_t>(prev_obj)
+ + RoundUp(prev_obj->SizeOf<kDefaultVerifyFlags>(), kAlignment);
+ // For every page find the object starting from which we need to call
+ // VisitReferences. It could either be an object that started on some
+ // preceding page, or some object starting within this page.
+ begin = RoundDown(reinterpret_cast<uintptr_t>(prev_obj) + kPageSize, kPageSize);
+ while (begin < end) {
+ // Utilize, if any, large object that started in some preceding page, but
+ // overlaps with this page as well.
+ if (prev_obj != nullptr && prev_obj_end > begin) {
+ DCHECK_LT(prev_obj, reinterpret_cast<mirror::Object*>(begin));
+ first_objs_non_moving_space_[page_idx].Assign(prev_obj);
+ mirror::Class* klass = prev_obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ if (bump_pointer_space_->HasAddress(klass)) {
+ LOG(WARNING) << "found inter-page object " << prev_obj
+ << " in non-moving space with klass " << klass
+ << " in moving space";
+ }
+ } else {
+ prev_obj_end = 0;
+ // It's sufficient to only search for previous object in the preceding page.
+ // If no live object started in that page and some object had started in
+ // the page preceding to that page, which was big enough to overlap with
+ // the current page, then we wouldn't be in the else part.
+ prev_obj = bitmap->FindPrecedingObject(begin, begin - kPageSize);
+ if (prev_obj != nullptr) {
+ prev_obj_end = reinterpret_cast<uintptr_t>(prev_obj)
+ + RoundUp(prev_obj->SizeOf<kDefaultVerifyFlags>(), kAlignment);
+ }
+ if (prev_obj_end > begin) {
+ mirror::Class* klass = prev_obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ if (bump_pointer_space_->HasAddress(klass)) {
+ LOG(WARNING) << "found inter-page object " << prev_obj
+ << " in non-moving space with klass " << klass
+ << " in moving space";
+ }
+ first_objs_non_moving_space_[page_idx].Assign(prev_obj);
+ } else {
+ // Find the first live object in this page
+ bitmap->VisitMarkedRange</*kVisitOnce*/ true>(
+ begin,
+ begin + kPageSize,
+ [this, page_idx] (mirror::Object* obj) {
+ first_objs_non_moving_space_[page_idx].Assign(obj);
+ });
+ }
+ // An empty entry indicates that the page has no live objects and hence
+ // can be skipped.
+ }
+ begin += kPageSize;
+ page_idx++;
+ }
+ non_moving_first_objs_count_ = page_idx;
+}
+
+class MarkCompact::ConcurrentCompactionGcTask : public SelfDeletingTask {
+ public:
+ explicit ConcurrentCompactionGcTask(MarkCompact* collector, size_t idx)
+ : collector_(collector), index_(idx) {}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wframe-larger-than="
+ void Run(Thread* self ATTRIBUTE_UNUSED) override REQUIRES_SHARED(Locks::mutator_lock_) {
+ // The passed page/buf to ConcurrentCompaction is used by the thread as a
+ // kPageSize buffer for compacting and updating objects into and then
+ // passing the buf to uffd ioctls.
+ if (kObjPtrPoisoning) {
+ uint8_t* page = collector_->compaction_buffers_map_.Begin() + index_ * kPageSize;
+ collector_->ConcurrentCompaction(page);
+ } else {
+ uint8_t buf[kPageSize];
+ collector_->ConcurrentCompaction(buf);
+ }
+ }
+#pragma clang diagnostic pop
+
+ private:
+ MarkCompact* const collector_;
+ size_t index_;
+};
+
+void MarkCompact::PrepareForCompaction() {
+ uint8_t* space_begin = bump_pointer_space_->Begin();
+ size_t vector_len = (black_allocations_begin_ - space_begin) / kOffsetChunkSize;
+ DCHECK_LE(vector_len, vector_length_);
+ for (size_t i = 0; i < vector_len; i++) {
+ DCHECK_LE(chunk_info_vec_[i], kOffsetChunkSize);
+ DCHECK_EQ(chunk_info_vec_[i], live_words_bitmap_->LiveBytesInBitmapWord(i));
+ }
+ InitMovingSpaceFirstObjects(vector_len);
+ InitNonMovingSpaceFirstObjects();
+
+ // TODO: We can do a lot of neat tricks with this offset vector to tune the
+ // compaction as we wish. Originally, the compaction algorithm slides all
+ // live objects towards the beginning of the heap. This is nice because it
+ // keeps the spatial locality of objects intact.
+ // However, sometimes it's desired to compact objects in certain portions
+ // of the heap. For instance, it is expected that, over time,
+ // objects towards the beginning of the heap are long lived and are always
+ // densely packed. In this case, it makes sense to only update references in
+ // there and not try to compact it.
+ // Furthermore, we might have some large objects and may not want to move such
+ // objects.
+ // We can adjust, without too much effort, the values in the chunk_info_vec_ such
+ // that the objects in the dense beginning area aren't moved. OTOH, large
+ // objects, which could be anywhere in the heap, could also be kept from
+ // moving by using a similar trick. The only issue is that by doing this we will
+ // leave an unused hole in the middle of the heap which can't be used for
+ // allocations until we do a *full* compaction.
+ //
+ // At this point every element in the chunk_info_vec_ contains the live-bytes
+ // of the corresponding chunk. For old-to-new address computation we need
+ // every element to reflect total live-bytes till the corresponding chunk.
+
+ // Live-bytes count is required to compute post_compact_end_ below.
+ uint32_t total;
+ // Update the vector one past the heap usage as it is required for black
+ // allocated objects' post-compact address computation.
+ if (vector_len < vector_length_) {
+ vector_len++;
+ total = 0;
+ } else {
+ // Fetch the value stored in the last element before it gets overwritten by
+ // std::exclusive_scan().
+ total = chunk_info_vec_[vector_len - 1];
+ }
+ std::exclusive_scan(chunk_info_vec_, chunk_info_vec_ + vector_len, chunk_info_vec_, 0);
+ total += chunk_info_vec_[vector_len - 1];
+
+ for (size_t i = vector_len; i < vector_length_; i++) {
+ DCHECK_EQ(chunk_info_vec_[i], 0u);
+ }
+ post_compact_end_ = AlignUp(space_begin + total, kPageSize);
+ CHECK_EQ(post_compact_end_, space_begin + moving_first_objs_count_ * kPageSize);
+ black_objs_slide_diff_ = black_allocations_begin_ - post_compact_end_;
+ // How do we handle compaction of heap portion used for allocations after the
+ // marking-pause?
+ // All allocations after the marking-pause are considered black (reachable)
+ // for this GC cycle. However, they need not be allocated contiguously as
+ // different mutators use TLABs. So we will compact the heap till the point
+ // where allocations took place before the marking-pause. And everything after
+ // that will be slid with TLAB holes, and then TLAB info in TLS will be
+ // appropriately updated in the pre-compaction pause.
+ // The chunk-info vector entries for the post marking-pause allocations will be
+ // also updated in the pre-compaction pause.
+
+ if (!uffd_initialized_ && CreateUserfaultfd(/*post_fork*/false)) {
+ // Register the buffer that we use for terminating concurrent compaction
+ struct uffdio_register uffd_register;
+ uffd_register.range.start = reinterpret_cast<uintptr_t>(conc_compaction_termination_page_);
+ uffd_register.range.len = kPageSize;
+ uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+ CHECK_EQ(ioctl(uffd_, UFFDIO_REGISTER, &uffd_register), 0)
+ << "ioctl_userfaultfd: register compaction termination page: " << strerror(errno);
+ }
+ // For zygote we create the thread pool each time before starting compaction,
+ // and get rid of it when finished. This is expected to happen rarely as
+ // zygote spends most of the time in native fork loop.
+ if (uffd_ != kFallbackMode) {
+ ThreadPool* pool = heap_->GetThreadPool();
+ if (UNLIKELY(pool == nullptr)) {
+ heap_->CreateThreadPool();
+ pool = heap_->GetThreadPool();
+ }
+ const size_t num_threads = pool->GetThreadCount();
+ thread_pool_counter_ = num_threads;
+ for (size_t i = 0; i < num_threads; i++) {
+ pool->AddTask(thread_running_gc_, new ConcurrentCompactionGcTask(this, i + 1));
+ }
+ CHECK_EQ(pool->GetTaskCount(thread_running_gc_), num_threads);
+ }
+}
+
+class MarkCompact::VerifyRootMarkedVisitor : public SingleRootVisitor {
+ public:
+ explicit VerifyRootMarkedVisitor(MarkCompact* collector) : collector_(collector) { }
+
+ void VisitRoot(mirror::Object* root, const RootInfo& info) override
+ REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) {
+ CHECK(collector_->IsMarked(root) != nullptr) << info.ToString();
+ }
+
+ private:
+ MarkCompact* const collector_;
+};
+
+void MarkCompact::ReMarkRoots(Runtime* runtime) {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ DCHECK_EQ(thread_running_gc_, Thread::Current());
+ Locks::mutator_lock_->AssertExclusiveHeld(thread_running_gc_);
+ MarkNonThreadRoots(runtime);
+ MarkConcurrentRoots(static_cast<VisitRootFlags>(kVisitRootFlagNewRoots
+ | kVisitRootFlagStopLoggingNewRoots
+ | kVisitRootFlagClearRootLog),
+ runtime);
+
+ if (kVerifyRootsMarked) {
+ TimingLogger::ScopedTiming t2("(Paused)VerifyRoots", GetTimings());
+ VerifyRootMarkedVisitor visitor(this);
+ runtime->VisitRoots(&visitor);
+ }
+}
+
+void MarkCompact::MarkingPause() {
+ TimingLogger::ScopedTiming t("(Paused)MarkingPause", GetTimings());
+ Runtime* runtime = Runtime::Current();
+ Locks::mutator_lock_->AssertExclusiveHeld(thread_running_gc_);
+ {
+ // Handle the dirty objects as we are a concurrent GC
+ WriterMutexLock mu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+ {
+ MutexLock mu2(thread_running_gc_, *Locks::runtime_shutdown_lock_);
+ MutexLock mu3(thread_running_gc_, *Locks::thread_list_lock_);
+ std::list<Thread*> thread_list = runtime->GetThreadList()->GetList();
+ for (Thread* thread : thread_list) {
+ thread->VisitRoots(this, static_cast<VisitRootFlags>(0));
+ // Need to revoke all the thread-local allocation stacks since we will
+ // swap the allocation stacks (below) and don't want anybody to allocate
+ // into the live stack.
+ thread->RevokeThreadLocalAllocationStack();
+ bump_pointer_space_->RevokeThreadLocalBuffers(thread);
+ }
+ }
+ // Re-mark root set. Doesn't include thread-roots as they are already marked
+ // above.
+ ReMarkRoots(runtime);
+ // Scan dirty objects.
+ RecursiveMarkDirtyObjects(/*paused*/ true, accounting::CardTable::kCardDirty);
+ {
+ TimingLogger::ScopedTiming t2("SwapStacks", GetTimings());
+ heap_->SwapStacks();
+ live_stack_freeze_size_ = heap_->GetLiveStack()->Size();
+ }
+ }
+ // Fetch only the accumulated objects-allocated count as it is guaranteed to
+ // be up-to-date after the TLAB revocation above.
+ freed_objects_ += bump_pointer_space_->GetAccumulatedObjectsAllocated();
+ // TODO: For PreSweepingGcVerification(), find correct strategy to visit/walk
+ // objects in bump-pointer space when we have a mark-bitmap to indicate live
+ // objects. At the same time we also need to be able to visit black allocations,
+ // even though they are not marked in the bitmap. Without both of these we fail
+ // pre-sweeping verification. As well as we leave windows open wherein a
+ // VisitObjects/Walk on the space would either miss some objects or visit
+ // unreachable ones. These windows are when we are switching from shared
+ // mutator-lock to exclusive and vice-versa starting from here till compaction pause.
+ // heap_->PreSweepingGcVerification(this);
+
+ // Disallow new system weaks to prevent a race which occurs when someone adds
+ // a new system weak before we sweep them. Since this new system weak may not
+ // be marked, the GC may incorrectly sweep it. This also fixes a race where
+ // interning may attempt to return a strong reference to a string that is
+ // about to be swept.
+ runtime->DisallowNewSystemWeaks();
+ // Enable the reference processing slow path, needs to be done with mutators
+ // paused since there is no lock in the GetReferent fast path.
+ heap_->GetReferenceProcessor()->EnableSlowPath();
+
+ // Capture 'end' of moving-space at this point. Every allocation beyond this
+ // point will be considered as black.
+ // Align-up to page boundary so that black allocations happen from next page
+ // onwards.
+ black_allocations_begin_ = bump_pointer_space_->AlignEnd(thread_running_gc_, kPageSize);
+ DCHECK(IsAligned<kAlignment>(black_allocations_begin_));
+ black_allocations_begin_ = AlignUp(black_allocations_begin_, kPageSize);
+}
+
+void MarkCompact::SweepSystemWeaks(Thread* self, Runtime* runtime, const bool paused) {
+ TimingLogger::ScopedTiming t(paused ? "(Paused)SweepSystemWeaks" : "SweepSystemWeaks",
+ GetTimings());
+ ReaderMutexLock mu(self, *Locks::heap_bitmap_lock_);
+ runtime->SweepSystemWeaks(this);
+}
+
+void MarkCompact::ProcessReferences(Thread* self) {
+ WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
+ GetHeap()->GetReferenceProcessor()->ProcessReferences(self, GetTimings());
+}
+
+void MarkCompact::Sweep(bool swap_bitmaps) {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ // Ensure that nobody inserted objects in the live stack after we swapped the
+ // stacks.
+ CHECK_GE(live_stack_freeze_size_, GetHeap()->GetLiveStack()->Size());
+ {
+ TimingLogger::ScopedTiming t2("MarkAllocStackAsLive", GetTimings());
+ // Mark everything allocated since the last GC as live so that we can sweep
+ // concurrently, knowing that new allocations won't be marked as live.
+ accounting::ObjectStack* live_stack = heap_->GetLiveStack();
+ heap_->MarkAllocStackAsLive(live_stack);
+ live_stack->Reset();
+ DCHECK(mark_stack_->IsEmpty());
+ }
+ for (const auto& space : GetHeap()->GetContinuousSpaces()) {
+ if (space->IsContinuousMemMapAllocSpace() && space != bump_pointer_space_) {
+ space::ContinuousMemMapAllocSpace* alloc_space = space->AsContinuousMemMapAllocSpace();
+ TimingLogger::ScopedTiming split(
+ alloc_space->IsZygoteSpace() ? "SweepZygoteSpace" : "SweepMallocSpace",
+ GetTimings());
+ RecordFree(alloc_space->Sweep(swap_bitmaps));
+ }
+ }
+ SweepLargeObjects(swap_bitmaps);
+}
+
+void MarkCompact::SweepLargeObjects(bool swap_bitmaps) {
+ space::LargeObjectSpace* los = heap_->GetLargeObjectsSpace();
+ if (los != nullptr) {
+ TimingLogger::ScopedTiming split(__FUNCTION__, GetTimings());
+ RecordFreeLOS(los->Sweep(swap_bitmaps));
+ }
+}
+
+void MarkCompact::ReclaimPhase() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ DCHECK(thread_running_gc_ == Thread::Current());
+ Runtime* const runtime = Runtime::Current();
+ // Process the references concurrently.
+ ProcessReferences(thread_running_gc_);
+ // TODO: Try to merge this system-weak sweeping with the one while updating
+ // references during the compaction pause.
+ SweepSystemWeaks(thread_running_gc_, runtime, /*paused*/ false);
+ runtime->AllowNewSystemWeaks();
+ // Clean up class loaders after system weaks are swept since that is how we know if class
+ // unloading occurred.
+ runtime->GetClassLinker()->CleanupClassLoaders();
+ {
+ WriterMutexLock mu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+ // Reclaim unmarked objects.
+ Sweep(false);
+ // Swap the live and mark bitmaps for each space which we modified space. This is an
+ // optimization that enables us to not clear live bits inside of the sweep. Only swaps unbound
+ // bitmaps.
+ SwapBitmaps();
+ // Unbind the live and mark bitmaps.
+ GetHeap()->UnBindBitmaps();
+ }
+}
+
+// We want to avoid checking for every reference if it's within the page or
+// not. This can be done if we know where in the page the holder object lies.
+// If it doesn't overlap either boundaries then we can skip the checks.
+template <bool kCheckBegin, bool kCheckEnd>
+class MarkCompact::RefsUpdateVisitor {
+ public:
+ explicit RefsUpdateVisitor(MarkCompact* collector,
+ mirror::Object* obj,
+ uint8_t* begin,
+ uint8_t* end)
+ : collector_(collector), obj_(obj), begin_(begin), end_(end) {
+ DCHECK(!kCheckBegin || begin != nullptr);
+ DCHECK(!kCheckEnd || end != nullptr);
+ }
+
+ void operator()(mirror::Object* old ATTRIBUTE_UNUSED, MemberOffset offset, bool /* is_static */)
+ const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
+ bool update = true;
+ if (kCheckBegin || kCheckEnd) {
+ uint8_t* ref = reinterpret_cast<uint8_t*>(obj_) + offset.Int32Value();
+ update = (!kCheckBegin || ref >= begin_) && (!kCheckEnd || ref < end_);
+ }
+ if (update) {
+ collector_->UpdateRef(obj_, offset);
+ }
+ }
+
+ // For object arrays we don't need to check boundaries here as it's done in
+ // VisitReferenes().
+ // TODO: Optimize reference updating using SIMD instructions. Object arrays
+ // are perfect as all references are tightly packed.
+ void operator()(mirror::Object* old ATTRIBUTE_UNUSED,
+ MemberOffset offset,
+ bool /*is_static*/,
+ bool /*is_obj_array*/)
+ const ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
+ collector_->UpdateRef(obj_, offset);
+ }
+
+ void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+ ALWAYS_INLINE
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!root->IsNull()) {
+ VisitRoot(root);
+ }
+ }
+
+ void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+ ALWAYS_INLINE
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ collector_->UpdateRoot(root);
+ }
+
+ private:
+ MarkCompact* const collector_;
+ mirror::Object* const obj_;
+ uint8_t* const begin_;
+ uint8_t* const end_;
+};
+
+bool MarkCompact::IsValidObject(mirror::Object* obj) const {
+ mirror::Class* klass = obj->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ if (!heap_->GetVerification()->IsValidHeapObjectAddress(klass)) {
+ return false;
+ }
+ return heap_->GetVerification()->IsValidClassUnchecked<kWithFromSpaceBarrier>(
+ obj->GetClass<kVerifyNone, kWithFromSpaceBarrier>());
+}
+
+template <typename Callback>
+void MarkCompact::VerifyObject(mirror::Object* ref, Callback& callback) const {
+ if (kIsDebugBuild) {
+ mirror::Class* klass = ref->GetClass<kVerifyNone, kWithFromSpaceBarrier>();
+ mirror::Class* pre_compact_klass = ref->GetClass<kVerifyNone, kWithoutReadBarrier>();
+ mirror::Class* klass_klass = klass->GetClass<kVerifyNone, kWithFromSpaceBarrier>();
+ mirror::Class* klass_klass_klass = klass_klass->GetClass<kVerifyNone, kWithFromSpaceBarrier>();
+ if (bump_pointer_space_->HasAddress(pre_compact_klass) &&
+ reinterpret_cast<uint8_t*>(pre_compact_klass) < black_allocations_begin_) {
+ CHECK(moving_space_bitmap_->Test(pre_compact_klass))
+ << "ref=" << ref
+ << " post_compact_end=" << static_cast<void*>(post_compact_end_)
+ << " pre_compact_klass=" << pre_compact_klass
+ << " black_allocations_begin=" << static_cast<void*>(black_allocations_begin_);
+ CHECK(live_words_bitmap_->Test(pre_compact_klass));
+ }
+ if (!IsValidObject(ref)) {
+ std::ostringstream oss;
+ oss << "Invalid object: "
+ << "ref=" << ref
+ << " klass=" << klass
+ << " klass_klass=" << klass_klass
+ << " klass_klass_klass=" << klass_klass_klass
+ << " pre_compact_klass=" << pre_compact_klass
+ << " from_space_begin=" << static_cast<void*>(from_space_begin_)
+ << " pre_compact_begin=" << static_cast<void*>(bump_pointer_space_->Begin())
+ << " post_compact_end=" << static_cast<void*>(post_compact_end_)
+ << " black_allocations_begin=" << static_cast<void*>(black_allocations_begin_);
+
+ // Call callback before dumping larger data like RAM and space dumps.
+ callback(oss);
+
+ oss << " \nobject="
+ << heap_->GetVerification()->DumpRAMAroundAddress(reinterpret_cast<uintptr_t>(ref), 128)
+ << " \nklass(from)="
+ << heap_->GetVerification()->DumpRAMAroundAddress(reinterpret_cast<uintptr_t>(klass), 128)
+ << "spaces:\n";
+ heap_->DumpSpaces(oss);
+ LOG(FATAL) << oss.str();
+ }
+ }
+}
+
+void MarkCompact::CompactPage(mirror::Object* obj, uint32_t offset, uint8_t* addr) {
+ DCHECK(moving_space_bitmap_->Test(obj)
+ && live_words_bitmap_->Test(obj));
+ DCHECK(live_words_bitmap_->Test(offset)) << "obj=" << obj
+ << " offset=" << offset
+ << " addr=" << static_cast<void*>(addr)
+ << " black_allocs_begin="
+ << static_cast<void*>(black_allocations_begin_)
+ << " post_compact_addr="
+ << static_cast<void*>(post_compact_end_);
+ uint8_t* const start_addr = addr;
+ // How many distinct live-strides do we have.
+ size_t stride_count = 0;
+ uint8_t* last_stride = addr;
+ uint32_t last_stride_begin = 0;
+ auto verify_obj_callback = [&] (std::ostream& os) {
+ os << " stride_count=" << stride_count
+ << " last_stride=" << static_cast<void*>(last_stride)
+ << " offset=" << offset
+ << " start_addr=" << static_cast<void*>(start_addr);
+ };
+ obj = GetFromSpaceAddr(obj);
+ live_words_bitmap_->VisitLiveStrides(offset,
+ black_allocations_begin_,
+ kPageSize,
+ [&addr,
+ &last_stride,
+ &stride_count,
+ &last_stride_begin,
+ verify_obj_callback,
+ this] (uint32_t stride_begin,
+ size_t stride_size,
+ bool /*is_last*/)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ const size_t stride_in_bytes = stride_size * kAlignment;
+ DCHECK_LE(stride_in_bytes, kPageSize);
+ last_stride_begin = stride_begin;
+ DCHECK(IsAligned<kAlignment>(addr));
+ memcpy(addr,
+ from_space_begin_ + stride_begin * kAlignment,
+ stride_in_bytes);
+ if (kIsDebugBuild) {
+ uint8_t* space_begin = bump_pointer_space_->Begin();
+ // We can interpret the first word of the stride as an
+ // obj only from second stride onwards, as the first
+ // stride's first-object may have started on previous
+ // page. The only exception is the first page of the
+ // moving space.
+ if (stride_count > 0
+ || stride_begin * kAlignment < kPageSize) {
+ mirror::Object* o =
+ reinterpret_cast<mirror::Object*>(space_begin
+ + stride_begin
+ * kAlignment);
+ CHECK(live_words_bitmap_->Test(o)) << "ref=" << o;
+ CHECK(moving_space_bitmap_->Test(o))
+ << "ref=" << o
+ << " bitmap: "
+ << moving_space_bitmap_->DumpMemAround(o);
+ VerifyObject(reinterpret_cast<mirror::Object*>(addr),
+ verify_obj_callback);
+ }
+ }
+ last_stride = addr;
+ addr += stride_in_bytes;
+ stride_count++;
+ });
+ DCHECK_LT(last_stride, start_addr + kPageSize);
+ DCHECK_GT(stride_count, 0u);
+ size_t obj_size = 0;
+ uint32_t offset_within_obj = offset * kAlignment
+ - (reinterpret_cast<uint8_t*>(obj) - from_space_begin_);
+ // First object
+ if (offset_within_obj > 0) {
+ mirror::Object* to_ref = reinterpret_cast<mirror::Object*>(start_addr - offset_within_obj);
+ if (stride_count > 1) {
+ RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/false> visitor(this,
+ to_ref,
+ start_addr,
+ nullptr);
+ obj_size = obj->VisitRefsForCompaction</*kFetchObjSize*/true, /*kVisitNativeRoots*/false>(
+ visitor, MemberOffset(offset_within_obj), MemberOffset(-1));
+ } else {
+ RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/true> visitor(this,
+ to_ref,
+ start_addr,
+ start_addr + kPageSize);
+ obj_size = obj->VisitRefsForCompaction</*kFetchObjSize*/true, /*kVisitNativeRoots*/false>(
+ visitor, MemberOffset(offset_within_obj), MemberOffset(offset_within_obj
+ + kPageSize));
+ }
+ obj_size = RoundUp(obj_size, kAlignment);
+ DCHECK_GT(obj_size, offset_within_obj);
+ obj_size -= offset_within_obj;
+ // If there is only one stride, then adjust last_stride_begin to the
+ // end of the first object.
+ if (stride_count == 1) {
+ last_stride_begin += obj_size / kAlignment;
+ }
+ }
+
+ // Except for the last page being compacted, the pages will have addr ==
+ // start_addr + kPageSize.
+ uint8_t* const end_addr = addr;
+ addr = start_addr;
+ size_t bytes_done = obj_size;
+ // All strides except the last one can be updated without any boundary
+ // checks.
+ DCHECK_LE(addr, last_stride);
+ size_t bytes_to_visit = last_stride - addr;
+ DCHECK_LE(bytes_to_visit, kPageSize);
+ while (bytes_to_visit > bytes_done) {
+ mirror::Object* ref = reinterpret_cast<mirror::Object*>(addr + bytes_done);
+ VerifyObject(ref, verify_obj_callback);
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false>
+ visitor(this, ref, nullptr, nullptr);
+ obj_size = ref->VisitRefsForCompaction(visitor, MemberOffset(0), MemberOffset(-1));
+ obj_size = RoundUp(obj_size, kAlignment);
+ bytes_done += obj_size;
+ }
+ // Last stride may have multiple objects in it and we don't know where the
+ // last object which crosses the page boundary starts, therefore check
+ // page-end in all of these objects. Also, we need to call
+ // VisitRefsForCompaction() with from-space object as we fetch object size,
+ // which in case of klass requires 'class_size_'.
+ uint8_t* from_addr = from_space_begin_ + last_stride_begin * kAlignment;
+ bytes_to_visit = end_addr - addr;
+ DCHECK_LE(bytes_to_visit, kPageSize);
+ while (bytes_to_visit > bytes_done) {
+ mirror::Object* ref = reinterpret_cast<mirror::Object*>(addr + bytes_done);
+ obj = reinterpret_cast<mirror::Object*>(from_addr);
+ VerifyObject(ref, verify_obj_callback);
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true>
+ visitor(this, ref, nullptr, start_addr + kPageSize);
+ obj_size = obj->VisitRefsForCompaction(visitor,
+ MemberOffset(0),
+ MemberOffset(end_addr - (addr + bytes_done)));
+ obj_size = RoundUp(obj_size, kAlignment);
+ from_addr += obj_size;
+ bytes_done += obj_size;
+ }
+ // The last page that we compact may have some bytes left untouched in the
+ // end, we should zero them as the kernel copies at page granularity.
+ if (UNLIKELY(bytes_done < kPageSize)) {
+ std::memset(addr + bytes_done, 0x0, kPageSize - bytes_done);
+ }
+}
+
+// We store the starting point (pre_compact_page - first_obj) and first-chunk's
+// size. If more TLAB(s) started in this page, then those chunks are identified
+// using mark bitmap. All this info is prepared in UpdateMovingSpaceBlackAllocations().
+// If we find a set bit in the bitmap, then we copy the remaining page and then
+// use the bitmap to visit each object for updating references.
+void MarkCompact::SlideBlackPage(mirror::Object* first_obj,
+ const size_t page_idx,
+ uint8_t* const pre_compact_page,
+ uint8_t* dest) {
+ DCHECK(IsAligned<kPageSize>(pre_compact_page));
+ size_t bytes_copied;
+ const uint32_t first_chunk_size = black_alloc_pages_first_chunk_size_[page_idx];
+ mirror::Object* next_page_first_obj = first_objs_moving_space_[page_idx + 1].AsMirrorPtr();
+ uint8_t* src_addr = reinterpret_cast<uint8_t*>(GetFromSpaceAddr(first_obj));
+ uint8_t* pre_compact_addr = reinterpret_cast<uint8_t*>(first_obj);
+ uint8_t* const pre_compact_page_end = pre_compact_page + kPageSize;
+ uint8_t* const dest_page_end = dest + kPageSize;
+
+ auto verify_obj_callback = [&] (std::ostream& os) {
+ os << " first_obj=" << first_obj
+ << " next_page_first_obj=" << next_page_first_obj
+ << " first_chunk_sie=" << first_chunk_size
+ << " dest=" << static_cast<void*>(dest)
+ << " pre_compact_page="
+ << static_cast<void* const>(pre_compact_page);
+ };
+ // We have empty portion at the beginning of the page. Zero it.
+ if (pre_compact_addr > pre_compact_page) {
+ bytes_copied = pre_compact_addr - pre_compact_page;
+ DCHECK_LT(bytes_copied, kPageSize);
+ std::memset(dest, 0x0, bytes_copied);
+ dest += bytes_copied;
+ } else {
+ bytes_copied = 0;
+ size_t offset = pre_compact_page - pre_compact_addr;
+ pre_compact_addr = pre_compact_page;
+ src_addr += offset;
+ DCHECK(IsAligned<kPageSize>(src_addr));
+ }
+ // Copy the first chunk of live words
+ std::memcpy(dest, src_addr, first_chunk_size);
+ // Update references in the first chunk. Use object size to find next object.
+ {
+ size_t bytes_to_visit = first_chunk_size;
+ size_t obj_size;
+ // The first object started in some previous page. So we need to check the
+ // beginning.
+ DCHECK_LE(reinterpret_cast<uint8_t*>(first_obj), pre_compact_addr);
+ size_t offset = pre_compact_addr - reinterpret_cast<uint8_t*>(first_obj);
+ if (bytes_copied == 0 && offset > 0) {
+ mirror::Object* to_obj = reinterpret_cast<mirror::Object*>(dest - offset);
+ mirror::Object* from_obj = reinterpret_cast<mirror::Object*>(src_addr - offset);
+ // If the next page's first-obj is in this page or nullptr, then we don't
+ // need to check end boundary
+ if (next_page_first_obj == nullptr
+ || (first_obj != next_page_first_obj
+ && reinterpret_cast<uint8_t*>(next_page_first_obj) <= pre_compact_page_end)) {
+ RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/false> visitor(this,
+ to_obj,
+ dest,
+ nullptr);
+ obj_size = from_obj->VisitRefsForCompaction<
+ /*kFetchObjSize*/true, /*kVisitNativeRoots*/false>(visitor,
+ MemberOffset(offset),
+ MemberOffset(-1));
+ } else {
+ RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/true> visitor(this,
+ to_obj,
+ dest,
+ dest_page_end);
+ from_obj->VisitRefsForCompaction<
+ /*kFetchObjSize*/false, /*kVisitNativeRoots*/false>(visitor,
+ MemberOffset(offset),
+ MemberOffset(offset
+ + kPageSize));
+ return;
+ }
+ obj_size = RoundUp(obj_size, kAlignment);
+ obj_size -= offset;
+ dest += obj_size;
+ bytes_to_visit -= obj_size;
+ }
+ bytes_copied += first_chunk_size;
+ // If the last object in this page is next_page_first_obj, then we need to check end boundary
+ bool check_last_obj = false;
+ if (next_page_first_obj != nullptr
+ && reinterpret_cast<uint8_t*>(next_page_first_obj) < pre_compact_page_end
+ && bytes_copied == kPageSize) {
+ size_t diff = pre_compact_page_end - reinterpret_cast<uint8_t*>(next_page_first_obj);
+ DCHECK_LE(diff, kPageSize);
+ DCHECK_LE(diff, bytes_to_visit);
+ bytes_to_visit -= diff;
+ check_last_obj = true;
+ }
+ while (bytes_to_visit > 0) {
+ mirror::Object* dest_obj = reinterpret_cast<mirror::Object*>(dest);
+ VerifyObject(dest_obj, verify_obj_callback);
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false> visitor(this,
+ dest_obj,
+ nullptr,
+ nullptr);
+ obj_size = dest_obj->VisitRefsForCompaction(visitor, MemberOffset(0), MemberOffset(-1));
+ obj_size = RoundUp(obj_size, kAlignment);
+ bytes_to_visit -= obj_size;
+ dest += obj_size;
+ }
+ DCHECK_EQ(bytes_to_visit, 0u);
+ if (check_last_obj) {
+ mirror::Object* dest_obj = reinterpret_cast<mirror::Object*>(dest);
+ VerifyObject(dest_obj, verify_obj_callback);
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true> visitor(this,
+ dest_obj,
+ nullptr,
+ dest_page_end);
+ mirror::Object* obj = GetFromSpaceAddr(next_page_first_obj);
+ obj->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor,
+ MemberOffset(0),
+ MemberOffset(dest_page_end - dest));
+ return;
+ }
+ }
+
+ // Probably a TLAB finished on this page and/or a new TLAB started as well.
+ if (bytes_copied < kPageSize) {
+ src_addr += first_chunk_size;
+ pre_compact_addr += first_chunk_size;
+ // Use mark-bitmap to identify where objects are. First call
+ // VisitMarkedRange for only the first marked bit. If found, zero all bytes
+ // until that object and then call memcpy on the rest of the page.
+ // Then call VisitMarkedRange for all marked bits *after* the one found in
+ // this invocation. This time to visit references.
+ uintptr_t start_visit = reinterpret_cast<uintptr_t>(pre_compact_addr);
+ uintptr_t page_end = reinterpret_cast<uintptr_t>(pre_compact_page_end);
+ mirror::Object* found_obj = nullptr;
+ moving_space_bitmap_->VisitMarkedRange</*kVisitOnce*/true>(start_visit,
+ page_end,
+ [&found_obj](mirror::Object* obj) {
+ found_obj = obj;
+ });
+ size_t remaining_bytes = kPageSize - bytes_copied;
+ if (found_obj == nullptr) {
+ // No more black objects in this page. Zero the remaining bytes and return.
+ std::memset(dest, 0x0, remaining_bytes);
+ return;
+ }
+ // Copy everything in this page, which includes any zeroed regions
+ // in-between.
+ std::memcpy(dest, src_addr, remaining_bytes);
+ DCHECK_LT(reinterpret_cast<uintptr_t>(found_obj), page_end);
+ moving_space_bitmap_->VisitMarkedRange(
+ reinterpret_cast<uintptr_t>(found_obj) + mirror::kObjectHeaderSize,
+ page_end,
+ [&found_obj, pre_compact_addr, dest, this, verify_obj_callback] (mirror::Object* obj)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ ptrdiff_t diff = reinterpret_cast<uint8_t*>(found_obj) - pre_compact_addr;
+ mirror::Object* ref = reinterpret_cast<mirror::Object*>(dest + diff);
+ VerifyObject(ref, verify_obj_callback);
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false>
+ visitor(this, ref, nullptr, nullptr);
+ ref->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor,
+ MemberOffset(0),
+ MemberOffset(-1));
+ // Remember for next round.
+ found_obj = obj;
+ });
+ // found_obj may have been updated in VisitMarkedRange. Visit the last found
+ // object.
+ DCHECK_GT(reinterpret_cast<uint8_t*>(found_obj), pre_compact_addr);
+ DCHECK_LT(reinterpret_cast<uintptr_t>(found_obj), page_end);
+ ptrdiff_t diff = reinterpret_cast<uint8_t*>(found_obj) - pre_compact_addr;
+ mirror::Object* ref = reinterpret_cast<mirror::Object*>(dest + diff);
+ VerifyObject(ref, verify_obj_callback);
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true> visitor(this,
+ ref,
+ nullptr,
+ dest_page_end);
+ ref->VisitRefsForCompaction</*kFetchObjSize*/false>(
+ visitor, MemberOffset(0), MemberOffset(page_end -
+ reinterpret_cast<uintptr_t>(found_obj)));
+ }
+}
+
+template <bool kFallback>
+void MarkCompact::CompactMovingSpace(uint8_t* page) {
+ // For every page we have a starting object, which may have started in some
+ // preceding page, and an offset within that object from where we must start
+ // copying.
+ // Consult the live-words bitmap to copy all contiguously live words at a
+ // time. These words may constitute multiple objects. To avoid the need for
+ // consulting mark-bitmap to find where does the next live object start, we
+ // use the object-size returned by VisitRefsForCompaction.
+ //
+ // TODO: Should we do this in reverse? If the probability of accessing an object
+ // is inversely proportional to the object's age, then it may make sense.
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ uint8_t* to_space = bump_pointer_space_->Begin();
+ auto copy_ioctl = [this] (void* dst, void* buffer) {
+ struct uffdio_copy uffd_copy;
+ uffd_copy.src = reinterpret_cast<uintptr_t>(buffer);
+ uffd_copy.dst = reinterpret_cast<uintptr_t>(dst);
+ uffd_copy.len = kPageSize;
+ uffd_copy.mode = 0;
+ CHECK_EQ(ioctl(uffd_, UFFDIO_COPY, &uffd_copy), 0)
+ << "ioctl: copy " << strerror(errno);
+ DCHECK_EQ(uffd_copy.copy, static_cast<ssize_t>(kPageSize));
+ };
+ size_t idx = 0;
+ while (idx < moving_first_objs_count_) {
+ // Relaxed memory-order is used as the subsequent ioctl syscall will act as a fence.
+ // In the concurrent case (!kFallback) we need to ensure that the update to
+ // moving_spaces_status_[idx] is released before the contents of the page.
+ if (kFallback
+ || moving_pages_status_[idx].exchange(PageState::kCompacting, std::memory_order_relaxed)
+ == PageState::kUncompacted) {
+ CompactPage(first_objs_moving_space_[idx].AsMirrorPtr(),
+ pre_compact_offset_moving_space_[idx],
+ kFallback ? to_space : page);
+ if (!kFallback) {
+ copy_ioctl(to_space, page);
+ }
+ }
+ to_space += kPageSize;
+ idx++;
+ }
+ // Allocated-black pages
+ size_t count = moving_first_objs_count_ + black_page_count_;
+ uint8_t* pre_compact_page = black_allocations_begin_;
+ DCHECK(IsAligned<kPageSize>(pre_compact_page));
+ while (idx < count) {
+ mirror::Object* first_obj = first_objs_moving_space_[idx].AsMirrorPtr();
+ if (first_obj != nullptr
+ && (kFallback
+ || moving_pages_status_[idx].exchange(PageState::kCompacting, std::memory_order_relaxed)
+ == PageState::kUncompacted)) {
+ DCHECK_GT(black_alloc_pages_first_chunk_size_[idx], 0u);
+ SlideBlackPage(first_obj,
+ idx,
+ pre_compact_page,
+ kFallback ? to_space : page);
+ if (!kFallback) {
+ copy_ioctl(to_space, page);
+ }
+ }
+ pre_compact_page += kPageSize;
+ to_space += kPageSize;
+ idx++;
+ }
+}
+
+void MarkCompact::UpdateNonMovingPage(mirror::Object* first, uint8_t* page) {
+ DCHECK_LT(reinterpret_cast<uint8_t*>(first), page + kPageSize);
+ // For every object found in the page, visit the previous object. This ensures
+ // that we can visit without checking page-end boundary.
+ // Call VisitRefsForCompaction with from-space read-barrier as the klass object and
+ // super-class loads require it.
+ // TODO: Set kVisitNativeRoots to false once we implement concurrent
+ // compaction
+ mirror::Object* curr_obj = first;
+ non_moving_space_bitmap_->VisitMarkedRange(
+ reinterpret_cast<uintptr_t>(first) + mirror::kObjectHeaderSize,
+ reinterpret_cast<uintptr_t>(page + kPageSize),
+ [&](mirror::Object* next_obj) {
+ // TODO: Once non-moving space update becomes concurrent, we'll
+ // require fetching the from-space address of 'curr_obj' and then call
+ // visitor on that.
+ if (reinterpret_cast<uint8_t*>(curr_obj) < page) {
+ RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/false>
+ visitor(this, curr_obj, page, page + kPageSize);
+ MemberOffset begin_offset(page - reinterpret_cast<uint8_t*>(curr_obj));
+ // Native roots shouldn't be visited as they are done when this
+ // object's beginning was visited in the preceding page.
+ curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false, /*kVisitNativeRoots*/false>(
+ visitor, begin_offset, MemberOffset(-1));
+ } else {
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false>
+ visitor(this, curr_obj, page, page + kPageSize);
+ curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor,
+ MemberOffset(0),
+ MemberOffset(-1));
+ }
+ curr_obj = next_obj;
+ });
+
+ MemberOffset end_offset(page + kPageSize - reinterpret_cast<uint8_t*>(curr_obj));
+ if (reinterpret_cast<uint8_t*>(curr_obj) < page) {
+ RefsUpdateVisitor</*kCheckBegin*/true, /*kCheckEnd*/true>
+ visitor(this, curr_obj, page, page + kPageSize);
+ curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false, /*kVisitNativeRoots*/false>(
+ visitor, MemberOffset(page - reinterpret_cast<uint8_t*>(curr_obj)), end_offset);
+ } else {
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/true>
+ visitor(this, curr_obj, page, page + kPageSize);
+ curr_obj->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor, MemberOffset(0), end_offset);
+ }
+}
+
+void MarkCompact::UpdateNonMovingSpace() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ uint8_t* page = non_moving_space_->Begin();
+ for (size_t i = 0; i < non_moving_first_objs_count_; i++) {
+ mirror::Object* obj = first_objs_non_moving_space_[i].AsMirrorPtr();
+ // null means there are no objects on the page to update references.
+ if (obj != nullptr) {
+ UpdateNonMovingPage(obj, page);
+ }
+ page += kPageSize;
+ }
+}
+
+void MarkCompact::UpdateMovingSpaceBlackAllocations() {
+ // For sliding black pages, we need the first-object, which overlaps with the
+ // first byte of the page. Additionally, we compute the size of first chunk of
+ // black objects. This will suffice for most black pages. Unlike, compaction
+ // pages, here we don't need to pre-compute the offset within first-obj from
+ // where sliding has to start. That can be calculated using the pre-compact
+ // address of the page. Therefore, to save space, we store the first chunk's
+ // size in black_alloc_pages_first_chunk_size_ array.
+ // For the pages which may have holes after the first chunk, which could happen
+ // if a new TLAB starts in the middle of the page, we mark the objects in
+ // the mark-bitmap. So, if the first-chunk size is smaller than kPageSize,
+ // then we use the mark-bitmap for the remainder of the page.
+ uint8_t* const begin = bump_pointer_space_->Begin();
+ uint8_t* black_allocs = black_allocations_begin_;
+ DCHECK_LE(begin, black_allocs);
+ size_t consumed_blocks_count = 0;
+ size_t first_block_size;
+ // Get the list of all blocks allocated in the bump-pointer space.
+ std::vector<size_t>* block_sizes = bump_pointer_space_->GetBlockSizes(thread_running_gc_,
+ &first_block_size);
+ DCHECK_LE(first_block_size, (size_t)(black_allocs - begin));
+ if (block_sizes != nullptr) {
+ size_t black_page_idx = moving_first_objs_count_;
+ uint8_t* block_end = begin + first_block_size;
+ uint32_t remaining_chunk_size = 0;
+ uint32_t first_chunk_size = 0;
+ mirror::Object* first_obj = nullptr;
+ for (size_t block_size : *block_sizes) {
+ block_end += block_size;
+ // Skip the blocks that are prior to the black allocations. These will be
+ // merged with the main-block later.
+ if (black_allocs >= block_end) {
+ consumed_blocks_count++;
+ continue;
+ }
+ mirror::Object* obj = reinterpret_cast<mirror::Object*>(black_allocs);
+ bool set_mark_bit = remaining_chunk_size > 0;
+ // We don't know how many objects are allocated in the current block. When we hit
+ // a null assume it's the end. This works as every block is expected to
+ // have objects allocated linearly using bump-pointer.
+ // BumpPointerSpace::Walk() also works similarly.
+ while (black_allocs < block_end
+ && obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() != nullptr) {
+ RememberDexCaches(obj);
+ if (first_obj == nullptr) {
+ first_obj = obj;
+ }
+ // We only need the mark-bitmap in the pages wherein a new TLAB starts in
+ // the middle of the page.
+ if (set_mark_bit) {
+ moving_space_bitmap_->Set(obj);
+ }
+ size_t obj_size = RoundUp(obj->SizeOf(), kAlignment);
+ // Handle objects which cross page boundary, including objects larger
+ // than page size.
+ if (remaining_chunk_size + obj_size >= kPageSize) {
+ set_mark_bit = false;
+ first_chunk_size += kPageSize - remaining_chunk_size;
+ remaining_chunk_size += obj_size;
+ // We should not store first-object and remaining_chunk_size if there were
+ // unused bytes before this TLAB, in which case we must have already
+ // stored the values (below).
+ if (black_alloc_pages_first_chunk_size_[black_page_idx] == 0) {
+ black_alloc_pages_first_chunk_size_[black_page_idx] = first_chunk_size;
+ first_objs_moving_space_[black_page_idx].Assign(first_obj);
+ }
+ black_page_idx++;
+ remaining_chunk_size -= kPageSize;
+ // Consume an object larger than page size.
+ while (remaining_chunk_size >= kPageSize) {
+ black_alloc_pages_first_chunk_size_[black_page_idx] = kPageSize;
+ first_objs_moving_space_[black_page_idx].Assign(obj);
+ black_page_idx++;
+ remaining_chunk_size -= kPageSize;
+ }
+ first_obj = remaining_chunk_size > 0 ? obj : nullptr;
+ first_chunk_size = remaining_chunk_size;
+ } else {
+ DCHECK_LE(first_chunk_size, remaining_chunk_size);
+ first_chunk_size += obj_size;
+ remaining_chunk_size += obj_size;
+ }
+ black_allocs += obj_size;
+ obj = reinterpret_cast<mirror::Object*>(black_allocs);
+ }
+ DCHECK_LE(black_allocs, block_end);
+ DCHECK_LT(remaining_chunk_size, kPageSize);
+ // consume the unallocated portion of the block
+ if (black_allocs < block_end) {
+ // first-chunk of the current page ends here. Store it.
+ if (first_chunk_size > 0) {
+ black_alloc_pages_first_chunk_size_[black_page_idx] = first_chunk_size;
+ first_objs_moving_space_[black_page_idx].Assign(first_obj);
+ first_chunk_size = 0;
+ }
+ first_obj = nullptr;
+ size_t page_remaining = kPageSize - remaining_chunk_size;
+ size_t block_remaining = block_end - black_allocs;
+ if (page_remaining <= block_remaining) {
+ block_remaining -= page_remaining;
+ // current page and the subsequent empty pages in the block
+ black_page_idx += 1 + block_remaining / kPageSize;
+ remaining_chunk_size = block_remaining % kPageSize;
+ } else {
+ remaining_chunk_size += block_remaining;
+ }
+ black_allocs = block_end;
+ }
+ }
+ black_page_count_ = black_page_idx - moving_first_objs_count_;
+ delete block_sizes;
+ }
+ // Update bump-pointer space by consuming all the pre-black blocks into the
+ // main one.
+ bump_pointer_space_->SetBlockSizes(thread_running_gc_,
+ post_compact_end_ - begin,
+ consumed_blocks_count);
+}
+
+void MarkCompact::UpdateNonMovingSpaceBlackAllocations() {
+ accounting::ObjectStack* stack = heap_->GetAllocationStack();
+ const StackReference<mirror::Object>* limit = stack->End();
+ uint8_t* const space_begin = non_moving_space_->Begin();
+ for (StackReference<mirror::Object>* it = stack->Begin(); it != limit; ++it) {
+ mirror::Object* obj = it->AsMirrorPtr();
+ if (obj != nullptr && non_moving_space_bitmap_->HasAddress(obj)) {
+ non_moving_space_bitmap_->Set(obj);
+ // Clear so that we don't try to set the bit again in the next GC-cycle.
+ it->Clear();
+ size_t idx = (reinterpret_cast<uint8_t*>(obj) - space_begin) / kPageSize;
+ uint8_t* page_begin = AlignDown(reinterpret_cast<uint8_t*>(obj), kPageSize);
+ mirror::Object* first_obj = first_objs_non_moving_space_[idx].AsMirrorPtr();
+ if (first_obj == nullptr
+ || (obj < first_obj && reinterpret_cast<uint8_t*>(first_obj) > page_begin)) {
+ first_objs_non_moving_space_[idx].Assign(obj);
+ }
+ mirror::Object* next_page_first_obj = first_objs_non_moving_space_[++idx].AsMirrorPtr();
+ uint8_t* next_page_begin = page_begin + kPageSize;
+ if (next_page_first_obj == nullptr
+ || reinterpret_cast<uint8_t*>(next_page_first_obj) > next_page_begin) {
+ size_t obj_size = RoundUp(obj->SizeOf<kDefaultVerifyFlags>(), kAlignment);
+ uint8_t* obj_end = reinterpret_cast<uint8_t*>(obj) + obj_size;
+ while (next_page_begin < obj_end) {
+ first_objs_non_moving_space_[idx++].Assign(obj);
+ next_page_begin += kPageSize;
+ }
+ }
+ // update first_objs count in case we went past non_moving_first_objs_count_
+ non_moving_first_objs_count_ = std::max(non_moving_first_objs_count_, idx);
+ }
+ }
+}
+
+class MarkCompact::ImmuneSpaceUpdateObjVisitor {
+ public:
+ explicit ImmuneSpaceUpdateObjVisitor(MarkCompact* collector) : collector_(collector) {}
+
+ ALWAYS_INLINE void operator()(mirror::Object* obj) const REQUIRES(Locks::mutator_lock_) {
+ RefsUpdateVisitor</*kCheckBegin*/false, /*kCheckEnd*/false> visitor(collector_,
+ obj,
+ /*begin_*/nullptr,
+ /*end_*/nullptr);
+ obj->VisitRefsForCompaction</*kFetchObjSize*/false>(visitor,
+ MemberOffset(0),
+ MemberOffset(-1));
+ }
+
+ static void Callback(mirror::Object* obj, void* arg) REQUIRES(Locks::mutator_lock_) {
+ reinterpret_cast<ImmuneSpaceUpdateObjVisitor*>(arg)->operator()(obj);
+ }
+
+ private:
+ MarkCompact* const collector_;
+};
+
+// TODO: JVMTI redefinition leads to situations wherein new class object(s) and the
+// corresponding native roots are setup but are not linked to class tables and
+// therefore are not accessible, leading to memory corruption.
+class MarkCompact::NativeRootsUpdateVisitor : public ClassLoaderVisitor, public DexCacheVisitor {
+ public:
+ explicit NativeRootsUpdateVisitor(MarkCompact* collector, PointerSize pointer_size)
+ : collector_(collector), pointer_size_(pointer_size) {}
+
+ ~NativeRootsUpdateVisitor() {
+ LOG(INFO) << "num_classes: " << classes_visited_.size()
+ << " num_dex_caches: " << dex_caches_visited_.size();
+ }
+
+ void Visit(ObjPtr<mirror::ClassLoader> class_loader) override
+ REQUIRES_SHARED(Locks::classlinker_classes_lock_, Locks::mutator_lock_) {
+ ClassTable* const class_table = class_loader->GetClassTable();
+ if (class_table != nullptr) {
+ class_table->VisitClassesAndRoots(*this);
+ }
+ }
+
+ void Visit(ObjPtr<mirror::DexCache> dex_cache) override
+ REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_) {
+ if (!dex_cache.IsNull()) {
+ uint32_t cache = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(dex_cache.Ptr()));
+ if (dex_caches_visited_.insert(cache).second) {
+ dex_cache->VisitNativeRoots<kDefaultVerifyFlags, kWithoutReadBarrier>(*this);
+ collector_->dex_caches_.erase(cache);
+ }
+ }
+ }
+
+ void VisitDexCache(mirror::DexCache* dex_cache)
+ REQUIRES_SHARED(Locks::dex_lock_, Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_) {
+ dex_cache->VisitNativeRoots<kDefaultVerifyFlags, kWithoutReadBarrier>(*this);
+ }
+
+ void operator()(mirror::Object* obj)
+ ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+ DCHECK(obj->IsClass<kDefaultVerifyFlags>());
+ ObjPtr<mirror::Class> klass = obj->AsClass<kDefaultVerifyFlags>();
+ VisitClassRoots(klass);
+ }
+
+ // For ClassTable::Visit()
+ bool operator()(ObjPtr<mirror::Class> klass)
+ ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!klass.IsNull()) {
+ VisitClassRoots(klass);
+ }
+ return true;
+ }
+
+ void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+ ALWAYS_INLINE
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!root->IsNull()) {
+ VisitRoot(root);
+ }
+ }
+
+ void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+ ALWAYS_INLINE
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ collector_->UpdateRoot(root);
+ }
+
+ private:
+ void VisitClassRoots(ObjPtr<mirror::Class> klass)
+ ALWAYS_INLINE REQUIRES_SHARED(Locks::mutator_lock_) {
+ mirror::Class* klass_ptr = klass.Ptr();
+ uint32_t k = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(klass_ptr));
+ // No reason to visit native roots of class in immune spaces.
+ if ((collector_->bump_pointer_space_->HasAddress(klass_ptr)
+ || collector_->non_moving_space_->HasAddress(klass_ptr))
+ && classes_visited_.insert(k).second) {
+ klass->VisitNativeRoots<kWithoutReadBarrier, /*kVisitProxyMethod*/false>(*this,
+ pointer_size_);
+ klass->VisitObsoleteDexCaches<kWithoutReadBarrier>(*this);
+ klass->VisitObsoleteClass<kWithoutReadBarrier>(*this);
+ }
+ }
+
+ std::unordered_set<uint32_t> dex_caches_visited_;
+ std::unordered_set<uint32_t> classes_visited_;
+ MarkCompact* const collector_;
+ PointerSize pointer_size_;
+};
+
+void MarkCompact::PreCompactionPhase() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ Runtime* runtime = Runtime::Current();
+ non_moving_space_bitmap_ = non_moving_space_->GetLiveBitmap();
+ if (kIsDebugBuild) {
+ pthread_attr_t attr;
+ size_t stack_size;
+ void* stack_addr;
+ pthread_getattr_np(pthread_self(), &attr);
+ pthread_attr_getstack(&attr, &stack_addr, &stack_size);
+ pthread_attr_destroy(&attr);
+ stack_addr_ = stack_addr;
+ stack_end_ = reinterpret_cast<char*>(stack_addr) + stack_size;
+ }
+
+ compacting_ = true;
+
+ {
+ TimingLogger::ScopedTiming t2("(Paused)UpdateCompactionDataStructures", GetTimings());
+ ReaderMutexLock rmu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+ // Refresh data-structures to catch-up on allocations that may have
+ // happened since marking-phase pause.
+ // There could be several TLABs that got allocated since marking pause. We
+ // don't want to compact them and instead update the TLAB info in TLS and
+ // let mutators continue to use the TLABs.
+ // We need to set all the bits in live-words bitmap corresponding to allocated
+ // objects. Also, we need to find the objects that are overlapping with
+ // page-begin boundaries. Unlike objects allocated before
+ // black_allocations_begin_, which can be identified via mark-bitmap, we can get
+ // this info only via walking the space past black_allocations_begin_, which
+ // involves fetching object size.
+ // TODO: We can reduce the time spent on this in a pause by performing one
+ // round of this concurrently prior to the pause.
+ UpdateMovingSpaceBlackAllocations();
+ // TODO: If we want to avoid this allocation in a pause then we will have to
+ // allocate an array for the entire moving-space size, which can be made
+ // part of info_map_.
+ moving_pages_status_ = new Atomic<PageState>[moving_first_objs_count_ + black_page_count_];
+ if (kIsDebugBuild) {
+ size_t len = moving_first_objs_count_ + black_page_count_;
+ for (size_t i = 0; i < len; i++) {
+ CHECK_EQ(moving_pages_status_[i].load(std::memory_order_relaxed), PageState::kUncompacted);
+ }
+ }
+ // Iterate over the allocation_stack_, for every object in the non-moving
+ // space:
+ // 1. Mark the object in live bitmap
+ // 2. Erase the object from allocation stack
+ // 3. In the corresponding page, if the first-object vector needs updating
+ // then do so.
+ UpdateNonMovingSpaceBlackAllocations();
+
+ heap_->GetReferenceProcessor()->UpdateRoots(this);
+ }
+
+ {
+ // Thread roots must be updated first (before space mremap and native root
+ // updation) to ensure that pre-update content is accessible.
+ TimingLogger::ScopedTiming t2("(Paused)UpdateThreadRoots", GetTimings());
+ MutexLock mu1(thread_running_gc_, *Locks::runtime_shutdown_lock_);
+ MutexLock mu2(thread_running_gc_, *Locks::thread_list_lock_);
+ std::list<Thread*> thread_list = runtime->GetThreadList()->GetList();
+ for (Thread* thread : thread_list) {
+ thread->VisitRoots(this, kVisitRootFlagAllRoots);
+ thread->AdjustTlab(black_objs_slide_diff_);
+ }
+ }
+
+ {
+ // Native roots must be updated before updating system weaks as class linker
+ // holds roots to class loaders and dex-caches as weak roots. Also, space
+ // mremap must be done after this step as we require reading
+ // class/dex-cache/class-loader content for updating native roots.
+ TimingLogger::ScopedTiming t2("(Paused)UpdateNativeRoots", GetTimings());
+ ClassLinker* class_linker = runtime->GetClassLinker();
+ NativeRootsUpdateVisitor visitor(this, class_linker->GetImagePointerSize());
+ {
+ ReaderMutexLock rmu(thread_running_gc_, *Locks::classlinker_classes_lock_);
+ class_linker->VisitBootClasses(&visitor);
+ class_linker->VisitClassLoaders(&visitor);
+ }
+ {
+ WriterMutexLock wmu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+ ReaderMutexLock rmu(thread_running_gc_, *Locks::dex_lock_);
+ class_linker->VisitDexCaches(&visitor);
+ for (uint32_t cache : dex_caches_) {
+ visitor.VisitDexCache(reinterpret_cast<mirror::DexCache*>(cache));
+ }
+ }
+ dex_caches_.clear();
+ }
+
+ SweepSystemWeaks(thread_running_gc_, runtime, /*paused*/true);
+ KernelPreparation();
+
+ {
+ TimingLogger::ScopedTiming t2("(Paused)UpdateConcurrentRoots", GetTimings());
+ runtime->VisitConcurrentRoots(this, kVisitRootFlagAllRoots);
+ }
+ {
+ // TODO: don't visit the transaction roots if it's not active.
+ TimingLogger::ScopedTiming t2("(Paused)UpdateNonThreadRoots", GetTimings());
+ runtime->VisitNonThreadRoots(this);
+ }
+
+ {
+ // TODO: Immune space updation has to happen either before or after
+ // remapping pre-compact pages to from-space. And depending on when it's
+ // done, we have to invoke VisitRefsForCompaction() with or without
+ // read-barrier.
+ TimingLogger::ScopedTiming t2("(Paused)UpdateImmuneSpaces", GetTimings());
+ accounting::CardTable* const card_table = heap_->GetCardTable();
+ for (auto& space : immune_spaces_.GetSpaces()) {
+ DCHECK(space->IsImageSpace() || space->IsZygoteSpace());
+ accounting::ContinuousSpaceBitmap* live_bitmap = space->GetLiveBitmap();
+ accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+ ImmuneSpaceUpdateObjVisitor visitor(this);
+ if (table != nullptr) {
+ table->ProcessCards();
+ table->VisitObjects(ImmuneSpaceUpdateObjVisitor::Callback, &visitor);
+ } else {
+ WriterMutexLock wmu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+ card_table->Scan<false>(
+ live_bitmap,
+ space->Begin(),
+ space->Limit(),
+ visitor,
+ accounting::CardTable::kCardDirty - 1);
+ }
+ }
+ }
+
+ UpdateNonMovingSpace();
+ // fallback mode
+ if (uffd_ == kFallbackMode) {
+ CompactMovingSpace</*kFallback*/true>();
+ } else {
+ // We must start worker threads before resuming mutators to avoid deadlocks.
+ heap_->GetThreadPool()->StartWorkers(thread_running_gc_);
+ }
+ stack_end_ = nullptr;
+}
+
+void MarkCompact::KernelPreparation() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ // TODO: Create mapping's at 2MB aligned addresses to benefit from optimized
+ // mremap.
+ size_t size = bump_pointer_space_->Capacity();
+ uint8_t* begin = bump_pointer_space_->Begin();
+ int flags = MREMAP_MAYMOVE | MREMAP_FIXED;
+ if (gHaveMremapDontunmap) {
+ flags |= MREMAP_DONTUNMAP;
+ }
+
+ void* ret = mremap(begin, size, size, flags, from_space_begin_);
+ CHECK_EQ(ret, static_cast<void*>(from_space_begin_))
+ << "mremap to move pages from moving space to from-space failed: " << strerror(errno)
+ << ". moving-space-addr=" << reinterpret_cast<void*>(begin)
+ << " size=" << size;
+
+ // Without MREMAP_DONTUNMAP the source mapping is unmapped by mremap. So mmap
+ // the moving space again.
+ if (!gHaveMremapDontunmap) {
+ ret = mmap(begin, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
+ CHECK_EQ(ret, static_cast<void*>(begin)) << "mmap for moving space failed: " << strerror(errno);
+ }
+
+ DCHECK_EQ(mprotect(from_space_begin_, size, PROT_READ), 0)
+ << "mprotect failed: " << strerror(errno);
+
+ if (uffd_ >= 0) {
+ // Userfaultfd registration
+ struct uffdio_register uffd_register;
+ uffd_register.range.start = reinterpret_cast<uintptr_t>(begin);
+ uffd_register.range.len = size;
+ uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
+ CHECK_EQ(ioctl(uffd_, UFFDIO_REGISTER, &uffd_register), 0)
+ << "ioctl_userfaultfd: register moving-space: " << strerror(errno);
+ }
+}
+
+void MarkCompact::ConcurrentCompaction(uint8_t* page) {
+ struct uffd_msg msg;
+ uint8_t* unused_space_begin = bump_pointer_space_->Begin()
+ + (moving_first_objs_count_ + black_page_count_) * kPageSize;
+ DCHECK(IsAligned<kPageSize>(unused_space_begin));
+ auto zeropage_ioctl = [this] (void* addr, bool tolerate_eexist) {
+ struct uffdio_zeropage uffd_zeropage;
+ DCHECK(IsAligned<kPageSize>(addr));
+ uffd_zeropage.range.start = reinterpret_cast<uintptr_t>(addr);
+ uffd_zeropage.range.len = kPageSize;
+ uffd_zeropage.mode = 0;
+ int ret = ioctl(uffd_, UFFDIO_ZEROPAGE, &uffd_zeropage);
+ CHECK(ret == 0 || (tolerate_eexist && ret == -1 && errno == EEXIST))
+ << "ioctl: zeropage: " << strerror(errno);
+ DCHECK_EQ(uffd_zeropage.zeropage, static_cast<ssize_t>(kPageSize));
+ };
+
+ auto copy_ioctl = [this] (void* fault_page, void* src) {
+ struct uffdio_copy uffd_copy;
+ uffd_copy.src = reinterpret_cast<uintptr_t>(src);
+ uffd_copy.dst = reinterpret_cast<uintptr_t>(fault_page);
+ uffd_copy.len = kPageSize;
+ uffd_copy.mode = 0;
+ CHECK_EQ(ioctl(uffd_, UFFDIO_COPY, &uffd_copy), 0)
+ << "ioctl: copy: " << strerror(errno);
+ DCHECK_EQ(uffd_copy.copy, static_cast<ssize_t>(kPageSize));
+ };
+
+ while (true) {
+ ssize_t nread = read(uffd_, &msg, sizeof(msg));
+ CHECK_GT(nread, 0);
+ CHECK_EQ(msg.event, UFFD_EVENT_PAGEFAULT);
+ DCHECK_EQ(nread, static_cast<ssize_t>(sizeof(msg)));
+ uint8_t* fault_addr = reinterpret_cast<uint8_t*>(msg.arg.pagefault.address);
+ if (fault_addr == conc_compaction_termination_page_) {
+ // The counter doesn't need to be updated atomically as only one thread
+ // would wake up against the gc-thread's load to this fault_addr. In fact,
+ // the other threads would wake up serially because every exiting thread
+ // will wake up gc-thread, which would retry load but again would find the
+ // page missing. Also, the value will be flushed to caches due to the ioctl
+ // syscall below.
+ uint8_t ret = thread_pool_counter_--;
+ // Only the last thread should map the zeropage so that the gc-thread can
+ // proceed.
+ if (ret == 1) {
+ zeropage_ioctl(fault_addr, /*tolerate_eexist*/ false);
+ } else {
+ struct uffdio_range uffd_range;
+ uffd_range.start = msg.arg.pagefault.address;
+ uffd_range.len = kPageSize;
+ CHECK_EQ(ioctl(uffd_, UFFDIO_WAKE, &uffd_range), 0)
+ << "ioctl: wake: " << strerror(errno);
+ }
+ break;
+ }
+ DCHECK(bump_pointer_space_->HasAddress(reinterpret_cast<mirror::Object*>(fault_addr)));
+ uint8_t* fault_page = AlignDown(fault_addr, kPageSize);
+ if (fault_addr >= unused_space_begin) {
+ // There is a race which allows more than one thread to install a
+ // zero-page. But we can tolerate that. So absorb the EEXIST returned by
+ // the ioctl and move on.
+ zeropage_ioctl(fault_page, /*tolerate_eexist*/ true);
+ continue;
+ }
+ size_t page_idx = (fault_page - bump_pointer_space_->Begin()) / kPageSize;
+ PageState state = moving_pages_status_[page_idx].load(std::memory_order_relaxed);
+ if (state == PageState::kUncompacted) {
+ // Relaxed memory-order is fine as the subsequent ioctl syscall guarantees
+ // status to be flushed before this thread attempts to copy/zeropage the
+ // fault_page.
+ state = moving_pages_status_[page_idx].exchange(PageState::kCompacting,
+ std::memory_order_relaxed);
+ }
+ if (state == PageState::kCompacting) {
+ // Somebody else took (or taking) care of the page, so nothing to do.
+ continue;
+ }
+
+ if (fault_page < post_compact_end_) {
+ // The page has to be compacted.
+ CompactPage(first_objs_moving_space_[page_idx].AsMirrorPtr(),
+ pre_compact_offset_moving_space_[page_idx],
+ page);
+ copy_ioctl(fault_page, page);
+ } else {
+ // The page either has to be slid, or if it's an empty page then a
+ // zeropage needs to be mapped.
+ mirror::Object* first_obj = first_objs_moving_space_[page_idx].AsMirrorPtr();
+ if (first_obj != nullptr) {
+ DCHECK_GT(pre_compact_offset_moving_space_[page_idx], 0u);
+ uint8_t* pre_compact_page = black_allocations_begin_ + (fault_page - post_compact_end_);
+ DCHECK(IsAligned<kPageSize>(pre_compact_page));
+ SlideBlackPage(first_obj,
+ page_idx,
+ pre_compact_page,
+ page);
+ copy_ioctl(fault_page, page);
+ } else {
+ // We should never have a case where two workers are trying to install a
+ // zeropage in this range as we synchronize using
+ // moving_pages_status_[page_idx].
+ zeropage_ioctl(fault_page, /*tolerate_eexist*/ false);
+ }
+ }
+ }
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wframe-larger-than="
+void MarkCompact::CompactionPhase() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ {
+ int32_t freed_bytes = black_objs_slide_diff_;
+ bump_pointer_space_->RecordFree(freed_objects_, freed_bytes);
+ RecordFree(ObjectBytePair(freed_objects_, freed_bytes));
+ }
+
+ if (kObjPtrPoisoning) {
+ CompactMovingSpace</*kFallback*/false>(compaction_buffers_map_.Begin());
+ // madvise the page so that we can get userfaults on it. We don't need to
+ // do this when not using poisoning as in that case the address location is
+ // untouched during compaction.
+ ZeroAndReleasePages(conc_compaction_termination_page_, kPageSize);
+ } else {
+ uint8_t buf[kPageSize];
+ CompactMovingSpace</*kFallback*/false>(buf);
+ }
+
+ // The following triggers 'special' userfaults. When received by the
+ // thread-pool workers, they will exit out of the compaction task. This fault
+ // happens because we madvise info_map_ above and it is at least kPageSize in length.
+ DCHECK(IsAligned<kPageSize>(conc_compaction_termination_page_));
+ CHECK_EQ(*reinterpret_cast<volatile uint8_t*>(conc_compaction_termination_page_), 0);
+ DCHECK_EQ(thread_pool_counter_, 0);
+
+ struct uffdio_range unregister_range;
+ unregister_range.start = reinterpret_cast<uintptr_t>(bump_pointer_space_->Begin());
+ unregister_range.len = bump_pointer_space_->Capacity();
+ CHECK_EQ(ioctl(uffd_, UFFDIO_UNREGISTER, &unregister_range), 0)
+ << "ioctl_userfaultfd: unregister moving-space: " << strerror(errno);
+
+ // When poisoning ObjPtr, we are forced to use buffers for page compaction in
+ // lower 4GB. Now that the usage is done, madvise them. But skip the first
+ // page, which is used by the gc-thread for the next iteration. Otherwise, we
+ // get into a deadlock due to userfault on it in the next iteration. This page
+ // is not consuming any physical memory because we already madvised it above
+ // and then we triggered a read userfault, which maps a special zero-page.
+ if (kObjPtrPoisoning) {
+ ZeroAndReleasePages(compaction_buffers_map_.Begin() + kPageSize,
+ compaction_buffers_map_.Size() - kPageSize);
+ } else {
+ ZeroAndReleasePages(conc_compaction_termination_page_, kPageSize);
+ }
+ heap_->GetThreadPool()->StopWorkers(thread_running_gc_);
+}
+#pragma clang diagnostic pop
+
+template <size_t kBufferSize>
+class MarkCompact::ThreadRootsVisitor : public RootVisitor {
+ public:
+ explicit ThreadRootsVisitor(MarkCompact* mark_compact, Thread* const self)
+ : mark_compact_(mark_compact), self_(self) {}
+
+ ~ThreadRootsVisitor() {
+ Flush();
+ }
+
+ void VisitRoots(mirror::Object*** roots, size_t count, const RootInfo& info ATTRIBUTE_UNUSED)
+ override REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_) {
+ for (size_t i = 0; i < count; i++) {
+ mirror::Object* obj = *roots[i];
+ if (mark_compact_->MarkObjectNonNullNoPush</*kParallel*/true>(obj)) {
+ Push(obj);
+ }
+ }
+ }
+
+ void VisitRoots(mirror::CompressedReference<mirror::Object>** roots,
+ size_t count,
+ const RootInfo& info ATTRIBUTE_UNUSED)
+ override REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_) {
+ for (size_t i = 0; i < count; i++) {
+ mirror::Object* obj = roots[i]->AsMirrorPtr();
+ if (mark_compact_->MarkObjectNonNullNoPush</*kParallel*/true>(obj)) {
+ Push(obj);
+ }
+ }
+ }
+
+ private:
+ void Flush() REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_) {
+ StackReference<mirror::Object>* start;
+ StackReference<mirror::Object>* end;
+ {
+ MutexLock mu(self_, mark_compact_->mark_stack_lock_);
+ // Loop here because even after expanding once it may not be sufficient to
+ // accommodate all references. It's almost impossible, but there is no harm
+ // in implementing it this way.
+ while (!mark_compact_->mark_stack_->BumpBack(idx_, &start, &end)) {
+ mark_compact_->ExpandMarkStack();
+ }
+ }
+ while (idx_ > 0) {
+ *start++ = roots_[--idx_];
+ }
+ DCHECK_EQ(start, end);
+ }
+
+ void Push(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_) {
+ if (UNLIKELY(idx_ >= kBufferSize)) {
+ Flush();
+ }
+ roots_[idx_++].Assign(obj);
+ }
+
+ StackReference<mirror::Object> roots_[kBufferSize];
+ size_t idx_ = 0;
+ MarkCompact* const mark_compact_;
+ Thread* const self_;
+};
+
+class MarkCompact::CheckpointMarkThreadRoots : public Closure {
+ public:
+ explicit CheckpointMarkThreadRoots(MarkCompact* mark_compact) : mark_compact_(mark_compact) {}
+
+ void Run(Thread* thread) override NO_THREAD_SAFETY_ANALYSIS {
+ ScopedTrace trace("Marking thread roots");
+ // Note: self is not necessarily equal to thread since thread may be
+ // suspended.
+ Thread* const self = Thread::Current();
+ CHECK(thread == self
+ || thread->IsSuspended()
+ || thread->GetState() == ThreadState::kWaitingPerformingGc)
+ << thread->GetState() << " thread " << thread << " self " << self;
+ {
+ ThreadRootsVisitor</*kBufferSize*/ 20> visitor(mark_compact_, self);
+ thread->VisitRoots(&visitor, kVisitRootFlagAllRoots);
+ }
+
+ // If thread is a running mutator, then act on behalf of the garbage
+ // collector. See the code in ThreadList::RunCheckpoint.
+ mark_compact_->GetBarrier().Pass(self);
+ }
+
+ private:
+ MarkCompact* const mark_compact_;
+};
+
+void MarkCompact::MarkRootsCheckpoint(Thread* self, Runtime* runtime) {
+ // We revote TLABs later during paused round of marking.
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ CheckpointMarkThreadRoots check_point(this);
+ ThreadList* thread_list = runtime->GetThreadList();
+ gc_barrier_.Init(self, 0);
+ // Request the check point is run on all threads returning a count of the threads that must
+ // run through the barrier including self.
+ size_t barrier_count = thread_list->RunCheckpoint(&check_point);
+ // Release locks then wait for all mutator threads to pass the barrier.
+ // If there are no threads to wait which implys that all the checkpoint functions are finished,
+ // then no need to release locks.
+ if (barrier_count == 0) {
+ return;
+ }
+ Locks::heap_bitmap_lock_->ExclusiveUnlock(self);
+ Locks::mutator_lock_->SharedUnlock(self);
+ {
+ ScopedThreadStateChange tsc(self, ThreadState::kWaitingForCheckPointsToRun);
+ gc_barrier_.Increment(self, barrier_count);
+ }
+ Locks::mutator_lock_->SharedLock(self);
+ Locks::heap_bitmap_lock_->ExclusiveLock(self);
+}
+
+void MarkCompact::MarkNonThreadRoots(Runtime* runtime) {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ runtime->VisitNonThreadRoots(this);
+}
+
+void MarkCompact::MarkConcurrentRoots(VisitRootFlags flags, Runtime* runtime) {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ runtime->VisitConcurrentRoots(this, flags);
+}
+
+void MarkCompact::RevokeAllThreadLocalBuffers() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ bump_pointer_space_->RevokeAllThreadLocalBuffers();
+}
+
+class MarkCompact::ScanObjectVisitor {
+ public:
+ explicit ScanObjectVisitor(MarkCompact* const mark_compact) ALWAYS_INLINE
+ : mark_compact_(mark_compact) {}
+
+ void operator()(ObjPtr<mirror::Object> obj) const
+ ALWAYS_INLINE
+ REQUIRES(Locks::heap_bitmap_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ mark_compact_->ScanObject</*kUpdateLiveWords*/ false>(obj.Ptr());
+ }
+
+ private:
+ MarkCompact* const mark_compact_;
+};
+
+void MarkCompact::UpdateAndMarkModUnion() {
+ accounting::CardTable* const card_table = heap_->GetCardTable();
+ for (const auto& space : immune_spaces_.GetSpaces()) {
+ const char* name = space->IsZygoteSpace()
+ ? "UpdateAndMarkZygoteModUnionTable"
+ : "UpdateAndMarkImageModUnionTable";
+ DCHECK(space->IsZygoteSpace() || space->IsImageSpace()) << *space;
+ TimingLogger::ScopedTiming t(name, GetTimings());
+ accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+ if (table != nullptr) {
+ // UpdateAndMarkReferences() doesn't visit Reference-type objects. But
+ // that's fine because these objects are immutable enough (referent can
+ // only be cleared) and hence the only referents they can have are intra-space.
+ table->UpdateAndMarkReferences(this);
+ } else {
+ // No mod-union table, scan all dirty/aged cards in the corresponding
+ // card-table. This can only occur for app images.
+ card_table->Scan</*kClearCard*/ false>(space->GetMarkBitmap(),
+ space->Begin(),
+ space->End(),
+ ScanObjectVisitor(this),
+ gc::accounting::CardTable::kCardAged);
+ }
+ }
+}
+
+void MarkCompact::MarkReachableObjects() {
+ UpdateAndMarkModUnion();
+ // Recursively mark all the non-image bits set in the mark bitmap.
+ ProcessMarkStack();
+}
+
+class MarkCompact::CardModifiedVisitor {
+ public:
+ explicit CardModifiedVisitor(MarkCompact* const mark_compact,
+ accounting::ContinuousSpaceBitmap* const bitmap,
+ accounting::CardTable* const card_table)
+ : visitor_(mark_compact), bitmap_(bitmap), card_table_(card_table) {}
+
+ void operator()(uint8_t* card,
+ uint8_t expected_value,
+ uint8_t new_value ATTRIBUTE_UNUSED) const {
+ if (expected_value == accounting::CardTable::kCardDirty) {
+ uintptr_t start = reinterpret_cast<uintptr_t>(card_table_->AddrFromCard(card));
+ bitmap_->VisitMarkedRange(start, start + accounting::CardTable::kCardSize, visitor_);
+ }
+ }
+
+ private:
+ ScanObjectVisitor visitor_;
+ accounting::ContinuousSpaceBitmap* bitmap_;
+ accounting::CardTable* const card_table_;
+};
+
+void MarkCompact::ScanDirtyObjects(bool paused, uint8_t minimum_age) {
+ accounting::CardTable* card_table = heap_->GetCardTable();
+ for (const auto& space : heap_->GetContinuousSpaces()) {
+ const char* name = nullptr;
+ switch (space->GetGcRetentionPolicy()) {
+ case space::kGcRetentionPolicyNeverCollect:
+ name = paused ? "(Paused)ScanGrayImmuneSpaceObjects" : "ScanGrayImmuneSpaceObjects";
+ break;
+ case space::kGcRetentionPolicyFullCollect:
+ name = paused ? "(Paused)ScanGrayZygoteSpaceObjects" : "ScanGrayZygoteSpaceObjects";
+ break;
+ case space::kGcRetentionPolicyAlwaysCollect:
+ name = paused ? "(Paused)ScanGrayAllocSpaceObjects" : "ScanGrayAllocSpaceObjects";
+ break;
+ default:
+ LOG(FATAL) << "Unreachable";
+ UNREACHABLE();
+ }
+ TimingLogger::ScopedTiming t(name, GetTimings());
+ ScanObjectVisitor visitor(this);
+ const bool is_immune_space = space->IsZygoteSpace() || space->IsImageSpace();
+ if (paused) {
+ DCHECK_EQ(minimum_age, gc::accounting::CardTable::kCardDirty);
+ // We can clear the card-table for any non-immune space.
+ if (is_immune_space) {
+ card_table->Scan</*kClearCard*/false>(space->GetMarkBitmap(),
+ space->Begin(),
+ space->End(),
+ visitor,
+ minimum_age);
+ } else {
+ card_table->Scan</*kClearCard*/true>(space->GetMarkBitmap(),
+ space->Begin(),
+ space->End(),
+ visitor,
+ minimum_age);
+ }
+ } else {
+ DCHECK_EQ(minimum_age, gc::accounting::CardTable::kCardAged);
+ accounting::ModUnionTable* table = heap_->FindModUnionTableFromSpace(space);
+ if (table) {
+ table->ProcessCards();
+ card_table->Scan</*kClearCard*/false>(space->GetMarkBitmap(),
+ space->Begin(),
+ space->End(),
+ visitor,
+ minimum_age);
+ } else {
+ CardModifiedVisitor card_modified_visitor(this, space->GetMarkBitmap(), card_table);
+ // For the alloc spaces we should age the dirty cards and clear the rest.
+ // For image and zygote-space without mod-union-table, age the dirty
+ // cards but keep the already aged cards unchanged.
+ // In either case, visit the objects on the cards that were changed from
+ // dirty to aged.
+ if (is_immune_space) {
+ card_table->ModifyCardsAtomic(space->Begin(),
+ space->End(),
+ [](uint8_t card) {
+ return (card == gc::accounting::CardTable::kCardClean)
+ ? card
+ : gc::accounting::CardTable::kCardAged;
+ },
+ card_modified_visitor);
+ } else {
+ card_table->ModifyCardsAtomic(space->Begin(),
+ space->End(),
+ AgeCardVisitor(),
+ card_modified_visitor);
+ }
+ }
+ }
+ }
+}
+
+void MarkCompact::RecursiveMarkDirtyObjects(bool paused, uint8_t minimum_age) {
+ ScanDirtyObjects(paused, minimum_age);
+ ProcessMarkStack();
+}
+
+void MarkCompact::MarkRoots(VisitRootFlags flags) {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ Runtime* runtime = Runtime::Current();
+ // Make sure that the checkpoint which collects the stack roots is the first
+ // one capturning GC-roots. As this one is supposed to find the address
+ // everything allocated after that (during this marking phase) will be
+ // considered 'marked'.
+ MarkRootsCheckpoint(thread_running_gc_, runtime);
+ MarkNonThreadRoots(runtime);
+ MarkConcurrentRoots(flags, runtime);
+}
+
+void MarkCompact::PreCleanCards() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ CHECK(!Locks::mutator_lock_->IsExclusiveHeld(thread_running_gc_));
+ MarkRoots(static_cast<VisitRootFlags>(kVisitRootFlagClearRootLog | kVisitRootFlagNewRoots));
+ RecursiveMarkDirtyObjects(/*paused*/ false, accounting::CardTable::kCardDirty - 1);
+}
+
+// In a concurrent marking algorithm, if we are not using a write/read barrier, as
+// in this case, then we need a stop-the-world (STW) round in the end to mark
+// objects which were written into concurrently while concurrent marking was
+// performed.
+// In order to minimize the pause time, we could take one of the two approaches:
+// 1. Keep repeating concurrent marking of dirty cards until the time spent goes
+// below a threshold.
+// 2. Do two rounds concurrently and then attempt a paused one. If we figure
+// that it's taking too long, then resume mutators and retry.
+//
+// Given the non-trivial fixed overhead of running a round (card table and root
+// scan), it might be better to go with approach 2.
+void MarkCompact::MarkingPhase() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ DCHECK_EQ(thread_running_gc_, Thread::Current());
+ WriterMutexLock mu(thread_running_gc_, *Locks::heap_bitmap_lock_);
+ BindAndResetBitmaps();
+ MarkRoots(
+ static_cast<VisitRootFlags>(kVisitRootFlagAllRoots | kVisitRootFlagStartLoggingNewRoots));
+ MarkReachableObjects();
+ // Pre-clean dirtied cards to reduce pauses.
+ PreCleanCards();
+
+ // Setup reference processing and forward soft references once before enabling
+ // slow path (in MarkingPause)
+ ReferenceProcessor* rp = GetHeap()->GetReferenceProcessor();
+ bool clear_soft_references = GetCurrentIteration()->GetClearSoftReferences();
+ rp->Setup(thread_running_gc_, this, /*concurrent=*/ true, clear_soft_references);
+ if (!clear_soft_references) {
+ // Forward as many SoftReferences as possible before inhibiting reference access.
+ rp->ForwardSoftReferences(GetTimings());
+ }
+}
+
+class MarkCompact::RefFieldsVisitor {
+ public:
+ ALWAYS_INLINE explicit RefFieldsVisitor(MarkCompact* const mark_compact)
+ : mark_compact_(mark_compact) {}
+
+ ALWAYS_INLINE void operator()(mirror::Object* obj,
+ MemberOffset offset,
+ bool is_static ATTRIBUTE_UNUSED) const
+ REQUIRES(Locks::heap_bitmap_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (kCheckLocks) {
+ Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+ Locks::heap_bitmap_lock_->AssertExclusiveHeld(Thread::Current());
+ }
+ mark_compact_->MarkObject(obj->GetFieldObject<mirror::Object>(offset), obj, offset);
+ }
+
+ void operator()(ObjPtr<mirror::Class> klass, ObjPtr<mirror::Reference> ref) const
+ REQUIRES(Locks::heap_bitmap_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ mark_compact_->DelayReferenceReferent(klass, ref);
+ }
+
+ void VisitRootIfNonNull(mirror::CompressedReference<mirror::Object>* root) const
+ REQUIRES(Locks::heap_bitmap_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (!root->IsNull()) {
+ VisitRoot(root);
+ }
+ }
+
+ void VisitRoot(mirror::CompressedReference<mirror::Object>* root) const
+ REQUIRES(Locks::heap_bitmap_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (kCheckLocks) {
+ Locks::mutator_lock_->AssertSharedHeld(Thread::Current());
+ Locks::heap_bitmap_lock_->AssertExclusiveHeld(Thread::Current());
+ }
+ mark_compact_->MarkObject(root->AsMirrorPtr());
+ }
+
+ private:
+ MarkCompact* const mark_compact_;
+};
+
+template <size_t kAlignment>
+size_t MarkCompact::LiveWordsBitmap<kAlignment>::LiveBytesInBitmapWord(size_t chunk_idx) const {
+ const size_t index = chunk_idx * kBitmapWordsPerVectorWord;
+ size_t words = 0;
+ for (uint32_t i = 0; i < kBitmapWordsPerVectorWord; i++) {
+ words += POPCOUNT(Bitmap::Begin()[index + i]);
+ }
+ return words * kAlignment;
+}
+
+void MarkCompact::UpdateLivenessInfo(mirror::Object* obj) {
+ DCHECK(obj != nullptr);
+ uintptr_t obj_begin = reinterpret_cast<uintptr_t>(obj);
+ size_t size = RoundUp(obj->SizeOf<kDefaultVerifyFlags>(), kAlignment);
+ uintptr_t bit_index = live_words_bitmap_->SetLiveWords(obj_begin, size);
+ size_t chunk_idx = (obj_begin - live_words_bitmap_->Begin()) / kOffsetChunkSize;
+ // Compute the bit-index within the chunk-info vector word.
+ bit_index %= kBitsPerVectorWord;
+ size_t first_chunk_portion = std::min(size, (kBitsPerVectorWord - bit_index) * kAlignment);
+
+ chunk_info_vec_[chunk_idx++] += first_chunk_portion;
+ DCHECK_LE(first_chunk_portion, size);
+ for (size -= first_chunk_portion; size > kOffsetChunkSize; size -= kOffsetChunkSize) {
+ DCHECK_EQ(chunk_info_vec_[chunk_idx], 0u);
+ chunk_info_vec_[chunk_idx++] = kOffsetChunkSize;
+ }
+ chunk_info_vec_[chunk_idx] += size;
+ freed_objects_--;
+}
+
+template <bool kUpdateLiveWords>
+void MarkCompact::ScanObject(mirror::Object* obj) {
+ RefFieldsVisitor visitor(this);
+ DCHECK(IsMarked(obj)) << "Scanning marked object " << obj << "\n" << heap_->DumpSpaces();
+ if (kUpdateLiveWords && moving_space_bitmap_->HasAddress(obj)) {
+ UpdateLivenessInfo(obj);
+ }
+ obj->VisitReferences(visitor, visitor);
+ RememberDexCaches(obj);
+}
+
+void MarkCompact::RememberDexCaches(mirror::Object* obj) {
+ if (obj->IsDexCache()) {
+ dex_caches_.insert(
+ mirror::CompressedReference<mirror::Object>::FromMirrorPtr(obj).AsVRegValue());
+ }
+}
+
+// Scan anything that's on the mark stack.
+void MarkCompact::ProcessMarkStack() {
+ TimingLogger::ScopedTiming t(__FUNCTION__, GetTimings());
+ // TODO: try prefetch like in CMS
+ while (!mark_stack_->IsEmpty()) {
+ mirror::Object* obj = mark_stack_->PopBack();
+ DCHECK(obj != nullptr);
+ ScanObject</*kUpdateLiveWords*/ true>(obj);
+ }
+}
+
+void MarkCompact::ExpandMarkStack() {
+ const size_t new_size = mark_stack_->Capacity() * 2;
+ std::vector<StackReference<mirror::Object>> temp(mark_stack_->Begin(),
+ mark_stack_->End());
+ mark_stack_->Resize(new_size);
+ for (auto& ref : temp) {
+ mark_stack_->PushBack(ref.AsMirrorPtr());
+ }
+ DCHECK(!mark_stack_->IsFull());
+}
+
+inline void MarkCompact::PushOnMarkStack(mirror::Object* obj) {
+ if (UNLIKELY(mark_stack_->IsFull())) {
+ ExpandMarkStack();
+ }
+ mark_stack_->PushBack(obj);
+}
+
+inline void MarkCompact::MarkObjectNonNull(mirror::Object* obj,
+ mirror::Object* holder,
+ MemberOffset offset) {
+ DCHECK(obj != nullptr);
+ if (MarkObjectNonNullNoPush</*kParallel*/false>(obj, holder, offset)) {
+ PushOnMarkStack(obj);
+ }
+}
+
+template <bool kParallel>
+inline bool MarkCompact::MarkObjectNonNullNoPush(mirror::Object* obj,
+ mirror::Object* holder,
+ MemberOffset offset) {
+ // We expect most of the referenes to be in bump-pointer space, so try that
+ // first to keep the cost of this function minimal.
+ if (LIKELY(moving_space_bitmap_->HasAddress(obj))) {
+ return kParallel ? !moving_space_bitmap_->AtomicTestAndSet(obj)
+ : !moving_space_bitmap_->Set(obj);
+ } else if (non_moving_space_bitmap_->HasAddress(obj)) {
+ return kParallel ? !non_moving_space_bitmap_->AtomicTestAndSet(obj)
+ : !non_moving_space_bitmap_->Set(obj);
+ } else if (immune_spaces_.ContainsObject(obj)) {
+ DCHECK(IsMarked(obj) != nullptr);
+ return false;
+ } else {
+ // Must be a large-object space, otherwise it's a case of heap corruption.
+ if (!IsAligned<kPageSize>(obj)) {
+ // Objects in large-object space are page aligned. So if we have an object
+ // which doesn't belong to any space and is not page-aligned as well, then
+ // it's memory corruption.
+ // TODO: implement protect/unprotect in bump-pointer space.
+ heap_->GetVerification()->LogHeapCorruption(holder, offset, obj, /*fatal*/ true);
+ }
+ DCHECK_NE(heap_->GetLargeObjectsSpace(), nullptr)
+ << "ref=" << obj
+ << " doesn't belong to any of the spaces and large object space doesn't exist";
+ accounting::LargeObjectBitmap* los_bitmap = heap_->GetLargeObjectsSpace()->GetMarkBitmap();
+ DCHECK(los_bitmap->HasAddress(obj));
+ return kParallel ? !los_bitmap->AtomicTestAndSet(obj)
+ : !los_bitmap->Set(obj);
+ }
+}
+
+inline void MarkCompact::MarkObject(mirror::Object* obj,
+ mirror::Object* holder,
+ MemberOffset offset) {
+ if (obj != nullptr) {
+ MarkObjectNonNull(obj, holder, offset);
+ }
+}
+
+mirror::Object* MarkCompact::MarkObject(mirror::Object* obj) {
+ MarkObject(obj, nullptr, MemberOffset(0));
+ return obj;
+}
+
+void MarkCompact::MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+ bool do_atomic_update ATTRIBUTE_UNUSED) {
+ MarkObject(obj->AsMirrorPtr(), nullptr, MemberOffset(0));
+}
+
+void MarkCompact::VisitRoots(mirror::Object*** roots,
+ size_t count,
+ const RootInfo& info) {
+ if (compacting_) {
+ for (size_t i = 0; i < count; ++i) {
+ UpdateRoot(roots[i], info);
+ }
+ } else {
+ for (size_t i = 0; i < count; ++i) {
+ MarkObjectNonNull(*roots[i]);
+ }
+ }
+}
+
+void MarkCompact::VisitRoots(mirror::CompressedReference<mirror::Object>** roots,
+ size_t count,
+ const RootInfo& info) {
+ // TODO: do we need to check if the root is null or not?
+ if (compacting_) {
+ for (size_t i = 0; i < count; ++i) {
+ UpdateRoot(roots[i], info);
+ }
+ } else {
+ for (size_t i = 0; i < count; ++i) {
+ MarkObjectNonNull(roots[i]->AsMirrorPtr());
+ }
+ }
+}
+
+mirror::Object* MarkCompact::IsMarked(mirror::Object* obj) {
+ if (moving_space_bitmap_->HasAddress(obj)) {
+ const bool is_black = reinterpret_cast<uint8_t*>(obj) >= black_allocations_begin_;
+ if (compacting_) {
+ if (is_black) {
+ return PostCompactBlackObjAddr(obj);
+ } else if (live_words_bitmap_->Test(obj)) {
+ return PostCompactOldObjAddr(obj);
+ } else {
+ return nullptr;
+ }
+ }
+ return (is_black || moving_space_bitmap_->Test(obj)) ? obj : nullptr;
+ } else if (non_moving_space_bitmap_->HasAddress(obj)) {
+ return non_moving_space_bitmap_->Test(obj) ? obj : nullptr;
+ } else if (immune_spaces_.ContainsObject(obj)) {
+ return obj;
+ } else {
+ DCHECK(heap_->GetLargeObjectsSpace())
+ << "ref=" << obj
+ << " doesn't belong to any of the spaces and large object space doesn't exist";
+ accounting::LargeObjectBitmap* los_bitmap = heap_->GetLargeObjectsSpace()->GetMarkBitmap();
+ if (los_bitmap->HasAddress(obj)) {
+ DCHECK(IsAligned<kPageSize>(obj));
+ return los_bitmap->Test(obj) ? obj : nullptr;
+ } else {
+ // The given obj is not in any of the known spaces, so return null. This could
+ // happen for instance in interpreter caches wherein a concurrent updation
+ // to the cache could result in obj being a non-reference. This is
+ // tolerable because SweepInterpreterCaches only updates if the given
+ // object has moved, which can't be the case for the non-reference.
+ return nullptr;
+ }
+ }
+}
+
+bool MarkCompact::IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj,
+ bool do_atomic_update ATTRIBUTE_UNUSED) {
+ mirror::Object* ref = obj->AsMirrorPtr();
+ if (ref == nullptr) {
+ return true;
+ }
+ return IsMarked(ref);
+}
+
+// Process the 'referent' field in a java.lang.ref.Reference. If the referent
+// has not yet been marked, put it on the appropriate list in the heap for later
+// processing.
+void MarkCompact::DelayReferenceReferent(ObjPtr<mirror::Class> klass,
+ ObjPtr<mirror::Reference> ref) {
+ heap_->GetReferenceProcessor()->DelayReferenceReferent(klass, ref, this);
+}
+
+void MarkCompact::FinishPhase() {
+ info_map_.MadviseDontNeedAndZero();
+ live_words_bitmap_->ClearBitmap();
+ from_space_map_.MadviseDontNeedAndZero();
+ if (UNLIKELY(Runtime::Current()->IsZygote() && uffd_ >= 0)) {
+ heap_->DeleteThreadPool();
+ close(uffd_);
+ uffd_ = -1;
+ uffd_initialized_ = false;
+ }
+ CHECK(mark_stack_->IsEmpty()); // Ensure that the mark stack is empty.
+ mark_stack_->Reset();
+ updated_roots_.clear();
+ delete[] moving_pages_status_;
+ DCHECK_EQ(thread_running_gc_, Thread::Current());
+ ReaderMutexLock mu(thread_running_gc_, *Locks::mutator_lock_);
+ WriterMutexLock mu2(thread_running_gc_, *Locks::heap_bitmap_lock_);
+ heap_->ClearMarkedObjects();
+}
+
+} // namespace collector
+} // namespace gc
+} // namespace art
diff --git a/runtime/gc/collector/mark_compact.h b/runtime/gc/collector/mark_compact.h
new file mode 100644
index 0000000..cb7440c
--- /dev/null
+++ b/runtime/gc/collector/mark_compact.h
@@ -0,0 +1,560 @@
+/*
+ * Copyright 2021 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_GC_COLLECTOR_MARK_COMPACT_H_
+#define ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_
+
+#include <memory>
+#include <unordered_set>
+
+#include "base/atomic.h"
+#include "barrier.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "garbage_collector.h"
+#include "gc/accounting/atomic_stack.h"
+#include "gc/accounting/bitmap-inl.h"
+#include "gc/accounting/heap_bitmap.h"
+#include "gc_root.h"
+#include "immune_spaces.h"
+#include "offsets.h"
+
+namespace art {
+
+namespace mirror {
+class DexCache;
+}
+
+namespace gc {
+
+class Heap;
+
+namespace space {
+class BumpPointerSpace;
+} // namespace space
+
+namespace collector {
+class MarkCompact : public GarbageCollector {
+ public:
+ static constexpr size_t kAlignment = kObjectAlignment;
+ // Fake file descriptor for fall back mode
+ static constexpr int kFallbackMode = -2;
+
+ explicit MarkCompact(Heap* heap);
+
+ ~MarkCompact() {}
+
+ void RunPhases() override REQUIRES(!Locks::mutator_lock_);
+
+ // Updated before (or in) pre-compaction pause and is accessed only in the
+ // pause or during concurrent compaction. The flag is reset after compaction
+ // is completed and never accessed by mutators. Therefore, safe to update
+ // without any memory ordering.
+ bool IsCompacting(Thread* self) const {
+ return compacting_ && self == thread_running_gc_;
+ }
+
+ GcType GetGcType() const override {
+ return kGcTypeFull;
+ }
+
+ CollectorType GetCollectorType() const override {
+ return kCollectorTypeCMC;
+ }
+
+ Barrier& GetBarrier() {
+ return gc_barrier_;
+ }
+
+ mirror::Object* MarkObject(mirror::Object* obj) override
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ void MarkHeapReference(mirror::HeapReference<mirror::Object>* obj,
+ bool do_atomic_update) override
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ void VisitRoots(mirror::Object*** roots,
+ size_t count,
+ const RootInfo& info) override
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ void VisitRoots(mirror::CompressedReference<mirror::Object>** roots,
+ size_t count,
+ const RootInfo& info) override
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ bool IsNullOrMarkedHeapReference(mirror::HeapReference<mirror::Object>* obj,
+ bool do_atomic_update) override
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ void RevokeAllThreadLocalBuffers() override;
+
+ void DelayReferenceReferent(ObjPtr<mirror::Class> klass,
+ ObjPtr<mirror::Reference> reference) override
+ REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+
+ mirror::Object* IsMarked(mirror::Object* obj) override
+ REQUIRES_SHARED(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+
+ // Perform GC-root updation and heap protection so that during the concurrent
+ // compaction phase we can receive faults and compact the corresponding pages
+ // on the fly. This is performed in a STW pause.
+ void CompactionPause() REQUIRES(Locks::mutator_lock_, !Locks::heap_bitmap_lock_);
+
+ mirror::Object* GetFromSpaceAddrFromBarrier(mirror::Object* old_ref) {
+ CHECK(compacting_);
+ if (live_words_bitmap_->HasAddress(old_ref)) {
+ return GetFromSpaceAddr(old_ref);
+ }
+ return old_ref;
+ }
+ // Called from Heap::PostForkChildAction() for non-zygote processes and from
+ // PrepareForCompaction() for zygote processes. Returns true if uffd was
+ // created or was already done.
+ bool CreateUserfaultfd(bool post_fork);
+
+ private:
+ using ObjReference = mirror::ObjectReference</*kPoisonReferences*/ false, mirror::Object>;
+ // Number of bits (live-words) covered by a single chunk-info (below)
+ // entry/word.
+ // TODO: Since popcount is performed usomg SIMD instructions, we should
+ // consider using 128-bit in order to halve the chunk-info size.
+ static constexpr uint32_t kBitsPerVectorWord = kBitsPerIntPtrT;
+ static constexpr uint32_t kOffsetChunkSize = kBitsPerVectorWord * kAlignment;
+ static_assert(kOffsetChunkSize < kPageSize);
+ // Bitmap with bits corresponding to every live word set. For an object
+ // which is 4 words in size will have the corresponding 4 bits set. This is
+ // required for efficient computation of new-address (post-compaction) from
+ // the given old-address (pre-compaction).
+ template <size_t kAlignment>
+ class LiveWordsBitmap : private accounting::MemoryRangeBitmap<kAlignment> {
+ using Bitmap = accounting::Bitmap;
+ using MemRangeBitmap = accounting::MemoryRangeBitmap<kAlignment>;
+
+ public:
+ static_assert(IsPowerOfTwo(kBitsPerVectorWord));
+ static_assert(IsPowerOfTwo(Bitmap::kBitsPerBitmapWord));
+ static_assert(kBitsPerVectorWord >= Bitmap::kBitsPerBitmapWord);
+ static constexpr uint32_t kBitmapWordsPerVectorWord =
+ kBitsPerVectorWord / Bitmap::kBitsPerBitmapWord;
+ static_assert(IsPowerOfTwo(kBitmapWordsPerVectorWord));
+ static LiveWordsBitmap* Create(uintptr_t begin, uintptr_t end);
+
+ // Return offset (within the indexed chunk-info) of the nth live word.
+ uint32_t FindNthLiveWordOffset(size_t chunk_idx, uint32_t n) const;
+ // Sets all bits in the bitmap corresponding to the given range. Also
+ // returns the bit-index of the first word.
+ ALWAYS_INLINE uintptr_t SetLiveWords(uintptr_t begin, size_t size);
+ // Count number of live words upto the given bit-index. This is to be used
+ // to compute the post-compact address of an old reference.
+ ALWAYS_INLINE size_t CountLiveWordsUpto(size_t bit_idx) const;
+ // Call 'visitor' for every stride of contiguous marked bits in the live-words
+ // bitmap, starting from begin_bit_idx. Only visit 'bytes' live bytes or
+ // until 'end', whichever comes first.
+ // Visitor is called with index of the first marked bit in the stride,
+ // stride size and whether it's the last stride in the given range or not.
+ template <typename Visitor>
+ ALWAYS_INLINE void VisitLiveStrides(uintptr_t begin_bit_idx,
+ uint8_t* end,
+ const size_t bytes,
+ Visitor&& visitor) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Count the number of live bytes in the given vector entry.
+ size_t LiveBytesInBitmapWord(size_t chunk_idx) const;
+ void ClearBitmap() { Bitmap::Clear(); }
+ ALWAYS_INLINE uintptr_t Begin() const { return MemRangeBitmap::CoverBegin(); }
+ ALWAYS_INLINE bool HasAddress(mirror::Object* obj) const {
+ return MemRangeBitmap::HasAddress(reinterpret_cast<uintptr_t>(obj));
+ }
+ ALWAYS_INLINE bool Test(uintptr_t bit_index) const {
+ return Bitmap::TestBit(bit_index);
+ }
+ ALWAYS_INLINE bool Test(mirror::Object* obj) const {
+ return MemRangeBitmap::Test(reinterpret_cast<uintptr_t>(obj));
+ }
+ ALWAYS_INLINE uintptr_t GetWord(size_t index) const {
+ static_assert(kBitmapWordsPerVectorWord == 1);
+ return Bitmap::Begin()[index * kBitmapWordsPerVectorWord];
+ }
+ };
+
+ // For a given object address in pre-compact space, return the corresponding
+ // address in the from-space, where heap pages are relocated in the compaction
+ // pause.
+ mirror::Object* GetFromSpaceAddr(mirror::Object* obj) const {
+ DCHECK(live_words_bitmap_->HasAddress(obj)) << " obj=" << obj;
+ return reinterpret_cast<mirror::Object*>(reinterpret_cast<uintptr_t>(obj)
+ + from_space_slide_diff_);
+ }
+
+ // Verifies that that given object reference refers to a valid object.
+ // Otherwise fataly dumps logs, including those from callback.
+ template <typename Callback>
+ void VerifyObject(mirror::Object* ref, Callback& callback) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Check if the obj is within heap and has a klass which is likely to be valid
+ // mirror::Class.
+ bool IsValidObject(mirror::Object* obj) const REQUIRES_SHARED(Locks::mutator_lock_);
+ void InitializePhase();
+ void FinishPhase() REQUIRES(!Locks::mutator_lock_, !Locks::heap_bitmap_lock_);
+ void MarkingPhase() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_);
+ void CompactionPhase() REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void SweepSystemWeaks(Thread* self, Runtime* runtime, const bool paused)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(!Locks::heap_bitmap_lock_);
+ // Update the reference at given offset in the given object with post-compact
+ // address.
+ ALWAYS_INLINE void UpdateRef(mirror::Object* obj, MemberOffset offset)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Verify that the gc-root is updated only once. Returns false if the update
+ // shouldn't be done.
+ ALWAYS_INLINE bool VerifyRootSingleUpdate(void* root,
+ mirror::Object* old_ref,
+ const RootInfo& info)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Update the given root with post-compact address.
+ ALWAYS_INLINE void UpdateRoot(mirror::CompressedReference<mirror::Object>* root,
+ const RootInfo& info = RootInfo(RootType::kRootUnknown))
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ ALWAYS_INLINE void UpdateRoot(mirror::Object** root,
+ const RootInfo& info = RootInfo(RootType::kRootUnknown))
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Given the pre-compact address, the function returns the post-compact
+ // address of the given object.
+ ALWAYS_INLINE mirror::Object* PostCompactAddress(mirror::Object* old_ref) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Compute post-compact address of an object in moving space. This function
+ // assumes that old_ref is in moving space.
+ ALWAYS_INLINE mirror::Object* PostCompactAddressUnchecked(mirror::Object* old_ref) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Compute the new address for an object which was allocated prior to starting
+ // this GC cycle.
+ ALWAYS_INLINE mirror::Object* PostCompactOldObjAddr(mirror::Object* old_ref) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Compute the new address for an object which was black allocated during this
+ // GC cycle.
+ ALWAYS_INLINE mirror::Object* PostCompactBlackObjAddr(mirror::Object* old_ref) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Identify immune spaces and reset card-table, mod-union-table, and mark
+ // bitmaps.
+ void BindAndResetBitmaps() REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ // Perform one last round of marking, identifying roots from dirty cards
+ // during a stop-the-world (STW) pause.
+ void MarkingPause() REQUIRES(Locks::mutator_lock_, !Locks::heap_bitmap_lock_);
+ // Perform stop-the-world pause prior to concurrent compaction.
+ // Updates GC-roots and protects heap so that during the concurrent
+ // compaction phase we can receive faults and compact the corresponding pages
+ // on the fly.
+ void PreCompactionPhase() REQUIRES(Locks::mutator_lock_);
+ // Compute offsets (in chunk_info_vec_) and other data structures required
+ // during concurrent compaction.
+ void PrepareForCompaction() REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Copy kPageSize live bytes starting from 'offset' (within the moving space),
+ // which must be within 'obj', into the kPageSize sized memory pointed by 'addr'.
+ // Then update the references within the copied objects. The boundary objects are
+ // partially updated such that only the references that lie in the page are updated.
+ // This is necessary to avoid cascading userfaults.
+ void CompactPage(mirror::Object* obj, uint32_t offset, uint8_t* addr)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Compact the bump-pointer space. Pass page that should be used as buffer for
+ // userfaultfd.
+ template <bool kFallback>
+ void CompactMovingSpace(uint8_t* page = nullptr) REQUIRES_SHARED(Locks::mutator_lock_);
+ // Update all the objects in the given non-moving space page. 'first' object
+ // could have started in some preceding page.
+ void UpdateNonMovingPage(mirror::Object* first, uint8_t* page)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Update all the references in the non-moving space.
+ void UpdateNonMovingSpace() REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // For all the pages in non-moving space, find the first object that overlaps
+ // with the pages' start address, and store in first_objs_non_moving_space_ array.
+ void InitNonMovingSpaceFirstObjects() REQUIRES_SHARED(Locks::mutator_lock_);
+ // In addition to the first-objects for every post-compact moving space page,
+ // also find offsets within those objects from where the contents should be
+ // copied to the page. The offsets are relative to the moving-space's
+ // beginning. Store the computed first-object and offset in first_objs_moving_space_
+ // and pre_compact_offset_moving_space_ respectively.
+ void InitMovingSpaceFirstObjects(const size_t vec_len) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Gather the info related to black allocations from bump-pointer space to
+ // enable concurrent sliding of these pages.
+ void UpdateMovingSpaceBlackAllocations() REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+ // Update first-object info from allocation-stack for non-moving space black
+ // allocations.
+ void UpdateNonMovingSpaceBlackAllocations() REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+
+ // Slides (retain the empty holes, which are usually part of some in-use TLAB)
+ // black page in the moving space. 'first_obj' is the object that overlaps with
+ // the first byte of the page being slid. pre_compact_page is the pre-compact
+ // address of the page being slid. 'page_idx' is used to fetch the first
+ // allocated chunk's size and next page's first_obj. 'dest' is the kPageSize
+ // sized memory where the contents would be copied.
+ void SlideBlackPage(mirror::Object* first_obj,
+ const size_t page_idx,
+ uint8_t* const pre_compact_page,
+ uint8_t* dest)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ // Perform reference-processing and the likes before sweeping the non-movable
+ // spaces.
+ void ReclaimPhase() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Locks::heap_bitmap_lock_);
+
+ // Mark GC-roots (except from immune spaces and thread-stacks) during a STW pause.
+ void ReMarkRoots(Runtime* runtime) REQUIRES(Locks::mutator_lock_, Locks::heap_bitmap_lock_);
+ // Concurrently mark GC-roots, except from immune spaces.
+ void MarkRoots(VisitRootFlags flags) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ // Collect thread stack roots via a checkpoint.
+ void MarkRootsCheckpoint(Thread* self, Runtime* runtime) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ // Second round of concurrent marking. Mark all gray objects that got dirtied
+ // since the first round.
+ void PreCleanCards() REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
+
+ void MarkNonThreadRoots(Runtime* runtime) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ void MarkConcurrentRoots(VisitRootFlags flags, Runtime* runtime)
+ REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
+
+ // Traverse through the reachable objects and mark them.
+ void MarkReachableObjects() REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ // Scan (only) immune spaces looking for references into the garbage collected
+ // spaces.
+ void UpdateAndMarkModUnion() REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ // Scan mod-union and card tables, covering all the spaces, to identify dirty objects.
+ // These are in 'minimum age' cards, which is 'kCardAged' in case of concurrent (second round)
+ // marking and kCardDirty during the STW pause.
+ void ScanDirtyObjects(bool paused, uint8_t minimum_age) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ // Recursively mark dirty objects. Invoked both concurrently as well in a STW
+ // pause in PausePhase().
+ void RecursiveMarkDirtyObjects(bool paused, uint8_t minimum_age)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ // Go through all the objects in the mark-stack until it's empty.
+ void ProcessMarkStack() override REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ void ExpandMarkStack() REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ // Scan object for references. If kUpdateLivewords is true then set bits in
+ // the live-words bitmap and add size to chunk-info.
+ template <bool kUpdateLiveWords>
+ void ScanObject(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ // Push objects to the mark-stack right after successfully marking objects.
+ void PushOnMarkStack(mirror::Object* obj)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ // Update the live-words bitmap as well as add the object size to the
+ // chunk-info vector. Both are required for computation of post-compact addresses.
+ // Also updates freed_objects_ counter.
+ void UpdateLivenessInfo(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void ProcessReferences(Thread* self)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(!Locks::heap_bitmap_lock_);
+
+ void MarkObjectNonNull(mirror::Object* obj,
+ mirror::Object* holder = nullptr,
+ MemberOffset offset = MemberOffset(0))
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ void MarkObject(mirror::Object* obj, mirror::Object* holder, MemberOffset offset)
+ REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ template <bool kParallel>
+ bool MarkObjectNonNullNoPush(mirror::Object* obj,
+ mirror::Object* holder = nullptr,
+ MemberOffset offset = MemberOffset(0))
+ REQUIRES(Locks::heap_bitmap_lock_)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ void Sweep(bool swap_bitmaps) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+ void SweepLargeObjects(bool swap_bitmaps) REQUIRES_SHARED(Locks::mutator_lock_)
+ REQUIRES(Locks::heap_bitmap_lock_);
+
+ // Store all the dex-cache objects visited during marking phase.
+ // This is required during compaction phase to ensure that we don't miss any
+ // of them from visiting (to update references). Somehow, iterating over
+ // class-tables to fetch these misses some of them, leading to memory
+ // corruption.
+ // TODO: once we implement concurrent compaction of classes and dex-caches,
+ // which will visit all of them, we should remove this.
+ void RememberDexCaches(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_);
+ // Perform all kernel operations required for concurrent compaction. Includes
+ // mremap to move pre-compact pages to from-space, followed by userfaultfd
+ // registration on the moving space.
+ void KernelPreparation();
+ // Called by thread-pool workers to read uffd_ and process fault events.
+ void ConcurrentCompaction(uint8_t* page) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ enum PageState : uint8_t {
+ kUncompacted = 0, // The page has not been compacted yet
+ kCompacting // Some thread (GC or mutator) is compacting the page
+ };
+
+ // Buffers, one per worker thread + gc-thread, to be used when
+ // kObjPtrPoisoning == true as in that case we can't have the buffer on the
+ // stack. The first page of the buffer is assigned to
+ // conc_compaction_termination_page_. A read access to this page signals
+ // termination of concurrent compaction by making worker threads terminate the
+ // userfaultfd read loop.
+ MemMap compaction_buffers_map_;
+ // For checkpoints
+ Barrier gc_barrier_;
+ // Every object inside the immune spaces is assumed to be marked.
+ ImmuneSpaces immune_spaces_;
+ // Required only when mark-stack is accessed in shared mode, which happens
+ // when collecting thread-stack roots using checkpoint.
+ Mutex mark_stack_lock_;
+ accounting::ObjectStack* mark_stack_;
+ // Special bitmap wherein all the bits corresponding to an object are set.
+ // TODO: make LiveWordsBitmap encapsulated in this class rather than a
+ // pointer. We tend to access its members in performance-sensitive
+ // code-path. Also, use a single MemMap for all the GC's data structures,
+ // which we will clear in the end. This would help in limiting the number of
+ // VMAs that get created in the kernel.
+ std::unique_ptr<LiveWordsBitmap<kAlignment>> live_words_bitmap_;
+ // Track GC-roots updated so far in a GC-cycle. This is to confirm that no
+ // GC-root is updated twice.
+ // TODO: Must be replaced with an efficient mechanism eventually. Or ensure
+ // that double updation doesn't happen in the first place.
+ std::unordered_set<void*> updated_roots_;
+ // Set of dex-caches visited during marking. See comment above
+ // RememberDexCaches() for the explanation.
+ std::unordered_set<uint32_t> dex_caches_;
+ MemMap from_space_map_;
+ // Any array of live-bytes in logical chunks of kOffsetChunkSize size
+ // in the 'to-be-compacted' space.
+ MemMap info_map_;
+ // The main space bitmap
+ accounting::ContinuousSpaceBitmap* moving_space_bitmap_;
+ accounting::ContinuousSpaceBitmap* non_moving_space_bitmap_;
+ space::ContinuousSpace* non_moving_space_;
+ space::BumpPointerSpace* const bump_pointer_space_;
+ Thread* thread_running_gc_;
+ // Array of pages' compaction status.
+ Atomic<PageState>* moving_pages_status_;
+ size_t vector_length_;
+ size_t live_stack_freeze_size_;
+
+ // For every page in the to-space (post-compact heap) we need to know the
+ // first object from which we must compact and/or update references. This is
+ // for both non-moving and moving space. Additionally, for the moving-space,
+ // we also need the offset within the object from where we need to start
+ // copying.
+ // chunk_info_vec_ holds live bytes for chunks during marking phase. After
+ // marking we perform an exclusive scan to compute offset for every chunk.
+ uint32_t* chunk_info_vec_;
+ // For pages before black allocations, pre_compact_offset_moving_space_[i]
+ // holds offset within the space from where the objects need to be copied in
+ // the ith post-compact page.
+ // Otherwise, black_alloc_pages_first_chunk_size_[i] holds the size of first
+ // non-empty chunk in the ith black-allocations page.
+ union {
+ uint32_t* pre_compact_offset_moving_space_;
+ uint32_t* black_alloc_pages_first_chunk_size_;
+ };
+ // first_objs_moving_space_[i] is the pre-compact address of the object which
+ // would overlap with the starting boundary of the ith post-compact page.
+ ObjReference* first_objs_moving_space_;
+ // First object for every page. It could be greater than the page's start
+ // address, or null if the page is empty.
+ ObjReference* first_objs_non_moving_space_;
+ size_t non_moving_first_objs_count_;
+ // Length of first_objs_moving_space_ and pre_compact_offset_moving_space_
+ // arrays. Also the number of pages which are to be compacted.
+ size_t moving_first_objs_count_;
+ // Number of pages containing black-allocated objects, indicating number of
+ // pages to be slid.
+ size_t black_page_count_;
+
+ uint8_t* from_space_begin_;
+ // moving-space's end pointer at the marking pause. All allocations beyond
+ // this will be considered black in the current GC cycle. Aligned up to page
+ // size.
+ uint8_t* black_allocations_begin_;
+ // End of compacted space. Use for computing post-compact addr of black
+ // allocated objects. Aligned up to page size.
+ uint8_t* post_compact_end_;
+ // Cache (black_allocations_begin_ - post_compact_end_) for post-compact
+ // address computations.
+ ptrdiff_t black_objs_slide_diff_;
+ // Cache (from_space_begin_ - bump_pointer_space_->Begin()) so that we can
+ // compute from-space address of a given pre-comapct addr efficiently.
+ ptrdiff_t from_space_slide_diff_;
+
+ // TODO: Remove once an efficient mechanism to deal with double root updation
+ // is incorporated.
+ void* stack_addr_;
+ void* stack_end_;
+
+ uint8_t* conc_compaction_termination_page_;
+ // Number of objects freed during this GC in moving space. It is decremented
+ // every time an object is discovered. And total-object count is added to it
+ // in MarkingPause(). It reaches the correct count only once the marking phase
+ // is completed.
+ int32_t freed_objects_;
+ // Userfault file descriptor, accessed only by the GC itself.
+ // kFallbackMode value indicates that we are in the fallback mode.
+ int uffd_;
+ // Used to exit from compaction loop at the end of concurrent compaction
+ uint8_t thread_pool_counter_;
+ // True while compacting.
+ bool compacting_;
+ // Flag indicating whether one-time uffd initialization has been done. It will
+ // be false on the first GC for non-zygote processes, and always for zygote.
+ // Its purpose is to minimize the userfaultfd overhead to the minimal in
+ // Heap::PostForkChildAction() as it's invoked in app startup path. With
+ // this, we register the compaction-termination page on the first GC.
+ bool uffd_initialized_;
+
+ class VerifyRootMarkedVisitor;
+ class ScanObjectVisitor;
+ class CheckpointMarkThreadRoots;
+ template<size_t kBufferSize> class ThreadRootsVisitor;
+ class CardModifiedVisitor;
+ class RefFieldsVisitor;
+ template <bool kCheckBegin, bool kCheckEnd> class RefsUpdateVisitor;
+ class NativeRootsUpdateVisitor;
+ class ImmuneSpaceUpdateObjVisitor;
+ class ConcurrentCompactionGcTask;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(MarkCompact);
+};
+
+} // namespace collector
+} // namespace gc
+} // namespace art
+
+#endif // ART_RUNTIME_GC_COLLECTOR_MARK_COMPACT_H_
diff --git a/runtime/gc/collector_type.h b/runtime/gc/collector_type.h
index 9c99964..c20e3a7 100644
--- a/runtime/gc/collector_type.h
+++ b/runtime/gc/collector_type.h
@@ -30,6 +30,8 @@
kCollectorTypeMS,
// Concurrent mark-sweep.
kCollectorTypeCMS,
+ // Concurrent mark-compact.
+ kCollectorTypeCMC,
// Semi-space / mark-sweep hybrid, enables compaction.
kCollectorTypeSS,
// Heap trimming collector, doesn't do any actual collecting.
@@ -63,12 +65,13 @@
std::ostream& operator<<(std::ostream& os, CollectorType collector_type);
static constexpr CollectorType kCollectorTypeDefault =
-#if ART_DEFAULT_GC_TYPE_IS_CMS
- kCollectorTypeCMS
+#if ART_DEFAULT_GC_TYPE_IS_CMC
+ kCollectorTypeCMC
#elif ART_DEFAULT_GC_TYPE_IS_SS
kCollectorTypeSS
-#else
+#elif ART_DEFAULT_GC_TYPE_IS_CMS
kCollectorTypeCMS
+#else
#error "ART default GC type must be set"
#endif
; // NOLINT [whitespace/semicolon] [5]
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index 9e1524e..9c76060 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -209,10 +209,8 @@
}
// IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for the
// BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
- // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant
- // since the allocator_type should be constant propagated.
- if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()
- && UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
+ // optimized out.
+ if (IsGcConcurrent() && UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
need_gc = true;
}
GetMetrics()->TotalBytesAllocated()->Add(bytes_tl_bulk_allocated);
@@ -442,7 +440,7 @@
return byte_count >= large_object_threshold_ && (c->IsPrimitiveArray() || c->IsStringClass());
}
-inline bool Heap::IsOutOfMemoryOnAllocation(AllocatorType allocator_type,
+inline bool Heap::IsOutOfMemoryOnAllocation(AllocatorType allocator_type ATTRIBUTE_UNUSED,
size_t alloc_size,
bool grow) {
size_t old_target = target_footprint_.load(std::memory_order_relaxed);
@@ -457,7 +455,7 @@
return true;
}
// We are between target_footprint_ and growth_limit_ .
- if (AllocatorMayHaveConcurrentGC(allocator_type) && IsGcConcurrent()) {
+ if (IsGcConcurrent()) {
return false;
} else {
if (grow) {
diff --git a/runtime/gc/heap-visit-objects-inl.h b/runtime/gc/heap-visit-objects-inl.h
index e20d981..a235c44 100644
--- a/runtime/gc/heap-visit-objects-inl.h
+++ b/runtime/gc/heap-visit-objects-inl.h
@@ -118,7 +118,7 @@
// For speed reasons, only perform it when Rosalloc could possibly be used.
// (Disabled for read barriers because it never uses Rosalloc).
// (See the DCHECK in RosAllocSpace constructor).
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
// Rosalloc has a race in allocation. Objects can be written into the allocation
// stack before their header writes are visible to this thread.
// See b/28790624 for more details.
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 8407ba4..dbe9161 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -21,10 +21,6 @@
#if defined(__BIONIC__) || defined(__GLIBC__)
#include <malloc.h> // For mallinfo()
#endif
-#if defined(__BIONIC__) && defined(ART_TARGET)
-#include <linux/userfaultfd.h>
-#include <sys/ioctl.h>
-#endif
#include <memory>
#include <random>
#include <unistd.h>
@@ -61,6 +57,7 @@
#include "gc/accounting/remembered_set.h"
#include "gc/accounting/space_bitmap-inl.h"
#include "gc/collector/concurrent_copying.h"
+#include "gc/collector/mark_compact.h"
#include "gc/collector/mark_sweep.h"
#include "gc/collector/partial_mark_sweep.h"
#include "gc/collector/semi_space.h"
@@ -410,7 +407,6 @@
backtrace_lock_(nullptr),
seen_backtrace_count_(0u),
unique_backtrace_count_(0u),
- uffd_(-1),
gc_disabled_for_shutdown_(false),
dump_region_info_before_gc_(dump_region_info_before_gc),
dump_region_info_after_gc_(dump_region_info_after_gc),
@@ -421,7 +417,7 @@
if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
LOG(INFO) << "Heap() entering";
}
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
CHECK_EQ(foreground_collector_type_, kCollectorTypeCC);
CHECK_EQ(background_collector_type_, kCollectorTypeCCBackground);
} else if (background_collector_type_ != gc::kCollectorTypeHomogeneousSpaceCompact) {
@@ -448,7 +444,8 @@
mark_bitmap_.reset(new accounting::HeapBitmap(this));
// We don't have hspace compaction enabled with CC.
- if (foreground_collector_type_ == kCollectorTypeCC) {
+ if (foreground_collector_type_ == kCollectorTypeCC
+ || foreground_collector_type_ == kCollectorTypeCMC) {
use_homogeneous_space_compaction_for_oom_ = false;
}
bool support_homogeneous_space_compaction =
@@ -629,10 +626,14 @@
std::move(main_mem_map_1));
CHECK(bump_pointer_space_ != nullptr) << "Failed to create bump pointer space";
AddSpace(bump_pointer_space_);
- temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
- std::move(main_mem_map_2));
- CHECK(temp_space_ != nullptr) << "Failed to create bump pointer space";
- AddSpace(temp_space_);
+ // For Concurrent Mark-compact GC we don't need the temp space to be in
+ // lower 4GB. So its temp space will be created by the GC itself.
+ if (foreground_collector_type_ != kCollectorTypeCMC) {
+ temp_space_ = space::BumpPointerSpace::CreateFromMemMap("Bump pointer space 2",
+ std::move(main_mem_map_2));
+ CHECK(temp_space_ != nullptr) << "Failed to create bump pointer space";
+ AddSpace(temp_space_);
+ }
CHECK(separate_non_moving_space);
} else {
CreateMainMallocSpace(std::move(main_mem_map_1), initial_size, growth_limit_, capacity_);
@@ -758,6 +759,10 @@
semi_space_collector_ = new collector::SemiSpace(this);
garbage_collectors_.push_back(semi_space_collector_);
}
+ if (MayUseCollector(kCollectorTypeCMC)) {
+ mark_compact_ = new collector::MarkCompact(this);
+ garbage_collectors_.push_back(mark_compact_);
+ }
if (MayUseCollector(kCollectorTypeCC)) {
concurrent_copying_collector_ = new collector::ConcurrentCopying(this,
/*young_gen=*/false,
@@ -963,7 +968,6 @@
void Heap::IncrementDisableThreadFlip(Thread* self) {
// Supposed to be called by mutators. If thread_flip_running_ is true, block. Otherwise, go ahead.
- CHECK(kUseReadBarrier);
bool is_nested = self->GetDisableThreadFlipCount() > 0;
self->IncrementDisableThreadFlipCount();
if (is_nested) {
@@ -994,10 +998,26 @@
}
}
+void Heap::EnsureObjectUserfaulted(ObjPtr<mirror::Object> obj) {
+ if (gUseUserfaultfd) {
+ // Use volatile to ensure that compiler loads from memory to trigger userfaults, if required.
+ volatile uint8_t volatile_sum;
+ volatile uint8_t* start = reinterpret_cast<volatile uint8_t*>(obj.Ptr());
+ volatile uint8_t* end = AlignUp(start + obj->SizeOf(), kPageSize);
+ uint8_t sum = 0;
+ // The first page is already touched by SizeOf().
+ start += kPageSize;
+ while (start < end) {
+ sum += *start;
+ start += kPageSize;
+ }
+ volatile_sum = sum;
+ }
+}
+
void Heap::DecrementDisableThreadFlip(Thread* self) {
// Supposed to be called by mutators. Decrement disable_thread_flip_count_ and potentially wake up
// the GC waiting before doing a thread flip.
- CHECK(kUseReadBarrier);
self->DecrementDisableThreadFlipCount();
bool is_outermost = self->GetDisableThreadFlipCount() == 0;
if (!is_outermost) {
@@ -1017,7 +1037,6 @@
void Heap::ThreadFlipBegin(Thread* self) {
// Supposed to be called by GC. Set thread_flip_running_ to be true. If disable_thread_flip_count_
// > 0, block. Otherwise, go ahead.
- CHECK(kUseReadBarrier);
ScopedThreadStateChange tsc(self, ThreadState::kWaitingForGcThreadFlip);
MutexLock mu(self, *thread_flip_lock_);
thread_flip_cond_->CheckSafeToWait(self);
@@ -1043,7 +1062,6 @@
void Heap::ThreadFlipEnd(Thread* self) {
// Supposed to be called by GC. Set thread_flip_running_ to false and potentially wake up mutators
// waiting before doing a JNI critical.
- CHECK(kUseReadBarrier);
MutexLock mu(self, *thread_flip_lock_);
CHECK(thread_flip_running_);
thread_flip_running_ = false;
@@ -1083,13 +1101,23 @@
}
}
-void Heap::CreateThreadPool() {
- const size_t num_threads = std::max(parallel_gc_threads_, conc_gc_threads_);
+void Heap::CreateThreadPool(size_t num_threads) {
+ if (num_threads == 0) {
+ num_threads = std::max(parallel_gc_threads_, conc_gc_threads_);
+ }
if (num_threads != 0) {
thread_pool_.reset(new ThreadPool("Heap thread pool", num_threads));
}
}
+void Heap::WaitForWorkersToBeCreated() {
+ DCHECK(!Runtime::Current()->IsShuttingDown(Thread::Current()))
+ << "Cannot create new threads during runtime shutdown";
+ if (thread_pool_ != nullptr) {
+ thread_pool_->WaitForWorkersToBeCreated();
+ }
+}
+
void Heap::MarkAllocStackAsLive(accounting::ObjectStack* stack) {
space::ContinuousSpace* space1 = main_space_ != nullptr ? main_space_ : non_moving_space_;
space::ContinuousSpace* space2 = non_moving_space_;
@@ -1497,6 +1525,20 @@
void Heap::DoPendingCollectorTransition() {
CollectorType desired_collector_type = desired_collector_type_;
+
+ if (collector_type_ == kCollectorTypeCC) {
+ // App's allocations (since last GC) more than the threshold then do TransitionGC
+ // when the app was in background. If not then don't do TransitionGC.
+ size_t num_bytes_allocated_since_gc = GetBytesAllocated() - num_bytes_alive_after_gc_;
+ if (num_bytes_allocated_since_gc <
+ (UnsignedDifference(target_footprint_.load(std::memory_order_relaxed),
+ num_bytes_alive_after_gc_)/4)
+ && !kStressCollectorTransition
+ && !IsLowMemoryMode()) {
+ return;
+ }
+ }
+
// Launch homogeneous space compaction if it is desired.
if (desired_collector_type == kCollectorTypeHomogeneousSpaceCompact) {
if (!CareAboutPauseTimes()) {
@@ -1505,12 +1547,12 @@
VLOG(gc) << "Homogeneous compaction ignored due to jank perceptible process state";
}
} else if (desired_collector_type == kCollectorTypeCCBackground) {
- DCHECK(kUseReadBarrier);
+ DCHECK(gUseReadBarrier);
if (!CareAboutPauseTimes()) {
// Invoke CC full compaction.
CollectGarbageInternal(collector::kGcTypeFull,
kGcCauseCollectorTransition,
- /*clear_soft_references=*/false, GC_NUM_ANY);
+ /*clear_soft_references=*/false, GetCurrentGcNum() + 1);
} else {
VLOG(gc) << "CC background compaction ignored due to jank perceptible process state";
}
@@ -2199,6 +2241,15 @@
}
break;
}
+ case kCollectorTypeCMC: {
+ gc_plan_.push_back(collector::kGcTypeFull);
+ if (use_tlab_) {
+ ChangeAllocator(kAllocatorTypeTLAB);
+ } else {
+ ChangeAllocator(kAllocatorTypeBumpPointer);
+ }
+ break;
+ }
case kCollectorTypeSS: {
gc_plan_.push_back(collector::kGcTypeFull);
if (use_tlab_) {
@@ -2368,10 +2419,6 @@
}
// We need to close userfaultfd fd for app/webview zygotes to avoid getattr
// (stat) on the fd during fork.
- if (uffd_ >= 0) {
- close(uffd_);
- uffd_ = -1;
- }
Thread* self = Thread::Current();
MutexLock mu(self, zygote_creation_lock_);
// Try to see if we have any Zygote spaces.
@@ -2710,6 +2757,9 @@
semi_space_collector_->SetSwapSemiSpaces(true);
collector = semi_space_collector_;
break;
+ case kCollectorTypeCMC:
+ collector = mark_compact_;
+ break;
case kCollectorTypeCC:
collector::ConcurrentCopying* active_cc_collector;
if (use_generational_cc_) {
@@ -2728,7 +2778,9 @@
default:
LOG(FATAL) << "Invalid collector type " << static_cast<size_t>(collector_type_);
}
- if (collector != active_concurrent_copying_collector_.load(std::memory_order_relaxed)) {
+ // temp_space_ will be null for kCollectorTypeCMC.
+ if (temp_space_ != nullptr
+ && collector != active_concurrent_copying_collector_.load(std::memory_order_relaxed)) {
temp_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
if (kIsDebugBuild) {
// Try to read each page of the memory map in case mprotect didn't work properly b/19894268.
@@ -3829,70 +3881,6 @@
return true; // Vacuously.
}
-#if defined(__BIONIC__) && defined(ART_TARGET)
-void Heap::MaybePerformUffdIoctls(GcCause cause, uint32_t requested_gc_num) const {
- if (uffd_ >= 0
- && cause == kGcCauseBackground
- && (requested_gc_num < 5 || requested_gc_num % 5 == 0)) {
- // Attempt to use all userfaultfd ioctls that we intend to use.
- // Register ioctl
- {
- struct uffdio_register uffd_register;
- uffd_register.range.start = 0;
- uffd_register.range.len = 0;
- uffd_register.mode = UFFDIO_REGISTER_MODE_MISSING;
- int ret = ioctl(uffd_, UFFDIO_REGISTER, &uffd_register);
- CHECK_EQ(ret, -1);
- CHECK_EQ(errno, EINVAL);
- }
- // Copy ioctl
- {
- struct uffdio_copy uffd_copy = {.src = 0, .dst = 0, .len = 0, .mode = 0};
- int ret = ioctl(uffd_, UFFDIO_COPY, &uffd_copy);
- CHECK_EQ(ret, -1);
- CHECK_EQ(errno, EINVAL);
- }
- // Zeropage ioctl
- {
- struct uffdio_zeropage uffd_zeropage;
- uffd_zeropage.range.start = 0;
- uffd_zeropage.range.len = 0;
- uffd_zeropage.mode = 0;
- int ret = ioctl(uffd_, UFFDIO_ZEROPAGE, &uffd_zeropage);
- CHECK_EQ(ret, -1);
- CHECK_EQ(errno, EINVAL);
- }
- // Continue ioctl
- {
- struct uffdio_continue uffd_continue;
- uffd_continue.range.start = 0;
- uffd_continue.range.len = 0;
- uffd_continue.mode = 0;
- int ret = ioctl(uffd_, UFFDIO_CONTINUE, &uffd_continue);
- CHECK_EQ(ret, -1);
- CHECK_EQ(errno, EINVAL);
- }
- // Wake ioctl
- {
- struct uffdio_range uffd_range = {.start = 0, .len = 0};
- int ret = ioctl(uffd_, UFFDIO_WAKE, &uffd_range);
- CHECK_EQ(ret, -1);
- CHECK_EQ(errno, EINVAL);
- }
- // Unregister ioctl
- {
- struct uffdio_range uffd_range = {.start = 0, .len = 0};
- int ret = ioctl(uffd_, UFFDIO_UNREGISTER, &uffd_range);
- CHECK_EQ(ret, -1);
- CHECK_EQ(errno, EINVAL);
- }
- }
-}
-#else
-void Heap::MaybePerformUffdIoctls(GcCause cause ATTRIBUTE_UNUSED,
- uint32_t requested_gc_num ATTRIBUTE_UNUSED) const {}
-#endif
-
void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full, uint32_t requested_gc_num) {
if (!Runtime::Current()->IsShuttingDown(self)) {
// Wait for any GCs currently running to finish. If this incremented GC number, we're done.
@@ -3919,12 +3907,9 @@
if (gc_type > next_gc_type &&
CollectGarbageInternal(gc_type, cause, false, requested_gc_num)
!= collector::kGcTypeNone) {
- MaybePerformUffdIoctls(cause, requested_gc_num);
break;
}
}
- } else {
- MaybePerformUffdIoctls(cause, requested_gc_num);
}
}
}
@@ -3956,16 +3941,6 @@
// For CC, we invoke a full compaction when going to the background, but the collector type
// doesn't change.
DCHECK_EQ(desired_collector_type_, kCollectorTypeCCBackground);
- // App's allocations (since last GC) more than the threshold then do TransitionGC
- // when the app was in background. If not then don't do TransitionGC.
- size_t num_bytes_allocated_since_gc = GetBytesAllocated() - num_bytes_alive_after_gc_;
- if (num_bytes_allocated_since_gc <
- (UnsignedDifference(target_footprint_.load(std::memory_order_relaxed),
- num_bytes_alive_after_gc_)/4)
- && !kStressCollectorTransition
- && !IsLowMemoryMode()) {
- return;
- }
}
DCHECK_NE(collector_type_, kCollectorTypeCCBackground);
CollectorTransitionTask* added_task = nullptr;
@@ -4280,7 +4255,7 @@
}
void Heap::AllowNewAllocationRecords() const {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_);
AllocRecordObjectMap* allocation_records = GetAllocationRecords();
if (allocation_records != nullptr) {
@@ -4289,7 +4264,7 @@
}
void Heap::DisallowNewAllocationRecords() const {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
MutexLock mu(Thread::Current(), *Locks::alloc_tracker_lock_);
AllocRecordObjectMap* allocation_records = GetAllocationRecords();
if (allocation_records != nullptr) {
@@ -4412,12 +4387,15 @@
}
void Heap::DisableGCForShutdown() {
- Thread* const self = Thread::Current();
- CHECK(Runtime::Current()->IsShuttingDown(self));
- MutexLock mu(self, *gc_complete_lock_);
+ MutexLock mu(Thread::Current(), *gc_complete_lock_);
gc_disabled_for_shutdown_ = true;
}
+bool Heap::IsGCDisabledForShutdown() const {
+ MutexLock mu(Thread::Current(), *gc_complete_lock_);
+ return gc_disabled_for_shutdown_;
+}
+
bool Heap::ObjectIsInBootImageSpace(ObjPtr<mirror::Object> obj) const {
DCHECK_EQ(IsBootImageAddress(obj.Ptr()),
any_of(boot_image_spaces_.begin(),
@@ -4494,8 +4472,13 @@
DCHECK_LE(alloc_size, self->TlabSize());
} else if (allocator_type == kAllocatorTypeTLAB) {
DCHECK(bump_pointer_space_ != nullptr);
+ // Try to allocate a page-aligned TLAB (not necessary though).
+ // TODO: for large allocations, which are rare, maybe we should allocate
+ // that object and return. There is no need to revoke the current TLAB,
+ // particularly if it's mostly unutilized.
+ size_t def_pr_tlab_size = RoundDown(alloc_size + kDefaultTLABSize, kPageSize) - alloc_size;
size_t next_tlab_size = JHPCalculateNextTlabSize(self,
- kDefaultTLABSize,
+ def_pr_tlab_size,
alloc_size,
&take_sample,
&bytes_until_sample);
@@ -4658,18 +4641,10 @@
uint64_t last_adj_time = NanoTime();
next_gc_type_ = NonStickyGcType(); // Always start with a full gc.
-#if defined(__BIONIC__) && defined(ART_TARGET)
- uffd_ = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK | UFFD_USER_MODE_ONLY);
- if (uffd_ >= 0) {
- struct uffdio_api api = {.api = UFFD_API, .features = 0};
- int ret = ioctl(uffd_, UFFDIO_API, &api);
- CHECK_EQ(ret, 0) << "ioctl_userfaultfd: API: " << strerror(errno);
- } else {
- // The syscall should fail only if it doesn't exist in the kernel or if it's
- // denied by SELinux.
- CHECK(errno == ENOSYS || errno == EACCES) << "userfaultfd: " << strerror(errno);
+ if (gUseUserfaultfd) {
+ DCHECK_NE(mark_compact_, nullptr);
+ mark_compact_->CreateUserfaultfd(/*post_fork*/true);
}
-#endif
// Temporarily increase target_footprint_ and concurrent_start_bytes_ to
// max values to avoid GC during app launch.
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 232c96b..044999d 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -87,6 +87,7 @@
namespace collector {
class ConcurrentCopying;
class GarbageCollector;
+class MarkCompact;
class MarkSweep;
class SemiSpace;
} // namespace collector
@@ -150,7 +151,7 @@
static constexpr size_t kMinLargeObjectThreshold = 3 * kPageSize;
static constexpr size_t kDefaultLargeObjectThreshold = kMinLargeObjectThreshold;
// Whether or not parallel GC is enabled. If not, then we never create the thread pool.
- static constexpr bool kDefaultEnableParallelGC = false;
+ static constexpr bool kDefaultEnableParallelGC = true;
static uint8_t* const kPreferredAllocSpaceBegin;
// Whether or not we use the free list large object space. Only use it if USE_ART_LOW_4G_ALLOCATOR
@@ -385,6 +386,9 @@
void ThreadFlipBegin(Thread* self) REQUIRES(!*thread_flip_lock_);
void ThreadFlipEnd(Thread* self) REQUIRES(!*thread_flip_lock_);
+ // Ensures that the obj doesn't cause userfaultfd in JNI critical calls.
+ void EnsureObjectUserfaulted(ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_);
+
// Clear all of the mark bits, doesn't clear bitmaps which have the same live bits as mark bits.
// Mutator lock is required for GetContinuousSpaces.
void ClearMarkedObjects()
@@ -578,6 +582,9 @@
return region_space_;
}
+ space::BumpPointerSpace* GetBumpPointerSpace() const {
+ return bump_pointer_space_;
+ }
// Implements java.lang.Runtime.maxMemory, returning the maximum amount of memory a program can
// consume. For a regular VM this would relate to the -Xmx option and would return -1 if no Xmx
// were specified. Android apps start with a growth limit (small heap size) which is
@@ -661,6 +668,10 @@
return live_stack_.get();
}
+ accounting::ObjectStack* GetAllocationStack() REQUIRES_SHARED(Locks::heap_bitmap_lock_) {
+ return allocation_stack_.get();
+ }
+
void PreZygoteFork() NO_THREAD_SAFETY_ANALYSIS;
// Mark and empty stack.
@@ -760,8 +771,10 @@
REQUIRES(!*gc_complete_lock_);
void ResetGcPerformanceInfo() REQUIRES(!*gc_complete_lock_);
- // Thread pool.
- void CreateThreadPool();
+ // Thread pool. Create either the given number of threads, or as per the
+ // values of conc_gc_threads_ and parallel_gc_threads_.
+ void CreateThreadPool(size_t num_threads = 0);
+ void WaitForWorkersToBeCreated();
void DeleteThreadPool();
ThreadPool* GetThreadPool() {
return thread_pool_.get();
@@ -812,6 +825,10 @@
return active_collector;
}
+ collector::MarkCompact* MarkCompactCollector() {
+ return mark_compact_;
+ }
+
CollectorType CurrentCollectorType() {
return collector_type_;
}
@@ -939,6 +956,7 @@
REQUIRES(!Locks::alloc_tracker_lock_);
void DisableGCForShutdown() REQUIRES(!*gc_complete_lock_);
+ bool IsGCDisabledForShutdown() const REQUIRES(!*gc_complete_lock_);
// Create a new alloc space and compact default alloc space to it.
HomogeneousSpaceCompactResult PerformHomogeneousSpaceCompact()
@@ -1001,9 +1019,6 @@
return main_space_backup_ != nullptr;
}
- // Attempt to use all the userfaultfd related ioctls.
- void MaybePerformUffdIoctls(GcCause cause, uint32_t requested_gc_num) const;
-
// Size_t saturating arithmetic
static ALWAYS_INLINE size_t UnsignedDifference(size_t x, size_t y) {
return x > y ? x - y : 0;
@@ -1019,19 +1034,11 @@
allocator_type != kAllocatorTypeTLAB &&
allocator_type != kAllocatorTypeRegion;
}
- static ALWAYS_INLINE bool AllocatorMayHaveConcurrentGC(AllocatorType allocator_type) {
- if (kUseReadBarrier) {
- // Read barrier may have the TLAB allocator but is always concurrent. TODO: clean this up.
- return true;
- }
- return
- allocator_type != kAllocatorTypeTLAB &&
- allocator_type != kAllocatorTypeBumpPointer;
- }
static bool IsMovingGc(CollectorType collector_type) {
return
collector_type == kCollectorTypeCC ||
collector_type == kCollectorTypeSS ||
+ collector_type == kCollectorTypeCMC ||
collector_type == kCollectorTypeCCBackground ||
collector_type == kCollectorTypeHomogeneousSpaceCompact;
}
@@ -1223,6 +1230,7 @@
// sweep GC, false for other GC types.
bool IsGcConcurrent() const ALWAYS_INLINE {
return collector_type_ == kCollectorTypeCC ||
+ collector_type_ == kCollectorTypeCMC ||
collector_type_ == kCollectorTypeCMS ||
collector_type_ == kCollectorTypeCCBackground;
}
@@ -1588,6 +1596,7 @@
std::vector<collector::GarbageCollector*> garbage_collectors_;
collector::SemiSpace* semi_space_collector_;
+ collector::MarkCompact* mark_compact_;
Atomic<collector::ConcurrentCopying*> active_concurrent_copying_collector_;
collector::ConcurrentCopying* young_concurrent_copying_collector_;
collector::ConcurrentCopying* concurrent_copying_collector_;
@@ -1680,9 +1689,6 @@
// Stack trace hashes that we already saw,
std::unordered_set<uint64_t> seen_backtraces_ GUARDED_BY(backtrace_lock_);
- // Userfaultfd file descriptor.
- // TODO (lokeshgidra): remove this when the userfaultfd-based GC is in use.
- int uffd_;
// We disable GC when we are shutting down the runtime in case there are daemon threads still
// allocating.
bool gc_disabled_for_shutdown_ GUARDED_BY(gc_complete_lock_);
@@ -1712,6 +1718,7 @@
friend class CollectorTransitionTask;
friend class collector::GarbageCollector;
friend class collector::ConcurrentCopying;
+ friend class collector::MarkCompact;
friend class collector::MarkSweep;
friend class collector::SemiSpace;
friend class GCCriticalSection;
diff --git a/runtime/gc/heap_verification_test.cc b/runtime/gc/heap_verification_test.cc
index ca6a30b..789a8e3 100644
--- a/runtime/gc/heap_verification_test.cc
+++ b/runtime/gc/heap_verification_test.cc
@@ -26,7 +26,7 @@
#include "mirror/string.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
-#include "verification.h"
+#include "verification-inl.h"
namespace art {
namespace gc {
@@ -76,11 +76,11 @@
Handle<mirror::String> string(
hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test")));
const Verification* const v = Runtime::Current()->GetHeap()->GetVerification();
- EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(1)));
- EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(4)));
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(1)));
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(4)));
EXPECT_FALSE(v->IsValidClass(nullptr));
EXPECT_TRUE(v->IsValidClass(string->GetClass()));
- EXPECT_FALSE(v->IsValidClass(string.Get()));
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(string.Get())));
}
TEST_F(VerificationTest, IsValidClassInHeap) {
@@ -95,9 +95,9 @@
Handle<mirror::String> string(
hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), "test")));
const Verification* const v = Runtime::Current()->GetHeap()->GetVerification();
- const uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass());
- EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(uint_klass - kObjectAlignment)));
- EXPECT_FALSE(v->IsValidClass(reinterpret_cast<const void*>(&uint_klass)));
+ uintptr_t uint_klass = reinterpret_cast<uintptr_t>(string->GetClass());
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(uint_klass - kObjectAlignment)));
+ EXPECT_FALSE(v->IsValidClass(reinterpret_cast<mirror::Class*>(&uint_klass)));
}
TEST_F(VerificationTest, DumpInvalidObjectInfo) {
diff --git a/runtime/gc/reference_processor.cc b/runtime/gc/reference_processor.cc
index 5e41ee4..772174f 100644
--- a/runtime/gc/reference_processor.cc
+++ b/runtime/gc/reference_processor.cc
@@ -90,7 +90,7 @@
ObjPtr<mirror::Object> ReferenceProcessor::GetReferent(Thread* self,
ObjPtr<mirror::Reference> reference) {
auto slow_path_required = [this, self]() REQUIRES_SHARED(Locks::mutator_lock_) {
- return kUseReadBarrier ? !self->GetWeakRefAccessEnabled() : SlowPathEnabled();
+ return gUseReadBarrier ? !self->GetWeakRefAccessEnabled() : SlowPathEnabled();
};
if (!slow_path_required()) {
return reference->GetReferent();
@@ -118,10 +118,10 @@
// Keeping reference_processor_lock_ blocks the broadcast when we try to reenable the fast path.
while (slow_path_required()) {
DCHECK(collector_ != nullptr);
- constexpr bool kOtherReadBarrier = kUseReadBarrier && !kUseBakerReadBarrier;
+ const bool other_read_barrier = !kUseBakerReadBarrier && gUseReadBarrier;
if (UNLIKELY(reference->IsFinalizerReferenceInstance()
|| rp_state_ == RpState::kStarting /* too early to determine mark state */
- || (kOtherReadBarrier && reference->IsPhantomReferenceInstance()))) {
+ || (other_read_barrier && reference->IsPhantomReferenceInstance()))) {
// Odd cases in which it doesn't hurt to just wait, or the wait is likely to be very brief.
// Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
@@ -210,7 +210,7 @@
}
{
MutexLock mu(self, *Locks::reference_processor_lock_);
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
CHECK_EQ(SlowPathEnabled(), concurrent_) << "Slow path must be enabled iff concurrent";
} else {
// Weak ref access is enabled at Zygote compaction by SemiSpace (concurrent_ == false).
@@ -305,7 +305,7 @@
// could result in a stale is_marked_callback_ being called before the reference processing
// starts since there is a small window of time where slow_path_enabled_ is enabled but the
// callback isn't yet set.
- if (!kUseReadBarrier && concurrent_) {
+ if (!gUseReadBarrier && concurrent_) {
// Done processing, disable the slow path and broadcast to the waiters.
DisableSlowPath(self);
}
@@ -418,8 +418,8 @@
void ReferenceProcessor::WaitUntilDoneProcessingReferences(Thread* self) {
// Wait until we are done processing reference.
- while ((!kUseReadBarrier && SlowPathEnabled()) ||
- (kUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
+ while ((!gUseReadBarrier && SlowPathEnabled()) ||
+ (gUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
// Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
// presence of threads blocking for weak ref access.
self->CheckEmptyCheckpointFromWeakRefAccess(Locks::reference_processor_lock_);
diff --git a/runtime/gc/space/bump_pointer_space-inl.h b/runtime/gc/space/bump_pointer_space-inl.h
index 20f7a93..2774b9e 100644
--- a/runtime/gc/space/bump_pointer_space-inl.h
+++ b/runtime/gc/space/bump_pointer_space-inl.h
@@ -20,6 +20,7 @@
#include "bump_pointer_space.h"
#include "base/bit_utils.h"
+#include "mirror/object-inl.h"
namespace art {
namespace gc {
@@ -89,6 +90,11 @@
return ret;
}
+inline mirror::Object* BumpPointerSpace::GetNextObject(mirror::Object* obj) {
+ const uintptr_t position = reinterpret_cast<uintptr_t>(obj) + obj->SizeOf();
+ return reinterpret_cast<mirror::Object*>(RoundUp(position, kAlignment));
+}
+
} // namespace space
} // namespace gc
} // namespace art
diff --git a/runtime/gc/space/bump_pointer_space-walk-inl.h b/runtime/gc/space/bump_pointer_space-walk-inl.h
index 5d05ea2..a978f62 100644
--- a/runtime/gc/space/bump_pointer_space-walk-inl.h
+++ b/runtime/gc/space/bump_pointer_space-walk-inl.h
@@ -17,12 +17,14 @@
#ifndef ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_WALK_INL_H_
#define ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_WALK_INL_H_
-#include "bump_pointer_space.h"
+#include "bump_pointer_space-inl.h"
#include "base/bit_utils.h"
#include "mirror/object-inl.h"
#include "thread-current-inl.h"
+#include <memory>
+
namespace art {
namespace gc {
namespace space {
@@ -32,6 +34,7 @@
uint8_t* pos = Begin();
uint8_t* end = End();
uint8_t* main_end = pos;
+ std::unique_ptr<std::vector<size_t>> block_sizes_copy;
// Internal indirection w/ NO_THREAD_SAFETY_ANALYSIS. Optimally, we'd like to have an annotation
// like
// REQUIRES_AS(visitor.operator(mirror::Object*))
@@ -49,15 +52,17 @@
MutexLock mu(Thread::Current(), block_lock_);
// If we have 0 blocks then we need to update the main header since we have bump pointer style
// allocation into an unbounded region (actually bounded by Capacity()).
- if (num_blocks_ == 0) {
+ if (block_sizes_.empty()) {
UpdateMainBlock();
}
main_end = Begin() + main_block_size_;
- if (num_blocks_ == 0) {
+ if (block_sizes_.empty()) {
// We don't have any other blocks, this means someone else may be allocating into the main
// block. In this case, we don't want to try and visit the other blocks after the main block
// since these could actually be part of the main block.
end = main_end;
+ } else {
+ block_sizes_copy.reset(new std::vector<size_t>(block_sizes_.begin(), block_sizes_.end()));
}
}
// Walk all of the objects in the main block first.
@@ -66,31 +71,33 @@
// No read barrier because obj may not be a valid object.
if (obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() == nullptr) {
// There is a race condition where a thread has just allocated an object but not set the
- // class. We can't know the size of this object, so we don't visit it and exit the function
- // since there is guaranteed to be not other blocks.
- return;
+ // class. We can't know the size of this object, so we don't visit it and break the loop
+ pos = main_end;
+ break;
} else {
no_thread_safety_analysis_visit(obj);
pos = reinterpret_cast<uint8_t*>(GetNextObject(obj));
}
}
// Walk the other blocks (currently only TLABs).
- while (pos < end) {
- BlockHeader* header = reinterpret_cast<BlockHeader*>(pos);
- size_t block_size = header->size_;
- pos += sizeof(BlockHeader); // Skip the header so that we know where the objects
- mirror::Object* obj = reinterpret_cast<mirror::Object*>(pos);
- const mirror::Object* end_obj = reinterpret_cast<const mirror::Object*>(pos + block_size);
- CHECK_LE(reinterpret_cast<const uint8_t*>(end_obj), End());
- // We don't know how many objects are allocated in the current block. When we hit a null class
- // assume its the end. TODO: Have a thread update the header when it flushes the block?
- // No read barrier because obj may not be a valid object.
- while (obj < end_obj && obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() != nullptr) {
- no_thread_safety_analysis_visit(obj);
- obj = GetNextObject(obj);
+ if (block_sizes_copy != nullptr) {
+ for (size_t block_size : *block_sizes_copy) {
+ mirror::Object* obj = reinterpret_cast<mirror::Object*>(pos);
+ const mirror::Object* end_obj = reinterpret_cast<const mirror::Object*>(pos + block_size);
+ CHECK_LE(reinterpret_cast<const uint8_t*>(end_obj), End());
+ // We don't know how many objects are allocated in the current block. When we hit a null class
+ // assume it's the end. TODO: Have a thread update the header when it flushes the block?
+ // No read barrier because obj may not be a valid object.
+ while (obj < end_obj && obj->GetClass<kDefaultVerifyFlags, kWithoutReadBarrier>() != nullptr) {
+ no_thread_safety_analysis_visit(obj);
+ obj = GetNextObject(obj);
+ }
+ pos += block_size;
}
- pos += block_size;
+ } else {
+ CHECK_EQ(end, main_end);
}
+ CHECK_EQ(pos, end);
}
} // namespace space
diff --git a/runtime/gc/space/bump_pointer_space.cc b/runtime/gc/space/bump_pointer_space.cc
index 3a0155a..7753f73 100644
--- a/runtime/gc/space/bump_pointer_space.cc
+++ b/runtime/gc/space/bump_pointer_space.cc
@@ -54,8 +54,9 @@
growth_end_(limit),
objects_allocated_(0), bytes_allocated_(0),
block_lock_("Block lock"),
- main_block_size_(0),
- num_blocks_(0) {
+ main_block_size_(0) {
+ // This constructor gets called only from Heap::PreZygoteFork(), which
+ // doesn't require a mark_bitmap.
}
BumpPointerSpace::BumpPointerSpace(const std::string& name, MemMap&& mem_map)
@@ -68,8 +69,11 @@
growth_end_(mem_map_.End()),
objects_allocated_(0), bytes_allocated_(0),
block_lock_("Block lock", kBumpPointerSpaceBlockLock),
- main_block_size_(0),
- num_blocks_(0) {
+ main_block_size_(0) {
+ mark_bitmap_ =
+ accounting::ContinuousSpaceBitmap::Create("bump-pointer space live bitmap",
+ Begin(),
+ Capacity());
}
void BumpPointerSpace::Clear() {
@@ -86,7 +90,7 @@
growth_end_ = Limit();
{
MutexLock mu(Thread::Current(), block_lock_);
- num_blocks_ = 0;
+ block_sizes_.clear();
main_block_size_ = 0;
}
}
@@ -97,11 +101,6 @@
<< reinterpret_cast<void*>(Limit());
}
-mirror::Object* BumpPointerSpace::GetNextObject(mirror::Object* obj) {
- const uintptr_t position = reinterpret_cast<uintptr_t>(obj) + obj->SizeOf();
- return reinterpret_cast<mirror::Object*>(RoundUp(position, kAlignment));
-}
-
size_t BumpPointerSpace::RevokeThreadLocalBuffers(Thread* thread) {
MutexLock mu(Thread::Current(), block_lock_);
RevokeThreadLocalBuffersLocked(thread);
@@ -141,23 +140,19 @@
}
void BumpPointerSpace::UpdateMainBlock() {
- DCHECK_EQ(num_blocks_, 0U);
+ DCHECK(block_sizes_.empty());
main_block_size_ = Size();
}
// Returns the start of the storage.
uint8_t* BumpPointerSpace::AllocBlock(size_t bytes) {
bytes = RoundUp(bytes, kAlignment);
- if (!num_blocks_) {
+ if (block_sizes_.empty()) {
UpdateMainBlock();
}
- uint8_t* storage = reinterpret_cast<uint8_t*>(
- AllocNonvirtualWithoutAccounting(bytes + sizeof(BlockHeader)));
+ uint8_t* storage = reinterpret_cast<uint8_t*>(AllocNonvirtualWithoutAccounting(bytes));
if (LIKELY(storage != nullptr)) {
- BlockHeader* header = reinterpret_cast<BlockHeader*>(storage);
- header->size_ = bytes; // Write out the block header.
- storage += sizeof(BlockHeader);
- ++num_blocks_;
+ block_sizes_.push_back(bytes);
}
return storage;
}
@@ -177,7 +172,7 @@
MutexLock mu3(Thread::Current(), block_lock_);
// If we don't have any blocks, we don't have any thread local buffers. This check is required
// since there can exist multiple bump pointer spaces which exist at the same time.
- if (num_blocks_ > 0) {
+ if (!block_sizes_.empty()) {
for (Thread* thread : thread_list) {
total += thread->GetThreadLocalBytesAllocated();
}
@@ -195,7 +190,7 @@
MutexLock mu3(Thread::Current(), block_lock_);
// If we don't have any blocks, we don't have any thread local buffers. This check is required
// since there can exist multiple bump pointer spaces which exist at the same time.
- if (num_blocks_ > 0) {
+ if (!block_sizes_.empty()) {
for (Thread* thread : thread_list) {
total += thread->GetThreadLocalObjectsAllocated();
}
@@ -240,6 +235,52 @@
return num_bytes;
}
+uint8_t* BumpPointerSpace::AlignEnd(Thread* self, size_t alignment) {
+ Locks::mutator_lock_->AssertExclusiveHeld(self);
+ DCHECK(IsAligned<kAlignment>(alignment));
+ uint8_t* end = end_.load(std::memory_order_relaxed);
+ uint8_t* aligned_end = AlignUp(end, alignment);
+ ptrdiff_t diff = aligned_end - end;
+ if (diff > 0) {
+ end_.store(aligned_end, std::memory_order_relaxed);
+ // If we have blocks after the main one. Then just add the diff to the last
+ // block.
+ MutexLock mu(self, block_lock_);
+ if (!block_sizes_.empty()) {
+ block_sizes_.back() += diff;
+ }
+ }
+ return end;
+}
+
+std::vector<size_t>* BumpPointerSpace::GetBlockSizes(Thread* self, size_t* main_block_size) {
+ std::vector<size_t>* block_sizes = nullptr;
+ MutexLock mu(self, block_lock_);
+ if (!block_sizes_.empty()) {
+ block_sizes = new std::vector<size_t>(block_sizes_.begin(), block_sizes_.end());
+ } else {
+ UpdateMainBlock();
+ }
+ *main_block_size = main_block_size_;
+ return block_sizes;
+}
+
+void BumpPointerSpace::SetBlockSizes(Thread* self,
+ const size_t main_block_size,
+ const size_t first_valid_idx) {
+ MutexLock mu(self, block_lock_);
+ main_block_size_ = main_block_size;
+ if (!block_sizes_.empty()) {
+ block_sizes_.erase(block_sizes_.begin(), block_sizes_.begin() + first_valid_idx);
+ }
+ size_t size = main_block_size;
+ for (size_t block_size : block_sizes_) {
+ size += block_size;
+ }
+ DCHECK(IsAligned<kAlignment>(size));
+ end_.store(Begin() + size, std::memory_order_relaxed);
+}
+
} // namespace space
} // namespace gc
} // namespace art
diff --git a/runtime/gc/space/bump_pointer_space.h b/runtime/gc/space/bump_pointer_space.h
index 08ed503..bba1711 100644
--- a/runtime/gc/space/bump_pointer_space.h
+++ b/runtime/gc/space/bump_pointer_space.h
@@ -17,9 +17,10 @@
#ifndef ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_H_
#define ART_RUNTIME_GC_SPACE_BUMP_POINTER_SPACE_H_
+#include "base/mutex.h"
#include "space.h"
-#include "base/mutex.h"
+#include <deque>
namespace art {
@@ -30,6 +31,7 @@
namespace gc {
namespace collector {
+class MarkCompact;
class MarkSweep;
} // namespace collector
@@ -39,7 +41,7 @@
// implementation as its intended to be evacuated.
class BumpPointerSpace final : public ContinuousMemMapAllocSpace {
public:
- typedef void(*WalkCallback)(void *start, void *end, size_t num_bytes, void* callback_arg);
+ using WalkCallback = void (*)(void *, void *, int, void *);
SpaceType GetType() const override {
return kSpaceTypeBumpPointerSpace;
@@ -100,10 +102,6 @@
return nullptr;
}
- accounting::ContinuousSpaceBitmap* GetMarkBitmap() override {
- return nullptr;
- }
-
// Reset the space to empty.
void Clear() override REQUIRES(!block_lock_);
@@ -120,6 +118,11 @@
REQUIRES(!*Locks::runtime_shutdown_lock_, !*Locks::thread_list_lock_, !block_lock_);
uint64_t GetObjectsAllocated() override REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!*Locks::runtime_shutdown_lock_, !*Locks::thread_list_lock_, !block_lock_);
+ // Return the pre-determined allocated object count. This could be beneficial
+ // when we know that all the TLABs are revoked.
+ int32_t GetAccumulatedObjectsAllocated() REQUIRES_SHARED(Locks::mutator_lock_) {
+ return objects_allocated_.load(std::memory_order_relaxed);
+ }
bool IsEmpty() const {
return Begin() == End();
}
@@ -128,18 +131,9 @@
return true;
}
- bool Contains(const mirror::Object* obj) const override {
- const uint8_t* byte_obj = reinterpret_cast<const uint8_t*>(obj);
- return byte_obj >= Begin() && byte_obj < End();
- }
-
// TODO: Change this? Mainly used for compacting to a particular region of memory.
BumpPointerSpace(const std::string& name, uint8_t* begin, uint8_t* limit);
- // Return the object which comes after obj, while ensuring alignment.
- static mirror::Object* GetNextObject(mirror::Object* obj)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Allocate a new TLAB, returns false if the allocation failed.
bool AllocNewTlab(Thread* self, size_t bytes) REQUIRES(!block_lock_);
@@ -165,7 +159,7 @@
REQUIRES_SHARED(Locks::mutator_lock_);
// Object alignment within the space.
- static constexpr size_t kAlignment = 8;
+ static constexpr size_t kAlignment = kObjectAlignment;
protected:
BumpPointerSpace(const std::string& name, MemMap&& mem_map);
@@ -183,23 +177,40 @@
AtomicInteger objects_allocated_; // Accumulated from revoked thread local regions.
AtomicInteger bytes_allocated_; // Accumulated from revoked thread local regions.
Mutex block_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
- // The objects at the start of the space are stored in the main block. The main block doesn't
- // have a header, this lets us walk empty spaces which are mprotected.
+ // The objects at the start of the space are stored in the main block.
size_t main_block_size_ GUARDED_BY(block_lock_);
- // The number of blocks in the space, if it is 0 then the space has one long continuous block
- // which doesn't have an updated header.
- size_t num_blocks_ GUARDED_BY(block_lock_);
+ // List of block sizes (in bytes) after the main-block. Needed for Walk().
+ // If empty then the space has only one long continuous block. Each TLAB
+ // allocation has one entry in this deque.
+ // Keeping block-sizes off-heap simplifies sliding compaction algorithms.
+ // The compaction algorithm should ideally compact all objects into the main
+ // block, thereby enabling erasing corresponding entries from here.
+ std::deque<size_t> block_sizes_ GUARDED_BY(block_lock_);
private:
- struct BlockHeader {
- size_t size_; // Size of the block in bytes, does not include the header.
- size_t unused_; // Ensures alignment of kAlignment.
- };
+ // Return the object which comes after obj, while ensuring alignment.
+ static mirror::Object* GetNextObject(mirror::Object* obj)
+ REQUIRES_SHARED(Locks::mutator_lock_);
- static_assert(sizeof(BlockHeader) % kAlignment == 0,
- "continuous block must be kAlignment aligned");
+ // Return a vector of block sizes on the space. Required by MarkCompact GC for
+ // walking black objects allocated after marking phase.
+ std::vector<size_t>* GetBlockSizes(Thread* self, size_t* main_block_size) REQUIRES(!block_lock_);
+
+ // Once the MarkCompact decides the post-compact layout of the space in the
+ // pre-compaction pause, it calls this function to update the block sizes. It is
+ // done by passing the new main-block size, which consumes a bunch of blocks
+ // into itself, and the index of first unconsumed block. This works as all the
+ // block sizes are ordered. Also updates 'end_' to reflect the change.
+ void SetBlockSizes(Thread* self, const size_t main_block_size, const size_t first_valid_idx)
+ REQUIRES(!block_lock_, Locks::mutator_lock_);
+
+ // Align end to the given alignment. This is done in MarkCompact GC when
+ // mutators are suspended so that upcoming TLAB allocations start with a new
+ // page. Returns the pre-alignment end.
+ uint8_t* AlignEnd(Thread* self, size_t alignment) REQUIRES(Locks::mutator_lock_);
friend class collector::MarkSweep;
+ friend class collector::MarkCompact;
DISALLOW_COPY_AND_ASSIGN(BumpPointerSpace);
};
diff --git a/runtime/gc/space/dlmalloc_space-inl.h b/runtime/gc/space/dlmalloc_space-inl.h
index 4fc4ada..6041fd0 100644
--- a/runtime/gc/space/dlmalloc_space-inl.h
+++ b/runtime/gc/space/dlmalloc_space-inl.h
@@ -18,7 +18,7 @@
#define ART_RUNTIME_GC_SPACE_DLMALLOC_SPACE_INL_H_
#include "dlmalloc_space.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
#include "thread.h"
namespace art {
diff --git a/runtime/gc/space/dlmalloc_space.cc b/runtime/gc/space/dlmalloc_space.cc
index 25cac7e..1edcdbd 100644
--- a/runtime/gc/space/dlmalloc_space.cc
+++ b/runtime/gc/space/dlmalloc_space.cc
@@ -350,11 +350,18 @@
}
#endif
+struct MspaceCbArgs {
+ size_t max_contiguous;
+ size_t used;
+};
+
static void MSpaceChunkCallback(void* start, void* end, size_t used_bytes, void* arg) {
size_t chunk_size = reinterpret_cast<uint8_t*>(end) - reinterpret_cast<uint8_t*>(start);
+ MspaceCbArgs* mspace_cb_args = reinterpret_cast<MspaceCbArgs*>(arg);
+ mspace_cb_args->used += used_bytes;
if (used_bytes < chunk_size) {
size_t chunk_free_bytes = chunk_size - used_bytes;
- size_t& max_contiguous_allocation = *reinterpret_cast<size_t*>(arg);
+ size_t& max_contiguous_allocation = mspace_cb_args->max_contiguous;
max_contiguous_allocation = std::max(max_contiguous_allocation, chunk_free_bytes);
}
}
@@ -362,16 +369,17 @@
bool DlMallocSpace::LogFragmentationAllocFailure(std::ostream& os,
size_t failed_alloc_bytes) {
Thread* const self = Thread::Current();
- size_t max_contiguous_allocation = 0;
+ MspaceCbArgs mspace_cb_args = {0, 0};
// To allow the Walk/InspectAll() to exclusively-lock the mutator
// lock, temporarily release the shared access to the mutator
// lock here by transitioning to the suspended state.
Locks::mutator_lock_->AssertSharedHeld(self);
ScopedThreadSuspension sts(self, ThreadState::kSuspended);
- Walk(MSpaceChunkCallback, &max_contiguous_allocation);
- if (failed_alloc_bytes > max_contiguous_allocation) {
- os << "; failed due to fragmentation (largest possible contiguous allocation "
- << max_contiguous_allocation << " bytes)";
+ Walk(MSpaceChunkCallback, &mspace_cb_args);
+ if (failed_alloc_bytes > mspace_cb_args.max_contiguous) {
+ os << "; failed due to malloc_space fragmentation (largest possible contiguous allocation "
+ << mspace_cb_args.max_contiguous << " bytes, space in use " << mspace_cb_args.used
+ << " bytes, capacity = " << Capacity() << ")";
return true;
}
return false;
diff --git a/runtime/gc/space/image_space.cc b/runtime/gc/space/image_space.cc
index 6afd63e..41bb62f 100644
--- a/runtime/gc/space/image_space.cc
+++ b/runtime/gc/space/image_space.cc
@@ -23,7 +23,9 @@
#include <memory>
#include <random>
#include <string>
+#include <vector>
+#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
#include "android-base/unique_fd.h"
@@ -198,7 +200,6 @@
// Helper class for relocating from one range of memory to another.
class RelocationRange {
public:
- RelocationRange() = default;
RelocationRange(const RelocationRange&) = default;
RelocationRange(uintptr_t source, uintptr_t dest, uintptr_t length)
: source_(source),
@@ -1366,9 +1367,9 @@
}
};
-static void AppendImageChecksum(uint32_t component_count,
- uint32_t checksum,
- /*inout*/std::string* checksums) {
+void ImageSpace::AppendImageChecksum(uint32_t component_count,
+ uint32_t checksum,
+ /*inout*/ std::string* checksums) {
static_assert(ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check.");
StringAppendF(checksums, "i;%u/%08x", component_count, checksum);
}
@@ -1378,7 +1379,7 @@
/*inout*/std::string_view* oat_checksums,
/*out*/std::string* error_msg) {
std::string image_checksum;
- AppendImageChecksum(component_count, checksum, &image_checksum);
+ ImageSpace::AppendImageChecksum(component_count, checksum, &image_checksum);
if (!StartsWith(*oat_checksums, image_checksum)) {
*error_msg = StringPrintf("Image checksum mismatch, expected %s to start with %s",
std::string(*oat_checksums).c_str(),
@@ -1389,182 +1390,6 @@
return true;
}
-// Helper class to find the primary boot image and boot image extensions
-// and determine the boot image layout.
-class ImageSpace::BootImageLayout {
- public:
- // Description of a "chunk" of the boot image, i.e. either primary boot image
- // or a boot image extension, used in conjunction with the boot class path to
- // load boot image components.
- struct ImageChunk {
- std::string base_location;
- std::string base_filename;
- std::vector<std::string> profile_files;
- size_t start_index;
- uint32_t component_count;
- uint32_t image_space_count;
- uint32_t reservation_size;
- uint32_t checksum;
- uint32_t boot_image_component_count;
- uint32_t boot_image_checksum;
- uint32_t boot_image_size;
-
- // The following file descriptors hold the memfd files for extensions compiled
- // in memory and described by the above fields. We want to use them to mmap()
- // the contents and then close them while treating the ImageChunk description
- // as immutable (const), so make these fields explicitly mutable.
- mutable android::base::unique_fd art_fd;
- mutable android::base::unique_fd vdex_fd;
- mutable android::base::unique_fd oat_fd;
- };
-
- BootImageLayout(ArrayRef<const std::string> image_locations,
- ArrayRef<const std::string> boot_class_path,
- ArrayRef<const std::string> boot_class_path_locations,
- ArrayRef<const int> boot_class_path_fds,
- ArrayRef<const int> boot_class_path_image_fds,
- ArrayRef<const int> boot_class_path_vdex_fds,
- ArrayRef<const int> boot_class_path_oat_fds)
- : image_locations_(image_locations),
- boot_class_path_(boot_class_path),
- boot_class_path_locations_(boot_class_path_locations),
- boot_class_path_fds_(boot_class_path_fds),
- boot_class_path_image_fds_(boot_class_path_image_fds),
- boot_class_path_vdex_fds_(boot_class_path_vdex_fds),
- boot_class_path_oat_fds_(boot_class_path_oat_fds) {}
-
- std::string GetPrimaryImageLocation();
-
- bool LoadFromSystem(InstructionSet image_isa, /*out*/std::string* error_msg) {
- return LoadOrValidateFromSystem(image_isa, /*oat_checksums=*/ nullptr, error_msg);
- }
-
- bool ValidateFromSystem(InstructionSet image_isa,
- /*inout*/std::string_view* oat_checksums,
- /*out*/std::string* error_msg) {
- DCHECK(oat_checksums != nullptr);
- return LoadOrValidateFromSystem(image_isa, oat_checksums, error_msg);
- }
-
- ArrayRef<const ImageChunk> GetChunks() const {
- return ArrayRef<const ImageChunk>(chunks_);
- }
-
- uint32_t GetBaseAddress() const {
- return base_address_;
- }
-
- size_t GetNextBcpIndex() const {
- return next_bcp_index_;
- }
-
- size_t GetTotalComponentCount() const {
- return total_component_count_;
- }
-
- size_t GetTotalReservationSize() const {
- return total_reservation_size_;
- }
-
- private:
- struct NamedComponentLocation {
- std::string base_location;
- size_t bcp_index;
- std::vector<std::string> profile_filenames;
- };
-
- std::string ExpandLocationImpl(const std::string& location,
- size_t bcp_index,
- bool boot_image_extension) {
- std::vector<std::string> expanded = ExpandMultiImageLocations(
- ArrayRef<const std::string>(boot_class_path_).SubArray(bcp_index, 1u),
- location,
- boot_image_extension);
- DCHECK_EQ(expanded.size(), 1u);
- return expanded[0];
- }
-
- std::string ExpandLocation(const std::string& location, size_t bcp_index) {
- if (bcp_index == 0u) {
- DCHECK_EQ(location, ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/ false));
- return location;
- } else {
- return ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/ true);
- }
- }
-
- std::string GetBcpComponentPath(size_t bcp_index) {
- DCHECK_LE(bcp_index, boot_class_path_.size());
- size_t bcp_slash_pos = boot_class_path_[bcp_index].rfind('/');
- DCHECK_NE(bcp_slash_pos, std::string::npos);
- return boot_class_path_[bcp_index].substr(0u, bcp_slash_pos + 1u);
- }
-
- bool VerifyImageLocation(ArrayRef<const std::string> components,
- /*out*/size_t* named_components_count,
- /*out*/std::string* error_msg);
-
- bool MatchNamedComponents(
- ArrayRef<const std::string> named_components,
- /*out*/std::vector<NamedComponentLocation>* named_component_locations,
- /*out*/std::string* error_msg);
-
- bool ValidateBootImageChecksum(const char* file_description,
- const ImageHeader& header,
- /*out*/std::string* error_msg);
-
- bool ValidateHeader(const ImageHeader& header,
- size_t bcp_index,
- const char* file_description,
- /*out*/std::string* error_msg);
-
- bool ValidateOatFile(const std::string& base_location,
- const std::string& base_filename,
- size_t bcp_index,
- size_t component_count,
- /*out*/std::string* error_msg);
-
- bool ReadHeader(const std::string& base_location,
- const std::string& base_filename,
- size_t bcp_index,
- /*out*/std::string* error_msg);
-
- // Compiles a consecutive subsequence of bootclasspath dex files, whose contents are included in
- // the profiles specified by `profile_filenames`, starting from `bcp_index`.
- bool CompileBootclasspathElements(const std::string& base_location,
- const std::string& base_filename,
- size_t bcp_index,
- const std::vector<std::string>& profile_filenames,
- ArrayRef<const std::string> dependencies,
- /*out*/std::string* error_msg);
-
- bool CheckAndRemoveLastChunkChecksum(/*inout*/std::string_view* oat_checksums,
- /*out*/std::string* error_msg);
-
- template <typename FilenameFn>
- bool LoadOrValidate(FilenameFn&& filename_fn,
- /*inout*/std::string_view* oat_checksums,
- /*out*/std::string* error_msg);
-
- bool LoadOrValidateFromSystem(InstructionSet image_isa,
- /*inout*/std::string_view* oat_checksums,
- /*out*/std::string* error_msg);
-
- ArrayRef<const std::string> image_locations_;
- ArrayRef<const std::string> boot_class_path_;
- ArrayRef<const std::string> boot_class_path_locations_;
- ArrayRef<const int> boot_class_path_fds_;
- ArrayRef<const int> boot_class_path_image_fds_;
- ArrayRef<const int> boot_class_path_vdex_fds_;
- ArrayRef<const int> boot_class_path_oat_fds_;
-
- std::vector<ImageChunk> chunks_;
- uint32_t base_address_ = 0u;
- size_t next_bcp_index_ = 0u;
- size_t total_component_count_ = 0u;
- size_t total_reservation_size_ = 0u;
-};
-
std::string ImageSpace::BootImageLayout::GetPrimaryImageLocation() {
DCHECK(!image_locations_.empty());
std::string location = image_locations_[0];
@@ -1886,7 +1711,7 @@
error_msg->c_str());
return false;
}
- if (!ImageSpace::ValidateOatFile(*oat_file, error_msg, dex_filenames, dex_fds)) {
+ if (!ImageSpace::ValidateOatFile(*oat_file, error_msg, dex_filenames, dex_fds, apex_versions_)) {
return false;
}
return true;
@@ -2151,48 +1976,12 @@
return true;
}
-bool ImageSpace::BootImageLayout::CheckAndRemoveLastChunkChecksum(
- /*inout*/std::string_view* oat_checksums,
- /*out*/std::string* error_msg) {
- DCHECK(oat_checksums != nullptr);
- DCHECK(!chunks_.empty());
- const ImageChunk& chunk = chunks_.back();
- size_t component_count = chunk.component_count;
- size_t checksum = chunk.checksum;
- if (!CheckAndRemoveImageChecksum(component_count, checksum, oat_checksums, error_msg)) {
- DCHECK(!error_msg->empty());
- return false;
- }
- if (oat_checksums->empty()) {
- if (next_bcp_index_ != boot_class_path_.size()) {
- *error_msg = StringPrintf("Checksum too short, missing %zu components.",
- boot_class_path_.size() - next_bcp_index_);
- return false;
- }
- return true;
- }
- if (!StartsWith(*oat_checksums, ":")) {
- *error_msg = StringPrintf("Missing ':' separator at start of %s",
- std::string(*oat_checksums).c_str());
- return false;
- }
- oat_checksums->remove_prefix(1u);
- if (oat_checksums->empty()) {
- *error_msg = "Missing checksums after the ':' separator.";
- return false;
- }
- return true;
-}
-
template <typename FilenameFn>
-bool ImageSpace::BootImageLayout::LoadOrValidate(FilenameFn&& filename_fn,
- /*inout*/std::string_view* oat_checksums,
- /*out*/std::string* error_msg) {
+bool ImageSpace::BootImageLayout::Load(FilenameFn&& filename_fn,
+ bool allow_in_memory_compilation,
+ /*out*/ std::string* error_msg) {
DCHECK(GetChunks().empty());
DCHECK_EQ(GetBaseAddress(), 0u);
- bool validate = (oat_checksums != nullptr);
- static_assert(ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check.");
- DCHECK_IMPLIES(validate, StartsWith(*oat_checksums, "i"));
ArrayRef<const std::string> components = image_locations_;
size_t named_components_count = 0u;
@@ -2223,17 +2012,14 @@
LOG(ERROR) << "Named image component already covered by previous image: " << base_location;
continue;
}
- if (validate && bcp_index > bcp_pos) {
- *error_msg = StringPrintf("End of contiguous boot class path images, remaining checksum: %s",
- std::string(*oat_checksums).c_str());
- return false;
- }
std::string local_error_msg;
- std::string* err_msg = validate ? error_msg : &local_error_msg;
std::string base_filename;
- if (!filename_fn(base_location, &base_filename, err_msg) ||
- !ReadHeader(base_location, base_filename, bcp_index, err_msg)) {
- if (validate) {
+ if (!filename_fn(base_location, &base_filename, &local_error_msg) ||
+ !ReadHeader(base_location, base_filename, bcp_index, &local_error_msg)) {
+ if (!allow_in_memory_compilation) {
+ // The boot image is unusable and we can't continue by generating a boot image in memory.
+ // All we can do is to return.
+ *error_msg = std::move(local_error_msg);
return false;
}
LOG(ERROR) << "Error reading named image component header for " << base_location
@@ -2280,14 +2066,6 @@
continue;
}
}
- if (validate) {
- if (!CheckAndRemoveLastChunkChecksum(oat_checksums, error_msg)) {
- return false;
- }
- if (oat_checksums->empty() || !StartsWith(*oat_checksums, "i")) {
- return true; // Let the caller deal with the dex file checksums if any.
- }
- }
bcp_pos = GetNextBcpIndex();
}
@@ -2320,24 +2098,10 @@
VLOG(image) << "Found image extension for " << ExpandLocation(base_location, bcp_pos);
bcp_pos = GetNextBcpIndex();
found = true;
- if (validate) {
- if (!CheckAndRemoveLastChunkChecksum(oat_checksums, error_msg)) {
- return false;
- }
- if (oat_checksums->empty() || !StartsWith(*oat_checksums, "i")) {
- return true; // Let the caller deal with the dex file checksums if any.
- }
- }
break;
}
}
if (!found) {
- if (validate) {
- *error_msg = StringPrintf("Missing extension for %s, remaining checksum: %s",
- bcp_component.c_str(),
- std::string(*oat_checksums).c_str());
- return false;
- }
++bcp_pos;
}
}
@@ -2346,16 +2110,16 @@
return true;
}
-bool ImageSpace::BootImageLayout::LoadOrValidateFromSystem(InstructionSet image_isa,
- /*inout*/std::string_view* oat_checksums,
- /*out*/std::string* error_msg) {
+bool ImageSpace::BootImageLayout::LoadFromSystem(InstructionSet image_isa,
+ bool allow_in_memory_compilation,
+ /*out*/ std::string* error_msg) {
auto filename_fn = [image_isa](const std::string& location,
/*out*/std::string* filename,
/*out*/std::string* err_msg ATTRIBUTE_UNUSED) {
*filename = GetSystemImageFilename(location.c_str(), image_isa);
return true;
};
- return LoadOrValidate(filename_fn, oat_checksums, error_msg);
+ return Load(filename_fn, allow_in_memory_compilation, error_msg);
}
class ImageSpace::BootImageLoader {
@@ -3111,8 +2875,24 @@
return false;
}
}
+
+ // As an optimization, madvise the oat file into memory if it's being used
+ // for execution with an active runtime. This can significantly improve
+ // ZygoteInit class preload performance.
+ if (executable_) {
+ Runtime* runtime = Runtime::Current();
+ if (runtime != nullptr) {
+ Runtime::MadviseFileForRange(runtime->GetMadviseWillNeedSizeOdex(),
+ oat_file->Size(),
+ oat_file->Begin(),
+ oat_file->End(),
+ oat_file->GetLocation());
+ }
+ }
+
space->oat_file_ = std::move(oat_file);
space->oat_file_non_owned_ = space->oat_file_.get();
+
return true;
}
@@ -3357,7 +3137,7 @@
boot_class_path_image_fds_,
boot_class_path_vdex_fds_,
boot_class_path_oat_fds_);
- if (!layout.LoadFromSystem(image_isa_, error_msg)) {
+ if (!layout.LoadFromSystem(image_isa_, /*allow_in_memory_compilation=*/true, error_msg)) {
return false;
}
@@ -3519,7 +3299,9 @@
<< ",name=\"" << GetName() << "\"]";
}
-bool ImageSpace::ValidateApexVersions(const OatFile& oat_file, std::string* error_msg) {
+bool ImageSpace::ValidateApexVersions(const OatFile& oat_file,
+ const std::string& apex_versions,
+ std::string* error_msg) {
// For a boot image, the key value store only exists in the first OAT file. Skip other OAT files.
if (oat_file.GetOatHeader().GetKeyValueStoreSize() == 0) {
return true;
@@ -3542,27 +3324,33 @@
// For a boot image, it can be generated from a subset of the bootclasspath.
// For an app image, some dex files get compiled with a subset of the bootclasspath.
// For such cases, the OAT APEX versions will be a prefix of the runtime APEX versions.
- if (!android::base::StartsWith(Runtime::Current()->GetApexVersions(), oat_apex_versions)) {
+ if (!android::base::StartsWith(apex_versions, oat_apex_versions)) {
*error_msg = StringPrintf(
"ValidateApexVersions found APEX versions mismatch between oat file '%s' and the runtime "
"(Oat file: '%s', Runtime: '%s')",
oat_file.GetLocation().c_str(),
oat_apex_versions,
- Runtime::Current()->GetApexVersions().c_str());
+ apex_versions.c_str());
return false;
}
return true;
}
bool ImageSpace::ValidateOatFile(const OatFile& oat_file, std::string* error_msg) {
- return ValidateOatFile(oat_file, error_msg, ArrayRef<const std::string>(), ArrayRef<const int>());
+ DCHECK(Runtime::Current() != nullptr);
+ return ValidateOatFile(oat_file,
+ error_msg,
+ ArrayRef<const std::string>(),
+ ArrayRef<const int>(),
+ Runtime::Current()->GetApexVersions());
}
bool ImageSpace::ValidateOatFile(const OatFile& oat_file,
std::string* error_msg,
ArrayRef<const std::string> dex_filenames,
- ArrayRef<const int> dex_fds) {
- if (!ValidateApexVersions(oat_file, error_msg)) {
+ ArrayRef<const int> dex_fds,
+ const std::string& apex_versions) {
+ if (!ValidateApexVersions(oat_file, apex_versions, error_msg)) {
return false;
}
@@ -3695,7 +3483,7 @@
return n;
}
-static size_t CheckAndCountBCPComponents(std::string_view oat_boot_class_path,
+size_t ImageSpace::CheckAndCountBCPComponents(std::string_view oat_boot_class_path,
ArrayRef<const std::string> boot_class_path,
/*out*/std::string* error_msg) {
// Check that the oat BCP is a prefix of current BCP locations and count components.
@@ -3727,110 +3515,6 @@
return component_count;
}
-bool ImageSpace::VerifyBootClassPathChecksums(std::string_view oat_checksums,
- std::string_view oat_boot_class_path,
- ArrayRef<const std::string> image_locations,
- ArrayRef<const std::string> boot_class_path_locations,
- ArrayRef<const std::string> boot_class_path,
- ArrayRef<const int> boot_class_path_fds,
- InstructionSet image_isa,
- /*out*/std::string* error_msg) {
- if (oat_checksums.empty() || oat_boot_class_path.empty()) {
- *error_msg = oat_checksums.empty() ? "Empty checksums." : "Empty boot class path.";
- return false;
- }
-
- DCHECK_EQ(boot_class_path_locations.size(), boot_class_path.size());
- size_t bcp_size =
- CheckAndCountBCPComponents(oat_boot_class_path, boot_class_path_locations, error_msg);
- if (bcp_size == static_cast<size_t>(-1)) {
- DCHECK(!error_msg->empty());
- return false;
- }
-
- size_t bcp_pos = 0u;
- if (StartsWith(oat_checksums, "i")) {
- // Use only the matching part of the BCP for validation. FDs are optional, so only pass the
- // sub-array if provided.
- ArrayRef<const int> bcp_fds = boot_class_path_fds.empty()
- ? ArrayRef<const int>()
- : boot_class_path_fds.SubArray(/*pos=*/ 0u, bcp_size);
- BootImageLayout layout(image_locations,
- boot_class_path.SubArray(/*pos=*/ 0u, bcp_size),
- boot_class_path_locations.SubArray(/*pos=*/ 0u, bcp_size),
- bcp_fds,
- /*boot_class_path_image_fds=*/ ArrayRef<const int>(),
- /*boot_class_path_vdex_fds=*/ ArrayRef<const int>(),
- /*boot_class_path_oat_fds=*/ ArrayRef<const int>());
- std::string primary_image_location = layout.GetPrimaryImageLocation();
- std::string system_filename;
- bool has_system = false;
- if (!FindImageFilename(primary_image_location.c_str(),
- image_isa,
- &system_filename,
- &has_system)) {
- *error_msg = StringPrintf("Unable to find image file for %s and %s",
- android::base::Join(image_locations, kComponentSeparator).c_str(),
- GetInstructionSetString(image_isa));
- return false;
- }
-
- DCHECK(has_system);
- if (!layout.ValidateFromSystem(image_isa, &oat_checksums, error_msg)) {
- return false;
- }
- bcp_pos = layout.GetNextBcpIndex();
- }
-
- for ( ; bcp_pos != bcp_size; ++bcp_pos) {
- static_assert(ImageSpace::kDexFileChecksumPrefix == 'd', "Format prefix check.");
- if (!StartsWith(oat_checksums, "d")) {
- *error_msg = StringPrintf("Missing dex checksums, expected %s to start with 'd'",
- std::string(oat_checksums).c_str());
- return false;
- }
- oat_checksums.remove_prefix(1u);
-
- const std::string& bcp_filename = boot_class_path[bcp_pos];
- std::vector<uint32_t> checksums;
- std::vector<std::string> dex_locations;
- const ArtDexFileLoader dex_file_loader;
- if (!dex_file_loader.GetMultiDexChecksums(bcp_filename.c_str(),
- &checksums,
- &dex_locations,
- error_msg)) {
- return false;
- }
- DCHECK(!checksums.empty());
- for (uint32_t checksum : checksums) {
- std::string dex_file_checksum = StringPrintf("/%08x", checksum);
- if (!StartsWith(oat_checksums, dex_file_checksum)) {
- *error_msg = StringPrintf(
- "Dex checksum mismatch for bootclasspath file %s, expected %s to start with %s",
- bcp_filename.c_str(),
- std::string(oat_checksums).c_str(),
- dex_file_checksum.c_str());
- return false;
- }
- oat_checksums.remove_prefix(dex_file_checksum.size());
- }
- if (bcp_pos + 1u != bcp_size) {
- if (!StartsWith(oat_checksums, ":")) {
- *error_msg = StringPrintf("Missing ':' separator at start of %s",
- std::string(oat_checksums).c_str());
- return false;
- }
- oat_checksums.remove_prefix(1u);
- }
- }
- if (!oat_checksums.empty()) {
- *error_msg = StringPrintf("Checksum too long, unexpected tail %s",
- std::string(oat_checksums).c_str());
- return false;
- }
- return true;
-}
-
bool ImageSpace::VerifyBootClassPathChecksums(
std::string_view oat_checksums,
std::string_view oat_boot_class_path,
diff --git a/runtime/gc/space/image_space.h b/runtime/gc/space/image_space.h
index 8a93f2b..bf9cda2 100644
--- a/runtime/gc/space/image_space.h
+++ b/runtime/gc/space/image_space.h
@@ -17,13 +17,15 @@
#ifndef ART_RUNTIME_GC_SPACE_IMAGE_SPACE_H_
#define ART_RUNTIME_GC_SPACE_IMAGE_SPACE_H_
+#include "android-base/unique_fd.h"
+#include "base/array_ref.h"
#include "gc/accounting/space_bitmap.h"
#include "image.h"
+#include "runtime.h"
#include "space.h"
namespace art {
-template <typename T> class ArrayRef;
class DexFile;
enum class InstructionSet;
class OatFile;
@@ -239,18 +241,6 @@
// Returns the total number of components (jar files) associated with the image spaces.
static size_t GetNumberOfComponents(ArrayRef<gc::space::ImageSpace* const> image_spaces);
- // Returns whether the checksums are valid for the given boot class path,
- // image location and ISA (may differ from the ISA of an initialized Runtime).
- // The boot image and dex files do not need to be loaded in memory.
- static bool VerifyBootClassPathChecksums(std::string_view oat_checksums,
- std::string_view oat_boot_class_path,
- ArrayRef<const std::string> image_locations,
- ArrayRef<const std::string> boot_class_path_locations,
- ArrayRef<const std::string> boot_class_path,
- ArrayRef<const int> boot_class_path_fds,
- InstructionSet image_isa,
- /*out*/std::string* error_msg);
-
// Returns whether the oat checksums and boot class path description are valid
// for the given boot image spaces and boot class path. Used for boot image extensions.
static bool VerifyBootClassPathChecksums(
@@ -267,8 +257,10 @@
const std::string& image_location,
bool boot_image_extension = false);
- // Returns true if the APEX versions in the OAT file match the current APEX versions.
- static bool ValidateApexVersions(const OatFile& oat_file, std::string* error_msg);
+ // Returns true if the APEX versions in the OAT file match the given APEX versions.
+ static bool ValidateApexVersions(const OatFile& oat_file,
+ const std::string& apex_versions,
+ std::string* error_msg);
// Returns true if the dex checksums in the given oat file match the
// checksums of the original dex files on disk. This is intended to be used
@@ -279,17 +271,23 @@
// oat and odex file.
//
// This function is exposed for testing purposes.
+ //
+ // Calling this function requires an active runtime.
static bool ValidateOatFile(const OatFile& oat_file, std::string* error_msg);
// Same as above, but allows to use `dex_filenames` and `dex_fds` to find the dex files instead of
- // using the dex filenames in the header of the oat file. This overload is useful when the actual
- // dex filenames are different from what's in the header (e.g., when we run dex2oat on host), or
- // when the runtime can only access files through FDs (e.g., when we run dex2oat on target in a
- // restricted SELinux domain).
+ // using the dex filenames in the header of the oat file, and also takes `apex_versions` from the
+ // input. This overload is useful when the actual dex filenames are different from what's in the
+ // header (e.g., when we run dex2oat on host), when the runtime can only access files through FDs
+ // (e.g., when we run dex2oat on target in a restricted SELinux domain), or when there is no
+ // active runtime.
+ //
+ // Calling this function does not require an active runtime.
static bool ValidateOatFile(const OatFile& oat_file,
std::string* error_msg,
ArrayRef<const std::string> dex_filenames,
- ArrayRef<const int> dex_fds);
+ ArrayRef<const int> dex_fds,
+ const std::string& apex_versions);
// Return the end of the image which includes non-heap objects such as ArtMethods and ArtFields.
uint8_t* GetImageEnd() const {
@@ -303,6 +301,181 @@
void ReleaseMetadata() REQUIRES_SHARED(Locks::mutator_lock_);
+ static void AppendImageChecksum(uint32_t component_count,
+ uint32_t checksum,
+ /*inout*/ std::string* checksums);
+
+ static size_t CheckAndCountBCPComponents(std::string_view oat_boot_class_path,
+ ArrayRef<const std::string> boot_class_path,
+ /*out*/ std::string* error_msg);
+
+ // Helper class to find the primary boot image and boot image extensions
+ // and determine the boot image layout.
+ class BootImageLayout {
+ public:
+ // Description of a "chunk" of the boot image, i.e. either primary boot image
+ // or a boot image extension, used in conjunction with the boot class path to
+ // load boot image components.
+ struct ImageChunk {
+ std::string base_location;
+ std::string base_filename;
+ std::vector<std::string> profile_files;
+ size_t start_index;
+ uint32_t component_count;
+ uint32_t image_space_count;
+ uint32_t reservation_size;
+ uint32_t checksum;
+ uint32_t boot_image_component_count;
+ uint32_t boot_image_checksum;
+ uint32_t boot_image_size;
+
+ // The following file descriptors hold the memfd files for extensions compiled
+ // in memory and described by the above fields. We want to use them to mmap()
+ // the contents and then close them while treating the ImageChunk description
+ // as immutable (const), so make these fields explicitly mutable.
+ mutable android::base::unique_fd art_fd;
+ mutable android::base::unique_fd vdex_fd;
+ mutable android::base::unique_fd oat_fd;
+ };
+
+ BootImageLayout(ArrayRef<const std::string> image_locations,
+ ArrayRef<const std::string> boot_class_path,
+ ArrayRef<const std::string> boot_class_path_locations,
+ ArrayRef<const int> boot_class_path_fds,
+ ArrayRef<const int> boot_class_path_image_fds,
+ ArrayRef<const int> boot_class_path_vdex_fds,
+ ArrayRef<const int> boot_class_path_oat_fds,
+ const std::string* apex_versions = nullptr)
+ : image_locations_(image_locations),
+ boot_class_path_(boot_class_path),
+ boot_class_path_locations_(boot_class_path_locations),
+ boot_class_path_fds_(boot_class_path_fds),
+ boot_class_path_image_fds_(boot_class_path_image_fds),
+ boot_class_path_vdex_fds_(boot_class_path_vdex_fds),
+ boot_class_path_oat_fds_(boot_class_path_oat_fds),
+ apex_versions_(GetApexVersions(apex_versions)) {}
+
+ std::string GetPrimaryImageLocation();
+
+ bool LoadFromSystem(InstructionSet image_isa,
+ bool allow_in_memory_compilation,
+ /*out*/ std::string* error_msg);
+
+ ArrayRef<const ImageChunk> GetChunks() const { return ArrayRef<const ImageChunk>(chunks_); }
+
+ uint32_t GetBaseAddress() const { return base_address_; }
+
+ size_t GetNextBcpIndex() const { return next_bcp_index_; }
+
+ size_t GetTotalComponentCount() const { return total_component_count_; }
+
+ size_t GetTotalReservationSize() const { return total_reservation_size_; }
+
+ private:
+ struct NamedComponentLocation {
+ std::string base_location;
+ size_t bcp_index;
+ std::vector<std::string> profile_filenames;
+ };
+
+ std::string ExpandLocationImpl(const std::string& location,
+ size_t bcp_index,
+ bool boot_image_extension) {
+ std::vector<std::string> expanded = ExpandMultiImageLocations(
+ ArrayRef<const std::string>(boot_class_path_).SubArray(bcp_index, 1u),
+ location,
+ boot_image_extension);
+ DCHECK_EQ(expanded.size(), 1u);
+ return expanded[0];
+ }
+
+ std::string ExpandLocation(const std::string& location, size_t bcp_index) {
+ if (bcp_index == 0u) {
+ DCHECK_EQ(location,
+ ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/false));
+ return location;
+ } else {
+ return ExpandLocationImpl(location, bcp_index, /*boot_image_extension=*/true);
+ }
+ }
+
+ std::string GetBcpComponentPath(size_t bcp_index) {
+ DCHECK_LE(bcp_index, boot_class_path_.size());
+ size_t bcp_slash_pos = boot_class_path_[bcp_index].rfind('/');
+ DCHECK_NE(bcp_slash_pos, std::string::npos);
+ return boot_class_path_[bcp_index].substr(0u, bcp_slash_pos + 1u);
+ }
+
+ bool VerifyImageLocation(ArrayRef<const std::string> components,
+ /*out*/ size_t* named_components_count,
+ /*out*/ std::string* error_msg);
+
+ bool MatchNamedComponents(
+ ArrayRef<const std::string> named_components,
+ /*out*/ std::vector<NamedComponentLocation>* named_component_locations,
+ /*out*/ std::string* error_msg);
+
+ bool ValidateBootImageChecksum(const char* file_description,
+ const ImageHeader& header,
+ /*out*/ std::string* error_msg);
+
+ bool ValidateHeader(const ImageHeader& header,
+ size_t bcp_index,
+ const char* file_description,
+ /*out*/ std::string* error_msg);
+
+ bool ValidateOatFile(const std::string& base_location,
+ const std::string& base_filename,
+ size_t bcp_index,
+ size_t component_count,
+ /*out*/ std::string* error_msg);
+
+ bool ReadHeader(const std::string& base_location,
+ const std::string& base_filename,
+ size_t bcp_index,
+ /*out*/ std::string* error_msg);
+
+ // Compiles a consecutive subsequence of bootclasspath dex files, whose contents are included in
+ // the profiles specified by `profile_filenames`, starting from `bcp_index`.
+ bool CompileBootclasspathElements(const std::string& base_location,
+ const std::string& base_filename,
+ size_t bcp_index,
+ const std::vector<std::string>& profile_filenames,
+ ArrayRef<const std::string> dependencies,
+ /*out*/ std::string* error_msg);
+
+ template <typename FilenameFn>
+ bool Load(FilenameFn&& filename_fn,
+ bool allow_in_memory_compilation,
+ /*out*/ std::string* error_msg);
+
+ // This function prefers taking APEX versions from the input instead of from the runtime if
+ // possible. If the input is present, `ValidateFromSystem` can work without an active runtime.
+ static const std::string& GetApexVersions(const std::string* apex_versions) {
+ if (apex_versions == nullptr) {
+ DCHECK(Runtime::Current() != nullptr);
+ return Runtime::Current()->GetApexVersions();
+ } else {
+ return *apex_versions;
+ }
+ }
+
+ ArrayRef<const std::string> image_locations_;
+ ArrayRef<const std::string> boot_class_path_;
+ ArrayRef<const std::string> boot_class_path_locations_;
+ ArrayRef<const int> boot_class_path_fds_;
+ ArrayRef<const int> boot_class_path_image_fds_;
+ ArrayRef<const int> boot_class_path_vdex_fds_;
+ ArrayRef<const int> boot_class_path_oat_fds_;
+
+ std::vector<ImageChunk> chunks_;
+ uint32_t base_address_ = 0u;
+ size_t next_bcp_index_ = 0u;
+ size_t total_component_count_ = 0u;
+ size_t total_reservation_size_ = 0u;
+ const std::string& apex_versions_;
+ };
+
protected:
// Tries to initialize an ImageSpace from the given image path, returning null on error.
//
@@ -342,7 +515,6 @@
friend class Space;
private:
- class BootImageLayout;
class BootImageLoader;
template <typename ReferenceVisitor>
class ClassTableVisitor;
diff --git a/runtime/gc/space/image_space_test.cc b/runtime/gc/space/image_space_test.cc
index 3a6d0e1..b3a5917 100644
--- a/runtime/gc/space/image_space_test.cc
+++ b/runtime/gc/space/image_space_test.cc
@@ -321,56 +321,6 @@
EXPECT_FALSE(ImageSpace::ValidateOatFile(*oat, &error_msg));
}
-TEST_F(DexoptTest, Checksums) {
- Runtime* runtime = Runtime::Current();
- ASSERT_TRUE(runtime != nullptr);
- ASSERT_FALSE(runtime->GetHeap()->GetBootImageSpaces().empty());
-
- std::vector<std::string> bcp = runtime->GetBootClassPath();
- std::vector<std::string> bcp_locations = runtime->GetBootClassPathLocations();
- std::vector<const DexFile*> dex_files = runtime->GetClassLinker()->GetBootClassPath();
-
- std::string error_msg;
- auto create_and_verify = [&]() {
- std::string checksums = gc::space::ImageSpace::GetBootClassPathChecksums(
- ArrayRef<gc::space::ImageSpace* const>(runtime->GetHeap()->GetBootImageSpaces()),
- ArrayRef<const DexFile* const>(dex_files));
- return gc::space::ImageSpace::VerifyBootClassPathChecksums(
- checksums,
- android::base::Join(bcp_locations, ':'),
- ArrayRef<const std::string>(runtime->GetImageLocations()),
- ArrayRef<const std::string>(bcp_locations),
- ArrayRef<const std::string>(bcp),
- /*boot_class_path_fds=*/ ArrayRef<const int>(),
- kRuntimeISA,
- &error_msg);
- };
-
- ASSERT_TRUE(create_and_verify()) << error_msg;
-
- std::vector<std::unique_ptr<const DexFile>> opened_dex_files;
- for (const std::string& src : { GetDexSrc1(), GetDexSrc2() }) {
- std::vector<std::unique_ptr<const DexFile>> new_dex_files;
- const ArtDexFileLoader dex_file_loader;
- ASSERT_TRUE(dex_file_loader.Open(src.c_str(),
- src,
- /*verify=*/ true,
- /*verify_checksum=*/ false,
- &error_msg,
- &new_dex_files))
- << error_msg;
-
- bcp.push_back(src);
- bcp_locations.push_back(src);
- for (std::unique_ptr<const DexFile>& df : new_dex_files) {
- dex_files.push_back(df.get());
- opened_dex_files.push_back(std::move(df));
- }
-
- ASSERT_TRUE(create_and_verify()) << error_msg;
- }
-}
-
template <bool kImage, bool kRelocate>
class ImageSpaceLoadingTest : public CommonRuntimeTest {
protected:
diff --git a/runtime/gc/space/malloc_space.h b/runtime/gc/space/malloc_space.h
index 5000656..59ab3f3 100644
--- a/runtime/gc/space/malloc_space.h
+++ b/runtime/gc/space/malloc_space.h
@@ -38,7 +38,7 @@
// A common parent of DlMallocSpace and RosAllocSpace.
class MallocSpace : public ContinuousMemMapAllocSpace {
public:
- typedef void(*WalkCallback)(void *start, void *end, size_t num_bytes, void* callback_arg);
+ using WalkCallback = void (*)(void *start, void *end, size_t num_bytes, void* callback_arg);
SpaceType GetType() const override {
return kSpaceTypeMallocSpace;
diff --git a/runtime/gc/space/region_space.h b/runtime/gc/space/region_space.h
index 1463eb7..27b9e9c 100644
--- a/runtime/gc/space/region_space.h
+++ b/runtime/gc/space/region_space.h
@@ -46,7 +46,7 @@
// A space that consists of equal-sized regions.
class RegionSpace final : public ContinuousMemMapAllocSpace {
public:
- typedef void(*WalkCallback)(void *start, void *end, size_t num_bytes, void* callback_arg);
+ using WalkCallback = void (*)(void *start, void *end, size_t num_bytes, void* callback_arg);
enum EvacMode {
kEvacModeNewlyAllocated,
diff --git a/runtime/gc/system_weak.h b/runtime/gc/system_weak.h
index ef85b39..77b9548 100644
--- a/runtime/gc/system_weak.h
+++ b/runtime/gc/system_weak.h
@@ -48,7 +48,7 @@
void Allow() override
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!allow_disallow_lock_) {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
MutexLock mu(Thread::Current(), allow_disallow_lock_);
allow_new_system_weak_ = true;
new_weak_condition_.Broadcast(Thread::Current());
@@ -57,7 +57,7 @@
void Disallow() override
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!allow_disallow_lock_) {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
MutexLock mu(Thread::Current(), allow_disallow_lock_);
allow_new_system_weak_ = false;
}
@@ -78,8 +78,8 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(allow_disallow_lock_) {
// Wait for GC's sweeping to complete and allow new records
- while (UNLIKELY((!kUseReadBarrier && !allow_new_system_weak_) ||
- (kUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
+ while (UNLIKELY((!gUseReadBarrier && !allow_new_system_weak_) ||
+ (gUseReadBarrier && !self->GetWeakRefAccessEnabled()))) {
// Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
// presence of threads blocking for weak ref access.
self->CheckEmptyCheckpointFromWeakRefAccess(&allow_disallow_lock_);
diff --git a/runtime/gc/system_weak_test.cc b/runtime/gc/system_weak_test.cc
index ca11297..4f552a6 100644
--- a/runtime/gc/system_weak_test.cc
+++ b/runtime/gc/system_weak_test.cc
@@ -111,6 +111,7 @@
CollectorType type = Runtime::Current()->GetHeap()->CurrentCollectorType();
switch (type) {
case CollectorType::kCollectorTypeCMS:
+ case CollectorType::kCollectorTypeCMC:
case CollectorType::kCollectorTypeCC:
case CollectorType::kCollectorTypeSS:
return true;
@@ -124,6 +125,7 @@
CollectorType type = Runtime::Current()->GetHeap()->CurrentCollectorType();
switch (type) {
case CollectorType::kCollectorTypeCMS:
+ case CollectorType::kCollectorTypeCMC:
return true;
default:
@@ -149,7 +151,12 @@
// Expect the holder to have been called.
EXPECT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
EXPECT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
- EXPECT_EQ(1U, cswh.sweep_count_);
+ // Userfaultfd GC uses SweepSystemWeaks also for concurrent updation.
+ // TODO: Explore this can be reverted back to unconditionally compare with 1
+ // once concurrent updation of native roots is full implemented in userfaultfd
+ // GC.
+ size_t expected_sweep_count = gUseUserfaultfd ? 2U : 1U;
+ EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
// Expect the weak to not be cleared.
EXPECT_FALSE(cswh.Get().IsNull());
@@ -170,7 +177,12 @@
// Expect the holder to have been called.
EXPECT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
EXPECT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
- EXPECT_EQ(1U, cswh.sweep_count_);
+ // Userfaultfd GC uses SweepSystemWeaks also for concurrent updation.
+ // TODO: Explore this can be reverted back to unconditionally compare with 1
+ // once concurrent updation of native roots is full implemented in userfaultfd
+ // GC.
+ size_t expected_sweep_count = gUseUserfaultfd ? 2U : 1U;
+ EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
// Expect the weak to be cleared.
EXPECT_TRUE(cswh.Get().IsNull());
@@ -194,7 +206,12 @@
// Expect the holder to have been called.
ASSERT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
ASSERT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
- ASSERT_EQ(1U, cswh.sweep_count_);
+ // Userfaultfd GC uses SweepSystemWeaks also for concurrent updation.
+ // TODO: Explore this can be reverted back to unconditionally compare with 1
+ // once concurrent updation of native roots is full implemented in userfaultfd
+ // GC.
+ size_t expected_sweep_count = gUseUserfaultfd ? 2U : 1U;
+ EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
// Expect the weak to not be cleared.
ASSERT_FALSE(cswh.Get().IsNull());
@@ -209,7 +226,7 @@
// Expectation: no change in the numbers.
EXPECT_EQ(CollectorDoesAllowOrBroadcast() ? 1U : 0U, cswh.allow_count_);
EXPECT_EQ(CollectorDoesDisallow() ? 1U : 0U, cswh.disallow_count_);
- EXPECT_EQ(1U, cswh.sweep_count_);
+ EXPECT_EQ(expected_sweep_count, cswh.sweep_count_);
}
} // namespace gc
diff --git a/runtime/gc/verification-inl.h b/runtime/gc/verification-inl.h
new file mode 100644
index 0000000..1ef96e2
--- /dev/null
+++ b/runtime/gc/verification-inl.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2021 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_GC_VERIFICATION_INL_H_
+#define ART_RUNTIME_GC_VERIFICATION_INL_H_
+
+#include "verification.h"
+
+#include "mirror/class-inl.h"
+
+namespace art {
+namespace gc {
+
+template <ReadBarrierOption kReadBarrierOption>
+bool Verification::IsValidClassUnchecked(mirror::Class* klass) const {
+ mirror::Class* k1 = klass->GetClass<kVerifyNone, kReadBarrierOption>();
+ if (!IsValidHeapObjectAddress(k1)) {
+ return false;
+ }
+ // `k1` should be class class, take the class again to verify.
+ // Note that this check may not be valid for the no image space
+ // since the class class might move around from moving GC.
+ mirror::Class* k2 = k1->GetClass<kVerifyNone, kReadBarrierOption>();
+ if (!IsValidHeapObjectAddress(k2)) {
+ return false;
+ }
+ return k1 == k2;
+}
+
+template <ReadBarrierOption kReadBarrierOption>
+bool Verification::IsValidClass(mirror::Class* klass) const {
+ if (!IsValidHeapObjectAddress(klass)) {
+ return false;
+ }
+ return IsValidClassUnchecked<kReadBarrierOption>(klass);
+}
+
+template <ReadBarrierOption kReadBarrierOption>
+bool Verification::IsValidObject(mirror::Object* obj) const {
+ if (!IsValidHeapObjectAddress(obj)) {
+ return false;
+ }
+ mirror::Class* klass = obj->GetClass<kVerifyNone, kReadBarrierOption>();
+ return IsValidClass(klass);
+}
+
+} // namespace gc
+} // namespace art
+
+#endif // ART_RUNTIME_GC_VERIFICATION_INL_H_
diff --git a/runtime/gc/verification.cc b/runtime/gc/verification.cc
index 9e0b8a2..5790755 100644
--- a/runtime/gc/verification.cc
+++ b/runtime/gc/verification.cc
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "verification.h"
+#include "verification-inl.h"
#include <iomanip>
#include <sstream>
@@ -29,23 +29,16 @@
namespace gc {
std::string Verification::DumpRAMAroundAddress(uintptr_t addr, uintptr_t bytes) const {
- const uintptr_t dump_start = addr - bytes;
- const uintptr_t dump_end = addr + bytes;
+ uintptr_t* dump_start = reinterpret_cast<uintptr_t*>(addr - bytes);
+ uintptr_t* dump_end = reinterpret_cast<uintptr_t*>(addr + bytes);
std::ostringstream oss;
- if (dump_start < dump_end &&
- IsAddressInHeapSpace(reinterpret_cast<const void*>(dump_start)) &&
- IsAddressInHeapSpace(reinterpret_cast<const void*>(dump_end - 1))) {
- oss << " adjacent_ram=";
- for (uintptr_t p = dump_start; p < dump_end; ++p) {
- if (p == addr) {
- // Marker of where the address is.
- oss << "|";
- }
- uint8_t* ptr = reinterpret_cast<uint8_t*>(p);
- oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<uintptr_t>(*ptr);
+ oss << " adjacent_ram=";
+ for (const uintptr_t* p = dump_start; p < dump_end; ++p) {
+ if (p == reinterpret_cast<uintptr_t*>(addr)) {
+ // Marker of where the address is.
+ oss << "|";
}
- } else {
- oss << " <invalid address>";
+ oss << std::hex << std::setfill('0') << std::setw(sizeof(uintptr_t) * 2) << *p << " ";
}
return oss.str();
}
@@ -132,25 +125,6 @@
return IsAligned<kObjectAlignment>(addr) && IsAddressInHeapSpace(addr, out_space);
}
-bool Verification::IsValidClass(const void* addr) const {
- if (!IsValidHeapObjectAddress(addr)) {
- return false;
- }
- mirror::Class* klass = reinterpret_cast<mirror::Class*>(const_cast<void*>(addr));
- mirror::Class* k1 = klass->GetClass<kVerifyNone, kWithoutReadBarrier>();
- if (!IsValidHeapObjectAddress(k1)) {
- return false;
- }
- // `k1` should be class class, take the class again to verify.
- // Note that this check may not be valid for the no image space since the class class might move
- // around from moving GC.
- mirror::Class* k2 = k1->GetClass<kVerifyNone, kWithoutReadBarrier>();
- if (!IsValidHeapObjectAddress(k2)) {
- return false;
- }
- return k1 == k2;
-}
-
using ObjectSet = std::set<mirror::Object*>;
using WorkQueue = std::deque<std::pair<mirror::Object*, std::string>>;
diff --git a/runtime/gc/verification.h b/runtime/gc/verification.h
index 6b456fd..7a5d01a 100644
--- a/runtime/gc/verification.h
+++ b/runtime/gc/verification.h
@@ -19,6 +19,7 @@
#include "obj_ptr.h"
#include "offsets.h"
+#include "read_barrier_option.h"
namespace art {
@@ -50,7 +51,16 @@
bool fatal) const REQUIRES_SHARED(Locks::mutator_lock_);
// Return true if the klass is likely to be a valid mirror::Class.
- bool IsValidClass(const void* klass) const REQUIRES_SHARED(Locks::mutator_lock_);
+ // Returns true if the class is a valid mirror::Class or possibly spuriously.
+ template <ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
+ bool IsValidClassUnchecked(mirror::Class* klass) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // Return true if the klass is likely to be a valid mirror::Class.
+ template <ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
+ bool IsValidClass(mirror::Class* klass) const REQUIRES_SHARED(Locks::mutator_lock_);
+ // Return true if the obj is likely to be a valid obj with valid mirror::Class.
+ template <ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
+ bool IsValidObject(mirror::Object* obj) const REQUIRES_SHARED(Locks::mutator_lock_);
// Does not allow null, checks alignment.
bool IsValidHeapObjectAddress(const void* addr, space::Space** out_space = nullptr) const
diff --git a/runtime/handle.cc b/runtime/handle.cc
index af77e23..e9c9113 100644
--- a/runtime/handle.cc
+++ b/runtime/handle.cc
@@ -42,6 +42,7 @@
namespace art {
+// NOLINTBEGIN(bugprone-macro-parentheses)
#define MAKE_OBJECT_FOR_GDB(ROOT, NAME, MIRROR) \
template <> MIRROR* Handle<MIRROR>::GetFromGdb() { \
return Get(); \
@@ -53,5 +54,6 @@
CLASS_MIRROR_ROOT_LIST(MAKE_OBJECT_FOR_GDB)
#undef MAKE_OBJECT_FOR_GDB
+// NOLINTEND(bugprone-macro-parentheses)
} // namespace art
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 6ec98ff..8735dcf 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -55,6 +55,9 @@
#include "thread_list.h"
namespace art {
+extern "C" NO_RETURN void artDeoptimize(Thread* self);
+extern "C" NO_RETURN void artDeliverPendingExceptionFromCode(Thread* self);
+
namespace instrumentation {
constexpr bool kVerboseInstrumentation = false;
@@ -104,69 +107,6 @@
Instrumentation* const instrumentation_;
};
-InstrumentationStackPopper::InstrumentationStackPopper(Thread* self)
- : self_(self),
- instrumentation_(Runtime::Current()->GetInstrumentation()),
- pop_until_(0u) {}
-
-InstrumentationStackPopper::~InstrumentationStackPopper() {
- std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
- self_->GetInstrumentationStack();
- for (auto i = stack->begin(); i != stack->end() && i->first <= pop_until_;) {
- i = stack->erase(i);
- }
-}
-
-bool InstrumentationStackPopper::PopFramesTo(uintptr_t stack_pointer,
- MutableHandle<mirror::Throwable>& exception) {
- std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
- self_->GetInstrumentationStack();
- DCHECK(!self_->IsExceptionPending());
- if (!instrumentation_->HasMethodUnwindListeners()) {
- pop_until_ = stack_pointer;
- return true;
- }
- if (kVerboseInstrumentation) {
- LOG(INFO) << "Popping frames for exception " << exception->Dump();
- }
- // The instrumentation events expect the exception to be set.
- self_->SetException(exception.Get());
- bool new_exception_thrown = false;
- auto i = stack->upper_bound(pop_until_);
-
- // Now pop all frames until reaching stack_pointer, or a new exception is
- // thrown. Note that `stack_pointer` doesn't need to be a return PC address
- // (in fact the exception handling code passes the start of the frame where
- // the catch handler is).
- for (; i != stack->end() && i->first <= stack_pointer; i++) {
- const InstrumentationStackFrame& frame = i->second;
- ArtMethod* method = frame.method_;
- // Notify listeners of method unwind.
- // TODO: improve the dex_pc information here.
- uint32_t dex_pc = dex::kDexNoIndex;
- if (kVerboseInstrumentation) {
- LOG(INFO) << "Popping for unwind " << method->PrettyMethod();
- }
- if (!method->IsRuntimeMethod() && !frame.interpreter_entry_) {
- instrumentation_->MethodUnwindEvent(self_, frame.this_object_, method, dex_pc);
- new_exception_thrown = self_->GetException() != exception.Get();
- if (new_exception_thrown) {
- pop_until_ = i->first;
- break;
- }
- }
- }
- if (!new_exception_thrown) {
- pop_until_ = stack_pointer;
- }
- exception.Assign(self_->GetException());
- self_->ClearException();
- if (kVerboseInstrumentation && new_exception_thrown) {
- LOG(INFO) << "Did partial pop of frames due to new exception";
- }
- return !new_exception_thrown;
-}
-
Instrumentation::Instrumentation()
: current_force_deopt_id_(0),
instrumentation_stubs_installed_(false),
@@ -182,12 +122,55 @@
have_watched_frame_pop_listeners_(false),
have_branch_listeners_(false),
have_exception_handled_listeners_(false),
- deoptimized_methods_lock_(new ReaderWriterMutex("deoptimized methods lock",
- kGenericBottomLock)),
quick_alloc_entry_points_instrumentation_counter_(0),
alloc_entrypoints_instrumented_(false) {
}
+bool Instrumentation::ProcessMethodUnwindCallbacks(Thread* self,
+ std::queue<ArtMethod*>& methods,
+ MutableHandle<mirror::Throwable>& exception) {
+ DCHECK(!self->IsExceptionPending());
+ if (!HasMethodUnwindListeners()) {
+ return true;
+ }
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << "Popping frames for exception " << exception->Dump();
+ }
+ // The instrumentation events expect the exception to be set.
+ self->SetException(exception.Get());
+ bool new_exception_thrown = false;
+
+ // Process callbacks for all methods that would be unwound until a new exception is thrown.
+ while (!methods.empty()) {
+ ArtMethod* method = methods.front();
+ methods.pop();
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << "Popping for unwind " << method->PrettyMethod();
+ }
+
+ if (method->IsRuntimeMethod()) {
+ continue;
+ }
+
+ // Notify listeners of method unwind.
+ // TODO: improve the dex_pc information here.
+ uint32_t dex_pc = dex::kDexNoIndex;
+ MethodUnwindEvent(self, method, dex_pc);
+ new_exception_thrown = self->GetException() != exception.Get();
+ if (new_exception_thrown) {
+ break;
+ }
+ }
+
+ exception.Assign(self->GetException());
+ self->ClearException();
+ if (kVerboseInstrumentation && new_exception_thrown) {
+ LOG(INFO) << "Did partial pop of frames due to new exception";
+ }
+ return !new_exception_thrown;
+}
+
+
void Instrumentation::InstallStubsForClass(ObjPtr<mirror::Class> klass) {
if (!klass->IsResolved()) {
// We need the class to be resolved to install/uninstall stubs. Otherwise its methods
@@ -206,6 +189,7 @@
return class_linker->IsQuickResolutionStub(code) ||
class_linker->IsQuickToInterpreterBridge(code) ||
class_linker->IsQuickGenericJniStub(code) ||
+ (code == interpreter::GetNterpWithClinitEntryPoint()) ||
(code == GetQuickInstrumentationEntryPoint());
}
@@ -227,6 +211,45 @@
method->GetDeclaringClass()->DescriptorEquals("Ljava/lang/reflect/Proxy;");
}
+// Returns true if we need entry exit stub to call entry hooks. JITed code
+// directly call entry / exit hooks and don't need the stub.
+static bool CodeNeedsEntryExitStub(const void* entry_point, ArtMethod* method)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ // Proxy.init should never have entry/exit stubs.
+ if (IsProxyInit(method)) {
+ return false;
+ }
+
+ // In some tests runtime isn't setup fully and hence the entry points could
+ // be nullptr.
+ if (entry_point == nullptr) {
+ return true;
+ }
+
+ // Code running in the interpreter doesn't need entry/exit stubs.
+ if (Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(entry_point)) {
+ return false;
+ }
+
+ // When jiting code for debuggable runtimes / instrumentation is active we generate the code to
+ // call method entry / exit hooks when required. Hence it is not required to update to
+ // instrumentation entry point for JITed code in debuggable mode.
+ jit::Jit* jit = Runtime::Current()->GetJit();
+ if (jit != nullptr && jit->GetCodeCache()->ContainsPc(entry_point)) {
+ // If JITed code was compiled with instrumentation support we don't need entry / exit stub.
+ OatQuickMethodHeader* header = OatQuickMethodHeader::FromEntryPoint(entry_point);
+ return !CodeInfo::IsDebuggable(header->GetOptimizedCodeInfoPtr());
+ }
+
+ // GenericJni trampoline can handle entry / exit hooks in debuggable runtimes.
+ if (Runtime::Current()->GetClassLinker()->IsQuickGenericJniStub(entry_point) &&
+ Runtime::Current()->IsJavaDebuggable()) {
+ return false;
+ }
+
+ return true;
+}
+
static void UpdateEntryPoints(ArtMethod* method, const void* quick_code)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (kIsDebugBuild) {
@@ -244,6 +267,11 @@
if (IsProxyInit(method)) {
CHECK_NE(quick_code, GetQuickInstrumentationEntryPoint());
}
+ const Instrumentation* instr = Runtime::Current()->GetInstrumentation();
+ if (instr->EntryExitStubsInstalled()) {
+ DCHECK(quick_code == GetQuickInstrumentationEntryPoint() ||
+ !CodeNeedsEntryExitStub(quick_code, method));
+ }
}
// If the method is from a boot image, don't dirty it if the entrypoint
// doesn't change.
@@ -252,64 +280,22 @@
}
}
-bool Instrumentation::CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // Proxy.init should never have entry/exit stubs.
- if (IsProxyInit(method)) {
- return false;
- }
-
- // In some tests runtime isn't setup fully and hence the entry points could
- // be nullptr.
- if (code == nullptr) {
- return true;
- }
-
- // Code running in the interpreter doesn't need entry/exit stubs.
- if (Runtime::Current()->GetClassLinker()->IsQuickToInterpreterBridge(code)) {
- return false;
- }
-
- // When jiting code for debuggable apps we generate the code to call method
- // entry / exit hooks when required. Hence it is not required to update
- // to instrumentation entry point for JITed code in debuggable mode.
- if (!Runtime::Current()->IsJavaDebuggable()) {
- return true;
- }
-
- // Native functions can have JITed entry points but we don't include support
- // for calling entry / exit hooks directly from the JITed code for native
- // functions. So we still have to install entry exit stubs for such cases.
- if (method->IsNative()) {
- return true;
- }
-
- jit::Jit* jit = Runtime::Current()->GetJit();
- if (jit != nullptr && jit->GetCodeCache()->ContainsPc(code)) {
- return false;
- }
- return true;
+bool Instrumentation::NeedsDexPcEvents(ArtMethod* method, Thread* thread) {
+ return (InterpretOnly(method) || thread->IsForceInterpreter()) && HasDexPcListeners();
}
bool Instrumentation::InterpretOnly(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) {
if (method->IsNative()) {
return false;
}
- return InterpretOnly() ||
- IsDeoptimized(method) ||
- Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method);
+ return InterpretOnly() || IsDeoptimized(method);
}
-static bool CanUseAotCode(ArtMethod* method, const void* quick_code)
+static bool CanUseAotCode(const void* quick_code)
REQUIRES_SHARED(Locks::mutator_lock_) {
if (quick_code == nullptr) {
return false;
}
- if (method->IsNative()) {
- // AOT code for native methods can always be used.
- return true;
- }
-
Runtime* runtime = Runtime::Current();
// For simplicity, we never use AOT code for debuggable.
if (runtime->IsJavaDebuggable()) {
@@ -345,7 +331,7 @@
// In debuggable mode, we can only use AOT code for native methods.
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
const void* aot_code = method->GetOatMethodQuickCode(class_linker->GetImagePointerSize());
- if (CanUseAotCode(method, aot_code)) {
+ if (CanUseAotCode(aot_code)) {
return aot_code;
}
@@ -396,7 +382,12 @@
// stub only if we have compiled code or we can execute nterp, and the method needs a class
// initialization check.
if (aot_code != nullptr || method->IsNative() || CanUseNterp(method)) {
- UpdateEntryPoints(method, GetQuickResolutionStub());
+ if (kIsDebugBuild && CanUseNterp(method)) {
+ // Adds some test coverage for the nterp clinit entrypoint.
+ UpdateEntryPoints(method, interpreter::GetNterpWithClinitEntryPoint());
+ } else {
+ UpdateEntryPoints(method, GetQuickResolutionStub());
+ }
} else {
UpdateEntryPoints(method, GetQuickToInterpreterBridge());
}
@@ -404,7 +395,7 @@
}
// Use the provided AOT code if possible.
- if (CanUseAotCode(method, aot_code)) {
+ if (CanUseAotCode(aot_code)) {
UpdateEntryPoints(method, aot_code);
return;
}
@@ -482,17 +473,26 @@
instrumentation_exit_pc_(instrumentation_exit_pc),
reached_existing_instrumentation_frames_(false),
force_deopt_id_(force_deopt_id),
- deopt_all_frames_(deopt_all_frames) {}
+ deopt_all_frames_(deopt_all_frames),
+ runtime_methods_need_deopt_check_(false) {}
bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
ArtMethod* m = GetMethod();
- if (m == nullptr) {
+ if (m == nullptr || m->IsRuntimeMethod()) {
if (kVerboseInstrumentation) {
- LOG(INFO) << " Skipping upcall. Frame " << GetFrameId();
+ LOG(INFO) << " Skipping upcall / runtime method. Frame " << GetFrameId();
}
- return true; // Ignore upcalls.
+ return true; // Ignore upcalls and runtime methods.
}
if (GetCurrentQuickFrame() == nullptr) {
+ // Since we are updating the instrumentation related information we have to recalculate
+ // NeedsDexPcEvents. For example, when a new method or thread is deoptimized / interpreter
+ // stubs are installed the NeedsDexPcEvents could change for the shadow frames on the stack.
+ // If we don't update it here we would miss reporting dex pc events which is incorrect.
+ ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+ DCHECK(shadow_frame != nullptr);
+ shadow_frame->SetNotifyDexPcMoveEvents(
+ Runtime::Current()->GetInstrumentation()->NeedsDexPcEvents(GetMethod(), GetThread()));
if (kVerboseInstrumentation) {
LOG(INFO) << "Pushing shadow frame method " << m->PrettyMethod();
}
@@ -507,11 +507,6 @@
auto it = instrumentation_stack_->find(GetReturnPcAddr());
CHECK(it != instrumentation_stack_->end());
const InstrumentationStackFrame& frame = it->second;
- if (m->IsRuntimeMethod()) {
- if (frame.interpreter_entry_) {
- return true;
- }
- }
// We've reached a frame which has already been installed with instrumentation exit stub.
// We should have already installed instrumentation or be interpreter on previous frames.
@@ -529,17 +524,24 @@
LOG(INFO) << "Ignoring already instrumented " << frame.Dump();
}
} else {
+ if (m->IsNative() && Runtime::Current()->IsJavaDebuggable()) {
+ // Native methods in debuggable runtimes don't use instrumentation stubs.
+ return true;
+ }
+
// If it is a JITed frame then just set the deopt bit if required
// otherwise continue
const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
if (method_header != nullptr && method_header->HasShouldDeoptimizeFlag()) {
if (deopt_all_frames_) {
+ runtime_methods_need_deopt_check_ = true;
SetShouldDeoptimizeFlag(DeoptimizeFlagValue::kDebug);
}
return true;
}
CHECK_NE(return_pc, 0U);
- if (UNLIKELY(reached_existing_instrumentation_frames_ && !m->IsRuntimeMethod())) {
+ DCHECK(!m->IsRuntimeMethod());
+ if (UNLIKELY(reached_existing_instrumentation_frames_)) {
// We already saw an existing instrumentation frame so this should be a runtime-method
// inserted by the interpreter or runtime.
std::string thread_name;
@@ -550,21 +552,9 @@
<< " return_pc is " << std::hex << return_pc;
UNREACHABLE();
}
- if (m->IsRuntimeMethod()) {
- size_t frame_size = GetCurrentQuickFrameInfo().FrameSizeInBytes();
- ArtMethod** caller_frame = reinterpret_cast<ArtMethod**>(
- reinterpret_cast<uint8_t*>(GetCurrentQuickFrame()) + frame_size);
- if (*caller_frame != nullptr && (*caller_frame)->IsNative()) {
- // Do not install instrumentation exit on return to JNI stubs.
- return true;
- }
- }
+
InstrumentationStackFrame instrumentation_frame(
- m->IsRuntimeMethod() ? nullptr : GetThisObject().Ptr(),
- m,
- return_pc,
- false,
- force_deopt_id_);
+ GetThisObject().Ptr(), m, return_pc, false, force_deopt_id_);
if (kVerboseInstrumentation) {
LOG(INFO) << "Pushing frame " << instrumentation_frame.Dump();
}
@@ -584,6 +574,7 @@
bool reached_existing_instrumentation_frames_;
uint64_t force_deopt_id_;
bool deopt_all_frames_;
+ bool runtime_methods_need_deopt_check_;
};
if (kVerboseInstrumentation) {
std::string thread_name;
@@ -601,6 +592,10 @@
deopt_all_frames);
visitor.WalkStack(true);
+ if (visitor.runtime_methods_need_deopt_check_) {
+ thread->SetDeoptCheckRequired(true);
+ }
+
if (instrumentation->ShouldNotifyMethodEnterExitEvents()) {
// Create method enter events for all methods currently on the thread's stack. We only do this
// if we haven't already processed the method enter events.
@@ -611,6 +606,34 @@
thread->VerifyStack();
}
+void UpdateNeedsDexPcEventsOnStack(Thread* thread) REQUIRES(Locks::mutator_lock_) {
+ Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
+
+ struct InstallStackVisitor final : public StackVisitor {
+ InstallStackVisitor(Thread* thread_in, Context* context)
+ : StackVisitor(thread_in, context, kInstrumentationStackWalk) {}
+
+ bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
+ ShadowFrame* shadow_frame = GetCurrentShadowFrame();
+ if (shadow_frame != nullptr) {
+ shadow_frame->SetNotifyDexPcMoveEvents(
+ Runtime::Current()->GetInstrumentation()->NeedsDexPcEvents(GetMethod(), GetThread()));
+ }
+ return true;
+ }
+ };
+
+ if (kVerboseInstrumentation) {
+ std::string thread_name;
+ thread->GetThreadName(thread_name);
+ LOG(INFO) << "Updating DexPcMoveEvents on shadow frames on stack " << thread_name;
+ }
+
+ std::unique_ptr<Context> context(Context::Create());
+ InstallStackVisitor visitor(thread, context.get());
+ visitor.WalkStack(true);
+}
+
void Instrumentation::InstrumentThreadStack(Thread* thread, bool force_deopt) {
instrumentation_stubs_installed_ = true;
InstrumentationInstallStack(thread, this, force_deopt);
@@ -622,14 +645,16 @@
Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
struct RestoreStackVisitor final : public StackVisitor {
- RestoreStackVisitor(Thread* thread_in, uintptr_t instrumentation_exit_pc,
+ RestoreStackVisitor(Thread* thread_in,
+ uintptr_t instrumentation_exit_pc,
Instrumentation* instrumentation)
: StackVisitor(thread_in, nullptr, kInstrumentationStackWalk),
thread_(thread_in),
instrumentation_exit_pc_(instrumentation_exit_pc),
instrumentation_(instrumentation),
instrumentation_stack_(thread_in->GetInstrumentationStack()),
- frames_removed_(0) {}
+ frames_removed_(0),
+ runtime_methods_need_deopt_check_(false) {}
bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
if (instrumentation_stack_->size() == 0) {
@@ -647,7 +672,13 @@
if (kVerboseInstrumentation) {
LOG(INFO) << " Skipping upcall. Frame " << GetFrameId();
}
- return true; // Ignore upcalls.
+ return true; // Ignore upcalls and runtime methods.
+ }
+ const OatQuickMethodHeader* method_header = GetCurrentOatQuickMethodHeader();
+ if (method_header != nullptr && method_header->HasShouldDeoptimizeFlag()) {
+ if (IsShouldDeoptimizeFlagForDebugSet()) {
+ runtime_methods_need_deopt_check_ = true;
+ }
}
auto it = instrumentation_stack_->find(GetReturnPcAddr());
if (it != instrumentation_stack_->end()) {
@@ -684,6 +715,7 @@
Instrumentation* const instrumentation_;
std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* const instrumentation_stack_;
size_t frames_removed_;
+ bool runtime_methods_need_deopt_check_;
};
if (kVerboseInstrumentation) {
std::string thread_name;
@@ -698,6 +730,10 @@
reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc());
RestoreStackVisitor visitor(thread, instrumentation_exit_pc, instrumentation);
visitor.WalkStack(true);
+ DCHECK_IMPLIES(visitor.runtime_methods_need_deopt_check_, thread->IsDeoptCheckRequired());
+ if (!visitor.runtime_methods_need_deopt_check_) {
+ thread->SetDeoptCheckRequired(false);
+ }
CHECK_EQ(visitor.frames_removed_, stack->size());
stack->clear();
}
@@ -791,6 +827,12 @@
exception_handled_listeners_,
listener,
&have_exception_handled_listeners_);
+ if (HasEvent(kDexPcMoved, events)) {
+ MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+ for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
+ UpdateNeedsDexPcEventsOnStack(thread);
+ }
+ }
}
static void PotentiallyRemoveListenerFrom(Instrumentation::InstrumentationEvent event,
@@ -872,6 +914,12 @@
exception_handled_listeners_,
listener,
&have_exception_handled_listeners_);
+ if (HasEvent(kDexPcMoved, events)) {
+ MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+ for (Thread* thread : Runtime::Current()->GetThreadList()->GetList()) {
+ UpdateNeedsDexPcEventsOnStack(thread);
+ }
+ }
}
Instrumentation::InstrumentationLevel Instrumentation::GetCurrentInstrumentationLevel() const {
@@ -1046,6 +1094,8 @@
return "obsolete";
} else if (code == interpreter::GetNterpEntryPoint()) {
return "nterp";
+ } else if (code == interpreter::GetNterpWithClinitEntryPoint()) {
+ return "nterp with clinit";
} else if (class_linker->IsQuickGenericJniStub(code)) {
return "generic jni";
} else if (Runtime::Current()->GetOatFileManager().ContainsPc(code)) {
@@ -1119,14 +1169,6 @@
return deoptimized_methods_.find(method) != deoptimized_methods_.end();
}
-ArtMethod* Instrumentation::BeginDeoptimizedMethod() {
- if (deoptimized_methods_.empty()) {
- // Empty.
- return nullptr;
- }
- return *deoptimized_methods_.begin();
-}
-
bool Instrumentation::RemoveDeoptimizedMethod(ArtMethod* method) {
auto it = deoptimized_methods_.find(method);
if (it == deoptimized_methods_.end()) {
@@ -1136,10 +1178,6 @@
return true;
}
-bool Instrumentation::IsDeoptimizedMethodsEmptyLocked() const {
- return deoptimized_methods_.empty();
-}
-
void Instrumentation::Deoptimize(ArtMethod* method) {
CHECK(!method->IsNative());
CHECK(!method->IsProxyMethod());
@@ -1147,7 +1185,7 @@
Thread* self = Thread::Current();
{
- WriterMutexLock mu(self, *GetDeoptimizedMethodsLock());
+ Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
bool has_not_been_deoptimized = AddDeoptimizedMethod(method);
CHECK(has_not_been_deoptimized) << "Method " << ArtMethod::PrettyMethod(method)
<< " is already deoptimized";
@@ -1173,9 +1211,8 @@
CHECK(!method->IsProxyMethod());
CHECK(method->IsInvokable());
- Thread* self = Thread::Current();
{
- WriterMutexLock mu(self, *GetDeoptimizedMethodsLock());
+ Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
bool found_and_erased = RemoveDeoptimizedMethod(method);
CHECK(found_and_erased) << "Method " << ArtMethod::PrettyMethod(method)
<< " is not deoptimized";
@@ -1192,7 +1229,11 @@
UpdateEntryPoints(method, GetQuickToInterpreterBridge());
} else if (NeedsClinitCheckBeforeCall(method) &&
!method->GetDeclaringClass()->IsVisiblyInitialized()) {
- UpdateEntryPoints(method, GetQuickResolutionStub());
+ if (EntryExitStubsInstalled()) {
+ UpdateEntryPoints(method, GetQuickInstrumentationEntryPoint());
+ } else {
+ UpdateEntryPoints(method, GetQuickResolutionStub());
+ }
} else {
UpdateEntryPoints(method, GetMaybeInstrumentedCodeForInvoke(method));
}
@@ -1204,29 +1245,26 @@
}
bool Instrumentation::IsDeoptimizedMethodsEmpty() const {
- ReaderMutexLock mu(Thread::Current(), *GetDeoptimizedMethodsLock());
return deoptimized_methods_.empty();
}
bool Instrumentation::IsDeoptimized(ArtMethod* method) {
DCHECK(method != nullptr);
- ReaderMutexLock mu(Thread::Current(), *GetDeoptimizedMethodsLock());
return IsDeoptimizedMethod(method);
}
-
void Instrumentation::DisableDeoptimization(const char* key) {
// Remove any instrumentation support added for deoptimization.
ConfigureStubs(key, InstrumentationLevel::kInstrumentNothing);
+ Locks::mutator_lock_->AssertExclusiveHeld(Thread::Current());
// Undeoptimized selected methods.
while (true) {
ArtMethod* method;
{
- ReaderMutexLock mu(Thread::Current(), *GetDeoptimizedMethodsLock());
- if (IsDeoptimizedMethodsEmptyLocked()) {
+ if (deoptimized_methods_.empty()) {
break;
}
- method = BeginDeoptimizedMethod();
+ method = *deoptimized_methods_.begin();
CHECK(method != nullptr);
}
Undeoptimize(method);
@@ -1270,10 +1308,14 @@
DCHECK(!method->IsProxyMethod()) << method->PrettyMethod();
ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
const void* code = method->GetEntryPointFromQuickCompiledCodePtrSize(kRuntimePointerSize);
- // If we don't have the instrumentation, the resolution stub, or the
- // interpreter as entrypoint, just return the current entrypoint, assuming
- // it's the most optimized.
+ // If we don't have the instrumentation, the resolution stub, the
+ // interpreter, or the nterp with clinit as entrypoint, just return the current entrypoint,
+ // assuming it's the most optimized.
+ // We don't want to return the nterp with clinit entrypoint as it calls the
+ // resolution stub, and the resolution stub will call `GetCodeForInvoke` to know the actual
+ // code to invoke.
if (code != GetQuickInstrumentationEntryPoint() &&
+ code != interpreter::GetNterpWithClinitEntryPoint() &&
!class_linker->IsQuickResolutionStub(code) &&
!class_linker->IsQuickToInterpreterBridge(code)) {
return code;
@@ -1345,16 +1387,12 @@
}
void Instrumentation::MethodUnwindEvent(Thread* thread,
- ObjPtr<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc) const {
if (HasMethodUnwindListeners()) {
- Thread* self = Thread::Current();
- StackHandleScope<1> hs(self);
- Handle<mirror::Object> thiz(hs.NewHandle(this_object));
for (InstrumentationListener* listener : method_unwind_listeners_) {
if (listener != nullptr) {
- listener->MethodUnwind(thread, thiz, method, dex_pc);
+ listener->MethodUnwind(thread, method, dex_pc);
}
}
}
@@ -1489,7 +1527,7 @@
if (!interpreter_entry) {
MethodEnterEvent(self, method);
if (self->IsExceptionPending()) {
- MethodUnwindEvent(self, h_this.Get(), method, 0);
+ MethodUnwindEvent(self, method, 0);
return;
}
}
@@ -1518,83 +1556,18 @@
return DeoptimizationMethodType::kDefault;
}
-// Try to get the shorty of a runtime method if it's an invocation stub.
-static char GetRuntimeMethodShorty(Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_) {
- char shorty = 'V';
- StackVisitor::WalkStack(
- [&shorty](const art::StackVisitor* stack_visitor) REQUIRES_SHARED(Locks::mutator_lock_) {
- ArtMethod* m = stack_visitor->GetMethod();
- if (m == nullptr || m->IsRuntimeMethod()) {
- return true;
- }
- // The first Java method.
- if (m->IsNative()) {
- // Use JNI method's shorty for the jni stub.
- shorty = m->GetShorty()[0];
- } else if (m->IsProxyMethod()) {
- // Proxy method just invokes its proxied method via
- // art_quick_proxy_invoke_handler.
- shorty = m->GetInterfaceMethodIfProxy(kRuntimePointerSize)->GetShorty()[0];
- } else {
- const Instruction& instr = m->DexInstructions().InstructionAt(stack_visitor->GetDexPc());
- if (instr.IsInvoke()) {
- uint16_t method_index = static_cast<uint16_t>(instr.VRegB());
- const DexFile* dex_file = m->GetDexFile();
- if (interpreter::IsStringInit(dex_file, method_index)) {
- // Invoking string init constructor is turned into invoking
- // StringFactory.newStringFromChars() which returns a string.
- shorty = 'L';
- } else {
- shorty = dex_file->GetMethodShorty(method_index)[0];
- }
-
- } else {
- // It could be that a non-invoke opcode invokes a stub, which in turn
- // invokes Java code. In such cases, we should never expect a return
- // value from the stub.
- }
- }
- // Stop stack walking since we've seen a Java frame.
- return false;
- },
- thread,
- /* context= */ nullptr,
- art::StackVisitor::StackWalkKind::kIncludeInlinedFrames);
- return shorty;
-}
-
-JValue Instrumentation::GetReturnValue(
- Thread* self, ArtMethod* method, bool* is_ref, uint64_t* gpr_result, uint64_t* fpr_result) {
+JValue Instrumentation::GetReturnValue(ArtMethod* method,
+ bool* is_ref,
+ uint64_t* gpr_result,
+ uint64_t* fpr_result) {
uint32_t length;
const PointerSize pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
- char return_shorty;
// Runtime method does not call into MethodExitEvent() so there should not be
// suspension point below.
ScopedAssertNoThreadSuspension ants(__FUNCTION__, method->IsRuntimeMethod());
- if (method->IsRuntimeMethod()) {
- Runtime* runtime = Runtime::Current();
- if (method != runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForClinit) &&
- method != runtime->GetCalleeSaveMethod(CalleeSaveType::kSaveEverythingForSuspendCheck)) {
- // If the caller is at an invocation point and the runtime method is not
- // for clinit, we need to pass return results to the caller.
- // We need the correct shorty to decide whether we need to pass the return
- // result for deoptimization below.
- return_shorty = GetRuntimeMethodShorty(self);
- } else {
- // Some runtime methods such as allocations, unresolved field getters, etc.
- // have return value. We don't need to set return_value since MethodExitEvent()
- // below isn't called for runtime methods. Deoptimization doesn't need the
- // value either since the dex instruction will be re-executed by the
- // interpreter, except these two cases:
- // (1) For an invoke, which is handled above to get the correct shorty.
- // (2) For MONITOR_ENTER/EXIT, which cannot be re-executed since it's not
- // idempotent. However there is no return value for it anyway.
- return_shorty = 'V';
- }
- } else {
- return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
- }
+ DCHECK(!method->IsRuntimeMethod());
+ char return_shorty = method->GetInterfaceMethodIfProxy(pointer_size)->GetShorty(&length)[0];
*is_ref = return_shorty == '[' || return_shorty == 'L';
JValue return_value;
@@ -1608,27 +1581,135 @@
return return_value;
}
-bool Instrumentation::ShouldDeoptimizeMethod(Thread* self, const NthCallerVisitor& visitor) {
- bool should_deoptimize_frame = false;
- const OatQuickMethodHeader* header = visitor.GetCurrentOatQuickMethodHeader();
- if (header != nullptr && header->HasShouldDeoptimizeFlag()) {
- uint8_t should_deopt_flag = visitor.GetShouldDeoptimizeFlag();
- // DeoptimizeFlag could be set for debugging or for CHA invalidations.
- // Deoptimize here only if it was requested for debugging. CHA
- // invalidations are handled in the JITed code.
- if ((should_deopt_flag & static_cast<uint8_t>(DeoptimizeFlagValue::kDebug)) != 0) {
- should_deoptimize_frame = true;
- }
+bool Instrumentation::PushDeoptContextIfNeeded(Thread* self,
+ DeoptimizationMethodType deopt_type,
+ bool is_ref,
+ const JValue& return_value)
+ REQUIRES_SHARED(Locks::mutator_lock_) {
+ if (self->IsExceptionPending()) {
+ return false;
}
- return (visitor.caller != nullptr) &&
- (InterpreterStubsInstalled() || IsDeoptimized(visitor.caller) ||
+
+ ArtMethod** sp = self->GetManagedStack()->GetTopQuickFrame();
+ DCHECK(sp != nullptr && (*sp)->IsRuntimeMethod());
+ if (!ShouldDeoptimizeCaller(self, sp)) {
+ return false;
+ }
+
+ // TODO(mythria): The current deopt behaviour is we just re-execute the
+ // alloc instruction so we don't need the return value. For instrumentation
+ // related deopts, we actually don't need to and can use the result we got
+ // here. Since this is a debug only feature it is not very important but
+ // consider reusing the result in future.
+ self->PushDeoptimizationContext(
+ return_value, is_ref, nullptr, /* from_code= */ false, deopt_type);
+ self->SetException(Thread::GetDeoptimizationException());
+ return true;
+}
+
+void Instrumentation::DeoptimizeIfNeeded(Thread* self,
+ ArtMethod** sp,
+ DeoptimizationMethodType type,
+ JValue return_value,
+ bool is_reference) {
+ if (self->IsAsyncExceptionPending() || ShouldDeoptimizeCaller(self, sp)) {
+ self->PushDeoptimizationContext(return_value,
+ is_reference,
+ nullptr,
+ /* from_code= */ false,
+ type);
+ artDeoptimize(self);
+ }
+}
+
+bool Instrumentation::NeedsSlowInterpreterForMethod(Thread* self, ArtMethod* method) {
+ return (method != nullptr) &&
+ (InterpreterStubsInstalled() ||
+ IsDeoptimized(method) ||
self->IsForceInterpreter() ||
// NB Since structurally obsolete compiled methods might have the offsets of
// methods/fields compiled in we need to go back to interpreter whenever we hit
// them.
- visitor.caller->GetDeclaringClass()->IsObsoleteObject() ||
- Dbg::IsForcedInterpreterNeededForUpcall(self, visitor.caller) ||
- should_deoptimize_frame);
+ method->GetDeclaringClass()->IsObsoleteObject() ||
+ Dbg::IsForcedInterpreterNeededForUpcall(self, method));
+}
+
+bool Instrumentation::ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp) {
+ // When exit stubs aren't installed we don't need to check for any instrumentation related
+ // deoptimizations.
+ // TODO(mythria): Once we remove instrumentation stubs rename AreExitStubsInstalled. This is
+ // used to check if any instrumentation related work needs to be done. For ex: calling method
+ // entry / exit hooks, checking for instrumentation related deopts in suspend points
+ if (!AreExitStubsInstalled()) {
+ return false;
+ }
+
+ ArtMethod* runtime_method = *sp;
+ DCHECK(runtime_method->IsRuntimeMethod());
+ QuickMethodFrameInfo frame_info = Runtime::Current()->GetRuntimeMethodFrameInfo(runtime_method);
+
+ uintptr_t caller_sp = reinterpret_cast<uintptr_t>(sp) + frame_info.FrameSizeInBytes();
+ ArtMethod* caller = *(reinterpret_cast<ArtMethod**>(caller_sp));
+ uintptr_t caller_pc_addr = reinterpret_cast<uintptr_t>(sp) + frame_info.GetReturnPcOffset();
+ uintptr_t caller_pc = *reinterpret_cast<uintptr_t*>(caller_pc_addr);
+
+ return ShouldDeoptimizeCaller(self, caller, caller_pc, caller_sp);
+}
+
+bool Instrumentation::ShouldDeoptimizeCaller(Thread* self, const NthCallerVisitor& visitor) {
+ uintptr_t caller_sp = reinterpret_cast<uintptr_t>(visitor.GetCurrentQuickFrame());
+ // When the caller isn't executing quick code there is no need to deoptimize.
+ if (visitor.GetCurrentOatQuickMethodHeader() == nullptr) {
+ return false;
+ }
+ return ShouldDeoptimizeCaller(self, visitor.GetOuterMethod(), visitor.caller_pc, caller_sp);
+}
+
+bool Instrumentation::ShouldDeoptimizeCaller(Thread* self,
+ ArtMethod* caller,
+ uintptr_t caller_pc,
+ uintptr_t caller_sp) {
+ if (caller == nullptr ||
+ caller->IsNative() ||
+ caller_pc == reinterpret_cast<uintptr_t>(GetQuickInstrumentationExitPc())) {
+ // If caller_pc is QuickInstrumentationExit then deoptimization will be handled by the
+ // instrumentation exit trampoline so we don't need to handle deoptimizations here.
+ // We need to check for a deoptimization here because when a redefinition happens it is
+ // not safe to use any compiled code because the field offsets might change. For native
+ // methods, we don't embed any field offsets so no need to check for a deoptimization.
+ // If the caller is null we don't need to do anything. This can happen when the caller
+ // is being interpreted by the switch interpreter (when called from
+ // artQuickToInterpreterBridge) / during shutdown / early startup.
+ return false;
+ }
+
+ bool needs_deopt = NeedsSlowInterpreterForMethod(self, caller);
+
+ // Non java debuggable apps don't support redefinition and hence it isn't required to check if
+ // frame needs to be deoptimized. We also want to avoid getting method header when we need a
+ // deopt anyway.
+ if (Runtime::Current()->IsJavaDebuggable() && !needs_deopt) {
+ const OatQuickMethodHeader* header = caller->GetOatQuickMethodHeader(caller_pc);
+ if (header != nullptr && header->HasShouldDeoptimizeFlag()) {
+ DCHECK(header->IsOptimized());
+ uint8_t* should_deopt_flag_addr =
+ reinterpret_cast<uint8_t*>(caller_sp) + header->GetShouldDeoptimizeFlagOffset();
+ if ((*should_deopt_flag_addr & static_cast<uint8_t>(DeoptimizeFlagValue::kDebug)) != 0) {
+ needs_deopt = true;
+ }
+ }
+ }
+
+ if (needs_deopt) {
+ if (!Runtime::Current()->IsAsyncDeoptimizeable(caller, caller_pc)) {
+ LOG(WARNING) << "Got a deoptimization request on un-deoptimizable method "
+ << caller->PrettyMethod();
+ return false;
+ }
+ return true;
+ }
+
+ return false;
}
TwoWordReturn Instrumentation::PopInstrumentationStackFrame(Thread* self,
@@ -1653,19 +1734,19 @@
self->VerifyStack();
ArtMethod* method = instrumentation_frame.method_;
+ DCHECK(!method->IsRuntimeMethod());
bool is_ref;
- JValue return_value = GetReturnValue(self, method, &is_ref, gpr_result, fpr_result);
+ JValue return_value = GetReturnValue(method, &is_ref, gpr_result, fpr_result);
StackHandleScope<1> hs(self);
MutableHandle<mirror::Object> res(hs.NewHandle<mirror::Object>(nullptr));
if (is_ref) {
// Take a handle to the return value so we won't lose it if we suspend.
- // FIXME: The `is_ref` is often guessed wrong, so even object aligment
- // assertion would fail for some tests. See b/204766614 .
- // DCHECK_ALIGNED(return_value.GetL(), kObjectAlignment);
+ DCHECK_ALIGNED(return_value.GetL(), kObjectAlignment);
res.Assign(return_value.GetL());
}
- if (!method->IsRuntimeMethod() && !instrumentation_frame.interpreter_entry_) {
+ if (!instrumentation_frame.interpreter_entry_) {
+ DCHECK(!method->IsRuntimeMethod());
// Note that sending the event may change the contents of *return_pc_addr.
MethodExitEvent(self, instrumentation_frame.method_, OptionalFrame{}, return_value);
}
@@ -1677,57 +1758,61 @@
// Check if we forced all threads to deoptimize in the time between this frame being created and
// now.
bool should_deoptimize_frame = instrumentation_frame.force_deopt_id_ != current_force_deopt_id_;
- bool deoptimize = ShouldDeoptimizeMethod(self, visitor) || should_deoptimize_frame;
+ bool deoptimize = ShouldDeoptimizeCaller(self, visitor) || should_deoptimize_frame;
if (is_ref) {
// Restore the return value if it's a reference since it might have moved.
*reinterpret_cast<mirror::Object**>(gpr_result) = res.Get();
}
- if (deoptimize && Runtime::Current()->IsAsyncDeoptimizeable(*return_pc_addr)) {
- if (kVerboseInstrumentation) {
- LOG(INFO) << "Deoptimizing "
- << visitor.caller->PrettyMethod()
- << " by returning from "
- << method->PrettyMethod()
- << " with result "
- << std::hex << return_value.GetJ() << std::dec
- << " in "
- << *self;
+
+ if (deoptimize) {
+ // NthCallerVisitor also takes inlined frames into consideration, so visitor.caller points to
+ // the inlined function. We need the actual method corresponding to the return_pc_addr to check
+ // if the method is deoptimizeable. So fetch the outer method.
+ if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.GetOuterMethod(), *return_pc_addr)) {
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << "Deoptimizing "
+ << visitor.caller->PrettyMethod()
+ << " by returning from "
+ << method->PrettyMethod()
+ << " with result "
+ << std::hex << return_value.GetJ() << std::dec
+ << " in "
+ << *self;
+ }
+ DeoptimizationMethodType deopt_method_type = GetDeoptimizationMethodType(method);
+ self->PushDeoptimizationContext(return_value,
+ is_ref,
+ /* exception= */ nullptr,
+ /* from_code= */ false,
+ deopt_method_type);
+ return GetTwoWordSuccessValue(
+ *return_pc_addr, reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint()));
+ } else {
+ VLOG(deopt) << "Got a deoptimization request on un-deoptimizable "
+ << visitor.caller->PrettyMethod() << " at PC "
+ << reinterpret_cast<void*>(*return_pc_addr);
}
- DeoptimizationMethodType deopt_method_type = GetDeoptimizationMethodType(method);
- self->PushDeoptimizationContext(return_value,
- is_ref,
- /* exception= */ nullptr,
- /* from_code= */ false,
- deopt_method_type);
- return GetTwoWordSuccessValue(*return_pc_addr,
- reinterpret_cast<uintptr_t>(GetQuickDeoptimizationEntryPoint()));
- } else {
- if (deoptimize && !Runtime::Current()->IsAsyncDeoptimizeable(*return_pc_addr)) {
- VLOG(deopt) << "Got a deoptimization request on un-deoptimizable " << method->PrettyMethod()
- << " at PC " << reinterpret_cast<void*>(*return_pc_addr);
- }
- if (kVerboseInstrumentation) {
- LOG(INFO) << "Returning from " << method->PrettyMethod()
- << " to PC " << reinterpret_cast<void*>(*return_pc_addr);
- }
- return GetTwoWordSuccessValue(0, *return_pc_addr);
}
+
+ if (kVerboseInstrumentation) {
+ LOG(INFO) << "Returning from " << method->PrettyMethod() << " to PC "
+ << reinterpret_cast<void*>(*return_pc_addr);
+ }
+ return GetTwoWordSuccessValue(0, *return_pc_addr);
}
-uintptr_t Instrumentation::PopFramesForDeoptimization(Thread* self, uintptr_t pop_until) const {
+uintptr_t Instrumentation::PopInstrumentationStackUntil(Thread* self, uintptr_t pop_until) const {
std::map<uintptr_t, instrumentation::InstrumentationStackFrame>* stack =
self->GetInstrumentationStack();
// Pop all instrumentation frames below `pop_until`.
uintptr_t return_pc = 0u;
for (auto i = stack->begin(); i != stack->end() && i->first <= pop_until;) {
- auto e = i;
- ++i;
if (kVerboseInstrumentation) {
- LOG(INFO) << "Popping for deoptimization " << e->second.method_->PrettyMethod();
+ LOG(INFO) << "Popping for deoptimization " << i->second.method_->PrettyMethod();
}
- return_pc = e->second.return_pc_;
- stack->erase(e);
+ return_pc = i->second.return_pc_;
+ i = stack->erase(i);
}
return return_pc;
}
diff --git a/runtime/instrumentation.h b/runtime/instrumentation.h
index c811935..23c433e 100644
--- a/runtime/instrumentation.h
+++ b/runtime/instrumentation.h
@@ -23,6 +23,7 @@
#include <list>
#include <memory>
#include <optional>
+#include <queue>
#include <unordered_set>
#include "arch/instruction_set.h"
@@ -31,6 +32,7 @@
#include "base/macros.h"
#include "base/safe_map.h"
#include "gc_root.h"
+#include "jvalue.h"
#include "offsets.h"
namespace art {
@@ -92,7 +94,6 @@
// Call-back for when a method is popped due to an exception throw. A method will either cause a
// MethodExited call-back or a MethodUnwind call-back when its activation is removed.
virtual void MethodUnwind(Thread* thread,
- Handle<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc)
REQUIRES_SHARED(Locks::mutator_lock_) = 0;
@@ -221,8 +222,7 @@
// Calls UndeoptimizeEverything which may visit class linker classes through ConfigureStubs.
void DisableDeoptimization(const char* key)
- REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
- REQUIRES(!GetDeoptimizedMethodsLock());
+ REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_);
bool AreAllMethodsDeoptimized() const {
return InterpreterStubsInstalled();
@@ -233,52 +233,44 @@
void DeoptimizeEverything(const char* key)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ !Locks::classlinker_classes_lock_);
// Executes everything with compiled code (or interpreter if there is no code). May visit class
// linker classes through ConfigureStubs.
void UndeoptimizeEverything(const char* key)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ !Locks::classlinker_classes_lock_);
// Deoptimize a method by forcing its execution with the interpreter. Nevertheless, a static
// method (except a class initializer) set to the resolution trampoline will be deoptimized only
// once its declaring class is initialized.
- void Deoptimize(ArtMethod* method)
- REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !GetDeoptimizedMethodsLock());
+ void Deoptimize(ArtMethod* method) REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_);
// Undeoptimze the method by restoring its entrypoints. Nevertheless, a static method
// (except a class initializer) set to the resolution trampoline will be updated only once its
// declaring class is initialized.
- void Undeoptimize(ArtMethod* method)
- REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_, !GetDeoptimizedMethodsLock());
+ void Undeoptimize(ArtMethod* method) REQUIRES(Locks::mutator_lock_, !Locks::thread_list_lock_);
// Indicates whether the method has been deoptimized so it is executed with the interpreter.
- bool IsDeoptimized(ArtMethod* method)
- REQUIRES(!GetDeoptimizedMethodsLock()) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsDeoptimized(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
// Indicates if any method needs to be deoptimized. This is used to avoid walking the stack to
// determine if a deoptimization is required.
- bool IsDeoptimizedMethodsEmpty() const
- REQUIRES(!GetDeoptimizedMethodsLock()) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsDeoptimizedMethodsEmpty() const REQUIRES_SHARED(Locks::mutator_lock_);
// Enable method tracing by installing instrumentation entry/exit stubs or interpreter.
void EnableMethodTracing(const char* key,
bool needs_interpreter = kDeoptimizeForAccurateMethodEntryExitListeners)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ !Locks::classlinker_classes_lock_);
// Disable method tracing by uninstalling instrumentation entry/exit stubs or interpreter.
void DisableMethodTracing(const char* key)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
REQUIRES(!Locks::thread_list_lock_,
- !Locks::classlinker_classes_lock_,
- !GetDeoptimizedMethodsLock());
+ !Locks::classlinker_classes_lock_);
void InstrumentQuickAllocEntryPoints() REQUIRES(!Locks::instrument_entrypoints_lock_);
@@ -300,11 +292,11 @@
// Update the code of a method respecting any installed stubs.
void UpdateMethodsCode(ArtMethod* method, const void* new_code)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Update the code of a native method to a JITed stub.
void UpdateNativeMethodsCodeToJitCode(ArtMethod* method, const void* new_code)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Return the code that we can execute for an invoke including from the JIT.
const void* GetCodeForInvoke(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -383,6 +375,20 @@
return have_exception_handled_listeners_;
}
+ // Returns if dex pc events need to be reported for the specified method.
+ // These events are reported when DexPCListeners are installed and at least one of the
+ // following conditions hold:
+ // 1. The method is deoptimized. This is done when there is a breakpoint on method.
+ // 2. When the thread is deoptimized. This is used when single stepping a single thread.
+ // 3. When interpreter stubs are installed. In this case no additional information is maintained
+ // about which methods need dex pc move events. This is usually used for features which need
+ // them for several methods across threads or need expensive processing. So it is OK to not
+ // further optimize this case.
+ // DexPCListeners are installed when there is a breakpoint on any method / single stepping
+ // on any of thread. These are removed when the last breakpoint was removed. See AddListener and
+ // RemoveListener for more details.
+ bool NeedsDexPcEvents(ArtMethod* method, Thread* thread) REQUIRES_SHARED(Locks::mutator_lock_);
+
bool NeedsSlowInterpreterForListeners() const REQUIRES_SHARED(Locks::mutator_lock_) {
return have_field_read_listeners_ ||
have_field_write_listeners_ ||
@@ -413,7 +419,6 @@
// Inform listeners that a method has been exited due to an exception.
void MethodUnwindEvent(Thread* thread,
- ObjPtr<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc) const
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -479,12 +484,35 @@
void ExceptionHandledEvent(Thread* thread, ObjPtr<mirror::Throwable> exception_object) const
REQUIRES_SHARED(Locks::mutator_lock_);
- JValue GetReturnValue(Thread* self,
- ArtMethod* method,
- bool* is_ref,
- uint64_t* gpr_result,
- uint64_t* fpr_result) REQUIRES_SHARED(Locks::mutator_lock_);
- bool ShouldDeoptimizeMethod(Thread* self, const NthCallerVisitor& visitor)
+ JValue GetReturnValue(ArtMethod* method, bool* is_ref, uint64_t* gpr_result, uint64_t* fpr_result)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ bool PushDeoptContextIfNeeded(Thread* self,
+ DeoptimizationMethodType deopt_type,
+ bool is_ref,
+ const JValue& result) REQUIRES_SHARED(Locks::mutator_lock_);
+ void DeoptimizeIfNeeded(Thread* self,
+ ArtMethod** sp,
+ DeoptimizationMethodType type,
+ JValue result,
+ bool is_ref) REQUIRES_SHARED(Locks::mutator_lock_);
+ // TODO(mythria): Update uses of ShouldDeoptimizeCaller that takes a visitor by a method that
+ // doesn't need to walk the stack. This is used on method exits to check if the caller needs a
+ // deoptimization.
+ bool ShouldDeoptimizeCaller(Thread* self, const NthCallerVisitor& visitor)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ // This returns if the caller of runtime method requires a deoptimization. This checks both if the
+ // method requires a deopt or if this particular frame needs a deopt because of a class
+ // redefinition.
+ bool ShouldDeoptimizeCaller(Thread* self, ArtMethod** sp) REQUIRES_SHARED(Locks::mutator_lock_);
+ // This is a helper function used by the two variants of ShouldDeoptimizeCaller.
+ // Remove this once ShouldDeoptimizeCaller is updated not to use NthCallerVisitor.
+ bool ShouldDeoptimizeCaller(Thread* self,
+ ArtMethod* caller,
+ uintptr_t caller_pc,
+ uintptr_t caller_sp) REQUIRES_SHARED(Locks::mutator_lock_);
+ // This returns if the specified method requires a deoptimization. This doesn't account if a stack
+ // frame involving this method requires a deoptimization.
+ bool NeedsSlowInterpreterForMethod(Thread* self, ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_);
// Called when an instrumented method is entered. The intended link register (lr) is saved so
@@ -510,19 +538,17 @@
uintptr_t* return_pc_addr,
uint64_t* gpr_result,
uint64_t* fpr_result)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+ REQUIRES_SHARED(Locks::mutator_lock_);
- // Pops nframes instrumentation frames from the current thread. Returns the return pc for the last
- // instrumentation frame that's popped.
- uintptr_t PopFramesForDeoptimization(Thread* self, uintptr_t stack_pointer) const
+ // Pops instrumentation frames until the specified stack_pointer from the current thread. Returns
+ // the return pc for the last instrumentation frame that's popped.
+ uintptr_t PopInstrumentationStackUntil(Thread* self, uintptr_t stack_pointer) const
REQUIRES_SHARED(Locks::mutator_lock_);
// Call back for configure stubs.
- void InstallStubsForClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!GetDeoptimizedMethodsLock());
+ void InstallStubsForClass(ObjPtr<mirror::Class> klass) REQUIRES_SHARED(Locks::mutator_lock_);
- void InstallStubsForMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
+ void InstallStubsForMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
// Install instrumentation exit stub on every method of the stack of the given thread.
// This is used by:
@@ -548,6 +574,11 @@
return alloc_entrypoints_instrumented_;
}
+ bool ProcessMethodUnwindCallbacks(Thread* self,
+ std::queue<ArtMethod*>& methods,
+ MutableHandle<mirror::Throwable>& exception)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
InstrumentationLevel GetCurrentInstrumentationLevel() const;
private:
@@ -555,11 +586,6 @@
// False otherwise.
bool RequiresInstrumentationInstallation(InstrumentationLevel new_level) const;
- // Returns true if we need entry exit stub to call entry hooks. JITed code
- // directly call entry / exit hooks and don't need the stub.
- static bool CodeNeedsEntryExitStub(const void* code, ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_);
-
// Update the current instrumentation_level_.
void UpdateInstrumentationLevel(InstrumentationLevel level);
@@ -570,12 +596,10 @@
// becomes the highest instrumentation level required by a client.
void ConfigureStubs(const char* key, InstrumentationLevel desired_instrumentation_level)
REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
- REQUIRES(!GetDeoptimizedMethodsLock(),
- !Locks::thread_list_lock_,
+ REQUIRES(!Locks::thread_list_lock_,
!Locks::classlinker_classes_lock_);
void UpdateStubs() REQUIRES(Locks::mutator_lock_, Roles::uninterruptible_)
- REQUIRES(!GetDeoptimizedMethodsLock(),
- !Locks::thread_list_lock_,
+ REQUIRES(!Locks::thread_list_lock_,
!Locks::classlinker_classes_lock_);
// If there are no pending deoptimizations restores the stack to the normal state by updating the
@@ -619,22 +643,11 @@
REQUIRES_SHARED(Locks::mutator_lock_);
// Read barrier-aware utility functions for accessing deoptimized_methods_
- bool AddDeoptimizedMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(GetDeoptimizedMethodsLock());
- bool IsDeoptimizedMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
- bool RemoveDeoptimizedMethod(ArtMethod* method)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(GetDeoptimizedMethodsLock());
- ArtMethod* BeginDeoptimizedMethod()
- REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
- bool IsDeoptimizedMethodsEmptyLocked() const
- REQUIRES_SHARED(Locks::mutator_lock_, GetDeoptimizedMethodsLock());
+ bool AddDeoptimizedMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
+ bool IsDeoptimizedMethod(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
+ bool RemoveDeoptimizedMethod(ArtMethod* method) REQUIRES(Locks::mutator_lock_);
void UpdateMethodsCodeImpl(ArtMethod* method, const void* new_code)
- REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!GetDeoptimizedMethodsLock());
-
- ReaderWriterMutex* GetDeoptimizedMethodsLock() const {
- return deoptimized_methods_lock_.get();
- }
+ REQUIRES_SHARED(Locks::mutator_lock_);
// A counter that's incremented every time a DeoptimizeAllFrames. We check each
// InstrumentationStackFrames creation id against this number and if they differ we deopt even if
@@ -718,8 +731,7 @@
// The set of methods being deoptimized (by the debugger) which must be executed with interpreter
// only.
- mutable std::unique_ptr<ReaderWriterMutex> deoptimized_methods_lock_ BOTTOM_MUTEX_ACQUIRED_AFTER;
- std::unordered_set<ArtMethod*> deoptimized_methods_ GUARDED_BY(GetDeoptimizedMethodsLock());
+ std::unordered_set<ArtMethod*> deoptimized_methods_ GUARDED_BY(Locks::mutator_lock_);
// Current interpreter handler table. This is updated each time the thread state flags are
// modified.
diff --git a/runtime/instrumentation_test.cc b/runtime/instrumentation_test.cc
index b0a81b6..d58fb4a 100644
--- a/runtime/instrumentation_test.cc
+++ b/runtime/instrumentation_test.cc
@@ -77,7 +77,6 @@
}
void MethodUnwind(Thread* thread ATTRIBUTE_UNUSED,
- Handle<mirror::Object> this_object ATTRIBUTE_UNUSED,
ArtMethod* method ATTRIBUTE_UNUSED,
uint32_t dex_pc ATTRIBUTE_UNUSED)
override REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -387,7 +386,7 @@
break;
}
case instrumentation::Instrumentation::kMethodUnwind:
- instr->MethodUnwindEvent(self, obj, method, dex_pc);
+ instr->MethodUnwindEvent(self, method, dex_pc);
break;
case instrumentation::Instrumentation::kDexPcMoved:
instr->DexPcMovedEvent(self, obj, method, dex_pc);
diff --git a/runtime/intern_table.cc b/runtime/intern_table.cc
index f587d01..10b2d65 100644
--- a/runtime/intern_table.cc
+++ b/runtime/intern_table.cc
@@ -190,8 +190,8 @@
{
ScopedThreadSuspension sts(self, ThreadState::kWaitingWeakGcRootRead);
MutexLock mu(self, *Locks::intern_table_lock_);
- while ((!kUseReadBarrier && weak_root_state_ == gc::kWeakRootStateNoReadsOrWrites) ||
- (kUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
+ while ((!gUseReadBarrier && weak_root_state_ == gc::kWeakRootStateNoReadsOrWrites) ||
+ (gUseReadBarrier && !self->GetWeakRefAccessEnabled())) {
weak_intern_condition_.Wait(self);
}
}
@@ -218,7 +218,7 @@
if (strong != nullptr) {
return strong;
}
- if (kUseReadBarrier ? self->GetWeakRefAccessEnabled()
+ if (gUseReadBarrier ? self->GetWeakRefAccessEnabled()
: weak_root_state_ != gc::kWeakRootStateNoReadsOrWrites) {
break;
}
@@ -230,7 +230,7 @@
auto h = hs.NewHandleWrapper(&s);
WaitUntilAccessible(self);
}
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
CHECK_EQ(weak_root_state_, gc::kWeakRootStateNormal);
} else {
CHECK(self->GetWeakRefAccessEnabled());
@@ -405,7 +405,10 @@
if (new_object == nullptr) {
it = set->erase(it);
} else {
- *it = GcRoot<mirror::String>(new_object->AsString());
+ // Don't use AsString as it does IsString check in debug builds which, in
+ // case of userfaultfd GC, is called when the object's content isn't
+ // thereyet.
+ *it = GcRoot<mirror::String>(ObjPtr<mirror::String>::DownCast(new_object));
++it;
}
}
@@ -426,7 +429,7 @@
}
void InternTable::ChangeWeakRootStateLocked(gc::WeakRootState new_state) {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
weak_root_state_ = new_state;
if (new_state != gc::kWeakRootStateNoReadsOrWrites) {
weak_intern_condition_.Broadcast(Thread::Current());
diff --git a/runtime/interpreter/interpreter.cc b/runtime/interpreter/interpreter.cc
index 38c94ab..e6fb221 100644
--- a/runtime/interpreter/interpreter.cc
+++ b/runtime/interpreter/interpreter.cc
@@ -255,6 +255,7 @@
}
}
+NO_STACK_PROTECTOR
static inline JValue Execute(
Thread* self,
const CodeItemDataAccessor& accessor,
@@ -265,41 +266,22 @@
DCHECK(!shadow_frame.GetMethod()->IsAbstract());
DCHECK(!shadow_frame.GetMethod()->IsNative());
+ // We cache the result of NeedsDexPcEvents in the shadow frame so we don't need to call
+ // NeedsDexPcEvents on every instruction for better performance. NeedsDexPcEvents only gets
+ // updated asynchronoulsy in a SuspendAll scope and any existing shadow frames are updated with
+ // new value. So it is safe to cache it here.
+ shadow_frame.SetNotifyDexPcMoveEvents(
+ Runtime::Current()->GetInstrumentation()->NeedsDexPcEvents(shadow_frame.GetMethod(), self));
+
if (LIKELY(!from_deoptimize)) { // Entering the method, but not via deoptimization.
if (kIsDebugBuild) {
CHECK_EQ(shadow_frame.GetDexPC(), 0u);
self->AssertNoPendingException();
}
- instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
ArtMethod *method = shadow_frame.GetMethod();
- if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
- instrumentation->MethodEnterEvent(self, method);
- if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
- // The caller will retry this invoke or ignore the result. Just return immediately without
- // any value.
- DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
- JValue ret = JValue();
- PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>(
- self, shadow_frame, ret, instrumentation, accessor.InsSize());
- return ret;
- }
- if (UNLIKELY(self->IsExceptionPending())) {
- instrumentation->MethodUnwindEvent(self,
- shadow_frame.GetThisObject(accessor.InsSize()),
- method,
- 0);
- JValue ret = JValue();
- if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
- DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
- PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>(
- self, shadow_frame, ret, instrumentation, accessor.InsSize());
- }
- return ret;
- }
- }
-
- if (!stay_in_interpreter && !self->IsForceInterpreter()) {
+ // If we can continue in JIT and have JITed code available execute JITed code.
+ if (!stay_in_interpreter && !self->IsForceInterpreter() && !shadow_frame.GetForcePopFrame()) {
jit::Jit* jit = Runtime::Current()->GetJit();
if (jit != nullptr) {
jit->MethodEntered(self, shadow_frame.GetMethod());
@@ -320,6 +302,32 @@
}
}
}
+
+ instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
+ if (UNLIKELY(instrumentation->HasMethodEntryListeners() || shadow_frame.GetForcePopFrame())) {
+ instrumentation->MethodEnterEvent(self, method);
+ if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
+ // The caller will retry this invoke or ignore the result. Just return immediately without
+ // any value.
+ DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+ JValue ret = JValue();
+ PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>(
+ self, shadow_frame, ret, instrumentation, accessor.InsSize());
+ return ret;
+ }
+ if (UNLIKELY(self->IsExceptionPending())) {
+ instrumentation->MethodUnwindEvent(self,
+ method,
+ 0);
+ JValue ret = JValue();
+ if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
+ DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
+ PerformNonStandardReturn<MonitorState::kNoMonitorsLocked>(
+ self, shadow_frame, ret, instrumentation, accessor.InsSize());
+ }
+ return ret;
+ }
+ }
}
ArtMethod* method = shadow_frame.GetMethod();
@@ -366,7 +374,7 @@
num_ins = accessor.InsSize();
} else if (!method->IsInvokable()) {
self->EndAssertNoThreadSuspension(old_cause);
- method->ThrowInvocationTimeError();
+ method->ThrowInvocationTimeError(receiver);
return;
} else {
DCHECK(method->IsNative()) << method->PrettyMethod();
@@ -476,6 +484,7 @@
const uint32_t dex_pc = shadow_frame->GetDexPC();
uint32_t new_dex_pc = dex_pc;
if (UNLIKELY(self->IsExceptionPending())) {
+ DCHECK(self->GetException() != Thread::GetDeoptimizationException());
// If we deoptimize from the QuickExceptionHandler, we already reported the exception throw
// event to the instrumentation. Skip throw listeners for the first frame. The deopt check
// should happen after the throw listener is called as throw listener can trigger a
@@ -514,7 +523,7 @@
new_dex_pc = dex_pc + instr->SizeInCodeUnits();
} else if (instr->IsInvoke()) {
DCHECK(deopt_method_type == DeoptimizationMethodType::kDefault);
- if (IsStringInit(instr, shadow_frame->GetMethod())) {
+ if (IsStringInit(*instr, shadow_frame->GetMethod())) {
uint16_t this_obj_vreg = GetReceiverRegisterForStringInit(instr);
// Move the StringFactory.newStringFromChars() result into the register representing
// "this object" when invoking the string constructor in the original dex instruction.
@@ -569,6 +578,7 @@
ret_val->SetJ(value.GetJ());
}
+NO_STACK_PROTECTOR
JValue EnterInterpreterFromEntryPoint(Thread* self, const CodeItemDataAccessor& accessor,
ShadowFrame* shadow_frame) {
DCHECK_EQ(self, Thread::Current());
@@ -585,6 +595,7 @@
return Execute(self, accessor, *shadow_frame, JValue());
}
+NO_STACK_PROTECTOR
void ArtInterpreterToInterpreterBridge(Thread* self,
const CodeItemDataAccessor& accessor,
ShadowFrame* shadow_frame,
diff --git a/runtime/interpreter/interpreter_cache-inl.h b/runtime/interpreter/interpreter_cache-inl.h
index cea8157..269f5fa 100644
--- a/runtime/interpreter/interpreter_cache-inl.h
+++ b/runtime/interpreter/interpreter_cache-inl.h
@@ -37,9 +37,9 @@
DCHECK(self->GetInterpreterCache() == this) << "Must be called from owning thread";
// For simplicity, only update the cache if weak ref accesses are enabled. If
- // they are disabled, this means the GC is processing the cache, and is
+ // they are disabled, this means the CC GC could be processing the cache, and
// reading it concurrently.
- if (kUseReadBarrier && self->GetWeakRefAccessEnabled()) {
+ if (!gUseReadBarrier || self->GetWeakRefAccessEnabled()) {
data_[IndexOf(key)] = Entry{key, value};
}
}
diff --git a/runtime/interpreter/interpreter_cache.h b/runtime/interpreter/interpreter_cache.h
index c57d023..8714bc6 100644
--- a/runtime/interpreter/interpreter_cache.h
+++ b/runtime/interpreter/interpreter_cache.h
@@ -47,7 +47,7 @@
class ALIGNED(16) InterpreterCache {
public:
// Aligned since we load the whole entry in single assembly instruction.
- typedef std::pair<const void*, size_t> Entry ALIGNED(2 * sizeof(size_t));
+ using Entry ALIGNED(2 * sizeof(size_t)) = std::pair<const void*, size_t>;
// 2x size increase/decrease corresponds to ~0.5% interpreter performance change.
// Value of 256 has around 75% cache hit rate.
diff --git a/runtime/interpreter/interpreter_common.cc b/runtime/interpreter/interpreter_common.cc
index c8a87c1..a9d473b 100644
--- a/runtime/interpreter/interpreter_common.cc
+++ b/runtime/interpreter/interpreter_common.cc
@@ -185,7 +185,6 @@
// Exception is not caught by the current method. We will unwind to the
// caller. Notify any instrumentation listener.
instrumentation->MethodUnwindEvent(self,
- shadow_frame.GetThisObject(),
shadow_frame.GetMethod(),
shadow_frame.GetDexPC());
}
@@ -243,7 +242,8 @@
JValue* result,
uint16_t number_of_inputs,
uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- uint32_t vregC) REQUIRES_SHARED(Locks::mutator_lock_);
+ uint32_t vregC,
+ bool string_init) REQUIRES_SHARED(Locks::mutator_lock_);
template <bool is_range>
ALWAYS_INLINE void CopyRegisters(ShadowFrame& caller_frame,
@@ -255,6 +255,7 @@
// END DECLARATIONS.
+NO_STACK_PROTECTOR
void ArtInterpreterToCompiledCodeBridge(Thread* self,
ArtMethod* caller,
ShadowFrame* shadow_frame,
@@ -1213,15 +1214,8 @@
JValue* result,
uint16_t number_of_inputs,
uint32_t (&arg)[Instruction::kMaxVarArgRegs],
- uint32_t vregC) {
- bool string_init = false;
- // Replace calls to String.<init> with equivalent StringFactory call.
- if (UNLIKELY(called_method->GetDeclaringClass()->IsStringClass()
- && called_method->IsConstructor())) {
- called_method = WellKnownClasses::StringInitToStringFactory(called_method);
- string_init = true;
- }
-
+ uint32_t vregC,
+ bool string_init) {
// Compute method information.
CodeItemDataAccessor accessor(called_method->DexInstructionData());
// Number of registers for the callee's call frame.
@@ -1411,8 +1405,14 @@
}
template<bool is_range, bool do_assignability_check>
-bool DoCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame,
- const Instruction* inst, uint16_t inst_data, JValue* result) {
+NO_STACK_PROTECTOR
+bool DoCall(ArtMethod* called_method,
+ Thread* self,
+ ShadowFrame& shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ bool is_string_init,
+ JValue* result) {
// Argument word count.
const uint16_t number_of_inputs =
(is_range) ? inst->VRegA_3rc(inst_data) : inst->VRegA_35c(inst_data);
@@ -1429,8 +1429,14 @@
}
return DoCallCommon<is_range, do_assignability_check>(
- called_method, self, shadow_frame,
- result, number_of_inputs, arg, vregC);
+ called_method,
+ self,
+ shadow_frame,
+ result,
+ number_of_inputs,
+ arg,
+ vregC,
+ is_string_init);
}
template <bool is_range, bool do_access_check, bool transaction_active>
@@ -1557,9 +1563,12 @@
// Explicit DoCall template function declarations.
#define EXPLICIT_DO_CALL_TEMPLATE_DECL(_is_range, _do_assignability_check) \
template REQUIRES_SHARED(Locks::mutator_lock_) \
- bool DoCall<_is_range, _do_assignability_check>(ArtMethod* method, Thread* self, \
+ bool DoCall<_is_range, _do_assignability_check>(ArtMethod* method, \
+ Thread* self, \
ShadowFrame& shadow_frame, \
- const Instruction* inst, uint16_t inst_data, \
+ const Instruction* inst, \
+ uint16_t inst_data, \
+ bool string_init, \
JValue* result)
EXPLICIT_DO_CALL_TEMPLATE_DECL(false, false);
EXPLICIT_DO_CALL_TEMPLATE_DECL(false, true);
diff --git a/runtime/interpreter/interpreter_common.h b/runtime/interpreter/interpreter_common.h
index 0b91120..49d7e64 100644
--- a/runtime/interpreter/interpreter_common.h
+++ b/runtime/interpreter/interpreter_common.h
@@ -20,7 +20,6 @@
#include "android-base/macros.h"
#include "instrumentation.h"
#include "interpreter.h"
-#include "interpreter_intrinsics.h"
#include "transaction.h"
#include <math.h>
@@ -126,8 +125,13 @@
// DoFastInvoke and DoInvokeVirtualQuick functions.
// Returns true on success, otherwise throws an exception and returns false.
template<bool is_range, bool do_assignability_check>
-bool DoCall(ArtMethod* called_method, Thread* self, ShadowFrame& shadow_frame,
- const Instruction* inst, uint16_t inst_data, JValue* result);
+bool DoCall(ArtMethod* called_method,
+ Thread* self,
+ ShadowFrame& shadow_frame,
+ const Instruction* inst,
+ uint16_t inst_data,
+ bool string_init,
+ JValue* result);
// Called by the switch interpreter to know if we can stay in it.
bool ShouldStayInSwitchInterpreter(ArtMethod* method)
@@ -220,7 +224,7 @@
// Handles all invoke-XXX/range instructions except for invoke-polymorphic[/range].
// Returns true on success, otherwise throws an exception and returns false.
-template<InvokeType type, bool is_range, bool do_access_check, bool is_mterp>
+template<InvokeType type, bool is_range, bool do_access_check>
static ALWAYS_INLINE bool DoInvoke(Thread* self,
ShadowFrame& shadow_frame,
const Instruction* inst,
@@ -231,63 +235,19 @@
if (UNLIKELY(self->ObserveAsyncException())) {
return false;
}
- const uint32_t method_idx = (is_range) ? inst->VRegB_3rc() : inst->VRegB_35c();
- const uint32_t vregC = (is_range) ? inst->VRegC_3rc() : inst->VRegC_35c();
+ const uint32_t vregC = is_range ? inst->VRegC_3rc() : inst->VRegC_35c();
+ ObjPtr<mirror::Object> obj = type == kStatic ? nullptr : shadow_frame.GetVRegReference(vregC);
ArtMethod* sf_method = shadow_frame.GetMethod();
-
- // Try to find the method in small thread-local cache first (only used when
- // nterp is not used as mterp and nterp use the cache in an incompatible way).
- InterpreterCache* tls_cache = self->GetInterpreterCache();
- size_t tls_value;
- ArtMethod* resolved_method;
- if (!IsNterpSupported() && LIKELY(tls_cache->Get(self, inst, &tls_value))) {
- resolved_method = reinterpret_cast<ArtMethod*>(tls_value);
- } else {
- ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
- constexpr ClassLinker::ResolveMode resolve_mode =
- do_access_check ? ClassLinker::ResolveMode::kCheckICCEAndIAE
- : ClassLinker::ResolveMode::kNoChecks;
- resolved_method = class_linker->ResolveMethod<resolve_mode>(self, method_idx, sf_method, type);
- if (UNLIKELY(resolved_method == nullptr)) {
- CHECK(self->IsExceptionPending());
- result->SetJ(0);
- return false;
- }
- if (!IsNterpSupported()) {
- tls_cache->Set(self, inst, reinterpret_cast<size_t>(resolved_method));
- }
- }
-
- // Null pointer check and virtual method resolution.
- ObjPtr<mirror::Object> receiver =
- (type == kStatic) ? nullptr : shadow_frame.GetVRegReference(vregC);
- ArtMethod* called_method;
- called_method = FindMethodToCall<type, do_access_check>(
- method_idx, resolved_method, &receiver, sf_method, self);
- if (UNLIKELY(called_method == nullptr)) {
- CHECK(self->IsExceptionPending());
- result->SetJ(0);
- return false;
- }
- if (UNLIKELY(!called_method->IsInvokable())) {
- called_method->ThrowInvocationTimeError();
+ bool string_init = false;
+ ArtMethod* called_method = FindMethodToCall<type>(self, sf_method, &obj, *inst, &string_init);
+ if (called_method == nullptr) {
+ DCHECK(self->IsExceptionPending());
result->SetJ(0);
return false;
}
- jit::Jit* jit = Runtime::Current()->GetJit();
- if (is_mterp && !is_range && called_method->IsIntrinsic()) {
- if (MterpHandleIntrinsic(&shadow_frame, called_method, inst, inst_data,
- shadow_frame.GetResultRegister())) {
- if (jit != nullptr && sf_method != nullptr) {
- jit->NotifyInterpreterToCompiledCodeTransition(self, sf_method);
- }
- return !self->IsExceptionPending();
- }
- }
-
- return DoCall<is_range, do_access_check>(called_method, self, shadow_frame, inst, inst_data,
- result);
+ return DoCall<is_range, do_access_check>(
+ called_method, self, shadow_frame, inst, inst_data, string_init, result);
}
static inline ObjPtr<mirror::MethodHandle> ResolveMethodHandle(Thread* self,
@@ -755,34 +715,6 @@
uint16_t arg_offset,
JValue* result);
-static inline bool IsStringInit(const DexFile* dex_file, uint32_t method_idx)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- const dex::MethodId& method_id = dex_file->GetMethodId(method_idx);
- const char* class_name = dex_file->StringByTypeIdx(method_id.class_idx_);
- const char* method_name = dex_file->GetMethodName(method_id);
- // Instead of calling ResolveMethod() which has suspend point and can trigger
- // GC, look up the method symbolically.
- // Compare method's class name and method name against string init.
- // It's ok since it's not allowed to create your own java/lang/String.
- // TODO: verify that assumption.
- if ((strcmp(class_name, "Ljava/lang/String;") == 0) &&
- (strcmp(method_name, "<init>") == 0)) {
- return true;
- }
- return false;
-}
-
-static inline bool IsStringInit(const Instruction* instr, ArtMethod* caller)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- if (instr->Opcode() == Instruction::INVOKE_DIRECT ||
- instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE) {
- uint16_t callee_method_idx = (instr->Opcode() == Instruction::INVOKE_DIRECT_RANGE) ?
- instr->VRegB_3rc() : instr->VRegB_35c();
- return IsStringInit(caller->GetDexFile(), callee_method_idx);
- }
- return false;
-}
-
// Set string value created from StringFactory.newStringFromXXX() into all aliases of
// StringFactory.newEmptyString().
void SetStringInitValueToAllAliases(ShadowFrame* shadow_frame,
diff --git a/runtime/interpreter/interpreter_intrinsics.cc b/runtime/interpreter/interpreter_intrinsics.cc
deleted file mode 100644
index c8344bc..0000000
--- a/runtime/interpreter/interpreter_intrinsics.cc
+++ /dev/null
@@ -1,678 +0,0 @@
-/*
- * Copyright (C) 2017 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 "interpreter/interpreter_intrinsics.h"
-
-#include "dex/dex_instruction.h"
-#include "intrinsics_enum.h"
-#include "interpreter/interpreter_common.h"
-
-namespace art {
-namespace interpreter {
-
-
-#define BINARY_INTRINSIC(name, op, get1, get2, set) \
-static ALWAYS_INLINE bool name(ShadowFrame* shadow_frame, \
- const Instruction* inst, \
- uint16_t inst_data, \
- JValue* result_register) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- uint32_t arg[Instruction::kMaxVarArgRegs] = {}; \
- inst->GetVarArgs(arg, inst_data); \
- result_register->set(op(shadow_frame->get1, shadow_frame->get2)); \
- return true; \
-}
-
-#define BINARY_II_INTRINSIC(name, op, set) \
- BINARY_INTRINSIC(name, op, GetVReg(arg[0]), GetVReg(arg[1]), set)
-
-#define BINARY_JJ_INTRINSIC(name, op, set) \
- BINARY_INTRINSIC(name, op, GetVRegLong(arg[0]), GetVRegLong(arg[2]), set)
-
-#define BINARY_JI_INTRINSIC(name, op, set) \
- BINARY_INTRINSIC(name, op, GetVRegLong(arg[0]), GetVReg(arg[2]), set)
-
-#define UNARY_INTRINSIC(name, op, get, set) \
-static ALWAYS_INLINE bool name(ShadowFrame* shadow_frame, \
- const Instruction* inst, \
- uint16_t inst_data, \
- JValue* result_register) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- uint32_t arg[Instruction::kMaxVarArgRegs] = {}; \
- inst->GetVarArgs(arg, inst_data); \
- result_register->set(op(shadow_frame->get(arg[0]))); \
- return true; \
-}
-
-
-// java.lang.Integer.reverse(I)I
-UNARY_INTRINSIC(MterpIntegerReverse, ReverseBits32, GetVReg, SetI);
-
-// java.lang.Integer.reverseBytes(I)I
-UNARY_INTRINSIC(MterpIntegerReverseBytes, BSWAP, GetVReg, SetI);
-
-// java.lang.Integer.bitCount(I)I
-UNARY_INTRINSIC(MterpIntegerBitCount, POPCOUNT, GetVReg, SetI);
-
-// java.lang.Integer.compare(II)I
-BINARY_II_INTRINSIC(MterpIntegerCompare, Compare, SetI);
-
-// java.lang.Integer.highestOneBit(I)I
-UNARY_INTRINSIC(MterpIntegerHighestOneBit, HighestOneBitValue, GetVReg, SetI);
-
-// java.lang.Integer.LowestOneBit(I)I
-UNARY_INTRINSIC(MterpIntegerLowestOneBit, LowestOneBitValue, GetVReg, SetI);
-
-// java.lang.Integer.numberOfLeadingZeros(I)I
-UNARY_INTRINSIC(MterpIntegerNumberOfLeadingZeros, JAVASTYLE_CLZ, GetVReg, SetI);
-
-// java.lang.Integer.numberOfTrailingZeros(I)I
-UNARY_INTRINSIC(MterpIntegerNumberOfTrailingZeros, JAVASTYLE_CTZ, GetVReg, SetI);
-
-// java.lang.Integer.rotateRight(II)I
-BINARY_II_INTRINSIC(MterpIntegerRotateRight, (Rot<int32_t, false>), SetI);
-
-// java.lang.Integer.rotateLeft(II)I
-BINARY_II_INTRINSIC(MterpIntegerRotateLeft, (Rot<int32_t, true>), SetI);
-
-// java.lang.Integer.signum(I)I
-UNARY_INTRINSIC(MterpIntegerSignum, Signum, GetVReg, SetI);
-
-// java.lang.Long.reverse(J)J
-UNARY_INTRINSIC(MterpLongReverse, ReverseBits64, GetVRegLong, SetJ);
-
-// java.lang.Long.reverseBytes(J)J
-UNARY_INTRINSIC(MterpLongReverseBytes, BSWAP, GetVRegLong, SetJ);
-
-// java.lang.Long.bitCount(J)I
-UNARY_INTRINSIC(MterpLongBitCount, POPCOUNT, GetVRegLong, SetI);
-
-// java.lang.Long.compare(JJ)I
-BINARY_JJ_INTRINSIC(MterpLongCompare, Compare, SetI);
-
-// java.lang.Long.highestOneBit(J)J
-UNARY_INTRINSIC(MterpLongHighestOneBit, HighestOneBitValue, GetVRegLong, SetJ);
-
-// java.lang.Long.lowestOneBit(J)J
-UNARY_INTRINSIC(MterpLongLowestOneBit, LowestOneBitValue, GetVRegLong, SetJ);
-
-// java.lang.Long.numberOfLeadingZeros(J)I
-UNARY_INTRINSIC(MterpLongNumberOfLeadingZeros, JAVASTYLE_CLZ, GetVRegLong, SetJ);
-
-// java.lang.Long.numberOfTrailingZeros(J)I
-UNARY_INTRINSIC(MterpLongNumberOfTrailingZeros, JAVASTYLE_CTZ, GetVRegLong, SetJ);
-
-// java.lang.Long.rotateRight(JI)J
-BINARY_JI_INTRINSIC(MterpLongRotateRight, (Rot<int64_t, false>), SetJ);
-
-// java.lang.Long.rotateLeft(JI)J
-BINARY_JI_INTRINSIC(MterpLongRotateLeft, (Rot<int64_t, true>), SetJ);
-
-// java.lang.Long.signum(J)I
-UNARY_INTRINSIC(MterpLongSignum, Signum, GetVRegLong, SetI);
-
-// java.lang.Short.reverseBytes(S)S
-UNARY_INTRINSIC(MterpShortReverseBytes, BSWAP, GetVRegShort, SetS);
-
-// java.lang.Math.min(II)I
-BINARY_II_INTRINSIC(MterpMathMinIntInt, std::min, SetI);
-
-// java.lang.Math.min(JJ)J
-BINARY_JJ_INTRINSIC(MterpMathMinLongLong, std::min, SetJ);
-
-// java.lang.Math.max(II)I
-BINARY_II_INTRINSIC(MterpMathMaxIntInt, std::max, SetI);
-
-// java.lang.Math.max(JJ)J
-BINARY_JJ_INTRINSIC(MterpMathMaxLongLong, std::max, SetJ);
-
-// java.lang.Math.abs(I)I
-UNARY_INTRINSIC(MterpMathAbsInt, std::abs, GetVReg, SetI);
-
-// java.lang.Math.abs(J)J
-UNARY_INTRINSIC(MterpMathAbsLong, std::abs, GetVRegLong, SetJ);
-
-// java.lang.Math.abs(F)F
-UNARY_INTRINSIC(MterpMathAbsFloat, 0x7fffffff&, GetVReg, SetI);
-
-// java.lang.Math.abs(D)D
-UNARY_INTRINSIC(MterpMathAbsDouble, INT64_C(0x7fffffffffffffff)&, GetVRegLong, SetJ);
-
-// java.lang.Math.sqrt(D)D
-UNARY_INTRINSIC(MterpMathSqrt, std::sqrt, GetVRegDouble, SetD);
-
-// java.lang.Math.ceil(D)D
-UNARY_INTRINSIC(MterpMathCeil, std::ceil, GetVRegDouble, SetD);
-
-// java.lang.Math.floor(D)D
-UNARY_INTRINSIC(MterpMathFloor, std::floor, GetVRegDouble, SetD);
-
-// java.lang.Math.sin(D)D
-UNARY_INTRINSIC(MterpMathSin, std::sin, GetVRegDouble, SetD);
-
-// java.lang.Math.cos(D)D
-UNARY_INTRINSIC(MterpMathCos, std::cos, GetVRegDouble, SetD);
-
-// java.lang.Math.tan(D)D
-UNARY_INTRINSIC(MterpMathTan, std::tan, GetVRegDouble, SetD);
-
-// java.lang.Math.asin(D)D
-UNARY_INTRINSIC(MterpMathAsin, std::asin, GetVRegDouble, SetD);
-
-// java.lang.Math.acos(D)D
-UNARY_INTRINSIC(MterpMathAcos, std::acos, GetVRegDouble, SetD);
-
-// java.lang.Math.atan(D)D
-UNARY_INTRINSIC(MterpMathAtan, std::atan, GetVRegDouble, SetD);
-
-// java.lang.String.charAt(I)C
-static ALWAYS_INLINE bool MterpStringCharAt(ShadowFrame* shadow_frame,
- const Instruction* inst,
- uint16_t inst_data,
- JValue* result_register)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- uint32_t arg[Instruction::kMaxVarArgRegs] = {};
- inst->GetVarArgs(arg, inst_data);
- ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
- int length = str->GetLength();
- int index = shadow_frame->GetVReg(arg[1]);
- uint16_t res;
- if (UNLIKELY(index < 0) || (index >= length)) {
- return false; // Punt and let non-intrinsic version deal with the throw.
- }
- if (str->IsCompressed()) {
- res = str->GetValueCompressed()[index];
- } else {
- res = str->GetValue()[index];
- }
- result_register->SetC(res);
- return true;
-}
-
-// java.lang.String.compareTo(Ljava/lang/string)I
-static ALWAYS_INLINE bool MterpStringCompareTo(ShadowFrame* shadow_frame,
- const Instruction* inst,
- uint16_t inst_data,
- JValue* result_register)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- uint32_t arg[Instruction::kMaxVarArgRegs] = {};
- inst->GetVarArgs(arg, inst_data);
- ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
- ObjPtr<mirror::Object> arg1 = shadow_frame->GetVRegReference(arg[1]);
- if (arg1 == nullptr) {
- return false;
- }
- result_register->SetI(str->CompareTo(arg1->AsString()));
- return true;
-}
-
-#define STRING_INDEXOF_INTRINSIC(name, starting_pos) \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
- const Instruction* inst, \
- uint16_t inst_data, \
- JValue* result_register) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- uint32_t arg[Instruction::kMaxVarArgRegs] = {}; \
- inst->GetVarArgs(arg, inst_data); \
- ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString(); \
- int ch = shadow_frame->GetVReg(arg[1]); \
- if (ch >= 0x10000) { \
- /* Punt if supplementary char. */ \
- return false; \
- } \
- result_register->SetI(str->FastIndexOf(ch, starting_pos)); \
- return true; \
-}
-
-// java.lang.String.indexOf(I)I
-STRING_INDEXOF_INTRINSIC(StringIndexOf, 0);
-
-// java.lang.String.indexOf(II)I
-STRING_INDEXOF_INTRINSIC(StringIndexOfAfter, shadow_frame->GetVReg(arg[2]));
-
-#define SIMPLE_STRING_INTRINSIC(name, operation) \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
- const Instruction* inst, \
- uint16_t inst_data, \
- JValue* result_register) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- uint32_t arg[Instruction::kMaxVarArgRegs] = {}; \
- inst->GetVarArgs(arg, inst_data); \
- ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString(); \
- result_register->operation; \
- return true; \
-}
-
-// java.lang.String.isEmpty()Z
-SIMPLE_STRING_INTRINSIC(StringIsEmpty, SetZ(str->GetLength() == 0))
-
-// java.lang.String.length()I
-SIMPLE_STRING_INTRINSIC(StringLength, SetI(str->GetLength()))
-
-// java.lang.String.getCharsNoCheck(II[CI)V
-static ALWAYS_INLINE bool MterpStringGetCharsNoCheck(ShadowFrame* shadow_frame,
- const Instruction* inst,
- uint16_t inst_data,
- JValue* result_register ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // Start, end & index already checked by caller - won't throw. Destination is uncompressed.
- uint32_t arg[Instruction::kMaxVarArgRegs] = {};
- inst->GetVarArgs(arg, inst_data);
- ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
- int32_t start = shadow_frame->GetVReg(arg[1]);
- int32_t end = shadow_frame->GetVReg(arg[2]);
- int32_t index = shadow_frame->GetVReg(arg[4]);
- ObjPtr<mirror::CharArray> array = shadow_frame->GetVRegReference(arg[3])->AsCharArray();
- uint16_t* dst = array->GetData() + index;
- int32_t len = (end - start);
- if (str->IsCompressed()) {
- const uint8_t* src_8 = str->GetValueCompressed() + start;
- for (int i = 0; i < len; i++) {
- dst[i] = src_8[i];
- }
- } else {
- uint16_t* src_16 = str->GetValue() + start;
- memcpy(dst, src_16, len * sizeof(uint16_t));
- }
- return true;
-}
-
-// java.lang.String.equalsLjava/lang/Object;)Z
-static ALWAYS_INLINE bool MterpStringEquals(ShadowFrame* shadow_frame,
- const Instruction* inst,
- uint16_t inst_data,
- JValue* result_register)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- uint32_t arg[Instruction::kMaxVarArgRegs] = {};
- inst->GetVarArgs(arg, inst_data);
- ObjPtr<mirror::String> str = shadow_frame->GetVRegReference(arg[0])->AsString();
- ObjPtr<mirror::Object> obj = shadow_frame->GetVRegReference(arg[1]);
- bool res = false; // Assume not equal.
- if ((obj != nullptr) && obj->IsString()) {
- ObjPtr<mirror::String> str2 = obj->AsString();
- if (str->GetCount() == str2->GetCount()) {
- // Length & compression status are same. Can use block compare.
- void* bytes1;
- void* bytes2;
- int len = str->GetLength();
- if (str->IsCompressed()) {
- bytes1 = str->GetValueCompressed();
- bytes2 = str2->GetValueCompressed();
- } else {
- len *= sizeof(uint16_t);
- bytes1 = str->GetValue();
- bytes2 = str2->GetValue();
- }
- res = (memcmp(bytes1, bytes2, len) == 0);
- }
- }
- result_register->SetZ(res);
- return true;
-}
-
-#define VARHANDLE_FENCE_INTRINSIC(name, std_memory_operation) \
-static ALWAYS_INLINE bool name(ShadowFrame* shadow_frame ATTRIBUTE_UNUSED, \
- const Instruction* inst ATTRIBUTE_UNUSED, \
- uint16_t inst_data ATTRIBUTE_UNUSED, \
- JValue* result_register ATTRIBUTE_UNUSED) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- std::atomic_thread_fence(std_memory_operation); \
- return true; \
-}
-
-// The VarHandle fence methods are static (unlike jdk.internal.misc.Unsafe versions).
-// The fences for the LoadLoadFence and StoreStoreFence are stronger
-// than strictly required, but the impact should be marginal.
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleFullFence, std::memory_order_seq_cst)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleAcquireFence, std::memory_order_acquire)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleReleaseFence, std::memory_order_release)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleLoadLoadFence, std::memory_order_acquire)
-VARHANDLE_FENCE_INTRINSIC(MterpVarHandleStoreStoreFence, std::memory_order_release)
-
-#define METHOD_HANDLE_INVOKE_INTRINSIC(name) \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
- const Instruction* inst, \
- uint16_t inst_data, \
- JValue* result) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- if (inst->Opcode() == Instruction::INVOKE_POLYMORPHIC) { \
- return DoInvokePolymorphic<false>(Thread::Current(), *shadow_frame, inst, inst_data, result); \
- } else { \
- return DoInvokePolymorphic<true>(Thread::Current(), *shadow_frame, inst, inst_data, result); \
- } \
-}
-
-METHOD_HANDLE_INVOKE_INTRINSIC(MethodHandleInvokeExact)
-METHOD_HANDLE_INVOKE_INTRINSIC(MethodHandleInvoke)
-
-#define VAR_HANDLE_ACCESSOR_INTRINSIC(name) \
-static ALWAYS_INLINE bool Mterp##name(ShadowFrame* shadow_frame, \
- const Instruction* inst, \
- uint16_t inst_data, \
- JValue* result) \
- REQUIRES_SHARED(Locks::mutator_lock_) { \
- return Do##name(Thread::Current(), *shadow_frame, inst, inst_data, result); \
-}
-
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndExchange)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndExchangeAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndExchangeRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleCompareAndSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGet);
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndAdd)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndAddAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndAddRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseAnd)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseAndAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseAndRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseOr)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseOrAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseOrRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseXor)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseXorAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndBitwiseXorRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndSetAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetAndSetRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetOpaque)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleGetVolatile)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSetOpaque)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSetRelease)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleSetVolatile)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSet)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSetAcquire)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSetPlain)
-VAR_HANDLE_ACCESSOR_INTRINSIC(VarHandleWeakCompareAndSetRelease)
-
-static ALWAYS_INLINE bool MterpReachabilityFence(ShadowFrame* shadow_frame ATTRIBUTE_UNUSED,
- const Instruction* inst ATTRIBUTE_UNUSED,
- uint16_t inst_data ATTRIBUTE_UNUSED,
- JValue* result_register ATTRIBUTE_UNUSED)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- // Do nothing; Its only purpose is to keep the argument reference live
- // at preceding suspend points. That's automatic in the interpreter.
- return true;
-}
-
-// Macro to help keep track of what's left to implement.
-#define UNIMPLEMENTED_CASE(name) \
- case Intrinsics::k##name: \
- res = false; \
- break;
-
-#define INTRINSIC_CASE(name) \
- case Intrinsics::k##name: \
- res = Mterp##name(shadow_frame, inst, inst_data, result_register); \
- break;
-
-bool MterpHandleIntrinsic(ShadowFrame* shadow_frame,
- ArtMethod* const called_method,
- const Instruction* inst,
- uint16_t inst_data,
- JValue* result_register)
- REQUIRES_SHARED(Locks::mutator_lock_) {
- Intrinsics intrinsic = static_cast<Intrinsics>(called_method->GetIntrinsic());
- bool res = false; // Assume failure
- switch (intrinsic) {
- UNIMPLEMENTED_CASE(DoubleDoubleToRawLongBits /* (D)J */)
- UNIMPLEMENTED_CASE(DoubleDoubleToLongBits /* (D)J */)
- UNIMPLEMENTED_CASE(DoubleIsInfinite /* (D)Z */)
- UNIMPLEMENTED_CASE(DoubleIsNaN /* (D)Z */)
- UNIMPLEMENTED_CASE(DoubleLongBitsToDouble /* (J)D */)
- UNIMPLEMENTED_CASE(FloatFloatToRawIntBits /* (F)I */)
- UNIMPLEMENTED_CASE(FloatFloatToIntBits /* (F)I */)
- UNIMPLEMENTED_CASE(FloatIsInfinite /* (F)Z */)
- UNIMPLEMENTED_CASE(FloatIsNaN /* (F)Z */)
- UNIMPLEMENTED_CASE(FloatIntBitsToFloat /* (I)F */)
- UNIMPLEMENTED_CASE(IntegerDivideUnsigned /* (II)I */)
- UNIMPLEMENTED_CASE(LongDivideUnsigned /* (JJ)J */)
- INTRINSIC_CASE(IntegerReverse)
- INTRINSIC_CASE(IntegerReverseBytes)
- INTRINSIC_CASE(IntegerBitCount)
- INTRINSIC_CASE(IntegerCompare)
- INTRINSIC_CASE(IntegerHighestOneBit)
- INTRINSIC_CASE(IntegerLowestOneBit)
- INTRINSIC_CASE(IntegerNumberOfLeadingZeros)
- INTRINSIC_CASE(IntegerNumberOfTrailingZeros)
- INTRINSIC_CASE(IntegerRotateRight)
- INTRINSIC_CASE(IntegerRotateLeft)
- INTRINSIC_CASE(IntegerSignum)
- INTRINSIC_CASE(LongReverse)
- INTRINSIC_CASE(LongReverseBytes)
- INTRINSIC_CASE(LongBitCount)
- INTRINSIC_CASE(LongCompare)
- INTRINSIC_CASE(LongHighestOneBit)
- INTRINSIC_CASE(LongLowestOneBit)
- INTRINSIC_CASE(LongNumberOfLeadingZeros)
- INTRINSIC_CASE(LongNumberOfTrailingZeros)
- INTRINSIC_CASE(LongRotateRight)
- INTRINSIC_CASE(LongRotateLeft)
- INTRINSIC_CASE(LongSignum)
- INTRINSIC_CASE(ShortReverseBytes)
- INTRINSIC_CASE(MathAbsDouble)
- INTRINSIC_CASE(MathAbsFloat)
- INTRINSIC_CASE(MathAbsLong)
- INTRINSIC_CASE(MathAbsInt)
- UNIMPLEMENTED_CASE(MathFmaDouble /* (DDD)D */)
- UNIMPLEMENTED_CASE(MathFmaFloat /* (FFF)F */)
- UNIMPLEMENTED_CASE(MathMinDoubleDouble /* (DD)D */)
- UNIMPLEMENTED_CASE(MathMinFloatFloat /* (FF)F */)
- INTRINSIC_CASE(MathMinLongLong)
- INTRINSIC_CASE(MathMinIntInt)
- UNIMPLEMENTED_CASE(MathMaxDoubleDouble /* (DD)D */)
- UNIMPLEMENTED_CASE(MathMaxFloatFloat /* (FF)F */)
- INTRINSIC_CASE(MathMaxLongLong)
- INTRINSIC_CASE(MathMaxIntInt)
- INTRINSIC_CASE(MathCos)
- INTRINSIC_CASE(MathSin)
- INTRINSIC_CASE(MathAcos)
- INTRINSIC_CASE(MathAsin)
- INTRINSIC_CASE(MathAtan)
- UNIMPLEMENTED_CASE(MathAtan2 /* (DD)D */)
- UNIMPLEMENTED_CASE(MathCbrt /* (D)D */)
- UNIMPLEMENTED_CASE(MathCosh /* (D)D */)
- UNIMPLEMENTED_CASE(MathExp /* (D)D */)
- UNIMPLEMENTED_CASE(MathExpm1 /* (D)D */)
- UNIMPLEMENTED_CASE(MathHypot /* (DD)D */)
- UNIMPLEMENTED_CASE(MathLog /* (D)D */)
- UNIMPLEMENTED_CASE(MathLog10 /* (D)D */)
- UNIMPLEMENTED_CASE(MathNextAfter /* (DD)D */)
- UNIMPLEMENTED_CASE(MathPow /* (DD)D */)
- UNIMPLEMENTED_CASE(MathSinh /* (D)D */)
- INTRINSIC_CASE(MathTan)
- UNIMPLEMENTED_CASE(MathTanh /* (D)D */)
- INTRINSIC_CASE(MathSqrt)
- INTRINSIC_CASE(MathCeil)
- INTRINSIC_CASE(MathFloor)
- UNIMPLEMENTED_CASE(MathRint /* (D)D */)
- UNIMPLEMENTED_CASE(MathRoundDouble /* (D)J */)
- UNIMPLEMENTED_CASE(MathRoundFloat /* (F)I */)
- UNIMPLEMENTED_CASE(MathMultiplyHigh /* (JJ)J */)
- UNIMPLEMENTED_CASE(SystemArrayCopyByte /* ([BI[BII)V */)
- UNIMPLEMENTED_CASE(SystemArrayCopyChar /* ([CI[CII)V */)
- UNIMPLEMENTED_CASE(SystemArrayCopyInt /* ([II[III)V */)
- UNIMPLEMENTED_CASE(SystemArrayCopy /* (Ljava/lang/Object;ILjava/lang/Object;II)V */)
- UNIMPLEMENTED_CASE(ThreadCurrentThread /* ()Ljava/lang/Thread; */)
- UNIMPLEMENTED_CASE(MemoryPeekByte /* (J)B */)
- UNIMPLEMENTED_CASE(MemoryPeekIntNative /* (J)I */)
- UNIMPLEMENTED_CASE(MemoryPeekLongNative /* (J)J */)
- UNIMPLEMENTED_CASE(MemoryPeekShortNative /* (J)S */)
- UNIMPLEMENTED_CASE(MemoryPokeByte /* (JB)V */)
- UNIMPLEMENTED_CASE(MemoryPokeIntNative /* (JI)V */)
- UNIMPLEMENTED_CASE(MemoryPokeLongNative /* (JJ)V */)
- UNIMPLEMENTED_CASE(MemoryPokeShortNative /* (JS)V */)
- INTRINSIC_CASE(ReachabilityFence /* (Ljava/lang/Object;)V */)
- INTRINSIC_CASE(StringCharAt)
- INTRINSIC_CASE(StringCompareTo)
- INTRINSIC_CASE(StringEquals)
- INTRINSIC_CASE(StringGetCharsNoCheck)
- INTRINSIC_CASE(StringIndexOf)
- INTRINSIC_CASE(StringIndexOfAfter)
- UNIMPLEMENTED_CASE(StringStringIndexOf /* (Ljava/lang/String;)I */)
- UNIMPLEMENTED_CASE(StringStringIndexOfAfter /* (Ljava/lang/String;I)I */)
- INTRINSIC_CASE(StringIsEmpty)
- INTRINSIC_CASE(StringLength)
- UNIMPLEMENTED_CASE(StringNewStringFromBytes /* ([BIII)Ljava/lang/String; */)
- UNIMPLEMENTED_CASE(StringNewStringFromChars /* (II[C)Ljava/lang/String; */)
- UNIMPLEMENTED_CASE(StringNewStringFromString /* (Ljava/lang/String;)Ljava/lang/String; */)
- UNIMPLEMENTED_CASE(StringBufferAppend /* (Ljava/lang/String;)Ljava/lang/StringBuffer; */)
- UNIMPLEMENTED_CASE(StringBufferLength /* ()I */)
- UNIMPLEMENTED_CASE(StringBufferToString /* ()Ljava/lang/String; */)
- UNIMPLEMENTED_CASE(
- StringBuilderAppendObject /* (Ljava/lang/Object;)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(
- StringBuilderAppendString /* (Ljava/lang/String;)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(
- StringBuilderAppendCharSequence /* (Ljava/lang/CharSequence;)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderAppendCharArray /* ([C)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderAppendBoolean /* (Z)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderAppendChar /* (C)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderAppendInt /* (I)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderAppendLong /* (J)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderAppendFloat /* (F)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderAppendDouble /* (D)Ljava/lang/StringBuilder; */)
- UNIMPLEMENTED_CASE(StringBuilderLength /* ()I */)
- UNIMPLEMENTED_CASE(StringBuilderToString /* ()Ljava/lang/String; */)
- UNIMPLEMENTED_CASE(UnsafeCASInt /* (Ljava/lang/Object;JII)Z */)
- UNIMPLEMENTED_CASE(UnsafeCASLong /* (Ljava/lang/Object;JJJ)Z */)
- UNIMPLEMENTED_CASE(UnsafeCASObject /* (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z */)
- UNIMPLEMENTED_CASE(UnsafeGet /* (Ljava/lang/Object;J)I */)
- UNIMPLEMENTED_CASE(UnsafeGetVolatile /* (Ljava/lang/Object;J)I */)
- UNIMPLEMENTED_CASE(UnsafeGetObject /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(UnsafeGetObjectVolatile /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(UnsafeGetLong /* (Ljava/lang/Object;J)J */)
- UNIMPLEMENTED_CASE(UnsafeGetLongVolatile /* (Ljava/lang/Object;J)J */)
- UNIMPLEMENTED_CASE(UnsafePut /* (Ljava/lang/Object;JI)V */)
- UNIMPLEMENTED_CASE(UnsafePutOrdered /* (Ljava/lang/Object;JI)V */)
- UNIMPLEMENTED_CASE(UnsafePutVolatile /* (Ljava/lang/Object;JI)V */)
- UNIMPLEMENTED_CASE(UnsafePutObject /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
- UNIMPLEMENTED_CASE(UnsafePutObjectOrdered /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
- UNIMPLEMENTED_CASE(UnsafePutObjectVolatile /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
- UNIMPLEMENTED_CASE(UnsafePutLong /* (Ljava/lang/Object;JJ)V */)
- UNIMPLEMENTED_CASE(UnsafePutLongOrdered /* (Ljava/lang/Object;JJ)V */)
- UNIMPLEMENTED_CASE(UnsafePutLongVolatile /* (Ljava/lang/Object;JJ)V */)
- UNIMPLEMENTED_CASE(UnsafeGetAndAddInt /* (Ljava/lang/Object;JI)I */)
- UNIMPLEMENTED_CASE(UnsafeGetAndAddLong /* (Ljava/lang/Object;JJ)J */)
- UNIMPLEMENTED_CASE(UnsafeGetAndSetInt /* (Ljava/lang/Object;JI)I */)
- UNIMPLEMENTED_CASE(UnsafeGetAndSetLong /* (Ljava/lang/Object;JJ)J */)
- UNIMPLEMENTED_CASE(UnsafeGetAndSetObject /* (Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(UnsafeLoadFence /* ()V */)
- UNIMPLEMENTED_CASE(UnsafeStoreFence /* ()V */)
- UNIMPLEMENTED_CASE(UnsafeFullFence /* ()V */)
- UNIMPLEMENTED_CASE(JdkUnsafeCASInt /* (Ljava/lang/Object;JII)Z */)
- UNIMPLEMENTED_CASE(JdkUnsafeCASLong /* (Ljava/lang/Object;JJJ)Z */)
- UNIMPLEMENTED_CASE(JdkUnsafeCASObject /* (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z */)
- UNIMPLEMENTED_CASE(JdkUnsafeCompareAndSetInt /* (Ljava/lang/Object;JII)Z */)
- UNIMPLEMENTED_CASE(JdkUnsafeCompareAndSetLong /* (Ljava/lang/Object;JJJ)Z */)
- UNIMPLEMENTED_CASE(JdkUnsafeCompareAndSetObject /* (Ljava/lang/Object;JLjava/lang/Object;Ljava/lang/Object;)Z */)
- UNIMPLEMENTED_CASE(JdkUnsafeGet /* (Ljava/lang/Object;J)I */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetVolatile /* (Ljava/lang/Object;J)I */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetAcquire /* (Ljava/lang/Object;J)I */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetObject /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetObjectVolatile /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetObjectAcquire /* (Ljava/lang/Object;J)Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetLong /* (Ljava/lang/Object;J)J */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetLongVolatile /* (Ljava/lang/Object;J)J */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetLongAcquire /* (Ljava/lang/Object;J)J */)
- UNIMPLEMENTED_CASE(JdkUnsafePut /* (Ljava/lang/Object;JI)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutOrdered /* (Ljava/lang/Object;JI)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutVolatile /* (Ljava/lang/Object;JI)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutRelease /* (Ljava/lang/Object;JI)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutObject /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutObjectOrdered /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutObjectVolatile /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutObjectRelease /* (Ljava/lang/Object;JLjava/lang/Object;)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutLong /* (Ljava/lang/Object;JJ)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutLongOrdered /* (Ljava/lang/Object;JJ)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutLongVolatile /* (Ljava/lang/Object;JJ)V */)
- UNIMPLEMENTED_CASE(JdkUnsafePutLongRelease /* (Ljava/lang/Object;JJ)V */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetAndAddInt /* (Ljava/lang/Object;JI)I */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetAndAddLong /* (Ljava/lang/Object;JJ)J */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetAndSetInt /* (Ljava/lang/Object;JI)I */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetAndSetLong /* (Ljava/lang/Object;JJ)J */)
- UNIMPLEMENTED_CASE(JdkUnsafeGetAndSetObject /* (Ljava/lang/Object;JLjava/lang/Object;)Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(JdkUnsafeLoadFence /* ()V */)
- UNIMPLEMENTED_CASE(JdkUnsafeStoreFence /* ()V */)
- UNIMPLEMENTED_CASE(JdkUnsafeFullFence /* ()V */)
- UNIMPLEMENTED_CASE(ReferenceGetReferent /* ()Ljava/lang/Object; */)
- UNIMPLEMENTED_CASE(ReferenceRefersTo /* (Ljava/lang/Object;)Z */)
- UNIMPLEMENTED_CASE(IntegerValueOf /* (I)Ljava/lang/Integer; */)
- UNIMPLEMENTED_CASE(ThreadInterrupted /* ()Z */)
- UNIMPLEMENTED_CASE(CRC32Update /* (II)I */)
- UNIMPLEMENTED_CASE(CRC32UpdateBytes /* (I[BII)I */)
- UNIMPLEMENTED_CASE(CRC32UpdateByteBuffer /* (IJII)I */)
- UNIMPLEMENTED_CASE(FP16Compare /* (SS)I */)
- UNIMPLEMENTED_CASE(FP16ToFloat /* (S)F */)
- UNIMPLEMENTED_CASE(FP16ToHalf /* (F)S */)
- UNIMPLEMENTED_CASE(FP16Floor /* (S)S */)
- UNIMPLEMENTED_CASE(FP16Ceil /* (S)S */)
- UNIMPLEMENTED_CASE(FP16Rint /* (S)S */)
- UNIMPLEMENTED_CASE(FP16Greater /* (SS)Z */)
- UNIMPLEMENTED_CASE(FP16GreaterEquals /* (SS)Z */)
- UNIMPLEMENTED_CASE(FP16Less /* (SS)Z */)
- UNIMPLEMENTED_CASE(FP16LessEquals /* (SS)Z */)
- UNIMPLEMENTED_CASE(FP16Min /* (SS)S */)
- UNIMPLEMENTED_CASE(FP16Max /* (SS)S */)
- INTRINSIC_CASE(VarHandleFullFence)
- INTRINSIC_CASE(VarHandleAcquireFence)
- INTRINSIC_CASE(VarHandleReleaseFence)
- INTRINSIC_CASE(VarHandleLoadLoadFence)
- INTRINSIC_CASE(VarHandleStoreStoreFence)
- INTRINSIC_CASE(MethodHandleInvokeExact)
- INTRINSIC_CASE(MethodHandleInvoke)
- INTRINSIC_CASE(VarHandleCompareAndExchange)
- INTRINSIC_CASE(VarHandleCompareAndExchangeAcquire)
- INTRINSIC_CASE(VarHandleCompareAndExchangeRelease)
- INTRINSIC_CASE(VarHandleCompareAndSet)
- INTRINSIC_CASE(VarHandleGet)
- INTRINSIC_CASE(VarHandleGetAcquire)
- INTRINSIC_CASE(VarHandleGetAndAdd)
- INTRINSIC_CASE(VarHandleGetAndAddAcquire)
- INTRINSIC_CASE(VarHandleGetAndAddRelease)
- INTRINSIC_CASE(VarHandleGetAndBitwiseAnd)
- INTRINSIC_CASE(VarHandleGetAndBitwiseAndAcquire)
- INTRINSIC_CASE(VarHandleGetAndBitwiseAndRelease)
- INTRINSIC_CASE(VarHandleGetAndBitwiseOr)
- INTRINSIC_CASE(VarHandleGetAndBitwiseOrAcquire)
- INTRINSIC_CASE(VarHandleGetAndBitwiseOrRelease)
- INTRINSIC_CASE(VarHandleGetAndBitwiseXor)
- INTRINSIC_CASE(VarHandleGetAndBitwiseXorAcquire)
- INTRINSIC_CASE(VarHandleGetAndBitwiseXorRelease)
- INTRINSIC_CASE(VarHandleGetAndSet)
- INTRINSIC_CASE(VarHandleGetAndSetAcquire)
- INTRINSIC_CASE(VarHandleGetAndSetRelease)
- INTRINSIC_CASE(VarHandleGetOpaque)
- INTRINSIC_CASE(VarHandleGetVolatile)
- INTRINSIC_CASE(VarHandleSet)
- INTRINSIC_CASE(VarHandleSetOpaque)
- INTRINSIC_CASE(VarHandleSetRelease)
- INTRINSIC_CASE(VarHandleSetVolatile)
- INTRINSIC_CASE(VarHandleWeakCompareAndSet)
- INTRINSIC_CASE(VarHandleWeakCompareAndSetAcquire)
- INTRINSIC_CASE(VarHandleWeakCompareAndSetPlain)
- INTRINSIC_CASE(VarHandleWeakCompareAndSetRelease)
- case Intrinsics::kNone:
- res = false;
- break;
- // Note: no default case to ensure we catch any newly added intrinsics.
- }
- return res;
-}
-
-} // namespace interpreter
-} // namespace art
diff --git a/runtime/interpreter/interpreter_intrinsics.h b/runtime/interpreter/interpreter_intrinsics.h
deleted file mode 100644
index 2a23002..0000000
--- a/runtime/interpreter/interpreter_intrinsics.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2017 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_INTERPRETER_INTRINSICS_H_
-#define ART_RUNTIME_INTERPRETER_INTERPRETER_INTRINSICS_H_
-
-#include "jvalue.h"
-
-namespace art {
-
-class ArtMethod;
-class Instruction;
-class ShadowFrame;
-
-namespace interpreter {
-
-// Invokes to methods identified as intrinics are routed here. If there is
-// no interpreter implementation, return false and a normal invoke will proceed.
-bool MterpHandleIntrinsic(ShadowFrame* shadow_frame,
- ArtMethod* const called_method,
- const Instruction* inst,
- uint16_t inst_data,
- JValue* result_register);
-
-} // namespace interpreter
-} // namespace art
-
-#endif // ART_RUNTIME_INTERPRETER_INTERPRETER_INTRINSICS_H_
diff --git a/runtime/interpreter/interpreter_switch_impl-inl.h b/runtime/interpreter/interpreter_switch_impl-inl.h
index d95c507..215194e 100644
--- a/runtime/interpreter/interpreter_switch_impl-inl.h
+++ b/runtime/interpreter/interpreter_switch_impl-inl.h
@@ -144,7 +144,7 @@
if (!CheckForceReturn()) {
return false;
}
- if (UNLIKELY(Instrumentation()->HasDexPcListeners())) {
+ if (UNLIKELY(shadow_frame_.GetNotifyDexPcMoveEvents())) {
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;
@@ -353,7 +353,7 @@
template<InvokeType type, bool is_range>
HANDLER_ATTRIBUTES bool HandleInvoke() {
- bool success = DoInvoke<type, is_range, do_access_check, /*is_mterp=*/ false>(
+ bool success = DoInvoke<type, is_range, do_access_check>(
Self(), shadow_frame_, inst_, inst_data_, ResultRegister());
return PossiblyHandlePendingExceptionOnInvoke(!success);
}
@@ -1816,7 +1816,7 @@
#define OPCODE_CASE(OPCODE, OPCODE_NAME, NAME, FORMAT, i, a, e, v) \
template<bool do_access_check, bool transaction_active> \
-ASAN_NO_INLINE static bool OP_##OPCODE_NAME( \
+ASAN_NO_INLINE NO_STACK_PROTECTOR static bool OP_##OPCODE_NAME( \
SwitchImplContext* ctx, \
const instrumentation::Instrumentation* instrumentation, \
Thread* self, \
@@ -1834,6 +1834,7 @@
#undef OPCODE_CASE
template<bool do_access_check, bool transaction_active>
+NO_STACK_PROTECTOR
void ExecuteSwitchImplCpp(SwitchImplContext* ctx) {
Thread* self = ctx->self;
const CodeItemDataAccessor& accessor = ctx->accessor;
diff --git a/runtime/interpreter/mterp/arm64ng/main.S b/runtime/interpreter/mterp/arm64ng/main.S
index 89de81f..81d6b7b 100644
--- a/runtime/interpreter/mterp/arm64ng/main.S
+++ b/runtime/interpreter/mterp/arm64ng/main.S
@@ -1590,6 +1590,14 @@
* rest method parameters
*/
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+ ldr wip, [x0, ART_METHOD_DECLARING_CLASS_OFFSET]
+ ldrb wip, [ip, MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET]
+ cmp ip, #MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE
+ bcs ExecuteNterpImpl
+ b art_quick_resolution_trampoline
+EndExecuteNterpWithClinitImpl:
+
OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
.cfi_startproc
sub x16, sp, #STACK_OVERFLOW_RESERVED_BYTES
diff --git a/runtime/interpreter/mterp/armng/main.S b/runtime/interpreter/mterp/armng/main.S
index 310a3fd..f89db40 100644
--- a/runtime/interpreter/mterp/armng/main.S
+++ b/runtime/interpreter/mterp/armng/main.S
@@ -1608,6 +1608,14 @@
* rest method parameters
*/
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+ ldr ip, [r0, ART_METHOD_DECLARING_CLASS_OFFSET]
+ ldrb ip, [ip, MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET]
+ cmp ip, #MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE
+ bcs ExecuteNterpImpl
+ b art_quick_resolution_trampoline
+EndExecuteNterpWithClinitImpl:
+
OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
.cfi_startproc
sub ip, sp, #STACK_OVERFLOW_RESERVED_BYTES
diff --git a/runtime/interpreter/mterp/nterp.cc b/runtime/interpreter/mterp/nterp.cc
index d70a846..ef916a9 100644
--- a/runtime/interpreter/mterp/nterp.cc
+++ b/runtime/interpreter/mterp/nterp.cc
@@ -26,7 +26,6 @@
#include "entrypoints/entrypoint_utils-inl.h"
#include "interpreter/interpreter_cache-inl.h"
#include "interpreter/interpreter_common.h"
-#include "interpreter/interpreter_intrinsics.h"
#include "interpreter/shadow_frame-inl.h"
#include "mirror/string-alloc-inl.h"
#include "nterp_helpers.h"
@@ -35,7 +34,7 @@
namespace interpreter {
bool IsNterpSupported() {
- return !kPoisonHeapReferences && kUseReadBarrier;
+ return !kPoisonHeapReferences;
}
bool CanRuntimeUseNterp() REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -58,10 +57,17 @@
// The entrypoint for nterp, which ArtMethods can directly point to.
extern "C" void ExecuteNterpImpl() REQUIRES_SHARED(Locks::mutator_lock_);
+// Another entrypoint, which does a clinit check at entry.
+extern "C" void ExecuteNterpWithClinitImpl() REQUIRES_SHARED(Locks::mutator_lock_);
+
const void* GetNterpEntryPoint() {
return reinterpret_cast<const void*>(interpreter::ExecuteNterpImpl);
}
+const void* GetNterpWithClinitEntryPoint() {
+ return reinterpret_cast<const void*>(interpreter::ExecuteNterpWithClinitImpl);
+}
+
/*
* Verify some constants used by the nterp interpreter.
*/
@@ -89,13 +95,12 @@
}
template<typename T>
-inline void UpdateCache(Thread* self, uint16_t* dex_pc_ptr, T value) {
- DCHECK(kUseReadBarrier) << "Nterp only works with read barriers";
+inline void UpdateCache(Thread* self, const uint16_t* dex_pc_ptr, T value) {
self->GetInterpreterCache()->Set(self, dex_pc_ptr, value);
}
template<typename T>
-inline void UpdateCache(Thread* self, uint16_t* dex_pc_ptr, T* value) {
+inline void UpdateCache(Thread* self, const uint16_t* dex_pc_ptr, T* value) {
UpdateCache(self, dex_pc_ptr, reinterpret_cast<size_t>(value));
}
@@ -245,7 +250,7 @@
}
FLATTEN
-extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, uint16_t* dex_pc_ptr)
+extern "C" size_t NterpGetMethod(Thread* self, ArtMethod* caller, const uint16_t* dex_pc_ptr)
REQUIRES_SHARED(Locks::mutator_lock_) {
UpdateHotness(caller);
const Instruction* inst = Instruction::At(dex_pc_ptr);
@@ -432,13 +437,14 @@
const Instruction* inst = Instruction::At(dex_pc_ptr);
uint16_t field_index = inst->VRegB_21c();
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ bool is_put = IsInstructionSPut(inst->Opcode());
ArtField* resolved_field = ResolveFieldWithAccessChecks(
self,
class_linker,
field_index,
caller,
/* is_static */ true,
- /* is_put */ IsInstructionSPut(inst->Opcode()),
+ is_put,
resolve_field_type);
if (resolved_field == nullptr) {
@@ -461,7 +467,16 @@
// check for it.
return reinterpret_cast<size_t>(resolved_field) | 1;
} else {
- UpdateCache(self, dex_pc_ptr, resolved_field);
+ // Try to resolve the field type even if we were not requested to. Only if
+ // the field type is successfully resolved can we update the cache. If we
+ // fail to resolve the type, we clear the exception to keep interpreter
+ // semantics of not throwing when null is stored.
+ if (is_put && resolve_field_type == 0 && resolved_field->ResolveType() == nullptr) {
+ DCHECK(self->IsExceptionPending());
+ self->ClearException();
+ } else {
+ UpdateCache(self, dex_pc_ptr, resolved_field);
+ }
return reinterpret_cast<size_t>(resolved_field);
}
}
@@ -475,13 +490,14 @@
const Instruction* inst = Instruction::At(dex_pc_ptr);
uint16_t field_index = inst->VRegC_22c();
ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ bool is_put = IsInstructionIPut(inst->Opcode());
ArtField* resolved_field = ResolveFieldWithAccessChecks(
self,
class_linker,
field_index,
caller,
/* is_static */ false,
- /* is_put */ IsInstructionIPut(inst->Opcode()),
+ is_put,
resolve_field_type);
if (resolved_field == nullptr) {
DCHECK(self->IsExceptionPending());
@@ -492,7 +508,16 @@
// of volatile.
return -resolved_field->GetOffset().Uint32Value();
}
- UpdateCache(self, dex_pc_ptr, resolved_field->GetOffset().Uint32Value());
+ // Try to resolve the field type even if we were not requested to. Only if
+ // the field type is successfully resolved can we update the cache. If we
+ // fail to resolve the type, we clear the exception to keep interpreter
+ // semantics of not throwing when null is stored.
+ if (is_put && resolve_field_type == 0 && resolved_field->ResolveType() == nullptr) {
+ DCHECK(self->IsExceptionPending());
+ self->ClearException();
+ } else {
+ UpdateCache(self, dex_pc_ptr, resolved_field->GetOffset().Uint32Value());
+ }
return resolved_field->GetOffset().Uint32Value();
}
diff --git a/runtime/interpreter/mterp/nterp.h b/runtime/interpreter/mterp/nterp.h
index 1590b28..4d5af39 100644
--- a/runtime/interpreter/mterp/nterp.h
+++ b/runtime/interpreter/mterp/nterp.h
@@ -32,6 +32,7 @@
bool IsNterpSupported();
bool CanRuntimeUseNterp();
const void* GetNterpEntryPoint();
+const void* GetNterpWithClinitEntryPoint();
constexpr uint16_t kNterpHotnessValue = 0;
diff --git a/runtime/interpreter/mterp/x86_64ng/main.S b/runtime/interpreter/mterp/x86_64ng/main.S
index bd191c0..3e476db 100644
--- a/runtime/interpreter/mterp/x86_64ng/main.S
+++ b/runtime/interpreter/mterp/x86_64ng/main.S
@@ -1694,6 +1694,13 @@
* rest method parameters
*/
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+ movl ART_METHOD_DECLARING_CLASS_OFFSET(%rdi), %r10d
+ cmpb $$(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE), MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET(%r10d)
+ jae ExecuteNterpImpl
+ jmp art_quick_resolution_trampoline
+EndExecuteNterpWithClinitImpl:
+
OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
.cfi_startproc
.cfi_def_cfa rsp, 8
diff --git a/runtime/interpreter/mterp/x86ng/main.S b/runtime/interpreter/mterp/x86ng/main.S
index db8519b..7872520 100644
--- a/runtime/interpreter/mterp/x86ng/main.S
+++ b/runtime/interpreter/mterp/x86ng/main.S
@@ -1757,6 +1757,15 @@
* rest method parameters
*/
+OAT_ENTRY ExecuteNterpWithClinitImpl, EndExecuteNterpWithClinitImpl
+ push %esi
+ movl ART_METHOD_DECLARING_CLASS_OFFSET(%eax), %esi
+ cmpb $$(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE), MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET(%esi)
+ pop %esi
+ jae ExecuteNterpImpl
+ jmp art_quick_resolution_trampoline
+EndExecuteNterpWithClinitImpl:
+
OAT_ENTRY ExecuteNterpImpl, EndExecuteNterpImpl
.cfi_startproc
.cfi_def_cfa esp, 4
diff --git a/runtime/interpreter/shadow_frame.h b/runtime/interpreter/shadow_frame.h
index 8cb2b33..be93dfa 100644
--- a/runtime/interpreter/shadow_frame.h
+++ b/runtime/interpreter/shadow_frame.h
@@ -54,7 +54,7 @@
// We have been requested to notify when this frame gets popped.
kNotifyFramePop = 1 << 0,
// We have been asked to pop this frame off the stack as soon as possible.
- kForcePopFrame = 1 << 1,
+ kForcePopFrame = 1 << 1,
// We have been asked to re-execute the last instruction.
kForceRetryInst = 1 << 2,
// Mark that we expect the next frame to retry the last instruction (used by instrumentation and
@@ -62,6 +62,9 @@
kSkipMethodExitEvents = 1 << 3,
// Used to suppress exception events caused by other instrumentation events.
kSkipNextExceptionEvent = 1 << 4,
+ // Used to specify if DexPCMoveEvents have to be reported. These events will
+ // only be reported if the method has a breakpoint set.
+ kNotifyDexPcMoveEvents = 1 << 5,
};
public:
@@ -169,14 +172,14 @@
int64_t GetVRegLong(size_t i) const {
DCHECK_LT(i + 1, NumberOfVRegs());
const uint32_t* vreg = &vregs_[i];
- typedef const int64_t unaligned_int64 __attribute__ ((aligned (4)));
+ using unaligned_int64 __attribute__((aligned(4))) = const int64_t;
return *reinterpret_cast<unaligned_int64*>(vreg);
}
double GetVRegDouble(size_t i) const {
DCHECK_LT(i + 1, NumberOfVRegs());
const uint32_t* vreg = &vregs_[i];
- typedef const double unaligned_double __attribute__ ((aligned (4)));
+ using unaligned_double __attribute__((aligned(4))) = const double;
return *reinterpret_cast<unaligned_double*>(vreg);
}
@@ -221,7 +224,7 @@
void SetVRegLong(size_t i, int64_t val) {
DCHECK_LT(i + 1, NumberOfVRegs());
uint32_t* vreg = &vregs_[i];
- typedef int64_t unaligned_int64 __attribute__ ((aligned (4)));
+ using unaligned_int64 __attribute__((aligned(4))) = int64_t;
*reinterpret_cast<unaligned_int64*>(vreg) = val;
// This is needed for moving collectors since these can update the vreg references if they
// happen to agree with references in the reference array.
@@ -232,7 +235,7 @@
void SetVRegDouble(size_t i, double val) {
DCHECK_LT(i + 1, NumberOfVRegs());
uint32_t* vreg = &vregs_[i];
- typedef double unaligned_double __attribute__ ((aligned (4)));
+ using unaligned_double __attribute__((aligned(4))) = double;
*reinterpret_cast<unaligned_double*>(vreg) = val;
// This is needed for moving collectors since these can update the vreg references if they
// happen to agree with references in the reference array.
@@ -373,6 +376,14 @@
UpdateFrameFlag(enable, FrameFlags::kSkipNextExceptionEvent);
}
+ bool GetNotifyDexPcMoveEvents() const {
+ return GetFrameFlag(FrameFlags::kNotifyDexPcMoveEvents);
+ }
+
+ void SetNotifyDexPcMoveEvents(bool enable) {
+ UpdateFrameFlag(enable, FrameFlags::kNotifyDexPcMoveEvents);
+ }
+
void CheckConsistentVRegs() const {
if (kIsDebugBuild) {
// A shadow frame visible to GC requires the following rule: for a given vreg,
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 62051ee..4d3e030 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -1557,7 +1557,7 @@
mirror::Object* new_value = shadow_frame->GetVRegReference(arg_offset + 5);
// Must use non transactional mode.
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
// Need to make sure the reference stored in the field is a to-space one before attempting the
// CAS or the CAS could fail incorrectly.
mirror::HeapReference<mirror::Object>* field_addr =
@@ -2163,6 +2163,7 @@
uint32_t* args,
JValue* result);
+// NOLINTNEXTLINE
#define ONE_PLUS(ShortNameIgnored, DescriptorIgnored, NameIgnored, SignatureIgnored) 1 +
static constexpr size_t kInvokeHandlersSize = UNSTARTED_RUNTIME_DIRECT_LIST(ONE_PLUS) 0;
static constexpr size_t kJniHandlersSize = UNSTARTED_RUNTIME_JNI_LIST(ONE_PLUS) 0;
diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc
index 75a692e..70948b8 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -420,11 +420,13 @@
shadow_frame->SetVRegReference(0, reference_empty_string.Get());
shadow_frame->SetVRegReference(1, string_arg.Get());
- interpreter::DoCall<false, false>(method,
+ ArtMethod* factory = WellKnownClasses::StringInitToStringFactory(method);
+ interpreter::DoCall<false, false>(factory,
self,
*shadow_frame,
Instruction::At(inst_data),
inst_data[0],
+ /* string_init= */ true,
&result);
ObjPtr<mirror::String> string_result = down_cast<mirror::String*>(result.GetL());
EXPECT_EQ(string_arg->GetLength(), string_result->GetLength());
@@ -1024,6 +1026,7 @@
*shadow_frame,
Instruction::At(inst_data),
inst_data[0],
+ /* string_init= */ false,
&result);
ObjPtr<mirror::String> string_result = down_cast<mirror::String*>(result.GetL());
ASSERT_TRUE(string_result != nullptr);
@@ -1179,6 +1182,7 @@
*shadow_frame,
Instruction::At(inst_data),
inst_data[0],
+ /* string_init= */ false,
&result);
CHECK(!self->IsExceptionPending());
}
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 6d634ae..239f207 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -208,7 +208,6 @@
// Jit GC for now (b/147208992).
if (code_cache->GetGarbageCollectCode()) {
code_cache->SetGarbageCollectCode(!jit_compiler_->GenerateDebugInfo() &&
- !Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled() &&
!jit->JitAtFirstUse());
}
@@ -259,10 +258,14 @@
return true;
}
-bool Jit::CompileMethod(ArtMethod* method,
- Thread* self,
- CompilationKind compilation_kind,
- bool prejit) {
+bool Jit::CompileMethodInternal(ArtMethod* method,
+ Thread* self,
+ CompilationKind compilation_kind,
+ bool prejit) {
+ if (kIsDebugBuild) {
+ MutexLock mu(self, *Locks::jit_lock_);
+ CHECK(GetCodeCache()->IsMethodBeingCompiled(method, compilation_kind));
+ }
DCHECK(Runtime::Current()->UseJitCompilation());
DCHECK(!method->IsRuntimeMethod());
@@ -279,9 +282,8 @@
compilation_kind = CompilationKind::kOptimized;
}
- RuntimeCallbacks* cb = Runtime::Current()->GetRuntimeCallbacks();
// Don't compile the method if it has breakpoints.
- if (cb->IsMethodBeingInspected(method)) {
+ if (Runtime::Current()->GetInstrumentation()->IsDeoptimized(method)) {
VLOG(jit) << "JIT not compiling " << method->PrettyMethod()
<< " due to not being safe to jit according to runtime-callbacks. For example, there"
<< " could be breakpoints in this method.";
@@ -323,7 +325,7 @@
<< ArtMethod::PrettyMethod(method_to_compile)
<< " kind=" << compilation_kind;
bool success = jit_compiler_->CompileMethod(self, region, method_to_compile, compilation_kind);
- code_cache_->DoneCompiling(method_to_compile, self, compilation_kind);
+ code_cache_->DoneCompiling(method_to_compile, self);
if (!success) {
VLOG(jit) << "Failed to compile method "
<< ArtMethod::PrettyMethod(method_to_compile)
@@ -568,12 +570,11 @@
// Before allowing the jump, make sure no code is actively inspecting the method to avoid
// jumping from interpreter to OSR while e.g. single stepping. Note that we could selectively
// disable OSR when single stepping, but that's currently hard to know at this point.
- if (Runtime::Current()->GetInstrumentation()->InterpreterStubsInstalled() ||
- Runtime::Current()->GetInstrumentation()->IsDeoptimized(method) ||
- thread->IsForceInterpreter() ||
- method->GetDeclaringClass()->IsObsoleteObject() ||
- Dbg::IsForcedInterpreterNeededForUpcall(thread, method) ||
- Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method)) {
+ // Currently, HaveLocalsChanged is not frame specific. It is possible to make it frame specific
+ // to allow OSR of frames that don't have any locals changed but it isn't worth the additional
+ // complexity.
+ if (Runtime::Current()->GetInstrumentation()->NeedsSlowInterpreterForMethod(thread, method) ||
+ Runtime::Current()->GetRuntimeCallbacks()->HaveLocalsChanged()) {
return false;
}
@@ -748,6 +749,51 @@
child_mapping_methods.Reset();
}
+class ScopedCompilation {
+ public:
+ ScopedCompilation(ScopedCompilation&& other) noexcept :
+ jit_(other.jit_),
+ method_(other.method_),
+ compilation_kind_(other.compilation_kind_),
+ owns_compilation_(other.owns_compilation_) {
+ other.owns_compilation_ = false;
+ }
+
+ ScopedCompilation(Jit* jit, ArtMethod* method, CompilationKind compilation_kind)
+ : jit_(jit),
+ method_(method),
+ compilation_kind_(compilation_kind),
+ owns_compilation_(true) {
+ MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+ // We don't want to enqueue any new tasks when thread pool has stopped. This simplifies
+ // the implementation of redefinition feature in jvmti.
+ if (jit_->GetThreadPool() == nullptr ||
+ !jit_->GetThreadPool()->HasStarted(Thread::Current()) ||
+ jit_->GetCodeCache()->IsMethodBeingCompiled(method_, compilation_kind_)) {
+ owns_compilation_ = false;
+ return;
+ }
+ jit_->GetCodeCache()->AddMethodBeingCompiled(method_, compilation_kind_);
+ }
+
+ bool OwnsCompilation() const {
+ return owns_compilation_;
+ }
+
+ ~ScopedCompilation() {
+ if (owns_compilation_) {
+ MutexLock mu(Thread::Current(), *Locks::jit_lock_);
+ jit_->GetCodeCache()->RemoveMethodBeingCompiled(method_, compilation_kind_);
+ }
+ }
+
+ private:
+ Jit* const jit_;
+ ArtMethod* const method_;
+ const CompilationKind compilation_kind_;
+ bool owns_compilation_;
+};
+
class JitCompileTask final : public Task {
public:
enum class TaskKind {
@@ -755,25 +801,16 @@
kPreCompile,
};
- JitCompileTask(ArtMethod* method, TaskKind task_kind, CompilationKind compilation_kind)
- : method_(method), kind_(task_kind), compilation_kind_(compilation_kind), klass_(nullptr) {
- ScopedObjectAccess soa(Thread::Current());
- // For a non-bootclasspath class, add a global ref to the class to prevent class unloading
- // until compilation is done.
- // When we precompile, this is either with boot classpath methods, or main
- // class loader methods, so we don't need to keep a global reference.
- if (method->GetDeclaringClass()->GetClassLoader() != nullptr &&
- kind_ != TaskKind::kPreCompile) {
- klass_ = soa.Vm()->AddGlobalRef(soa.Self(), method_->GetDeclaringClass());
- CHECK(klass_ != nullptr);
- }
- }
-
- ~JitCompileTask() {
- if (klass_ != nullptr) {
- ScopedObjectAccess soa(Thread::Current());
- soa.Vm()->DeleteGlobalRef(soa.Self(), klass_);
- }
+ JitCompileTask(ArtMethod* method,
+ TaskKind task_kind,
+ CompilationKind compilation_kind,
+ ScopedCompilation&& sc)
+ : method_(method),
+ kind_(task_kind),
+ compilation_kind_(compilation_kind),
+ scoped_compilation_(std::move(sc)) {
+ DCHECK(scoped_compilation_.OwnsCompilation());
+ DCHECK(!sc.OwnsCompilation());
}
void Run(Thread* self) override {
@@ -782,7 +819,7 @@
switch (kind_) {
case TaskKind::kCompile:
case TaskKind::kPreCompile: {
- Runtime::Current()->GetJit()->CompileMethod(
+ Runtime::Current()->GetJit()->CompileMethodInternal(
method_,
self,
compilation_kind_,
@@ -802,7 +839,7 @@
ArtMethod* const method_;
const TaskKind kind_;
const CompilationKind compilation_kind_;
- jobject klass_;
+ ScopedCompilation scoped_compilation_;
DISALLOW_IMPLICIT_CONSTRUCTORS(JitCompileTask);
};
@@ -1290,6 +1327,21 @@
}
}
+void Jit::AddCompileTask(Thread* self,
+ ArtMethod* method,
+ CompilationKind compilation_kind,
+ bool precompile) {
+ ScopedCompilation sc(this, method, compilation_kind);
+ if (!sc.OwnsCompilation()) {
+ return;
+ }
+ JitCompileTask::TaskKind task_kind = precompile
+ ? JitCompileTask::TaskKind::kPreCompile
+ : JitCompileTask::TaskKind::kCompile;
+ thread_pool_->AddTask(
+ self, new JitCompileTask(method, task_kind, compilation_kind, std::move(sc)));
+}
+
bool Jit::CompileMethodFromProfile(Thread* self,
ClassLinker* class_linker,
uint32_t method_idx,
@@ -1310,21 +1362,27 @@
// Already seen by another profile.
return false;
}
+ CompilationKind compilation_kind = CompilationKind::kOptimized;
const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
if (class_linker->IsQuickToInterpreterBridge(entry_point) ||
class_linker->IsQuickGenericJniStub(entry_point) ||
- (entry_point == interpreter::GetNterpEntryPoint()) ||
- // We explicitly check for the stub. The trampoline is for methods backed by
- // a .oat file that has a compiled version of the method.
+ class_linker->IsNterpEntryPoint(entry_point) ||
+ // We explicitly check for the resolution stub, and not the resolution trampoline.
+ // The trampoline is for methods backed by a .oat file that has a compiled version of
+ // the method.
(entry_point == GetQuickResolutionStub())) {
VLOG(jit) << "JIT Zygote processing method " << ArtMethod::PrettyMethod(method)
<< " from profile";
method->SetPreCompiled();
+ ScopedCompilation sc(this, method, compilation_kind);
+ if (!sc.OwnsCompilation()) {
+ return false;
+ }
if (!add_to_queue) {
- CompileMethod(method, self, CompilationKind::kOptimized, /* prejit= */ true);
+ CompileMethodInternal(method, self, compilation_kind, /* prejit= */ true);
} else {
Task* task = new JitCompileTask(
- method, JitCompileTask::TaskKind::kPreCompile, CompilationKind::kOptimized);
+ method, JitCompileTask::TaskKind::kPreCompile, compilation_kind, std::move(sc));
if (compile_after_boot) {
AddPostBootTask(self, task);
} else {
@@ -1475,11 +1533,7 @@
// hotness threshold. If we're not only using the baseline compiler, enqueue a compilation
// task that will compile optimize the method.
if (!options_->UseBaselineCompiler()) {
- thread_pool_->AddTask(
- self,
- new JitCompileTask(method,
- JitCompileTask::TaskKind::kCompile,
- CompilationKind::kOptimized));
+ AddCompileTask(self, method, CompilationKind::kOptimized);
}
}
@@ -1499,23 +1553,17 @@
bool was_runtime_thread_;
};
-void Jit::MethodEntered(Thread* thread, ArtMethod* method) {
+void Jit::MethodEntered(Thread* self, ArtMethod* method) {
Runtime* runtime = Runtime::Current();
if (UNLIKELY(runtime->UseJitCompilation() && JitAtFirstUse())) {
ArtMethod* np_method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
if (np_method->IsCompilable()) {
- // TODO(ngeoffray): For JIT at first use, use kPreCompile. Currently we don't due to
- // conflicts with jitzygote optimizations.
- JitCompileTask compile_task(
- method, JitCompileTask::TaskKind::kCompile, CompilationKind::kOptimized);
- // Fake being in a runtime thread so that class-load behavior will be the same as normal jit.
- ScopedSetRuntimeThread ssrt(thread);
- compile_task.Run(thread);
+ CompileMethod(method, self, CompilationKind::kOptimized, /* prejit= */ false);
}
return;
}
- AddSamples(thread, method);
+ AddSamples(self, method);
}
void Jit::WaitForCompilationToFinish(Thread* self) {
@@ -1620,7 +1668,6 @@
// Jit GC for now (b/147208992).
code_cache_->SetGarbageCollectCode(
!jit_compiler_->GenerateDebugInfo() &&
- !runtime->GetInstrumentation()->AreExitStubsInstalled() &&
!JitAtFirstUse());
if (is_system_server && runtime->HasImageWithProfile()) {
@@ -1745,9 +1792,7 @@
if (!method->IsNative() && !code_cache_->IsOsrCompiled(method)) {
// If we already have compiled code for it, nterp may be stuck in a loop.
// Compile OSR.
- thread_pool_->AddTask(
- self,
- new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kOsr));
+ AddCompileTask(self, method, CompilationKind::kOsr);
}
return;
}
@@ -1764,7 +1809,7 @@
return;
}
- static constexpr size_t kIndividualSharedMethodHotnessThreshold = 0xff;
+ static constexpr size_t kIndividualSharedMethodHotnessThreshold = 0x3f;
if (method->IsMemorySharedMethod()) {
MutexLock mu(self, lock_);
auto it = shared_method_counters_.find(method);
@@ -1781,17 +1826,27 @@
}
if (!method->IsNative() && GetCodeCache()->CanAllocateProfilingInfo()) {
- thread_pool_->AddTask(
- self,
- new JitCompileTask(method, JitCompileTask::TaskKind::kCompile, CompilationKind::kBaseline));
+ AddCompileTask(self, method, CompilationKind::kBaseline);
} else {
- thread_pool_->AddTask(
- self,
- new JitCompileTask(method,
- JitCompileTask::TaskKind::kCompile,
- CompilationKind::kOptimized));
+ AddCompileTask(self, method, CompilationKind::kOptimized);
}
}
+bool Jit::CompileMethod(ArtMethod* method,
+ Thread* self,
+ CompilationKind compilation_kind,
+ bool prejit) {
+ ScopedCompilation sc(this, method, compilation_kind);
+ // TODO: all current users of this method expect us to wait if it is being compiled.
+ if (!sc.OwnsCompilation()) {
+ return false;
+ }
+ // Fake being in a runtime thread so that class-load behavior will be the same as normal jit.
+ ScopedSetRuntimeThread ssrt(self);
+ // TODO(ngeoffray): For JIT at first use, use kPreCompile. Currently we don't due to
+ // conflicts with jitzygote optimizations.
+ return CompileMethodInternal(method, self, compilation_kind, prejit);
+}
+
} // namespace jit
} // namespace art
diff --git a/runtime/jit/jit.h b/runtime/jit/jit.h
index b439c8e..fd92451 100644
--- a/runtime/jit/jit.h
+++ b/runtime/jit/jit.h
@@ -53,6 +53,7 @@
namespace jit {
class JitCodeCache;
+class JitCompileTask;
class JitMemoryRegion;
class JitOptions;
@@ -461,6 +462,17 @@
static bool BindCompilerMethods(std::string* error_msg);
+ void AddCompileTask(Thread* self,
+ ArtMethod* method,
+ CompilationKind compilation_kind,
+ bool precompile = false);
+
+ bool CompileMethodInternal(ArtMethod* method,
+ Thread* self,
+ CompilationKind compilation_kind,
+ bool prejit)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
// JIT compiler
static void* jit_library_handle_;
static JitCompilerInterface* jit_compiler_;
@@ -507,6 +519,8 @@
// between the zygote and apps.
std::map<ArtMethod*, uint16_t> shared_method_counters_;
+ friend class art::jit::JitCompileTask;
+
DISALLOW_COPY_AND_ASSIGN(Jit);
};
diff --git a/runtime/jit/jit_code_cache.cc b/runtime/jit/jit_code_cache.cc
index 0b34688..39f165d 100644
--- a/runtime/jit/jit_code_cache.cc
+++ b/runtime/jit/jit_code_cache.cc
@@ -40,7 +40,7 @@
#include "entrypoints/entrypoint_utils-inl.h"
#include "entrypoints/runtime_asm_entrypoints.h"
#include "gc/accounting/bitmap-inl.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
#include "gc/scoped_gc_critical_section.h"
#include "handle.h"
#include "handle_scope-inl.h"
@@ -422,7 +422,6 @@
// TODO: Do not use IsMarked for j.l.Class, and adjust once we move this method
// out of the weak access/creation pause. b/32167580
if (new_object != nullptr && new_object != object) {
- DCHECK(new_object->IsString());
roots[i] = GcRoot<mirror::Object>(new_object);
}
} else {
@@ -560,7 +559,7 @@
}
bool JitCodeCache::IsWeakAccessEnabled(Thread* self) const {
- return kUseReadBarrier
+ return gUseReadBarrier
? self->GetWeakRefAccessEnabled()
: is_weak_access_enabled_.load(std::memory_order_seq_cst);
}
@@ -583,13 +582,13 @@
}
void JitCodeCache::AllowInlineCacheAccess() {
- DCHECK(!kUseReadBarrier);
+ DCHECK(!gUseReadBarrier);
is_weak_access_enabled_.store(true, std::memory_order_seq_cst);
BroadcastForInlineCacheAccess();
}
void JitCodeCache::DisallowInlineCacheAccess() {
- DCHECK(!kUseReadBarrier);
+ DCHECK(!gUseReadBarrier);
is_weak_access_enabled_.store(false, std::memory_order_seq_cst);
}
@@ -1594,10 +1593,35 @@
return osr_code_map_.find(method) != osr_code_map_.end();
}
+void JitCodeCache::VisitRoots(RootVisitor* visitor) {
+ Thread* self = Thread::Current();
+ gc::Heap* const heap = Runtime::Current()->GetHeap();
+ if (heap->CurrentCollectorType() != gc::CollectorType::kCollectorTypeCMC
+ || !heap->MarkCompactCollector()->IsCompacting(self)) {
+ MutexLock mu(self, *Locks::jit_lock_);
+ UnbufferedRootVisitor root_visitor(visitor, RootInfo(kRootStickyClass));
+ for (ArtMethod* method : current_optimized_compilations_) {
+ method->VisitRoots(root_visitor, kRuntimePointerSize);
+ }
+ for (ArtMethod* method : current_baseline_compilations_) {
+ method->VisitRoots(root_visitor, kRuntimePointerSize);
+ }
+ for (ArtMethod* method : current_osr_compilations_) {
+ method->VisitRoots(root_visitor, kRuntimePointerSize);
+ }
+ }
+}
+
bool JitCodeCache::NotifyCompilationOf(ArtMethod* method,
Thread* self,
CompilationKind compilation_kind,
bool prejit) {
+ if (kIsDebugBuild) {
+ MutexLock mu(self, *Locks::jit_lock_);
+ // Note: the compilation kind may have been adjusted after what was passed initially.
+ // We really just want to check that the method is indeed being compiled.
+ CHECK(IsMethodBeingCompiled(method));
+ }
const void* existing_entry_point = method->GetEntryPointFromQuickCompiledCode();
if (compilation_kind != CompilationKind::kOsr && ContainsPc(existing_entry_point)) {
OatQuickMethodHeader* method_header =
@@ -1686,13 +1710,8 @@
}
}
}
- MutexLock mu(self, *Locks::jit_lock_);
- if (IsMethodBeingCompiled(method, compilation_kind)) {
- return false;
- }
- AddMethodBeingCompiled(method, compilation_kind);
- return true;
}
+ return true;
}
ProfilingInfo* JitCodeCache::NotifyCompilerUse(ArtMethod* method, Thread* self) {
@@ -1715,9 +1734,7 @@
it->second->DecrementInlineUse();
}
-void JitCodeCache::DoneCompiling(ArtMethod* method,
- Thread* self,
- CompilationKind compilation_kind) {
+void JitCodeCache::DoneCompiling(ArtMethod* method, Thread* self) {
DCHECK_EQ(Thread::Current(), self);
MutexLock mu(self, *Locks::jit_lock_);
if (UNLIKELY(method->IsNative())) {
@@ -1729,8 +1746,6 @@
// Failed to compile; the JNI compiler never fails, but the cache may be full.
jni_stubs_map_.erase(it); // Remove the entry added in NotifyCompilationOf().
} // else Commit() updated entrypoints of all methods in the JniStubData.
- } else {
- RemoveMethodBeingCompiled(method, compilation_kind);
}
}
diff --git a/runtime/jit/jit_code_cache.h b/runtime/jit/jit_code_cache.h
index fb861a4..a534ba9 100644
--- a/runtime/jit/jit_code_cache.h
+++ b/runtime/jit/jit_code_cache.h
@@ -215,7 +215,7 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::jit_lock_);
- void DoneCompiling(ArtMethod* method, Thread* self, CompilationKind compilation_kind)
+ void DoneCompiling(ArtMethod* method, Thread* self)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::jit_lock_);
@@ -403,6 +403,20 @@
ProfilingInfo* GetProfilingInfo(ArtMethod* method, Thread* self);
void ResetHotnessCounter(ArtMethod* method, Thread* self);
+ void VisitRoots(RootVisitor* visitor);
+
+ // Return whether `method` is being compiled with the given mode.
+ bool IsMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
+ REQUIRES(Locks::jit_lock_);
+
+ // Remove `method` from the list of methods meing compiled with the given mode.
+ void RemoveMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
+ REQUIRES(Locks::jit_lock_);
+
+ // Record that `method` is being compiled with the given mode.
+ void AddMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
+ REQUIRES(Locks::jit_lock_);
+
private:
JitCodeCache();
@@ -492,18 +506,6 @@
REQUIRES(!Locks::jit_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Record that `method` is being compiled with the given mode.
- void AddMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
- REQUIRES(Locks::jit_lock_);
-
- // Remove `method` from the list of methods meing compiled with the given mode.
- void RemoveMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
- REQUIRES(Locks::jit_lock_);
-
- // Return whether `method` is being compiled with the given mode.
- bool IsMethodBeingCompiled(ArtMethod* method, CompilationKind compilation_kind)
- REQUIRES(Locks::jit_lock_);
-
// Return whether `method` is being compiled in any mode.
bool IsMethodBeingCompiled(ArtMethod* method) REQUIRES(Locks::jit_lock_);
diff --git a/runtime/jit/jit_memory_region.cc b/runtime/jit/jit_memory_region.cc
index 56407f5..3f43aca 100644
--- a/runtime/jit/jit_memory_region.cc
+++ b/runtime/jit/jit_memory_region.cc
@@ -27,7 +27,7 @@
#include "base/membarrier.h"
#include "base/memfd.h"
#include "base/systrace.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
#include "jit/jit_scoped_code_cache_write.h"
#include "oat_quick_method_header.h"
#include "palette/palette.h"
diff --git a/runtime/jni/java_vm_ext-inl.h b/runtime/jni/java_vm_ext-inl.h
index 29cdf1b..c98a553 100644
--- a/runtime/jni/java_vm_ext-inl.h
+++ b/runtime/jni/java_vm_ext-inl.h
@@ -26,7 +26,7 @@
inline bool JavaVMExt::MayAccessWeakGlobals(Thread* self) const {
DCHECK(self != nullptr);
- return kUseReadBarrier
+ return gUseReadBarrier
? self->GetWeakRefAccessEnabled()
: allow_accessing_weak_globals_.load(std::memory_order_seq_cst);
}
diff --git a/runtime/jni/java_vm_ext.cc b/runtime/jni/java_vm_ext.cc
index f41b6c0..39d5729 100644
--- a/runtime/jni/java_vm_ext.cc
+++ b/runtime/jni/java_vm_ext.cc
@@ -729,8 +729,8 @@
MutexLock mu(self, *Locks::jni_weak_globals_lock_);
// CMS needs this to block for concurrent reference processing because an object allocated during
// the GC won't be marked and concurrent reference processing would incorrectly clear the JNI weak
- // ref. But CC (kUseReadBarrier == true) doesn't because of the to-space invariant.
- if (!kUseReadBarrier) {
+ // ref. But CC (gUseReadBarrier == true) doesn't because of the to-space invariant.
+ if (!gUseReadBarrier) {
WaitForWeakGlobalsAccess(self);
}
std::string error_msg;
@@ -809,7 +809,7 @@
}
void JavaVMExt::DisallowNewWeakGlobals() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
Thread* const self = Thread::Current();
MutexLock mu(self, *Locks::jni_weak_globals_lock_);
// DisallowNewWeakGlobals is only called by CMS during the pause. It is required to have the
@@ -820,7 +820,7 @@
}
void JavaVMExt::AllowNewWeakGlobals() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
Thread* self = Thread::Current();
MutexLock mu(self, *Locks::jni_weak_globals_lock_);
allow_accessing_weak_globals_.store(true, std::memory_order_seq_cst);
@@ -876,7 +876,7 @@
return DecodeWeakGlobal(self, ref);
}
// self can be null during a runtime shutdown. ~Runtime()->~ClassLinker()->DecodeWeakGlobal().
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
DCHECK(allow_accessing_weak_globals_.load(std::memory_order_seq_cst));
}
return weak_globals_.SynchronizedGet(ref);
diff --git a/runtime/jni/jni_internal.cc b/runtime/jni/jni_internal.cc
index e3153fd..5763787 100644
--- a/runtime/jni/jni_internal.cc
+++ b/runtime/jni/jni_internal.cc
@@ -1950,6 +1950,7 @@
return InvokeWithJValues(soa, nullptr, mid, args).GetD();
}
+ NO_STACK_PROTECTOR
static void CallStaticVoidMethod(JNIEnv* env, jclass, jmethodID mid, ...) {
va_list ap;
va_start(ap, mid);
@@ -1959,6 +1960,7 @@
InvokeWithVarArgs(soa, nullptr, mid, ap);
}
+ NO_STACK_PROTECTOR
static void CallStaticVoidMethodV(JNIEnv* env, jclass, jmethodID mid, va_list args) {
CHECK_NON_NULL_ARGUMENT_RETURN_VOID(mid);
ScopedObjectAccess soa(env);
@@ -2176,14 +2178,17 @@
if (heap->IsMovableObject(s)) {
StackHandleScope<1> hs(soa.Self());
HandleWrapperObjPtr<mirror::String> h(hs.NewHandleWrapper(&s));
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier && !gUseUserfaultfd) {
heap->IncrementDisableMovingGC(soa.Self());
} else {
- // For the CC collector, we only need to wait for the thread flip rather
+ // For the CC and CMC collector, we only need to wait for the thread flip rather
// than the whole GC to occur thanks to the to-space invariant.
heap->IncrementDisableThreadFlip(soa.Self());
}
}
+ // Ensure that the string doesn't cause userfaults in case passed on to
+ // the kernel.
+ heap->EnsureObjectUserfaulted(s);
if (is_copy != nullptr) {
*is_copy = JNI_FALSE;
}
@@ -2199,7 +2204,7 @@
gc::Heap* heap = Runtime::Current()->GetHeap();
ObjPtr<mirror::String> s = soa.Decode<mirror::String>(java_string);
if (!s->IsCompressed() && heap->IsMovableObject(s)) {
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier && !gUseUserfaultfd) {
heap->DecrementDisableMovingGC(soa.Self());
} else {
heap->DecrementDisableThreadFlip(soa.Self());
@@ -2366,16 +2371,18 @@
}
gc::Heap* heap = Runtime::Current()->GetHeap();
if (heap->IsMovableObject(array)) {
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier && !gUseUserfaultfd) {
heap->IncrementDisableMovingGC(soa.Self());
} else {
- // For the CC collector, we only need to wait for the thread flip rather than the whole GC
- // to occur thanks to the to-space invariant.
+ // For the CC and CMC collector, we only need to wait for the thread flip rather
+ // than the whole GC to occur thanks to the to-space invariant.
heap->IncrementDisableThreadFlip(soa.Self());
}
// Re-decode in case the object moved since IncrementDisableGC waits for GC to complete.
array = soa.Decode<mirror::Array>(java_array);
}
+ // Ensure that the array doesn't cause userfaults in case passed on to the kernel.
+ heap->EnsureObjectUserfaulted(array);
if (is_copy != nullptr) {
*is_copy = JNI_FALSE;
}
@@ -2967,7 +2974,7 @@
delete[] reinterpret_cast<uint64_t*>(elements);
} else if (heap->IsMovableObject(array)) {
// Non copy to a movable object must means that we had disabled the moving GC.
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier && !gUseUserfaultfd) {
heap->DecrementDisableMovingGC(soa.Self());
} else {
heap->DecrementDisableThreadFlip(soa.Self());
diff --git a/runtime/lock_word.h b/runtime/lock_word.h
index 84f45c2..599a599 100644
--- a/runtime/lock_word.h
+++ b/runtime/lock_word.h
@@ -183,8 +183,7 @@
LockState GetState() const {
CheckReadBarrierState();
- if ((!kUseReadBarrier && UNLIKELY(value_ == 0)) ||
- (kUseReadBarrier && UNLIKELY((value_ & kGCStateMaskShiftedToggled) == 0))) {
+ if (UNLIKELY((value_ & kGCStateMaskShiftedToggled) == 0)) {
return kUnlocked;
} else {
uint32_t internal_state = (value_ >> kStateShift) & kStateMask;
@@ -288,7 +287,7 @@
void CheckReadBarrierState() const {
if (kIsDebugBuild && ((value_ >> kStateShift) & kStateMask) != kStateForwardingAddress) {
uint32_t rb_state = ReadBarrierState();
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
DCHECK_EQ(rb_state, 0U);
} else {
DCHECK(rb_state == ReadBarrier::NonGrayState() ||
diff --git a/runtime/managed_stack.h b/runtime/managed_stack.h
index 04a27fe..0e7dfe3 100644
--- a/runtime/managed_stack.h
+++ b/runtime/managed_stack.h
@@ -43,6 +43,8 @@
// code.
class PACKED(4) ManagedStack {
public:
+ static size_t constexpr kTaggedJniSpMask = 0x3;
+
ManagedStack()
: tagged_top_quick_frame_(TaggedTopQuickFrame::CreateNotTagged(nullptr)),
link_(nullptr),
@@ -75,8 +77,12 @@
return tagged_top_quick_frame_.GetSp();
}
- bool GetTopQuickFrameTag() const {
- return tagged_top_quick_frame_.GetTag();
+ bool GetTopQuickFrameGenericJniTag() const {
+ return tagged_top_quick_frame_.GetGenericJniTag();
+ }
+
+ bool GetTopQuickFrameJitJniTag() const {
+ return tagged_top_quick_frame_.GetJitJniTag();
}
bool HasTopQuickFrame() const {
@@ -89,10 +95,10 @@
tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateNotTagged(top);
}
- void SetTopQuickFrameTagged(ArtMethod** top) {
+ void SetTopQuickFrameGenericJniTagged(ArtMethod** top) {
DCHECK(top_shadow_frame_ == nullptr);
DCHECK_ALIGNED(top, 4u);
- tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateTagged(top);
+ tagged_top_quick_frame_ = TaggedTopQuickFrame::CreateGenericJniTagged(top);
}
static constexpr size_t TaggedTopQuickFrameOffset() {
@@ -129,26 +135,30 @@
return TaggedTopQuickFrame(reinterpret_cast<uintptr_t>(sp));
}
- static TaggedTopQuickFrame CreateTagged(ArtMethod** sp) {
+ static TaggedTopQuickFrame CreateGenericJniTagged(ArtMethod** sp) {
DCHECK_ALIGNED(sp, 4u);
return TaggedTopQuickFrame(reinterpret_cast<uintptr_t>(sp) | 1u);
}
// Get SP known to be not tagged and non-null.
ArtMethod** GetSpKnownNotTagged() const {
- DCHECK(!GetTag());
+ DCHECK(!GetGenericJniTag() && !GetJitJniTag());
DCHECK_NE(tagged_sp_, 0u);
return reinterpret_cast<ArtMethod**>(tagged_sp_);
}
ArtMethod** GetSp() const {
- return reinterpret_cast<ArtMethod**>(tagged_sp_ & ~static_cast<uintptr_t>(1u));
+ return reinterpret_cast<ArtMethod**>(tagged_sp_ & ~static_cast<uintptr_t>(kTaggedJniSpMask));
}
- bool GetTag() const {
+ bool GetGenericJniTag() const {
return (tagged_sp_ & 1u) != 0u;
}
+ bool GetJitJniTag() const {
+ return (tagged_sp_ & 2u) != 0u;
+ }
+
uintptr_t GetTaggedSp() const {
return tagged_sp_;
}
diff --git a/runtime/method_handles.cc b/runtime/method_handles.cc
index 1327a24..286e097 100644
--- a/runtime/method_handles.cc
+++ b/runtime/method_handles.cc
@@ -625,6 +625,10 @@
case mirror::MethodHandle::kInstanceGet: {
size_t obj_reg = operands->GetOperand(0);
ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+ if (obj == nullptr) {
+ ThrowNullPointerException("Receiver is null");
+ return false;
+ }
MethodHandleFieldGet(self, shadow_frame, obj, field, field_type, result);
return true;
}
@@ -648,6 +652,10 @@
callsite_type->GetPTypes()->Get(kPTypeIndex)->GetPrimitiveType(),
value_reg);
ObjPtr<mirror::Object> obj = shadow_frame.GetVRegReference(obj_reg);
+ if (obj == nullptr) {
+ ThrowNullPointerException("Receiver is null");
+ return false;
+ }
return MethodHandleFieldPut(self, shadow_frame, obj, field, field_type, value);
}
case mirror::MethodHandle::kStaticPut: {
diff --git a/runtime/metrics/reporter.cc b/runtime/metrics/reporter.cc
index a44066e..28ca997 100644
--- a/runtime/metrics/reporter.cc
+++ b/runtime/metrics/reporter.cc
@@ -126,10 +126,17 @@
// Configure the backends
if (config_.dump_to_logcat) {
- backends_.emplace_back(new LogBackend(LogSeverity::INFO));
+ backends_.emplace_back(new LogBackend(std::make_unique<TextFormatter>(), LogSeverity::INFO));
}
if (config_.dump_to_file.has_value()) {
- backends_.emplace_back(new FileBackend(config_.dump_to_file.value()));
+ std::unique_ptr<MetricsFormatter> formatter;
+ if (config_.metrics_format == "xml") {
+ formatter = std::make_unique<XmlFormatter>();
+ } else {
+ formatter = std::make_unique<TextFormatter>();
+ }
+
+ backends_.emplace_back(new FileBackend(std::move(formatter), config_.dump_to_file.value()));
}
if (config_.dump_to_statsd) {
auto backend = CreateStatsdBackend();
@@ -291,6 +298,7 @@
.dump_to_logcat = gFlags.MetricsWriteToLogcat(),
.dump_to_file = gFlags.MetricsWriteToFile.GetValueOptional(),
.dump_to_statsd = gFlags.MetricsWriteToStatsd(),
+ .metrics_format = gFlags.MetricsFormat(),
.period_spec = period_spec,
.reporting_num_mods = reporting_num_mods,
.reporting_mods = reporting_mods,
diff --git a/runtime/metrics/reporter.h b/runtime/metrics/reporter.h
index daeaf1f..af9e0ca 100644
--- a/runtime/metrics/reporter.h
+++ b/runtime/metrics/reporter.h
@@ -78,6 +78,9 @@
// If set, provides a file name to enable metrics logging to a file.
std::optional<std::string> dump_to_file;
+ // Provides the desired output format for metrics written to a file.
+ std::string metrics_format;
+
// The reporting period configuration.
std::optional<ReportingPeriodSpec> period_spec;
diff --git a/runtime/metrics/reporter_test.cc b/runtime/metrics/reporter_test.cc
index 3807c77..7d9377a 100644
--- a/runtime/metrics/reporter_test.cc
+++ b/runtime/metrics/reporter_test.cc
@@ -174,9 +174,9 @@
CompilationReason reason = CompilationReason::kUnknown) {
// TODO: we should iterate through all the other metrics to make sure they were not
// reported. However, we don't have an easy to use iteration mechanism over metrics yet.
- // We should ads one
+ // We should add one
ASSERT_EQ(backend_->GetReports().size(), size);
- for (auto report : backend_->GetReports()) {
+ for (const TestBackend::Report& report : backend_->GetReports()) {
ASSERT_EQ(report.data.Get(DatumId::kClassVerificationCount), with_metrics ? 2u : 0u);
ASSERT_EQ(report.data.Get(DatumId::kJitMethodCompileCount), with_metrics ? 1u : 0u);
}
@@ -411,7 +411,7 @@
const std::string& spec_str,
bool startup_first,
bool continuous,
- std::vector<uint32_t> periods) {
+ const std::vector<uint32_t>& periods) {
Verify(spec_str, true, startup_first, continuous, periods);
}
@@ -420,7 +420,7 @@
bool valid,
bool startup_first,
bool continuous,
- std::vector<uint32_t> periods) {
+ const std::vector<uint32_t>& periods) {
std::string error_msg;
std::optional<ReportingPeriodSpec> spec = ReportingPeriodSpec::Parse(spec_str, &error_msg);
diff --git a/runtime/metrics/statsd.cc b/runtime/metrics/statsd.cc
index f68d507..78c3622 100644
--- a/runtime/metrics/statsd.cc
+++ b/runtime/metrics/statsd.cc
@@ -106,6 +106,30 @@
case DatumId::kFullGcTracingThroughputAvg:
return std::make_optional(
statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_TRACING_THROUGHPUT_AVG_MB_PER_SEC);
+ case DatumId::kGcWorldStopTime:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_TIME_US);
+ case DatumId::kGcWorldStopCount:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_WORLD_STOP_COUNT);
+ case DatumId::kYoungGcScannedBytes:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_SCANNED_BYTES);
+ case DatumId::kYoungGcFreedBytes:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_FREED_BYTES);
+ case DatumId::kYoungGcDuration:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_YOUNG_GENERATION_COLLECTION_DURATION_MS);
+ case DatumId::kFullGcScannedBytes:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_SCANNED_BYTES);
+ case DatumId::kFullGcFreedBytes:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_FREED_BYTES);
+ case DatumId::kFullGcDuration:
+ return std::make_optional(
+ statsd::ART_DATUM_REPORTED__KIND__ART_DATUM_GC_FULL_HEAP_COLLECTION_DURATION_MS);
}
}
@@ -226,8 +250,8 @@
EncodeCompileFilter(session_data_.compiler_filter),
EncodeCompilationReason(session_data_.compilation_reason),
current_timestamp_,
- /*thread_type=*/0, // TODO: collect and report thread type (0 means UNKNOWN, but that
- // constant is not present in all branches)
+ 0, // TODO: collect and report thread type (0 means UNKNOWN, but that
+ // constant is not present in all branches)
datum_id.value(),
static_cast<int64_t>(value),
statsd::ART_DATUM_REPORTED__DEX_METADATA_TYPE__ART_DEX_METADATA_TYPE_UNKNOWN,
diff --git a/runtime/mirror/array-inl.h b/runtime/mirror/array-inl.h
index b0e77b4..a7faa37 100644
--- a/runtime/mirror/array-inl.h
+++ b/runtime/mirror/array-inl.h
@@ -36,12 +36,11 @@
return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 0, 0, pointer_size);
}
-template<VerifyObjectFlags kVerifyFlags>
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
inline size_t Array::SizeOf() {
- // No read barrier is needed for reading a constant primitive field through
- // constant reference field chain. See ReadBarrierOption.
size_t component_size_shift =
- GetClass<kVerifyFlags, kWithoutReadBarrier>()->GetComponentSizeShift();
+ GetClass<kVerifyFlags, kReadBarrierOption>()
+ ->template GetComponentSizeShift<kReadBarrierOption>();
// Don't need to check this since we already check this in GetClass.
int32_t component_count =
GetLength<static_cast<VerifyObjectFlags>(kVerifyFlags & ~kVerifyThis)>();
@@ -98,7 +97,7 @@
if (kTransactionActive) {
Runtime::Current()->RecordWriteArray(this, i, GetWithoutChecks(i));
}
- DCHECK(CheckIsValidIndex<kVerifyFlags>(i));
+ DCHECK(CheckIsValidIndex<kVerifyFlags>(i)) << i << " " << GetLength<kVerifyFlags>();
GetData()[i] = value;
}
// Backward copy where elements are of aligned appropriately for T. Count is in T sized units.
diff --git a/runtime/mirror/array.h b/runtime/mirror/array.h
index 4bf9dee..dfe7d47 100644
--- a/runtime/mirror/array.h
+++ b/runtime/mirror/array.h
@@ -58,7 +58,8 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Roles::uninterruptible_);
- template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+ template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+ ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
size_t SizeOf() REQUIRES_SHARED(Locks::mutator_lock_);
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
ALWAYS_INLINE int32_t GetLength() REQUIRES_SHARED(Locks::mutator_lock_) {
diff --git a/runtime/mirror/class-inl.h b/runtime/mirror/class-inl.h
index b6bd22e..77f78c5 100644
--- a/runtime/mirror/class-inl.h
+++ b/runtime/mirror/class-inl.h
@@ -1077,10 +1077,9 @@
return 1U << GetComponentSizeShift();
}
+template <ReadBarrierOption kReadBarrierOption>
inline size_t Class::GetComponentSizeShift() {
- // No read barrier is needed for reading a constant primitive field through
- // constant reference field. See ReadBarrierOption.
- return GetComponentType<kDefaultVerifyFlags, kWithoutReadBarrier>()->GetPrimitiveTypeSizeShift();
+ return GetComponentType<kDefaultVerifyFlags, kReadBarrierOption>()->GetPrimitiveTypeSizeShift();
}
inline bool Class::IsObjectClass() {
@@ -1106,11 +1105,9 @@
return GetComponentType<kVerifyFlags, kWithoutReadBarrier>() != nullptr;
}
-template<VerifyObjectFlags kVerifyFlags>
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
inline bool Class::IsObjectArrayClass() {
- // We do not need a read barrier here as the primitive type is constant,
- // both from-space and to-space component type classes shall yield the same result.
- const ObjPtr<Class> component_type = GetComponentType<kVerifyFlags, kWithoutReadBarrier>();
+ const ObjPtr<Class> component_type = GetComponentType<kVerifyFlags, kReadBarrierOption>();
constexpr VerifyObjectFlags kNewFlags = RemoveThisFlags(kVerifyFlags);
return component_type != nullptr && !component_type->IsPrimitive<kNewFlags>();
}
diff --git a/runtime/mirror/class-refvisitor-inl.h b/runtime/mirror/class-refvisitor-inl.h
index 8c85387..ee5c11f 100644
--- a/runtime/mirror/class-refvisitor-inl.h
+++ b/runtime/mirror/class-refvisitor-inl.h
@@ -51,22 +51,39 @@
}
}
-template<ReadBarrierOption kReadBarrierOption, class Visitor>
+template<ReadBarrierOption kReadBarrierOption, bool kVisitProxyMethod, class Visitor>
void Class::VisitNativeRoots(Visitor& visitor, PointerSize pointer_size) {
VisitFields<kReadBarrierOption>([&](ArtField* field) REQUIRES_SHARED(art::Locks::mutator_lock_) {
field->VisitRoots(visitor);
- if (kIsDebugBuild && IsResolved()) {
+ if (kIsDebugBuild && !gUseUserfaultfd && IsResolved()) {
CHECK_EQ(field->GetDeclaringClass<kReadBarrierOption>(), this)
<< GetStatus() << field->GetDeclaringClass()->PrettyClass() << " != " << PrettyClass();
}
});
// Don't use VisitMethods because we don't want to hit the class-ext methods twice.
for (ArtMethod& method : GetMethods(pointer_size)) {
- method.VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+ method.VisitRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
}
ObjPtr<ClassExt> ext(GetExtData<kDefaultVerifyFlags, kReadBarrierOption>());
if (!ext.IsNull()) {
- ext->VisitNativeRoots<kReadBarrierOption, Visitor>(visitor, pointer_size);
+ ext->VisitNativeRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
+ }
+}
+
+template<ReadBarrierOption kReadBarrierOption>
+void Class::VisitObsoleteDexCaches(DexCacheVisitor& visitor) {
+ ObjPtr<ClassExt> ext(GetExtData<kDefaultVerifyFlags, kReadBarrierOption>());
+ if (!ext.IsNull()) {
+ ext->VisitDexCaches<kDefaultVerifyFlags, kReadBarrierOption>(visitor);
+ }
+}
+
+template<ReadBarrierOption kReadBarrierOption, class Visitor>
+void Class::VisitObsoleteClass(Visitor& visitor) {
+ ObjPtr<ClassExt> ext(GetExtData<kDefaultVerifyFlags, kReadBarrierOption>());
+ if (!ext.IsNull()) {
+ ObjPtr<Class> klass = ext->GetObsoleteClass<kDefaultVerifyFlags, kReadBarrierOption>();
+ visitor(klass);
}
}
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 90efce5..97af90a 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -64,6 +64,7 @@
template<typename T> class StrideIterator;
template<size_t kNumReferences> class PACKED(4) StackHandleScope;
class Thread;
+class DexCacheVisitor;
namespace mirror {
@@ -236,6 +237,15 @@
// Set access flags, recording the change if running inside a Transaction.
void SetAccessFlags(uint32_t new_access_flags) REQUIRES_SHARED(Locks::mutator_lock_);
+ void SetInBootImageAndNotInPreloadedClasses() REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint32_t flags = GetAccessFlags();
+ SetAccessFlags(flags | kAccInBootImageAndNotInPreloadedClasses);
+ }
+
+ ALWAYS_INLINE bool IsInBootImageAndNotInPreloadedClasses() REQUIRES_SHARED(Locks::mutator_lock_) {
+ return (GetAccessFlags() & kAccInBootImageAndNotInPreloadedClasses) != 0;
+ }
+
// Returns true if the class is an enum.
ALWAYS_INLINE bool IsEnum() REQUIRES_SHARED(Locks::mutator_lock_) {
return (GetAccessFlags() & kAccEnum) != 0;
@@ -486,6 +496,7 @@
size_t GetComponentSize() REQUIRES_SHARED(Locks::mutator_lock_);
+ template<ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
size_t GetComponentSizeShift() REQUIRES_SHARED(Locks::mutator_lock_);
bool IsObjectClass() REQUIRES_SHARED(Locks::mutator_lock_);
@@ -495,7 +506,8 @@
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
bool IsInstantiable() REQUIRES_SHARED(Locks::mutator_lock_);
- template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
+ template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+ ReadBarrierOption kReadBarrierOption = kWithoutReadBarrier>
ALWAYS_INLINE bool IsObjectArrayClass() REQUIRES_SHARED(Locks::mutator_lock_);
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
@@ -553,7 +565,7 @@
// The size of java.lang.Class.class.
static uint32_t ClassClassSize(PointerSize pointer_size) {
// The number of vtable entries in java.lang.Class.
- uint32_t vtable_entries = Object::kVTableLength + 67;
+ uint32_t vtable_entries = Object::kVTableLength + 72;
return ComputeClassSize(true, vtable_entries, 0, 0, 4, 1, 0, pointer_size);
}
@@ -570,6 +582,9 @@
static constexpr MemberOffset ObjectSizeAllocFastPathOffset() {
return OFFSET_OF_OBJECT_MEMBER(Class, object_size_alloc_fast_path_);
}
+ static constexpr MemberOffset ClinitThreadIdOffset() {
+ return OFFSET_OF_OBJECT_MEMBER(Class, clinit_thread_id_);
+ }
ALWAYS_INLINE void SetObjectSize(uint32_t new_object_size) REQUIRES_SHARED(Locks::mutator_lock_);
@@ -1170,10 +1185,19 @@
// Visit native roots visits roots which are keyed off the native pointers such as ArtFields and
// ArtMethods.
- template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+ template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+ bool kVisitProxyMethod = true,
+ class Visitor>
void VisitNativeRoots(Visitor& visitor, PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Visit obsolete dex caches possibly stored in ext_data_
+ template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
+ void VisitObsoleteDexCaches(DexCacheVisitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
+ template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+ void VisitObsoleteClass(Visitor& visitor) REQUIRES_SHARED(Locks::mutator_lock_);
+
// Visit ArtMethods directly owned by this class.
template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
void VisitMethods(Visitor visitor, PointerSize pointer_size)
@@ -1417,7 +1441,7 @@
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!Roles::uninterruptible_);
// 'Class' Object Fields
- // Order governed by java field ordering. See art::ClassLinker::LinkFields.
+ // Order governed by java field ordering. See art::ClassLinker::LinkFieldsHelper::LinkFields.
// Defining class loader, or null for the "bootstrap" system loader.
HeapReference<ClassLoader> class_loader_;
diff --git a/runtime/mirror/class_ext-inl.h b/runtime/mirror/class_ext-inl.h
index ddd46b9..9d6ac43 100644
--- a/runtime/mirror/class_ext-inl.h
+++ b/runtime/mirror/class_ext-inl.h
@@ -23,6 +23,7 @@
#include "art_method-inl.h"
#include "base/enums.h"
#include "base/globals.h"
+#include "class_linker.h"
#include "handle_scope.h"
#include "jni/jni_internal.h"
#include "jni_id_type.h"
@@ -148,8 +149,9 @@
return GetFieldObject<Throwable>(OFFSET_OF_OBJECT_MEMBER(ClassExt, erroneous_state_error_));
}
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
inline ObjPtr<ObjectArray<DexCache>> ClassExt::GetObsoleteDexCaches() {
- return GetFieldObject<ObjectArray<DexCache>>(
+ return GetFieldObject<ObjectArray<DexCache>, kVerifyFlags, kReadBarrierOption>(
OFFSET_OF_OBJECT_MEMBER(ClassExt, obsolete_dex_caches_));
}
@@ -164,13 +166,25 @@
return GetFieldObject<Object>(OFFSET_OF_OBJECT_MEMBER(ClassExt, original_dex_file_));
}
-template<ReadBarrierOption kReadBarrierOption, class Visitor>
+template<ReadBarrierOption kReadBarrierOption, bool kVisitProxyMethod, class Visitor>
void ClassExt::VisitNativeRoots(Visitor& visitor, PointerSize pointer_size) {
VisitMethods<kReadBarrierOption>([&](ArtMethod* method) {
- method->VisitRoots<kReadBarrierOption>(visitor, pointer_size);
+ method->VisitRoots<kReadBarrierOption, kVisitProxyMethod>(visitor, pointer_size);
}, pointer_size);
}
+template<VerifyObjectFlags kVerifyFlags, ReadBarrierOption kReadBarrierOption>
+void ClassExt::VisitDexCaches(DexCacheVisitor& visitor) {
+ ObjPtr<ObjectArray<DexCache>> arr(GetObsoleteDexCaches<kVerifyFlags, kReadBarrierOption>());
+ if (!arr.IsNull()) {
+ int32_t len = arr->GetLength();
+ for (int32_t i = 0; i < len; i++) {
+ ObjPtr<mirror::DexCache> dex_cache = arr->Get<kVerifyFlags, kReadBarrierOption>(i);
+ visitor.Visit(dex_cache);
+ }
+ }
+}
+
template<ReadBarrierOption kReadBarrierOption, class Visitor>
void ClassExt::VisitMethods(Visitor visitor, PointerSize pointer_size) {
ObjPtr<PointerArray> arr(GetObsoleteMethods<kDefaultVerifyFlags, kReadBarrierOption>());
diff --git a/runtime/mirror/class_ext.h b/runtime/mirror/class_ext.h
index 4ce3b10..b025eb2 100644
--- a/runtime/mirror/class_ext.h
+++ b/runtime/mirror/class_ext.h
@@ -27,6 +27,7 @@
namespace art {
struct ClassExtOffsets;
+class DexCacheVisitor;
namespace mirror {
@@ -46,6 +47,8 @@
ObjPtr<Throwable> GetErroneousStateError() REQUIRES_SHARED(Locks::mutator_lock_);
+ template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+ ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
ObjPtr<ObjectArray<DexCache>> GetObsoleteDexCaches() REQUIRES_SHARED(Locks::mutator_lock_);
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
@@ -126,10 +129,21 @@
static bool ExtendObsoleteArrays(Handle<ClassExt> h_this, Thread* self, uint32_t increase)
REQUIRES_SHARED(Locks::mutator_lock_);
- template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
+ template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+ bool kVisitProxyMethod = true,
+ class Visitor>
inline void VisitNativeRoots(Visitor& visitor, PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // NO_THREAD_SAFETY_ANALYSIS for dex_lock and heap_bitmap_lock_ as both are at
+ // higher lock-level than class-table's lock, which is already acquired and
+ // is at lower (kClassLoaderClassesLock) level.
+ template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+ ReadBarrierOption kReadBarrierOption = kWithReadBarrier>
+ inline void VisitDexCaches(DexCacheVisitor& visitor)
+ NO_THREAD_SAFETY_ANALYSIS
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
template<ReadBarrierOption kReadBarrierOption = kWithReadBarrier, class Visitor>
inline void VisitMethods(Visitor visitor, PointerSize pointer_size)
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -156,6 +170,10 @@
bool EnsureJniIdsArrayPresent(MemberOffset off, size_t count)
REQUIRES_SHARED(Locks::mutator_lock_);
+ // Backing store of user-defined values pertaining to a class.
+ // Maintained by the ClassValue class.
+ HeapReference<Object> class_value_map_;
+
// The saved error for this class being erroneous.
HeapReference<Throwable> erroneous_state_error_;
@@ -181,9 +199,10 @@
// classes sfields_ array or '0' if no id has been assigned to that field yet.
HeapReference<PointerArray> static_jfield_ids_;
+ int32_t pre_redefine_class_def_index_;
+
// Native pointer to DexFile and ClassDef index of this class before it was JVMTI-redefined.
int64_t pre_redefine_dex_file_ptr_;
- int32_t pre_redefine_class_def_index_;
friend struct art::ClassExtOffsets; // for verifying offset information
DISALLOW_IMPLICIT_CONSTRUCTORS(ClassExt);
diff --git a/runtime/mirror/dex_cache-inl.h b/runtime/mirror/dex_cache-inl.h
index 2791fe3..b937c2c 100644
--- a/runtime/mirror/dex_cache-inl.h
+++ b/runtime/mirror/dex_cache-inl.h
@@ -60,7 +60,7 @@
return nullptr;
}
mirror::DexCache* dex_cache = this;
- if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
+ if (gUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
// Several code paths use DexCache without read-barrier for performance.
// We have to check the "to-space" object here to avoid allocating twice.
dex_cache = reinterpret_cast<DexCache*>(ReadBarrier::Mark(dex_cache));
@@ -405,20 +405,27 @@
VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
// Visit arrays after.
if (kVisitNativeRoots) {
- VisitDexCachePairs<String, kReadBarrierOption, Visitor>(
- GetStrings<kVerifyFlags>(), NumStrings<kVerifyFlags>(), visitor);
+ VisitNativeRoots<kVerifyFlags, kReadBarrierOption>(visitor);
+ }
+}
- VisitDexCachePairs<Class, kReadBarrierOption, Visitor>(
- GetResolvedTypes<kVerifyFlags>(), NumResolvedTypes<kVerifyFlags>(), visitor);
+template <VerifyObjectFlags kVerifyFlags,
+ ReadBarrierOption kReadBarrierOption,
+ typename Visitor>
+inline void DexCache::VisitNativeRoots(const Visitor& visitor) {
+ VisitDexCachePairs<String, kReadBarrierOption, Visitor>(
+ GetStrings<kVerifyFlags>(), NumStrings<kVerifyFlags>(), visitor);
- VisitDexCachePairs<MethodType, kReadBarrierOption, Visitor>(
- GetResolvedMethodTypes<kVerifyFlags>(), NumResolvedMethodTypes<kVerifyFlags>(), visitor);
+ VisitDexCachePairs<Class, kReadBarrierOption, Visitor>(
+ GetResolvedTypes<kVerifyFlags>(), NumResolvedTypes<kVerifyFlags>(), visitor);
- GcRoot<mirror::CallSite>* resolved_call_sites = GetResolvedCallSites<kVerifyFlags>();
- size_t num_call_sites = NumResolvedCallSites<kVerifyFlags>();
- for (size_t i = 0; resolved_call_sites != nullptr && i != num_call_sites; ++i) {
- visitor.VisitRootIfNonNull(resolved_call_sites[i].AddressWithoutBarrier());
- }
+ VisitDexCachePairs<MethodType, kReadBarrierOption, Visitor>(
+ GetResolvedMethodTypes<kVerifyFlags>(), NumResolvedMethodTypes<kVerifyFlags>(), visitor);
+
+ GcRoot<mirror::CallSite>* resolved_call_sites = GetResolvedCallSites<kVerifyFlags>();
+ size_t num_call_sites = NumResolvedCallSites<kVerifyFlags>();
+ for (size_t i = 0; resolved_call_sites != nullptr && i != num_call_sites; ++i) {
+ visitor.VisitRootIfNonNull(resolved_call_sites[i].AddressWithoutBarrier());
}
}
diff --git a/runtime/mirror/dex_cache.h b/runtime/mirror/dex_cache.h
index 6701405..78c6bb5 100644
--- a/runtime/mirror/dex_cache.h
+++ b/runtime/mirror/dex_cache.h
@@ -444,6 +444,12 @@
ObjPtr<ClassLoader> GetClassLoader() REQUIRES_SHARED(Locks::mutator_lock_);
+ template <VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+ ReadBarrierOption kReadBarrierOption = kWithReadBarrier,
+ typename Visitor>
+ void VisitNativeRoots(const Visitor& visitor)
+ REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(Locks::heap_bitmap_lock_);
+
private:
// Allocate new array in linear alloc and save it in the given fields.
template<typename T, size_t kMaxCacheSize>
diff --git a/runtime/mirror/object-inl.h b/runtime/mirror/object-inl.h
index c679fde..318a811 100644
--- a/runtime/mirror/object-inl.h
+++ b/runtime/mirror/object-inl.h
@@ -104,7 +104,7 @@
}
inline uint32_t Object::GetMarkBit() {
- CHECK(kUseReadBarrier);
+ CHECK(gUseReadBarrier);
return GetLockWord(false).MarkBitState();
}
@@ -880,7 +880,7 @@
// inheritance hierarchy and find reference offsets the hard way. In the static case, just
// consider this class.
for (ObjPtr<Class> klass = kIsStatic
- ? AsClass<kVerifyFlags>()
+ ? ObjPtr<Class>::DownCast(this)
: GetClass<kVerifyFlags, kReadBarrierOption>();
klass != nullptr;
klass = kIsStatic ? nullptr : klass->GetSuperClass<kVerifyFlags, kReadBarrierOption>()) {
diff --git a/runtime/mirror/object-refvisitor-inl.h b/runtime/mirror/object-refvisitor-inl.h
index f98c433..5251953 100644
--- a/runtime/mirror/object-refvisitor-inl.h
+++ b/runtime/mirror/object-refvisitor-inl.h
@@ -90,6 +90,104 @@
}
}
+// Could be called with from-space address of the object as we access klass and
+// length (in case of arrays/strings) and we don't want to cause cascading faults.
+template <bool kFetchObjSize,
+ bool kVisitNativeRoots,
+ VerifyObjectFlags kVerifyFlags,
+ ReadBarrierOption kReadBarrierOption,
+ typename Visitor>
+inline size_t Object::VisitRefsForCompaction(const Visitor& visitor,
+ MemberOffset begin,
+ MemberOffset end) {
+ constexpr VerifyObjectFlags kSizeOfFlags = RemoveThisFlags(kVerifyFlags);
+ size_t size;
+ // We want to continue using pre-compact klass to avoid cascading faults.
+ ObjPtr<Class> klass = GetClass<kVerifyFlags, kReadBarrierOption>();
+ DCHECK(klass != nullptr) << "obj=" << this;
+ const uint32_t class_flags = klass->GetClassFlags<kVerifyNone>();
+ if (LIKELY(class_flags == kClassFlagNormal)) {
+ DCHECK((!klass->IsVariableSize<kVerifyFlags>()));
+ VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
+ size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+ DCHECK((!klass->IsClassClass<kVerifyFlags>()));
+ DCHECK(!klass->IsStringClass<kVerifyFlags>());
+ DCHECK(!klass->IsClassLoaderClass<kVerifyFlags>());
+ DCHECK((!klass->IsArrayClass<kVerifyFlags>()));
+ } else {
+ if ((class_flags & kClassFlagNoReferenceFields) == 0) {
+ DCHECK(!klass->IsStringClass<kVerifyFlags>());
+ if (class_flags == kClassFlagClass) {
+ DCHECK((klass->IsClassClass<kVerifyFlags>()));
+ ObjPtr<Class> as_klass = ObjPtr<Class>::DownCast(this);
+ as_klass->VisitReferences<kVisitNativeRoots, kVerifyFlags, kReadBarrierOption>(klass,
+ visitor);
+ size = kFetchObjSize ? as_klass->SizeOf<kSizeOfFlags>() : 0;
+ } else if (class_flags == kClassFlagObjectArray) {
+ DCHECK((klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
+ ObjPtr<ObjectArray<Object>> obj_arr = ObjPtr<ObjectArray<Object>>::DownCast(this);
+ obj_arr->VisitReferences(visitor, begin, end);
+ size = kFetchObjSize ? obj_arr->SizeOf<kSizeOfFlags, kReadBarrierOption>() : 0;
+ } else if ((class_flags & kClassFlagReference) != 0) {
+ VisitInstanceFieldsReferences<kVerifyFlags, kReadBarrierOption>(klass, visitor);
+ // Visit referent also as this is about updating the reference only.
+ // There is no reference processing happening here.
+ visitor(this, mirror::Reference::ReferentOffset(), /* is_static= */ false);
+ size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+ } else if (class_flags == kClassFlagDexCache) {
+ ObjPtr<DexCache> const dex_cache = ObjPtr<DexCache>::DownCast(this);
+ dex_cache->VisitReferences<kVisitNativeRoots,
+ kVerifyFlags,
+ kReadBarrierOption>(klass, visitor);
+ size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+ } else {
+ ObjPtr<ClassLoader> const class_loader = ObjPtr<ClassLoader>::DownCast(this);
+ class_loader->VisitReferences<kVisitNativeRoots,
+ kVerifyFlags,
+ kReadBarrierOption>(klass, visitor);
+ size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+ }
+ } else {
+ DCHECK((!klass->IsClassClass<kVerifyFlags>()));
+ DCHECK((!klass->IsObjectArrayClass<kVerifyFlags, kReadBarrierOption>()));
+ if ((class_flags & kClassFlagString) != 0) {
+ size = kFetchObjSize ? static_cast<String*>(this)->SizeOf<kSizeOfFlags>() : 0;
+ } else if (klass->IsArrayClass<kVerifyFlags>()) {
+ // TODO: We can optimize this by implementing a SizeOf() version which takes
+ // component-size-shift as an argument, thereby avoiding multiple loads of
+ // component_type.
+ size = kFetchObjSize
+ ? static_cast<Array*>(this)->SizeOf<kSizeOfFlags, kReadBarrierOption>()
+ : 0;
+ } else {
+ DCHECK_EQ(class_flags, kClassFlagNoReferenceFields)
+ << "class_flags: " << std::hex << class_flags;
+ // Only possibility left is of a normal klass instance with no references.
+ size = kFetchObjSize ? klass->GetObjectSize<kSizeOfFlags>() : 0;
+ }
+
+ if (kIsDebugBuild) {
+ // String still has instance fields for reflection purposes but these don't exist in
+ // actual string instances.
+ if (!klass->IsStringClass<kVerifyFlags>()) {
+ size_t total_reference_instance_fields = 0;
+ ObjPtr<Class> super_class = klass;
+ do {
+ total_reference_instance_fields +=
+ super_class->NumReferenceInstanceFields<kVerifyFlags>();
+ super_class = super_class->GetSuperClass<kVerifyFlags, kReadBarrierOption>();
+ } while (super_class != nullptr);
+ // The only reference field should be the object's class. This field is handled at the
+ // beginning of the function.
+ CHECK_EQ(total_reference_instance_fields, 1u);
+ }
+ }
+ }
+ }
+ visitor(this, ClassOffset(), /* is_static= */ false);
+ return size;
+}
+
} // namespace mirror
} // namespace art
diff --git a/runtime/mirror/object.cc b/runtime/mirror/object.cc
index ede1c66..bb9e85d 100644
--- a/runtime/mirror/object.cc
+++ b/runtime/mirror/object.cc
@@ -115,7 +115,7 @@
}
}
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
// We need a RB here. After copying the whole object above, copy references fields one by one
// again with a RB to make sure there are no from space refs. TODO: Optimize this later?
CopyReferenceFieldsWithReadBarrierVisitor visitor(dest);
diff --git a/runtime/mirror/object.h b/runtime/mirror/object.h
index ac72745..0ba545b 100644
--- a/runtime/mirror/object.h
+++ b/runtime/mirror/object.h
@@ -647,6 +647,17 @@
typename JavaLangRefVisitor = VoidFunctor>
void VisitReferences(const Visitor& visitor, const JavaLangRefVisitor& ref_visitor)
NO_THREAD_SAFETY_ANALYSIS;
+ // VisitReferences version for compaction. It is invoked with from-space
+ // object so that portions of the object, like klass and length (for arrays),
+ // can be accessed without causing cascading faults.
+ template <bool kFetchObjSize = true,
+ bool kVisitNativeRoots = false,
+ VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags,
+ ReadBarrierOption kReadBarrierOption = kWithFromSpaceBarrier,
+ typename Visitor>
+ size_t VisitRefsForCompaction(const Visitor& visitor,
+ MemberOffset begin,
+ MemberOffset end) NO_THREAD_SAFETY_ANALYSIS;
ArtField* FindFieldByOffset(MemberOffset offset) REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/mirror/object_array-inl.h b/runtime/mirror/object_array-inl.h
index e4fe03b..87f24eb 100644
--- a/runtime/mirror/object_array-inl.h
+++ b/runtime/mirror/object_array-inl.h
@@ -121,7 +121,7 @@
if (copy_forward) {
// Forward copy.
bool baker_non_gray_case = false;
- if (kUseReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
uintptr_t fake_address_dependency;
if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
baker_non_gray_case = true;
@@ -146,7 +146,7 @@
} else {
// Backward copy.
bool baker_non_gray_case = false;
- if (kUseReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
uintptr_t fake_address_dependency;
if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
baker_non_gray_case = true;
@@ -196,7 +196,7 @@
// We can't use memmove since it does not handle read barriers and may do by per byte copying.
// See b/32012820.
bool baker_non_gray_case = false;
- if (kUseReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
uintptr_t fake_address_dependency;
if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
baker_non_gray_case = true;
@@ -244,7 +244,7 @@
ObjPtr<T> o = nullptr;
int i = 0;
bool baker_non_gray_case = false;
- if (kUseReadBarrier && kUseBakerReadBarrier) {
+ if (gUseReadBarrier && kUseBakerReadBarrier) {
uintptr_t fake_address_dependency;
if (!ReadBarrier::IsGray(src.Ptr(), &fake_address_dependency)) {
baker_non_gray_case = true;
@@ -327,7 +327,20 @@
inline void ObjectArray<T>::VisitReferences(const Visitor& visitor) {
const size_t length = static_cast<size_t>(GetLength());
for (size_t i = 0; i < length; ++i) {
- visitor(this, OffsetOfElement(i), false);
+ visitor(this, OffsetOfElement(i), /* is_static= */ false);
+ }
+}
+
+template<class T> template<typename Visitor>
+inline void ObjectArray<T>::VisitReferences(const Visitor& visitor,
+ MemberOffset begin,
+ MemberOffset end) {
+ const size_t length = static_cast<size_t>(GetLength());
+ begin = std::max(begin, OffsetOfElement(0));
+ end = std::min(end, OffsetOfElement(length));
+ while (begin < end) {
+ visitor(this, begin, /* is_static= */ false, /*is_obj_array*/ true);
+ begin += kHeapReferenceSize;
}
}
diff --git a/runtime/mirror/object_array.h b/runtime/mirror/object_array.h
index a20c86b..9a53708 100644
--- a/runtime/mirror/object_array.h
+++ b/runtime/mirror/object_array.h
@@ -150,6 +150,10 @@
// REQUIRES_SHARED(Locks::mutator_lock_).
template<typename Visitor>
void VisitReferences(const Visitor& visitor) NO_THREAD_SAFETY_ANALYSIS;
+ template<typename Visitor>
+ void VisitReferences(const Visitor& visitor,
+ MemberOffset begin,
+ MemberOffset end) NO_THREAD_SAFETY_ANALYSIS;
friend class Object; // For VisitReferences
DISALLOW_IMPLICIT_CONSTRUCTORS(ObjectArray);
diff --git a/runtime/mirror/object_reference.h b/runtime/mirror/object_reference.h
index 386244d..4c3a5dc 100644
--- a/runtime/mirror/object_reference.h
+++ b/runtime/mirror/object_reference.h
@@ -50,6 +50,7 @@
vis("Ljava/lang/ClassNotFoundException;") \
vis("Ljava/lang/DexCache;") \
vis("Ljava/lang/Object;") \
+ vis("Ljava/lang/StackFrameInfo;") \
vis("Ljava/lang/StackTraceElement;") \
vis("Ljava/lang/String;") \
vis("Ljava/lang/Throwable;") \
diff --git a/runtime/mirror/stack_frame_info.cc b/runtime/mirror/stack_frame_info.cc
new file mode 100644
index 0000000..dd3e8f7
--- /dev/null
+++ b/runtime/mirror/stack_frame_info.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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 "stack_frame_info.h"
+
+#include "class-alloc-inl.h"
+#include "class.h"
+#include "class_root-inl.h"
+#include "gc/accounting/card_table-inl.h"
+#include "handle_scope-inl.h"
+#include "object-inl.h"
+#include "string.h"
+
+namespace art {
+namespace mirror {
+
+void StackFrameInfo::AssignFields(Handle<Class> declaring_class,
+ Handle<MethodType> method_type,
+ Handle<String> method_name,
+ Handle<String> file_name,
+ int32_t line_number,
+ int32_t dex_pc) {
+ if (Runtime::Current()->IsActiveTransaction()) {
+ SetFields<true>(declaring_class.Get(), method_type.Get(), method_name.Get(),
+ file_name.Get(), line_number, dex_pc);
+ } else {
+ SetFields<false>(declaring_class.Get(), method_type.Get(), method_name.Get(),
+ file_name.Get(), line_number, dex_pc);
+ }
+}
+
+template<bool kTransactionActive>
+void StackFrameInfo::SetFields(ObjPtr<Class> declaring_class,
+ ObjPtr<MethodType> method_type,
+ ObjPtr<String> method_name,
+ ObjPtr<String> file_name,
+ int32_t line_number,
+ int32_t bci) {
+ SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, declaring_class_),
+ declaring_class);
+ SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, method_type_),
+ method_type);
+ SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, method_name_),
+ method_name);
+ SetFieldObject<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, file_name_),
+ file_name);
+ SetField32<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, line_number_),
+ line_number);
+ SetField32<kTransactionActive>(OFFSET_OF_OBJECT_MEMBER(StackFrameInfo, bci_),
+ bci);
+}
+
+} // namespace mirror
+} // namespace art
diff --git a/runtime/mirror/stack_frame_info.h b/runtime/mirror/stack_frame_info.h
new file mode 100644
index 0000000..24f8c8f
--- /dev/null
+++ b/runtime/mirror/stack_frame_info.h
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 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_MIRROR_STACK_FRAME_INFO_H_
+#define ART_RUNTIME_MIRROR_STACK_FRAME_INFO_H_
+
+#include "method_type.h"
+#include "object.h"
+#include "stack_trace_element.h"
+
+namespace art {
+
+template<class T> class Handle;
+struct StackFrameInfoOffsets;
+
+namespace mirror {
+
+// C++ mirror of java.lang.StackFrameInfo
+class MANAGED StackFrameInfo final : public Object {
+ public:
+ MIRROR_CLASS("Ljava/lang/StackFrameInfo;");
+
+ void AssignFields(Handle<Class> declaring_class,
+ Handle<MethodType> method_type,
+ Handle<String> method_name,
+ Handle<String> file_name,
+ int32_t line_number,
+ int32_t dex_pc)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ private:
+ // Field order required by test "ValidateFieldOrderOfJavaCppUnionClasses".
+ HeapReference<Class> declaring_class_;
+ HeapReference<String> file_name_;
+ HeapReference<String> method_name_;
+ HeapReference<Class> method_type_;
+ HeapReference<StackTraceElement> ste_;
+ int32_t bci_;
+ int32_t line_number_;
+ bool retain_class_ref_;
+
+ template<bool kTransactionActive>
+ void SetFields(ObjPtr<Class> declaring_class,
+ ObjPtr<MethodType> method_type,
+ ObjPtr<String> method_name,
+ ObjPtr<String> file_name,
+ int32_t line_number,
+ int32_t bci)
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
+ friend struct art::StackFrameInfoOffsets; // for verifying offset information
+ DISALLOW_IMPLICIT_CONSTRUCTORS(StackFrameInfo);
+};
+
+} // namespace mirror
+} // namespace art
+
+#endif // ART_RUNTIME_MIRROR_STACK_FRAME_INFO_H_
diff --git a/runtime/mirror/string-inl.h b/runtime/mirror/string-inl.h
index 5903872..8fb4415 100644
--- a/runtime/mirror/string-inl.h
+++ b/runtime/mirror/string-inl.h
@@ -35,9 +35,9 @@
// lambda$codePoints$1$CharSequence
// which were virtual functions in standalone desugar, becomes
// direct functions with D8 desugaring.
- uint32_t vtable_entries = Object::kVTableLength + 60;
+ uint32_t vtable_entries = Object::kVTableLength + 64;
#else
- uint32_t vtable_entries = Object::kVTableLength + 62;
+ uint32_t vtable_entries = Object::kVTableLength + 66;
#endif
return Class::ComputeClassSize(true, vtable_entries, 0, 0, 0, 1, 2, pointer_size);
}
diff --git a/runtime/mirror/var_handle.cc b/runtime/mirror/var_handle.cc
index d36a2ab..68d329d 100644
--- a/runtime/mirror/var_handle.cc
+++ b/runtime/mirror/var_handle.cc
@@ -205,7 +205,7 @@
// Method to insert a read barrier for accessors to reference fields.
inline void ReadBarrierForVarHandleAccess(ObjPtr<Object> obj, MemberOffset field_offset)
REQUIRES_SHARED(Locks::mutator_lock_) {
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
// We need to ensure that the reference stored in the field is a to-space one before attempting
// the CompareAndSet/CompareAndExchange/Exchange operation otherwise it will fail incorrectly
// if obj is in the process of being moved.
diff --git a/runtime/monitor.cc b/runtime/monitor.cc
index 0cad79b..4e64c95 100644
--- a/runtime/monitor.cc
+++ b/runtime/monitor.cc
@@ -1139,7 +1139,7 @@
lock_word.GCState()));
// Only this thread pays attention to the count. Thus there is no need for stronger
// than relaxed memory ordering.
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
h_obj->SetLockWord(thin_locked, /* as_volatile= */ false);
AtraceMonitorLock(self, h_obj.Get(), /* is_wait= */ false);
return h_obj.Get(); // Success!
@@ -1239,7 +1239,7 @@
} else {
new_lw = LockWord::FromDefault(lock_word.GCState());
}
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
DCHECK_EQ(new_lw.ReadBarrierState(), 0U);
// TODO: This really only needs memory_order_release, but we currently have
// no way to specify that. In fact there seem to be no legitimate uses of SetLockWord
@@ -1409,7 +1409,7 @@
{
ObjPtr<mirror::Object> lock_object = thread->GetMonitorEnterObject();
if (lock_object != nullptr) {
- if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
+ if (gUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
// We may call Thread::Dump() in the middle of the CC thread flip and this thread's stack
// may have not been flipped yet and "pretty_object" may be a from-space (stale) ref, in
// which case the GetLockOwnerThreadId() call below will crash. So explicitly mark/forward
@@ -1613,13 +1613,13 @@
}
void MonitorList::DisallowNewMonitors() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
MutexLock mu(Thread::Current(), monitor_list_lock_);
allow_new_monitors_ = false;
}
void MonitorList::AllowNewMonitors() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
Thread* self = Thread::Current();
MutexLock mu(self, monitor_list_lock_);
allow_new_monitors_ = true;
@@ -1637,8 +1637,8 @@
MutexLock mu(self, monitor_list_lock_);
// CMS needs this to block for concurrent reference processing because an object allocated during
// the GC won't be marked and concurrent reference processing would incorrectly clear the JNI weak
- // ref. But CC (kUseReadBarrier == true) doesn't because of the to-space invariant.
- while (!kUseReadBarrier && UNLIKELY(!allow_new_monitors_)) {
+ // ref. But CC (gUseReadBarrier == true) doesn't because of the to-space invariant.
+ while (!gUseReadBarrier && UNLIKELY(!allow_new_monitors_)) {
// Check and run the empty checkpoint before blocking so the empty checkpoint will work in the
// presence of threads blocking for weak ref access.
self->CheckEmptyCheckpointFromWeakRefAccess(&monitor_list_lock_);
diff --git a/runtime/monitor_objects_stack_visitor.cc b/runtime/monitor_objects_stack_visitor.cc
index 2e75e37..524c0ec 100644
--- a/runtime/monitor_objects_stack_visitor.cc
+++ b/runtime/monitor_objects_stack_visitor.cc
@@ -90,7 +90,7 @@
void MonitorObjectsStackVisitor::VisitLockedObject(ObjPtr<mirror::Object> o, void* context) {
MonitorObjectsStackVisitor* self = reinterpret_cast<MonitorObjectsStackVisitor*>(context);
if (o != nullptr) {
- if (kUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
+ if (gUseReadBarrier && Thread::Current()->GetIsGcMarking()) {
// We may call Thread::Dump() in the middle of the CC thread flip and this thread's stack
// may have not been flipped yet and "o" may be a from-space (stale) ref, in which case the
// IdentityHashCode call below will crash. So explicitly mark/forward it here.
diff --git a/runtime/monitor_pool.h b/runtime/monitor_pool.h
index e07aa97..133cde0 100644
--- a/runtime/monitor_pool.h
+++ b/runtime/monitor_pool.h
@@ -189,12 +189,12 @@
// Size of a monitor, rounded up to a multiple of alignment.
static constexpr size_t kAlignedMonitorSize = (sizeof(Monitor) + kMonitorAlignment - 1) &
-kMonitorAlignment;
- // As close to a page as we can get seems a good start.
- static constexpr size_t kChunkCapacity = kPageSize / kAlignedMonitorSize;
- // Chunk size that is referenced in the id. We can collapse this to the actually used storage
- // in a chunk, i.e., kChunkCapacity * kAlignedMonitorSize, but this will mean proper divisions.
- static constexpr size_t kChunkSize = kPageSize;
+ // Size of the chunks holding the actual monitors. The bottom bits of the monitor id are the
+ // index into such a chunk. We can collapse this to the actually used storage
+ // in a chunk, i.e., kChunkCapacity * kAlignedMonitorSize, but this would mean proper divisions.
+ static constexpr size_t kChunkSize = 4096;
static_assert(IsPowerOfTwo(kChunkSize), "kChunkSize must be power of 2");
+ static constexpr size_t kChunkCapacity = kChunkSize / kAlignedMonitorSize;
// The number of chunks of storage that can be referenced by the initial chunk list.
// The total number of usable monitor chunks is typically 255 times this number, so it
// should be large enough that we don't run out. We run out of address bits if it's > 512.
@@ -215,7 +215,7 @@
// Array of pointers to lists (again arrays) of pointers to chunks containing monitors.
// Zeroth entry points to a list (array) of kInitialChunkStorage pointers to chunks.
// Each subsequent list as twice as large as the preceding one.
- // Monitor Ids are interpreted as follows:
+ // Monitor Ids are effectively interpreted as follows:
// Top 3 bits (of 28): index into monitor_chunks_.
// Next 16 bits: index into the chunk list, i.e. monitor_chunks_[i].
// Last 9 bits: offset within chunk, expressed as multiple of kMonitorAlignment.
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index db5d420..e75afd2 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -41,7 +41,7 @@
#include "dex/dex_file-inl.h"
#include "dex/dex_file_types.h"
#include "gc/accounting/card_table-inl.h"
-#include "gc/allocator/dlmalloc.h"
+#include "gc/allocator/art-dlmalloc.h"
#include "gc/heap.h"
#include "gc/space/dlmalloc_space.h"
#include "gc/space/image_space.h"
diff --git a/runtime/native/java_lang_Class.cc b/runtime/native/java_lang_Class.cc
index da42e61..6434e63 100644
--- a/runtime/native/java_lang_Class.cc
+++ b/runtime/native/java_lang_Class.cc
@@ -32,6 +32,7 @@
#include "hidden_api.h"
#include "jni/jni_internal.h"
#include "mirror/class-alloc-inl.h"
+#include "mirror/class_ext.h"
#include "mirror/class-inl.h"
#include "mirror/class_loader.h"
#include "mirror/field.h"
@@ -750,6 +751,71 @@
return soa.AddLocalReference<jclass>(annotations::GetDeclaringClass(klass));
}
+static jclass Class_getNestHostFromAnnotation(JNIEnv* env, jobject javaThis) {
+ ScopedFastNativeObjectAccess soa(env);
+ StackHandleScope<1> hs(soa.Self());
+ Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+ if (klass->IsObsoleteObject()) {
+ ThrowRuntimeException("Obsolete Object!");
+ return nullptr;
+ }
+ if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
+ return nullptr;
+ }
+ ObjPtr<mirror::Class> hostClass = annotations::GetNestHost(klass);
+ if (hostClass == nullptr) {
+ return nullptr;
+ }
+ return soa.AddLocalReference<jclass>(hostClass);
+}
+
+static jobjectArray Class_getNestMembersFromAnnotation(JNIEnv* env, jobject javaThis) {
+ ScopedFastNativeObjectAccess soa(env);
+ StackHandleScope<1> hs(soa.Self());
+ Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+ if (klass->IsObsoleteObject()) {
+ ThrowRuntimeException("Obsolete Object!");
+ return nullptr;
+ }
+ if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
+ return nullptr;
+ }
+ ObjPtr<mirror::ObjectArray<mirror::Class>> classes = annotations::GetNestMembers(klass);
+ if (classes == nullptr) {
+ return nullptr;
+ }
+ return soa.AddLocalReference<jobjectArray>(classes);
+}
+
+static jobjectArray Class_getPermittedSubclassesFromAnnotation(JNIEnv* env, jobject javaThis) {
+ ScopedFastNativeObjectAccess soa(env);
+ StackHandleScope<1> hs(soa.Self());
+ Handle<mirror::Class> klass(hs.NewHandle(DecodeClass(soa, javaThis)));
+ if (klass->IsObsoleteObject()) {
+ ThrowRuntimeException("Obsolete Object!");
+ return nullptr;
+ }
+ if (klass->IsProxyClass() || klass->GetDexCache() == nullptr) {
+ return nullptr;
+ }
+ ObjPtr<mirror::ObjectArray<mirror::Class>> classes = annotations::GetPermittedSubclasses(klass);
+ if (classes == nullptr) {
+ return nullptr;
+ }
+ return soa.AddLocalReference<jobjectArray>(classes);
+}
+
+static jobject Class_ensureExtDataPresent(JNIEnv* env, jobject javaThis) {
+ ScopedFastNativeObjectAccess soa(env);
+ StackHandleScope<2> hs(soa.Self());
+ Handle<mirror::Class> klass = hs.NewHandle(DecodeClass(soa, javaThis));
+
+ ObjPtr<mirror::Object> extDataPtr =
+ mirror::Class::EnsureExtDataPresent(klass, Thread::Current());
+
+ return soa.AddLocalReference<jobject>(extDataPtr);
+}
+
static jobject Class_newInstance(JNIEnv* env, jobject javaThis) {
ScopedFastNativeObjectAccess soa(env);
StackHandleScope<4> hs(soa.Self());
@@ -841,6 +907,7 @@
static JNINativeMethod gMethods[] = {
FAST_NATIVE_METHOD(Class, classForName,
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Class, ensureExtDataPresent, "()Ldalvik/system/ClassExt;"),
FAST_NATIVE_METHOD(Class, getDeclaredAnnotation,
"(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;"),
FAST_NATIVE_METHOD(Class, getDeclaredAnnotations, "()[Ljava/lang/annotation/Annotation;"),
@@ -865,6 +932,9 @@
FAST_NATIVE_METHOD(Class, getInterfacesInternal, "()[Ljava/lang/Class;"),
FAST_NATIVE_METHOD(Class, getPrimitiveClass, "(Ljava/lang/String;)Ljava/lang/Class;"),
FAST_NATIVE_METHOD(Class, getNameNative, "()Ljava/lang/String;"),
+ FAST_NATIVE_METHOD(Class, getNestHostFromAnnotation, "()Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Class, getNestMembersFromAnnotation, "()[Ljava/lang/Class;"),
+ FAST_NATIVE_METHOD(Class, getPermittedSubclassesFromAnnotation, "()[Ljava/lang/Class;"),
FAST_NATIVE_METHOD(Class, getPublicDeclaredFields, "()[Ljava/lang/reflect/Field;"),
FAST_NATIVE_METHOD(Class, getSignatureAnnotation, "()[Ljava/lang/String;"),
FAST_NATIVE_METHOD(Class, isAnonymousClass, "()Z"),
diff --git a/runtime/native/java_lang_StackStreamFactory.cc b/runtime/native/java_lang_StackStreamFactory.cc
new file mode 100644
index 0000000..f876c10
--- /dev/null
+++ b/runtime/native/java_lang_StackStreamFactory.cc
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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 "java_lang_StackStreamFactory.h"
+
+#include "nativehelper/jni_macros.h"
+
+#include "jni/jni_internal.h"
+#include "native_util.h"
+#include "scoped_fast_native_object_access-inl.h"
+#include "thread.h"
+
+namespace art {
+
+static jobject StackStreamFactory_nativeGetStackAnchor(JNIEnv* env, jclass) {
+ ScopedFastNativeObjectAccess soa(env);
+ return soa.Self()->CreateInternalStackTrace(soa);
+}
+
+static jint StackStreamFactory_nativeFetchStackFrameInfo(JNIEnv* env, jclass,
+ jlong mode, jobject anchor, jint startLevel, jint batchSize, jint startBufferIndex,
+ jobjectArray frameBuffer) {
+ if (anchor == nullptr) {
+ return startLevel;
+ }
+ ScopedFastNativeObjectAccess soa(env);
+ return Thread::InternalStackTraceToStackFrameInfoArray(soa, mode, anchor,
+ startLevel, batchSize, startBufferIndex, frameBuffer);
+}
+
+static JNINativeMethod gMethods[] = {
+ FAST_NATIVE_METHOD(StackStreamFactory, nativeGetStackAnchor, "()Ljava/lang/Object;"),
+ FAST_NATIVE_METHOD(StackStreamFactory, nativeFetchStackFrameInfo, "(JLjava/lang/Object;III[Ljava/lang/Object;)I"),
+};
+
+void register_java_lang_StackStreamFactory(JNIEnv* env) {
+ REGISTER_NATIVE_METHODS("java/lang/StackStreamFactory");
+}
+
+} // namespace art
diff --git a/artd/binder/android/os/IArtd.aidl b/runtime/native/java_lang_StackStreamFactory.h
similarity index 61%
copy from artd/binder/android/os/IArtd.aidl
copy to runtime/native/java_lang_StackStreamFactory.h
index a16764b..2216871 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/runtime/native/java_lang_StackStreamFactory.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package android.os;
+#ifndef ART_RUNTIME_NATIVE_JAVA_LANG_STACKSTREAMFACTORY_H_
+#define ART_RUNTIME_NATIVE_JAVA_LANG_STACKSTREAMFACTORY_H_
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
-}
+#include <jni.h>
+
+namespace art {
+
+void register_java_lang_StackStreamFactory(JNIEnv* env);
+
+} // namespace art
+
+#endif // ART_RUNTIME_NATIVE_JAVA_LANG_STACKSTREAMFACTORY_H_
diff --git a/runtime/native/java_lang_ref_Reference.cc b/runtime/native/java_lang_ref_Reference.cc
index f23010b..8b5635d 100644
--- a/runtime/native/java_lang_ref_Reference.cc
+++ b/runtime/native/java_lang_ref_Reference.cc
@@ -37,7 +37,7 @@
}
static jboolean Reference_refersTo0(JNIEnv* env, jobject javaThis, jobject o) {
- if (kUseReadBarrier && !kUseBakerReadBarrier) {
+ if (gUseReadBarrier && !kUseBakerReadBarrier) {
// Fall back to naive implementation that may block and needlessly preserve javaThis.
return env->IsSameObject(Reference_getReferent(env, javaThis), o);
}
@@ -48,7 +48,7 @@
if (referent == other) {
return JNI_TRUE;
}
- if (!kUseReadBarrier || referent.IsNull() || other.IsNull()) {
+ if (!gUseReadBarrier || referent.IsNull() || other.IsNull()) {
return JNI_FALSE;
}
// Explicitly handle the case in which referent is a from-space pointer. Don't use a
diff --git a/runtime/native/java_lang_reflect_Method.cc b/runtime/native/java_lang_reflect_Method.cc
index 2c0dd80..706f1a6 100644
--- a/runtime/native/java_lang_reflect_Method.cc
+++ b/runtime/native/java_lang_reflect_Method.cc
@@ -80,6 +80,7 @@
}
}
+NO_STACK_PROTECTOR
static jobject Method_invoke(JNIEnv* env, jobject javaMethod, jobject javaReceiver,
jobjectArray javaArgs) {
ScopedFastNativeObjectAccess soa(env);
diff --git a/runtime/native/jdk_internal_misc_Unsafe.cc b/runtime/native/jdk_internal_misc_Unsafe.cc
index 307a2fa..e708732 100644
--- a/runtime/native/jdk_internal_misc_Unsafe.cc
+++ b/runtime/native/jdk_internal_misc_Unsafe.cc
@@ -99,7 +99,7 @@
ObjPtr<mirror::Object> expectedValue = soa.Decode<mirror::Object>(javaExpectedValue);
ObjPtr<mirror::Object> newValue = soa.Decode<mirror::Object>(javaNewValue);
// JNI must use non transactional mode.
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
// Need to make sure the reference stored in the field is a to-space one before attempting the
// CAS or the CAS could fail incorrectly.
// Note that the read barrier load does NOT need to be volatile.
diff --git a/runtime/native/sun_misc_Unsafe.cc b/runtime/native/sun_misc_Unsafe.cc
index e9c5af0..1781a29 100644
--- a/runtime/native/sun_misc_Unsafe.cc
+++ b/runtime/native/sun_misc_Unsafe.cc
@@ -69,7 +69,7 @@
ObjPtr<mirror::Object> expectedValue = soa.Decode<mirror::Object>(javaExpectedValue);
ObjPtr<mirror::Object> newValue = soa.Decode<mirror::Object>(javaNewValue);
// JNI must use non transactional mode.
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
// Need to make sure the reference stored in the field is a to-space one before attempting the
// CAS or the CAS could fail incorrectly.
// Note that the read barrier load does NOT need to be volatile.
diff --git a/runtime/native_stack_dump.cc b/runtime/native_stack_dump.cc
index b7c3665..30b5ee6 100644
--- a/runtime/native_stack_dump.cc
+++ b/runtime/native_stack_dump.cc
@@ -24,8 +24,7 @@
#include "art_method.h"
// For DumpNativeStack.
-#include <backtrace/Backtrace.h>
-#include <backtrace/BacktraceMap.h>
+#include <unwindstack/AndroidUnwinder.h>
#if defined(__linux__)
@@ -69,13 +68,16 @@
static constexpr bool kUseAddr2line = !kIsTargetBuild;
std::string FindAddr2line() {
-#ifdef ART_CLANG_PATH
+#if !defined(ART_TARGET) && !defined(ART_CLANG_PATH)
+ #error "ART_CLANG_PATH must be defined on host build"
+#endif
+#if defined(ART_CLANG_PATH)
const char* env_value = getenv("ANDROID_BUILD_TOP");
if (env_value != nullptr) {
return std::string(env_value) + "/" + ART_CLANG_PATH + "/bin/llvm-addr2line";
}
#endif
- return std::string("/usr/bin/addr2line");
+ return std::string("llvm-addr2line");
}
ALWAYS_INLINE
@@ -321,27 +323,33 @@
void DumpNativeStack(std::ostream& os,
pid_t tid,
- BacktraceMap* existing_map,
+ const char* prefix,
+ ArtMethod* current_method,
+ void* ucontext_ptr,
+ bool skip_frames) {
+ unwindstack::AndroidLocalUnwinder unwinder;
+ DumpNativeStack(os, unwinder, tid, prefix, current_method, ucontext_ptr, skip_frames);
+}
+
+void DumpNativeStack(std::ostream& os,
+ unwindstack::AndroidLocalUnwinder& unwinder,
+ pid_t tid,
const char* prefix,
ArtMethod* current_method,
void* ucontext_ptr,
bool skip_frames) {
// Historical note: This was disabled when running under Valgrind (b/18119146).
- BacktraceMap* map = existing_map;
- std::unique_ptr<BacktraceMap> tmp_map;
- if (map == nullptr) {
- tmp_map.reset(BacktraceMap::Create(getpid()));
- map = tmp_map.get();
+ unwindstack::AndroidUnwinderData data(!skip_frames /*show_all_frames*/);
+ bool unwind_ret;
+ if (ucontext_ptr != nullptr) {
+ unwind_ret = unwinder.Unwind(ucontext_ptr, data);
+ } else {
+ unwind_ret = unwinder.Unwind(tid, data);
}
- std::unique_ptr<Backtrace> backtrace(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, tid, map));
- backtrace->SetSkipFrames(skip_frames);
- if (!backtrace->Unwind(0, reinterpret_cast<ucontext*>(ucontext_ptr))) {
- os << prefix << "(backtrace::Unwind failed for thread " << tid
- << ": " << backtrace->GetErrorString(backtrace->GetError()) << ")" << std::endl;
- return;
- } else if (backtrace->NumFrames() == 0) {
- os << prefix << "(no native stack frames for thread " << tid << ")" << std::endl;
+ if (!unwind_ret) {
+ os << prefix << "(Unwind failed for thread " << tid << ": "
+ << data.GetErrorString() << ")" << std::endl;
return;
}
@@ -356,9 +364,8 @@
}
std::unique_ptr<Addr2linePipe> addr2line_state;
-
- for (Backtrace::const_iterator it = backtrace->begin();
- it != backtrace->end(); ++it) {
+ data.DemangleFunctionNames();
+ for (const unwindstack::FrameData& frame : data.frames) {
// We produce output like this:
// ] #00 pc 000075bb8 /system/lib/libc.so (unwind_backtrace_thread+536)
// In order for parsing tools to continue to function, the stack dump
@@ -367,53 +374,55 @@
// The parsers require a single space before and after pc, and two spaces
// after the <RELATIVE_ADDR>. There can be any prefix data before the
// #XX. <RELATIVE_ADDR> has to be a hex number but with no 0x prefix.
- os << prefix << StringPrintf("#%02zu pc ", it->num);
+ os << prefix << StringPrintf("#%02zu pc ", frame.num);
bool try_addr2line = false;
- if (!BacktraceMap::IsValid(it->map)) {
+ if (frame.map_info == nullptr) {
os << StringPrintf(Is64BitInstructionSet(kRuntimeISA) ? "%016" PRIx64 " ???"
: "%08" PRIx64 " ???",
- it->pc);
+ frame.pc);
} else {
os << StringPrintf(Is64BitInstructionSet(kRuntimeISA) ? "%016" PRIx64 " "
: "%08" PRIx64 " ",
- it->rel_pc);
- if (it->map.name.empty()) {
- os << StringPrintf("<anonymous:%" PRIx64 ">", it->map.start);
+ frame.rel_pc);
+ const std::shared_ptr<unwindstack::MapInfo>& map_info = frame.map_info;
+ if (map_info->name().empty()) {
+ os << StringPrintf("<anonymous:%" PRIx64 ">", map_info->start());
} else {
- os << it->map.name;
+ os << map_info->name().c_str();
}
- if (it->map.offset != 0) {
- os << StringPrintf(" (offset %" PRIx64 ")", it->map.offset);
+ if (map_info->elf_start_offset() != 0) {
+ os << StringPrintf(" (offset %" PRIx64 ")", map_info->elf_start_offset());
}
os << " (";
- if (!it->func_name.empty()) {
- os << it->func_name;
- if (it->func_offset != 0) {
- os << "+" << it->func_offset;
+ if (!frame.function_name.empty()) {
+ os << frame.function_name.c_str();
+ if (frame.function_offset != 0) {
+ os << "+" << frame.function_offset;
}
// Functions found using the gdb jit interface will be in an empty
// map that cannot be found using addr2line.
- if (!it->map.name.empty()) {
+ if (!map_info->name().empty()) {
try_addr2line = true;
}
} else if (current_method != nullptr &&
Locks::mutator_lock_->IsSharedHeld(Thread::Current()) &&
- PcIsWithinQuickCode(current_method, it->pc)) {
+ PcIsWithinQuickCode(current_method, frame.pc)) {
const void* start_of_code = current_method->GetEntryPointFromQuickCompiledCode();
os << current_method->JniLongName() << "+"
- << (it->pc - reinterpret_cast<uint64_t>(start_of_code));
+ << (frame.pc - reinterpret_cast<uint64_t>(start_of_code));
} else {
os << "???";
}
os << ")";
- std::string build_id = map->GetBuildId(it->pc);
+ std::string build_id = map_info->GetPrintableBuildID();
if (!build_id.empty()) {
os << " (BuildId: " << build_id << ")";
}
}
os << std::endl;
if (try_addr2line && use_addr2line) {
- Addr2line(it->map.name, it->rel_pc, os, prefix, &addr2line_state);
+ // Guaranteed that map_info is not nullptr and name is non-empty.
+ Addr2line(frame.map_info->name(), frame.rel_pc, os, prefix, &addr2line_state);
}
}
@@ -426,7 +435,15 @@
void DumpNativeStack(std::ostream& os ATTRIBUTE_UNUSED,
pid_t tid ATTRIBUTE_UNUSED,
- BacktraceMap* existing_map ATTRIBUTE_UNUSED,
+ const char* prefix ATTRIBUTE_UNUSED,
+ ArtMethod* current_method ATTRIBUTE_UNUSED,
+ void* ucontext_ptr ATTRIBUTE_UNUSED,
+ bool skip_frames ATTRIBUTE_UNUSED) {
+}
+
+void DumpNativeStack(std::ostream& os ATTRIBUTE_UNUSED,
+ unwindstack::AndroidLocalUnwinder& existing_map ATTRIBUTE_UNUSED,
+ pid_t tid ATTRIBUTE_UNUSED,
const char* prefix ATTRIBUTE_UNUSED,
ArtMethod* current_method ATTRIBUTE_UNUSED,
void* ucontext_ptr ATTRIBUTE_UNUSED,
diff --git a/runtime/native_stack_dump.h b/runtime/native_stack_dump.h
index 4d4b36b..99fb59c 100644
--- a/runtime/native_stack_dump.h
+++ b/runtime/native_stack_dump.h
@@ -23,7 +23,9 @@
#include "base/macros.h"
-class BacktraceMap;
+namespace unwindstack {
+class AndroidLocalUnwinder;
+} // namespace unwindstack
namespace art {
@@ -32,7 +34,15 @@
// Dumps the native stack for thread 'tid' to 'os'.
void DumpNativeStack(std::ostream& os,
pid_t tid,
- BacktraceMap* map = nullptr,
+ const char* prefix = "",
+ ArtMethod* current_method = nullptr,
+ void* ucontext = nullptr,
+ bool skip_frames = true)
+ NO_THREAD_SAFETY_ANALYSIS;
+
+void DumpNativeStack(std::ostream& os,
+ unwindstack::AndroidLocalUnwinder& unwinder,
+ pid_t tid,
const char* prefix = "",
ArtMethod* current_method = nullptr,
void* ucontext = nullptr,
diff --git a/runtime/oat.h b/runtime/oat.h
index 462d41c..341e70b 100644
--- a/runtime/oat.h
+++ b/runtime/oat.h
@@ -32,8 +32,8 @@
class PACKED(4) OatHeader {
public:
static constexpr std::array<uint8_t, 4> kOatMagic { { 'o', 'a', 't', '\n' } };
- // Last oat version changed reason: Revert^4 "bss support for inlining BCP into non-BCP".
- static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '2', '5', '\0' } };
+ // Last oat version changed reason: Don't use instrumentation stubs for native methods.
+ static constexpr std::array<uint8_t, 4> kOatVersion { { '2', '2', '7', '\0' } };
static constexpr const char* kDex2OatCmdLineKey = "dex2oat-cmdline";
static constexpr const char* kDebuggableKey = "debuggable";
diff --git a/runtime/oat_file.cc b/runtime/oat_file.cc
index 63778c7..a2000f3 100644
--- a/runtime/oat_file.cc
+++ b/runtime/oat_file.cc
@@ -465,15 +465,23 @@
const VdexFile* vdex_file,
const uint8_t** type_lookup_table_data,
std::string* error_msg) {
- if (type_lookup_table_start == nullptr ||
- reinterpret_cast<const uint32_t*>(type_lookup_table_start)[0] == 0) {
+ if (type_lookup_table_start == nullptr) {
*type_lookup_table_data = nullptr;
return true;
}
- *type_lookup_table_data = type_lookup_table_start + sizeof(uint32_t);
- size_t expected_table_size = TypeLookupTable::RawDataLength(header.class_defs_size_);
+ if (UNLIKELY(!vdex_file->Contains(type_lookup_table_start))) {
+ *error_msg =
+ StringPrintf("In vdex file '%s' found invalid type lookup table pointer %p not in [%p, %p]",
+ vdex_file->GetName().c_str(),
+ type_lookup_table_start,
+ vdex_file->Begin(),
+ vdex_file->End());
+ return false;
+ }
+
size_t found_size = reinterpret_cast<const uint32_t*>(type_lookup_table_start)[0];
+ size_t expected_table_size = TypeLookupTable::RawDataLength(header.class_defs_size_);
if (UNLIKELY(found_size != expected_table_size)) {
*error_msg =
StringPrintf("In vdex file '%s' unexpected type lookup table size: found %zu, expected %zu",
@@ -482,6 +490,13 @@
expected_table_size);
return false;
}
+
+ if (found_size == 0) {
+ *type_lookup_table_data = nullptr;
+ return true;
+ }
+
+ *type_lookup_table_data = type_lookup_table_start + sizeof(uint32_t);
if (UNLIKELY(!vdex_file->Contains(*type_lookup_table_data))) {
*error_msg =
StringPrintf("In vdex file '%s' found invalid type lookup table pointer %p not in [%p, %p]",
@@ -1815,7 +1830,7 @@
store.Put(OatHeader::kCompilerFilter, CompilerFilter::NameOfFilter(CompilerFilter::kVerify));
store.Put(OatHeader::kCompilationReasonKey, "vdex");
store.Put(OatHeader::kConcurrentCopying,
- kUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
+ gUseReadBarrier ? OatHeader::kTrueValue : OatHeader::kFalseValue);
oat_header_.reset(OatHeader::Create(kRuntimeISA,
isa_features.get(),
number_of_dex_files,
@@ -1907,17 +1922,6 @@
reservation,
error_msg);
if (with_dlopen != nullptr) {
- Runtime* runtime = Runtime::Current();
- // The runtime might not be available at this point if we're running
- // dex2oat or oatdump.
- if (runtime != nullptr) {
- size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex();
- Runtime::MadviseFileForRange(madvise_size_limit,
- with_dlopen->Size(),
- with_dlopen->Begin(),
- with_dlopen->End(),
- oat_location);
- }
return with_dlopen;
}
if (kPrintDlOpenErrorMessage) {
@@ -2252,7 +2256,7 @@
return OatFile::OatClass(oat_file_,
ClassStatus::kNotReady,
/* type= */ OatClassType::kNoneCompiled,
- /* bitmap_size= */ 0u,
+ /* num_methods= */ 0u,
/* bitmap_pointer= */ nullptr,
/* methods_pointer= */ nullptr);
}
diff --git a/runtime/oat_file.h b/runtime/oat_file.h
index c1b1acb..fdb4217 100644
--- a/runtime/oat_file.h
+++ b/runtime/oat_file.h
@@ -270,7 +270,7 @@
return OatClass(/* oat_file= */ nullptr,
ClassStatus::kErrorUnresolved,
OatClassType::kNoneCompiled,
- /* bitmap_size= */ 0,
+ /* num_methods= */ 0,
/* bitmap_pointer= */ nullptr,
/* methods_pointer= */ nullptr);
}
diff --git a/runtime/oat_file_assistant.cc b/runtime/oat_file_assistant.cc
index 914d2dd..389479c 100644
--- a/runtime/oat_file_assistant.cc
+++ b/runtime/oat_file_assistant.cc
@@ -16,15 +16,18 @@
#include "oat_file_assistant.h"
-#include <sstream>
-
#include <sys/stat.h>
-#include "zlib.h"
+
+#include <memory>
+#include <sstream>
+#include <vector>
#include "android-base/file.h"
+#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
-
+#include "arch/instruction_set.h"
+#include "base/array_ref.h"
#include "base/compiler_filter.h"
#include "base/file_utils.h"
#include "base/logging.h" // For VLOG.
@@ -44,13 +47,16 @@
#include "gc/space/image_space.h"
#include "image.h"
#include "oat.h"
+#include "oat_file_assistant_context.h"
#include "runtime.h"
#include "scoped_thread_state_change-inl.h"
#include "vdex_file.h"
+#include "zlib.h"
namespace art {
-using android::base::StringPrintf;
+using ::android::base::ConsumePrefix;
+using ::android::base::StringPrintf;
static constexpr const char* kAnonymousDexPrefix = "Anonymous-DexFile@";
static constexpr const char* kVdexExtension = ".vdex";
@@ -82,22 +88,24 @@
const InstructionSet isa,
ClassLoaderContext* context,
bool load_executable,
- bool only_load_trusted_executable)
+ bool only_load_trusted_executable,
+ OatFileAssistantContext* ofa_context)
: OatFileAssistant(dex_location,
isa,
context,
load_executable,
only_load_trusted_executable,
- /*vdex_fd=*/ -1,
- /*oat_fd=*/ -1,
- /*zip_fd=*/ -1) {}
-
+ ofa_context,
+ /*vdex_fd=*/-1,
+ /*oat_fd=*/-1,
+ /*zip_fd=*/-1) {}
OatFileAssistant::OatFileAssistant(const char* dex_location,
const InstructionSet isa,
ClassLoaderContext* context,
bool load_executable,
bool only_load_trusted_executable,
+ OatFileAssistantContext* ofa_context,
int vdex_fd,
int oat_fd,
int zip_fd)
@@ -105,12 +113,12 @@
isa_(isa),
load_executable_(load_executable),
only_load_trusted_executable_(only_load_trusted_executable),
- odex_(this, /*is_oat_location=*/ false),
- oat_(this, /*is_oat_location=*/ true),
- vdex_for_odex_(this, /*is_oat_location=*/ false),
- vdex_for_oat_(this, /*is_oat_location=*/ true),
- dm_for_odex_(this, /*is_oat_location=*/ false),
- dm_for_oat_(this, /*is_oat_location=*/ true),
+ odex_(this, /*is_oat_location=*/false),
+ oat_(this, /*is_oat_location=*/true),
+ vdex_for_odex_(this, /*is_oat_location=*/false),
+ vdex_for_oat_(this, /*is_oat_location=*/true),
+ dm_for_odex_(this, /*is_oat_location=*/false),
+ dm_for_oat_(this, /*is_oat_location=*/true),
zip_fd_(zip_fd) {
CHECK(dex_location != nullptr) << "OatFileAssistant: null dex location";
CHECK_IMPLIES(load_executable, context != nullptr) << "Loading executable without a context";
@@ -127,12 +135,33 @@
dex_location_.assign(dex_location);
+ Runtime* runtime = Runtime::Current();
+
+ if (load_executable_ && runtime == nullptr) {
+ LOG(WARNING) << "OatFileAssistant: Load executable specified, "
+ << "but no active runtime is found. Will not attempt to load executable.";
+ load_executable_ = false;
+ }
+
if (load_executable_ && isa != kRuntimeISA) {
LOG(WARNING) << "OatFileAssistant: Load executable specified, "
<< "but isa is not kRuntimeISA. Will not attempt to load executable.";
load_executable_ = false;
}
+ if (ofa_context == nullptr) {
+ CHECK(runtime != nullptr) << "runtime_options is not provided, and no active runtime is found.";
+ ofa_context_ = std::make_unique<OatFileAssistantContext>(runtime);
+ } else {
+ ofa_context_ = ofa_context;
+ }
+
+ if (runtime == nullptr) {
+ // We need `MemMap` for mapping files. We don't have to initialize it when there is a runtime
+ // because the runtime initializes it.
+ MemMap::Init();
+ }
+
// Get the odex filename.
std::string error_msg;
std::string odex_file_name;
@@ -159,7 +188,11 @@
if (!UseFdToReadFiles()) {
// Get the oat filename.
std::string oat_file_name;
- if (DexLocationToOatFilename(dex_location_, isa_, &oat_file_name, &error_msg)) {
+ if (DexLocationToOatFilename(dex_location_,
+ isa_,
+ GetRuntimeOptions().deny_art_apex_data_files,
+ &oat_file_name,
+ &error_msg)) {
oat_.Reset(oat_file_name, /*use_fd=*/ false);
std::string vdex_file_name = GetVdexFilename(oat_file_name);
vdex_for_oat_.Reset(vdex_file_name, UseFdToReadFiles(), zip_fd, vdex_fd, oat_fd);
@@ -190,6 +223,48 @@
}
}
+std::unique_ptr<OatFileAssistant> OatFileAssistant::Create(
+ const std::string& filename,
+ const std::string& isa_str,
+ const std::string& context_str,
+ bool load_executable,
+ bool only_load_trusted_executable,
+ OatFileAssistantContext* ofa_context,
+ /*out*/ std::unique_ptr<ClassLoaderContext>* context,
+ /*out*/ std::string* error_msg) {
+ InstructionSet isa = GetInstructionSetFromString(isa_str.c_str());
+ if (isa == InstructionSet::kNone) {
+ *error_msg = StringPrintf("Instruction set '%s' is invalid", isa_str.c_str());
+ return nullptr;
+ }
+
+ std::unique_ptr<ClassLoaderContext> tmp_context = ClassLoaderContext::Create(context_str.c_str());
+ if (tmp_context == nullptr) {
+ *error_msg = StringPrintf("Class loader context '%s' is invalid", context_str.c_str());
+ return nullptr;
+ }
+
+ if (!tmp_context->OpenDexFiles(android::base::Dirname(filename.c_str()),
+ /*context_fds=*/{},
+ /*only_read_checksums=*/true)) {
+ *error_msg =
+ StringPrintf("Failed to load class loader context files for '%s' with context '%s'",
+ filename.c_str(),
+ context_str.c_str());
+ return nullptr;
+ }
+
+ auto assistant = std::make_unique<OatFileAssistant>(filename.c_str(),
+ isa,
+ tmp_context.get(),
+ load_executable,
+ only_load_trusted_executable,
+ ofa_context);
+
+ *context = std::move(tmp_context);
+ return assistant;
+}
+
bool OatFileAssistant::UseFdToReadFiles() {
return zip_fd_ >= 0;
}
@@ -199,11 +274,9 @@
// specified by the user. This is okay, because the boot class path should
// be the same for all ISAs.
// TODO: Can we verify the boot class path is the same for all ISAs?
- Runtime* runtime = Runtime::Current();
- ClassLinker* class_linker = runtime->GetClassLinker();
- const auto& boot_class_path = class_linker->GetBootClassPath();
- for (size_t i = 0; i < boot_class_path.size(); i++) {
- if (boot_class_path[i]->GetLocation() == dex_location_) {
+ for (const std::string& boot_class_path_location :
+ GetRuntimeOptions().boot_class_path_locations) {
+ if (boot_class_path_location == dex_location_) {
VLOG(oat) << "Dex location " << dex_location_ << " is in boot class path";
return true;
}
@@ -211,19 +284,61 @@
return false;
}
-int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target,
+OatFileAssistant::DexOptTrigger OatFileAssistant::GetDexOptTrigger(
+ CompilerFilter::Filter target_compiler_filter, bool profile_changed, bool downgrade) {
+ if (downgrade) {
+ // The caller's intention is to downgrade the compiler filter. We should only re-compile if the
+ // target compiler filter is worse than the current one.
+ return DexOptTrigger{.targetFilterIsWorse = true};
+ }
+
+ // This is the usual case. The caller's intention is to see if a better oat file can be generated.
+ DexOptTrigger dexopt_trigger{.targetFilterIsBetter = true, .primaryBootImageBecomesUsable = true};
+ if (profile_changed && CompilerFilter::DependsOnProfile(target_compiler_filter)) {
+ // Since the profile has been changed, we should re-compile even if the compilation does not
+ // make the compiler filter better.
+ dexopt_trigger.targetFilterIsSame = true;
+ }
+ return dexopt_trigger;
+}
+
+int OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
bool profile_changed,
bool downgrade) {
OatFileInfo& info = GetBestInfo();
- DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target,
- profile_changed,
- downgrade);
+ DexOptNeeded dexopt_needed = info.GetDexOptNeeded(
+ target_compiler_filter, GetDexOptTrigger(target_compiler_filter, profile_changed, downgrade));
+ if (dexopt_needed != kNoDexOptNeeded && (&info == &dm_for_oat_ || &info == &dm_for_odex_)) {
+ // The usable vdex file is in the DM file. This information cannot be encoded in the integer.
+ // Return kDex2OatFromScratch so that neither the vdex in the "oat" location nor the vdex in the
+ // "odex" location will be picked by installd.
+ return kDex2OatFromScratch;
+ }
if (info.IsOatLocation() || dexopt_needed == kDex2OatFromScratch) {
return dexopt_needed;
}
return -dexopt_needed;
}
+bool OatFileAssistant::GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
+ DexOptTrigger dexopt_trigger,
+ /*out*/ DexOptStatus* dexopt_status) {
+ OatFileInfo& info = GetBestInfo();
+ DexOptNeeded dexopt_needed = info.GetDexOptNeeded(target_compiler_filter, dexopt_trigger);
+ if (info.IsUseable()) {
+ if (&info == &dm_for_oat_ || &info == &dm_for_odex_) {
+ dexopt_status->location_ = kLocationDm;
+ } else if (info.IsOatLocation()) {
+ dexopt_status->location_ = kLocationOat;
+ } else {
+ dexopt_status->location_ = kLocationOdex;
+ }
+ } else {
+ dexopt_status->location_ = kLocationNoneOrError;
+ }
+ return dexopt_needed != kNoDexOptNeeded;
+}
+
bool OatFileAssistant::IsUpToDate() {
return GetBestInfo().Status() == kOatUpToDate;
}
@@ -419,9 +534,7 @@
// compiled code and are otherwise okay, we should return something like
// kOatRelocationOutOfDate. If they don't contain compiled code, the read
// barrier state doesn't matter.
- const bool is_cc = file.GetOatHeader().IsConcurrentCopying();
- constexpr bool kRuntimeIsCC = kUseReadBarrier;
- if (is_cc != kRuntimeIsCC) {
+ if (file.GetOatHeader().IsConcurrentCopying() != gUseReadBarrier) {
return kOatCannotOpen;
}
@@ -443,7 +556,8 @@
VLOG(oat) << "Oat image checksum does not match image checksum.";
return kOatBootImageOutOfDate;
}
- if (!gc::space::ImageSpace::ValidateApexVersions(file, &error_msg)) {
+ if (!gc::space::ImageSpace::ValidateApexVersions(
+ file, GetOatFileAssistantContext()->GetApexVersions(), &error_msg)) {
VLOG(oat) << error_msg;
return kOatBootImageOutOfDate;
}
@@ -454,9 +568,9 @@
// zip_file_only_contains_uncompressed_dex_ is only set during fetching the dex checksums.
DCHECK(required_dex_checksums_attempted_);
if (only_load_trusted_executable_ &&
- !LocationIsTrusted(file.GetLocation(), !Runtime::Current()->DenyArtApexDataFiles()) &&
- file.ContainsDexCode() &&
- zip_file_only_contains_uncompressed_dex_) {
+ !LocationIsTrusted(file.GetLocation(),
+ !GetRuntimeOptions().deny_art_apex_data_files) &&
+ file.ContainsDexCode() && zip_file_only_contains_uncompressed_dex_) {
LOG(ERROR) << "Not loading "
<< dex_location_
<< ": oat file has dex code, but APK has uncompressed dex code";
@@ -474,6 +588,11 @@
InstructionSet isa,
/* out */ std::string* dex_location,
/* out */ std::string* vdex_filename) {
+ // Normally, OatFileAssistant should not assume that there is an active runtime. However, we
+ // reference the runtime here. This is okay because we are in a static function that is unrelated
+ // to other parts of OatFileAssistant.
+ DCHECK(Runtime::Current() != nullptr);
+
uint32_t checksum = adler32(0L, Z_NULL, 0);
for (const DexFile::Header* header : headers) {
checksum = adler32_combine(checksum,
@@ -571,13 +690,23 @@
InstructionSet isa,
std::string* oat_filename,
std::string* error_msg) {
+ DCHECK(Runtime::Current() != nullptr);
+ return DexLocationToOatFilename(
+ location, isa, Runtime::Current()->DenyArtApexDataFiles(), oat_filename, error_msg);
+}
+
+bool OatFileAssistant::DexLocationToOatFilename(const std::string& location,
+ InstructionSet isa,
+ bool deny_art_apex_data_files,
+ std::string* oat_filename,
+ std::string* error_msg) {
CHECK(oat_filename != nullptr);
CHECK(error_msg != nullptr);
// Check if `location` could have an oat file in the ART APEX data directory. If so, and the
// file exists, use it.
const std::string apex_data_file = GetApexDataOdexFilename(location, isa);
- if (!apex_data_file.empty() && !Runtime::Current()->DenyArtApexDataFiles()) {
+ if (!apex_data_file.empty() && !deny_art_apex_data_files) {
if (OS::FileExists(apex_data_file.c_str(), /*check_file_type=*/true)) {
*oat_filename = apex_data_file;
return true;
@@ -640,6 +769,105 @@
return required_dex_checksums_found_ ? &cached_required_dex_checksums_ : nullptr;
}
+bool OatFileAssistant::ValidateBootClassPathChecksums(OatFileAssistantContext* ofa_context,
+ InstructionSet isa,
+ std::string_view oat_checksums,
+ std::string_view oat_boot_class_path,
+ /*out*/ std::string* error_msg) {
+ const std::vector<std::string>& bcp_locations =
+ ofa_context->GetRuntimeOptions().boot_class_path_locations;
+
+ if (oat_checksums.empty() || oat_boot_class_path.empty()) {
+ *error_msg = oat_checksums.empty() ? "Empty checksums" : "Empty boot class path";
+ return false;
+ }
+
+ size_t oat_bcp_size = gc::space::ImageSpace::CheckAndCountBCPComponents(
+ oat_boot_class_path, ArrayRef<const std::string>(bcp_locations), error_msg);
+ DCHECK_LE(oat_bcp_size, bcp_locations.size());
+ if (oat_bcp_size == static_cast<size_t>(-1)) {
+ DCHECK(!error_msg->empty());
+ return false;
+ }
+
+ size_t bcp_index = 0;
+ size_t boot_image_index = 0;
+ bool found_d = false;
+
+ while (bcp_index < oat_bcp_size) {
+ static_assert(gc::space::ImageSpace::kImageChecksumPrefix == 'i', "Format prefix check");
+ static_assert(gc::space::ImageSpace::kDexFileChecksumPrefix == 'd', "Format prefix check");
+ if (StartsWith(oat_checksums, "i") && !found_d) {
+ const std::vector<OatFileAssistantContext::BootImageInfo>& boot_image_info_list =
+ ofa_context->GetBootImageInfoList(isa);
+ if (boot_image_index >= boot_image_info_list.size()) {
+ *error_msg = StringPrintf("Missing boot image for %s, remaining checksums: %s",
+ bcp_locations[bcp_index].c_str(),
+ std::string(oat_checksums).c_str());
+ return false;
+ }
+
+ const OatFileAssistantContext::BootImageInfo& boot_image_info =
+ boot_image_info_list[boot_image_index];
+ if (!ConsumePrefix(&oat_checksums, boot_image_info.checksum)) {
+ *error_msg = StringPrintf("Image checksum mismatch, expected %s to start with %s",
+ std::string(oat_checksums).c_str(),
+ boot_image_info.checksum.c_str());
+ return false;
+ }
+
+ bcp_index += boot_image_info.component_count;
+ boot_image_index++;
+ } else if (StartsWith(oat_checksums, "d")) {
+ found_d = true;
+ const std::vector<std::string>* bcp_checksums =
+ ofa_context->GetBcpChecksums(bcp_index, error_msg);
+ if (bcp_checksums == nullptr) {
+ return false;
+ }
+ oat_checksums.remove_prefix(1u);
+ for (const std::string& checksum : *bcp_checksums) {
+ if (!ConsumePrefix(&oat_checksums, checksum)) {
+ *error_msg = StringPrintf(
+ "Dex checksum mismatch for bootclasspath file %s, expected %s to start with %s",
+ bcp_locations[bcp_index].c_str(),
+ std::string(oat_checksums).c_str(),
+ checksum.c_str());
+ return false;
+ }
+ }
+
+ bcp_index++;
+ } else {
+ *error_msg = StringPrintf("Unexpected checksums, expected %s to start with %s",
+ std::string(oat_checksums).c_str(),
+ found_d ? "'d'" : "'i' or 'd'");
+ return false;
+ }
+
+ if (bcp_index < oat_bcp_size) {
+ if (!ConsumePrefix(&oat_checksums, ":")) {
+ if (oat_checksums.empty()) {
+ *error_msg =
+ StringPrintf("Checksum too short, missing %zu components", oat_bcp_size - bcp_index);
+ } else {
+ *error_msg = StringPrintf("Missing ':' separator at start of %s",
+ std::string(oat_checksums).c_str());
+ }
+ return false;
+ }
+ }
+ }
+
+ if (!oat_checksums.empty()) {
+ *error_msg =
+ StringPrintf("Checksum too long, unexpected tail: %s", std::string(oat_checksums).c_str());
+ return false;
+ }
+
+ return true;
+}
+
bool OatFileAssistant::ValidateBootClassPathChecksums(const OatFile& oat_file) {
// Get the checksums and the BCP from the oat file.
const char* oat_boot_class_path_checksums =
@@ -649,45 +877,26 @@
if (oat_boot_class_path_checksums == nullptr || oat_boot_class_path == nullptr) {
return false;
}
- std::string_view oat_boot_class_path_checksums_view(oat_boot_class_path_checksums);
- std::string_view oat_boot_class_path_view(oat_boot_class_path);
- if (oat_boot_class_path_view == cached_boot_class_path_ &&
- oat_boot_class_path_checksums_view == cached_boot_class_path_checksums_) {
- return true;
- }
- Runtime* runtime = Runtime::Current();
std::string error_msg;
- bool result = false;
- // Fast path when the runtime boot classpath cheksums and boot classpath
- // locations directly match.
- if (oat_boot_class_path_checksums_view == runtime->GetBootClassPathChecksums() &&
- isa_ == kRuntimeISA &&
- oat_boot_class_path_view == android::base::Join(runtime->GetBootClassPathLocations(), ":")) {
- result = true;
- } else {
- result = gc::space::ImageSpace::VerifyBootClassPathChecksums(
- oat_boot_class_path_checksums_view,
- oat_boot_class_path_view,
- ArrayRef<const std::string>(runtime->GetImageLocations()),
- ArrayRef<const std::string>(runtime->GetBootClassPathLocations()),
- ArrayRef<const std::string>(runtime->GetBootClassPath()),
- ArrayRef<const int>(runtime->GetBootClassPathFds()),
- isa_,
- &error_msg);
- }
+ bool result = ValidateBootClassPathChecksums(GetOatFileAssistantContext(),
+ isa_,
+ oat_boot_class_path_checksums,
+ oat_boot_class_path,
+ &error_msg);
if (!result) {
VLOG(oat) << "Failed to verify checksums of oat file " << oat_file.GetLocation()
<< " error: " << error_msg;
return false;
}
- // This checksum has been validated, so save it.
- cached_boot_class_path_ = oat_boot_class_path_view;
- cached_boot_class_path_checksums_ = oat_boot_class_path_checksums_view;
return true;
}
+bool OatFileAssistant::IsPrimaryBootImageUsable() {
+ return !GetOatFileAssistantContext()->GetBootImageInfoList(isa_).empty();
+}
+
OatFileAssistant::OatFileInfo& OatFileAssistant::GetBestInfo() {
ScopedTrace trace("GetBestInfo");
// TODO(calin): Document the side effects of class loading when
@@ -808,14 +1017,16 @@
}
OatFileAssistant::DexOptNeeded OatFileAssistant::OatFileInfo::GetDexOptNeeded(
- CompilerFilter::Filter target,
- bool profile_changed,
- bool downgrade) {
-
+ CompilerFilter::Filter target_compiler_filter, const DexOptTrigger dexopt_trigger) {
if (IsUseable()) {
- return CompilerFilterIsOkay(target, profile_changed, downgrade)
- ? kNoDexOptNeeded
- : kDex2OatForFilter;
+ return ShouldRecompileForFilter(target_compiler_filter, dexopt_trigger) ? kDex2OatForFilter :
+ kNoDexOptNeeded;
+ }
+
+ // In this case, the oat file is not usable. If the caller doesn't seek for a better compiler
+ // filter (e.g., the caller wants to downgrade), then we should not recompile.
+ if (!dexopt_trigger.targetFilterIsBetter) {
+ return kNoDexOptNeeded;
}
if (Status() == kOatBootImageOutOfDate) {
@@ -840,7 +1051,8 @@
return nullptr;
}
- if (LocationIsOnArtApexData(filename_) && Runtime::Current()->DenyArtApexDataFiles()) {
+ if (LocationIsOnArtApexData(filename_) &&
+ oat_file_assistant_->GetRuntimeOptions().deny_art_apex_data_files) {
LOG(WARNING) << "OatFileAssistant rejected file " << filename_
<< ": ART apexdata is untrusted.";
return nullptr;
@@ -934,25 +1146,24 @@
return file_.get();
}
-bool OatFileAssistant::OatFileInfo::CompilerFilterIsOkay(
- CompilerFilter::Filter target, bool profile_changed, bool downgrade) {
+bool OatFileAssistant::OatFileInfo::ShouldRecompileForFilter(CompilerFilter::Filter target,
+ const DexOptTrigger dexopt_trigger) {
const OatFile* file = GetFile();
- if (file == nullptr) {
- return false;
- }
+ DCHECK(file != nullptr);
CompilerFilter::Filter current = file->GetCompilerFilter();
- if (profile_changed && CompilerFilter::DependsOnProfile(current)) {
- VLOG(oat) << "Compiler filter not okay because Profile changed";
- return false;
+ if (dexopt_trigger.targetFilterIsBetter && CompilerFilter::IsBetter(target, current)) {
+ return true;
+ }
+ if (dexopt_trigger.targetFilterIsSame && current == target) {
+ return true;
+ }
+ if (dexopt_trigger.targetFilterIsWorse && CompilerFilter::IsBetter(current, target)) {
+ return true;
}
- if (downgrade) {
- return !CompilerFilter::IsBetter(current, target);
- }
-
- if (CompilerFilter::DependsOnImageChecksum(current) &&
- CompilerFilter::IsAsGoodAs(current, target)) {
+ if (dexopt_trigger.primaryBootImageBecomesUsable &&
+ CompilerFilter::DependsOnImageChecksum(current)) {
// If the oat file has been compiled without an image, and the runtime is
// now running with an image loaded from disk, return that we need to
// re-compile. The recompilation will generate a better oat file, and with an app
@@ -961,13 +1172,13 @@
file->GetOatHeader().GetStoreValueByKey(OatHeader::kBootClassPathChecksumsKey);
if (oat_boot_class_path_checksums != nullptr &&
!StartsWith(oat_boot_class_path_checksums, "i") &&
- !Runtime::Current()->HasImageWithProfile()) {
+ oat_file_assistant_->IsPrimaryBootImageUsable()) {
DCHECK(!file->GetOatHeader().RequiresImage());
- return false;
+ return true;
}
}
- return CompilerFilter::IsAsGoodAs(current, target);
+ return false;
}
bool OatFileAssistant::ClassLoaderContextIsOkay(const OatFile& oat_file) const {
@@ -1044,17 +1255,19 @@
// TODO(calin): we could provide a more refined status here
// (e.g. run from uncompressed apk, run with vdex but not oat etc). It will allow us to
// track more experiments but adds extra complexity.
-void OatFileAssistant::GetOptimizationStatus(
- const std::string& filename,
- InstructionSet isa,
- std::string* out_compilation_filter,
- std::string* out_compilation_reason) {
+void OatFileAssistant::GetOptimizationStatus(const std::string& filename,
+ InstructionSet isa,
+ std::string* out_compilation_filter,
+ std::string* out_compilation_reason,
+ OatFileAssistantContext* ofa_context) {
// It may not be possible to load an oat file executable (e.g., selinux restrictions). Load
// non-executable and check the status manually.
OatFileAssistant oat_file_assistant(filename.c_str(),
isa,
- /* context= */ nullptr,
- /*load_executable=*/ false);
+ /*context=*/nullptr,
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/false,
+ ofa_context);
std::string out_odex_location; // unused
std::string out_odex_status; // unused
oat_file_assistant.GetOptimizationStatus(
@@ -1090,28 +1303,25 @@
OatStatus status = oat_file_info.Status();
const char* reason = oat_file->GetCompilationReason();
*out_compilation_reason = reason == nullptr ? "unknown" : reason;
+
+ // If the oat file is invalid, the vdex file will be picked, so the status is `kOatUpToDate`. If
+ // the vdex file is also invalid, then either `oat_file` is nullptr, or `status` is
+ // `kOatDexOutOfDate`.
+ DCHECK(status == kOatUpToDate || status == kOatDexOutOfDate);
+
switch (status) {
case kOatUpToDate:
*out_compilation_filter = CompilerFilter::NameOfFilter(oat_file->GetCompilerFilter());
*out_odex_status = "up-to-date";
return;
- case kOatCannotOpen: // This should never happen, but be robust.
- *out_compilation_filter = "error";
- *out_compilation_reason = "error";
- // This mostly happens when we cannot open the vdex file,
- // or the file is corrupt.
- *out_odex_status = "io-error-or-corruption";
- return;
-
+ case kOatCannotOpen:
case kOatBootImageOutOfDate:
- *out_compilation_filter = "run-from-apk-fallback";
- *out_odex_status = "boot-image-more-recent";
- return;
-
case kOatContextOutOfDate:
- *out_compilation_filter = "run-from-apk-fallback";
- *out_odex_status = "context-mismatch";
+ // These should never happen, but be robust.
+ *out_compilation_filter = "unexpected";
+ *out_compilation_reason = "unexpected";
+ *out_odex_status = "unexpected";
return;
case kOatDexOutOfDate:
diff --git a/runtime/oat_file_assistant.h b/runtime/oat_file_assistant.h
index c243cc3..ce069d2 100644
--- a/runtime/oat_file_assistant.h
+++ b/runtime/oat_file_assistant.h
@@ -19,16 +19,19 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <sstream>
#include <string>
+#include <variant>
-#include "base/compiler_filter.h"
#include "arch/instruction_set.h"
+#include "base/compiler_filter.h"
#include "base/os.h"
#include "base/scoped_flock.h"
#include "base/unix_file/fd_file.h"
#include "class_loader_context.h"
#include "oat_file.h"
+#include "oat_file_assistant_context.h"
namespace art {
@@ -89,6 +92,45 @@
kOatUpToDate,
};
+ // A bit field to represent the conditions where dexopt should be performed.
+ struct DexOptTrigger {
+ // Dexopt should be performed if the target compiler filter is better than the current compiler
+ // filter. See `CompilerFilter::IsBetter`.
+ bool targetFilterIsBetter : 1;
+ // Dexopt should be performed if the target compiler filter is the same as the current compiler
+ // filter.
+ bool targetFilterIsSame : 1;
+ // Dexopt should be performed if the target compiler filter is worse than the current compiler
+ // filter. See `CompilerFilter::IsBetter`.
+ bool targetFilterIsWorse : 1;
+ // Dexopt should be performed if the current oat file was compiled without a primary image,
+ // and the runtime is now running with a primary image loaded from disk.
+ bool primaryBootImageBecomesUsable : 1;
+ };
+
+ // Represents the location of the current oat file and/or vdex file.
+ enum Location {
+ // Does not exist, or an error occurs.
+ kLocationNoneOrError = 0,
+ // In the global "dalvik-cache" folder.
+ kLocationOat = 1,
+ // In the "oat" folder next to the dex file.
+ kLocationOdex = 2,
+ // In the DM file. This means the only usable file is the vdex file.
+ kLocationDm = 3,
+ };
+
+ // Represents the status of the current oat file and/or vdex file.
+ class DexOptStatus {
+ public:
+ Location GetLocation() { return location_; }
+ bool IsVdexUsable() { return location_ != kLocationNoneOrError; }
+
+ private:
+ Location location_ = kLocationNoneOrError;
+ friend class OatFileAssistant;
+ };
+
// Constructs an OatFileAssistant object to assist the oat file
// corresponding to the given dex location with the target instruction set.
//
@@ -110,11 +152,15 @@
// only_load_trusted_executable should be true if the caller intends to have
// only oat files from trusted locations loaded executable. See IsTrustedLocation() for
// details on trusted locations.
+ //
+ // runtime_options should be provided with all the required fields filled if the caller intends to
+ // use OatFileAssistant without a runtime.
OatFileAssistant(const char* dex_location,
const InstructionSet isa,
ClassLoaderContext* context,
bool load_executable,
- bool only_load_trusted_executable = false);
+ bool only_load_trusted_executable = false,
+ OatFileAssistantContext* ofa_context = nullptr);
// Similar to this(const char*, const InstructionSet, bool), however, if a valid zip_fd is
// provided, vdex, oat, and zip files will be read from vdex_fd, oat_fd and zip_fd respectively.
@@ -124,10 +170,25 @@
ClassLoaderContext* context,
bool load_executable,
bool only_load_trusted_executable,
+ OatFileAssistantContext* ofa_context,
int vdex_fd,
int oat_fd,
int zip_fd);
+ // A convenient factory function that accepts ISA, class loader context, and compiler filter in
+ // strings. Returns the created instance and ClassLoaderContext on success, or returns nullptr and
+ // outputs an error message if it fails to parse the input strings.
+ // The returned ClassLoaderContext must live at least as long as the OatFileAssistant.
+ static std::unique_ptr<OatFileAssistant> Create(
+ const std::string& filename,
+ const std::string& isa_str,
+ const std::string& context_str,
+ bool load_executable,
+ bool only_load_trusted_executable,
+ OatFileAssistantContext* ofa_context,
+ /*out*/ std::unique_ptr<ClassLoaderContext>* context,
+ /*out*/ std::string* error_msg);
+
// Returns true if the dex location refers to an element of the boot class
// path.
bool IsInBootClassPath();
@@ -148,10 +209,18 @@
// Returns a positive status code if the status refers to the oat file in
// the oat location. Returns a negative status code if the status refers to
// the oat file in the odex location.
+ //
+ // Deprecated. Use the other overload.
int GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
bool profile_changed = false,
bool downgrade = false);
+ // Returns true if dexopt needs to be performed with respect to the given target compilation
+ // filter and dexopt trigger. Also returns the status of the current oat file and/or vdex file.
+ bool GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
+ const DexOptTrigger dexopt_trigger,
+ /*out*/ DexOptStatus* dexopt_status);
+
// Returns true if there is up-to-date code for this dex location,
// irrespective of the compiler filter of the up-to-date code.
bool IsUpToDate();
@@ -176,7 +245,7 @@
// - out_compilation_reason: the optimization reason. The reason might
// be "unknown" if the compiler artifacts were not annotated during optimizations.
// - out_odex_status: a human readable refined status of the validity of the odex file.
- // E.g. up-to-date, boot-image-more-recent, apk-more-recent.
+ // Possible values are: "up-to-date", "apk-more-recent", and "io-error-no-oat".
//
// This method will try to mimic the runtime effect of loading the dex file.
// For example, if there is no usable oat file, the compiler filter will be set
@@ -189,7 +258,8 @@
static void GetOptimizationStatus(const std::string& filename,
InstructionSet isa,
std::string* out_compilation_filter,
- std::string* out_compilation_reason);
+ std::string* out_compilation_reason,
+ OatFileAssistantContext* ofa_context = nullptr);
// Open and returns an image space associated with the oat file.
static std::unique_ptr<gc::space::ImageSpace> OpenImageSpace(const OatFile* oat_file);
@@ -253,15 +323,28 @@
// Returns false on error, in which case error_msg describes the error and
// oat_filename is not changed.
// Neither oat_filename nor error_msg may be null.
+ //
+ // Calling this function requires an active runtime.
static bool DexLocationToOatFilename(const std::string& location,
InstructionSet isa,
std::string* oat_filename,
std::string* error_msg);
+ // Same as above, but also takes `deny_art_apex_data_files` from input.
+ //
+ // Calling this function does not require an active runtime.
+ static bool DexLocationToOatFilename(const std::string& location,
+ InstructionSet isa,
+ bool deny_art_apex_data_files,
+ std::string* oat_filename,
+ std::string* error_msg);
+
// Computes the dex location and vdex filename. If the data directory of the process
// is known, creates an absolute path in that directory and tries to infer path
// of a corresponding vdex file. Otherwise only creates a basename dex_location
// from the combined checksums. Returns true if all out-arguments have been set.
+ //
+ // Calling this function requires an active runtime.
static bool AnonymousDexVdexLocation(const std::vector<const DexFile::Header*>& dex_headers,
InstructionSet isa,
/* out */ std::string* dex_location,
@@ -273,6 +356,16 @@
bool ClassLoaderContextIsOkay(const OatFile& oat_file) const;
+ // Validates the boot class path checksum of an OatFile.
+ bool ValidateBootClassPathChecksums(const OatFile& oat_file);
+
+ // Validates the given bootclasspath and bootclasspath checksums found in an oat header.
+ static bool ValidateBootClassPathChecksums(OatFileAssistantContext* ofa_context,
+ InstructionSet isa,
+ std::string_view oat_checksums,
+ std::string_view oat_boot_class_path,
+ /*out*/ std::string* error_msg);
+
private:
class OatFileInfo {
public:
@@ -298,15 +391,10 @@
// Returns the status of this oat file.
OatStatus Status();
- // Return the DexOptNeeded value for this oat file with respect to the
- // given target_compilation_filter.
- // profile_changed should be true to indicate the profile has recently
- // changed for this dex location.
- // downgrade should be true if the purpose of dexopt is to downgrade the
- // compiler filter.
+ // Return the DexOptNeeded value for this oat file with respect to the given target compilation
+ // filter and dexopt trigger.
DexOptNeeded GetDexOptNeeded(CompilerFilter::Filter target_compiler_filter,
- bool profile_changed,
- bool downgrade);
+ const DexOptTrigger dexopt_trigger);
// Returns the loaded file.
// Loads the file if needed. Returns null if the file failed to load.
@@ -339,13 +427,10 @@
std::unique_ptr<OatFile> ReleaseFileForUse();
private:
- // Returns true if the compiler filter used to generate the file is at
- // least as good as the given target filter. profile_changed should be
- // true to indicate the profile has recently changed for this dex
- // location.
- // downgrade should be true if the purpose of dexopt is to downgrade the
- // compiler filter.
- bool CompilerFilterIsOkay(CompilerFilter::Filter target, bool profile_changed, bool downgrade);
+ // Returns true if the oat file is usable but at least one dexopt trigger is matched. This
+ // function should only be called if the oat file is usable.
+ bool ShouldRecompileForFilter(CompilerFilter::Filter target,
+ const DexOptTrigger dexopt_trigger);
// Release the loaded oat file.
// Returns null if the oat file hasn't been loaded.
@@ -409,8 +494,32 @@
// dex_location_ dex file.
const std::vector<uint32_t>* GetRequiredDexChecksums();
- // Validates the boot class path checksum of an OatFile.
- bool ValidateBootClassPathChecksums(const OatFile& oat_file);
+ // Returns whether there is at least one boot image usable.
+ bool IsPrimaryBootImageUsable();
+
+ // Returns the trigger for the deprecated overload of `GetDexOptNeeded`.
+ //
+ // Deprecated. Do not use in new code.
+ DexOptTrigger GetDexOptTrigger(CompilerFilter::Filter target_compiler_filter,
+ bool profile_changed,
+ bool downgrade);
+
+ // Returns the pointer to the owned or unowned instance of OatFileAssistantContext.
+ OatFileAssistantContext* GetOatFileAssistantContext() {
+ if (std::holds_alternative<OatFileAssistantContext*>(ofa_context_)) {
+ return std::get<OatFileAssistantContext*>(ofa_context_);
+ } else {
+ return std::get<std::unique_ptr<OatFileAssistantContext>>(ofa_context_).get();
+ }
+ }
+
+ // The runtime options taken from the active runtime or the input.
+ //
+ // All member functions should get runtime options from this variable rather than referencing the
+ // active runtime. This is to allow OatFileAssistant to function without an active runtime.
+ const OatFileAssistantContext::RuntimeOptions& GetRuntimeOptions() {
+ return GetOatFileAssistantContext()->GetRuntimeOptions();
+ }
std::string dex_location_;
@@ -459,8 +568,8 @@
// File descriptor corresponding to apk, dex file, or zip.
int zip_fd_;
- std::string cached_boot_class_path_;
- std::string cached_boot_class_path_checksums_;
+ // Owned or unowned instance of OatFileAssistantContext.
+ std::variant<std::unique_ptr<OatFileAssistantContext>, OatFileAssistantContext*> ofa_context_;
friend class OatFileAssistantTest;
diff --git a/runtime/oat_file_assistant_context.cc b/runtime/oat_file_assistant_context.cc
new file mode 100644
index 0000000..becc329
--- /dev/null
+++ b/runtime/oat_file_assistant_context.cc
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2022 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 "oat_file_assistant_context.h"
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "android-base/logging.h"
+#include "android-base/stringprintf.h"
+#include "arch/instruction_set.h"
+#include "base/array_ref.h"
+#include "base/logging.h"
+#include "base/mem_map.h"
+#include "class_linker.h"
+#include "dex/art_dex_file_loader.h"
+#include "gc/heap.h"
+#include "gc/space/image_space.h"
+
+namespace art {
+
+using ::android::base::StringPrintf;
+using ::art::gc::space::ImageSpace;
+
+OatFileAssistantContext::OatFileAssistantContext(
+ std::unique_ptr<OatFileAssistantContext::RuntimeOptions> runtime_options)
+ : runtime_options_(std::move(runtime_options)) {
+ DCHECK_EQ(runtime_options_->boot_class_path.size(),
+ runtime_options_->boot_class_path_locations.size());
+ DCHECK_IMPLIES(
+ runtime_options_->boot_class_path_fds != nullptr,
+ runtime_options_->boot_class_path.size() == runtime_options_->boot_class_path_fds->size());
+ // Opening dex files and boot images require MemMap.
+ MemMap::Init();
+}
+
+OatFileAssistantContext::OatFileAssistantContext(Runtime* runtime)
+ : OatFileAssistantContext(std::make_unique<OatFileAssistantContext::RuntimeOptions>(
+ OatFileAssistantContext::RuntimeOptions{
+ .image_locations = runtime->GetImageLocations(),
+ .boot_class_path = runtime->GetBootClassPath(),
+ .boot_class_path_locations = runtime->GetBootClassPathLocations(),
+ .boot_class_path_fds = !runtime->GetBootClassPathFds().empty() ?
+ &runtime->GetBootClassPathFds() :
+ nullptr,
+ .deny_art_apex_data_files = runtime->DenyArtApexDataFiles(),
+ })) {
+ // Fetch boot image info from the runtime.
+ std::vector<BootImageInfo>& boot_image_info_list = boot_image_info_list_by_isa_[kRuntimeISA];
+ for (const ImageSpace* image_space : runtime->GetHeap()->GetBootImageSpaces()) {
+ // We only need the checksum of the first component for each boot image. They are in image
+ // spaces that have a non-zero component count.
+ if (image_space->GetComponentCount() > 0) {
+ BootImageInfo& boot_image_info = boot_image_info_list.emplace_back();
+ boot_image_info.component_count = image_space->GetComponentCount();
+ ImageSpace::AppendImageChecksum(image_space->GetComponentCount(),
+ image_space->GetImageHeader().GetImageChecksum(),
+ &boot_image_info.checksum);
+ }
+ }
+
+ // Fetch BCP checksums from the runtime.
+ size_t bcp_index = 0;
+ std::vector<std::string>* current_bcp_checksums = nullptr;
+ for (const DexFile* dex_file : runtime->GetClassLinker()->GetBootClassPath()) {
+ if (!DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str())) {
+ DCHECK_LT(bcp_index, runtime_options_->boot_class_path.size());
+ current_bcp_checksums = &bcp_checksums_by_index_[bcp_index++];
+ }
+ DCHECK_NE(current_bcp_checksums, nullptr);
+ current_bcp_checksums->push_back(StringPrintf("/%08x", dex_file->GetLocationChecksum()));
+ }
+ DCHECK_EQ(bcp_index, runtime_options_->boot_class_path.size());
+
+ // Fetch APEX versions from the runtime.
+ apex_versions_ = runtime->GetApexVersions();
+}
+
+const OatFileAssistantContext::RuntimeOptions& OatFileAssistantContext::GetRuntimeOptions() const {
+ return *runtime_options_;
+}
+
+bool OatFileAssistantContext::FetchAll(std::string* error_msg) {
+ std::vector<InstructionSet> isas = GetSupportedInstructionSets(error_msg);
+ if (isas.empty()) {
+ return false;
+ }
+ for (InstructionSet isa : isas) {
+ GetBootImageInfoList(isa);
+ }
+ for (size_t i = 0; i < runtime_options_->boot_class_path.size(); i++) {
+ if (GetBcpChecksums(i, error_msg) == nullptr) {
+ return false;
+ }
+ }
+ GetApexVersions();
+ return true;
+}
+
+const std::vector<OatFileAssistantContext::BootImageInfo>&
+OatFileAssistantContext::GetBootImageInfoList(InstructionSet isa) {
+ if (auto it = boot_image_info_list_by_isa_.find(isa); it != boot_image_info_list_by_isa_.end()) {
+ return it->second;
+ }
+
+ ImageSpace::BootImageLayout layout(
+ ArrayRef<const std::string>(runtime_options_->image_locations),
+ ArrayRef<const std::string>(runtime_options_->boot_class_path),
+ ArrayRef<const std::string>(runtime_options_->boot_class_path_locations),
+ runtime_options_->boot_class_path_fds != nullptr ?
+ ArrayRef<const int>(*runtime_options_->boot_class_path_fds) :
+ ArrayRef<const int>(),
+ /*boot_class_path_image_fds=*/ArrayRef<const int>(),
+ /*boot_class_path_vdex_fds=*/ArrayRef<const int>(),
+ /*boot_class_path_oat_fds=*/ArrayRef<const int>(),
+ &GetApexVersions());
+
+ std::string error_msg;
+ if (!layout.LoadFromSystem(isa, /*allow_in_memory_compilation=*/false, &error_msg)) {
+ // At this point, `layout` contains a subset of boot images that can be loaded.
+ VLOG(oat) << "Some error occurred when loading boot images for oat file validation: "
+ << error_msg;
+ }
+
+ std::vector<BootImageInfo>& boot_image_info_list = boot_image_info_list_by_isa_[isa];
+ for (const ImageSpace::BootImageLayout::ImageChunk& chunk : layout.GetChunks()) {
+ BootImageInfo& boot_image_info = boot_image_info_list.emplace_back();
+ boot_image_info.component_count = chunk.component_count;
+ ImageSpace::AppendImageChecksum(
+ chunk.component_count, chunk.checksum, &boot_image_info.checksum);
+ }
+ return boot_image_info_list;
+}
+
+const std::vector<std::string>* OatFileAssistantContext::GetBcpChecksums(size_t bcp_index,
+ std::string* error_msg) {
+ DCHECK_LT(bcp_index, runtime_options_->boot_class_path.size());
+
+ if (auto it = bcp_checksums_by_index_.find(bcp_index); it != bcp_checksums_by_index_.end()) {
+ return &it->second;
+ }
+
+ std::vector<uint32_t> checksums;
+ std::vector<std::string> dex_locations;
+ ArtDexFileLoader dex_file_loader;
+ if (!dex_file_loader.GetMultiDexChecksums(
+ runtime_options_->boot_class_path[bcp_index].c_str(),
+ &checksums,
+ &dex_locations,
+ error_msg,
+ runtime_options_->boot_class_path_fds != nullptr ?
+ (*runtime_options_->boot_class_path_fds)[bcp_index] :
+ -1)) {
+ return nullptr;
+ }
+
+ DCHECK(!checksums.empty());
+ std::vector<std::string>& bcp_checksums = bcp_checksums_by_index_[bcp_index];
+ for (uint32_t checksum : checksums) {
+ bcp_checksums.push_back(StringPrintf("/%08x", checksum));
+ }
+ return &bcp_checksums;
+}
+
+const std::string& OatFileAssistantContext::GetApexVersions() {
+ if (apex_versions_.has_value()) {
+ return apex_versions_.value();
+ }
+
+ apex_versions_ = Runtime::GetApexVersions(
+ ArrayRef<const std::string>(runtime_options_->boot_class_path_locations));
+ return apex_versions_.value();
+}
+
+} // namespace art
diff --git a/runtime/oat_file_assistant_context.h b/runtime/oat_file_assistant_context.h
new file mode 100644
index 0000000..cc98c59
--- /dev/null
+++ b/runtime/oat_file_assistant_context.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 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_OAT_FILE_ASSISTANT_CONTEXT_H_
+#define ART_RUNTIME_OAT_FILE_ASSISTANT_CONTEXT_H_
+
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "arch/instruction_set.h"
+#include "runtime.h"
+
+namespace art {
+
+// A helper class for OatFileAssistant that fetches and caches information including boot image
+// checksums, bootclasspath checksums, and APEX versions. The same instance can be reused across
+// OatFileAssistant calls on different dex files for different instruction sets.
+// This class is not thread-safe until `FetchAll` is called.
+class OatFileAssistantContext {
+ public:
+ // Options that a runtime would take.
+ // Note that the struct only keeps references, so the caller must keep the objects alive during
+ // the lifetime of OatFileAssistant.
+ struct RuntimeOptions {
+ // Required. See `-Ximage`.
+ const std::vector<std::string>& image_locations;
+ // Required. See `-Xbootclasspath`.
+ const std::vector<std::string>& boot_class_path;
+ // Required. See `-Xbootclasspath-locations`.
+ const std::vector<std::string>& boot_class_path_locations;
+ // Optional. See `-Xbootclasspathfds`.
+ const std::vector<int>* const boot_class_path_fds = nullptr;
+ // Optional. See `-Xdeny-art-apex-data-files`.
+ const bool deny_art_apex_data_files = false;
+ };
+
+ // Information about a boot image.
+ struct BootImageInfo {
+ // Number of BCP jars covered by the boot image.
+ size_t component_count;
+ // Checksum of the boot image. The format is "i;<component_count>/<checksum_in_8_digit_hex>"
+ std::string checksum;
+ };
+
+ // Constructs OatFileAssistantContext from runtime options. Does not fetch information on
+ // construction. Information will be fetched from disk when needed.
+ explicit OatFileAssistantContext(std::unique_ptr<RuntimeOptions> runtime_options);
+ // Constructs OatFileAssistantContext from a runtime instance. Fetches as much information as
+ // possible from the runtime. The rest information will be fetched from disk when needed.
+ explicit OatFileAssistantContext(Runtime* runtime);
+ // Returns runtime options.
+ const RuntimeOptions& GetRuntimeOptions() const;
+ // Fetches all information that hasn't been fetched from disk and caches it. All operations will
+ // be read-only after a successful call to this function.
+ bool FetchAll(std::string* error_msg);
+ // Returns information about the boot image of the given instruction set.
+ const std::vector<BootImageInfo>& GetBootImageInfoList(InstructionSet isa);
+ // Returns the checksums of the dex files in the BCP jar at the given index, or nullptr on error.
+ // The format of each checksum is "/<checksum_in_8_digit_hex>".
+ const std::vector<std::string>* GetBcpChecksums(size_t bcp_index, std::string* error_msg);
+ // Returns a string that represents the apex versions of boot classpath jars. See
+ // `Runtime::apex_versions_` for the encoding format.
+ const std::string& GetApexVersions();
+
+ private:
+ std::unique_ptr<RuntimeOptions> runtime_options_;
+ std::unordered_map<InstructionSet, std::vector<BootImageInfo>> boot_image_info_list_by_isa_;
+ std::unordered_map<size_t, std::vector<std::string>> bcp_checksums_by_index_;
+ std::optional<std::string> apex_versions_;
+};
+
+} // namespace art
+
+#endif // ART_RUNTIME_OAT_FILE_ASSISTANT_CONTEXT_H_
diff --git a/runtime/oat_file_assistant_test.cc b/runtime/oat_file_assistant_test.cc
index 07998dd..2904ecb 100644
--- a/runtime/oat_file_assistant_test.cc
+++ b/runtime/oat_file_assistant_test.cc
@@ -16,97 +16,149 @@
#include "oat_file_assistant.h"
+#include <fcntl.h>
+#include <gtest/gtest.h>
#include <sys/param.h>
+#include <functional>
+#include <iterator>
+#include <memory>
#include <string>
+#include <type_traits>
#include <vector>
-#include <fcntl.h>
-#include <gtest/gtest.h>
-
+#include "android-base/scopeguard.h"
#include "android-base/strings.h"
-
+#include "arch/instruction_set.h"
#include "art_field-inl.h"
#include "base/os.h"
#include "base/utils.h"
-#include "class_linker-inl.h"
+#include "class_linker.h"
#include "class_loader_context.h"
#include "common_runtime_test.h"
#include "dexopt_test.h"
-#include "hidden_api.h"
#include "oat.h"
#include "oat_file.h"
+#include "oat_file_assistant_context.h"
#include "oat_file_manager.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread-current-inl.h"
+#include "scoped_thread_state_change.h"
+#include "thread.h"
namespace art {
-class OatFileAssistantTest : public DexoptTest {
+class OatFileAssistantBaseTest : public DexoptTest {};
+
+class OatFileAssistantTest : public OatFileAssistantBaseTest,
+ public testing::WithParamInterface<bool> {
public:
- void VerifyOptimizationStatus(OatFileAssistant* assistant,
- const std::string& file,
- const std::string& expected_filter,
- const std::string& expected_reason,
- const std::string& expected_odex_status) {
- // Verify the static methods (called from PM for dexOptNeeded).
- std::string compilation_filter1;
- std::string compilation_reason1;
-
- OatFileAssistant::GetOptimizationStatus(
- file, kRuntimeISA, &compilation_filter1, &compilation_reason1);
-
- ASSERT_EQ(expected_filter, compilation_filter1);
- ASSERT_EQ(expected_reason, compilation_reason1);
-
- // Verify the instance methods (called at runtime for systrace).
- std::string odex_location2; // ignored
- std::string compilation_filter2;
- std::string compilation_reason2;
- std::string odex_status2;
-
- assistant->GetOptimizationStatus(
- &odex_location2,
- &compilation_filter2,
- &compilation_reason2,
- &odex_status2);
-
- ASSERT_EQ(expected_filter, compilation_filter2);
- ASSERT_EQ(expected_reason, compilation_reason2);
- ASSERT_EQ(expected_odex_status, odex_status2);
+ void SetUp() override {
+ DexoptTest::SetUp();
+ with_runtime_ = GetParam();
+ ofa_context_ = CreateOatFileAssistantContext();
}
- void VerifyOptimizationStatus(OatFileAssistant* assistant,
- const std::string& file,
- CompilerFilter::Filter expected_filter,
+ // Verifies all variants of `GetOptimizationStatus`.
+ //
+ // `expected_filter` can be either a value of `CompilerFilter::Filter` or a string.
+ // If `check_context` is true, only verifies the variants that checks class loader context.
+ template <typename T>
+ void VerifyOptimizationStatus(const std::string& file,
+ ClassLoaderContext* context,
+ const T& expected_filter,
const std::string& expected_reason,
- const std::string& expected_odex_status) {
- VerifyOptimizationStatus(
- assistant,
- file,
- CompilerFilter::NameOfFilter(expected_filter),
- expected_reason,
- expected_odex_status);
- }
-
- void InsertNewBootClasspathEntry() {
- std::string extra_dex_filename = GetMultiDexSrc1();
- Runtime* runtime = Runtime::Current();
- runtime->boot_class_path_.push_back(extra_dex_filename);
- if (!runtime->boot_class_path_locations_.empty()) {
- runtime->boot_class_path_locations_.push_back(extra_dex_filename);
+ const std::string& expected_odex_status,
+ bool check_context = false) {
+ std::string expected_filter_name;
+ if constexpr (std::is_same_v<T, CompilerFilter::Filter>) {
+ expected_filter_name = CompilerFilter::NameOfFilter(expected_filter);
+ } else {
+ expected_filter_name = expected_filter;
}
+
+ // Verify the static method (called from PM for dumpsys).
+ // This variant does not check class loader context.
+ if (!check_context) {
+ std::string compilation_filter1;
+ std::string compilation_reason1;
+
+ OatFileAssistant::GetOptimizationStatus(file,
+ kRuntimeISA,
+ &compilation_filter1,
+ &compilation_reason1,
+ MaybeGetOatFileAssistantContext());
+
+ ASSERT_EQ(expected_filter_name, compilation_filter1);
+ ASSERT_EQ(expected_reason, compilation_reason1);
+ }
+
+ // Verify the instance methods (called at runtime and from artd).
+ OatFileAssistant assistant = CreateOatFileAssistant(file.c_str(), context);
+
+ std::string odex_location3; // ignored
+ std::string compilation_filter3;
+ std::string compilation_reason3;
+ std::string odex_status3;
+
+ assistant.GetOptimizationStatus(
+ &odex_location3, &compilation_filter3, &compilation_reason3, &odex_status3);
+
+ ASSERT_EQ(expected_filter_name, compilation_filter3);
+ ASSERT_EQ(expected_reason, compilation_reason3);
+ ASSERT_EQ(expected_odex_status, odex_status3);
}
- int GetDexOptNeeded(
- OatFileAssistant* assistant,
- CompilerFilter::Filter compiler_filter,
- bool profile_changed = false,
- bool downgrade = false) {
- return assistant->GetDexOptNeeded(
- compiler_filter,
- profile_changed,
- downgrade);
+ bool InsertNewBootClasspathEntry(const std::string& src, std::string* error_msg) {
+ std::vector<std::unique_ptr<const DexFile>> dex_files;
+ ArtDexFileLoader dex_file_loader;
+ if (!dex_file_loader.Open(src.c_str(),
+ src,
+ /*verify=*/true,
+ /*verify_checksum=*/false,
+ error_msg,
+ &dex_files)) {
+ return false;
+ }
+
+ runtime_->AppendToBootClassPath(src, src, dex_files);
+ std::move(dex_files.begin(), dex_files.end(), std::back_inserter(opened_dex_files_));
+
+ return true;
+ }
+
+ // Verifies the current version of `GetDexOptNeeded` (called from artd).
+ void VerifyGetDexOptNeeded(OatFileAssistant* assistant,
+ CompilerFilter::Filter compiler_filter,
+ OatFileAssistant::DexOptTrigger dexopt_trigger,
+ bool expected_dexopt_needed,
+ bool expected_is_vdex_usable,
+ OatFileAssistant::Location expected_location) {
+ OatFileAssistant::DexOptStatus status;
+ EXPECT_EQ(
+ assistant->GetDexOptNeeded(compiler_filter, dexopt_trigger, &status),
+ expected_dexopt_needed);
+ EXPECT_EQ(status.IsVdexUsable(), expected_is_vdex_usable);
+ EXPECT_EQ(status.GetLocation(), expected_location);
+ }
+
+ // Verifies all versions of `GetDexOptNeeded` with the default dexopt trigger.
+ void VerifyGetDexOptNeededDefault(OatFileAssistant* assistant,
+ CompilerFilter::Filter compiler_filter,
+ bool expected_dexopt_needed,
+ bool expected_is_vdex_usable,
+ OatFileAssistant::Location expected_location,
+ int expected_legacy_result) {
+ // Verify the current version (called from artd).
+ VerifyGetDexOptNeeded(assistant,
+ compiler_filter,
+ default_trigger_,
+ expected_dexopt_needed,
+ expected_is_vdex_usable,
+ expected_location);
+
+ // Verify the legacy version (called from PM).
+ EXPECT_EQ(
+ assistant->GetDexOptNeeded(compiler_filter, /*profile_changed=*/false, /*downgrade=*/false),
+ expected_legacy_result);
}
static std::unique_ptr<ClassLoaderContext> InitializeDefaultContext() {
@@ -115,7 +167,58 @@
return context;
}
+ // Temporarily disables the pointer to the current runtime if `with_runtime_` is false.
+ // Essentially simulates an environment where there is no active runtime.
+ android::base::ScopeGuard<std::function<void()>> ScopedMaybeWithoutRuntime() {
+ if (!with_runtime_) {
+ Runtime::TestOnlySetCurrent(nullptr);
+ }
+ return android::base::make_scope_guard(
+ [this]() { Runtime::TestOnlySetCurrent(runtime_.get()); });
+ }
+
+ std::unique_ptr<OatFileAssistantContext> CreateOatFileAssistantContext() {
+ return std::make_unique<OatFileAssistantContext>(
+ std::make_unique<OatFileAssistantContext::RuntimeOptions>(
+ OatFileAssistantContext::RuntimeOptions{
+ .image_locations = runtime_->GetImageLocations(),
+ .boot_class_path = runtime_->GetBootClassPath(),
+ .boot_class_path_locations = runtime_->GetBootClassPathLocations(),
+ .boot_class_path_fds = !runtime_->GetBootClassPathFds().empty() ?
+ &runtime_->GetBootClassPathFds() :
+ nullptr,
+ .deny_art_apex_data_files = runtime_->DenyArtApexDataFiles(),
+ }));
+ }
+
+ OatFileAssistantContext* MaybeGetOatFileAssistantContext() {
+ return with_runtime_ ? nullptr : ofa_context_.get();
+ }
+
+ // A helper function to create OatFileAssistant with some default arguments.
+ OatFileAssistant CreateOatFileAssistant(const char* dex_location,
+ ClassLoaderContext* context = nullptr,
+ bool load_executable = false,
+ int vdex_fd = -1,
+ int oat_fd = -1,
+ int zip_fd = -1) {
+ return OatFileAssistant(dex_location,
+ kRuntimeISA,
+ context != nullptr ? context : default_context_.get(),
+ load_executable,
+ /*only_load_trusted_executable=*/false,
+ MaybeGetOatFileAssistantContext(),
+ vdex_fd,
+ oat_fd,
+ zip_fd);
+ }
+
std::unique_ptr<ClassLoaderContext> default_context_ = InitializeDefaultContext();
+ bool with_runtime_;
+ const OatFileAssistant::DexOptTrigger default_trigger_{.targetFilterIsBetter = true,
+ .primaryBootImageBecomesUsable = true};
+ std::unique_ptr<OatFileAssistantContext> ofa_context_;
+ std::vector<std::unique_ptr<const DexFile>> opened_dex_files_;
};
class ScopedNonWritable {
@@ -154,7 +257,7 @@
// Case: We have a MultiDEX file and up-to-date ODEX file for it with relative
// encoded dex locations.
// Expect: The oat file status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, RelativeEncodedDexLocation) {
+TEST_P(OatFileAssistantTest, RelativeEncodedDexLocation) {
std::string dex_location = GetScratchDir() + "/RelativeEncodedDexLocation.jar";
std::string odex_location = GetOdexDir() + "/RelativeEncodedDexLocation.odex";
@@ -172,21 +275,24 @@
std::string error_msg;
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
// Verify we can load both dex files.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_TRUE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(2u, dex_files.size());
}
-TEST_F(OatFileAssistantTest, MakeUpToDateWithContext) {
+TEST_P(OatFileAssistantTest, MakeUpToDateWithContext) {
std::string dex_location = GetScratchDir() + "/TestDex.jar";
std::string odex_location = GetOdexDir() + "/TestDex.odex";
std::string context_location = GetScratchDir() + "/ContextDex.jar";
@@ -198,8 +304,6 @@
ASSERT_TRUE(context != nullptr);
ASSERT_TRUE(context->OpenDexFiles());
- OatFileAssistant oat_file_assistant(dex_location.c_str(), kRuntimeISA, context.get(), false);
-
std::string error_msg;
std::vector<std::string> args;
args.push_back("--dex-file=" + dex_location);
@@ -207,6 +311,10 @@
args.push_back("--class-loader-context=" + context_str);
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(), context.get());
+
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_NE(nullptr, oat_file.get());
ASSERT_NE(nullptr, oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
@@ -214,7 +322,7 @@
oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kClassPathKey));
}
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithUpToDateContextRelative) {
std::string dex_location = GetScratchDir() + "/TestDex.jar";
std::string odex_location = GetOdexDir() + "/TestDex.odex";
std::string context_location = GetScratchDir() + "/ContextDex.jar";
@@ -228,11 +336,6 @@
std::vector<int> context_fds;
ASSERT_TRUE(relative_context->OpenDexFiles(GetScratchDir(), context_fds));
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- relative_context.get(),
- false);
-
std::string error_msg;
std::vector<std::string> args;
args.push_back("--dex-file=" + dex_location);
@@ -240,29 +343,53 @@
args.push_back("--class-loader-context=PCL[" + context_location + "]");
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kDefaultCompilerFilter));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant =
+ CreateOatFileAssistant(dex_location.c_str(), relative_context.get());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kDefaultCompilerFilter,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
}
// Case: We have a DEX file, but no OAT file for it.
// Expect: The status is kDex2OatNeeded.
-TEST_F(OatFileAssistantTest, DexNoOat) {
+TEST_P(OatFileAssistantTest, DexNoOat) {
std::string dex_location = GetScratchDir() + "/DexNoOat.jar";
Copy(GetDexSrc1(), dex_location);
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeedProfile));
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -270,25 +397,24 @@
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
VerifyOptimizationStatus(
- &oat_file_assistant,
- dex_location,
- "run-from-apk",
- "unknown",
- "io-error-no-oat");
+ dex_location, default_context_.get(), "run-from-apk", "unknown", "io-error-no-oat");
}
// Case: We have no DEX file and no OAT file.
// Expect: Status is kNoDexOptNeeded. Loading should fail, but not crash.
-TEST_F(OatFileAssistantTest, NoDexNoOat) {
+TEST_P(OatFileAssistantTest, NoDexNoOat) {
std::string dex_location = GetScratchDir() + "/NoDexNoOat.jar";
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
EXPECT_FALSE(oat_file_assistant.HasDexFiles());
// Trying to get the best oat file should fail, but not crash.
@@ -298,27 +424,41 @@
// Case: We have a DEX file and an ODEX file, but no OAT file.
// Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OdexUpToDate) {
+TEST_P(OatFileAssistantTest, OdexUpToDate) {
std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
Copy(GetDexSrc1(), dex_location);
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
- // Force the use of oat location by making the dex parent not writable.
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- /*load_executable=*/ false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+ // Force the use of oat location by making the dex parent not writable.
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kEverything,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
@@ -326,39 +466,50 @@
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
VerifyOptimizationStatus(
- &oat_file_assistant,
- dex_location,
- CompilerFilter::kSpeed,
- "install",
- "up-to-date");
+ dex_location, default_context_.get(), CompilerFilter::kSpeed, "install", "up-to-date");
}
// Case: We have an ODEX file compiled against partial boot image.
// Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OdexUpToDatePartialBootImage) {
+TEST_P(OatFileAssistantTest, OdexUpToDatePartialBootImage) {
std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
Copy(GetDexSrc1(), dex_location);
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
// Insert an extra dex file to the boot class path.
- InsertNewBootClasspathEntry();
+ std::string error_msg;
+ ASSERT_TRUE(InsertNewBootClasspathEntry(GetMultiDexSrc1(), &error_msg)) << error_msg;
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
// Force the use of oat location by making the dex parent not writable.
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- /*load_executable=*/ false);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kEverything,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
@@ -366,17 +517,13 @@
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
VerifyOptimizationStatus(
- &oat_file_assistant,
- dex_location,
- CompilerFilter::kSpeed,
- "install",
- "up-to-date");
+ dex_location, default_context_.get(), CompilerFilter::kSpeed, "install", "up-to-date");
}
// Case: We have a DEX file and a PIC ODEX file, but no OAT file. We load the dex
// file via a symlink.
// Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OdexUpToDateSymLink) {
+TEST_P(OatFileAssistantTest, OdexUpToDateSymLink) {
std::string scratch_dir = GetScratchDir();
std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
@@ -389,19 +536,34 @@
ASSERT_EQ(0, symlink(scratch_dir.c_str(), link.c_str()));
dex_location = link + "/OdexUpToDate.jar";
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kEverything,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
@@ -411,7 +573,7 @@
// Case: We have a DEX file and up-to-date OAT file for it.
// Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, OatUpToDate) {
+TEST_P(OatFileAssistantTest, OatUpToDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -426,19 +588,34 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kEverything,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -446,16 +623,12 @@
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
VerifyOptimizationStatus(
- &oat_file_assistant,
- dex_location,
- CompilerFilter::kSpeed,
- "unknown",
- "up-to-date");
+ dex_location, default_context_.get(), CompilerFilter::kSpeed, "unknown", "up-to-date");
}
// Case: Passing valid file descriptors of updated odex/vdex files along with the dex file.
// Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithFd) {
std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex";
@@ -470,22 +643,38 @@
android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY | O_CLOEXEC));
android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false,
- false,
- vdex_fd.get(),
- odex_fd.get(),
- zip_fd.get());
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/false,
+ vdex_fd.get(),
+ odex_fd.get(),
+ zip_fd.get());
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kEverything,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
@@ -495,7 +684,7 @@
// Case: Passing invalid odex fd and valid vdex and zip fds.
// Expect: The status should be kDex2OatForBootImage.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexFd) {
std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
std::string vdex_location = GetScratchDir() + "/OatUpToDate.vdex";
@@ -509,20 +698,32 @@
android::base::unique_fd vdex_fd(open(vdex_location.c_str(), O_RDONLY | O_CLOEXEC));
android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false,
- false,
- vdex_fd.get(),
- /* oat_fd= */ -1,
- zip_fd.get());
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kEverything));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/false,
+ vdex_fd.get(),
+ /*oat_fd=*/-1,
+ zip_fd.get());
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kEverything,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -532,7 +733,7 @@
// Case: Passing invalid vdex fd and valid odex and zip fds.
// Expect: The status should be kDex2OatFromScratch.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidVdexFd) {
std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
std::string odex_location = GetScratchDir() + "/OatUpToDate.odex";
@@ -545,17 +746,21 @@
android::base::unique_fd odex_fd(open(odex_location.c_str(), O_RDONLY | O_CLOEXEC));
android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false,
- false,
- /* vdex_fd= */ -1,
- odex_fd.get(),
- zip_fd.get());
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/false,
+ /*vdex_fd=*/-1,
+ odex_fd.get(),
+ zip_fd.get());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
@@ -564,29 +769,34 @@
// Case: Passing invalid vdex and odex fd with valid zip fd.
// Expect: The status is kDex2oatFromScratch.
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithInvalidOdexVdexFd) {
std::string dex_location = GetScratchDir() + "/OatUpToDate.jar";
Copy(GetDexSrc1(), dex_location);
android::base::unique_fd zip_fd(open(dex_location.c_str(), O_RDONLY | O_CLOEXEC));
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false,
- false,
- /* vdex_fd= */ -1,
- /* oat_fd= */ -1,
- zip_fd);
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/false,
+ /*vdex_fd=*/-1,
+ /*oat_fd=*/-1,
+ zip_fd);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
}
// Case: We have a DEX file and up-to-date VDEX file for it, but no
// ODEX file.
-TEST_F(OatFileAssistantTest, VdexUpToDateNoOdex) {
+TEST_P(OatFileAssistantTest, VdexUpToDateNoOdex) {
std::string dex_location = GetScratchDir() + "/VdexUpToDateNoOdex.jar";
std::string odex_location = GetOdexDir() + "/VdexUpToDateNoOdex.oat";
@@ -597,30 +807,32 @@
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
ASSERT_EQ(0, unlink(odex_location.c_str()));
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
// Make sure we don't crash in this case when we dump the status. We don't
// care what the actual dumped value is.
oat_file_assistant.GetStatusDump();
- VerifyOptimizationStatus(
- &oat_file_assistant,
- dex_location,
- "verify",
- "vdex",
- "up-to-date");
+ VerifyOptimizationStatus(dex_location, default_context_.get(), "verify", "vdex", "up-to-date");
}
// Case: We have a DEX file and empty VDEX and ODEX files.
-TEST_F(OatFileAssistantTest, EmptyVdexOdex) {
+TEST_P(OatFileAssistantTest, EmptyVdexOdex) {
std::string dex_location = GetScratchDir() + "/EmptyVdexOdex.jar";
std::string odex_location = GetOdexDir() + "/EmptyVdexOdex.oat";
std::string vdex_location = GetOdexDir() + "/EmptyVdexOdex.vdex";
@@ -629,17 +841,20 @@
ScratchFile vdex_file(vdex_location.c_str());
ScratchFile odex_file(odex_location.c_str());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
}
// Case: We have a DEX file and up-to-date (OAT) VDEX file for it, but no OAT
// file.
-TEST_F(OatFileAssistantTest, VdexUpToDateNoOat) {
+TEST_P(OatFileAssistantTest, VdexUpToDateNoOat) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -650,7 +865,8 @@
std::string oat_location;
std::string error_msg;
ASSERT_TRUE(OatFileAssistant::DexLocationToOatFilename(
- dex_location, kRuntimeISA, &oat_location, &error_msg)) << error_msg;
+ dex_location, kRuntimeISA, /* deny_art_apex_data_files= */false, &oat_location, &error_msg))
+ << error_msg;
Copy(GetDexSrc1(), dex_location);
GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
@@ -658,19 +874,23 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
- EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
}
// Case: We have a DEX file and speed-profile OAT file for it.
// Expect: The status is kNoDexOptNeeded if the profile hasn't changed, but
// kDex2Oat if the profile has changed.
-TEST_F(OatFileAssistantTest, ProfileOatUpToDate) {
+TEST_P(OatFileAssistantTest, ProfileOatUpToDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -684,19 +904,52 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ default_trigger_,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat);
+ EXPECT_EQ(
+ OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile, /*profile_changed=*/false));
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ default_trigger_,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat);
EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeedProfile, false));
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify, /*profile_changed=*/false));
+
+ OatFileAssistant::DexOptTrigger profile_changed_trigger = default_trigger_;
+ profile_changed_trigger.targetFilterIsSame = true;
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ profile_changed_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat);
+ EXPECT_EQ(
+ OatFileAssistant::kDex2OatForFilter,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile, /*profile_changed=*/true));
+
+ // We should not recompile even if `profile_changed` is true because the compiler filter should
+ // not be downgraded.
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ profile_changed_trigger,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat);
EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify, false));
- EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeedProfile, true));
- EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify, true));
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify, /*profile_changed=*/true));
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -706,7 +959,7 @@
// Case: We have a MultiDEX file and up-to-date OAT file for it.
// Expect: The status is kNoDexOptNeeded and we load all dex files.
-TEST_F(OatFileAssistantTest, MultiDexOatUpToDate) {
+TEST_P(OatFileAssistantTest, MultiDexOatUpToDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -720,18 +973,25 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
// Verify we can load both dex files.
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_TRUE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(2u, dex_files.size());
@@ -739,7 +999,7 @@
// Case: We have a MultiDEX file where the non-main multdex entry is out of date.
// Expect: The status is kDex2OatNeeded.
-TEST_F(OatFileAssistantTest, MultiDexNonMainOutOfDate) {
+TEST_P(OatFileAssistantTest, MultiDexNonMainOutOfDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -759,18 +1019,21 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
}
// Case: We have a DEX file and an OAT file out of date with respect to the
// dex checksum.
-TEST_F(OatFileAssistantTest, OatDexOutOfDate) {
+TEST_P(OatFileAssistantTest, OatDexOutOfDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -788,14 +1051,21 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -803,16 +1073,12 @@
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
VerifyOptimizationStatus(
- &oat_file_assistant,
- dex_location,
- "run-from-apk-fallback",
- "unknown",
- "apk-more-recent");
+ dex_location, default_context_.get(), "run-from-apk-fallback", "unknown", "apk-more-recent");
}
// Case: We have a DEX file and an (ODEX) VDEX file out of date with respect
// to the dex checksum, but no ODEX file.
-TEST_F(OatFileAssistantTest, VdexDexOutOfDate) {
+TEST_P(OatFileAssistantTest, VdexDexOutOfDate) {
std::string dex_location = GetScratchDir() + "/VdexDexOutOfDate.jar";
std::string odex_location = GetOdexDir() + "/VdexDexOutOfDate.oat";
@@ -821,18 +1087,21 @@
ASSERT_EQ(0, unlink(odex_location.c_str()));
Copy(GetDexSrc2(), dex_location);
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
}
// Case: We have a MultiDEX (ODEX) VDEX file where the non-main multidex entry
// is out of date and there is no corresponding ODEX file.
-TEST_F(OatFileAssistantTest, VdexMultiDexNonMainOutOfDate) {
+TEST_P(OatFileAssistantTest, VdexMultiDexNonMainOutOfDate) {
std::string dex_location = GetScratchDir() + "/VdexMultiDexNonMainOutOfDate.jar";
std::string odex_location = GetOdexDir() + "/VdexMultiDexNonMainOutOfDate.odex";
@@ -841,18 +1110,21 @@
ASSERT_EQ(0, unlink(odex_location.c_str()));
Copy(GetMultiDexSrc2(), dex_location);
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
}
// Case: We have a DEX file and an OAT file out of date with respect to the
// boot image.
-TEST_F(OatFileAssistantTest, OatImageOutOfDate) {
+TEST_P(OatFileAssistantTest, OatImageOutOfDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -869,35 +1141,70 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
- EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OatFileStatus());
EXPECT_TRUE(oat_file_assistant.HasDexFiles());
+ VerifyOptimizationStatus(dex_location, default_context_.get(), "verify", "vdex", "up-to-date");
+}
+
+TEST_P(OatFileAssistantTest, OatContextOutOfDate) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ std::string odex_location = GetOdexDir() + "/TestDex.odex";
+
+ std::string context_location = GetScratchDir() + "/ContextDex.jar";
+ Copy(GetDexSrc1(), dex_location);
+ Copy(GetDexSrc2(), context_location);
+
+ std::string error_msg;
+ std::vector<std::string> args;
+ args.push_back("--dex-file=" + dex_location);
+ args.push_back("--oat-file=" + odex_location);
+ args.push_back("--class-loader-context=PCL[" + context_location + "]");
+ ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
+
+ // Update the context by overriding the jar file.
+ Copy(GetMultiDexSrc2(), context_location);
+
+ std::unique_ptr<ClassLoaderContext> context =
+ ClassLoaderContext::Create("PCL[" + context_location + "]");
+ ASSERT_TRUE(context != nullptr);
+ ASSERT_TRUE(context->OpenDexFiles());
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
VerifyOptimizationStatus(
- &oat_file_assistant,
- dex_location,
- "verify",
- "vdex",
- "up-to-date");
+ dex_location.c_str(), context.get(), "verify", "vdex", "up-to-date", /*check_context=*/true);
}
// Case: We have a DEX file and a verify-at-runtime OAT file out of date with
// respect to the boot image.
// It shouldn't matter that the OAT file is out of date, because it is
// verify-at-runtime.
-TEST_F(OatFileAssistantTest, OatVerifyAtRuntimeImageOutOfDate) {
+TEST_P(OatFileAssistantTest, OatVerifyAtRuntimeImageOutOfDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -914,14 +1221,21 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOat,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -930,7 +1244,7 @@
}
// Case: We have a DEX file and an ODEX file, but no OAT file.
-TEST_F(OatFileAssistantTest, DexOdexNoOat) {
+TEST_P(OatFileAssistantTest, DexOdexNoOat) {
std::string dex_location = GetScratchDir() + "/DexOdexNoOat.jar";
std::string odex_location = GetOdexDir() + "/DexOdexNoOat.odex";
@@ -938,16 +1252,23 @@
Copy(GetDexSrc1(), dex_location);
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
- // Verify the status.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ // Verify the status.
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
@@ -961,31 +1282,46 @@
// Case: We have a resource-only DEX file, no ODEX file and no
// OAT file. Expect: The status is kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, ResourceOnlyDex) {
+TEST_P(OatFileAssistantTest, ResourceOnlyDex) {
std::string dex_location = GetScratchDir() + "/ResourceOnlyDex.jar";
Copy(GetResourceOnlySrc1(), dex_location);
- // Verify the status.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kVerify));
+ // Verify the status.
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
EXPECT_FALSE(oat_file_assistant.HasDexFiles());
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -995,7 +1331,7 @@
// Case: We have a DEX file, an ODEX file and an OAT file.
// Expect: It shouldn't crash. We should load the odex file executable.
-TEST_F(OatFileAssistantTest, OdexOatOverlap) {
+TEST_P(OatFileAssistantTest, OdexOatOverlap) {
std::string dex_location = GetScratchDir() + "/OdexOatOverlap.jar";
std::string odex_location = GetOdexDir() + "/OdexOatOverlap.odex";
@@ -1004,14 +1340,19 @@
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
- // Verify things don't go bad.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ // Verify things don't go bad.
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
@@ -1021,7 +1362,9 @@
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_TRUE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(1u, dex_files.size());
@@ -1029,7 +1372,7 @@
// Case: We have a DEX file and a VerifyAtRuntime ODEX file, but no OAT file.
// Expect: The status is kNoDexOptNeeded, because VerifyAtRuntime contains no code.
-TEST_F(OatFileAssistantTest, DexVerifyAtRuntimeOdexNoOat) {
+TEST_P(OatFileAssistantTest, DexVerifyAtRuntimeOdexNoOat) {
std::string dex_location = GetScratchDir() + "/DexVerifyAtRuntimeOdexNoOat.jar";
std::string odex_location = GetOdexDir() + "/DexVerifyAtRuntimeOdexNoOat.odex";
@@ -1037,16 +1380,23 @@
Copy(GetDexSrc1(), dex_location);
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kExtract);
- // Verify the status.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ // Verify the status.
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
@@ -1056,7 +1406,7 @@
// Case: We have a DEX file and up-to-date OAT file for it.
// Expect: We should load an executable dex file.
-TEST_F(OatFileAssistantTest, LoadOatUpToDate) {
+TEST_P(OatFileAssistantTest, LoadOatUpToDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -1071,15 +1421,18 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
// Load the oat using an oat file assistant.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_TRUE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(1u, dex_files.size());
@@ -1087,7 +1440,7 @@
// Case: We have a DEX file and up-to-date quicken OAT file for it.
// Expect: We should still load the oat file as executable.
-TEST_F(OatFileAssistantTest, LoadExecInterpretOnlyOatUpToDate) {
+TEST_P(OatFileAssistantTest, LoadExecInterpretOnlyOatUpToDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -1102,15 +1455,18 @@
ScopedNonWritable scoped_non_writable(dex_location);
ASSERT_TRUE(scoped_non_writable.IsSuccessful());
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
// Load the oat using an oat file assistant.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_TRUE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(1u, dex_files.size());
@@ -1118,7 +1474,7 @@
// Case: We have a DEX file and up-to-date OAT file for it.
// Expect: Loading non-executable should load the oat non-executable.
-TEST_F(OatFileAssistantTest, LoadNoExecOatUpToDate) {
+TEST_P(OatFileAssistantTest, LoadNoExecOatUpToDate) {
if (IsExecutedAsRoot()) {
// We cannot simulate non writable locations when executed as root: b/38000545.
LOG(ERROR) << "Test skipped because it's running as root";
@@ -1134,15 +1490,18 @@
GenerateOatForTest(dex_location.c_str(), CompilerFilter::kSpeed);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
// Load the oat using an oat file assistant.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_FALSE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(1u, dex_files.size());
@@ -1186,36 +1545,42 @@
// Case: Non-absolute path to Dex location.
// Expect: Not sure, but it shouldn't crash.
-TEST_F(OatFileAssistantTest, NonAbsoluteDexLocation) {
+TEST_P(OatFileAssistantTest, NonAbsoluteDexLocation) {
std::string abs_dex_location = GetScratchDir() + "/NonAbsoluteDexLocation.jar";
Copy(GetDexSrc1(), abs_dex_location);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
std::string dex_location = MakePathRelative(abs_dex_location);
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
}
// Case: Very short, non-existent Dex location.
// Expect: kNoDexOptNeeded.
-TEST_F(OatFileAssistantTest, ShortDexLocation) {
+TEST_P(OatFileAssistantTest, ShortDexLocation) {
std::string dex_location = "/xx";
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OatFileStatus());
EXPECT_FALSE(oat_file_assistant.HasDexFiles());
@@ -1223,17 +1588,20 @@
// Case: Non-standard extension for dex file.
// Expect: The status is kDex2OatNeeded.
-TEST_F(OatFileAssistantTest, LongDexExtension) {
+TEST_P(OatFileAssistantTest, LongDexExtension) {
std::string dex_location = GetScratchDir() + "/LongDexExtension.jarx";
Copy(GetDexSrc1(), dex_location);
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
- EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kSpeed));
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError,
+ /*expected_legacy_result=*/OatFileAssistant::kDex2OatFromScratch);
EXPECT_FALSE(oat_file_assistant.IsInBootClassPath());
EXPECT_EQ(OatFileAssistant::kOatCannotOpen, oat_file_assistant.OdexFileStatus());
@@ -1243,7 +1611,7 @@
// A task to generate a dex location. Used by the RaceToGenerate test.
class RaceGenerateTask : public Task {
public:
- RaceGenerateTask(OatFileAssistantTest& test,
+ RaceGenerateTask(OatFileAssistantBaseTest& test,
const std::string& dex_location,
const std::string& oat_location,
Mutex* lock)
@@ -1251,8 +1619,7 @@
dex_location_(dex_location),
oat_location_(oat_location),
lock_(lock),
- loaded_oat_file_(nullptr)
- {}
+ loaded_oat_file_(nullptr) {}
void Run(Thread* self ATTRIBUTE_UNUSED) override {
// Load the dex files, and save a pointer to the loaded oat file, so that
@@ -1288,7 +1655,7 @@
}
private:
- OatFileAssistantTest& test_;
+ OatFileAssistantBaseTest& test_;
std::string dex_location_;
std::string oat_location_;
Mutex* lock_;
@@ -1297,7 +1664,7 @@
// Test the case where dex2oat invocations race with multiple processes trying to
// load the oat file.
-TEST_F(OatFileAssistantTest, RaceToGenerate) {
+TEST_F(OatFileAssistantBaseTest, RaceToGenerate) {
std::string dex_location = GetScratchDir() + "/RaceToGenerate.jar";
std::string oat_location = GetOdexDir() + "/RaceToGenerate.oat";
@@ -1309,7 +1676,7 @@
// take a while to generate.
Copy(GetLibCoreDexFileNames()[0], dex_location);
- const size_t kNumThreads = 32;
+ const size_t kNumThreads = 16;
Thread* self = Thread::Current();
ThreadPool thread_pool("Oat file assistant test thread pool", kNumThreads);
std::vector<std::unique_ptr<RaceGenerateTask>> tasks;
@@ -1336,7 +1703,7 @@
// Case: We have a DEX file and an ODEX file, and no OAT file,
// Expect: We should load the odex file executable.
-TEST_F(OatFileAssistantTest, LoadDexOdexNoOat) {
+TEST_P(OatFileAssistantTest, LoadDexOdexNoOat) {
std::string dex_location = GetScratchDir() + "/LoadDexOdexNoOat.jar";
std::string odex_location = GetOdexDir() + "/LoadDexOdexNoOat.odex";
@@ -1344,15 +1711,18 @@
Copy(GetDexSrc1(), dex_location);
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
// Load the oat using an executable oat file assistant.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_TRUE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(1u, dex_files.size());
@@ -1360,7 +1730,7 @@
// Case: We have a MultiDEX file and an ODEX file, and no OAT file.
// Expect: We should load the odex file executable.
-TEST_F(OatFileAssistantTest, LoadMultiDexOdexNoOat) {
+TEST_P(OatFileAssistantTest, LoadMultiDexOdexNoOat) {
std::string dex_location = GetScratchDir() + "/LoadMultiDexOdexNoOat.jar";
std::string odex_location = GetOdexDir() + "/LoadMultiDexOdexNoOat.odex";
@@ -1368,15 +1738,18 @@
Copy(GetMultiDexSrc1(), dex_location);
GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
// Load the oat using an executable oat file assistant.
- OatFileAssistant oat_file_assistant(dex_location.c_str(),
- kRuntimeISA,
- default_context_.get(),
- true);
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str(),
+ /*context=*/nullptr,
+ /*load_executable=*/true);
std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
ASSERT_TRUE(oat_file.get() != nullptr);
- EXPECT_TRUE(oat_file->IsExecutable());
+ if (with_runtime_) {
+ EXPECT_TRUE(oat_file->IsExecutable());
+ }
std::vector<std::unique_ptr<const DexFile>> dex_files;
dex_files = oat_file_assistant.LoadDexFiles(*oat_file, dex_location.c_str());
EXPECT_EQ(2u, dex_files.size());
@@ -1402,7 +1775,7 @@
// Verify the dexopt status values from dalvik.system.DexFile
// match the OatFileAssistant::DexOptStatus values.
-TEST_F(OatFileAssistantTest, DexOptStatusValues) {
+TEST_F(OatFileAssistantBaseTest, DexOptStatusValues) {
std::pair<OatFileAssistant::DexOptNeeded, const char*> mapping[] = {
{OatFileAssistant::kNoDexOptNeeded, "NO_DEXOPT_NEEDED"},
{OatFileAssistant::kDex2OatFromScratch, "DEX2OAT_FROM_SCRATCH"},
@@ -1426,7 +1799,7 @@
}
}
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithOutOfDateContext) {
std::string dex_location = GetScratchDir() + "/TestDex.jar";
std::string odex_location = GetOdexDir() + "/TestDex.odex";
@@ -1455,43 +1828,321 @@
ASSERT_TRUE(updated_context != nullptr);
std::vector<int> context_fds;
ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums*/ true));
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(), kRuntimeISA, updated_context.get(), false);
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant =
+ CreateOatFileAssistant(dex_location.c_str(), updated_context.get());
// DexOptNeeded should advise compilation for filter when the context changes.
- EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kDefaultCompilerFilter));
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kDefaultCompilerFilter,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/-OatFileAssistant::kDex2OatForFilter);
}
{
std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
ASSERT_TRUE(updated_context != nullptr);
std::vector<int> context_fds;
- ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums*/ true));
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(), kRuntimeISA, updated_context.get(), false);
- // Now check that DexOptNeeded does not advise compilation if we only extracted the file.
+ ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums*/ true));
args.push_back("--compiler-filter=extract");
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant =
+ CreateOatFileAssistant(dex_location.c_str(), updated_context.get());
+ // Now check that DexOptNeeded does not advise compilation if we only extracted the file.
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
}
{
std::unique_ptr<ClassLoaderContext> updated_context = ClassLoaderContext::Create(context_str);
ASSERT_TRUE(updated_context != nullptr);
std::vector<int> context_fds;
- ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums*/ true));
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(), kRuntimeISA, updated_context.get(), false);
- // Now check that DexOptNeeded does not advise compilation if we only verify the file.
+ ASSERT_TRUE(updated_context->OpenDexFiles("", context_fds, /*only_read_checksums*/ true));
args.push_back("--compiler-filter=verify");
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
- EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
- GetDexOptNeeded(&oat_file_assistant, CompilerFilter::kExtract));
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant =
+ CreateOatFileAssistant(dex_location.c_str(), updated_context.get());
+ // Now check that DexOptNeeded does not advise compilation if we only verify the file.
+ VerifyGetDexOptNeededDefault(&oat_file_assistant,
+ CompilerFilter::kExtract,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex,
+ /*expected_legacy_result=*/OatFileAssistant::kNoDexOptNeeded);
}
}
+// Case: We have a DEX file and speed-profile ODEX file for it. The caller's intention is to
+// downgrade the compiler filter.
+// Expect: Dexopt should be performed only if the target compiler filter is worse than the current
+// one.
+TEST_P(OatFileAssistantTest, Downgrade) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ std::string odex_location = GetOdexDir() + "/TestDex.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ OatFileAssistant::DexOptTrigger downgrade_trigger{.targetFilterIsWorse = true};
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ downgrade_trigger,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex);
+ EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kSpeed, /*profile_changed=*/false, /*downgrade=*/true));
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ downgrade_trigger,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex);
+ EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kSpeedProfile, /*profile_changed=*/false, /*downgrade=*/true));
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ downgrade_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex);
+ EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kVerify, /*profile_changed=*/false, /*downgrade=*/true));
+}
+
+// Case: We have a DEX file but we don't have an ODEX file for it. The caller's intention is to
+// downgrade the compiler filter.
+// Expect: Dexopt should never be performed regardless of the target compiler filter.
+TEST_P(OatFileAssistantTest, DowngradeNoOdex) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ Copy(GetDexSrc1(), dex_location);
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ OatFileAssistant::DexOptTrigger downgrade_trigger{.targetFilterIsWorse = true};
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ downgrade_trigger,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+ EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kSpeed, /*profile_changed=*/false, /*downgrade=*/true));
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ downgrade_trigger,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+ EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kSpeedProfile, /*profile_changed=*/false, /*downgrade=*/true));
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ downgrade_trigger,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+ EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kVerify, /*profile_changed=*/false, /*downgrade=*/true));
+}
+
+// Case: We have a DEX file and speed-profile ODEX file for it. The legacy version is called with
+// both `profile_changed` and `downgrade` being true. This won't happen in the real case. Just to be
+// complete.
+// Expect: The behavior should be as `profile_changed` is false and `downgrade` is true.
+TEST_P(OatFileAssistantTest, ProfileChangedDowngrade) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ std::string odex_location = GetOdexDir() + "/TestDex.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kSpeed, /*profile_changed=*/true, /*downgrade=*/true));
+
+ EXPECT_EQ(-OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kSpeedProfile, /*profile_changed=*/true, /*downgrade=*/true));
+
+ EXPECT_EQ(-OatFileAssistant::kDex2OatForFilter,
+ oat_file_assistant.GetDexOptNeeded(
+ CompilerFilter::kVerify, /*profile_changed=*/true, /*downgrade=*/true));
+}
+
+// Case: We have a DEX file and speed-profile ODEX file for it. The caller's intention is to force
+// the compilation.
+// Expect: Dexopt should be performed regardless of the target compiler filter. The VDEX file is
+// usable.
+//
+// The legacy version does not support this case. Historically, Package Manager does not take the
+// result from OatFileAssistant for forced compilation. It uses an arbitrary non-zero value instead.
+// Therefore, we don't test the legacy version here.
+TEST_P(OatFileAssistantTest, Force) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ std::string odex_location = GetOdexDir() + "/TestDex.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeedProfile);
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ OatFileAssistant::DexOptTrigger force_trigger{.targetFilterIsBetter = true,
+ .targetFilterIsSame = true,
+ .targetFilterIsWorse = true,
+ .primaryBootImageBecomesUsable = true};
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ force_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex);
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ force_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex);
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ force_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationOdex);
+}
+
+// Case: We have a DEX file but we don't have an ODEX file for it. The caller's intention is to
+// force the compilation.
+// Expect: Dexopt should be performed regardless of the target compiler filter. No VDEX file is
+// usable.
+//
+// The legacy version does not support this case. Historically, Package Manager does not take the
+// result from OatFileAssistant for forced compilation. It uses an arbitrary non-zero value instead.
+// Therefore, we don't test the legacy version here.
+TEST_P(OatFileAssistantTest, ForceNoOdex) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ Copy(GetDexSrc1(), dex_location);
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+ OatFileAssistant::DexOptTrigger force_trigger{.targetFilterIsBetter = true,
+ .targetFilterIsSame = true,
+ .targetFilterIsWorse = true,
+ .primaryBootImageBecomesUsable = true};
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ force_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ force_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ force_trigger,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/false,
+ /*expected_location=*/OatFileAssistant::kLocationNoneOrError);
+}
+
+// Case: We have a DEX file and a DM file for it.
+// Expect: Dexopt should be performed if the compiler filter is better than "verify". The location
+// should be kLocationDm.
+//
+// The legacy version should return kDex2OatFromScratch if the target compiler filter is better than
+// "verify".
+TEST_P(OatFileAssistantTest, DmUpToDate) {
+ std::string dex_location = GetScratchDir() + "/TestDex.jar";
+ std::string dm_location = GetScratchDir() + "/TestDex.dm";
+ std::string odex_location = GetOdexDir() + "/TestDex.odex";
+ std::string vdex_location = GetOdexDir() + "/TestDex.vdex";
+ Copy(GetDexSrc1(), dex_location);
+
+ // Generate temporary ODEX and VDEX files in order to create the DM file from.
+ GenerateOdexForTest(
+ dex_location, odex_location, CompilerFilter::kVerify, "install", {"--copy-dex-files=false"});
+
+ CreateDexMetadata(vdex_location, dm_location);
+
+ // Cleanup the temporary files.
+ ASSERT_EQ(0, unlink(odex_location.c_str()));
+ ASSERT_EQ(0, unlink(vdex_location.c_str()));
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeed,
+ default_trigger_,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationDm);
+ EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeed));
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kSpeedProfile,
+ default_trigger_,
+ /*expected_dexopt_needed=*/true,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationDm);
+ EXPECT_EQ(OatFileAssistant::kDex2OatFromScratch,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kSpeedProfile));
+
+ VerifyGetDexOptNeeded(&oat_file_assistant,
+ CompilerFilter::kVerify,
+ default_trigger_,
+ /*expected_dexopt_needed=*/false,
+ /*expected_is_vdex_usable=*/true,
+ /*expected_location=*/OatFileAssistant::kLocationDm);
+ EXPECT_EQ(OatFileAssistant::kNoDexOptNeeded,
+ oat_file_assistant.GetDexOptNeeded(CompilerFilter::kVerify));
+}
+
// Test that GetLocation of a dex file is the same whether the dex
// filed is backed by an oat file or not.
-TEST_F(OatFileAssistantTest, GetDexLocation) {
+TEST_F(OatFileAssistantBaseTest, GetDexLocation) {
std::string dex_location = GetScratchDir() + "/TestDex.jar";
std::string oat_location = GetOdexDir() + "/TestDex.odex";
std::string art_location = GetOdexDir() + "/TestDex.art";
@@ -1539,7 +2190,7 @@
// Test that a dex file on the platform location gets the right hiddenapi domain,
// regardless of whether it has a backing oat file.
-TEST_F(OatFileAssistantTest, SystemFrameworkDir) {
+TEST_F(OatFileAssistantBaseTest, SystemFrameworkDir) {
std::string filebase = "OatFileAssistantTestSystemFrameworkDir";
std::string dex_location = GetAndroidRoot() + "/framework/" + filebase + ".jar";
Copy(GetDexSrc1(), dex_location);
@@ -1619,7 +2270,7 @@
}
// Make sure OAT files that require app images are not loaded as executable.
-TEST_F(OatFileAssistantTest, LoadOatNoArt) {
+TEST_F(OatFileAssistantBaseTest, LoadOatNoArt) {
std::string dex_location = GetScratchDir() + "/TestDex.jar";
std::string odex_location = GetOdexDir() + "/TestDex.odex";
std::string art_location = GetOdexDir() + "/TestDex.art";
@@ -1653,7 +2304,7 @@
EXPECT_FALSE(oat_file->IsExecutable());
}
-TEST_F(OatFileAssistantTest, GetDexOptNeededWithApexVersions) {
+TEST_P(OatFileAssistantTest, GetDexOptNeededWithApexVersions) {
std::string dex_location = GetScratchDir() + "/TestDex.jar";
std::string odex_location = GetOdexDir() + "/TestDex.odex";
Copy(GetDexSrc1(), dex_location);
@@ -1667,8 +2318,9 @@
args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions());
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
}
@@ -1681,8 +2333,9 @@
args.push_back("--apex-versions=" + Runtime::Current()->GetApexVersions().substr(0, 1));
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
EXPECT_EQ(OatFileAssistant::kOatUpToDate, oat_file_assistant.OdexFileStatus());
}
@@ -1695,12 +2348,135 @@
args.push_back("--apex-versions=/1/2/3/4");
ASSERT_TRUE(Dex2Oat(args, &error_msg)) << error_msg;
- OatFileAssistant oat_file_assistant(
- dex_location.c_str(), kRuntimeISA, default_context_.get(), false);
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ OatFileAssistant oat_file_assistant = CreateOatFileAssistant(dex_location.c_str());
EXPECT_EQ(OatFileAssistant::kOatBootImageOutOfDate, oat_file_assistant.OdexFileStatus());
}
}
+TEST_P(OatFileAssistantTest, Create) {
+ std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ std::unique_ptr<ClassLoaderContext> context;
+ std::string error_msg;
+ std::unique_ptr<OatFileAssistant> oat_file_assistant =
+ OatFileAssistant::Create(dex_location,
+ GetInstructionSetString(kRuntimeISA),
+ default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeGetOatFileAssistantContext(),
+ &context,
+ &error_msg);
+ ASSERT_NE(oat_file_assistant, nullptr);
+
+ // Verify that the created instance is usable.
+ VerifyOptimizationStatus(dex_location, default_context_.get(), "speed", "install", "up-to-date");
+}
+
+TEST_P(OatFileAssistantTest, ErrorOnInvalidIsaString) {
+ std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ std::unique_ptr<ClassLoaderContext> context;
+ std::string error_msg;
+ EXPECT_EQ(OatFileAssistant::Create(dex_location,
+ /*isa_str=*/"foo",
+ default_context_->EncodeContextForDex2oat(/*base_dir=*/""),
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeGetOatFileAssistantContext(),
+ &context,
+ &error_msg),
+ nullptr);
+ EXPECT_EQ(error_msg, "Instruction set 'foo' is invalid");
+}
+
+TEST_P(OatFileAssistantTest, ErrorOnInvalidContextString) {
+ std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ std::unique_ptr<ClassLoaderContext> context;
+ std::string error_msg;
+ EXPECT_EQ(OatFileAssistant::Create(dex_location,
+ GetInstructionSetString(kRuntimeISA),
+ /*context_str=*/"foo",
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeGetOatFileAssistantContext(),
+ &context,
+ &error_msg),
+ nullptr);
+ EXPECT_EQ(error_msg, "Class loader context 'foo' is invalid");
+}
+
+TEST_P(OatFileAssistantTest, ErrorOnInvalidContextFile) {
+ std::string dex_location = GetScratchDir() + "/OdexUpToDate.jar";
+ std::string odex_location = GetOdexDir() + "/OdexUpToDate.odex";
+ Copy(GetDexSrc1(), dex_location);
+ GenerateOdexForTest(dex_location, odex_location, CompilerFilter::kSpeed, "install");
+
+ // Create a broken context file.
+ std::string context_location = GetScratchDir() + "/BrokenContext.jar";
+ std::ofstream output(context_location);
+ output.close();
+
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+
+ std::unique_ptr<ClassLoaderContext> context;
+ std::string error_msg;
+ EXPECT_EQ(OatFileAssistant::Create(dex_location,
+ GetInstructionSetString(kRuntimeISA),
+ /*context_str=*/"PCL[" + context_location + "]",
+ /*load_executable=*/false,
+ /*only_load_trusted_executable=*/true,
+ MaybeGetOatFileAssistantContext(),
+ &context,
+ &error_msg),
+ nullptr);
+ EXPECT_EQ(error_msg,
+ "Failed to load class loader context files for '" + dex_location +
+ "' with context 'PCL[" + context_location + "]'");
+}
+
+// Verifies that `OatFileAssistant::ValidateBootClassPathChecksums` accepts the checksum string
+// produced by `gc::space::ImageSpace::GetBootClassPathChecksums`.
+TEST_P(OatFileAssistantTest, ValidateBootClassPathChecksums) {
+ std::string error_msg;
+ auto create_and_verify = [&]() {
+ std::string checksums = gc::space::ImageSpace::GetBootClassPathChecksums(
+ ArrayRef<gc::space::ImageSpace* const>(runtime_->GetHeap()->GetBootImageSpaces()),
+ ArrayRef<const DexFile* const>(runtime_->GetClassLinker()->GetBootClassPath()));
+ std::string bcp_locations = android::base::Join(runtime_->GetBootClassPathLocations(), ':');
+
+ ofa_context_ = CreateOatFileAssistantContext();
+ auto scoped_maybe_without_runtime = ScopedMaybeWithoutRuntime();
+ return OatFileAssistant::ValidateBootClassPathChecksums(
+ ofa_context_.get(), kRuntimeISA, checksums, bcp_locations, &error_msg);
+ };
+
+ ASSERT_TRUE(create_and_verify()) << error_msg;
+
+ for (const std::string& src : {GetDexSrc1(), GetDexSrc2()}) {
+ ASSERT_TRUE(InsertNewBootClasspathEntry(src, &error_msg)) << error_msg;
+ ASSERT_TRUE(create_and_verify()) << error_msg;
+ }
+}
+
// TODO: More Tests:
// * Test class linker falls back to unquickened dex for DexNoOat
// * Test class linker falls back to unquickened dex for MultiDexNoOat
@@ -1713,4 +2489,7 @@
// - Dex is stripped, don't have odex.
// - Oat file corrupted after status check, before reload unexecutable
// because it's unrelocated and no dex2oat
+
+INSTANTIATE_TEST_SUITE_P(WithOrWithoutRuntime, OatFileAssistantTest, testing::Values(true, false));
+
} // namespace art
diff --git a/runtime/oat_file_manager.cc b/runtime/oat_file_manager.cc
index c3a268d..6f1e95a 100644
--- a/runtime/oat_file_manager.cc
+++ b/runtime/oat_file_manager.cc
@@ -196,11 +196,11 @@
LOG(WARNING) << "Opening an oat file without a class loader. "
<< "Are you using the deprecated DexFile APIs?";
} else if (context != nullptr) {
- OatFileAssistant oat_file_assistant(dex_location,
- kRuntimeISA,
- context.get(),
- runtime->GetOatFilesExecutable(),
- only_use_system_oat_files_);
+ auto oat_file_assistant = std::make_unique<OatFileAssistant>(dex_location,
+ kRuntimeISA,
+ context.get(),
+ runtime->GetOatFilesExecutable(),
+ only_use_system_oat_files_);
// Get the current optimization status for trace debugging.
// Implementation detail note: GetOptimizationStatus will select the same
@@ -210,11 +210,8 @@
std::string compilation_filter;
std::string compilation_reason;
std::string odex_status;
- oat_file_assistant.GetOptimizationStatus(
- &odex_location,
- &compilation_filter,
- &compilation_reason,
- &odex_status);
+ oat_file_assistant->GetOptimizationStatus(
+ &odex_location, &compilation_filter, &compilation_reason, &odex_status);
Runtime::Current()->GetAppInfo()->RegisterOdexStatus(
dex_location,
@@ -229,8 +226,18 @@
compilation_filter.c_str(),
compilation_reason.c_str()));
+ const bool has_registered_app_info = Runtime::Current()->GetAppInfo()->HasRegisteredAppInfo();
+ const AppInfo::CodeType code_type =
+ Runtime::Current()->GetAppInfo()->GetRegisteredCodeType(dex_location);
+ // We only want to madvise primary/split dex artifacts as a startup optimization. However,
+ // as the code_type for those artifacts may not be set until the initial app info registration,
+ // we conservatively madvise everything until the app info registration is complete.
+ const bool should_madvise_vdex_and_odex = !has_registered_app_info ||
+ code_type == AppInfo::CodeType::kPrimaryApk ||
+ code_type == AppInfo::CodeType::kSplitApk;
+
// Proceed with oat file loading.
- std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());
+ std::unique_ptr<const OatFile> oat_file(oat_file_assistant->GetBestOatFile().release());
VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="
<< (oat_file != nullptr ? oat_file->GetLocation() : "")
<< " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";
@@ -244,13 +251,23 @@
// Load the dex files from the oat file.
bool added_image_space = false;
if (oat_file->IsExecutable()) {
+ if (should_madvise_vdex_and_odex) {
+ VLOG(oat) << "Madvising oat file: " << oat_file->GetLocation();
+ size_t madvise_size_limit = runtime->GetMadviseWillNeedSizeOdex();
+ Runtime::MadviseFileForRange(madvise_size_limit,
+ oat_file->Size(),
+ oat_file->Begin(),
+ oat_file->End(),
+ oat_file->GetLocation());
+ }
+
ScopedTrace app_image_timing("AppImage:Loading");
// We need to throw away the image space if we are debuggable but the oat-file source of the
// image is not otherwise we might get classes with inlined methods or other such things.
std::unique_ptr<gc::space::ImageSpace> image_space;
if (ShouldLoadAppImage(oat_file.get())) {
- image_space = oat_file_assistant.OpenImageSpace(oat_file.get());
+ image_space = oat_file_assistant->OpenImageSpace(oat_file.get());
}
if (image_space != nullptr) {
ScopedObjectAccess soa(self);
@@ -310,12 +327,13 @@
<< oat_file->GetLocation()
<< " non-executable as it requires an image which we failed to load";
// file as non-executable.
- OatFileAssistant nonexecutable_oat_file_assistant(dex_location,
- kRuntimeISA,
- context.get(),
- /*load_executable=*/false,
- only_use_system_oat_files_);
- oat_file.reset(nonexecutable_oat_file_assistant.GetBestOatFile().release());
+ auto nonexecutable_oat_file_assistant =
+ std::make_unique<OatFileAssistant>(dex_location,
+ kRuntimeISA,
+ context.get(),
+ /*load_executable=*/false,
+ only_use_system_oat_files_);
+ oat_file.reset(nonexecutable_oat_file_assistant->GetBestOatFile().release());
// The file could be deleted concurrently (for example background
// dexopt, or secondary oat file being deleted by the app).
@@ -325,7 +343,7 @@
}
if (oat_file != nullptr) {
- dex_files = oat_file_assistant.LoadDexFiles(*oat_file.get(), dex_location);
+ dex_files = oat_file_assistant->LoadDexFiles(*oat_file.get(), dex_location);
// Register for tracking.
for (const auto& dex_file : dex_files) {
@@ -345,7 +363,8 @@
if (oat_file != nullptr) {
VdexFile* vdex_file = oat_file->GetVdexFile();
- if (vdex_file != nullptr) {
+ if (should_madvise_vdex_and_odex && vdex_file != nullptr) {
+ VLOG(oat) << "Madvising vdex file: " << vdex_file->GetName();
// Opened vdex file from an oat file, madvise it to its loaded state.
// TODO(b/196052575): Unify dex and vdex madvise knobs and behavior.
const size_t madvise_size_limit = Runtime::Current()->GetMadviseWillNeedSizeVdex();
@@ -365,7 +384,7 @@
// If so, report an error with the current stack trace.
// Most likely the developer didn't intend to do this because it will waste
// performance and memory.
- if (oat_file_assistant.GetBestStatus() == OatFileAssistant::kOatContextOutOfDate) {
+ if (oat_file_assistant->GetBestStatus() == OatFileAssistant::kOatContextOutOfDate) {
std::set<const DexFile*> already_exists_in_classpath =
context->CheckForDuplicateDexFiles(MakeNonOwningPointerVector(dex_files));
if (!already_exists_in_classpath.empty()) {
diff --git a/runtime/oat_quick_method_header.h b/runtime/oat_quick_method_header.h
index e347588..0bbf23f 100644
--- a/runtime/oat_quick_method_header.h
+++ b/runtime/oat_quick_method_header.h
@@ -28,6 +28,11 @@
class ArtMethod;
+// Size in bytes of the should_deoptimize flag on stack.
+// We just need 4 bytes for our purpose regardless of the architecture. Frame size
+// calculation will automatically do alignment for the final frame size.
+static constexpr size_t kShouldDeoptimizeFlagSize = 4;
+
// OatQuickMethodHeader precedes the raw code chunk generated by the compiler.
class PACKED(4) OatQuickMethodHeader {
public:
@@ -145,6 +150,17 @@
return CodeInfo::DecodeFrameInfo(GetOptimizedCodeInfoPtr());
}
+ size_t GetShouldDeoptimizeFlagOffset() const {
+ DCHECK(IsOptimized());
+ QuickMethodFrameInfo frame_info = GetFrameInfo();
+ size_t frame_size = frame_info.FrameSizeInBytes();
+ size_t core_spill_size =
+ POPCOUNT(frame_info.CoreSpillMask()) * GetBytesPerGprSpillLocation(kRuntimeISA);
+ size_t fpu_spill_size =
+ POPCOUNT(frame_info.FpSpillMask()) * GetBytesPerFprSpillLocation(kRuntimeISA);
+ return frame_size - core_spill_size - fpu_spill_size - kShouldDeoptimizeFlagSize;
+ }
+
uintptr_t ToNativeQuickPc(ArtMethod* method,
const uint32_t dex_pc,
bool is_for_catch_handler,
diff --git a/runtime/offsets.h b/runtime/offsets.h
index cc18bf4..7974111 100644
--- a/runtime/offsets.h
+++ b/runtime/offsets.h
@@ -37,12 +37,28 @@
constexpr size_t SizeValue() const {
return val_;
}
+ Offset& operator+=(const size_t rhs) {
+ val_ += rhs;
+ return *this;
+ }
constexpr bool operator==(Offset o) const {
return SizeValue() == o.SizeValue();
}
constexpr bool operator!=(Offset o) const {
return !(*this == o);
}
+ constexpr bool operator<(Offset o) const {
+ return SizeValue() < o.SizeValue();
+ }
+ constexpr bool operator<=(Offset o) const {
+ return !(*this > o);
+ }
+ constexpr bool operator>(Offset o) const {
+ return o < *this;
+ }
+ constexpr bool operator>=(Offset o) const {
+ return !(*this < o);
+ }
protected:
size_t val_;
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 4d24482..0450e3e 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -732,9 +732,7 @@
Exit(0);
}
// If `boot.art` exists in the ART APEX, it will be used. Otherwise, Everything will be JITed.
- args.Set(M::Image,
- ParseStringList<':'>{{"boot.art!/apex/com.android.art/etc/boot-image.prof",
- "/nonx/boot-framework.art!/system/etc/boot-image.prof"}});
+ args.Set(M::Image, ParseStringList<':'>::Split(GetJitZygoteBootImageLocation()));
}
if (!args.Exists(M::CompilerCallbacksPtr) && !args.Exists(M::Image)) {
diff --git a/runtime/quick_exception_handler.cc b/runtime/quick_exception_handler.cc
index 2a6929a..8029c03 100644
--- a/runtime/quick_exception_handler.cc
+++ b/runtime/quick_exception_handler.cc
@@ -16,6 +16,7 @@
#include "quick_exception_handler.h"
#include <ios>
+#include <queue>
#include "arch/context.h"
#include "art_method-inl.h"
@@ -67,12 +68,15 @@
Context* context,
Handle<mirror::Throwable>* exception,
QuickExceptionHandler* exception_handler,
- uint32_t skip_frames)
+ uint32_t skip_frames,
+ bool skip_top_unwind_callback)
REQUIRES_SHARED(Locks::mutator_lock_)
: StackVisitor(self, context, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
exception_(exception),
exception_handler_(exception_handler),
- skip_frames_(skip_frames) {
+ skip_frames_(skip_frames),
+ skip_unwind_callback_(skip_top_unwind_callback) {
+ DCHECK_IMPLIES(skip_unwind_callback_, skip_frames_ == 0);
}
bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
@@ -95,7 +99,24 @@
DCHECK(method->IsCalleeSaveMethod());
return true;
}
- return HandleTryItems(method);
+ bool continue_stack_walk = HandleTryItems(method);
+ // Collect methods for which MethodUnwind callback needs to be invoked. MethodUnwind callback
+ // can potentially throw, so we want to call these after we find the catch block.
+ // We stop the stack walk when we find the catch block. If we are ending the stack walk we don't
+ // have to unwind this method so don't record it.
+ if (continue_stack_walk && !skip_unwind_callback_) {
+ // Skip unwind callback is only used when method exit callback has thrown an exception. In
+ // that case, we should have runtime method (artMethodExitHook) on top of stack and the
+ // second should be the method for which method exit was called.
+ DCHECK_IMPLIES(skip_unwind_callback_, GetFrameDepth() == 2);
+ unwound_methods_.push(method);
+ }
+ skip_unwind_callback_ = false;
+ return continue_stack_walk;
+ }
+
+ std::queue<ArtMethod*>& GetUnwoundMethods() {
+ return unwound_methods_;
}
private:
@@ -139,20 +160,29 @@
QuickExceptionHandler* const exception_handler_;
// The number of frames to skip searching for catches in.
uint32_t skip_frames_;
+ // The list of methods we would skip to reach the catch block. We record these to call
+ // MethodUnwind callbacks.
+ std::queue<ArtMethod*> unwound_methods_;
+ // Specifies if the unwind callback should be ignored for method at the top of the stack.
+ bool skip_unwind_callback_;
DISALLOW_COPY_AND_ASSIGN(CatchBlockStackVisitor);
};
// Finds the appropriate exception catch after calling all method exit instrumentation functions.
-// Note that this might change the exception being thrown.
-void QuickExceptionHandler::FindCatch(ObjPtr<mirror::Throwable> exception) {
+// Note that this might change the exception being thrown. If is_method_exit_exception is true
+// skip the method unwind call for the method on top of the stack as the exception was thrown by
+// method exit callback.
+void QuickExceptionHandler::FindCatch(ObjPtr<mirror::Throwable> exception,
+ bool is_method_exit_exception) {
DCHECK(!is_deoptimization_);
- instrumentation::InstrumentationStackPopper popper(self_);
+ instrumentation::Instrumentation* instr = Runtime::Current()->GetInstrumentation();
// The number of total frames we have so far popped.
uint32_t already_popped = 0;
bool popped_to_top = true;
StackHandleScope<1> hs(self_);
MutableHandle<mirror::Throwable> exception_ref(hs.NewHandle(exception));
+ bool skip_top_unwind_callback = is_method_exit_exception;
// Sending the instrumentation events (done by the InstrumentationStackPopper) can cause new
// exceptions to be thrown which will override the current exception. Therefore we need to perform
// the search for a catch in a loop until we have successfully popped all the way to a catch or
@@ -166,11 +196,15 @@
}
// Walk the stack to find catch handler.
- CatchBlockStackVisitor visitor(self_, context_,
+ CatchBlockStackVisitor visitor(self_,
+ context_,
&exception_ref,
this,
- /*skip_frames=*/already_popped);
+ /*skip_frames=*/already_popped,
+ skip_top_unwind_callback);
visitor.WalkStack(true);
+ skip_top_unwind_callback = false;
+
uint32_t new_pop_count = handler_frame_depth_;
DCHECK_GE(new_pop_count, already_popped);
already_popped = new_pop_count;
@@ -195,9 +229,13 @@
handler_method_header_->IsOptimized()) {
SetCatchEnvironmentForOptimizedHandler(&visitor);
}
- popped_to_top =
- popper.PopFramesTo(reinterpret_cast<uintptr_t>(handler_quick_frame_), exception_ref);
+ popped_to_top = instr->ProcessMethodUnwindCallbacks(self_,
+ visitor.GetUnwoundMethods(),
+ exception_ref);
} while (!popped_to_top);
+
+ // Pop off frames on instrumentation stack to keep it in sync with what is on the stack.
+ instr->PopInstrumentationStackUntil(self_, reinterpret_cast<uintptr_t>(handler_quick_frame_));
if (!clear_exception_) {
// Put exception back in root set with clear throw location.
self_->SetException(exception_ref.Get());
@@ -361,13 +399,15 @@
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.
- CHECK_EQ(GetFrameDepth(), 1U);
+ // 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.
+ CHECK((GetFrameDepth() == 1U) || (GetFrameDepth() == 0U));
callee_method_ = method;
return true;
} else if (!single_frame_deopt_ &&
- !Runtime::Current()->IsAsyncDeoptimizeable(GetCurrentQuickFramePc())) {
+ !Runtime::Current()->IsAsyncDeoptimizeable(GetOuterMethod(),
+ GetCurrentQuickFramePc())) {
// We hit some code that's not deoptimizeable. However, Single-frame deoptimization triggered
// from compiled code is always allowed since HDeoptimize always saves the full environment.
LOG(WARNING) << "Got request to deoptimize un-deoptimizable method "
@@ -642,7 +682,7 @@
uintptr_t return_pc = 0;
if (method_tracing_active_) {
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
- return_pc = instrumentation->PopFramesForDeoptimization(
+ return_pc = instrumentation->PopInstrumentationStackUntil(
self_, reinterpret_cast<uintptr_t>(handler_quick_frame_));
}
return return_pc;
diff --git a/runtime/quick_exception_handler.h b/runtime/quick_exception_handler.h
index 4ff981d..9554f1d 100644
--- a/runtime/quick_exception_handler.h
+++ b/runtime/quick_exception_handler.h
@@ -49,7 +49,8 @@
// Find the catch handler for the given exception and call all required Instrumentation methods.
// Note this might result in the exception being caught being different from 'exception'.
- void FindCatch(ObjPtr<mirror::Throwable> exception) REQUIRES_SHARED(Locks::mutator_lock_);
+ void FindCatch(ObjPtr<mirror::Throwable> exception, bool is_method_exit_exception)
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Deoptimize the stack to the upcall/some code that's not deoptimizeable. For
// every compiled frame, we create a "copy" shadow frame that will be executed
diff --git a/runtime/read_barrier-inl.h b/runtime/read_barrier-inl.h
index b0434d8..ff4693f 100644
--- a/runtime/read_barrier-inl.h
+++ b/runtime/read_barrier-inl.h
@@ -21,6 +21,7 @@
#include "gc/accounting/read_barrier_table.h"
#include "gc/collector/concurrent_copying-inl.h"
+#include "gc/collector/mark_compact.h"
#include "gc/heap.h"
#include "mirror/object-readbarrier-inl.h"
#include "mirror/object_reference.h"
@@ -34,7 +35,7 @@
inline MirrorType* ReadBarrier::Barrier(
mirror::Object* obj, MemberOffset offset, mirror::HeapReference<MirrorType>* ref_addr) {
constexpr bool with_read_barrier = kReadBarrierOption == kWithReadBarrier;
- if (kUseReadBarrier && with_read_barrier) {
+ if (gUseReadBarrier && with_read_barrier) {
if (kCheckDebugDisallowReadBarrierCount) {
Thread* const self = Thread::Current();
if (self != nullptr) {
@@ -91,6 +92,12 @@
LOG(FATAL) << "Unexpected read barrier type";
UNREACHABLE();
}
+ } else if (kReadBarrierOption == kWithFromSpaceBarrier) {
+ CHECK(gUseUserfaultfd);
+ MirrorType* old = ref_addr->template AsMirrorPtr<kIsVolatile>();
+ mirror::Object* ref =
+ Runtime::Current()->GetHeap()->MarkCompactCollector()->GetFromSpaceAddrFromBarrier(old);
+ return reinterpret_cast<MirrorType*>(ref);
} else {
// No read barrier.
return ref_addr->template AsMirrorPtr<kIsVolatile>();
@@ -102,7 +109,7 @@
GcRootSource* gc_root_source) {
MirrorType* ref = *root;
const bool with_read_barrier = kReadBarrierOption == kWithReadBarrier;
- if (kUseReadBarrier && with_read_barrier) {
+ if (gUseReadBarrier && with_read_barrier) {
if (kCheckDebugDisallowReadBarrierCount) {
Thread* const self = Thread::Current();
if (self != nullptr) {
@@ -147,7 +154,7 @@
GcRootSource* gc_root_source) {
MirrorType* ref = root->AsMirrorPtr();
const bool with_read_barrier = kReadBarrierOption == kWithReadBarrier;
- if (kUseReadBarrier && with_read_barrier) {
+ if (gUseReadBarrier && with_read_barrier) {
if (kCheckDebugDisallowReadBarrierCount) {
Thread* const self = Thread::Current();
if (self != nullptr) {
@@ -192,7 +199,7 @@
inline MirrorType* ReadBarrier::IsMarked(MirrorType* ref) {
// Only read-barrier configurations can have mutators run while
// the GC is marking.
- if (!kUseReadBarrier) {
+ if (!gUseReadBarrier) {
return ref;
}
// IsMarked does not handle null, so handle it here.
diff --git a/runtime/read_barrier.h b/runtime/read_barrier.h
index 3b89377..be5a9a0 100644
--- a/runtime/read_barrier.h
+++ b/runtime/read_barrier.h
@@ -94,7 +94,7 @@
// Without the holder object, and only with the read barrier configuration (no-op otherwise).
static void MaybeAssertToSpaceInvariant(mirror::Object* ref)
REQUIRES_SHARED(Locks::mutator_lock_) {
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
AssertToSpaceInvariant(ref);
}
}
diff --git a/runtime/read_barrier_config.h b/runtime/read_barrier_config.h
index d505bed..d5baee2 100644
--- a/runtime/read_barrier_config.h
+++ b/runtime/read_barrier_config.h
@@ -62,17 +62,17 @@
static constexpr bool kUseTableLookupReadBarrier = false;
#endif
-static constexpr bool kUseReadBarrier = kUseBakerReadBarrier || kUseTableLookupReadBarrier;
-
-// Debugging flag that forces the generation of read barriers, but
-// does not trigger the use of the concurrent copying GC.
-//
-// TODO: Remove this flag when the read barriers compiler
-// instrumentation is completed.
-static constexpr bool kForceReadBarrier = false;
-// TODO: Likewise, remove this flag when kForceReadBarrier is removed
-// and replace it with kUseReadBarrier.
-static constexpr bool kEmitCompilerReadBarrier = kForceReadBarrier || kUseReadBarrier;
+#ifdef ART_FORCE_USE_READ_BARRIER
+constexpr bool gUseReadBarrier = kUseBakerReadBarrier || kUseTableLookupReadBarrier;
+constexpr bool gUseUserfaultfd = !gUseReadBarrier;
+#else
+extern const bool gUseReadBarrier;
+#ifdef ART_DEFAULT_GC_TYPE_IS_CMC
+extern const bool gUseUserfaultfd;
+#else
+constexpr bool gUseUserfaultfd = false;
+#endif
+#endif
// Disabled for performance reasons.
static constexpr bool kCheckDebugDisallowReadBarrierCount = kIsDebugBuild;
diff --git a/runtime/read_barrier_option.h b/runtime/read_barrier_option.h
index d918d46..36fc2d2 100644
--- a/runtime/read_barrier_option.h
+++ b/runtime/read_barrier_option.h
@@ -84,6 +84,7 @@
enum ReadBarrierOption {
kWithReadBarrier, // Perform a read barrier.
kWithoutReadBarrier, // Don't perform a read barrier.
+ kWithFromSpaceBarrier, // Get the from-space address for the given to-space address. Used by CMC
};
} // namespace art
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index a7290a2..afa49d0 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -523,6 +523,7 @@
} // anonymous namespace
template <>
+NO_STACK_PROTECTOR
JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
jobject obj,
ArtMethod* method,
@@ -555,6 +556,7 @@
}
template <>
+NO_STACK_PROTECTOR
JValue InvokeWithVarArgs(const ScopedObjectAccessAlreadyRunnable& soa,
jobject obj,
jmethodID mid,
diff --git a/runtime/reflection.h b/runtime/reflection.h
index b0e27da..13dc8e1 100644
--- a/runtime/reflection.h
+++ b/runtime/reflection.h
@@ -99,6 +99,7 @@
// num_frames is number of frames we look up for access check.
template<PointerSize pointer_size>
+NO_STACK_PROTECTOR
jobject InvokeMethod(const ScopedObjectAccessAlreadyRunnable& soa,
jobject method,
jobject receiver,
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index e20f883..6e583af 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -16,15 +16,15 @@
#include "runtime.h"
-// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
-#include <sys/mount.h>
+#include <utility>
+
#ifdef __linux__
-#include <linux/fs.h>
#include <sys/prctl.h>
#endif
#include <fcntl.h>
#include <signal.h>
+#include <sys/mount.h>
#include <sys/syscall.h>
#if defined(__APPLE__)
@@ -125,6 +125,7 @@
#include "native/dalvik_system_ZygoteHooks.h"
#include "native/java_lang_Class.h"
#include "native/java_lang_Object.h"
+#include "native/java_lang_StackStreamFactory.h"
#include "native/java_lang_String.h"
#include "native/java_lang_StringFactory.h"
#include "native/java_lang_System.h"
@@ -152,6 +153,7 @@
#include "native_bridge_art_interface.h"
#include "native_stack_dump.h"
#include "nativehelper/scoped_local_ref.h"
+#include "nterp_helpers.h"
#include "oat.h"
#include "oat_file_manager.h"
#include "oat_quick_method_header.h"
@@ -178,6 +180,7 @@
#include "well_known_classes.h"
#ifdef ART_TARGET_ANDROID
+#include <android/api-level.h>
#include <android/set_abort_message.h>
#include "com_android_apex.h"
namespace apex = com::android::apex;
@@ -200,10 +203,6 @@
static constexpr double kNormalMinLoadFactor = 0.4;
static constexpr double kNormalMaxLoadFactor = 0.7;
-// Extra added to the default heap growth multiplier. Used to adjust the GC ergonomics for the read
-// barrier config.
-static constexpr double kExtraDefaultHeapGrowthMultiplier = kUseReadBarrier ? 1.0 : 0.0;
-
Runtime* Runtime::instance_ = nullptr;
struct TraceConfig {
@@ -406,6 +405,12 @@
if (oat_file_manager_ != nullptr) {
oat_file_manager_->WaitForWorkersToBeCreated();
}
+ // Disable GC before deleting the thread-pool and shutting down runtime as it
+ // restricts attaching new threads.
+ heap_->DisableGCForShutdown();
+ heap_->WaitForWorkersToBeCreated();
+ // Make sure to let the GC complete if it is running.
+ heap_->WaitForGcToComplete(gc::kGcCauseBackground, self);
{
ScopedTrace trace2("Wait for shutdown cond");
@@ -440,8 +445,6 @@
self = nullptr;
}
- // Make sure to let the GC complete if it is running.
- heap_->WaitForGcToComplete(gc::kGcCauseBackground, self);
heap_->DeleteThreadPool();
if (oat_file_manager_ != nullptr) {
oat_file_manager_->DeleteThreadPool();
@@ -543,7 +546,7 @@
os << "Runtime aborting...\n";
if (Runtime::Current() == nullptr) {
os << "(Runtime does not yet exist!)\n";
- DumpNativeStack(os, GetTid(), nullptr, " native: ", nullptr);
+ DumpNativeStack(os, GetTid(), " native: ", nullptr);
return;
}
Thread* self = Thread::Current();
@@ -555,7 +558,7 @@
if (self == nullptr) {
os << "(Aborting thread was not attached to runtime!)\n";
- DumpNativeStack(os, GetTid(), nullptr, " native: ", nullptr);
+ DumpNativeStack(os, GetTid(), " native: ", nullptr);
} else {
os << "Aborting thread:\n";
if (Locks::mutator_lock_->IsExclusiveHeld(self) || Locks::mutator_lock_->IsSharedHeld(self)) {
@@ -697,38 +700,52 @@
// notreached
}
-class FindNativeMethodsVisitor : public ClassVisitor {
+/**
+ * Update entrypoints (native and Java) of methods before the first fork. This
+ * helps sharing pages where ArtMethods are allocated between the zygote and
+ * forked apps.
+ */
+class UpdateMethodsPreFirstForkVisitor : public ClassVisitor {
public:
- FindNativeMethodsVisitor(Thread* self, ClassLinker* class_linker)
+ UpdateMethodsPreFirstForkVisitor(Thread* self, ClassLinker* class_linker)
: vm_(down_cast<JNIEnvExt*>(self->GetJniEnv())->GetVm()),
self_(self),
- class_linker_(class_linker) {}
+ class_linker_(class_linker),
+ can_use_nterp_(interpreter::CanRuntimeUseNterp()) {}
bool operator()(ObjPtr<mirror::Class> klass) override REQUIRES_SHARED(Locks::mutator_lock_) {
bool is_initialized = klass->IsVisiblyInitialized();
for (ArtMethod& method : klass->GetDeclaredMethods(kRuntimePointerSize)) {
- if (method.IsNative() && (is_initialized || !NeedsClinitCheckBeforeCall(&method))) {
- const void* existing = method.GetEntryPointFromJni();
- if (method.IsCriticalNative()
- ? class_linker_->IsJniDlsymLookupCriticalStub(existing)
- : class_linker_->IsJniDlsymLookupStub(existing)) {
- const void* native_code =
- vm_->FindCodeForNativeMethod(&method, /*error_msg=*/ nullptr, /*can_suspend=*/ false);
- if (native_code != nullptr) {
- class_linker_->RegisterNative(self_, &method, native_code);
+ if (is_initialized || !NeedsClinitCheckBeforeCall(&method)) {
+ if (method.IsNative()) {
+ const void* existing = method.GetEntryPointFromJni();
+ if (method.IsCriticalNative()
+ ? class_linker_->IsJniDlsymLookupCriticalStub(existing)
+ : class_linker_->IsJniDlsymLookupStub(existing)) {
+ const void* native_code =
+ vm_->FindCodeForNativeMethod(&method, /*error_msg=*/ nullptr, /*can_suspend=*/ false);
+ if (native_code != nullptr) {
+ class_linker_->RegisterNative(self_, &method, native_code);
+ }
}
}
+ } else if (can_use_nterp_) {
+ const void* existing = method.GetEntryPointFromQuickCompiledCode();
+ if (class_linker_->IsQuickResolutionStub(existing) && CanMethodUseNterp(&method)) {
+ method.SetEntryPointFromQuickCompiledCode(interpreter::GetNterpWithClinitEntryPoint());
+ }
}
}
return true;
}
private:
- JavaVMExt* vm_;
- Thread* self_;
- ClassLinker* class_linker_;
+ JavaVMExt* const vm_;
+ Thread* const self_;
+ ClassLinker* const class_linker_;
+ const bool can_use_nterp_;
- DISALLOW_COPY_AND_ASSIGN(FindNativeMethodsVisitor);
+ DISALLOW_COPY_AND_ASSIGN(UpdateMethodsPreFirstForkVisitor);
};
void Runtime::PreZygoteFork() {
@@ -742,8 +759,7 @@
// Ensure we call FixupStaticTrampolines on all methods that are
// initialized.
class_linker_->MakeInitializedClassesVisiblyInitialized(soa.Self(), /*wait=*/ true);
- // Update native method JNI entrypoints.
- FindNativeMethodsVisitor visitor(soa.Self(), class_linker_);
+ UpdateMethodsPreFirstForkVisitor visitor(soa.Self(), class_linker_);
class_linker_->VisitClasses(&visitor);
}
heap_->PreZygoteFork();
@@ -990,6 +1006,22 @@
}
CreateJitCodeCache(/*rwx_memory_allowed=*/true);
CreateJit();
+#ifdef ADDRESS_SANITIZER
+ // (b/238730394): In older implementations of sanitizer + glibc there is a race between
+ // pthread_create and dlopen that could cause a deadlock. pthread_create interceptor in ASAN
+ // uses dl_pthread_iterator with a callback that could request a dl_load_lock via call to
+ // __tls_get_addr [1]. dl_pthread_iterate would already hold dl_load_lock so this could cause a
+ // deadlock. __tls_get_addr needs a dl_load_lock only when there is a dlopen happening in
+ // parallel. As a workaround we wait for the pthread_create (i.e JIT thread pool creation) to
+ // finish before going to the next phase. Creating a system class loader could need a dlopen so
+ // we wait here till threads are initialized.
+ // [1] https://github.com/llvm/llvm-project/blob/main/compiler-rt/lib/sanitizer_common/sanitizer_linux_libcdep.cpp#L408
+ // See this for more context: https://reviews.llvm.org/D98926
+ // TODO(b/238730394): Revisit this workaround once we migrate to musl libc.
+ if (jit_ != nullptr) {
+ jit_->GetThreadPool()->WaitForWorkersToBeCreated();
+ }
+#endif
}
// Send the start phase event. We have to wait till here as this is when the main thread peer
@@ -1128,8 +1160,8 @@
std::vector<std::string> jars = android::base::Split(system_server_classpath, ":");
app_info_.RegisterAppInfo("android",
jars,
- /*cur_profile_path=*/ "",
- /*ref_profile_path=*/ "",
+ /*profile_output_filename=*/ "",
+ /*ref_profile_filename=*/ "",
AppInfo::CodeType::kPrimaryApk);
}
@@ -1144,7 +1176,6 @@
}
// Create the thread pools.
- heap_->CreateThreadPool();
// Avoid creating the runtime thread pool for system server since it will not be used and would
// waste memory.
if (!is_system_server) {
@@ -1328,9 +1359,9 @@
detailMessageField->SetObject</* kTransactionActive= */ false>(exception->Read(), message);
}
-void Runtime::InitializeApexVersions() {
+std::string Runtime::GetApexVersions(ArrayRef<const std::string> boot_class_path_locations) {
std::vector<std::string_view> bcp_apexes;
- for (std::string_view jar : Runtime::Current()->GetBootClassPathLocations()) {
+ for (std::string_view jar : boot_class_path_locations) {
std::string_view apex = ApexNameFromLocation(jar);
if (!apex.empty()) {
bcp_apexes.push_back(apex);
@@ -1338,20 +1369,20 @@
}
static const char* kApexFileName = "/apex/apex-info-list.xml";
// Start with empty markers.
- apex_versions_ = std::string(bcp_apexes.size(), '/');
+ std::string empty_apex_versions(bcp_apexes.size(), '/');
// When running on host or chroot, we just use empty markers.
if (!kIsTargetBuild || !OS::FileExists(kApexFileName)) {
- return;
+ return empty_apex_versions;
}
#ifdef ART_TARGET_ANDROID
if (access(kApexFileName, R_OK) != 0) {
PLOG(WARNING) << "Failed to read " << kApexFileName;
- return;
+ return empty_apex_versions;
}
auto info_list = apex::readApexInfoList(kApexFileName);
if (!info_list.has_value()) {
LOG(WARNING) << "Failed to parse " << kApexFileName;
- return;
+ return empty_apex_versions;
}
std::string result;
@@ -1375,10 +1406,17 @@
android::base::StringAppendF(&result, "/%" PRIu64, version);
}
}
- apex_versions_ = result;
+ return result;
+#else
+ return empty_apex_versions; // Not an Android build.
#endif
}
+void Runtime::InitializeApexVersions() {
+ apex_versions_ =
+ GetApexVersions(ArrayRef<const std::string>(Runtime::Current()->GetBootClassPathLocations()));
+}
+
void Runtime::ReloadAllFlags(const std::string& caller) {
FlagBase::ReloadAllFlags(caller);
}
@@ -1587,9 +1625,11 @@
// If low memory mode, use 1.0 as the multiplier by default.
foreground_heap_growth_multiplier = 1.0f;
} else {
+ // Extra added to the default heap growth multiplier for concurrent GC
+ // compaction algorithms. This is done for historical reasons.
+ // TODO: remove when we revisit heap configurations.
foreground_heap_growth_multiplier =
- runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier) +
- kExtraDefaultHeapGrowthMultiplier;
+ runtime_options.GetOrDefault(Opt::ForegroundHeapGrowthMultiplier) + 1.0f;
}
XGcOption xgc_option = runtime_options.GetOrDefault(Opt::GcOption);
@@ -1617,9 +1657,9 @@
image_locations_,
instruction_set_,
// Override the collector type to CC if the read barrier config.
- kUseReadBarrier ? gc::kCollectorTypeCC : xgc_option.collector_type_,
- kUseReadBarrier ? BackgroundGcOption(gc::kCollectorTypeCCBackground)
- : runtime_options.GetOrDefault(Opt::BackgroundGc),
+ gUseReadBarrier ? gc::kCollectorTypeCC : xgc_option.collector_type_,
+ gUseReadBarrier ? BackgroundGcOption(gc::kCollectorTypeCCBackground)
+ : BackgroundGcOption(xgc_option.collector_type_),
runtime_options.GetOrDefault(Opt::LargeObjectSpace),
runtime_options.GetOrDefault(Opt::LargeObjectThreshold),
runtime_options.GetOrDefault(Opt::ParallelGCThreads),
@@ -2237,6 +2277,7 @@
register_java_lang_reflect_Parameter(env);
register_java_lang_reflect_Proxy(env);
register_java_lang_ref_Reference(env);
+ register_java_lang_StackStreamFactory(env);
register_java_lang_String(env);
register_java_lang_StringFactory(env);
register_java_lang_System(env);
@@ -2457,6 +2498,9 @@
class_linker_->VisitRoots(visitor, flags);
jni_id_manager_->VisitRoots(visitor);
heap_->VisitAllocationRecords(visitor);
+ if (jit_ != nullptr) {
+ jit_->GetCodeCache()->VisitRoots(visitor);
+ }
if ((flags & kVisitRootFlagNewRoots) == 0) {
// Guaranteed to have no new roots in the constant roots.
VisitConstantRoots(visitor);
@@ -2585,7 +2629,7 @@
}
void Runtime::DisallowNewSystemWeaks() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
monitor_list_->DisallowNewMonitors();
intern_table_->ChangeWeakRootState(gc::kWeakRootStateNoReadsOrWrites);
java_vm_->DisallowNewWeakGlobals();
@@ -2601,7 +2645,7 @@
}
void Runtime::AllowNewSystemWeaks() {
- CHECK(!kUseReadBarrier);
+ CHECK(!gUseReadBarrier);
monitor_list_->AllowNewMonitors();
intern_table_->ChangeWeakRootState(gc::kWeakRootStateNormal); // TODO: Do this in the sweeping.
java_vm_->AllowNewWeakGlobals();
@@ -3043,12 +3087,13 @@
return verify_ == verifier::VerifyMode::kSoftFail;
}
-bool Runtime::IsAsyncDeoptimizeable(uintptr_t code) const {
+bool Runtime::IsAsyncDeoptimizeable(ArtMethod* method, uintptr_t code) const {
if (OatQuickMethodHeader::NterpMethodHeader != nullptr) {
if (OatQuickMethodHeader::NterpMethodHeader->Contains(code)) {
return true;
}
}
+
// We only support async deopt (ie the compiled code is not explicitly asking for
// deopt, but something else like the debugger) in debuggable JIT code.
// We could look at the oat file where `code` is being defined,
@@ -3056,8 +3101,14 @@
// only rely on the JIT for debuggable apps.
// The JIT-zygote is not debuggable so we need to be sure to exclude code from the non-private
// region as well.
- return IsJavaDebuggable() && GetJit() != nullptr &&
- GetJit()->GetCodeCache()->PrivateRegionContainsPc(reinterpret_cast<const void*>(code));
+ if (GetJit() != nullptr &&
+ GetJit()->GetCodeCache()->PrivateRegionContainsPc(reinterpret_cast<const void*>(code))) {
+ // If the code is JITed code then check if it was compiled as debuggable.
+ const OatQuickMethodHeader* header = method->GetOatQuickMethodHeader(code);
+ return CodeInfo::IsDebuggable(header->GetOptimizedCodeInfoPtr());
+ }
+
+ return false;
}
LinearAlloc* Runtime::CreateLinearAlloc() {
@@ -3144,15 +3195,19 @@
auto pointer_size = Runtime::Current()->GetClassLinker()->GetImagePointerSize();
for (auto& m : klass->GetMethods(pointer_size)) {
const void* code = m.GetEntryPointFromQuickCompiledCode();
+ // For java debuggable runtimes we also deoptimize native methods. For other cases (boot
+ // image profiling) we don't need to deoptimize native methods. If this changes also
+ // update Instrumentation::CanUseAotCode.
+ bool deoptimize_native_methods = Runtime::Current()->IsJavaDebuggable();
if (Runtime::Current()->GetHeap()->IsInBootImageOatFile(code) &&
- !m.IsNative() &&
+ (!m.IsNative() || deoptimize_native_methods) &&
!m.IsProxyMethod()) {
instrumentation_->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
}
if (Runtime::Current()->GetJit() != nullptr &&
Runtime::Current()->GetJit()->GetCodeCache()->IsInZygoteExecSpace(code) &&
- !m.IsNative()) {
+ (!m.IsNative() || deoptimize_native_methods)) {
DCHECK(!m.IsProxyMethod());
instrumentation_->InitializeMethodsCode(&m, /*aot_code=*/ nullptr);
}
@@ -3180,14 +3235,12 @@
// If we've already started and we are setting this runtime to debuggable,
// we patch entry points of methods in boot image to interpreter bridge, as
// boot image code may be AOT compiled as not debuggable.
- if (!GetInstrumentation()->IsForcedInterpretOnly()) {
- UpdateEntryPointsClassVisitor visitor(GetInstrumentation());
- GetClassLinker()->VisitClasses(&visitor);
- jit::Jit* jit = GetJit();
- if (jit != nullptr) {
- // Code previously compiled may not be compiled debuggable.
- jit->GetCodeCache()->TransitionToDebuggable();
- }
+ UpdateEntryPointsClassVisitor visitor(GetInstrumentation());
+ GetClassLinker()->VisitClasses(&visitor);
+ jit::Jit* jit = GetJit();
+ if (jit != nullptr) {
+ // Code previously compiled may not be compiled debuggable.
+ jit->GetCodeCache()->TransitionToDebuggable();
}
}
@@ -3358,6 +3411,22 @@
const uint8_t* map_begin,
const uint8_t* map_end,
const std::string& file_name) {
+#ifdef ART_TARGET_ANDROID
+ // Short-circuit the madvise optimization for background processes. This
+ // avoids IO and memory contention with foreground processes, particularly
+ // those involving app startup.
+ // Note: We can only safely short-circuit the madvise on T+, as it requires
+ // the framework to always immediately notify ART of process states.
+ static const int kApiLevel = android_get_device_api_level();
+ const bool accurate_process_state_at_startup = kApiLevel >= __ANDROID_API_T__;
+ if (accurate_process_state_at_startup) {
+ const Runtime* runtime = Runtime::Current();
+ if (runtime != nullptr && !runtime->InJankPerceptibleProcessState()) {
+ return;
+ }
+ }
+#endif // ART_TARGET_ANDROID
+
// Ideal blockTransferSize for madvising files (128KiB)
static constexpr size_t kIdealIoTransferSizeBytes = 128*1024;
@@ -3399,6 +3468,8 @@
}
}
+// Return whether a boot image has a profile. This means we'll need to pre-JIT
+// methods in that profile for performance.
bool Runtime::HasImageWithProfile() const {
for (gc::space::ImageSpace* space : GetHeap()->GetBootImageSpaces()) {
if (!space->GetProfileFiles().empty()) {
@@ -3408,4 +3479,70 @@
return false;
}
+void Runtime::AppendToBootClassPath(const std::string& filename, const std::string& location) {
+ DCHECK(!DexFileLoader::IsMultiDexLocation(filename.c_str()));
+ boot_class_path_.push_back(filename);
+ if (!boot_class_path_locations_.empty()) {
+ DCHECK(!DexFileLoader::IsMultiDexLocation(location.c_str()));
+ boot_class_path_locations_.push_back(location);
+ }
+}
+
+void Runtime::AppendToBootClassPath(
+ const std::string& filename,
+ const std::string& location,
+ const std::vector<std::unique_ptr<const art::DexFile>>& dex_files) {
+ AppendToBootClassPath(filename, location);
+ ScopedObjectAccess soa(Thread::Current());
+ for (const std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
+ // The first element must not be at a multi-dex location, while other elements must be.
+ DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+ dex_file.get() == dex_files.begin()->get());
+ GetClassLinker()->AppendToBootClassPath(Thread::Current(), dex_file.get());
+ }
+}
+
+void Runtime::AppendToBootClassPath(const std::string& filename,
+ const std::string& location,
+ const std::vector<const art::DexFile*>& dex_files) {
+ AppendToBootClassPath(filename, location);
+ ScopedObjectAccess soa(Thread::Current());
+ for (const art::DexFile* dex_file : dex_files) {
+ // The first element must not be at a multi-dex location, while other elements must be.
+ DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+ dex_file == *dex_files.begin());
+ GetClassLinker()->AppendToBootClassPath(Thread::Current(), dex_file);
+ }
+}
+
+void Runtime::AppendToBootClassPath(
+ const std::string& filename,
+ const std::string& location,
+ const std::vector<std::pair<const art::DexFile*, ObjPtr<mirror::DexCache>>>&
+ dex_files_and_cache) {
+ AppendToBootClassPath(filename, location);
+ ScopedObjectAccess soa(Thread::Current());
+ for (const auto& [dex_file, dex_cache] : dex_files_and_cache) {
+ // The first element must not be at a multi-dex location, while other elements must be.
+ DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+ dex_file == dex_files_and_cache.begin()->first);
+ GetClassLinker()->AppendToBootClassPath(dex_file, dex_cache);
+ }
+}
+
+void Runtime::AddExtraBootDexFiles(const std::string& filename,
+ const std::string& location,
+ std::vector<std::unique_ptr<const art::DexFile>>&& dex_files) {
+ AppendToBootClassPath(filename, location);
+ ScopedObjectAccess soa(Thread::Current());
+ if (kIsDebugBuild) {
+ for (const std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
+ // The first element must not be at a multi-dex location, while other elements must be.
+ DCHECK_NE(DexFileLoader::IsMultiDexLocation(dex_file->GetLocation().c_str()),
+ dex_file.get() == dex_files.begin()->get());
+ }
+ }
+ GetClassLinker()->AddExtraBootDexFiles(Thread::Current(), std::move(dex_files));
+}
+
} // namespace art
diff --git a/runtime/runtime.h b/runtime/runtime.h
index e7b71e2..a932718 100644
--- a/runtime/runtime.h
+++ b/runtime/runtime.h
@@ -257,6 +257,13 @@
return instance_;
}
+ // Set the current runtime to be the given instance.
+ // Note that this function is not responsible for cleaning up the old instance or taking the
+ // ownership of the new instance.
+ //
+ // For test use only.
+ static void TestOnlySetCurrent(Runtime* instance) { instance_ = instance; }
+
// Aborts semi-cleanly. Used in the implementation of LOG(FATAL), which most
// callers should prefer.
NO_RETURN static void Abort(const char* msg) REQUIRES(!Locks::abort_lock_);
@@ -295,6 +302,28 @@
return boot_class_path_locations_.empty() ? boot_class_path_ : boot_class_path_locations_;
}
+ // Dynamically adds an element to boot class path.
+ void AppendToBootClassPath(const std::string& filename,
+ const std::string& location,
+ const std::vector<std::unique_ptr<const art::DexFile>>& dex_files);
+
+ // Same as above, but takes raw pointers.
+ void AppendToBootClassPath(const std::string& filename,
+ const std::string& location,
+ const std::vector<const art::DexFile*>& dex_files);
+
+ // Same as above, but also takes a dex cache for each dex file.
+ void AppendToBootClassPath(
+ const std::string& filename,
+ const std::string& location,
+ const std::vector<std::pair<const art::DexFile*, ObjPtr<mirror::DexCache>>>&
+ dex_files_and_cache);
+
+ // Dynamically adds an element to boot class path and takes ownership of the dex files.
+ void AddExtraBootDexFiles(const std::string& filename,
+ const std::string& location,
+ std::vector<std::unique_ptr<const art::DexFile>>&& dex_files);
+
const std::vector<int>& GetBootClassPathFds() const {
return boot_class_path_fds_;
}
@@ -498,6 +527,10 @@
return OFFSETOF_MEMBER(Runtime, callee_save_methods_[static_cast<size_t>(type)]);
}
+ static constexpr MemberOffset GetInstrumentationOffset() {
+ return MemberOffset(OFFSETOF_MEMBER(Runtime, instrumentation_));
+ }
+
InstructionSet GetInstructionSet() const {
return instruction_set_;
}
@@ -890,7 +923,8 @@
// Returns if the code can be deoptimized asynchronously. Code may be compiled with some
// optimization that makes it impossible to deoptimize.
- bool IsAsyncDeoptimizeable(uintptr_t code) const REQUIRES_SHARED(Locks::mutator_lock_);
+ bool IsAsyncDeoptimizeable(ArtMethod* method, uintptr_t code) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
// Returns a saved copy of the environment (getenv/setenv values).
// Used by Fork to protect against overwriting LD_LIBRARY_PATH, etc.
@@ -1073,6 +1107,10 @@
// image rather that an image loaded from disk.
bool HasImageWithProfile() const;
+ bool GetNoSigChain() const {
+ return no_sig_chain_;
+ }
+
// Trigger a flag reload from system properties or device congfigs.
//
// Should only be called from runtime init and zygote post fork as
@@ -1084,6 +1122,10 @@
// See Flags::ReloadAllFlags as well.
static void ReloadAllFlags(const std::string& caller);
+ // Parses /apex/apex-info-list.xml to build a string containing apex versions of boot classpath
+ // jars, which is encoded into .oat files.
+ static std::string GetApexVersions(ArrayRef<const std::string> boot_class_path_locations);
+
private:
static void InitPlatformSignalHandlers();
@@ -1124,10 +1166,11 @@
ThreadPool* AcquireThreadPool() REQUIRES(!Locks::runtime_thread_pool_lock_);
void ReleaseThreadPool() REQUIRES(!Locks::runtime_thread_pool_lock_);
- // Parses /apex/apex-info-list.xml to initialize a string containing versions
- // of boot classpath jars and encoded into .oat files.
+ // Caches the apex versions produced by `GetApexVersions`.
void InitializeApexVersions();
+ void AppendToBootClassPath(const std::string& filename, const std::string& location);
+
// A pointer to the active runtime or null.
static Runtime* instance_;
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index 753ac28..28c81a2 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -105,9 +105,9 @@
Remove(cb, &method_inspection_callbacks_);
}
-bool RuntimeCallbacks::IsMethodBeingInspected(ArtMethod* m) {
+bool RuntimeCallbacks::HaveLocalsChanged() {
for (MethodInspectionCallback* cb : COPY(method_inspection_callbacks_)) {
- if (cb->IsMethodBeingInspected(m)) {
+ if (cb->HaveLocalsChanged()) {
return true;
}
}
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index b1a7e55..98584a8 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -143,9 +143,8 @@
public:
virtual ~MethodInspectionCallback() {}
- // Returns true if the method is being inspected currently and the runtime should not modify it in
- // potentially dangerous ways (i.e. replace with compiled version, JIT it, etc).
- virtual bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+ // Returns true if any locals have changed. If any locals have changed we shouldn't OSR.
+ virtual bool HaveLocalsChanged() REQUIRES_SHARED(Locks::mutator_lock_) = 0;
};
// Callback to let something request to be notified when reflective objects are being visited and
@@ -225,9 +224,9 @@
void AddParkCallback(ParkCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
void RemoveParkCallback(ParkCallback* cb) REQUIRES_SHARED(Locks::mutator_lock_);
- // Returns true if some MethodInspectionCallback indicates the method is being inspected/depended
- // on by some code.
- bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
+ // Returns true if any locals have changed. This is used to prevent OSRing frames that have
+ // some locals changed.
+ bool HaveLocalsChanged() REQUIRES_SHARED(Locks::mutator_lock_);
void AddMethodInspectionCallback(MethodInspectionCallback* cb)
REQUIRES_SHARED(Locks::mutator_lock_);
diff --git a/runtime/runtime_common.h b/runtime/runtime_common.h
index 925594e..ec08907 100644
--- a/runtime/runtime_common.h
+++ b/runtime/runtime_common.h
@@ -42,7 +42,7 @@
void Dump(std::ostream& os) const {
// This is a backtrace from a crash, do not skip any frames in case the
// crash is in the unwinder itself.
- DumpNativeStack(os, GetTid(), nullptr, "\t", nullptr, raw_context_, false);
+ DumpNativeStack(os, GetTid(), "\t", nullptr, raw_context_, false);
}
private:
// Stores the context of the signal that was unexpected and will terminate the runtime. The
diff --git a/runtime/runtime_options.def b/runtime/runtime_options.def
index 76d1657..6721834 100644
--- a/runtime/runtime_options.def
+++ b/runtime/runtime_options.def
@@ -80,7 +80,7 @@
RUNTIME_OPTIONS_KEY (Unit, IgnoreMaxFootprint)
RUNTIME_OPTIONS_KEY (bool, AlwaysLogExplicitGcs, true)
RUNTIME_OPTIONS_KEY (Unit, LowMemoryMode)
-RUNTIME_OPTIONS_KEY (bool, UseTLAB, (kUseTlab || kUseReadBarrier))
+RUNTIME_OPTIONS_KEY (bool, UseTLAB, kUseTlab)
RUNTIME_OPTIONS_KEY (bool, EnableHSpaceCompactForOOM, true)
RUNTIME_OPTIONS_KEY (bool, UseJitCompilation, true)
RUNTIME_OPTIONS_KEY (bool, UseProfiledJitCompilation, false)
diff --git a/runtime/stack.cc b/runtime/stack.cc
index 50a96d0..5ee20e5 100644
--- a/runtime/stack.cc
+++ b/runtime/stack.cc
@@ -129,7 +129,7 @@
GetCurrentQuickFrame(), cur_quick_frame_pc_, abort_on_failure);
} else if (cur_oat_quick_method_header_->IsOptimized()) {
StackMap* stack_map = GetCurrentStackMap();
- DCHECK(stack_map->IsValid());
+ CHECK(stack_map->IsValid()) << "StackMap not found for " << std::hex << cur_quick_frame_pc_;
return stack_map->GetDexPc();
} else {
DCHECK(cur_oat_quick_method_header_->IsNterpMethodHeader());
@@ -780,7 +780,6 @@
DCHECK(thread_ == Thread::Current() || thread_->IsSuspended());
}
CHECK_EQ(cur_depth_, 0U);
- size_t inlined_frames_count = 0;
for (const ManagedStack* current_fragment = thread_->GetManagedStack();
current_fragment != nullptr; current_fragment = current_fragment->GetLink()) {
@@ -788,6 +787,12 @@
cur_quick_frame_ = current_fragment->GetTopQuickFrame();
cur_quick_frame_pc_ = 0;
DCHECK(cur_oat_quick_method_header_ == nullptr);
+
+ if (kDebugStackWalk) {
+ LOG(INFO) << "Tid=" << thread_-> GetThreadId()
+ << ", ManagedStack fragement: " << current_fragment;
+ }
+
if (cur_quick_frame_ != nullptr) { // Handle quick stack frames.
// Can't be both a shadow and a quick fragment.
DCHECK(current_fragment->GetTopShadowFrame() == nullptr);
@@ -800,10 +805,20 @@
// between GenericJNI frame and JIT-compiled JNI stub; the entrypoint may have
// changed since the frame was entered. The top quick frame tag indicates
// GenericJNI here, otherwise it's either AOT-compiled or JNI-compiled JNI stub.
- if (UNLIKELY(current_fragment->GetTopQuickFrameTag())) {
+ if (UNLIKELY(current_fragment->GetTopQuickFrameGenericJniTag())) {
// The generic JNI does not have any method header.
cur_oat_quick_method_header_ = nullptr;
+ } else if (UNLIKELY(current_fragment->GetTopQuickFrameJitJniTag())) {
+ // Should be JITed code.
+ Runtime* runtime = Runtime::Current();
+ const void* code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method);
+ CHECK(code != nullptr) << method->PrettyMethod();
+ cur_oat_quick_method_header_ = OatQuickMethodHeader::FromCodePointer(code);
} else {
+ // We are sure we are not running GenericJni here. Though the entry point could still be
+ // GenericJnistub. The entry point is usually JITed, AOT or instrumentation stub when
+ // instrumentation is enabled. It could be lso a resolution stub if the class isn't
+ // visibly initialized yet.
const void* existing_entry_point = method->GetEntryPointFromQuickCompiledCode();
CHECK(existing_entry_point != nullptr);
Runtime* runtime = Runtime::Current();
@@ -819,7 +834,11 @@
if (code != nullptr) {
cur_oat_quick_method_header_ = OatQuickMethodHeader::FromEntryPoint(code);
} else {
- // This must be a JITted JNI stub frame.
+ // This must be a JITted JNI stub frame. For non-debuggable runtimes we only generate
+ // JIT stubs if there are no AOT stubs for native methods. Since we checked for AOT
+ // code earlier, we must be running JITed code. For debuggable runtimes we might have
+ // JIT code even when AOT code is present but we tag SP in JITed JNI stubs
+ // in debuggable runtimes. This case is handled earlier.
CHECK(runtime->GetJit() != nullptr);
code = runtime->GetJit()->GetCodeCache()->GetJniStubCode(method);
CHECK(code != nullptr) << method->PrettyMethod();
@@ -834,8 +853,12 @@
cur_oat_quick_method_header_ = method->GetOatQuickMethodHeader(cur_quick_frame_pc_);
}
header_retrieved = false; // Force header retrieval in next iteration.
- ValidateFrame();
+ if (kDebugStackWalk) {
+ LOG(INFO) << "Early print: Tid=" << thread_-> GetThreadId() << ", method: "
+ << ArtMethod::PrettyMethod(method) << "@" << method;
+ }
+ ValidateFrame();
if ((walk_kind_ == StackWalkKind::kIncludeInlinedFrames)
&& (cur_oat_quick_method_header_ != nullptr)
&& cur_oat_quick_method_header_->IsOptimized()
@@ -854,7 +877,6 @@
return;
}
cur_depth_++;
- inlined_frames_count++;
}
}
}
@@ -908,7 +930,8 @@
cur_quick_frame_ = reinterpret_cast<ArtMethod**>(next_frame);
if (kDebugStackWalk) {
- LOG(INFO) << ArtMethod::PrettyMethod(method) << "@" << method << " size=" << frame_size
+ LOG(INFO) << "Tid=" << thread_-> GetThreadId() << ", method: "
+ << ArtMethod::PrettyMethod(method) << "@" << method << " size=" << frame_size
<< std::boolalpha
<< " optimized=" << (cur_oat_quick_method_header_ != nullptr &&
cur_oat_quick_method_header_->IsOptimized())
@@ -928,6 +951,12 @@
cur_oat_quick_method_header_ = nullptr;
} else if (cur_shadow_frame_ != nullptr) {
do {
+ if (kDebugStackWalk) {
+ ArtMethod* method = cur_shadow_frame_->GetMethod();
+ LOG(INFO) << "Tid=" << thread_-> GetThreadId() << ", method: "
+ << ArtMethod::PrettyMethod(method) << "@" << method
+ << ", ShadowFrame";
+ }
ValidateFrame();
bool should_continue = VisitFrame();
if (UNLIKELY(!should_continue)) {
diff --git a/runtime/stack.h b/runtime/stack.h
index 1b00b54..bfda57b 100644
--- a/runtime/stack.h
+++ b/runtime/stack.h
@@ -58,11 +58,6 @@
};
std::ostream& operator<<(std::ostream& os, VRegKind rhs);
-// Size in bytes of the should_deoptimize flag on stack.
-// We just need 4 bytes for our purpose regardless of the architecture. Frame size
-// calculation will automatically do alignment for the final frame size.
-static constexpr size_t kShouldDeoptimizeFlagSize = 4;
-
/*
* Our current stack layout.
* The Dalvik registers come first, followed by the
@@ -306,6 +301,11 @@
return *GetShouldDeoptimizeFlagAddr();
}
+ bool IsShouldDeoptimizeFlagForDebugSet() const REQUIRES_SHARED(Locks::mutator_lock_) {
+ uint8_t should_deopt_flag = GetShouldDeoptimizeFlag();
+ return (should_deopt_flag & static_cast<uint8_t>(DeoptimizeFlagValue::kDebug)) != 0;
+ }
+
private:
// Private constructor known in the case that num_frames_ has already been computed.
StackVisitor(Thread* thread,
diff --git a/runtime/stack_map.h b/runtime/stack_map.h
index 7a13dbd..7876a67 100644
--- a/runtime/stack_map.h
+++ b/runtime/stack_map.h
@@ -449,6 +449,10 @@
return (*code_info_data & kIsBaseline) != 0;
}
+ ALWAYS_INLINE static bool IsDebuggable(const uint8_t* code_info_data) {
+ return (*code_info_data & kIsDebuggable) != 0;
+ }
+
private:
// Scan backward to determine dex register locations at given stack map.
void DecodeDexRegisterMap(uint32_t stack_map_index,
@@ -495,11 +499,16 @@
enum Flags {
kHasInlineInfo = 1 << 0,
kIsBaseline = 1 << 1,
+ kIsDebuggable = 1 << 2,
};
// The CodeInfo starts with sequence of variable-length bit-encoded integers.
+ // (Please see kVarintMax for more details about encoding).
static constexpr size_t kNumHeaders = 7;
- uint32_t flags_ = 0; // Note that the space is limited to three bits.
+ // Note that the space for flags is limited to three bits. We use a custom encoding where we
+ // encode the value inline if it is less than kVarintMax. We want to access flags without
+ // decoding the entire CodeInfo so the value of flags cannot be more than kVarintMax.
+ uint32_t flags_ = 0;
uint32_t code_size_ = 0; // The size of native PC range in bytes.
uint32_t packed_frame_size_ = 0; // Frame size in kStackAlignment units.
uint32_t core_spill_mask_ = 0;
diff --git a/runtime/subtype_check_info.h b/runtime/subtype_check_info.h
index d734557..05afd76 100644
--- a/runtime/subtype_check_info.h
+++ b/runtime/subtype_check_info.h
@@ -153,7 +153,7 @@
// Create from the depth and the bitstring+of state.
// This is done for convenience to avoid passing in "depth" everywhere,
// since our current state is almost always a function of depth.
- static SubtypeCheckInfo Create(SubtypeCheckBits compressed_value, size_t depth) {
+ static SubtypeCheckInfo Create(const SubtypeCheckBits& compressed_value, size_t depth) {
SubtypeCheckInfo io;
io.depth_ = depth;
io.bitstring_and_of_ = compressed_value;
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index 324cd37..4110ed2 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -373,7 +373,7 @@
}
inline bool Thread::GetWeakRefAccessEnabled() const {
- CHECK(kUseReadBarrier);
+ DCHECK(gUseReadBarrier);
DCHECK(this == Thread::Current());
WeakRefAccessState s = tls32_.weak_ref_access_enabled.load(std::memory_order_relaxed);
if (LIKELY(s == WeakRefAccessState::kVisiblyEnabled)) {
@@ -428,7 +428,7 @@
int delta,
AtomicInteger* suspend_barrier,
SuspendReason reason) {
- if (delta > 0 && ((kUseReadBarrier && this != self) || suspend_barrier != nullptr)) {
+ if (delta > 0 && ((gUseReadBarrier && this != self) || suspend_barrier != nullptr)) {
// When delta > 0 (requesting a suspend), ModifySuspendCountInternal() may fail either if
// active_suspend_barriers is full or we are in the middle of a thread flip. Retry in a loop.
while (true) {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 78ba26d..d233e6f 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -41,6 +41,8 @@
#include "android-base/stringprintf.h"
#include "android-base/strings.h"
+#include "unwindstack/AndroidUnwinder.h"
+
#include "arch/context-inl.h"
#include "arch/context.h"
#include "art_field-inl.h"
@@ -84,6 +86,7 @@
#include "mirror/class_loader.h"
#include "mirror/object_array-alloc-inl.h"
#include "mirror/object_array-inl.h"
+#include "mirror/stack_frame_info.h"
#include "mirror/stack_trace_element.h"
#include "monitor.h"
#include "monitor_objects_stack_visitor.h"
@@ -166,7 +169,7 @@
void UpdateReadBarrierEntrypoints(QuickEntryPoints* qpoints, bool is_active);
void Thread::SetIsGcMarkingAndUpdateEntrypoints(bool is_marking) {
- CHECK(kUseReadBarrier);
+ CHECK(gUseReadBarrier);
tls32_.is_gc_marking = is_marking;
UpdateReadBarrierEntrypoints(&tlsPtr_.quick_entrypoints, /* is_active= */ is_marking);
}
@@ -272,6 +275,7 @@
ObjPtr<mirror::Throwable> exception,
bool from_code,
DeoptimizationMethodType method_type) {
+ DCHECK(exception != Thread::GetDeoptimizationException());
DeoptimizationContextRecord* record = new DeoptimizationContextRecord(
return_value,
is_reference,
@@ -786,7 +790,7 @@
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wframe-larger-than="
NO_INLINE
- static void Touch(uintptr_t target) {
+ __attribute__((no_sanitize("memtag"))) static void Touch(uintptr_t target) {
volatile size_t zero = 0;
// Use a large local volatile array to ensure a large frame size. Do not use anything close
// to a full page for ASAN. It would be nice to ensure the frame size is at most a page, but
@@ -1390,10 +1394,15 @@
tls32_.num_name_readers.fetch_sub(1 /* at least memory_order_release */);
}
-void Thread::Dump(std::ostream& os, bool dump_native_stack, BacktraceMap* backtrace_map,
- bool force_dump_stack) const {
+void Thread::Dump(std::ostream& os, bool dump_native_stack, bool force_dump_stack) const {
DumpState(os);
- DumpStack(os, dump_native_stack, backtrace_map, force_dump_stack);
+ DumpStack(os, dump_native_stack, force_dump_stack);
+}
+
+void Thread::Dump(std::ostream& os, unwindstack::AndroidLocalUnwinder& unwinder,
+ bool dump_native_stack, bool force_dump_stack) const {
+ DumpState(os);
+ DumpStack(os, unwinder, dump_native_stack, force_dump_stack);
}
ObjPtr<mirror::String> Thread::GetThreadName() const {
@@ -1473,7 +1482,7 @@
return false;
}
- if (kUseReadBarrier && delta > 0 && this != self && tlsPtr_.flip_function != nullptr) {
+ if (gUseReadBarrier && delta > 0 && this != self && tlsPtr_.flip_function != nullptr) {
// Force retry of a suspend request if it's in the middle of a thread flip to avoid a
// deadlock. b/31683379.
return false;
@@ -1980,6 +1989,9 @@
if (thread->IsStillStarting()) {
os << " (still starting up)";
}
+ if (thread->tls32_.disable_thread_flip_count != 0) {
+ os << " DisableFlipCount = " << thread->tls32_.disable_thread_flip_count;
+ }
os << "\n";
} else {
os << '"' << ::art::GetThreadName(tid) << '"'
@@ -2276,7 +2288,14 @@
void Thread::DumpStack(std::ostream& os,
bool dump_native_stack,
- BacktraceMap* backtrace_map,
+ bool force_dump_stack) const {
+ unwindstack::AndroidLocalUnwinder unwinder;
+ DumpStack(os, unwinder, dump_native_stack, force_dump_stack);
+}
+
+void Thread::DumpStack(std::ostream& os,
+ unwindstack::AndroidLocalUnwinder& unwinder,
+ bool dump_native_stack,
bool force_dump_stack) const {
// TODO: we call this code when dying but may not have suspended the thread ourself. The
// IsSuspended check is therefore racy with the use for dumping (normally we inhibit
@@ -2295,7 +2314,7 @@
GetCurrentMethod(nullptr,
/*check_suspended=*/ !force_dump_stack,
/*abort_on_error=*/ !(dump_for_abort || force_dump_stack));
- DumpNativeStack(os, GetTid(), backtrace_map, " native: ", method);
+ DumpNativeStack(os, unwinder, GetTid(), " native: ", method);
}
DumpJavaStack(os,
/*check_suspended=*/ !force_dump_stack,
@@ -2559,7 +2578,7 @@
}
// Mark-stack revocation must be performed at the very end. No
// checkpoint/flip-function or read-barrier should be called after this.
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
Runtime::Current()->GetHeap()->ConcurrentCopyingCollector()->RevokeThreadLocalMarkStack(this);
}
}
@@ -3138,6 +3157,148 @@
return result;
}
+[[nodiscard]] static ObjPtr<mirror::StackFrameInfo> InitStackFrameInfo(
+ const ScopedObjectAccessAlreadyRunnable& soa,
+ ClassLinker* class_linker,
+ Handle<mirror::StackFrameInfo> stackFrameInfo,
+ ArtMethod* method,
+ uint32_t dex_pc) REQUIRES_SHARED(Locks::mutator_lock_) {
+ StackHandleScope<4> hs(soa.Self());
+ int32_t line_number;
+ auto source_name_object(hs.NewHandle<mirror::String>(nullptr));
+ if (method->IsProxyMethod()) {
+ line_number = -1;
+ // source_name_object intentionally left null for proxy methods
+ } else {
+ line_number = method->GetLineNumFromDexPC(dex_pc);
+ if (line_number == -1) {
+ // Make the line_number field of StackFrameInfo hold the dex pc.
+ // source_name_object is intentionally left null if we failed to map the dex pc to
+ // a line number (most probably because there is no debug info). See b/30183883.
+ line_number = static_cast<int32_t>(dex_pc);
+ } else {
+ const char* source_file = method->GetDeclaringClassSourceFile();
+ if (source_file != nullptr) {
+ source_name_object.Assign(mirror::String::AllocFromModifiedUtf8(soa.Self(), source_file));
+ if (source_name_object == nullptr) {
+ soa.Self()->AssertPendingOOMException();
+ return nullptr;
+ }
+ }
+ }
+ }
+
+ Handle<mirror::Class> declaring_class_object(
+ hs.NewHandle<mirror::Class>(method->GetDeclaringClass()));
+
+ ArtMethod* interface_method = method->GetInterfaceMethodIfProxy(kRuntimePointerSize);
+ const char* method_name = interface_method->GetName();
+ CHECK(method_name != nullptr);
+ Handle<mirror::String> method_name_object(
+ hs.NewHandle(mirror::String::AllocFromModifiedUtf8(soa.Self(), method_name)));
+ if (method_name_object == nullptr) {
+ soa.Self()->AssertPendingOOMException();
+ return nullptr;
+ }
+
+ dex::ProtoIndex proto_idx =
+ method->GetDexFile()->GetIndexForProtoId(interface_method->GetPrototype());
+ Handle<mirror::MethodType> method_type_object(hs.NewHandle<mirror::MethodType>(
+ class_linker->ResolveMethodType(soa.Self(), proto_idx, interface_method)));
+ if (method_type_object == nullptr) {
+ soa.Self()->AssertPendingOOMException();
+ return nullptr;
+ }
+
+ stackFrameInfo->AssignFields(declaring_class_object,
+ method_type_object,
+ method_name_object,
+ source_name_object,
+ line_number,
+ static_cast<int32_t>(dex_pc));
+ return stackFrameInfo.Get();
+}
+
+constexpr jlong FILL_CLASS_REFS_ONLY = 0x2; // StackStreamFactory.FILL_CLASS_REFS_ONLY
+
+jint Thread::InternalStackTraceToStackFrameInfoArray(
+ const ScopedObjectAccessAlreadyRunnable& soa,
+ jlong mode, // See java.lang.StackStreamFactory for the mode flags
+ jobject internal,
+ jint startLevel,
+ jint batchSize,
+ jint startBufferIndex,
+ jobjectArray output_array) {
+ // Decode the internal stack trace into the depth, method trace and PC trace.
+ // Subtract one for the methods and PC trace.
+ int32_t depth = soa.Decode<mirror::Array>(internal)->GetLength() - 1;
+ DCHECK_GE(depth, 0);
+
+ StackHandleScope<6> hs(soa.Self());
+ Handle<mirror::ObjectArray<mirror::Object>> framesOrClasses =
+ hs.NewHandle(soa.Decode<mirror::ObjectArray<mirror::Object>>(output_array));
+
+ jint endBufferIndex = startBufferIndex;
+
+ if (startLevel < 0 || startLevel >= depth) {
+ return endBufferIndex;
+ }
+
+ int32_t bufferSize = framesOrClasses->GetLength();
+ if (startBufferIndex < 0 || startBufferIndex >= bufferSize) {
+ return endBufferIndex;
+ }
+
+ // The FILL_CLASS_REFS_ONLY flag is defined in AbstractStackWalker.fetchStackFrames() javadoc.
+ bool isClassArray = (mode & FILL_CLASS_REFS_ONLY) != 0;
+
+ Handle<mirror::ObjectArray<mirror::Object>> decoded_traces =
+ hs.NewHandle(soa.Decode<mirror::Object>(internal)->AsObjectArray<mirror::Object>());
+ // Methods and dex PC trace is element 0.
+ DCHECK(decoded_traces->Get(0)->IsIntArray() || decoded_traces->Get(0)->IsLongArray());
+ Handle<mirror::PointerArray> method_trace =
+ hs.NewHandle(ObjPtr<mirror::PointerArray>::DownCast(decoded_traces->Get(0)));
+
+ ClassLinker* const class_linker = Runtime::Current()->GetClassLinker();
+ Handle<mirror::Class> sfi_class =
+ hs.NewHandle(class_linker->FindSystemClass(soa.Self(), "Ljava/lang/StackFrameInfo;"));
+ DCHECK(sfi_class != nullptr);
+
+ MutableHandle<mirror::StackFrameInfo> frame = hs.NewHandle<mirror::StackFrameInfo>(nullptr);
+ MutableHandle<mirror::Class> clazz = hs.NewHandle<mirror::Class>(nullptr);
+ for (uint32_t i = static_cast<uint32_t>(startLevel); i < static_cast<uint32_t>(depth); ++i) {
+ if (endBufferIndex >= startBufferIndex + batchSize || endBufferIndex >= bufferSize) {
+ break;
+ }
+
+ ArtMethod* method = method_trace->GetElementPtrSize<ArtMethod*>(i, kRuntimePointerSize);
+ if (isClassArray) {
+ clazz.Assign(method->GetDeclaringClass());
+ framesOrClasses->Set(endBufferIndex, clazz.Get());
+ } else {
+ // Prepare parameters for fields in StackFrameInfo
+ uint32_t dex_pc = method_trace->GetElementPtrSize<uint32_t>(
+ i + static_cast<uint32_t>(method_trace->GetLength()) / 2, kRuntimePointerSize);
+
+ ObjPtr<mirror::Object> frameObject = framesOrClasses->Get(endBufferIndex);
+ // If libcore didn't allocate the object, we just stop here, but it's unlikely.
+ if (frameObject == nullptr || !frameObject->InstanceOf(sfi_class.Get())) {
+ break;
+ }
+ frame.Assign(ObjPtr<mirror::StackFrameInfo>::DownCast(frameObject));
+ frame.Assign(InitStackFrameInfo(soa, class_linker, frame, method, dex_pc));
+ // Break if InitStackFrameInfo fails to allocate objects or assign the fields.
+ if (frame == nullptr) {
+ break;
+ }
+ }
+
+ ++endBufferIndex;
+ }
+
+ return endBufferIndex;
+}
+
jobjectArray Thread::CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const {
// This code allocates. Do not allow it to operate with a pending exception.
if (IsExceptionPending()) {
@@ -3574,6 +3735,7 @@
QUICK_ENTRY_POINT_INFO(pAputObject)
QUICK_ENTRY_POINT_INFO(pJniMethodStart)
QUICK_ENTRY_POINT_INFO(pJniMethodEnd)
+ QUICK_ENTRY_POINT_INFO(pJniMethodEntryHook)
QUICK_ENTRY_POINT_INFO(pJniDecodeReferenceResult)
QUICK_ENTRY_POINT_INFO(pJniLockObject)
QUICK_ENTRY_POINT_INFO(pJniUnlockObject)
@@ -3691,11 +3853,14 @@
os << offset;
}
-void Thread::QuickDeliverException() {
+void Thread::QuickDeliverException(bool is_method_exit_exception) {
// Get exception from thread.
ObjPtr<mirror::Throwable> exception = GetException();
CHECK(exception != nullptr);
if (exception == GetDeoptimizationException()) {
+ // This wasn't a real exception, so just clear it here. If there was an actual exception it
+ // will be recorded in the DeoptimizationContext and it will be restored later.
+ ClearException();
artDeoptimize(this);
UNREACHABLE();
}
@@ -3750,7 +3915,7 @@
if (Dbg::IsForcedInterpreterNeededForException(this) || force_deopt || IsForceInterpreter()) {
NthCallerVisitor visitor(this, 0, false);
visitor.WalkStack();
- if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.caller_pc)) {
+ if (Runtime::Current()->IsAsyncDeoptimizeable(visitor.GetOuterMethod(), visitor.caller_pc)) {
// method_type shouldn't matter due to exception handling.
const DeoptimizationMethodType method_type = DeoptimizationMethodType::kDefault;
// Save the exception into the deoptimization context so it can be restored
@@ -3781,7 +3946,7 @@
// resolution.
ClearException();
QuickExceptionHandler exception_handler(this, false);
- exception_handler.FindCatch(exception);
+ exception_handler.FindCatch(exception, is_method_exit_exception);
if (exception_handler.GetClearException()) {
// Exception was cleared as part of delivery.
DCHECK(!IsExceptionPending());
@@ -3851,7 +4016,11 @@
// We are visiting the references in compiled frames, so we do not need
// to know the inlined frames.
: StackVisitor(thread, context, StackVisitor::StackWalkKind::kSkipInlinedFrames),
- visitor_(visitor) {}
+ visitor_(visitor) {
+ gc::Heap* const heap = Runtime::Current()->GetHeap();
+ visit_declaring_class_ = heap->CurrentCollectorType() != gc::CollectorType::kCollectorTypeCMC
+ || !heap->MarkCompactCollector()->IsCompacting(Thread::Current());
+ }
bool VisitFrame() override REQUIRES_SHARED(Locks::mutator_lock_) {
if (false) {
@@ -3896,6 +4065,9 @@
void VisitDeclaringClass(ArtMethod* method)
REQUIRES_SHARED(Locks::mutator_lock_)
NO_THREAD_SAFETY_ANALYSIS {
+ if (!visit_declaring_class_) {
+ return;
+ }
ObjPtr<mirror::Class> klass = method->GetDeclaringClassUnchecked<kWithoutReadBarrier>();
// klass can be null for runtime methods.
if (klass != nullptr) {
@@ -3978,7 +4150,7 @@
// (PC shall be known thanks to the runtime frame for throwing SIOOBE).
// Note that JIT does not emit that intrinic implementation.
const void* pc = reinterpret_cast<const void*>(GetCurrentQuickFramePc());
- if (pc != 0u && Runtime::Current()->GetHeap()->IsInBootImageOatFile(pc)) {
+ if (pc != nullptr && Runtime::Current()->GetHeap()->IsInBootImageOatFile(pc)) {
return;
}
}
@@ -4189,6 +4361,7 @@
// Visitor for when we visit a root.
RootVisitor& visitor_;
+ bool visit_declaring_class_;
};
class RootCallbackVisitor {
@@ -4282,6 +4455,9 @@
static void SweepCacheEntry(IsMarkedVisitor* visitor, const Instruction* inst, size_t* value)
REQUIRES_SHARED(Locks::mutator_lock_) {
+ // WARNING: The interpreter will not modify the cache while this method is running in GC.
+ // However, ClearAllInterpreterCaches can still run if any dex file is closed.
+ // Therefore the cache entry can be nulled at any point through this method.
if (inst == nullptr) {
return;
}
@@ -4307,6 +4483,9 @@
case Opcode::CONST_STRING:
case Opcode::CONST_STRING_JUMBO: {
mirror::Object* object = reinterpret_cast<mirror::Object*>(*value);
+ if (object == nullptr) {
+ return;
+ }
mirror::Object* new_object = visitor->IsMarked(object);
// We know the string is marked because it's a strongly-interned string that
// is always alive (see b/117621117 for trying to make those strings weak).
@@ -4425,6 +4604,15 @@
return has_tlab;
}
+void Thread::AdjustTlab(size_t slide_bytes) {
+ if (HasTlab()) {
+ tlsPtr_.thread_local_start -= slide_bytes;
+ tlsPtr_.thread_local_pos -= slide_bytes;
+ tlsPtr_.thread_local_end -= slide_bytes;
+ tlsPtr_.thread_local_limit -= slide_bytes;
+ }
+}
+
std::ostream& operator<<(std::ostream& os, const Thread& thread) {
thread.ShortDump(os);
return os;
@@ -4534,7 +4722,7 @@
mirror::Object* Thread::GetPeerFromOtherThread() const {
DCHECK(tlsPtr_.jpeer == nullptr);
mirror::Object* peer = tlsPtr_.opeer;
- if (kUseReadBarrier && Current()->GetIsGcMarking()) {
+ if (gUseReadBarrier && Current()->GetIsGcMarking()) {
// We may call Thread::Dump() in the middle of the CC thread flip and this thread's stack
// may have not been flipped yet and peer may be a from-space (stale) ref. So explicitly
// mark/forward it here.
@@ -4606,7 +4794,9 @@
CHECK(self_->IsExceptionPending()) << *self_;
ObjPtr<mirror::Throwable> old_suppressed(excp_.Get());
excp_.Assign(self_->GetException());
- LOG(WARNING) << message << "Suppressing old exception: " << old_suppressed->Dump();
+ if (old_suppressed != nullptr) {
+ LOG(WARNING) << message << "Suppressing old exception: " << old_suppressed->Dump();
+ }
self_->ClearException();
}
diff --git a/runtime/thread.h b/runtime/thread.h
index dd8b061..6b1c16c 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -48,7 +48,9 @@
#include "runtime_stats.h"
#include "thread_state.h"
-class BacktraceMap;
+namespace unwindstack {
+class AndroidLocalUnwinder;
+} // namespace unwindstack
namespace art {
@@ -188,7 +190,7 @@
// This should match RosAlloc::kNumThreadLocalSizeBrackets.
static constexpr size_t kNumRosAllocThreadLocalSizeBracketsInThread = 16;
-static constexpr size_t kSharedMethodHotnessThreshold = 0xffff;
+static constexpr size_t kSharedMethodHotnessThreshold = 0x1fff;
// Thread's stack layout for implicit stack overflow checks:
//
@@ -270,7 +272,11 @@
// Dumps the detailed thread state and the thread stack (used for SIGQUIT).
void Dump(std::ostream& os,
bool dump_native_stack = true,
- BacktraceMap* backtrace_map = nullptr,
+ bool force_dump_stack = false) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void Dump(std::ostream& os,
+ unwindstack::AndroidLocalUnwinder& unwinder,
+ bool dump_native_stack = true,
bool force_dump_stack = false) const
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -373,11 +379,11 @@
void WaitForFlipFunction(Thread* self) REQUIRES_SHARED(Locks::mutator_lock_);
gc::accounting::AtomicStack<mirror::Object>* GetThreadLocalMarkStack() {
- CHECK(kUseReadBarrier);
+ CHECK(gUseReadBarrier);
return tlsPtr_.thread_local_mark_stack;
}
void SetThreadLocalMarkStack(gc::accounting::AtomicStack<mirror::Object>* stack) {
- CHECK(kUseReadBarrier);
+ CHECK(gUseReadBarrier);
tlsPtr_.thread_local_mark_stack = stack;
}
@@ -546,8 +552,12 @@
// that needs to be dealt with, false otherwise.
bool ObserveAsyncException() REQUIRES_SHARED(Locks::mutator_lock_);
- // Find catch block and perform long jump to appropriate exception handle
- NO_RETURN void QuickDeliverException() REQUIRES_SHARED(Locks::mutator_lock_);
+ // Find catch block and perform long jump to appropriate exception handle. When
+ // is_method_exit_exception is true, the exception was thrown by the method exit callback and we
+ // should not send method unwind for the method on top of the stack since method exit callback was
+ // already called.
+ NO_RETURN void QuickDeliverException(bool is_method_exit_exception = false)
+ REQUIRES_SHARED(Locks::mutator_lock_);
Context* GetLongJumpContext();
void ReleaseLongJumpContext(Context* context) {
@@ -573,8 +583,8 @@
tlsPtr_.managed_stack.SetTopQuickFrame(top_method);
}
- void SetTopOfStackTagged(ArtMethod** top_method) {
- tlsPtr_.managed_stack.SetTopQuickFrameTagged(top_method);
+ void SetTopOfStackGenericJniTagged(ArtMethod** top_method) {
+ tlsPtr_.managed_stack.SetTopQuickFrameGenericJniTagged(top_method);
}
void SetTopOfShadowStack(ShadowFrame* top) {
@@ -708,6 +718,16 @@
jobjectArray output_array = nullptr, int* stack_depth = nullptr)
REQUIRES_SHARED(Locks::mutator_lock_);
+ static jint InternalStackTraceToStackFrameInfoArray(
+ const ScopedObjectAccessAlreadyRunnable& soa,
+ jlong mode, // See java.lang.StackStreamFactory for the mode flags
+ jobject internal,
+ jint startLevel,
+ jint batchSize,
+ jint startIndex,
+ jobjectArray output_array) // java.lang.StackFrameInfo[]
+ REQUIRES_SHARED(Locks::mutator_lock_);
+
jobjectArray CreateAnnotatedStackTrace(const ScopedObjectAccessAlreadyRunnable& soa) const
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -739,6 +759,13 @@
}
template<PointerSize pointer_size>
+ static constexpr ThreadOffset<pointer_size> TidOffset() {
+ return ThreadOffset<pointer_size>(
+ OFFSETOF_MEMBER(Thread, tls32_) +
+ OFFSETOF_MEMBER(tls_32bit_sized_values, tid));
+ }
+
+ template<PointerSize pointer_size>
static constexpr ThreadOffset<pointer_size> InterruptedOffset() {
return ThreadOffset<pointer_size>(
OFFSETOF_MEMBER(Thread, tls32_) +
@@ -766,6 +793,13 @@
OFFSETOF_MEMBER(tls_32bit_sized_values, is_gc_marking));
}
+ template <PointerSize pointer_size>
+ static constexpr ThreadOffset<pointer_size> DeoptCheckRequiredOffset() {
+ return ThreadOffset<pointer_size>(
+ OFFSETOF_MEMBER(Thread, tls32_) +
+ OFFSETOF_MEMBER(tls_32bit_sized_values, is_deopt_check_required));
+ }
+
static constexpr size_t IsGcMarkingSize() {
return sizeof(tls32_.is_gc_marking);
}
@@ -1011,33 +1045,34 @@
}
bool GetIsGcMarking() const {
- CHECK(kUseReadBarrier);
+ CHECK(gUseReadBarrier);
return tls32_.is_gc_marking;
}
void SetIsGcMarkingAndUpdateEntrypoints(bool is_marking);
+ bool IsDeoptCheckRequired() const { return tls32_.is_deopt_check_required; }
+
+ void SetDeoptCheckRequired(bool flag) { tls32_.is_deopt_check_required = flag; }
+
bool GetWeakRefAccessEnabled() const; // Only safe for current thread.
void SetWeakRefAccessEnabled(bool enabled) {
- CHECK(kUseReadBarrier);
+ DCHECK(gUseReadBarrier);
WeakRefAccessState new_state = enabled ?
WeakRefAccessState::kEnabled : WeakRefAccessState::kDisabled;
tls32_.weak_ref_access_enabled.store(new_state, std::memory_order_release);
}
uint32_t GetDisableThreadFlipCount() const {
- CHECK(kUseReadBarrier);
return tls32_.disable_thread_flip_count;
}
void IncrementDisableThreadFlipCount() {
- CHECK(kUseReadBarrier);
++tls32_.disable_thread_flip_count;
}
void DecrementDisableThreadFlipCount() {
- CHECK(kUseReadBarrier);
DCHECK_GT(tls32_.disable_thread_flip_count, 0U);
--tls32_.disable_thread_flip_count;
}
@@ -1206,6 +1241,10 @@
DCHECK_LE(tlsPtr_.thread_local_end, tlsPtr_.thread_local_limit);
}
+ // Called from Concurrent mark-compact GC to slide the TLAB pointers backwards
+ // to adjust to post-compact addresses.
+ void AdjustTlab(size_t slide_bytes);
+
// Doesn't check that there is room.
mirror::Object* AllocTlab(size_t bytes);
void SetTlab(uint8_t* start, uint8_t* end, uint8_t* limit);
@@ -1481,7 +1520,11 @@
void DumpState(std::ostream& os) const REQUIRES_SHARED(Locks::mutator_lock_);
void DumpStack(std::ostream& os,
bool dump_native_stack = true,
- BacktraceMap* backtrace_map = nullptr,
+ bool force_dump_stack = false) const
+ REQUIRES_SHARED(Locks::mutator_lock_);
+ void DumpStack(std::ostream& os,
+ unwindstack::AndroidLocalUnwinder& unwinder,
+ bool dump_native_stack = true,
bool force_dump_stack = false) const
REQUIRES_SHARED(Locks::mutator_lock_);
@@ -1712,6 +1755,7 @@
thread_exit_check_count(0),
is_transitioning_to_runnable(false),
is_gc_marking(false),
+ is_deopt_check_required(false),
weak_ref_access_enabled(WeakRefAccessState::kVisiblyEnabled),
disable_thread_flip_count(0),
user_code_suspend_count(0),
@@ -1766,6 +1810,12 @@
// GC roots.
bool32_t is_gc_marking;
+ // True if we need to check for deoptimization when returning from the runtime functions. This
+ // is required only when a class is redefined to prevent executing code that has field offsets
+ // embedded. For non-debuggable apps redefinition is not allowed and this flag should always be
+ // set to false.
+ bool32_t is_deopt_check_required;
+
// Thread "interrupted" status; stays raised until queried or thrown.
Atomic<bool32_t> interrupted;
@@ -2186,13 +2236,13 @@
explicit ScopedTransitioningToRunnable(Thread* self)
: self_(self) {
DCHECK_EQ(self, Thread::Current());
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
self_->SetIsTransitioningToRunnable(true);
}
}
~ScopedTransitioningToRunnable() {
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
self_->SetIsTransitioningToRunnable(false);
}
}
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 6482e72..6b23c34 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -24,9 +24,9 @@
#include <vector>
#include "android-base/stringprintf.h"
-#include "backtrace/BacktraceMap.h"
#include "nativehelper/scoped_local_ref.h"
#include "nativehelper/scoped_utf_chars.h"
+#include "unwindstack/AndroidUnwinder.h"
#include "base/aborting.h"
#include "base/histogram-inl.h"
@@ -101,12 +101,11 @@
Runtime::Current()->DetachCurrentThread();
}
WaitForOtherNonDaemonThreadsToExit();
- // Disable GC and wait for GC to complete in case there are still daemon threads doing
- // allocations.
+ // The only caller of this function, ~Runtime, has already disabled GC and
+ // ensured that the last GC is finished.
gc::Heap* const heap = Runtime::Current()->GetHeap();
- heap->DisableGCForShutdown();
- // In case a GC is in progress, wait for it to finish.
- heap->WaitForGcToComplete(gc::kGcCauseBackground, Thread::Current());
+ CHECK(heap->IsGCDisabledForShutdown());
+
// TODO: there's an unaddressed race here where a thread may attach during shutdown, see
// Thread::Init.
SuspendAllDaemonThreadsForShutdown();
@@ -124,10 +123,10 @@
void ThreadList::DumpNativeStacks(std::ostream& os) {
MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
- std::unique_ptr<BacktraceMap> map(BacktraceMap::Create(getpid()));
+ unwindstack::AndroidLocalUnwinder unwinder;
for (const auto& thread : list_) {
os << "DUMPING THREAD " << thread->GetTid() << "\n";
- DumpNativeStack(os, thread->GetTid(), map.get(), "\t");
+ DumpNativeStack(os, unwinder, thread->GetTid(), "\t");
os << "\n";
}
}
@@ -153,7 +152,7 @@
// refactor DumpState to avoid skipping analysis.
Thread::DumpState(os, nullptr, tid);
if (dump_native_stack) {
- DumpNativeStack(os, tid, nullptr, " native: ");
+ DumpNativeStack(os, tid, " native: ");
}
os << std::endl;
}
@@ -195,11 +194,8 @@
// Avoid verifying count in case a thread doesn't end up passing through the barrier.
// This avoids a SIGABRT that would otherwise happen in the destructor.
barrier_(0, /*verify_count_on_shutdown=*/false),
- backtrace_map_(dump_native_stack ? BacktraceMap::Create(getpid()) : nullptr),
+ unwinder_(std::vector<std::string>{}, std::vector<std::string> {"oat", "odex"}),
dump_native_stack_(dump_native_stack) {
- if (backtrace_map_ != nullptr) {
- backtrace_map_->SetSuffixesToIgnore(std::vector<std::string> { "oat", "odex" });
- }
}
void Run(Thread* thread) override {
@@ -210,7 +206,7 @@
std::ostringstream local_os;
{
ScopedObjectAccess soa(self);
- thread->Dump(local_os, dump_native_stack_, backtrace_map_.get());
+ thread->Dump(local_os, unwinder_, dump_native_stack_);
}
{
// Use the logging lock to ensure serialization when writing to the common ostream.
@@ -237,7 +233,7 @@
// The barrier to be passed through and for the requestor to wait upon.
Barrier barrier_;
// A backtrace map, so that all threads use a shared info and don't reacquire/parse separately.
- std::unique_ptr<BacktraceMap> backtrace_map_;
+ unwindstack::AndroidLocalUnwinder unwinder_;
// Whether we should dump the native stack.
const bool dump_native_stack_;
};
@@ -486,7 +482,6 @@
// Assume it's stuck and safe to dump its stack.
thread->Dump(LOG_STREAM(FATAL_WITHOUT_ABORT),
/*dump_native_stack=*/ true,
- /*backtrace_map=*/ nullptr,
/*force_dump_stack=*/ true);
}
}
@@ -1275,7 +1270,7 @@
}
CHECK(!Contains(self));
list_.push_back(self);
- if (kUseReadBarrier) {
+ if (gUseReadBarrier) {
gc::collector::ConcurrentCopying* const cc =
Runtime::Current()->GetHeap()->ConcurrentCopyingCollector();
// Initialize according to the state of the CC collector.
@@ -1291,6 +1286,10 @@
DCHECK_EQ(self, Thread::Current());
CHECK_NE(self->GetState(), ThreadState::kRunnable);
Locks::mutator_lock_->AssertNotHeld(self);
+ if (self->tls32_.disable_thread_flip_count != 0) {
+ LOG(FATAL) << "Incomplete PrimitiveArrayCritical section at exit: " << *self << "count = "
+ << self->tls32_.disable_thread_flip_count;
+ }
VLOG(threads) << "ThreadList::Unregister() " << *self;
@@ -1320,7 +1319,7 @@
std::string thread_name;
self->GetThreadName(thread_name);
std::ostringstream os;
- DumpNativeStack(os, GetTid(), nullptr, " native: ", nullptr);
+ DumpNativeStack(os, GetTid(), " native: ", nullptr);
LOG(ERROR) << "Request to unregister unattached thread " << thread_name << "\n" << os.str();
break;
} else {
diff --git a/runtime/thread_pool.cc b/runtime/thread_pool.cc
index 57d7f61..b361d16 100644
--- a/runtime/thread_pool.cc
+++ b/runtime/thread_pool.cc
@@ -246,6 +246,11 @@
started_ = false;
}
+bool ThreadPool::HasStarted(Thread* self) {
+ MutexLock mu(self, task_queue_lock_);
+ return started_;
+}
+
Task* ThreadPool::GetTask(Thread* self) {
MutexLock mu(self, task_queue_lock_);
while (!IsShuttingDown()) {
diff --git a/runtime/thread_pool.h b/runtime/thread_pool.h
index b9e5a97..5c75733 100644
--- a/runtime/thread_pool.h
+++ b/runtime/thread_pool.h
@@ -123,6 +123,9 @@
// Do not allow workers to grab any new tasks.
void StopWorkers(Thread* self) REQUIRES(!task_queue_lock_);
+ // Returns if the thread pool has started.
+ bool HasStarted(Thread* self) REQUIRES(!task_queue_lock_);
+
// Add a new task, the first available started worker will process it. Does not delete the task
// after running it, it is the caller's responsibility.
void AddTask(Thread* self, Task* task) REQUIRES(!task_queue_lock_);
diff --git a/runtime/trace.cc b/runtime/trace.cc
index ec61726..6b4fb29 100644
--- a/runtime/trace.cc
+++ b/runtime/trace.cc
@@ -757,7 +757,6 @@
}
void Trace::MethodUnwind(Thread* thread,
- Handle<mirror::Object> this_object ATTRIBUTE_UNUSED,
ArtMethod* method,
uint32_t dex_pc ATTRIBUTE_UNUSED) {
uint32_t thread_clock_diff = 0;
diff --git a/runtime/trace.h b/runtime/trace.h
index c6f36e4..ab2fe8f 100644
--- a/runtime/trace.h
+++ b/runtime/trace.h
@@ -184,7 +184,6 @@
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_)
override;
void MethodUnwind(Thread* thread,
- Handle<mirror::Object> this_object,
ArtMethod* method,
uint32_t dex_pc)
REQUIRES_SHARED(Locks::mutator_lock_) REQUIRES(!unique_methods_lock_, !streaming_lock_)
diff --git a/runtime/transaction.cc b/runtime/transaction.cc
index 006aa56..08452bd 100644
--- a/runtime/transaction.cc
+++ b/runtime/transaction.cc
@@ -410,7 +410,6 @@
for (auto& it : array_logs_) {
mirror::Array* old_root = it.first;
- CHECK(!old_root->IsObjectArray());
mirror::Array* new_root = old_root;
visitor->VisitRoot(reinterpret_cast<mirror::Object**>(&new_root), RootInfo(kRootUnknown));
if (new_root != old_root) {
diff --git a/runtime/transaction.h b/runtime/transaction.h
index d147038..6fa8e58 100644
--- a/runtime/transaction.h
+++ b/runtime/transaction.h
@@ -245,15 +245,17 @@
REQUIRES(Locks::intern_table_lock_);
void VisitRoots(RootVisitor* visitor) REQUIRES_SHARED(Locks::mutator_lock_);
- InternStringLog() = default;
+ // Only the move constructor is supported.
+ InternStringLog() = delete;
+ InternStringLog(const InternStringLog& log) = delete;
+ InternStringLog& operator=(const InternStringLog& log) = delete;
InternStringLog(InternStringLog&& log) = default;
+ InternStringLog& operator=(InternStringLog&& log) = delete;
private:
mutable GcRoot<mirror::String> str_;
const StringKind string_kind_;
const StringOp string_op_;
-
- DISALLOW_COPY_AND_ASSIGN(InternStringLog);
};
class ResolveStringLog : public ValueObject {
diff --git a/runtime/verifier/method_verifier.cc b/runtime/verifier/method_verifier.cc
index ec0ac73..2f996f8 100644
--- a/runtime/verifier/method_verifier.cc
+++ b/runtime/verifier/method_verifier.cc
@@ -4899,7 +4899,7 @@
template <bool kVerifierDebug>
bool MethodVerifier<kVerifierDebug>::PotentiallyMarkRuntimeThrow() {
- if (IsAotMode() || IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kT)) {
+ if (IsAotMode() || IsSdkVersionSetAndAtLeast(api_level_, SdkVersion::kS_V2)) {
return false;
}
// Compatibility mode: we treat the following code unreachable and the verifier
diff --git a/runtime/verifier/register_line-inl.h b/runtime/verifier/register_line-inl.h
index 6b53687..7b5a496 100644
--- a/runtime/verifier/register_line-inl.h
+++ b/runtime/verifier/register_line-inl.h
@@ -20,7 +20,6 @@
#include "register_line.h"
#include "base/logging.h" // For VLOG.
-#include "debug_print.h"
#include "method_verifier.h"
#include "reg_type_cache-inl.h"
@@ -139,14 +138,6 @@
}
verifier->Fail(fail_type) << "register v" << vsrc << " has type "
<< src_type << " but expected " << check_type;
- if (check_type.IsNonZeroReferenceTypes() &&
- !check_type.IsUnresolvedTypes() &&
- check_type.HasClass() &&
- src_type.IsNonZeroReferenceTypes() &&
- !src_type.IsUnresolvedTypes() &&
- src_type.HasClass()) {
- DumpB77342775DebugData(check_type.GetClass(), src_type.GetClass());
- }
return false;
}
if (check_type.IsLowHalf()) {
diff --git a/test/000-nop/build b/test/000-nop/build
deleted file mode 100644
index 5233a2d..0000000
--- a/test/000-nop/build
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/sh
-
-# Nothing to do here.
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/000-nop/build.py
similarity index 83%
copy from test/2038-hiddenapi-jvmti-ext/build
copy to test/000-nop/build.py
index f4b029f..846e9a6 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/000-nop/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+# Nothing to do here.
diff --git a/test/003-omnibus-opcodes/build b/test/003-omnibus-opcodes/build
deleted file mode 100644
index a14ddc9..0000000
--- a/test/003-omnibus-opcodes/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2008 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/004-JniTest/build b/test/004-JniTest/build.py
old mode 100755
new mode 100644
similarity index 80%
rename from test/004-JniTest/build
rename to test/004-JniTest/build.py
index 460f2db..e31d770
--- a/test/004-JniTest/build
+++ b/test/004-JniTest/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,6 +13,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from art_build_rules import build_run_test
+import shutil, os
+
#
# Perform a mostly normal build.
# Since this test imports 'dalvik.annotation.optimization.FastNative' (and CriticalNative),
@@ -25,12 +27,11 @@
# has a different ABI and cannot be tested on RI.
#
-# Stop on failure.
-set -e
-
# Use release mode to check optimizations do not break JNI.
-export D8_FLAGS=--release
-./default-build "$@"
+build_run_test(d8_flags=['--release'])
# Remove the *-aotex build artifacts (but keep src-aotex) with dalvik.* annotations.
-rm -rf classes-aotex classes-aotex.jar $TEST_NAME-aotex.jar
+shutil.rmtree("classes-aotex")
+if os.environ["BUILD_MODE"] != "jvm":
+ os.remove("classes-aotex.jar")
+ os.remove("004-JniTest-aotex.jar")
diff --git a/test/004-ReferenceMap/build b/test/004-ReferenceMap/generate-sources
old mode 100644
new mode 100755
similarity index 97%
rename from test/004-ReferenceMap/build
rename to test/004-ReferenceMap/generate-sources
index 6203c97..9edc200
--- a/test/004-ReferenceMap/build
+++ b/test/004-ReferenceMap/generate-sources
@@ -23,4 +23,3 @@
######################################################################
${SOONG_ZIP} --jar -o classes.jar -f classes.dex
-./default-build "$@"
diff --git a/test/004-StackWalk/build b/test/004-StackWalk/generate-sources
old mode 100644
new mode 100755
similarity index 97%
rename from test/004-StackWalk/build
rename to test/004-StackWalk/generate-sources
index 6203c97..9edc200
--- a/test/004-StackWalk/build
+++ b/test/004-StackWalk/generate-sources
@@ -23,4 +23,3 @@
######################################################################
${SOONG_ZIP} --jar -o classes.jar -f classes.dex
-./default-build "$@"
diff --git a/test/005-annotations/build b/test/005-annotations/build.py
similarity index 73%
rename from test/005-annotations/build
rename to test/005-annotations/build.py
index bdc1950..8cf988c 100644
--- a/test/005-annotations/build
+++ b/test/005-annotations/build.py
@@ -1,12 +1,11 @@
-#!/bin/bash
#
-# Copyright (C) 2012 The Android Open Source Project
+# Copyright (C) 2022 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
+# 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,
@@ -14,9 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Stop if something fails.
-set -e
+from art_build_rules import build_run_test
# Build intermediate object to preserve class-retention annotations.
-export D8_FLAGS=--intermediate
-./default-build "$@"
+build_run_test(d8_flags=['--intermediate'])
diff --git a/test/018-stack-overflow/Android.bp b/test/018-stack-overflow/Android.bp
index 504bdaa..ef817a8 100644
--- a/test/018-stack-overflow/Android.bp
+++ b/test/018-stack-overflow/Android.bp
@@ -15,12 +15,13 @@
java_test {
name: "art-run-test-018-stack-overflow",
defaults: ["art-run-test-defaults"],
- test_config_template: ":art-run-test-target-template",
+ test_config_template: ":art-run-test-target-cts-template",
srcs: ["src/**/*.java"],
data: [
":art-run-test-018-stack-overflow-expected-stdout",
":art-run-test-018-stack-overflow-expected-stderr",
],
+ test_suites: ["cts"],
}
// Test's expected standard output.
diff --git a/test/018-stack-overflow/test-metadata.json b/test/018-stack-overflow/test-metadata.json
new file mode 100644
index 0000000..975ada7
--- /dev/null
+++ b/test/018-stack-overflow/test-metadata.json
@@ -0,0 +1,3 @@
+{
+ "test_suites": ["cts"]
+}
diff --git a/test/023-many-interfaces/build b/test/023-many-interfaces/generate-sources
old mode 100644
new mode 100755
similarity index 96%
rename from test/023-many-interfaces/build
rename to test/023-many-interfaces/generate-sources
index 6d1c8e3..f9ba1b8
--- a/test/023-many-interfaces/build
+++ b/test/023-many-interfaces/generate-sources
@@ -19,5 +19,3 @@
# Write out a bunch of interface source files.
./iface-gen
-
-./default-build "$@"
diff --git a/test/056-const-string-jumbo/build b/test/056-const-string-jumbo/generate-sources
old mode 100644
new mode 100755
similarity index 98%
rename from test/056-const-string-jumbo/build
rename to test/056-const-string-jumbo/generate-sources
index c1d711b..5c7ade8
--- a/test/056-const-string-jumbo/build
+++ b/test/056-const-string-jumbo/generate-sources
@@ -38,5 +38,3 @@
}
printf("}\n") > fileName;
}'
-
-./default-build "$@"
diff --git a/test/065-mismatched-implements/build b/test/065-mismatched-implements/build.py
old mode 100755
new mode 100644
similarity index 85%
rename from test/065-mismatched-implements/build
rename to test/065-mismatched-implements/build.py
index 41823b5..4284d83
--- a/test/065-mismatched-implements/build
+++ b/test/065-mismatched-implements/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -13,15 +12,12 @@
# 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.
-# Make us exit on a failure.
-set -e
+from art_build_rules import build_run_test
# Don't use desugar because the build fails when it encounters ICCE.
#
# Exception in thread "main" java.lang.IllegalArgumentException
# at com.google.common.base.Preconditions.checkArgument(Preconditions.java:108)
# at com.google.devtools.build.android.desugar.DefaultMethodClassFixer$DefaultMethodFinder.visit(DefaultMethodClassFixer.java:295)
-export USE_DESUGAR=false
-
-./default-build "$@"
+build_run_test(use_desugar=False)
diff --git a/test/066-mismatched-super/build b/test/066-mismatched-super/build
deleted file mode 100644
index 50aceb4..0000000
--- a/test/066-mismatched-super/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-USE_DESUGAR=false ./default-build "$@"
diff --git a/test/677-fsi/build b/test/066-mismatched-super/build.py
old mode 100755
new mode 100644
similarity index 80%
copy from test/677-fsi/build
copy to test/066-mismatched-super/build.py
index b90b408..337bd29
--- a/test/677-fsi/build
+++ b/test/066-mismatched-super/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(use_desugar=False)
diff --git a/test/066-mismatched-super/src/Main.java b/test/066-mismatched-super/src/Main.java
index 6ae1198..bb042b7 100644
--- a/test/066-mismatched-super/src/Main.java
+++ b/test/066-mismatched-super/src/Main.java
@@ -28,7 +28,8 @@
try {
ExtendsFinal ef = new ExtendsFinal();
System.out.println("Succeeded unexpectedly");
- } catch (VerifyError ve) {
+ } catch (VerifyError | IncompatibleClassChangeError ve) {
+ // b/242985231 - from JDK 17 the JVM throws IncompatibleClassChangeError
System.out.println("Got expected VerifyError");
}
}
diff --git a/test/071-dexfile-get-static-size/build b/test/071-dexfile-get-static-size/generate-sources
similarity index 100%
rename from test/071-dexfile-get-static-size/build
rename to test/071-dexfile-get-static-size/generate-sources
diff --git a/test/071-dexfile-map-clean/build b/test/071-dexfile-map-clean/build.py
old mode 100755
new mode 100644
similarity index 82%
rename from test/071-dexfile-map-clean/build
rename to test/071-dexfile-map-clean/build.py
index a171fc3..f8c3efd
--- a/test/071-dexfile-map-clean/build
+++ b/test/071-dexfile-map-clean/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,8 +13,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from art_build_rules import build_run_test
+
# Any JAR files used by this test shall have their classes.dex be stored, NOT compressed.
# This is needed for our test correctness which validates classes.dex are mapped file-backed.
#
# In addition, align to at least 4 bytes since that's the dex alignment requirement.
-./default-build "$@" --zip-compression-method store --zip-align 4
+build_run_test(zip_compression_method="store", zip_align_bytes="4")
diff --git a/test/089-many-methods/build.py b/test/089-many-methods/build.py
new file mode 100644
index 0000000..052ae72
--- /dev/null
+++ b/test/089-many-methods/build.py
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2022 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.
+
+from art_build_rules import build_run_test
+
+# Specify old API level as d8 automagically produces a multidex file
+# when the API level is above 20. Failing the build here is deliberate.
+# Force DEX generation so test also passes with --jvm.
+try:
+ build_run_test(api_level=20, need_dex=True)
+ assert False, "Test was not expected to build successfully"
+except Exception as e:
+ # Check that a build failure happened (the test is not expected to run).
+ assert "Cannot fit requested classes in a single dex" in str(e), e
diff --git a/test/089-many-methods/build b/test/089-many-methods/generate-sources
old mode 100644
new mode 100755
similarity index 72%
rename from test/089-many-methods/build
rename to test/089-many-methods/generate-sources
index 5225e3f..f38a025
--- a/test/089-many-methods/build
+++ b/test/089-many-methods/generate-sources
@@ -42,15 +42,3 @@
}
printf("}\n") > fileName;
}'
-
-# Force DEX generation so test also passes with --jvm.
-export NEED_DEX=true
-
-# Specify old API level as d8 automagically produces a multidex file
-# when the API level is above 20. Failing the build here is deliberate.
-./default-build --api-level 20 "$@" > /dev/null 2> stderr.txt || true
-
-# Check that a build failure happened (the test is not expected to run).
-EXPECTED_ERROR="Cannot fit requested classes in a single dex"
-grep -q "$EXPECTED_ERROR" stderr.txt
-rm stderr.txt # Check passed. Remove output due to non-deterministic paths.
diff --git a/test/091-override-package-private-method/build b/test/091-override-package-private-method/build
deleted file mode 100755
index 6b298f7..0000000
--- a/test/091-override-package-private-method/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/100-reflect2/expected-stdout.txt b/test/100-reflect2/expected-stdout.txt
index a59f230..4b885e4 100644
--- a/test/100-reflect2/expected-stdout.txt
+++ b/test/100-reflect2/expected-stdout.txt
@@ -33,7 +33,7 @@
14 (class java.lang.Short)
[java.lang.String(int,int,char[]), public java.lang.String(), public java.lang.String(byte[]), public java.lang.String(byte[],int), public java.lang.String(byte[],int,int), public java.lang.String(byte[],int,int,int), public java.lang.String(byte[],int,int,java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],int,int,java.nio.charset.Charset), public java.lang.String(byte[],java.lang.String) throws java.io.UnsupportedEncodingException, public java.lang.String(byte[],java.nio.charset.Charset), public java.lang.String(char[]), public java.lang.String(char[],int,int), public java.lang.String(int[],int,int), public java.lang.String(java.lang.String), public java.lang.String(java.lang.StringBuffer), public java.lang.String(java.lang.StringBuilder)]
[private final int java.lang.String.count, private int java.lang.String.hash, private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields, private static final long java.lang.String.serialVersionUID, public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER]
-[native void java.lang.String.getCharsNoCheck(int,int,char[],int), private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder), private int java.lang.String.indexOfNonWhitespace(), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfSupplementary(int,int), private native java.lang.String java.lang.String.doRepeat(int), private native java.lang.String java.lang.String.doReplace(char,char), private native java.lang.String java.lang.String.fastSubstring(int,int), private static int java.lang.String.indexOf(java.lang.String,java.lang.String,int), private static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int), public boolean java.lang.String.contains(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.StringBuffer), public boolean java.lang.String.endsWith(java.lang.String), public boolean java.lang.String.equals(java.lang.Object), public boolean java.lang.String.equalsIgnoreCase(java.lang.String), public boolean java.lang.String.isBlank(), public boolean java.lang.String.isEmpty(), public boolean java.lang.String.matches(java.lang.String), public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int), public boolean java.lang.String.regionMatches(int,java.lang.String,int,int), public boolean java.lang.String.startsWith(java.lang.String), public boolean java.lang.String.startsWith(java.lang.String,int), public byte[] java.lang.String.getBytes(), public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException, public byte[] java.lang.String.getBytes(java.nio.charset.Charset), public int java.lang.String.codePointAt(int), public int java.lang.String.codePointBefore(int), public int java.lang.String.codePointCount(int,int), public int java.lang.String.compareTo(java.lang.Object), public int java.lang.String.compareToIgnoreCase(java.lang.String), public int java.lang.String.hashCode(), public int java.lang.String.indexOf(int), public int java.lang.String.indexOf(int,int), public int java.lang.String.indexOf(java.lang.String), public int java.lang.String.indexOf(java.lang.String,int), public int java.lang.String.lastIndexOf(int), public int java.lang.String.lastIndexOf(int,int), public int java.lang.String.lastIndexOf(java.lang.String), public int java.lang.String.lastIndexOf(java.lang.String,int), public int java.lang.String.length(), public int java.lang.String.offsetByCodePoints(int,int), public java.lang.CharSequence java.lang.String.subSequence(int,int), public java.lang.String java.lang.String.repeat(int), public java.lang.String java.lang.String.replace(char,char), public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence), public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String), public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String), public java.lang.String java.lang.String.strip(), public java.lang.String java.lang.String.stripLeading(), public java.lang.String java.lang.String.stripTrailing(), public java.lang.String java.lang.String.substring(int), public java.lang.String java.lang.String.substring(int,int), public java.lang.String java.lang.String.toLowerCase(), public java.lang.String java.lang.String.toLowerCase(java.util.Locale), public java.lang.String java.lang.String.toString(), public java.lang.String java.lang.String.toUpperCase(), public java.lang.String java.lang.String.toUpperCase(java.util.Locale), public java.lang.String java.lang.String.trim(), public java.lang.String[] java.lang.String.split(java.lang.String), public java.lang.String[] java.lang.String.split(java.lang.String,int), public java.util.stream.IntStream java.lang.String.chars(), public java.util.stream.IntStream java.lang.String.codePoints(), public java.util.stream.Stream java.lang.String.lines(), public native char java.lang.String.charAt(int), public native char[] java.lang.String.toCharArray(), public native int java.lang.String.compareTo(java.lang.String), public native java.lang.String java.lang.String.concat(java.lang.String), public native java.lang.String java.lang.String.intern(), public static java.lang.String java.lang.String.copyValueOf(char[]), public static java.lang.String java.lang.String.copyValueOf(char[],int,int), public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable), public static java.lang.String java.lang.String.valueOf(boolean), public static java.lang.String java.lang.String.valueOf(char), public static java.lang.String java.lang.String.valueOf(char[]), public static java.lang.String java.lang.String.valueOf(char[],int,int), public static java.lang.String java.lang.String.valueOf(double), public static java.lang.String java.lang.String.valueOf(float), public static java.lang.String java.lang.String.valueOf(int), public static java.lang.String java.lang.String.valueOf(java.lang.Object), public static java.lang.String java.lang.String.valueOf(long), public void java.lang.String.getBytes(int,int,byte[],int), public void java.lang.String.getChars(int,int,char[],int), static int java.lang.String.indexOf(char[],int,int,char[],int,int,int), static int java.lang.String.indexOf(char[],int,int,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(char[],int,int,java.lang.String,int), static void java.lang.String.checkBoundsBeginEnd(int,int,int), static void java.lang.String.checkBoundsOffCount(int,int,int), static void java.lang.String.checkIndex(int,int), void java.lang.String.getChars(char[],int)]
+[native void java.lang.String.getCharsNoCheck(int,int,char[],int), private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder), private int java.lang.String.indexOfNonWhitespace(), private int java.lang.String.indexOfSupplementary(int,int), private int java.lang.String.lastIndexOfNonWhitespace(), private int java.lang.String.lastIndexOfSupplementary(int,int), private native java.lang.String java.lang.String.doRepeat(int), private native java.lang.String java.lang.String.doReplace(char,char), private native java.lang.String java.lang.String.fastSubstring(int,int), private static int java.lang.String.indexOf(java.lang.String,java.lang.String,int), private static int java.lang.String.lastIndexOf(java.lang.String,java.lang.String,int), private static int java.lang.String.outdent(java.util.List), public boolean java.lang.String.contains(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.CharSequence), public boolean java.lang.String.contentEquals(java.lang.StringBuffer), public boolean java.lang.String.endsWith(java.lang.String), public boolean java.lang.String.equals(java.lang.Object), public boolean java.lang.String.equalsIgnoreCase(java.lang.String), public boolean java.lang.String.isBlank(), public boolean java.lang.String.isEmpty(), public boolean java.lang.String.matches(java.lang.String), public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int), public boolean java.lang.String.regionMatches(int,java.lang.String,int,int), public boolean java.lang.String.startsWith(java.lang.String), public boolean java.lang.String.startsWith(java.lang.String,int), public byte[] java.lang.String.getBytes(), public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException, public byte[] java.lang.String.getBytes(java.nio.charset.Charset), public int java.lang.String.codePointAt(int), public int java.lang.String.codePointBefore(int), public int java.lang.String.codePointCount(int,int), public int java.lang.String.compareTo(java.lang.Object), public int java.lang.String.compareToIgnoreCase(java.lang.String), public int java.lang.String.hashCode(), public int java.lang.String.indexOf(int), public int java.lang.String.indexOf(int,int), public int java.lang.String.indexOf(java.lang.String), public int java.lang.String.indexOf(java.lang.String,int), public int java.lang.String.lastIndexOf(int), public int java.lang.String.lastIndexOf(int,int), public int java.lang.String.lastIndexOf(java.lang.String), public int java.lang.String.lastIndexOf(java.lang.String,int), public int java.lang.String.length(), public int java.lang.String.offsetByCodePoints(int,int), public java.lang.CharSequence java.lang.String.subSequence(int,int), public java.lang.String java.lang.String.formatted(java.lang.Object[]), public java.lang.String java.lang.String.indent(int), public java.lang.String java.lang.String.repeat(int), public java.lang.String java.lang.String.replace(char,char), public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence), public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String), public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String), public java.lang.String java.lang.String.strip(), public java.lang.String java.lang.String.stripIndent(), public java.lang.String java.lang.String.stripLeading(), public java.lang.String java.lang.String.stripTrailing(), public java.lang.String java.lang.String.substring(int), public java.lang.String java.lang.String.substring(int,int), public java.lang.String java.lang.String.toLowerCase(), public java.lang.String java.lang.String.toLowerCase(java.util.Locale), public java.lang.String java.lang.String.toString(), public java.lang.String java.lang.String.toUpperCase(), public java.lang.String java.lang.String.toUpperCase(java.util.Locale), public java.lang.String java.lang.String.translateEscapes(), public java.lang.String java.lang.String.trim(), public java.lang.String[] java.lang.String.split(java.lang.String), public java.lang.String[] java.lang.String.split(java.lang.String,int), public java.util.stream.IntStream java.lang.String.chars(), public java.util.stream.IntStream java.lang.String.codePoints(), public java.util.stream.Stream java.lang.String.lines(), public native char java.lang.String.charAt(int), public native char[] java.lang.String.toCharArray(), public native int java.lang.String.compareTo(java.lang.String), public native java.lang.String java.lang.String.concat(java.lang.String), public native java.lang.String java.lang.String.intern(), public static java.lang.String java.lang.String.copyValueOf(char[]), public static java.lang.String java.lang.String.copyValueOf(char[],int,int), public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[]), public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable), public static java.lang.String java.lang.String.valueOf(boolean), public static java.lang.String java.lang.String.valueOf(char), public static java.lang.String java.lang.String.valueOf(char[]), public static java.lang.String java.lang.String.valueOf(char[],int,int), public static java.lang.String java.lang.String.valueOf(double), public static java.lang.String java.lang.String.valueOf(float), public static java.lang.String java.lang.String.valueOf(int), public static java.lang.String java.lang.String.valueOf(java.lang.Object), public static java.lang.String java.lang.String.valueOf(long), public void java.lang.String.getBytes(int,int,byte[],int), public void java.lang.String.getChars(int,int,char[],int), static int java.lang.String.indexOf(char[],int,int,char[],int,int,int), static int java.lang.String.indexOf(char[],int,int,java.lang.String,int), static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int), static int java.lang.String.lastIndexOf(char[],int,int,java.lang.String,int), static java.lang.String java.lang.String.lambda$indent$0(java.lang.String,java.lang.String), static java.lang.String java.lang.String.lambda$indent$1(java.lang.String), static java.lang.String java.lang.String.lambda$indent$2(int,java.lang.String), static java.lang.String java.lang.String.lambda$stripIndent$3(int,java.lang.String), static void java.lang.String.checkBoundsBeginEnd(int,int,int), static void java.lang.String.checkBoundsOffCount(int,int,int), static void java.lang.String.checkIndex(int,int), void java.lang.String.getChars(char[],int)]
[]
[interface java.io.Serializable, interface java.lang.Comparable, interface java.lang.CharSequence]
0
diff --git a/test/1000-non-moving-space-stress/src-art/Main.java b/test/1000-non-moving-space-stress/src-art/Main.java
index 18bfdd3..3cd7224 100644
--- a/test/1000-non-moving-space-stress/src-art/Main.java
+++ b/test/1000-non-moving-space-stress/src-art/Main.java
@@ -15,8 +15,11 @@
*/
import dalvik.system.VMRuntime;
+import java.lang.ref.Reference; // For reachabilityFence.
+import java.util.ArrayList;
public class Main {
+ private static final boolean SHOULD_PRINT = false; // True causes failure.
public static void main(String[] args) throws Exception {
VMRuntime runtime = VMRuntime.getRuntime();
@@ -35,15 +38,40 @@
Object[] moving_array = new Object[S];
}
} catch (OutOfMemoryError e) {
- // Stop here.
+ System.out.println("Unexpected OOME");
}
- System.out.println("passed");
+ Runtime.getRuntime().gc();
+ int numAllocs = 0;
+ ArrayList<Object> chunks = new ArrayList<>();
+ try {
+ final int MAX_PLAUSIBLE_ALLOCS = 1024 * 1024;
+ for (numAllocs = 0; numAllocs < MAX_PLAUSIBLE_ALLOCS; ++numAllocs) {
+ chunks.add(runtime.newNonMovableArray(Object.class, 252)); // About 1KB
+ }
+ // If we get here, we've allocated about 1GB of nonmovable memory, which
+ // should be impossible.
+ } catch (OutOfMemoryError e) {
+ chunks.remove(0); // Give us a little space back.
+ if (((Object[]) (chunks.get(42)))[17] != null) {
+ System.out.println("Bad entry in chunks array");
+ } else {
+ chunks.clear(); // Recover remaining space.
+ if (SHOULD_PRINT) {
+ System.out.println("Successfully allocated " + numAllocs + " non-movable KBs");
+ }
+ System.out.println("passed");
+ }
+ Reference.reachabilityFence(chunks);
+ return;
+ }
+ Reference.reachabilityFence(chunks);
+ System.out.println("Failed to exhaust non-movable space");
}
// When using the Concurrent Copying (CC) collector (default collector),
// this method allocates an object in the non-moving space and an object
// in the region space, make the former reference the later, and returns
- // nothing (so that none of these objects are reachable when upon return).
+ // nothing (so that none of these objects are reachable upon return).
static void $noinline$Alloc(VMRuntime runtime) {
Object[] non_moving_array = (Object[]) runtime.newNonMovableArray(Object.class, 1);
// Small object, unlikely to trigger garbage collection.
diff --git a/test/1001-app-image-regions/build b/test/1001-app-image-regions/generate-sources
similarity index 97%
rename from test/1001-app-image-regions/build
rename to test/1001-app-image-regions/generate-sources
index 16c3a5b..0fbe23f 100755
--- a/test/1001-app-image-regions/build
+++ b/test/1001-app-image-regions/generate-sources
@@ -30,5 +30,3 @@
echo " static class Inner${i} { void test(){} }" >> "${other_file}"
done
echo "}" >> "${other_file}"
-
-./default-build "$@"
diff --git a/test/1003-metadata-section-strings/build b/test/1003-metadata-section-strings/generate-sources
similarity index 97%
rename from test/1003-metadata-section-strings/build
rename to test/1003-metadata-section-strings/generate-sources
index cd2cacd..c3e73f5 100755
--- a/test/1003-metadata-section-strings/build
+++ b/test/1003-metadata-section-strings/generate-sources
@@ -37,5 +37,3 @@
}
}
EOF
-
-./default-build "$@"
diff --git a/test/111-unresolvable-exception/build b/test/111-unresolvable-exception/build
deleted file mode 100644
index f378df1..0000000
--- a/test/111-unresolvable-exception/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/124-missing-classes/build b/test/124-missing-classes/build
deleted file mode 100644
index a40cbc9..0000000
--- a/test/124-missing-classes/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2012 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/126-miranda-multidex/build b/test/126-miranda-multidex/generate-sources
old mode 100644
new mode 100755
similarity index 96%
rename from test/126-miranda-multidex/build
rename to test/126-miranda-multidex/generate-sources
index c827e55..b35d5ef
--- a/test/126-miranda-multidex/build
+++ b/test/126-miranda-multidex/generate-sources
@@ -19,4 +19,3 @@
# Signal to default-build that this is a multidex test.
mkdir src-multidex
-./default-build "$@"
diff --git a/test/127-checker-secondarydex/build b/test/127-checker-secondarydex/build
deleted file mode 100755
index f378df1..0000000
--- a/test/127-checker-secondarydex/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/137-cfi/cfi.cc b/test/137-cfi/cfi.cc
index 4e756d2..bf5cc76 100644
--- a/test/137-cfi/cfi.cc
+++ b/test/137-cfi/cfi.cc
@@ -28,7 +28,7 @@
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
-#include <backtrace/Backtrace.h>
+#include <unwindstack/AndroidUnwinder.h>
#include "base/file_utils.h"
#include "base/logging.h"
@@ -106,23 +106,25 @@
// Helper to look for a sequence in the stack trace.
#if __linux__
-static bool CheckStack(Backtrace* bt, const std::vector<std::string>& seq) {
+static bool CheckStack(unwindstack::AndroidUnwinder& unwinder,
+ unwindstack::AndroidUnwinderData& data,
+ const std::vector<std::string>& seq) {
size_t cur_search_index = 0; // The currently active index in seq.
CHECK_GT(seq.size(), 0U);
bool any_empty_name = false;
- for (size_t i = 0; i < bt->NumFrames(); i++) {
- const backtrace_frame_data_t* frame = bt->GetFrame(i);
- if (BacktraceMap::IsValid(frame->map)) {
+ for (const unwindstack::FrameData& frame : data.frames) {
+ const std::string& function_name = frame.function_name;
+ if (frame.map_info != nullptr) {
if (cur_search_index < seq.size()) {
- LOG(INFO) << "Got " << frame->func_name << ", looking for " << seq[cur_search_index];
- if (frame->func_name.find(seq[cur_search_index]) != std::string::npos) {
+ LOG(INFO) << "Got " << function_name << ", looking for " << seq[cur_search_index];
+ if (function_name.find(seq[cur_search_index]) != std::string::npos) {
cur_search_index++;
}
}
}
- any_empty_name |= frame->func_name.empty();
- if (frame->func_name == "main") {
+ any_empty_name |= function_name.empty();
+ if (function_name == "main") {
break;
}
}
@@ -140,10 +142,8 @@
return true;
}
- for (Backtrace::const_iterator it = bt->begin(); it != bt->end(); ++it) {
- if (BacktraceMap::IsValid(it->map)) {
- printf(" %s\n", Backtrace::FormatFrameData(&*it).c_str());
- }
+ for (const unwindstack::FrameData& frame : data.frames) {
+ printf(" %s\n", unwinder.FormatFrame(frame).c_str());
}
return false;
@@ -166,13 +166,11 @@
#if __linux__
MutexLock mu(Thread::Current(), *GetNativeDebugInfoLock()); // Avoid races with the JIT thread.
- std::unique_ptr<Backtrace> bt(Backtrace::Create(BACKTRACE_CURRENT_PROCESS, GetTid()));
- if (!bt->Unwind(0, nullptr)) {
+ unwindstack::AndroidLocalUnwinder unwinder;
+ unwindstack::AndroidUnwinderData data;
+ if (!unwinder.Unwind(data)) {
printf("Cannot unwind in process.\n");
return JNI_FALSE;
- } else if (bt->NumFrames() == 0) {
- printf("No frames for unwind in process.\n");
- return JNI_FALSE;
}
// We cannot really parse an exact stack, as the optimizing compiler may inline some functions.
@@ -186,7 +184,7 @@
"Main.main" // The Java entry method.
};
- bool result = CheckStack(bt.get(), seq);
+ bool result = CheckStack(unwinder, data, seq);
if (!kCauseSegfault) {
return result ? JNI_TRUE : JNI_FALSE;
} else {
@@ -260,17 +258,13 @@
return JNI_FALSE;
}
- std::unique_ptr<Backtrace> bt(Backtrace::Create(pid, BACKTRACE_CURRENT_THREAD));
bool result = true;
- if (!bt->Unwind(0, nullptr)) {
+ unwindstack::AndroidRemoteUnwinder unwinder(pid);
+ unwindstack::AndroidUnwinderData data;
+ if (!unwinder.Unwind(data)) {
printf("Cannot unwind other process.\n");
result = false;
- } else if (bt->NumFrames() == 0) {
- printf("No frames for unwind of other process.\n");
- result = false;
- }
-
- if (result) {
+ } else {
// See comment in unwindInProcess for non-exact stack matching.
// "mini-debug-info" does not include parameters to save space.
std::vector<std::string> seq = {
@@ -280,7 +274,7 @@
"Main.main" // The Java entry method.
};
- result = CheckStack(bt.get(), seq);
+ result = CheckStack(unwinder, data, seq);
}
constexpr bool kSigQuitOnFail = true;
diff --git a/test/138-duplicate-classes-check2/build b/test/138-duplicate-classes-check2/build
deleted file mode 100755
index 6b298f7..0000000
--- a/test/138-duplicate-classes-check2/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/160-read-barrier-stress/build b/test/160-read-barrier-stress/build
deleted file mode 100755
index 90b6b95..0000000
--- a/test/160-read-barrier-stress/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-./default-build "$@" --experimental var-handles
diff --git a/test/807-method-handle-and-mr/build b/test/160-read-barrier-stress/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/160-read-barrier-stress/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/160-read-barrier-stress/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/161-final-abstract-class/build b/test/161-final-abstract-class/generate-sources
old mode 100644
new mode 100755
similarity index 100%
rename from test/161-final-abstract-class/build
rename to test/161-final-abstract-class/generate-sources
diff --git a/test/166-bad-interface-super/build b/test/166-bad-interface-super/build.py
similarity index 73%
rename from test/166-bad-interface-super/build
rename to test/166-bad-interface-super/build.py
index bba6184..f74f0e9 100644
--- a/test/166-bad-interface-super/build
+++ b/test/166-bad-interface-super/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,14 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from art_build_rules import build_run_test
+import os
+
# Use the jasmin sources for JVM, otherwise the smali sources.
-extra_arg="--no-jasmin"
-
-for arg in "$@"; do
- if [[ "$arg" == "--jvm" ]]; then
- extra_arg="--no-smali"
- break
- fi
-done
-
-./default-build "$@" "$extra_arg"
+if os.environ["BUILD_MODE"] == "jvm":
+ build_run_test(has_smali=False)
+else:
+ build_run_test(has_jasmin=False)
diff --git a/test/173-missing-field-type/smali/BadField.smali b/test/173-missing-field-type/smali/BadField.smali
index 7593983..1734034 100644
--- a/test/173-missing-field-type/smali/BadField.smali
+++ b/test/173-missing-field-type/smali/BadField.smali
@@ -41,6 +41,18 @@
return-void
.end method
+.method public static storeStatic(Ljava/lang/Object;)V
+ .registers 1
+ sput-object p0, LBadField;->widget:LWidget;
+ return-void
+.end method
+
+.method public static storeInstance(LBadField;Ljava/lang/Object;)V
+ .registers 2
+ iput-object p1, p0, LBadField;->iwidget:LWidget;
+ return-void
+.end method
+
.method public static storeInstanceObject()V
.registers 2
new-instance v1, LBadField;
diff --git a/test/173-missing-field-type/src-art/Main.java b/test/173-missing-field-type/src-art/Main.java
index 35dbbea..8c5a741 100644
--- a/test/173-missing-field-type/src-art/Main.java
+++ b/test/173-missing-field-type/src-art/Main.java
@@ -15,6 +15,7 @@
*/
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
public class Main {
public static void main(String[] args) throws Throwable {
@@ -24,10 +25,16 @@
// Storing null is OK.
c.getMethod("storeStaticNull").invoke(null);
c.getMethod("storeInstanceNull").invoke(null);
+ c.getMethod("storeStatic", Object.class).invoke(null, new Object[]{ null });
+ c.getMethod("storeInstance", c, Object.class).invoke(
+ null, new Object[]{ c.newInstance(), null });
// Storing anything else should throw an exception.
- testStoreObject(c, "storeStaticObject");
- testStoreObject(c, "storeInstanceObject");
+ testStoreObject(c.getMethod("storeStaticObject"));
+ testStoreObject(c.getMethod("storeInstanceObject"));
+ testStoreObject(c.getMethod("storeStatic", Object.class), new Object());
+ testStoreObject(
+ c.getMethod("storeInstance", c, Object.class), c.newInstance(), new Object());
// Loading is OK.
c = Class.forName("BadFieldGet");
@@ -38,9 +45,9 @@
c.getMethod(methodName).invoke(null);
}
- public static void testStoreObject(Class<?> c, String methodName) throws Throwable {
+ public static void testStoreObject(Method method, Object... arguments) throws Throwable {
try {
- c.getMethod(methodName).invoke(null);
+ method.invoke(null, arguments);
throw new Error("Expected NoClassDefFoundError");
} catch (InvocationTargetException expected) {
Throwable e = expected.getCause();
diff --git a/test/180-native-default-method/build b/test/180-native-default-method/build
deleted file mode 100644
index b6b604f..0000000
--- a/test/180-native-default-method/build
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2020 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@"
-
-if [[ $@ != *"--jvm"* ]]; then
- # Change the generated dex file to have a v35 magic number if it is version 38
- if test -f classes.dex && head -c 7 classes.dex | grep -q 038; then
- # place ascii value '035' into the classes.dex file starting at byte 4.
- printf '035' | dd status=none conv=notrunc of=classes.dex bs=1 seek=4 count=3
- rm -f $TEST_NAME.jar
- ${SOONG_ZIP} -o $TEST_NAME.jar -f classes.dex
- else
- echo Unexpected dex verison
- exit 1
- fi
-fi
diff --git a/test/180-native-default-method/build.py b/test/180-native-default-method/build.py
new file mode 100644
index 0000000..122bcc0
--- /dev/null
+++ b/test/180-native-default-method/build.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2022 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.
+
+from art_build_rules import build_run_test
+import subprocess, os
+
+build_run_test()
+
+if os.environ["BUILD_MODE"] != "jvm":
+ # Change the generated dex file to have a v35 magic number if it is version 38
+ with open("classes.dex", "rb+") as f:
+ assert f.read(8) == b'dex\n038\x00'
+ f.seek(0)
+ f.write(b'dex\n035\x00')
+ os.remove("180-native-default-method.jar")
+ subprocess.run([os.environ["SOONG_ZIP"], "-o",
+ "180-native-default-method.jar", "-f", "classes.dex"], check=True)
diff --git a/test/181-default-methods/build b/test/181-default-methods/build
deleted file mode 100644
index 9cd5738..0000000
--- a/test/181-default-methods/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/181-default-methods/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/181-default-methods/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/181-default-methods/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/182-method-linking/src/Main.java b/test/182-method-linking/src/Main.java
index 3902956..638ff65 100644
--- a/test/182-method-linking/src/Main.java
+++ b/test/182-method-linking/src/Main.java
@@ -33,12 +33,6 @@
public class Main {
public static void main(String args[]) {
- try {
- Class.forName("dalvik.system.PathClassLoader");
- } catch (ClassNotFoundException e) {
- usingRI = true;
- }
-
// A single method signature can result in multiple vtable entries
// when package-private methods from different packages are involved.
// All classes here define the method `void foo()` but classes
@@ -120,7 +114,6 @@
CXI1 cxi1 = new CXI1();
I1.callI1Foo(cxi1);
} catch (IllegalAccessError expected) {
- printOnDalvik("Calling pkg1.I1.foo on pkg1.CXI1");
System.out.println("Caught IllegalAccessError");
}
@@ -128,7 +121,6 @@
CXI2 cxi2 = new CXI2();
I2.callI2Foo(cxi2);
} catch (IllegalAccessError expected) {
- printOnDalvik("Calling pkg2.I2.foo on pkg1.CXI2");
System.out.println("Caught IllegalAccessError");
}
@@ -136,7 +128,6 @@
DXI1 dxi1 = new DXI1();
I1.callI1Foo(dxi1);
} catch (IllegalAccessError expected) {
- printOnDalvik("Calling pkg1.I1.foo on pkg2.DXI1");
System.out.println("Caught IllegalAccessError");
}
@@ -144,17 +135,7 @@
DXI2 dxi2 = new DXI2();
I2.callI2Foo(dxi2);
} catch (IllegalAccessError expected) {
- printOnDalvik("Calling pkg2.I2.foo on pkg2.DXI2");
System.out.println("Caught IllegalAccessError");
}
}
-
- private static void printOnDalvik(String line) {
- if (!usingRI) {
- // FIXME: Delay IAE until calling the method. Bug: 211854716
- System.out.println(line);
- }
- }
-
- private static boolean usingRI = false;
}
diff --git a/test/1940-ddms-ext/ddm_ext.cc b/test/1940-ddms-ext/ddm_ext.cc
index 110ad64..6461bdd 100644
--- a/test/1940-ddms-ext/ddm_ext.cc
+++ b/test/1940-ddms-ext/ddm_ext.cc
@@ -21,8 +21,8 @@
// Test infrastructure
#include "jvmti_helper.h"
-#include "nativehelper/scoped_local_ref.h"
-#include "nativehelper/scoped_primitive_array.h"
+#include "scoped_local_ref.h"
+#include "scoped_primitive_array.h"
#include "test_env.h"
namespace art {
diff --git a/test/1940-ddms-ext/src-art/art/Test1940.java b/test/1940-ddms-ext/src-art/art/Test1940.java
index 605e409..7adcd4a 100644
--- a/test/1940-ddms-ext/src-art/art/Test1940.java
+++ b/test/1940-ddms-ext/src-art/art/Test1940.java
@@ -16,6 +16,7 @@
package art;
+import java.lang.reflect.Field;
import org.apache.harmony.dalvik.ddmc.*;
import dalvik.system.VMDebug;
@@ -50,18 +51,21 @@
}
}
- private static boolean chunkEq(Chunk a, Chunk b) {
- return a.type == b.type &&
- a.offset == b.offset &&
- a.length == b.length &&
- Arrays.equals(a.data, b.data);
+ private static boolean chunkEq(Chunk c1, Chunk c2) {
+ ChunkWrapper a = new ChunkWrapper(c1);
+ ChunkWrapper b = new ChunkWrapper(c2);
+ return a.type() == b.type() &&
+ a.offset() == b.offset() &&
+ a.length() == b.length() &&
+ Arrays.equals(a.data(), b.data());
}
private static String printChunk(Chunk k) {
- byte[] out = new byte[k.length];
- System.arraycopy(k.data, k.offset, out, 0, k.length);
+ ChunkWrapper c = new ChunkWrapper(k);
+ byte[] out = new byte[c.length()];
+ System.arraycopy(c.data(), c.offset(), out, 0, c.length());
return String.format("Chunk(Type: 0x%X, Len: %d, data: %s)",
- k.type, k.length, Arrays.toString(out));
+ c.type(), c.length(), Arrays.toString(out));
}
private static final class MyDdmHandler extends ChunkHandler {
@@ -73,7 +77,8 @@
// For this test we will simply calculate the checksum
ByteBuffer b = ByteBuffer.wrap(new byte[8]);
Adler32 a = new Adler32();
- a.update(req.data, req.offset, req.length);
+ ChunkWrapper reqWrapper = new ChunkWrapper(req);
+ a.update(reqWrapper.data(), reqWrapper.offset(), reqWrapper.length());
b.order(ByteOrder.BIG_ENDIAN);
long val = a.getValue();
b.putLong(val);
@@ -92,6 +97,49 @@
}
}
+
+ /**
+ * Wrapper for accessing the hidden fields in {@link Chunk} in CTS.
+ */
+ private static class ChunkWrapper {
+ private Chunk c;
+
+ ChunkWrapper(Chunk c) {
+ this.c = c;
+ }
+
+ int type() {
+ return c.type;
+ }
+
+ int length() {
+ try {
+ Field f = Chunk.class.getField("length");
+ return (int) f.get(c);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ byte[] data() {
+ try {
+ Field f = Chunk.class.getField("data");
+ return (byte[]) f.get(c);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ int offset() {
+ try {
+ Field f = Chunk.class.getField("offset");
+ return (int) f.get(c);
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
public static final ChunkHandler SINGLE_HANDLER = new MyDdmHandler();
public static DdmHandler CURRENT_HANDLER;
diff --git a/test/677-fsi/build b/test/1948-obsolete-const-method-handle/build.py
old mode 100755
new mode 100644
similarity index 80%
copy from test/677-fsi/build
copy to test/1948-obsolete-const-method-handle/build.py
index b90b408..193aa91
--- a/test/677-fsi/build
+++ b/test/1948-obsolete-const-method-handle/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(api_level=28)
diff --git a/test/1948-obsolete-const-method-handle/build b/test/1948-obsolete-const-method-handle/generate-sources
old mode 100644
new mode 100755
similarity index 94%
rename from test/1948-obsolete-const-method-handle/build
rename to test/1948-obsolete-const-method-handle/generate-sources
index d0e7a8c..9cdc749
--- a/test/1948-obsolete-const-method-handle/build
+++ b/test/1948-obsolete-const-method-handle/generate-sources
@@ -19,5 +19,3 @@
mkdir classes
./util-src/build-classes $PWD/classes
-
-./default-build --api-level 28 "$@"
diff --git a/test/1965-get-set-local-primitive-no-tables/build b/test/1965-get-set-local-primitive-no-tables/generate-sources
old mode 100644
new mode 100755
similarity index 96%
rename from test/1965-get-set-local-primitive-no-tables/build
rename to test/1965-get-set-local-primitive-no-tables/generate-sources
index 6631df9..c2f8653
--- a/test/1965-get-set-local-primitive-no-tables/build
+++ b/test/1965-get-set-local-primitive-no-tables/generate-sources
@@ -22,4 +22,3 @@
else
mv smali smali-unused
fi
-./default-build "$@"
diff --git a/test/1966-get-set-local-objects-no-table/build b/test/1966-get-set-local-objects-no-table/build
deleted file mode 100644
index 6631df9..0000000
--- a/test/1966-get-set-local-objects-no-table/build
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# make us exit on a failure
-set -e
-
-if [[ $@ != *"--jvm"* ]]; then
- mv jasmin jasmin-unused
-else
- mv smali smali-unused
-fi
-./default-build "$@"
diff --git a/test/1965-get-set-local-primitive-no-tables/build b/test/1966-get-set-local-objects-no-table/generate-sources
old mode 100644
new mode 100755
similarity index 96%
copy from test/1965-get-set-local-primitive-no-tables/build
copy to test/1966-get-set-local-objects-no-table/generate-sources
index 6631df9..c2f8653
--- a/test/1965-get-set-local-primitive-no-tables/build
+++ b/test/1966-get-set-local-objects-no-table/generate-sources
@@ -22,4 +22,3 @@
else
mv smali smali-unused
fi
-./default-build "$@"
diff --git a/test/1980-obsolete-object-cleared/expected-stdout.txt b/test/1980-obsolete-object-cleared/expected-stdout.txt
index 9d18b2c..f8a6aec 100644
--- a/test/1980-obsolete-object-cleared/expected-stdout.txt
+++ b/test/1980-obsolete-object-cleared/expected-stdout.txt
@@ -154,10 +154,16 @@
public int java.lang.Class.getModifiers() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
Calling public java.lang.String java.lang.Class.getName() with params: []
public java.lang.String java.lang.Class.getName() on (obsolete)class Main$Transform with [] = Main$Transform
+Calling public java.lang.Class java.lang.Class.getNestHost() with params: []
+public java.lang.Class java.lang.Class.getNestHost() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
+Calling public java.lang.Class[] java.lang.Class.getNestMembers() with params: []
+public java.lang.Class[] java.lang.Class.getNestMembers() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
Calling public java.lang.Package java.lang.Class.getPackage() with params: []
public java.lang.Package java.lang.Class.getPackage() on (obsolete)class Main$Transform with [] = null
Calling public java.lang.String java.lang.Class.getPackageName() with params: []
public java.lang.String java.lang.Class.getPackageName() on (obsolete)class Main$Transform with [] =
+Calling public java.lang.Class[] java.lang.Class.getPermittedSubclasses() with params: []
+public java.lang.Class[] java.lang.Class.getPermittedSubclasses() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
Calling public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() with params: []
public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() on (obsolete)class Main$Transform with [] = null
Calling public java.net.URL java.lang.Class.getResource(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
@@ -217,6 +223,8 @@
public boolean java.lang.Class.isPrimitive() on (obsolete)class Main$Transform with [] = false
Calling public boolean java.lang.Class.isProxy() with params: []
public boolean java.lang.Class.isProxy() on (obsolete)class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isSealed() with params: []
+public boolean java.lang.Class.isSealed() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
Calling public boolean java.lang.Class.isSynthetic() with params: []
public boolean java.lang.Class.isSynthetic() with [] throws java.lang.reflect.InvocationTargetException: java.lang.RuntimeException: Obsolete Object!
Calling public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException with params: []
@@ -367,10 +375,16 @@
public int java.lang.Class.getModifiers() on class Main$Transform with [] = 9
Calling public java.lang.String java.lang.Class.getName() with params: []
public java.lang.String java.lang.Class.getName() on class Main$Transform with [] = Main$Transform
+Calling public java.lang.Class java.lang.Class.getNestHost() with params: []
+public java.lang.Class java.lang.Class.getNestHost() on class Main$Transform with [] = class Main$Transform
+Calling public java.lang.Class[] java.lang.Class.getNestMembers() with params: []
+public java.lang.Class[] java.lang.Class.getNestMembers() on class Main$Transform with [] = [class Main$Transform]
Calling public java.lang.Package java.lang.Class.getPackage() with params: []
public java.lang.Package java.lang.Class.getPackage() on class Main$Transform with [] = null
Calling public java.lang.String java.lang.Class.getPackageName() with params: []
public java.lang.String java.lang.Class.getPackageName() on class Main$Transform with [] =
+Calling public java.lang.Class[] java.lang.Class.getPermittedSubclasses() with params: []
+public java.lang.Class[] java.lang.Class.getPermittedSubclasses() on class Main$Transform with [] = null
Calling public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() with params: []
public java.security.ProtectionDomain java.lang.Class.getProtectionDomain() on class Main$Transform with [] = null
Calling public java.net.URL java.lang.Class.getResource(java.lang.String) with params: [[NOT_USED_STRING, foo, SECRET_ARRAY]]
@@ -430,6 +444,8 @@
public boolean java.lang.Class.isPrimitive() on class Main$Transform with [] = false
Calling public boolean java.lang.Class.isProxy() with params: []
public boolean java.lang.Class.isProxy() on class Main$Transform with [] = false
+Calling public boolean java.lang.Class.isSealed() with params: []
+public boolean java.lang.Class.isSealed() on class Main$Transform with [] = false
Calling public boolean java.lang.Class.isSynthetic() with params: []
public boolean java.lang.Class.isSynthetic() on class Main$Transform with [] = false
Calling public native java.lang.Object java.lang.Class.newInstance() throws java.lang.InstantiationException,java.lang.IllegalAccessException with params: []
diff --git a/test/1981-structural-redef-private-method-handles/build b/test/1981-structural-redef-private-method-handles/build
deleted file mode 100755
index c80d7ad..0000000
--- a/test/1981-structural-redef-private-method-handles/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/807-method-handle-and-mr/build b/test/1981-structural-redef-private-method-handles/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/1981-structural-redef-private-method-handles/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/1981-structural-redef-private-method-handles/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/1983-structural-redefinition-failures/build b/test/1983-structural-redefinition-failures/build
deleted file mode 100755
index c80d7ad..0000000
--- a/test/1983-structural-redefinition-failures/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2019 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/807-method-handle-and-mr/build b/test/1983-structural-redefinition-failures/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/1983-structural-redefinition-failures/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/1983-structural-redefinition-failures/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/677-fsi/build b/test/2000-virtual-list-structural/build.py
old mode 100755
new mode 100644
similarity index 68%
copy from test/677-fsi/build
copy to test/2000-virtual-list-structural/build.py
index b90b408..0d8dc0c
--- a/test/677-fsi/build
+++ b/test/2000-virtual-list-structural/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+import os
+
+build_run_test(use_desugar=False)
+
+os.rename("src-ex/java/util/AbstractCollection.bak",
+ "src-ex/java/util/AbstractCollection.java")
+
diff --git a/test/2000-virtual-list-structural/build b/test/2000-virtual-list-structural/generate-sources
similarity index 82%
rename from test/2000-virtual-list-structural/build
rename to test/2000-virtual-list-structural/generate-sources
index f8496bec..5e21f12 100755
--- a/test/2000-virtual-list-structural/build
+++ b/test/2000-virtual-list-structural/generate-sources
@@ -23,9 +23,3 @@
# Patch the copied version.
patch src-ex/java/util/AbstractCollection.java AbstractCollection.patch
-
-USE_DESUGAR=false ./default-build "$@"
-
-# restore the symlink
-rm src-ex/java/util/AbstractCollection.java
-mv src-ex/java/util/AbstractCollection.bak src-ex/java/util/AbstractCollection.java
diff --git a/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc b/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
index 5eaaa05..78bb772 100644
--- a/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
+++ b/test/2011-stack-walk-concurrent-instrument/stack_walk_concurrent.cc
@@ -89,9 +89,8 @@
ScopedSuspendAll ssa(__FUNCTION__);
Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(other,
/* deopt_all_frames= */ false);
- MutexLock mu(Thread::Current(), *Locks::thread_suspend_count_lock_);
- bool updated = other->ModifySuspendCount(Thread::Current(), -1, nullptr, SuspendReason::kInternal);
- CHECK(updated);
+ bool resumed = art::Runtime::Current()->GetThreadList()->Resume(other, SuspendReason::kInternal);
+ CHECK(resumed);
instrumented = true;
return;
}
diff --git a/test/975-iface-private/build b/test/2034-spaces-in-SimpleName/build.py
old mode 100755
new mode 100644
similarity index 73%
rename from test/975-iface-private/build
rename to test/2034-spaces-in-SimpleName/build.py
index 14230c2..7601e12
--- a/test/975-iface-private/build
+++ b/test/2034-spaces-in-SimpleName/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2015 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# make us exit on a failure
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental default-methods
+# Use API level 10000 for spaces in SimpleName
+build_run_test(use_desugar=False, api_level="10000")
diff --git a/test/2034-spaces-in-SimpleName/build b/test/2034-spaces-in-SimpleName/generate-sources
similarity index 89%
rename from test/2034-spaces-in-SimpleName/build
rename to test/2034-spaces-in-SimpleName/generate-sources
index 8261ed2..99fc7e0 100755
--- a/test/2034-spaces-in-SimpleName/build
+++ b/test/2034-spaces-in-SimpleName/generate-sources
@@ -24,6 +24,3 @@
${JAVA:-java} -cp "$ASM_JAR:." SpacesInSimpleName.java
mkdir ../classes && mv Main.class ../classes/Main.class
cd ..
-
-# Use API level 10000 for spaces in SimpleName
-USE_DESUGAR=false ./default-build "$@" --api-level 10000
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/2038-hiddenapi-jvmti-ext/build.py
similarity index 80%
copy from test/2038-hiddenapi-jvmti-ext/build
copy to test/2038-hiddenapi-jvmti-ext/build.py
index f4b029f..8367ad0 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/2038-hiddenapi-jvmti-ext/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+from art_build_rules import build_run_test
+
+build_run_test(use_hiddenapi=True)
diff --git a/test/2040-huge-native-alloc/src/Main.java b/test/2040-huge-native-alloc/src/Main.java
index 59e1266..3c8ae23 100644
--- a/test/2040-huge-native-alloc/src/Main.java
+++ b/test/2040-huge-native-alloc/src/Main.java
@@ -24,7 +24,7 @@
int allocated = 0;
int deallocated = 0;
static Object lock = new Object();
- final static int MAX_TRIES = 4;
+ final static int MAX_TRIES = 10;
WeakReference<BufferHolder>[] references = new WeakReference[HOW_MANY_HUGE];
class BufferHolder {
@@ -61,6 +61,14 @@
if (new Main().tryToRun(i == MAX_TRIES)) {
break;
}
+ if (i == MAX_TRIES / 2) {
+ // Maybe some transient CPU load is causing issues here?
+ try {
+ Thread.sleep(3000);
+ } catch (InterruptedException ignored) {
+ System.out.println("Unexpected interrupt");
+ }
+ }
// Clean up and try again.
Runtime.getRuntime().gc();
System.runFinalization();
@@ -84,7 +92,10 @@
if (startingGcNum != getGcNum()) {
// Happens rarely, fail and retry.
- return false;
+ if (!lastChance) {
+ return false;
+ }
+ System.out.println("Triggered early GC");
}
// One of the notifications should block for GC to catch up.
long actualTime = timeNotifications();
diff --git a/test/2042-checker-dce-always-throw/src/Main.java b/test/2042-checker-dce-always-throw/src/Main.java
index 99738a7..1aa10a0 100644
--- a/test/2042-checker-dce-always-throw/src/Main.java
+++ b/test/2042-checker-dce-always-throw/src/Main.java
@@ -20,12 +20,19 @@
assertEquals(0, $noinline$testSimplifyThrow(1));
// Basic test for non-trivial blocks (i.e. not just an invoke and a Goto)
+ assertEquals(0, $noinline$testSimplifyThrowAndPrint(1));
assertEquals(0, $noinline$testSimplifyTwoThrows(1));
+ assertEquals(0, $noinline$testSimplifyWithArgument(1));
// Try catch tests
assertEquals(0, $noinline$testDoNotSimplifyInTry(1));
assertEquals(0, $noinline$testSimplifyInCatch(1));
- assertEquals(0, $noinline$testDoNotSimplifyInCatchInOuterTry(1));
+ assertEquals(0, $noinline$testDoNotSimplifyInCatchInOuterTry(1, 1));
+
+ // Test that we update the phis correctly after simplifying an always throwing method, and
+ // recomputing dominance.
+ assertEquals(0, $noinline$testUpdatePhisCorrectly(1, 1));
+ assertEquals(0, $noinline$testDeleteAllUsesBeforeDeletingInstruction(1, 1));
}
private static void alwaysThrows() throws Error {
@@ -51,6 +58,28 @@
return 0;
}
+ /// CHECK-START: int Main.$noinline$testSimplifyThrowAndPrint(int) dead_code_elimination$after_inlining (before)
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+ /// CHECK-DAG: InvokeVirtual method_name:java.io.PrintStream.println
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+ /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>"
+
+ /// CHECK-START: int Main.$noinline$testSimplifyThrowAndPrint(int) dead_code_elimination$after_inlining (after)
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+ /// CHECK-START: int Main.$noinline$testSimplifyThrowAndPrint(int) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: InvokeVirtual method_name:java.io.PrintStream.println
+ private static int $noinline$testSimplifyThrowAndPrint(int num) {
+ if (num == 0) {
+ alwaysThrows();
+ System.out.println("I am unrechable!");
+ }
+ return 0;
+ }
+
/// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (before)
/// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
/// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock>> method_name:Main.alwaysThrows always_throws:true
@@ -60,10 +89,14 @@
/// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (after)
/// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
- /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock>> method_name:Main.alwaysThrows always_throws:true
/// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
/// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+ // Check that the second `alwaysThrows` gets removed.
+ /// CHECK-START: int Main.$noinline$testSimplifyTwoThrows(int) dead_code_elimination$after_inlining (after)
+ /// CHECK: InvokeStaticOrDirect method_name:Main.alwaysThrows always_throws:true
+ /// CHECK-NOT: InvokeStaticOrDirect method_name:Main.alwaysThrows always_throws:true
+
// Tests that we simplify the always throwing branch directly to the exit, even with blocks that
// are not just the throwing instruction and a Goto.
private static int $noinline$testSimplifyTwoThrows(int num) {
@@ -74,6 +107,35 @@
return 0;
}
+ private static int throwIfZero(int num) {
+ if (num == 0) {
+ throw new Error("num is 0!");
+ }
+ return num / num;
+ }
+
+ /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (before)
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.throwIfZero always_throws:true
+ /// CHECK-DAG: InvokeVirtual method_name:java.io.PrintStream.println
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+ /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>"
+
+ /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (after)
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.throwIfZero always_throws:true
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+ /// CHECK-START: int Main.$noinline$testSimplifyWithArgument(int) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: InvokeVirtual method_name:java.io.PrintStream.println
+ private static int $noinline$testSimplifyWithArgument(int num) {
+ if (num == 0) {
+ throwIfZero(0);
+ System.out.println("I am unrechable!");
+ }
+ return 0;
+ }
+
/// CHECK-START: int Main.$noinline$testSimplifyThrowWithTryCatch(int) dead_code_elimination$after_inlining (before)
/// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
/// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
@@ -190,25 +252,25 @@
}
}
- /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (before)
+ /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$after_inlining (before)
/// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
/// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
/// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
/// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>"
- /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (after)
+ /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$after_inlining (after)
/// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
/// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
/// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
/// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>"
// Consistency check to make sure we have the try catches in the graph at this stage.
- /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$after_inlining (before)
+ /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$after_inlining (before)
/// CHECK-DAG: TryBoundary kind:entry
/// CHECK-DAG: TryBoundary kind:entry
// Consistency check to that we do not simplify it by the last DCE pass either
- /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int) dead_code_elimination$final (after)
+ /// CHECK-START: int Main.$noinline$testDoNotSimplifyInCatchInOuterTry(int, int) dead_code_elimination$final (after)
/// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
/// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
/// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
@@ -217,13 +279,15 @@
// Similar to testSimplifyInCatch, but now the throw is in an outer try and we shouldn't simplify
// it. Like in testDoNotSimplifyInTry, we need the help of the inliner to have an invoke followed
// by a Goto.
- private static int $noinline$testDoNotSimplifyInCatchInOuterTry(int num) {
+ private static int $noinline$testDoNotSimplifyInCatchInOuterTry(int num, int other_num) {
try {
try {
throw new Error();
} catch (Error e) {
if (num == 0) {
- $inline$testDoNotSimplifyInner(num);
+ // We use `other_num` here because otherwise we propagate the knowledge that `num` equals
+ // zero.
+ $inline$testDoNotSimplifyInner(other_num);
}
return 0;
}
@@ -232,6 +296,93 @@
}
}
+ // Check that when we perform SimplifyAlwaysThrows, that the phi for `phi_value` exists, and that
+ // we correctly update it after running DCE.
+
+ /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int, int) dead_code_elimination$after_inlining (before)
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0
+ /// CHECK-DAG: <<Const5:i\d+>> IntConstant 5
+ /// CHECK-DAG: <<ReturnValue:i\d+>> Phi [<<Const0>>,<<Const5>>]
+ /// CHECK-DAG: Return [<<ReturnValue>>]
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+ /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>"
+
+ /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int, int) dead_code_elimination$after_inlining (after)
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0
+ /// CHECK-DAG: Return [<<Const0>>]
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+ /// CHECK-START: int Main.$noinline$testUpdatePhisCorrectly(int, int) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: Phi
+ private static int $noinline$testUpdatePhisCorrectly(int num, int other_num) {
+ int phi_value = 0;
+ if (num == 0) {
+ alwaysThrows();
+
+ // This while loop is here so that the `if (num == 0)` will be several blocks instead of
+ // just one. We use `other_num` here because otherwise we propagate the knowledge that `num`
+ // equals zero.
+ while (other_num == 0) {
+ // Assign to phi_value so that the loop is not empty.
+ phi_value = 2;
+ }
+
+ phi_value = 5;
+ }
+ return phi_value;
+ }
+
+
+ // Test to check that we delete all uses before the instruction.
+ private static int $noinline$foo(int num) {
+ return num;
+ }
+
+ /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int, int) dead_code_elimination$after_inlining (before)
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0
+ /// CHECK-DAG: <<Invoke:i\d+>> InvokeStaticOrDirect method_name:Main.$noinline$foo
+ /// CHECK-DAG: <<ReturnValue:i\d+>> Phi [<<Const0>>,<<Invoke>>]
+ /// CHECK-DAG: Return [<<ReturnValue>>]
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<TargetBlock:B\d+>>
+ /// CHECK-EVAL: "<<ExitBlock>>" != "<<TargetBlock>>"
+
+ /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int, int) dead_code_elimination$after_inlining (after)
+ /// CHECK-DAG: <<Const0:i\d+>> IntConstant 0
+ /// CHECK-DAG: Return [<<Const0>>]
+ /// CHECK-DAG: InvokeStaticOrDirect block:<<InvokeBlock:B\d+>> method_name:Main.alwaysThrows always_throws:true
+ /// CHECK-DAG: Exit block:<<ExitBlock:B\d+>>
+ /// CHECK-DAG: Goto block:<<InvokeBlock>> target:<<ExitBlock>>
+
+ /// CHECK-START: int Main.$noinline$testDeleteAllUsesBeforeDeletingInstruction(int, int) dead_code_elimination$after_inlining (after)
+ /// CHECK-NOT: Phi
+ private static int $noinline$testDeleteAllUsesBeforeDeletingInstruction(int num, int other_num) {
+ int phi_value = 0;
+ if (num == 0) {
+ alwaysThrows();
+
+ // This while loop is here so that we have a Goto after `alwaysThrows` and therefore perform
+ // the `SimplifyAlwaysThrows` optimization. We use `other_num` here because otherwise we
+ // propagate the knowledge that `num` equals zero.
+ while (other_num == 0) {
+ // Assign to phi_value so that the loop is not empty.
+ phi_value = 2;
+ }
+
+ try {
+ phi_value = $noinline$foo(2);
+ } catch (Error e) {
+ throw new Error("We shouldn't hit this");
+ }
+ }
+ return phi_value;
+ }
+
static void assertEquals(int expected, int actual) {
if (expected != actual) {
throw new AssertionError("Expected " + expected + " got " + actual);
diff --git a/test/2042-reference-processing/Android.bp b/test/2042-reference-processing/Android.bp
new file mode 100644
index 0000000..a73b8d0
--- /dev/null
+++ b/test/2042-reference-processing/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2042-reference-processing`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2042-reference-processing",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2042-reference-processing-expected-stdout",
+ ":art-run-test-2042-reference-processing-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2042-reference-processing-expected-stdout",
+ out: ["art-run-test-2042-reference-processing-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2042-reference-processing-expected-stderr",
+ out: ["art-run-test-2042-reference-processing-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2042-reference-processing/expected-stderr.txt b/test/2042-reference-processing/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2042-reference-processing/expected-stderr.txt
diff --git a/test/2042-reference-processing/expected-stdout.txt b/test/2042-reference-processing/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2042-reference-processing/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2042-reference-processing/info.txt b/test/2042-reference-processing/info.txt
new file mode 100644
index 0000000..e2d7a99
--- /dev/null
+++ b/test/2042-reference-processing/info.txt
@@ -0,0 +1,6 @@
+A test for reference processing correctness.
+
+The emphasis here is on fundamental properties. In particular, references to
+unreachable referents should be enqueued, and this should ensure that uncleared
+References don't point to objects for which References were enqueued. We also
+check various other ordering properties for java.lang.ref.References.
diff --git a/test/2042-reference-processing/src/Main.java b/test/2042-reference-processing/src/Main.java
new file mode 100644
index 0000000..ed67052
--- /dev/null
+++ b/test/2042-reference-processing/src/Main.java
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2022 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.lang.ref.PhantomReference;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.math.BigInteger;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
+import java.util.TreeMap;
+
+/**
+ * Test that objects get finalized and their references cleared in the right order.
+ *
+ * We maintain a list of nominally MAX_LIVE_OBJS numbered finalizable objects.
+ * We then alternately drop the last 50, and add 50 more. When we see an object finalized
+ * or its reference cleared, we make sure that the preceding objects in its group of 50
+ * have also had their references cleared. We also perform a number of other more
+ * straightforward checks, such as ensuring that all references are eventually cleared,
+ * and all objects are finalized.
+ */
+public class Main {
+ // TODO(b/216481630) Enable CHECK_PHANTOM_REFS. This currently occasionally reports a few
+ // PhantomReferences as not enqueued. If this report is correct, this needs to be tracked
+ // down and fixed.
+ static final boolean CHECK_PHANTOM_REFS = false;
+
+ static final int MAX_LIVE_OBJS = 150;
+ static final int DROP_OBJS = 50; // Number of linked objects dropped in each batch.
+ static final int MIN_LIVE_OBJS = MAX_LIVE_OBJS - DROP_OBJS;
+ static final int TOTAL_OBJS = 200_000; // Allocate this many finalizable objects in total.
+ static final boolean REPORT_DROPS = false;
+ static volatile boolean pleaseStop;
+
+ AtomicInteger totalFinalized = new AtomicInteger(0);
+ Object phantomRefsLock = new Object();
+ int maxDropped = 0;
+ int liveObjects = 0;
+
+ // Number of next finalizable object to be allocated.
+ int nextAllocated = 0;
+
+ // List of finalizable objects in descending order. We add to the front and drop
+ // from the rear.
+ FinalizableObject listHead;
+
+ // A possibly incomplete list of FinalizableObject indices that were finalized, but
+ // have yet to be checked for consistency with reference processing.
+ ArrayBlockingQueue<Integer> finalized = new ArrayBlockingQueue<>(20_000);
+
+ // Maps from object number to Reference; Cleared references are deleted when queues are
+ // processed.
+ TreeMap<Integer, MyWeakReference> weakRefs = new TreeMap<>();
+ HashMap<Integer, MyPhantomReference> phantomRefs = new HashMap<>();
+
+ class FinalizableObject {
+ int n;
+ FinalizableObject next;
+ FinalizableObject(int num, FinalizableObject nextObj) {
+ n = num;
+ next = nextObj;
+ }
+ protected void finalize() {
+ if (!inPhantomRefs(n)) {
+ System.out.println("PhantomRef enqueued before finalizer ran");
+ }
+ totalFinalized.incrementAndGet();
+ if (!finalized.offer(n) && REPORT_DROPS) {
+ System.out.println("Dropped finalization of " + n);
+ }
+ }
+ }
+ ReferenceQueue<FinalizableObject> refQueue = new ReferenceQueue<>();
+ class MyWeakReference extends WeakReference<FinalizableObject> {
+ int n;
+ MyWeakReference(FinalizableObject obj) {
+ super(obj, refQueue);
+ n = obj.n;
+ }
+ };
+ class MyPhantomReference extends PhantomReference<FinalizableObject> {
+ int n;
+ MyPhantomReference(FinalizableObject obj) {
+ super(obj, refQueue);
+ n = obj.n;
+ }
+ }
+ boolean inPhantomRefs(int n) {
+ synchronized(phantomRefsLock) {
+ MyPhantomReference ref = phantomRefs.get(n);
+ if (ref == null) {
+ return false;
+ }
+ if (ref.n != n) {
+ System.out.println("phantomRef retrieval failed");
+ }
+ return true;
+ }
+ }
+
+ void CheckOKToClearWeak(int num) {
+ if (num > maxDropped) {
+ System.out.println("WeakRef to live object " + num + " was cleared/enqueued.");
+ }
+ int batchEnd = (num / DROP_OBJS + 1) * DROP_OBJS;
+ for (MyWeakReference wr : weakRefs.subMap(num + 1, batchEnd).values()) {
+ if (wr.n <= num || wr.n / DROP_OBJS != num / DROP_OBJS) {
+ throw new AssertionError("MyWeakReference logic error!");
+ }
+ // wr referent was dropped in same batch and precedes it in list.
+ if (wr.get() != null) {
+ // This violates the WeakReference spec, and can result in strong references
+ // to objects that have been cleaned.
+ System.out.println("WeakReference to " + wr.n
+ + " was erroneously cleared after " + num);
+ }
+ }
+ }
+
+ void CheckOKToClearPhantom(int num) {
+ if (num > maxDropped) {
+ System.out.println("PhantomRef to live object " + num + " was enqueued.");
+ }
+ MyWeakReference wr = weakRefs.get(num);
+ if (wr != null && wr.get() != null) {
+ System.out.println("PhantomRef cleared before WeakRef for " + num);
+ }
+ }
+
+ void emptyAndCheckQueues() {
+ // Check recently finalized objects for consistency with cleared references.
+ while (true) {
+ Integer num = finalized.poll();
+ if (num == null) {
+ break;
+ }
+ MyWeakReference wr = weakRefs.get(num);
+ if (wr != null) {
+ if (wr.n != num) {
+ System.out.println("Finalization logic error!");
+ }
+ if (wr.get() != null) {
+ System.out.println("Finalizing object with uncleared reference");
+ }
+ }
+ CheckOKToClearWeak(num);
+ }
+ // Check recently enqueued references for consistency.
+ while (true) {
+ Reference<FinalizableObject> ref = (Reference<FinalizableObject>) refQueue.poll();
+ if (ref == null) {
+ break;
+ }
+ if (ref instanceof MyWeakReference) {
+ MyWeakReference wr = (MyWeakReference) ref;
+ if (wr.get() != null) {
+ System.out.println("WeakRef " + wr.n + " enqueued but not cleared");
+ }
+ CheckOKToClearWeak(wr.n);
+ if (weakRefs.remove(Integer.valueOf(wr.n)) != ref) {
+ System.out.println("Missing WeakReference: " + wr.n);
+ }
+ } else if (ref instanceof MyPhantomReference) {
+ MyPhantomReference pr = (MyPhantomReference) ref;
+ CheckOKToClearPhantom(pr.n);
+ if (phantomRefs.remove(Integer.valueOf(pr.n)) != ref) {
+ System.out.println("Missing PhantomReference: " + pr.n);
+ }
+ } else {
+ System.out.println("Found unrecognized reference in queue");
+ }
+ }
+ }
+
+
+ /**
+ * Add n objects to the head of the list. These will be assigned the next n consecutive
+ * numbers after the current head of the list.
+ */
+ void addObjects(int n) {
+ for (int i = 0; i < n; ++i) {
+ int me = nextAllocated++;
+ listHead = new FinalizableObject(me, listHead);
+ weakRefs.put(me, new MyWeakReference(listHead));
+ synchronized(phantomRefsLock) {
+ phantomRefs.put(me, new MyPhantomReference(listHead));
+ }
+ }
+ liveObjects += n;
+ }
+
+ /**
+ * Drop n finalizable objects from the tail of the list. These are the lowest-numbered objects
+ * in the list.
+ */
+ void dropObjects(int n) {
+ FinalizableObject list = listHead;
+ FinalizableObject last = null;
+ if (n > liveObjects) {
+ System.out.println("Removing too many elements");
+ }
+ if (liveObjects == n) {
+ maxDropped = list.n;
+ listHead = null;
+ } else {
+ final int skip = liveObjects - n;
+ for (int i = 0; i < skip; ++i) {
+ last = list;
+ list = list.next;
+ }
+ int expected = nextAllocated - skip - 1;
+ if (list.n != expected) {
+ System.out.println("dropObjects found " + list.n + " but expected " + expected);
+ }
+ maxDropped = expected;
+ last.next = null;
+ }
+ liveObjects -= n;
+ }
+
+ void testLoop() {
+ System.out.println("Starting");
+ addObjects(MIN_LIVE_OBJS);
+ final int ITERS = (TOTAL_OBJS - MIN_LIVE_OBJS) / DROP_OBJS;
+ for (int i = 0; i < ITERS; ++i) {
+ addObjects(DROP_OBJS);
+ if (liveObjects != MAX_LIVE_OBJS) {
+ System.out.println("Unexpected live object count");
+ }
+ dropObjects(DROP_OBJS);
+ emptyAndCheckQueues();
+ }
+ dropObjects(MIN_LIVE_OBJS);
+ if (liveObjects != 0 || listHead != null) {
+ System.out.println("Unexpected live objecs at end");
+ }
+ if (maxDropped != TOTAL_OBJS - 1) {
+ System.out.println("Unexpected dropped object count: " + maxDropped);
+ }
+ for (int i = 0; i < 2; ++i) {
+ Runtime.getRuntime().gc();
+ System.runFinalization();
+ emptyAndCheckQueues();
+ }
+ if (!weakRefs.isEmpty()) {
+ System.out.println("Weak Reference map nonempty size = " + weakRefs.size());
+ }
+ if (CHECK_PHANTOM_REFS && !phantomRefs.isEmpty()) {
+ try {
+ Thread.sleep(500);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpected interrupt");
+ }
+ if (!phantomRefs.isEmpty()) {
+ System.out.println("Phantom Reference map nonempty size = " + phantomRefs.size());
+ System.out.print("First elements:");
+ int i = 0;
+ for (MyPhantomReference pr : phantomRefs.values()) {
+ System.out.print(" " + pr.n);
+ if (++i > 10) {
+ break;
+ }
+ }
+ System.out.println("");
+ }
+ }
+ if (totalFinalized.get() != TOTAL_OBJS) {
+ System.out.println("Finalized only " + totalFinalized + " objects");
+ }
+ }
+
+ static Runnable causeGCs = new Runnable() {
+ public void run() {
+ // Allocate a lot.
+ BigInteger counter = BigInteger.ZERO;
+ while (!pleaseStop) {
+ counter = counter.add(BigInteger.TEN);
+ }
+ // Look at counter to reduce chance of optimizing out the allocation.
+ if (counter.longValue() % 10 != 0) {
+ System.out.println("Bad causeGCs counter value: " + counter);
+ }
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ Main theTest = new Main();
+ Thread gcThread = new Thread(causeGCs);
+ gcThread.setDaemon(true); // Terminate if main thread dies.
+ gcThread.start();
+ theTest.testLoop();
+ pleaseStop = true;
+ gcThread.join();
+ System.out.println("Finished");
+ }
+}
diff --git a/test/2043-reference-pauses/Android.bp b/test/2043-reference-pauses/Android.bp
new file mode 100644
index 0000000..a84aea2
--- /dev/null
+++ b/test/2043-reference-pauses/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2043-reference-pauses`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2043-reference-pauses",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2043-reference-pauses-expected-stdout",
+ ":art-run-test-2043-reference-pauses-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2043-reference-pauses-expected-stdout",
+ out: ["art-run-test-2043-reference-pauses-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2043-reference-pauses-expected-stderr",
+ out: ["art-run-test-2043-reference-pauses-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2043-reference-pauses/expected-stderr.txt b/test/2043-reference-pauses/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2043-reference-pauses/expected-stderr.txt
diff --git a/test/2043-reference-pauses/expected-stdout.txt b/test/2043-reference-pauses/expected-stdout.txt
new file mode 100644
index 0000000..8b3e832
--- /dev/null
+++ b/test/2043-reference-pauses/expected-stdout.txt
@@ -0,0 +1,2 @@
+Starting
+Finished
diff --git a/test/2043-reference-pauses/info.txt b/test/2043-reference-pauses/info.txt
new file mode 100644
index 0000000..f76fa32
--- /dev/null
+++ b/test/2043-reference-pauses/info.txt
@@ -0,0 +1,5 @@
+Tests WeakReference processing and retention of objects needed by finalizers.
+
+Can be used as Reference.get() blocking benchmark by setting PRINT_TIMES to
+true. This will print maximum observed latencies for Reference.get() when
+significant memory is only reachable from SoftReferences and Finalizers.
diff --git a/test/2043-reference-pauses/src/Main.java b/test/2043-reference-pauses/src/Main.java
new file mode 100644
index 0000000..e390155
--- /dev/null
+++ b/test/2043-reference-pauses/src/Main.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2022 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.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.SoftReference;
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+/**
+ * Basic test of WeakReferences with large amounts of memory that's only reachable through
+ * finalizers. Also makes sure that finalizer-reachable data is not collected.
+ * Can easily be modified to time Reference.get() blocking.
+ */
+public class Main {
+ static final boolean PRINT_TIMES = false; // true will cause benchmark failure.
+ // Data structures repeatedly allocated in background to trigger GC.
+ // Size of finalizer reachable trees.
+ static final int TREE_HEIGHT = 15; // Trees contain 2^TREE_HEIGHT -1 allocated objects.
+ // Number of finalizable tree-owning objects that exist at one point.
+ static final int N_RESURRECTING_OBJECTS = 10;
+ // Number of short-lived, not finalizer-reachable, objects allocated between trees.
+ static final int N_PLAIN_OBJECTS = 20_000;
+ // Number of SoftReferences to CBTs we allocate.
+ static final int N_SOFTREFS = 10;
+
+ static final boolean BACKGROUND_GC_THREAD = true;
+ static final int NBATCHES = 10;
+ static final int NREFS = PRINT_TIMES ? 1_000_000 : 300_000; // Multiple of NBATCHES.
+ static final int REFS_PER_BATCH = NREFS / NBATCHES;
+
+ static volatile boolean pleaseStop = false;
+
+ // Large array of WeakReferences filled and accessed by tests below.
+ ArrayList<WeakReference<Integer>> weakRefs = new ArrayList<>(NREFS);
+
+ /**
+ * Complete binary tree data structure. make(n) takes O(2^n) space.
+ */
+ static class CBT {
+ CBT left;
+ CBT right;
+ CBT(CBT l, CBT r) {
+ left = l;
+ right = r;
+ }
+ static CBT make(int n) {
+ if (n == 0) {
+ return null;
+ }
+ return new CBT(make(n - 1), make(n - 1));
+ }
+ /**
+ * Check that path described by bit-vector path has the correct length.
+ */
+ void check(int n, int path) {
+ CBT current = this;
+ for (int i = 0; i < n; i++, path = path >>> 1) {
+ // Unexpectedly short paths result in NPE.
+ if ((path & 1) == 0) {
+ current = current.left;
+ } else {
+ current = current.right;
+ }
+ }
+ if (current != null) {
+ System.out.println("Complete binary tree path too long");
+ }
+ }
+ }
+
+
+ /**
+ * A finalizable object that refers to O(2^TREE_HEIGHT) otherwise unreachable memory.
+ * When finalized, it creates a new identical object, making sure that one always stays
+ * around.
+ */
+ static class ResurrectingObject {
+ CBT stuff;
+ ResurrectingObject() {
+ stuff = CBT.make(TREE_HEIGHT);
+ }
+ static ResurrectingObject a[] = new ResurrectingObject[2];
+ static int i = 0;
+ static synchronized void allocOne() {
+ a[(++i) % 2] = new ResurrectingObject();
+ // Check the previous one to make it hard to optimize anything out.
+ if (i > 1) {
+ a[(i + 1) % 2].stuff.check(TREE_HEIGHT, i /* weirdly interpreted as path */);
+ }
+ }
+ protected void finalize() {
+ stuff.check(TREE_HEIGHT, 42 /* Some path descriptor */);
+ // Allocate a new one to replace this one.
+ allocOne();
+ }
+ }
+
+ void fillWeakRefs() {
+ for (int i = 0; i < NREFS; ++i) {
+ weakRefs.add(null);
+ }
+ }
+
+ /*
+ * Return maximum observed time in nanos to dereference a WeakReference to an unreachable
+ * object. weakRefs is presumed to be pre-filled to have the correct size.
+ */
+ long timeUnreachableInner() {
+ long maxNanos = 0;
+ // Fill weakRefs with WeakReferences to unreachable integers, a batch at a time.
+ // Then time and test .get() calls on carefully sampled array entries, some of which
+ // will have been cleared.
+ for (int i = 0; i < NBATCHES; ++i) {
+ for (int j = 0; j < REFS_PER_BATCH; ++j) {
+ weakRefs.set(i * REFS_PER_BATCH + j,
+ new WeakReference(new Integer(i * REFS_PER_BATCH + j)));
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpected exception");
+ }
+ // Iterate over the filled-in section of weakRefs, but look only at a subset of the
+ // elements, making sure the subsets for different top-level iterations are disjoint.
+ // Otherwise the get() calls here will extend the lifetimes of the referents, and we
+ // may never see any cleared WeakReferences.
+ for (int j = (i + 1) * REFS_PER_BATCH - i - 1; j >= 0; j -= NBATCHES) {
+ WeakReference<Integer> wr = weakRefs.get(j);
+ if (wr != null) {
+ long startNanos = System.nanoTime();
+ Integer referent = wr.get();
+ long totalNanos = System.nanoTime() - startNanos;
+ if (referent == null) {
+ // Optimization to reduce max space use and scanning time.
+ weakRefs.set(j, null);
+ }
+ maxNanos = Math.max(maxNanos, totalNanos);
+ if (referent != null && referent.intValue() != j) {
+ System.out.println("Unexpected referent; expected " + j + " got "
+ + referent.intValue());
+ }
+ }
+ }
+ }
+ return maxNanos;
+ }
+
+ /*
+ * Wrapper for the above that also checks that references were reclaimed.
+ * We do this separately to make sure any stack references from the core of the
+ * test are gone. Empirically, we otherwise sometimes see the zeroth WeakReference
+ * not reclaimed.
+ */
+ long timeUnreachable() {
+ long maxNanos = timeUnreachableInner();
+ Runtime.getRuntime().gc();
+ System.runFinalization(); // Presumed to wait for reference clearing.
+ for (int i = 0; i < NREFS; ++i) {
+ if (weakRefs.get(i) != null && weakRefs.get(i).get() != null) {
+ System.out.println("WeakReference to " + i + " wasn't cleared");
+ }
+ }
+ return maxNanos;
+ }
+
+ /**
+ * Return maximum observed time in nanos to dereference a WeakReference to a reachable
+ * object. Overwrites weakRefs, which is presumed to have NREFS entries already.
+ */
+ long timeReachable() {
+ long maxNanos = 0;
+ // Similar to the above, but we use WeakReferences to otherwise reachable objects,
+ // which should thus not get cleared.
+ Integer[] strongRefs = new Integer[NREFS];
+ for (int i = 0; i < NBATCHES; ++i) {
+ for (int j = i * REFS_PER_BATCH; j < (i + 1) * REFS_PER_BATCH; ++j) {
+ Integer newObj = new Integer(j);
+ strongRefs[j] = newObj;
+ weakRefs.set(j, new WeakReference(newObj));
+ }
+ for (int j = (i + 1) * REFS_PER_BATCH - 1; j >= 0; --j) {
+ WeakReference<Integer> wr = weakRefs.get(j);
+ long startNanos = System.nanoTime();
+ Integer referent = wr.get();
+ long totalNanos = System.nanoTime() - startNanos;
+ maxNanos = Math.max(maxNanos, totalNanos);
+ if (referent == null) {
+ System.out.println("Unexpectedly cleared referent at " + j);
+ } else if (referent.intValue() != j) {
+ System.out.println("Unexpected reachable referent; expected " + j + " got "
+ + referent.intValue());
+ }
+ }
+ }
+ Reference.reachabilityFence(strongRefs);
+ return maxNanos;
+ }
+
+ void runTest() {
+ System.out.println("Starting");
+ fillWeakRefs();
+ long unreachableNanos = timeUnreachable();
+ if (PRINT_TIMES) {
+ System.out.println("Finished timeUnrechable()");
+ }
+ long reachableNanos = timeReachable();
+ String unreachableMillis =
+ String. format("%,.3f", ((double) unreachableNanos) / 1_000_000);
+ String reachableMillis =
+ String. format("%,.3f", ((double) reachableNanos) / 1_000_000);
+ if (PRINT_TIMES) {
+ System.out.println(
+ "Max time for WeakReference.get (unreachable): " + unreachableMillis);
+ System.out.println(
+ "Max time for WeakReference.get (reachable): " + reachableMillis);
+ }
+ // Only report extremely egregious pauses to avoid spurious failures.
+ if (unreachableNanos > 10_000_000_000L) {
+ System.out.println("WeakReference.get (unreachable) time unreasonably long");
+ }
+ if (reachableNanos > 10_000_000_000L) {
+ System.out.println("WeakReference.get (reachable) time unreasonably long");
+ }
+ }
+
+ /**
+ * Allocate and GC a lot, while keeping significant amounts of finalizer and
+ * SoftReference-reachable memory around.
+ */
+ static Runnable allocFinalizable = new Runnable() {
+ public void run() {
+ // Allocate and drop some finalizable objects that take a long time
+ // to mark. Designed to be hard to optimize away. Each of these objects will
+ // build a new one in its finalizer before really going away.
+ ArrayList<SoftReference<CBT>> softRefs = new ArrayList<>(N_SOFTREFS);
+ for (int i = 0; i < N_SOFTREFS; ++i) {
+ // These should not normally get reclaimed, since we shouldn't run out of
+ // memory. They do increase tracing time.
+ softRefs.add(new SoftReference(CBT.make(TREE_HEIGHT)));
+ }
+ for (int i = 0; i < N_RESURRECTING_OBJECTS; ++i) {
+ ResurrectingObject.allocOne();
+ }
+ BigInteger counter = BigInteger.ZERO;
+ for (int i = 1; !pleaseStop; ++i) {
+ // Allocate a lot of short-lived objects, using BigIntegers to minimize the chance
+ // of the allocation getting optimized out. This makes things slightly more
+ // realistic, since not all objects will be finalizer reachable.
+ for (int j = 0; j < N_PLAIN_OBJECTS / 2; ++j) {
+ counter = counter.add(BigInteger.TEN);
+ }
+ // Look at counter to reduce chance of optimizing out the allocation.
+ if (counter.longValue() % 10 != 0) {
+ System.out.println("Bad allocFinalizable counter value: " + counter);
+ }
+ // Explicitly collect here, mostly to prevent heap growth. Otherwise we get
+ // ahead of the GC and eventually block on it.
+ Runtime.getRuntime().gc();
+ if (PRINT_TIMES && i % 100 == 0) {
+ System.out.println("Collected " + i + " times");
+ }
+ }
+ // To be safe, access softRefs.
+ final CBT sample = softRefs.get(N_SOFTREFS / 2).get();
+ if (sample != null) {
+ sample.check(TREE_HEIGHT, 47 /* some path descriptor */);
+ }
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ Main theTest = new Main();
+ Thread allocThread = null;
+ if (BACKGROUND_GC_THREAD) {
+ allocThread = new Thread(allocFinalizable);
+ allocThread.setDaemon(true); // Terminate if main thread dies.
+ allocThread.start();
+ }
+ theTest.runTest();
+ if (BACKGROUND_GC_THREAD) {
+ pleaseStop = true;
+ allocThread.join();
+ }
+ System.out.println("Finished");
+ }
+}
diff --git a/test/2044-get-stack-traces/Android.bp b/test/2044-get-stack-traces/Android.bp
new file mode 100644
index 0000000..79aea7c
--- /dev/null
+++ b/test/2044-get-stack-traces/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2044-get-stack-traces`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2044-get-stack-traces",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2044-get-stack-traces-expected-stdout",
+ ":art-run-test-2044-get-stack-traces-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2044-get-stack-traces-expected-stdout",
+ out: ["art-run-test-2044-get-stack-traces-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2044-get-stack-traces-expected-stderr",
+ out: ["art-run-test-2044-get-stack-traces-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2044-get-stack-traces/expected-stderr.txt b/test/2044-get-stack-traces/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2044-get-stack-traces/expected-stderr.txt
diff --git a/test/2044-get-stack-traces/expected-stdout.txt b/test/2044-get-stack-traces/expected-stdout.txt
new file mode 100644
index 0000000..d396083
--- /dev/null
+++ b/test/2044-get-stack-traces/expected-stdout.txt
@@ -0,0 +1,8 @@
+Starting
+Starting helper
+Starting helper
+Starting helper
+Starting helper
+Starting helper
+Finished worker stack traces
+Finished
diff --git a/test/2044-get-stack-traces/info.txt b/test/2044-get-stack-traces/info.txt
new file mode 100644
index 0000000..f9cac7a
--- /dev/null
+++ b/test/2044-get-stack-traces/info.txt
@@ -0,0 +1,4 @@
+Tests multiple simultaneous calls to Thread.getStackTrace()
+
+This is a stress test for code under suspicion in the context of b/234542166
+and others.
diff --git a/test/2044-get-stack-traces/src/Main.java b/test/2044-get-stack-traces/src/Main.java
new file mode 100644
index 0000000..7840389
--- /dev/null
+++ b/test/2044-get-stack-traces/src/Main.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2022 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.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.SoftReference;
+import java.math.BigInteger;
+import java.util.ArrayList;
+
+/**
+ * We construct a main thread and worker threads, each retrieving stack traces
+ * from the other. Since there are multiple workers, we may get a large number
+ * of simultaneous stack trace attempts.
+ */
+public class Main {
+ static final int NUM_THREADS = 5;
+ static Thread mainThread;
+ static volatile boolean pleaseStop = false;
+
+ private static void getTrace(Thread t) {
+ StackTraceElement trace[] = t.getStackTrace();
+ if (!pleaseStop && (trace.length < 1 || trace.length > 20)) {
+ // If called from traceGetter, we were started by the main thread, and it was still
+ // running after the trace, so the main thread should have at least one frame on
+ // the stack. If called by main(), we waited for all the traceGetters to start,
+ // and didn't yet allow them to stop, so the same should be true.
+ System.out.println("Stack trace for " + t.getName() + " has size " + trace.length);
+ for (StackTraceElement e : trace) {
+ System.out.println(e.toString());
+ }
+ }
+ }
+
+ /**
+ * Repeatedly get and minimally check stack trace of main thread.
+ */
+ static Runnable traceGetter = new Runnable() {
+ public void run() {
+ System.out.println("Starting helper");
+ while (!pleaseStop) {
+ getTrace(mainThread);
+ }
+ }
+ };
+
+ public static void main(String[] args) throws Exception {
+ System.out.println("Starting");
+ Thread[] t = new Thread[NUM_THREADS];
+ mainThread = Thread.currentThread();
+ for (int i = 0; i < NUM_THREADS; ++i) {
+ t[i] = new Thread(traceGetter);
+ t[i].start();
+ }
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpectedly interrupted");
+ }
+ for (int i = 0; i < NUM_THREADS; ++i) {
+ getTrace(t[i]);
+ }
+ System.out.println("Finished worker stack traces");
+ long now = System.currentTimeMillis();
+ while (System.currentTimeMillis() - now < 2000) {
+ try {
+ Thread.sleep(1);
+ } catch (InterruptedException e) {
+ System.out.println("Unexpectedly interrupted");
+ }
+ }
+ pleaseStop = true;
+ System.out.println("Finished");
+ }
+}
diff --git a/test/2233-checker-remove-loop-suspend-check/Android.bp b/test/2233-checker-remove-loop-suspend-check/Android.bp
new file mode 100644
index 0000000..f581ee3
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/Android.bp
@@ -0,0 +1,43 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2233-checker-remove-loop-suspend-check`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2233-checker-remove-loop-suspend-check",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-2233-checker-remove-loop-suspend-check-expected-stdout",
+ ":art-run-test-2233-checker-remove-loop-suspend-check-expected-stderr",
+ ],
+ // Include the Java source files in the test's artifacts, to make Checker assertions
+ // available to the TradeFed test runner.
+ include_srcs: true,
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-2233-checker-remove-loop-suspend-check-expected-stdout",
+ out: ["art-run-test-2233-checker-remove-loop-suspend-check-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-2233-checker-remove-loop-suspend-check-expected-stderr",
+ out: ["art-run-test-2233-checker-remove-loop-suspend-check-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2233-checker-remove-loop-suspend-check/expected-stderr.txt b/test/2233-checker-remove-loop-suspend-check/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/expected-stderr.txt
diff --git a/test/2233-checker-remove-loop-suspend-check/expected-stdout.txt b/test/2233-checker-remove-loop-suspend-check/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/expected-stdout.txt
diff --git a/test/2233-checker-remove-loop-suspend-check/info.txt b/test/2233-checker-remove-loop-suspend-check/info.txt
new file mode 100644
index 0000000..fe8d8eb
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/info.txt
@@ -0,0 +1 @@
+Test to check the removal of SuspendCheck for finite simple plain loops.
diff --git a/test/2233-checker-remove-loop-suspend-check/src/Main.java b/test/2233-checker-remove-loop-suspend-check/src/Main.java
new file mode 100644
index 0000000..bea67e7
--- /dev/null
+++ b/test/2233-checker-remove-loop-suspend-check/src/Main.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+
+ static final int ITERATIONS = 16;
+
+ // Test 1: This test checks whether the SuspendCheck is removed from the
+ // header.
+
+ /// CHECK-START-ARM64: void Main.$noinline$testRemoveSuspendCheck(int[]) disassembly (after)
+ /// CHECK: SuspendCheck loop:<<LoopId:B\d+>>
+ /// CHECK-NEXT: dex_pc:{{.*}}
+ /// CHECK: Goto loop:<<LoopId>>
+ /// CHECK-NEXT: b
+ /// CHECK-NOT: SuspendCheckSlowPathARM64
+
+ public static void $noinline$testRemoveSuspendCheck(int[] a) {
+ for (int i = 0; i < ITERATIONS; i++) {
+ a[i++] = i;
+ }
+ }
+
+ // Test 2: This test checks that the SuspendCheck is not removed from the
+ // header because it contains a call to another function.
+
+ /// CHECK-START-ARM64: void Main.testRemoveSuspendCheckWithCall(int[]) disassembly (after)
+ /// CHECK: SuspendCheck loop:<<LoopId:B\d+>>
+ /// CHECK: Goto loop:<<LoopId>>
+ /// CHECK-NEXT: ldr
+ /// CHECK: SuspendCheckSlowPathARM64
+ /// CHECK: SuspendCheckSlowPathARM64
+
+ public static void testRemoveSuspendCheckWithCall(int[] a) {
+ for (int i = 0; i < ITERATIONS; i++) {
+ a[i++] = i;
+ $noinline$testRemoveSuspendCheck(a);
+ }
+ }
+
+ // Test 3: This test checks that the SuspendCheck is not removed from the
+ // header because INSTR_COUNT * TRIP_COUNT exceeds the defined heuristic.
+
+ /// CHECK-START-ARM64: void Main.testRemoveSuspendCheckAboveHeuristic(int[]) disassembly (after)
+ /// CHECK: SuspendCheck loop:<<LoopId:B\d+>>
+ /// CHECK: Goto loop:<<LoopId>>
+ /// CHECK-NEXT: ldr
+ /// CHECK: SuspendCheckSlowPathARM64
+
+ public static void testRemoveSuspendCheckAboveHeuristic(int[] a) {
+ for (int i = 0; i < ITERATIONS * 6; i++) {
+ a[i++] = i;
+ }
+ }
+
+ // Test 4: This test checks that the SuspendCheck is not removed from the
+ // header because the trip count is not known at compile time.
+
+ /// CHECK-START-ARM64: void Main.testRemoveSuspendCheckUnknownCount(int[], int) disassembly (after)
+ /// CHECK: SuspendCheck loop:<<LoopId:B\d+>>
+ /// CHECK: Goto loop:<<LoopId>>
+ /// CHECK-NEXT: ldr
+ /// CHECK: SuspendCheckSlowPathARM64
+
+ public static void testRemoveSuspendCheckUnknownCount(int[] a, int n) {
+ for (int i = 0; i < n; i++) {
+ a[i++] = i;
+ }
+ }
+
+ public static void main(String[] args) {
+ int[] a = new int[100];
+ $noinline$testRemoveSuspendCheck(a);
+ testRemoveSuspendCheckWithCall(a);
+ testRemoveSuspendCheckAboveHeuristic(a);
+ testRemoveSuspendCheckUnknownCount(a, 4);
+ }
+}
diff --git a/test/807-method-handle-and-mr/build b/test/2239-varhandle-perf/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/2239-varhandle-perf/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/2239-varhandle-perf/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/2239-varhandle-perf/build b/test/2239-varhandle-perf/generate-sources
similarity index 94%
rename from test/2239-varhandle-perf/build
rename to test/2239-varhandle-perf/generate-sources
index 115a0fb..310ad06 100755
--- a/test/2239-varhandle-perf/build
+++ b/test/2239-varhandle-perf/generate-sources
@@ -28,5 +28,3 @@
# Generate tests and Main that covers both the generated tests and manual tests
python3 ./util-src/generate_java.py "${GENERATED_SRC}"
-
-./default-build "$@" --experimental var-handles
diff --git a/test/303-verification-stress/build b/test/303-verification-stress/generate-sources
old mode 100644
new mode 100755
similarity index 96%
rename from test/303-verification-stress/build
rename to test/303-verification-stress/generate-sources
index 6e4a1d6..2a2cdb6
--- a/test/303-verification-stress/build
+++ b/test/303-verification-stress/generate-sources
@@ -19,5 +19,3 @@
# Write out a bunch of source files.
./classes-gen
-
-./default-build "$@"
diff --git a/test/370-dex-v37/build b/test/370-dex-v37/build
deleted file mode 100755
index f472428..0000000
--- a/test/370-dex-v37/build
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@"
-
-if [[ $@ != *"--jvm"* ]]; then
- # Change the generated dex file to have a v36 magic number if it is version 35
- if test -f classes.dex && head -c 7 classes.dex | grep -q 035; then
- # place ascii value '037' into the classes.dex file starting at byte 4.
- printf '037' | dd status=none conv=notrunc of=classes.dex bs=1 seek=4 count=3
- rm -f $TEST_NAME.jar
- zip $TEST_NAME.jar classes.dex
- fi
-fi
diff --git a/test/370-dex-v37/build.py b/test/370-dex-v37/build.py
new file mode 100644
index 0000000..5ae5a03
--- /dev/null
+++ b/test/370-dex-v37/build.py
@@ -0,0 +1,29 @@
+#
+# Copyright (C) 2022 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.
+
+from art_build_rules import build_run_test
+import subprocess, os
+
+build_run_test()
+
+if os.environ["BUILD_MODE"] != "jvm":
+ # Change the generated dex file to have a v37 magic number if it is version 35
+ with open("classes.dex", "rb+") as f:
+ if f.read(8) == b'dex\n035\x00':
+ f.seek(0)
+ f.write(b'dex\n037\x00')
+ os.remove("370-dex-v37.jar")
+ subprocess.run([os.environ["SOONG_ZIP"], "-o",
+ "370-dex-v37.jar", "-f", "classes.dex"], check=True)
diff --git a/test/442-checker-constant-folding/src/Main.java b/test/442-checker-constant-folding/src/Main.java
index 1bdf7b5..168ebbf 100644
--- a/test/442-checker-constant-folding/src/Main.java
+++ b/test/442-checker-constant-folding/src/Main.java
@@ -1577,6 +1577,282 @@
return (double) imm;
}
+ /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding (before)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Mul
+
+ /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding (before)
+ /// CHECK-NOT: IntConstant 6
+
+ /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding (after)
+ /// CHECK-NOT: Add
+
+ /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding (after)
+ /// CHECK-NOT: Mul
+
+ /// CHECK-START: int Main.$inline$SpecialCaseForZeroInt(int) constant_folding (after)
+ /// CHECK-DAG: <<Const:i\d+>> IntConstant 6
+ /// CHECK-DAG: Return [<<Const>>]
+ private static int $inline$SpecialCaseForZeroInt(int value) {
+ if (value == 0) {
+ return (value + 2) * 3;
+ }
+ return value;
+ }
+
+ /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding (before)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Mul
+
+ /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding (before)
+ /// CHECK-NOT: LongConstant 6
+
+ /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding (after)
+ /// CHECK-NOT: Add
+
+ /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding (after)
+ /// CHECK-NOT: Mul
+
+ /// CHECK-START: long Main.$inline$SpecialCaseForZeroLong(long) constant_folding (after)
+ /// CHECK-DAG: <<Const:j\d+>> LongConstant 6
+ /// CHECK-DAG: Return [<<Const>>]
+ private static long $inline$SpecialCaseForZeroLong(long value) {
+ if (value == 0L) {
+ return (value + 2) * 3;
+ }
+ return value;
+ }
+
+ /// CHECK-START: float Main.$noinline$SpecialCaseForZeroFloat(float) constant_folding (before)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Mul
+
+ /// CHECK-START: float Main.$noinline$SpecialCaseForZeroFloat(float) constant_folding (after)
+ /// CHECK-NOT: FloatConstant 6
+
+ /// CHECK-START: float Main.$noinline$SpecialCaseForZeroFloat(float) constant_folding (after)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Mul
+ private static float $noinline$SpecialCaseForZeroFloat(float value) {
+ if (value == 0F) {
+ return (value + 2F) * 3F;
+ }
+ return value;
+ }
+
+ /// CHECK-START: double Main.$noinline$SpecialCaseForZeroDouble(double) constant_folding (before)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Mul
+
+ /// CHECK-START: double Main.$noinline$SpecialCaseForZeroDouble(double) constant_folding (after)
+ /// CHECK-NOT: DoubleConstant 6
+
+ /// CHECK-START: double Main.$noinline$SpecialCaseForZeroDouble(double) constant_folding (after)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Mul
+ private static double $noinline$SpecialCaseForZeroDouble(double value) {
+ if (value == 0D) {
+ return (value + 2D) * 3D;
+ }
+ return value;
+ }
+
+ // Note that we have Add instead of sub since internally we do `Add(value, -1)`.
+ /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding (before)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding (after)
+ /// CHECK-NOT: Add
+
+ /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding (after)
+ /// CHECK-NOT: Div
+
+ /// CHECK-START: int Main.$noinline$NotEqualsPropagationInt(int) constant_folding (after)
+ /// CHECK-DAG: <<Const:i\d+>> IntConstant 1
+ /// CHECK-DAG: Return [<<Const>>]
+ private static int $noinline$NotEqualsPropagationInt(int value) {
+ if (value != 3) {
+ return value;
+ } else {
+ return (value - 1) / 2;
+ }
+ }
+
+ /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding (before)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding (after)
+ /// CHECK-NOT: Sub
+
+ /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding (after)
+ /// CHECK-NOT: Div
+
+ /// CHECK-START: long Main.$noinline$NotEqualsPropagationLong(long) constant_folding (after)
+ /// CHECK-DAG: <<Const:j\d+>> LongConstant 1
+ /// CHECK-DAG: Return [<<Const>>]
+ private static long $noinline$NotEqualsPropagationLong(long value) {
+ if (value != 3L) {
+ return value;
+ } else {
+ return (value - 1L) / 2L;
+ }
+ }
+
+ /// CHECK-START: float Main.$noinline$NotEqualsPropagationFloat(float) constant_folding (before)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: float Main.$noinline$NotEqualsPropagationFloat(float) constant_folding (after)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+ private static float $noinline$NotEqualsPropagationFloat(float value) {
+ if (value != 3F) {
+ return value;
+ } else {
+ return (value - 1F) / 2F;
+ }
+ }
+
+ /// CHECK-START: double Main.$noinline$NotEqualsPropagationDouble(double) constant_folding (before)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: double Main.$noinline$NotEqualsPropagationDouble(double) constant_folding (after)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+ private static double $noinline$NotEqualsPropagationDouble(double value) {
+ if (value != 3D) {
+ return value;
+ } else {
+ return (value - 1D) / 2D;
+ }
+ }
+
+ /// CHECK-START: int Main.$noinline$InlineCaleeWithSpecialCaseForZeroInt(int) inliner (after)
+ /// CHECK-NOT: Add
+
+ /// CHECK-START: int Main.$noinline$InlineCaleeWithSpecialCaseForZeroInt(int) inliner (after)
+ /// CHECK-NOT: Mul
+
+ /// CHECK-START: int Main.$noinline$InlineCaleeWithSpecialCaseForZeroInt(int) inliner (after)
+ /// CHECK-DAG: <<Const:i\d+>> IntConstant 6
+ /// CHECK-DAG: Return [<<Const>>]
+ private static int $noinline$InlineCaleeWithSpecialCaseForZeroInt(int value) {
+ if (value == 0) {
+ return $inline$SpecialCaseForZeroInt(value);
+ }
+ return value;
+ }
+
+ /// CHECK-START: long Main.$noinline$InlineCaleeWithSpecialCaseForZeroLong(long) inliner (after)
+ /// CHECK-NOT: Add
+
+ /// CHECK-START: long Main.$noinline$InlineCaleeWithSpecialCaseForZeroLong(long) inliner (after)
+ /// CHECK-NOT: Mul
+
+ /// CHECK-START: long Main.$noinline$InlineCaleeWithSpecialCaseForZeroLong(long) inliner (after)
+ /// CHECK-DAG: <<Const:j\d+>> LongConstant 6
+ /// CHECK-DAG: Return [<<Const>>]
+ private static long $noinline$InlineCaleeWithSpecialCaseForZeroLong(long value) {
+ if (value == 0L) {
+ return $inline$SpecialCaseForZeroLong(value);
+ }
+ return value;
+ }
+
+ // Check that don't propagate the value == 3 on `if not true` branch, as the `if true` branch also
+ // flows into the same block.
+ /// CHECK-START: int Main.$noinline$NotEqualsImplicitElseInt(int) constant_folding (before)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: int Main.$noinline$NotEqualsImplicitElseInt(int) constant_folding (after)
+ /// CHECK-DAG: Add
+ /// CHECK-DAG: Div
+ private static int $noinline$NotEqualsImplicitElseInt(int value) {
+ if (value != 3) {
+ value++;
+ }
+ return (value - 1) / 2;
+ }
+
+ /// CHECK-START: long Main.$noinline$NotEqualsImplicitElseLong(long) constant_folding (before)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: long Main.$noinline$NotEqualsImplicitElseLong(long) constant_folding (after)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+ private static long $noinline$NotEqualsImplicitElseLong(long value) {
+ if (value != 3L) {
+ value += 1L;
+ }
+ return (value - 1L) / 2L;
+ }
+
+ /// CHECK-START: float Main.$noinline$NotEqualsImplicitElseFloat(float) constant_folding (before)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: float Main.$noinline$NotEqualsImplicitElseFloat(float) constant_folding (after)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+ private static float $noinline$NotEqualsImplicitElseFloat(float value) {
+ if (value != 3F) {
+ value += 1F;
+ }
+ return (value - 1F) / 2F;
+ }
+
+ /// CHECK-START: double Main.$noinline$NotEqualsImplicitElseDouble(double) constant_folding (before)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+
+ /// CHECK-START: double Main.$noinline$NotEqualsImplicitElseDouble(double) constant_folding (after)
+ /// CHECK-DAG: Sub
+ /// CHECK-DAG: Div
+ private static double $noinline$NotEqualsImplicitElseDouble(double value) {
+ if (value != 3D) {
+ value += 1D;
+ }
+ return (value - 1D) / 2D;
+ }
+
+ // By propagating the boolean we can elimniate some equality comparisons as we already know their
+ // result. In turn, we also enable DeadCodeElimination to eliminate more code.
+ /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean) constant_folding (before)
+ /// CHECK-DAG: Equal
+ /// CHECK-DAG: Equal
+ /// CHECK-DAG: Equal
+
+ /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean) constant_folding (after)
+ /// CHECK: Equal
+ /// CHECK-NOT: Equal
+
+ /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean) dead_code_elimination$initial (before)
+ /// CHECK-DAG: IntConstant 1
+ /// CHECK-DAG: IntConstant 2
+ /// CHECK-DAG: IntConstant 3
+ /// CHECK-DAG: IntConstant 4
+
+ /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean) dead_code_elimination$initial (after)
+ /// CHECK-DAG: IntConstant 1
+ /// CHECK-DAG: IntConstant 4
+
+ /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean) dead_code_elimination$initial (after)
+ /// CHECK-NOT: IntConstant 2
+
+ /// CHECK-START: int Main.$noinline$PropagatingParameterValue(boolean) dead_code_elimination$initial (after)
+ /// CHECK-NOT: IntConstant 3
+ private static int $noinline$PropagatingParameterValue(boolean value) {
+ if (value) {
+ return value ? 1 : 2;
+ } else {
+ return value ? 3 : 4;
+ }
+ }
public static void main(String[] args) throws Exception {
assertIntEquals(-42, IntNegation());
@@ -1708,6 +1984,51 @@
assertDoubleEquals(33, ReturnDouble33());
assertDoubleEquals(34, ReturnDouble34());
assertDoubleEquals(99.25, ReturnDouble99P25());
+
+ // Tests for propagating known values due to if clauses.
+
+ // Propagating within the same method. These are marked $inline$ since we used them in
+ // `InlineCaleeWithSpecialCaseForZeroInt`.
+ assertIntEquals(6, $inline$SpecialCaseForZeroInt(0));
+ assertIntEquals(3, $inline$SpecialCaseForZeroInt(3));
+ assertLongEquals(6L, $inline$SpecialCaseForZeroLong(0L));
+ assertLongEquals(3L, $inline$SpecialCaseForZeroLong(3L));
+ // Floats and doubles we do not optimize (here and below). These methods are here to guarantee
+ // that.
+ assertFloatEquals(6F, $noinline$SpecialCaseForZeroFloat(0F));
+ assertFloatEquals(3F, $noinline$SpecialCaseForZeroFloat(3F));
+ assertDoubleEquals(6D, $noinline$SpecialCaseForZeroDouble(0D));
+ assertDoubleEquals(3D, $noinline$SpecialCaseForZeroDouble(3D));
+
+ // Propagating within the same method, with not equals
+ assertIntEquals(0, $noinline$NotEqualsPropagationInt(0));
+ assertIntEquals(1, $noinline$NotEqualsPropagationInt(3));
+ assertLongEquals(0L, $noinline$NotEqualsPropagationLong(0L));
+ assertLongEquals(1L, $noinline$NotEqualsPropagationLong(3L));
+ assertFloatEquals(0F, $noinline$NotEqualsPropagationFloat(0F));
+ assertFloatEquals(1F, $noinline$NotEqualsPropagationFloat(3F));
+ assertDoubleEquals(0D, $noinline$NotEqualsPropagationDouble(0D));
+ assertDoubleEquals(1D, $noinline$NotEqualsPropagationDouble(3D));
+
+ // Propagating so that the inliner can use it.
+ assertIntEquals(6, $noinline$InlineCaleeWithSpecialCaseForZeroInt(0));
+ assertIntEquals(3, $noinline$InlineCaleeWithSpecialCaseForZeroInt(3));
+ assertLongEquals(6L, $noinline$InlineCaleeWithSpecialCaseForZeroLong(0L));
+ assertLongEquals(3L, $noinline$InlineCaleeWithSpecialCaseForZeroLong(3L));
+
+ // Propagating within the same method, with not equals
+ assertIntEquals(0, $noinline$NotEqualsImplicitElseInt(0));
+ assertIntEquals(1, $noinline$NotEqualsImplicitElseInt(3));
+ assertLongEquals(0L, $noinline$NotEqualsImplicitElseLong(0L));
+ assertLongEquals(1L, $noinline$NotEqualsImplicitElseLong(3L));
+ assertFloatEquals(0F, $noinline$NotEqualsImplicitElseFloat(0F));
+ assertFloatEquals(1F, $noinline$NotEqualsImplicitElseFloat(3F));
+ assertDoubleEquals(0D, $noinline$NotEqualsImplicitElseDouble(0D));
+ assertDoubleEquals(1D, $noinline$NotEqualsImplicitElseDouble(3D));
+
+ // Propagating parameters.
+ assertIntEquals(1, $noinline$PropagatingParameterValue(true));
+ assertIntEquals(4, $noinline$PropagatingParameterValue(false));
}
Main() throws ClassNotFoundException {
diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java
index 1144366..fbe3586 100644
--- a/test/449-checker-bce/src/Main.java
+++ b/test/449-checker-bce/src/Main.java
@@ -1125,7 +1125,7 @@
/// CHECK-DAG: <<Len:i\d+>> ArrayLength [<<Nul>>] loop:none
/// CHECK-DAG: Equal [<<Len>>,<<Val>>] loop:none
/// CHECK-DAG: <<Idx:i\d+>> Phi loop:<<Loop:B\d+>>
- /// CHECK-DAG: BoundsCheck [<<Idx>>,<<Len>>] loop:<<Loop>>
+ /// CHECK-DAG: BoundsCheck [<<Idx>>,<<Val>>] loop:<<Loop>>
//
/// CHECK-START: void Main.lengthAlias4(int[]) BCE (after)
/// CHECK-NOT: BoundsCheck
diff --git a/test/485-checker-dce-loop-update/smali/TestCase.smali b/test/485-checker-dce-loop-update/smali/TestCase.smali
index 5290bad..3e7bca9 100644
--- a/test/485-checker-dce-loop-update/smali/TestCase.smali
+++ b/test/485-checker-dce-loop-update/smali/TestCase.smali
@@ -224,7 +224,7 @@
## CHECK-DAG: If [<<ArgY>>] loop:<<HeaderY>>
#
# ### Inner loop ###
-## CHECK-DAG: <<PhiZ2:i\d+>> Phi [<<PhiZ1>>,<<XorZ>>] loop:<<HeaderZ:B\d+>>
+## CHECK-DAG: <<PhiZ2:i\d+>> Phi [<<PhiZ1>>,<<Cst0>>] loop:<<HeaderZ:B\d+>>
## CHECK-DAG: <<XorZ>> Xor [<<PhiZ2>>,<<Cst1>>] loop:<<HeaderZ>>
## CHECK-DAG: <<CondZ:z\d+>> Equal [<<XorZ>>,<<Cst0>>] loop:<<HeaderZ>>
## CHECK-DAG: If [<<CondZ>>] loop:<<HeaderZ>>
@@ -246,8 +246,8 @@
## CHECK-DAG: <<Add7>> Add [<<PhiX>>,<<Cst7>>] loop:<<HeaderY>>
#
# ### Inner loop ###
-## CHECK-DAG: <<PhiZ:i\d+>> Phi [<<ArgZ>>,<<XorZ:i\d+>>] loop:<<HeaderZ:B\d+>>
-## CHECK-DAG: <<XorZ>> Xor [<<PhiZ>>,<<Cst1>>] loop:<<HeaderZ>>
+## CHECK-DAG: <<PhiZ:i\d+>> Phi [<<ArgZ>>,<<Cst0>>] loop:<<HeaderZ:B\d+>>
+## CHECK-DAG: <<XorZ:i\d+>> Xor [<<PhiZ>>,<<Cst1>>] loop:<<HeaderZ>>
## CHECK-DAG: <<CondZ:z\d+>> Equal [<<XorZ>>,<<Cst0>>] loop:<<HeaderZ>>
## CHECK-DAG: If [<<CondZ>>] loop:<<HeaderZ>>
#
diff --git a/test/543-checker-dce-trycatch/smali/TestCase.smali b/test/543-checker-dce-trycatch/smali/TestCase.smali
index 7ad9ba8..15eaabb 100644
--- a/test/543-checker-dce-trycatch/smali/TestCase.smali
+++ b/test/543-checker-dce-trycatch/smali/TestCase.smali
@@ -206,6 +206,7 @@
## CHECK-START: int TestCase.testCatchPhiInputs_DefinedInTryBlock(int, int, int, int) dead_code_elimination$after_inlining (before)
## CHECK-DAG: <<Arg0:i\d+>> ParameterValue
## CHECK-DAG: <<Arg1:i\d+>> ParameterValue
+## CHECK-DAG: <<Const0x0:i\d+>> IntConstant 0
## CHECK-DAG: <<Const0xa:i\d+>> IntConstant 10
## CHECK-DAG: <<Const0xb:i\d+>> IntConstant 11
## CHECK-DAG: <<Const0xc:i\d+>> IntConstant 12
@@ -215,7 +216,7 @@
## CHECK-DAG: <<Const0x10:i\d+>> IntConstant 16
## CHECK-DAG: <<Const0x11:i\d+>> IntConstant 17
## CHECK-DAG: <<Add:i\d+>> Add [<<Arg0>>,<<Arg1>>]
-## CHECK-DAG: <<Phi:i\d+>> Phi [<<Add>>,<<Const0xf>>] reg:3 is_catch_phi:false
+## CHECK-DAG: <<Phi:i\d+>> Phi [<<Const0x0>>,<<Const0xf>>] reg:3 is_catch_phi:false
## CHECK-DAG: Phi [<<Const0xa>>,<<Const0xb>>,<<Const0xd>>] reg:1 is_catch_phi:true
## CHECK-DAG: Phi [<<Add>>,<<Const0xc>>,<<Const0xe>>] reg:2 is_catch_phi:true
## CHECK-DAG: Phi [<<Phi>>,<<Const0x10>>,<<Const0x11>>] reg:3 is_catch_phi:true
@@ -248,7 +249,8 @@
if-eqz v3, :define_phi
const v3, 0xf
:define_phi
- # v3 = Phi [Add, 0xf] # dead catch phi input, defined in the dead block (HPhi)
+ # v3 = Phi [Add, 0xf] # dead catch phi input, defined in the dead block (HPhi).
+ # Note that the Add has to be equal to 0 since we do `if-eqz v3`
div-int/2addr p0, v2
:else
diff --git a/test/543-env-long-ref/env_long_ref.cc b/test/543-env-long-ref/env_long_ref.cc
index 1c30d46..7084714 100644
--- a/test/543-env-long-ref/env_long_ref.cc
+++ b/test/543-env-long-ref/env_long_ref.cc
@@ -36,7 +36,8 @@
found = true;
// For optimized non-debuggable code do not expect dex register info to be present.
if (stack_visitor->GetCurrentShadowFrame() == nullptr &&
- !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetCurrentQuickFramePc())) {
+ !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetOuterMethod(),
+ stack_visitor->GetCurrentQuickFramePc())) {
return true;
}
uint32_t stack_value = 0;
diff --git a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali
index a30a11a..493567c 100644
--- a/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali
+++ b/test/559-checker-irreducible-loop/smali/IrreducibleLoop.smali
@@ -140,24 +140,23 @@
# other_loop_entry
# i1 = phi(p0, i0)
#
-## CHECK-START: int IrreducibleLoop.liveness(int) liveness (after)
+## CHECK-START: int IrreducibleLoop.liveness(int, int) liveness (after)
## CHECK-DAG: <<Arg:i\d+>> ParameterValue liveness:<<ArgLiv:\d+>> ranges:{[<<ArgLiv>>,<<ArgLoopPhiUse:\d+>>)}
## CHECK-DAG: <<LoopPhi:i\d+>> Phi [<<Arg>>,<<PhiInLoop:i\d+>>] liveness:<<ArgLoopPhiUse>> ranges:{[<<ArgLoopPhiUse>>,<<PhiInLoopUse:\d+>>)}
## CHECK-DAG: <<PhiInLoop>> Phi [<<Arg>>,<<LoopPhi>>] liveness:<<PhiInLoopUse>> ranges:{[<<PhiInLoopUse>>,<<BackEdgeLifetimeEnd:\d+>>)}
## CHECK: Return liveness:<<ReturnLiveness:\d+>>
## CHECK-EVAL: <<ReturnLiveness>> == <<BackEdgeLifetimeEnd>> + 2
-.method public static liveness(I)I
+.method public static liveness(II)I
.registers 2
- const/16 v0, 42
- if-eq p0, v0, :other_loop_entry
+ if-eq p0, p1, :other_loop_entry
:loop_entry
- add-int v0, v0, p0
- if-ne v1, v0, :exit
+ add-int p1, p1, p0
+ if-ne v0, p1, :exit
:other_loop_entry
- add-int v0, v0, v0
+ add-int p1, p1, p1
goto :loop_entry
:exit
- return v0
+ return p1
.end method
# Check that we don't GVN across irreducible loops:
diff --git a/test/559-checker-irreducible-loop/src/Main.java b/test/559-checker-irreducible-loop/src/Main.java
index 97165ec..b22e9b8 100644
--- a/test/559-checker-irreducible-loop/src/Main.java
+++ b/test/559-checker-irreducible-loop/src/Main.java
@@ -38,8 +38,8 @@
}
{
- Method m = c.getMethod("liveness", int.class);
- Object[] arguments = { 42 };
+ Method m = c.getMethod("liveness", int.class, int.class);
+ Object[] arguments = { 42, 42 };
System.out.println(m.invoke(null, arguments));
}
diff --git a/test/563-checker-fakestring/smali/TestCase.smali b/test/563-checker-fakestring/smali/TestCase.smali
index 4721eca..c6561d5 100644
--- a/test/563-checker-fakestring/smali/TestCase.smali
+++ b/test/563-checker-fakestring/smali/TestCase.smali
@@ -310,7 +310,7 @@
## CHECK-NOT: NewInstance
## CHECK-DAG: <<Invoke1:l\d+>> InvokeStaticOrDirect method_name:java.lang.String.<init>
## CHECK-DAG: <<Invoke2:l\d+>> InvokeStaticOrDirect method_name:java.lang.String.<init>
-## CHECK-DAG: <<Phi:l\d+>> Phi [<<Invoke2>>,<<Invoke1>>]
+## CHECK-DAG: <<Phi:l\d+>> Phi [<<Invoke1>>,<<Invoke2>>]
## CHECK-DAG: Return [<<Phi>>]
.method public static loopAndStringInitAndPhi([BZ)Ljava/lang/String;
.registers 4
diff --git a/test/563-checker-fakestring/src/Main.java b/test/563-checker-fakestring/src/Main.java
index 7e775b3..d45e803 100644
--- a/test/563-checker-fakestring/src/Main.java
+++ b/test/563-checker-fakestring/src/Main.java
@@ -34,6 +34,11 @@
}
}
+ // Create an empty int[] to force loading the int[] class before compiling
+ // TestCase.deoptimizeNewInstance.
+ // This makes sure the compiler can properly type int[] and not bail.
+ static int[] emptyArray = new int[0];
+
public static void main(String[] args) throws Throwable {
System.loadLibrary(args[0]);
Class<?> c = Class.forName("TestCase");
diff --git a/test/564-checker-inline-loop/src/Main.java b/test/564-checker-inline-loop/src/Main.java
index 6929913..41eca35 100644
--- a/test/564-checker-inline-loop/src/Main.java
+++ b/test/564-checker-inline-loop/src/Main.java
@@ -21,9 +21,6 @@
/// CHECK-DAG: Return [<<Invoke>>]
/// CHECK-START: int Main.inlineLoop() inliner (after)
- /// CHECK-NOT: InvokeStaticOrDirect
-
- /// CHECK-START: int Main.inlineLoop() inliner (after)
/// CHECK-DAG: <<Constant:i\d+>> IntConstant 42
/// CHECK-DAG: Return [<<Constant>>]
@@ -31,31 +28,31 @@
/// CHECK: Goto loop:{{B\d+}}
public static int inlineLoop() {
- return loopMethod();
+ return $inline$loopMethod();
}
/// CHECK-START: void Main.inlineWithinLoop() inliner (before)
/// CHECK: InvokeStaticOrDirect
- /// CHECK-START: void Main.inlineWithinLoop() inliner (after)
- /// CHECK-NOT: InvokeStaticOrDirect
-
/// CHECK-START: void Main.inlineWithinLoop() licm (after)
/// CHECK-DAG: Goto loop:<<OuterLoop:B\d+>> outer_loop:none
/// CHECK-DAG: Goto outer_loop:<<OuterLoop>>
public static void inlineWithinLoop() {
while (doLoop) {
- loopMethod();
+ $inline$loopMethod();
}
}
- public static int loopMethod() {
- while (doLoop) {}
+ public static int $inline$loopMethod() {
+ // We use `otherDoLoop` here so we don't propagate the knowledge that `doLoop` is true when
+ // inlining from `inlineWithinLoop`.
+ while (otherDoLoop) {}
return 42;
}
public static boolean doLoop = false;
+ public static boolean otherDoLoop = false;
public static void main(String[] args) {
inlineLoop();
diff --git a/test/565-checker-condition-liveness/src/Main.java b/test/565-checker-condition-liveness/src/Main.java
index 17a8613..350d9ad 100644
--- a/test/565-checker-condition-liveness/src/Main.java
+++ b/test/565-checker-condition-liveness/src/Main.java
@@ -33,8 +33,8 @@
/// CHECK-START-{ARM,ARM64}: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
/// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[23,25]
- /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,23,25]
- /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,23,25]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,23,25,33]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,23,25,33]
/// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[23,25]
/// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
/// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
@@ -43,9 +43,9 @@
/// CHECK-DAG: TryBoundary
/// CHECK-START-{ARM,ARM64}-DEBUGGABLE: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
- /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[11,23,25]
- /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,23,25]
- /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,23,25]
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[11,23,25,33]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,23,25,33]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,23,25,33]
/// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[23,25]
/// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
/// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
@@ -56,8 +56,8 @@
// X86 and X86_64 generate at use site the ArrayLength, meaning only the BoundsCheck will have environment uses.
/// CHECK-START-{X86,X86_64}: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
/// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[25,25]
- /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,25,25]
- /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,25,25]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,25,25,33]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,25,25,33]
/// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[25,25]
/// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
/// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
@@ -66,9 +66,9 @@
/// CHECK-DAG: TryBoundary
/// CHECK-START-{X86,X86_64}-DEBUGGABLE: void Main.testThrowIntoCatchBlock(int, java.lang.Object, int[]) liveness (after)
- /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[11,25,25]
- /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,25,25]
- /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,25,25]
+ /// CHECK-DAG: <<IntArg:i\d+>> ParameterValue env_uses:[11,25,25,33]
+ /// CHECK-DAG: <<RefArg:l\d+>> ParameterValue env_uses:[11,25,25,33]
+ /// CHECK-DAG: <<Array:l\d+>> ParameterValue env_uses:[11,25,25,33]
/// CHECK-DAG: <<Const1:i\d+>> IntConstant 1 env_uses:[25,25]
/// CHECK-DAG: SuspendCheck env:[[_,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:10
/// CHECK-DAG: NullCheck env:[[<<Const1>>,<<IntArg>>,<<RefArg>>,<<Array>>]] liveness:20
diff --git a/test/567-checker-builder-intrinsics/src/TestMinMax.java b/test/567-checker-builder-intrinsics/src/TestMinMax.java
index 0e88517..7207006 100644
--- a/test/567-checker-builder-intrinsics/src/TestMinMax.java
+++ b/test/567-checker-builder-intrinsics/src/TestMinMax.java
@@ -564,6 +564,16 @@
return x;
}
+ /// CHECK-START: int TestMinMax.minmax3(int) select_generator (after)
+ /// CHECK-DAG: <<Par:i\d+>> ParameterValue
+ /// CHECK-DAG: <<P100:i\d+>> IntConstant 100
+ /// CHECK-DAG: <<M100:i\d+>> IntConstant -100
+ /// CHECK-DAG: <<Cnd1:z\d+>> LessThanOrEqual [<<Par>>,<<P100>>]
+ /// CHECK-DAG: <<Cnd2:z\d+>> GreaterThanOrEqual [<<Par>>,<<M100>>]
+ /// CHECK-DAG: <<Sel1:i\d+>> Select [<<M100>>,<<Par>>,<<Cnd2>>]
+ /// CHECK-DAG: <<Sel2:i\d+>> Select [<<P100>>,<<Sel1>>,<<Cnd1>>]
+ /// CHECK-DAG: Return [<<Sel2>>]
+
/// CHECK-START: int TestMinMax.minmax3(int) instruction_simplifier$after_gvn (after)
/// CHECK-DAG: <<Par:i\d+>> ParameterValue
/// CHECK-DAG: <<P100:i\d+>> IntConstant 100
@@ -578,6 +588,16 @@
return (x > 100) ? 100 : ((x < -100) ? -100 : x);
}
+ /// CHECK-START: int TestMinMax.minmax4(int) select_generator (after)
+ /// CHECK-DAG: <<Par:i\d+>> ParameterValue
+ /// CHECK-DAG: <<P100:i\d+>> IntConstant 100
+ /// CHECK-DAG: <<M100:i\d+>> IntConstant -100
+ /// CHECK-DAG: <<Cnd1:z\d+>> GreaterThanOrEqual [<<Par>>,<<M100>>]
+ /// CHECK-DAG: <<Cnd2:z\d+>> LessThanOrEqual [<<Par>>,<<P100>>]
+ /// CHECK-DAG: <<Sel1:i\d+>> Select [<<P100>>,<<Par>>,<<Cnd2>>]
+ /// CHECK-DAG: <<Sel2:i\d+>> Select [<<M100>>,<<Sel1>>,<<Cnd1>>]
+ /// CHECK-DAG: Return [<<Sel2>>]
+
/// CHECK-START: int TestMinMax.minmax4(int) instruction_simplifier$after_gvn (after)
/// CHECK-DAG: <<Par:i\d+>> ParameterValue
/// CHECK-DAG: <<P100:i\d+>> IntConstant 100
diff --git a/test/596-checker-dead-phi/smali/IrreducibleLoop.smali b/test/596-checker-dead-phi/smali/IrreducibleLoop.smali
index bab2ba9..9f822bf 100644
--- a/test/596-checker-dead-phi/smali/IrreducibleLoop.smali
+++ b/test/596-checker-dead-phi/smali/IrreducibleLoop.smali
@@ -20,18 +20,19 @@
# not adjacent. This revealed a bug in our SSA builder, where a dead loop phi would
# be replaced by its incoming input during SsaRedundantPhiElimination.
-# Check that the outer loop suspend check environment only has the parameter vreg.
-## CHECK-START: int IrreducibleLoop.liveness(int) builder (after)
-## CHECK-DAG: <<Phi:i\d+>> Phi reg:4 loop:{{B\d+}} irreducible:false
-## CHECK-DAG: SuspendCheck env:[[_,_,_,_,<<Phi>>]] loop:{{B\d+}} irreducible:false
+# Check that the outer loop suspend check environment only has the two parameter vregs.
+## CHECK-START: int IrreducibleLoop.liveness(int, int) builder (after)
+## CHECK-DAG: <<Phi1:i\d+>> Phi reg:3 loop:{{B\d+}} irreducible:false
+## CHECK-DAG: <<Phi2:i\d+>> Phi reg:4 loop:{{B\d+}} irreducible:false
+## CHECK-DAG: SuspendCheck env:[[_,_,_,<<Phi1>>,<<Phi2>>]] loop:{{B\d+}} irreducible:false
# Check that the linear order has non-adjacent loop blocks.
-## CHECK-START: int IrreducibleLoop.liveness(int) liveness (after)
+## CHECK-START: int IrreducibleLoop.liveness(int, int) liveness (after)
## CHECK-DAG: Mul liveness:<<LPreEntry2:\d+>>
## CHECK-DAG: Add liveness:<<LBackEdge1:\d+>>
## CHECK-EVAL: <<LBackEdge1>> < <<LPreEntry2>>
-.method public static liveness(I)I
+.method public static liveness(II)I
.registers 5
const-string v1, "MyString"
@@ -50,8 +51,9 @@
if-ne v2, v3, :pre_header2
:pre_entry2
- # Add a marker on the irreducible loop entry.
- mul-int/2addr p0, p0
+ # Add a marker on the irreducible loop entry. Here we use p1 because p0 is a
+ # known constant and we eliminate the Mul otherwise.
+ mul-int/2addr p1, p1
goto :back_edge2
:back_edge2
@@ -61,8 +63,9 @@
if-eqz p0, :back_edge2
:back_edge1
- # Add a marker on the outer loop back edge.
- add-int/2addr p0, p0
+ # Add a marker on the outer loop back edge. Here we use p1 because p0 is a
+ # known constant and we eliminate the Add otherwise.
+ add-int/2addr p1, p1
# Set a wide register, to have v1 undefined at the back edge.
const-wide/16 v0, 0x1
goto :header1
diff --git a/test/596-checker-dead-phi/src/Main.java b/test/596-checker-dead-phi/src/Main.java
index f3a55df..c3384ad 100644
--- a/test/596-checker-dead-phi/src/Main.java
+++ b/test/596-checker-dead-phi/src/Main.java
@@ -22,8 +22,8 @@
// Note that we don't actually enter the loops in the 'liveness'
// method, so this is just a verification that that part of the code we
// generated for that method is correct.
- Method m = c.getMethod("liveness", int.class);
- Object[] arguments = { 42 };
+ Method m = c.getMethod("liveness", int.class, int.class);
+ Object[] arguments = {42, 12};
System.out.println(m.invoke(null, arguments));
}
}
diff --git a/test/597-deopt-busy-loop/src/FloatLoop.java b/test/597-deopt-busy-loop/src/FloatLoop.java
index 57667a6..7aadce4 100644
--- a/test/597-deopt-busy-loop/src/FloatLoop.java
+++ b/test/597-deopt-busy-loop/src/FloatLoop.java
@@ -51,6 +51,10 @@
}
}
+ // Create an empty int[] to force loading the int[] class before compiling $noinline$busyLoop.
+ // This makes sure the compiler can properly type int[] and not bail.
+ static int[] emptyArray = new int[0];
+
public void $noinline$busyLoop() {
Main.assertIsManaged();
diff --git a/test/616-cha-interface-default/build b/test/616-cha-interface-default/build
deleted file mode 100644
index d9654f8..0000000
--- a/test/616-cha-interface-default/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2017 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.
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/616-cha-interface-default/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/616-cha-interface-default/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/616-cha-interface-default/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/618-checker-induction/src/Main.java b/test/618-checker-induction/src/Main.java
index dd76e41..824a8db 100644
--- a/test/618-checker-induction/src/Main.java
+++ b/test/618-checker-induction/src/Main.java
@@ -607,6 +607,61 @@
return closed;
}
+ // Checks that we do not loop optimize if the calculation of the trip count would overflow.
+ /// CHECK-START: int Main.closedLinearStepOverflow() loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi1>>] loop:none
+ //
+ /// CHECK-START: int Main.closedLinearStepOverflow() loop_optimization (after)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi1>>] loop:none
+ private static int closedLinearStepOverflow() {
+ int closed = 0;
+ // Note that this isn't a "one-off" error. We are using MIN and MAX to make sure we overflow.
+ for (int i = Integer.MIN_VALUE; i < (Integer.MAX_VALUE - 80); i += 79) {
+ closed++;
+ }
+ return closed;
+ }
+
+ // Since we cannot guarantee that the start/end wouldn't overflow we do not perform loop
+ // optimization.
+ /// CHECK-START: int Main.$inline$closedByParameters(int, int) loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi1>>] loop:none
+ //
+ /// CHECK-START: int Main.$inline$closedByParameters(int, int) loop_optimization (after)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi1>>] loop:none
+ private static int $inline$closedByParameters(int start, int end) {
+ int closed = 0;
+ for (int i = start; i < end; i++) {
+ closed++;
+ }
+ return closed;
+ }
+
+ // Since we are inlining `closedByParameters` we know that the parameters are fixed and
+ // therefore we can perform loop optimization.
+ /// CHECK-START: int Main.closedByParametersWithInline() loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop>> outer_loop:none
+ /// CHECK-DAG: Return [<<Phi1>>] loop:none
+ //
+ /// CHECK-START: int Main.closedByParametersWithInline() loop_optimization (after)
+ /// CHECK-NOT: Phi
+ //
+ /// CHECK-START: int Main.closedByParametersWithInline() instruction_simplifier$after_bce (after)
+ /// CHECK-DAG: <<Int:i\d+>> IntConstant 10 loop:none
+ /// CHECK-DAG: Return [<<Int>>] loop:none
+ private static int closedByParametersWithInline() {
+ return $inline$closedByParameters(0, 10);
+ }
+
/// CHECK-START: int Main.waterFall() loop_optimization (before)
/// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop1:B\d+>> outer_loop:none
/// CHECK-DAG: <<Phi2:i\d+>> Phi loop:<<Loop2:B\d+>> outer_loop:none
@@ -829,6 +884,32 @@
return k;
}
+ /// CHECK-START: long Main.closedLinearInductionUnmatchedTypesNotOptimized() loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:j\d+>> Phi loop:<<Loop>> outer_loop:none
+ //
+ /// CHECK-START: long Main.closedLinearInductionUnmatchedTypesNotOptimized() loop_optimization (after)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ /// CHECK-DAG: <<Phi2:j\d+>> Phi loop:<<Loop>> outer_loop:none
+ private static long closedLinearInductionUnmatchedTypesNotOptimized() {
+ long sum = 0;
+ for (int i = 0; i < 10; ++i) {
+ ++sum;
+ }
+ return sum;
+ }
+
+ /// CHECK-START: short Main.closedLinearInductionNarrowingNotOptimized() loop_optimization (before)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ //
+ /// CHECK-START: short Main.closedLinearInductionNarrowingNotOptimized() loop_optimization (after)
+ /// CHECK-DAG: <<Phi1:i\d+>> Phi loop:<<Loop:B\d+>> outer_loop:none
+ private static short closedLinearInductionNarrowingNotOptimized() {
+ short i = 0;
+ for (; i < 10; ++i);
+ return i;
+ }
+
public static void main(String[] args) {
deadSingleLoop();
deadSingleLoopN(4);
@@ -896,6 +977,9 @@
expectEquals(20, closedFeed());
expectEquals(-10, closedLargeUp());
expectEquals(10, closedLargeDown());
+ expectEquals(54366674, closedLinearStepOverflow());
+ expectEquals(10, $inline$closedByParameters(0, 10));
+ expectEquals(10, closedByParametersWithInline());
expectEquals(50, waterFall());
expectEquals(false, periodicBoolIdiom1());
@@ -925,6 +1009,9 @@
expectEquals(-41, exceptionExitBeforeAdd());
expectEquals(-51, exceptionExitAfterAdd());
+ expectEquals(10, closedLinearInductionUnmatchedTypesNotOptimized());
+ expectEquals(10, closedLinearInductionNarrowingNotOptimized());
+
System.out.println("passed");
}
diff --git a/test/638-no-line-number/build b/test/638-no-line-number/build.py
similarity index 74%
rename from test/638-no-line-number/build
rename to test/638-no-line-number/build.py
index 9cd1955..bc8b681 100644
--- a/test/638-no-line-number/build
+++ b/test/638-no-line-number/build.py
@@ -1,12 +1,11 @@
-#!/bin/bash
#
-# Copyright (C) 2017 The Android Open Source Project
+# Copyright (C) 2022 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
+# 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,
@@ -14,9 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Stop if something fails.
-set -e
+from art_build_rules import build_run_test
# Only keep the source name, to make sure we do remove it in the stack trace
# when there is no line number mapping.
-JAVAC_ARGS="$JAVAC_ARGS -g:source" ./default-build "$@"
+build_run_test(javac_args=["-g:source"])
diff --git a/test/648-many-direct-methods/build b/test/648-many-direct-methods/generate-sources
similarity index 96%
rename from test/648-many-direct-methods/build
rename to test/648-many-direct-methods/generate-sources
index 7e888e5..8907645 100755
--- a/test/648-many-direct-methods/build
+++ b/test/648-many-direct-methods/generate-sources
@@ -21,5 +21,3 @@
# Generate the Java file or fail.
./util-src/generate_java.py ./src
-
-./default-build "$@"
diff --git a/test/661-oat-writer-layout/run b/test/661-oat-writer-layout/run
index 087cd20..3c09690 100644
--- a/test/661-oat-writer-layout/run
+++ b/test/661-oat-writer-layout/run
@@ -19,4 +19,4 @@
# -- we accomplish this by blocklisting other compiler variants
# and we also have to pass the option explicitly as dex2oat
# defaults to speed-profile if a profile is specified.
-"${RUN}" "$@" --profile -Xcompiler-option --compiler-filter=speed
+${RUN} "$@" --profile -Xcompiler-option --compiler-filter=speed
diff --git a/test/663-odd-dex-size2/build b/test/663-odd-dex-size2/build
deleted file mode 100644
index 5636558..0000000
--- a/test/663-odd-dex-size2/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2017 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.
-
-# Nothing to do
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/663-odd-dex-size2/build.py
similarity index 83%
copy from test/2038-hiddenapi-jvmti-ext/build
copy to test/663-odd-dex-size2/build.py
index f4b029f..76845c9 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/663-odd-dex-size2/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+# Nothing to do.
diff --git a/test/663-odd-dex-size3/build b/test/663-odd-dex-size3/build
deleted file mode 100644
index 5636558..0000000
--- a/test/663-odd-dex-size3/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2017 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.
-
-# Nothing to do
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/663-odd-dex-size3/build.py
similarity index 83%
copy from test/2038-hiddenapi-jvmti-ext/build
copy to test/663-odd-dex-size3/build.py
index f4b029f..c4fc9ee 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/663-odd-dex-size3/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+# Nothing to do
diff --git a/test/663-odd-dex-size4/build b/test/663-odd-dex-size4/build
deleted file mode 100644
index 5636558..0000000
--- a/test/663-odd-dex-size4/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2017 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.
-
-# Nothing to do
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/663-odd-dex-size4/build.py
similarity index 83%
copy from test/2038-hiddenapi-jvmti-ext/build
copy to test/663-odd-dex-size4/build.py
index f4b029f..c4fc9ee 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/663-odd-dex-size4/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+# Nothing to do
diff --git a/test/670-bitstring-type-check/build b/test/670-bitstring-type-check/generate-sources
old mode 100644
new mode 100755
similarity index 99%
rename from test/670-bitstring-type-check/build
rename to test/670-bitstring-type-check/generate-sources
index 38307f2..4d88839
--- a/test/670-bitstring-type-check/build
+++ b/test/670-bitstring-type-check/generate-sources
@@ -212,5 +212,3 @@
}
}
EOF
-
-./default-build "$@"
diff --git a/test/674-hiddenapi/build b/test/674-hiddenapi/build.py
similarity index 81%
rename from test/674-hiddenapi/build
rename to test/674-hiddenapi/build.py
index 330a6de..f518d51 100644
--- a/test/674-hiddenapi/build
+++ b/test/674-hiddenapi/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-set -e
+from art_build_rules import build_run_test, rm
+import os
# Build the jars twice. First with applying hiddenapi, creating a boot jar, then
# a second time without to create a normal jar. We need to do this because we
@@ -23,16 +23,14 @@
# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
# class path dex files, so the boot jar loads fine in the latter case.
-export USE_HIDDENAPI=true
-./default-build "$@"
+build_run_test(use_hiddenapi=True)
# Move the jar file into the resource folder to be bundled with the test.
-mkdir res
-mv ${TEST_NAME}.jar res/boot.jar
+os.mkdir("res")
+os.rename("674-hiddenapi.jar", "res/boot.jar")
# Clear all intermediate files otherwise default-build would either skip
# compilation or fail rebuilding.
-rm -rf classes*
+rm("classes*")
-export USE_HIDDENAPI=false
-./default-build "$@"
+build_run_test(use_hiddenapi=False)
diff --git a/test/674-hiddenapi/hiddenapi.cc b/test/674-hiddenapi/hiddenapi.cc
index ebe9d10..5fa2532 100644
--- a/test/674-hiddenapi/hiddenapi.cc
+++ b/test/674-hiddenapi/hiddenapi.cc
@@ -15,13 +15,10 @@
*/
#include "base/sdk_version.h"
-#include "class_linker.h"
#include "dex/art_dex_file_loader.h"
#include "hidden_api.h"
#include "jni.h"
#include "runtime.h"
-#include "scoped_thread_state_change-inl.h"
-#include "thread.h"
#include "ti-agent/scoped_utf_chars.h"
namespace art {
@@ -77,10 +74,7 @@
Java_Main_setDexDomain(env, klass, int_index, is_core_platform);
- ScopedObjectAccess soa(Thread::Current());
- for (std::unique_ptr<const DexFile>& dex_file : opened_dex_files[index]) {
- Runtime::Current()->GetClassLinker()->AppendToBootClassPath(Thread::Current(), dex_file.get());
- }
+ Runtime::Current()->AppendToBootClassPath(path, path, opened_dex_files[index]);
return int_index;
}
diff --git a/test/674-vdex-uncompress/build b/test/674-vdex-uncompress/build.py
old mode 100755
new mode 100644
similarity index 79%
rename from test/674-vdex-uncompress/build
rename to test/674-vdex-uncompress/build.py
index 7b1804d..a694a9d
--- a/test/674-vdex-uncompress/build
+++ b/test/674-vdex-uncompress/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,6 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+from art_build_rules import build_run_test
+
# Uncompress and align the dex files so that dex2oat will not copy the dex
# code to the .vdex file.
-./default-build "$@" --zip-compression-method store --zip-align 4
+build_run_test(zip_compression_method="store", zip_align_bytes="4")
diff --git a/test/677-fsi/build b/test/677-fsi/build.py
old mode 100755
new mode 100644
similarity index 76%
copy from test/677-fsi/build
copy to test/677-fsi/build.py
index b90b408..d16a996
--- a/test/677-fsi/build
+++ b/test/677-fsi/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(zip_compression_method="store", zip_align_bytes=4)
diff --git a/test/690-hiddenapi-same-name-methods/build b/test/690-hiddenapi-same-name-methods/build.py
similarity index 80%
rename from test/690-hiddenapi-same-name-methods/build
rename to test/690-hiddenapi-same-name-methods/build.py
index c364b3b..8367ad0 100644
--- a/test/690-hiddenapi-same-name-methods/build
+++ b/test/690-hiddenapi-same-name-methods/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2019 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+from art_build_rules import build_run_test
+
+build_run_test(use_hiddenapi=True)
diff --git a/test/691-hiddenapi-proxy/build b/test/691-hiddenapi-proxy/build.py
similarity index 80%
rename from test/691-hiddenapi-proxy/build
rename to test/691-hiddenapi-proxy/build.py
index c364b3b..8367ad0 100644
--- a/test/691-hiddenapi-proxy/build
+++ b/test/691-hiddenapi-proxy/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2019 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+from art_build_rules import build_run_test
+
+build_run_test(use_hiddenapi=True)
diff --git a/test/701-easy-div-rem/build b/test/701-easy-div-rem/generate-sources
old mode 100644
new mode 100755
similarity index 96%
rename from test/701-easy-div-rem/build
rename to test/701-easy-div-rem/generate-sources
index 6d114b6..efe4b05
--- a/test/701-easy-div-rem/build
+++ b/test/701-easy-div-rem/generate-sources
@@ -20,5 +20,3 @@
# Write out the source file.
mkdir src
python3 ./genMain.py
-
-./default-build "$@"
diff --git a/test/702-LargeBranchOffset/build b/test/702-LargeBranchOffset/generate-sources
old mode 100644
new mode 100755
similarity index 96%
rename from test/702-LargeBranchOffset/build
rename to test/702-LargeBranchOffset/generate-sources
index 2505b0a..b01afc4
--- a/test/702-LargeBranchOffset/build
+++ b/test/702-LargeBranchOffset/generate-sources
@@ -20,5 +20,3 @@
# Write out the source file.
mkdir -p src
./generate
-
-./default-build "$@"
diff --git a/test/710-varhandle-creation/build b/test/710-varhandle-creation/build
deleted file mode 100644
index ca1e557..0000000
--- a/test/710-varhandle-creation/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2017 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/807-method-handle-and-mr/build b/test/710-varhandle-creation/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/710-varhandle-creation/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/710-varhandle-creation/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/807-method-handle-and-mr/build b/test/712-varhandle-invocations/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/712-varhandle-invocations/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/712-varhandle-invocations/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/712-varhandle-invocations/build b/test/712-varhandle-invocations/generate-sources
similarity index 95%
rename from test/712-varhandle-invocations/build
rename to test/712-varhandle-invocations/generate-sources
index 9a6e96e..d025b26 100755
--- a/test/712-varhandle-invocations/build
+++ b/test/712-varhandle-invocations/generate-sources
@@ -31,5 +31,3 @@
# Generate tests and Main that covers both the generated tests and manual tests
python3 ./util-src/generate_java.py "${GENERATED_SRC}" ${MANUAL_TESTS}
-
-./default-build "$@" --experimental var-handles
diff --git a/test/713-varhandle-invokers/build b/test/713-varhandle-invokers/build
deleted file mode 100755
index 09d376b..0000000
--- a/test/713-varhandle-invokers/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/807-method-handle-and-mr/build b/test/713-varhandle-invokers/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/713-varhandle-invokers/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/713-varhandle-invokers/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/714-invoke-custom-lambda-metafactory/build b/test/714-invoke-custom-lambda-metafactory/build
deleted file mode 100644
index b5002ba..0000000
--- a/test/714-invoke-custom-lambda-metafactory/build
+++ /dev/null
@@ -1,22 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-# Opt-out from desugaring to ensure offending lambda is in the DEX.
-export USE_DESUGAR=false
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/714-invoke-custom-lambda-metafactory/build.py
old mode 100755
new mode 100644
similarity index 76%
copy from test/677-fsi/build
copy to test/714-invoke-custom-lambda-metafactory/build.py
index b90b408..7563062
--- a/test/677-fsi/build
+++ b/test/714-invoke-custom-lambda-metafactory/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(use_desugar=False, experimental="method-handles")
diff --git a/test/715-clinit-implicit-parameter-annotations/build b/test/715-clinit-implicit-parameter-annotations/build
deleted file mode 100644
index 2b5f92c..0000000
--- a/test/715-clinit-implicit-parameter-annotations/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental parameter-annotations
diff --git a/test/677-fsi/build b/test/715-clinit-implicit-parameter-annotations/build.py
old mode 100755
new mode 100644
similarity index 77%
copy from test/677-fsi/build
copy to test/715-clinit-implicit-parameter-annotations/build.py
index b90b408..19c759c
--- a/test/677-fsi/build
+++ b/test/715-clinit-implicit-parameter-annotations/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="parameter-annotations")
diff --git a/test/716-jli-jit-samples/build b/test/716-jli-jit-samples/build
deleted file mode 100755
index 730a8a1..0000000
--- a/test/716-jli-jit-samples/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/807-method-handle-and-mr/build b/test/716-jli-jit-samples/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/716-jli-jit-samples/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/716-jli-jit-samples/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/719-varhandle-concurrency/build b/test/719-varhandle-concurrency/build
deleted file mode 100755
index 98a9967..0000000
--- a/test/719-varhandle-concurrency/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2022 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.
-
-# Make us exit on a failure
-set -e
-
-./default-build "$@" --experimental var-handles
diff --git a/test/807-method-handle-and-mr/build b/test/719-varhandle-concurrency/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/807-method-handle-and-mr/build
copy to test/719-varhandle-concurrency/build.py
index 12a8e18..d6b35b1
--- a/test/807-method-handle-and-mr/build
+++ b/test/719-varhandle-concurrency/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="var-handles")
diff --git a/test/719-varhandle-concurrency/src/Main.java b/test/719-varhandle-concurrency/src/Main.java
index 1c2135b..577cdbf 100644
--- a/test/719-varhandle-concurrency/src/Main.java
+++ b/test/719-varhandle-concurrency/src/Main.java
@@ -39,14 +39,15 @@
}
private static final int TASK_COUNT = 10000;
- private static final int THREAD_COUNT = 100;
+ private static final int THREAD_COUNT = 20;
/* Each test may need several retries before a concurrent failure is seen. In the past, for a
* known bug, between 5 and 10 retries were sufficient. Use RETRIES to configure how many
* iterations to retry for each test scenario. However, to avoid the test running for too long,
* for example with gcstress, set a cap duration in MAX_RETRIES_DURATION. With this at least one
* iteration would run, but there could be fewer retries if each of them takes too long. */
private static final int RETRIES = 50;
- private static final Duration MAX_RETRIES_DURATION = Duration.ofMinutes(1);
+ // b/235431387: timeout reduced from 1 minute
+ private static final Duration MAX_RETRIES_DURATION = Duration.ofSeconds(15);
public static void main(String[] args) throws Throwable {
testConcurrentProcessing(new CompareAndExchangeRunnerFactory(), "compareAndExchange");
diff --git a/test/804-class-extends-itself/build b/test/804-class-extends-itself/generate-sources
old mode 100644
new mode 100755
similarity index 100%
rename from test/804-class-extends-itself/build
rename to test/804-class-extends-itself/generate-sources
diff --git a/test/807-method-handle-and-mr/build b/test/807-method-handle-and-mr/build.py
old mode 100755
new mode 100644
similarity index 79%
rename from test/807-method-handle-and-mr/build
rename to test/807-method-handle-and-mr/build.py
index 12a8e18..e8e08eb
--- a/test/807-method-handle-and-mr/build
+++ b/test/807-method-handle-and-mr/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2017 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-# Exit on failure.
-set -e
+from art_build_rules import build_run_test
-./default-build "$@" --experimental method-handles
+build_run_test(experimental="method-handles")
diff --git a/test/817-hiddenapi/build b/test/817-hiddenapi/build
deleted file mode 100644
index 330a6de..0000000
--- a/test/817-hiddenapi/build
+++ /dev/null
@@ -1,38 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2018 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.
-
-set -e
-
-# Build the jars twice. First with applying hiddenapi, creating a boot jar, then
-# a second time without to create a normal jar. We need to do this because we
-# want to load the jar once as an app module and once as a member of the boot
-# class path. The DexFileVerifier would fail on the former as it does not allow
-# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
-# class path dex files, so the boot jar loads fine in the latter case.
-
-export USE_HIDDENAPI=true
-./default-build "$@"
-
-# Move the jar file into the resource folder to be bundled with the test.
-mkdir res
-mv ${TEST_NAME}.jar res/boot.jar
-
-# Clear all intermediate files otherwise default-build would either skip
-# compilation or fail rebuilding.
-rm -rf classes*
-
-export USE_HIDDENAPI=false
-./default-build "$@"
diff --git a/test/674-hiddenapi/build b/test/817-hiddenapi/build.py
similarity index 81%
copy from test/674-hiddenapi/build
copy to test/817-hiddenapi/build.py
index 330a6de..1de286a 100644
--- a/test/674-hiddenapi/build
+++ b/test/817-hiddenapi/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,7 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-set -e
+from art_build_rules import build_run_test, rm
+import os
# Build the jars twice. First with applying hiddenapi, creating a boot jar, then
# a second time without to create a normal jar. We need to do this because we
@@ -23,16 +23,14 @@
# hidden API access flags in dex files. DexFileVerifier is not invoked on boot
# class path dex files, so the boot jar loads fine in the latter case.
-export USE_HIDDENAPI=true
-./default-build "$@"
+build_run_test(use_hiddenapi=True)
# Move the jar file into the resource folder to be bundled with the test.
-mkdir res
-mv ${TEST_NAME}.jar res/boot.jar
+os.mkdir("res")
+os.rename("817-hiddenapi.jar", "res/boot.jar")
# Clear all intermediate files otherwise default-build would either skip
# compilation or fail rebuilding.
-rm -rf classes*
+rm("classes*")
-export USE_HIDDENAPI=false
-./default-build "$@"
+build_run_test(use_hiddenapi=False)
diff --git a/test/822-hiddenapi-future/build b/test/822-hiddenapi-future/build.py
similarity index 80%
rename from test/822-hiddenapi-future/build
rename to test/822-hiddenapi-future/build.py
index 02ce549..8367ad0 100644
--- a/test/822-hiddenapi-future/build
+++ b/test/822-hiddenapi-future/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2021 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+from art_build_rules import build_run_test
+
+build_run_test(use_hiddenapi=True)
diff --git a/test/829-unresolved-enclosing/build b/test/829-unresolved-enclosing/build
deleted file mode 100644
index f378df1..0000000
--- a/test/829-unresolved-enclosing/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2014 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.
-
-# Stop if something fails.
-set -e
-
-./default-build "$@"
diff --git a/test/837-deopt/Android.bp b/test/837-deopt/Android.bp
new file mode 100644
index 0000000..2e0e3aa
--- /dev/null
+++ b/test/837-deopt/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `837-deopt`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-837-deopt",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-no-test-suite-tag-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-837-deopt-expected-stdout",
+ ":art-run-test-837-deopt-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-837-deopt-expected-stdout",
+ out: ["art-run-test-837-deopt-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-837-deopt-expected-stderr",
+ out: ["art-run-test-837-deopt-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/837-deopt/expected-stderr.txt b/test/837-deopt/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/837-deopt/expected-stderr.txt
diff --git a/test/837-deopt/expected-stdout.txt b/test/837-deopt/expected-stdout.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/837-deopt/expected-stdout.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/837-deopt/info.txt b/test/837-deopt/info.txt
new file mode 100644
index 0000000..5c95277
--- /dev/null
+++ b/test/837-deopt/info.txt
@@ -0,0 +1 @@
+Tests around deoptimization.
diff --git a/test/837-deopt/src/Main.java b/test/837-deopt/src/Main.java
new file mode 100644
index 0000000..8e3ad7c
--- /dev/null
+++ b/test/837-deopt/src/Main.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+class Main {
+ int field = 42;
+
+ // Test that deoptimization preserves objects that are singletons.
+ public static int $noinline$foo(Main arg) {
+ Main m = new Main();
+ arg.returnValue();
+ return m.field;
+ }
+
+ // Test that doing OSR after deoptimization works.
+ public static int $noinline$foo2(Main arg, boolean osr) {
+ Main m = new Main();
+ arg.returnValue();
+ if (osr) {
+ while (!isInOsrCode("$noinline$foo2")) {}
+ }
+ return m.field;
+ }
+
+ public static void main(String[] args) throws Throwable {
+ System.loadLibrary(args[0]);
+ if (isDebuggable()) {
+ // We do not deoptimize with inline caches when the app is debuggable, so just don't run the
+ // test.
+ return;
+ }
+ test1();
+ test2();
+ }
+
+ public static void assertEquals(int expected, int actual) {
+ if (expected != actual) {
+ throw new Error("Expected " + expected + ", got " + actual);
+ }
+ }
+
+ public static void test1() {
+ ensureJitBaselineCompiled(Main.class, "$noinline$foo");
+ // Surround the call with GCs to increase chances we execute $noinline$foo
+ // while the GC isn't marking. This makes sure the inline cache is populated.
+ Runtime.getRuntime().gc();
+ assertEquals(42, $noinline$foo(new Main()));
+ Runtime.getRuntime().gc();
+
+ ensureJitCompiled(Main.class, "$noinline$foo");
+ assertEquals(42, $noinline$foo(new SubMain()));
+ }
+
+ public static void test2() {
+ ensureJitBaselineCompiled(Main.class, "$noinline$foo2");
+ // Surround the call with GCs to increase chances we execute $noinline$foo
+ // while the GC isn't marking. This makes sure the inline cache is populated.
+ Runtime.getRuntime().gc();
+ assertEquals(42, $noinline$foo2(new Main(), false));
+ Runtime.getRuntime().gc();
+
+ ensureJitCompiled(Main.class, "$noinline$foo2");
+ assertEquals(42, $noinline$foo2(new SubMain(), true));
+ }
+
+ public String returnValue() {
+ return "Main";
+ }
+
+ public static native void ensureJitCompiled(Class<?> cls, String methodName);
+ public static native void ensureJitBaselineCompiled(Class<?> cls, String methodName);
+ public static native boolean isInOsrCode(String methodName);
+ public static native boolean isDebuggable();
+}
+
+// Define a subclass with another implementation of returnValue to deoptimize $noinline$foo and
+// $noinline$foo2.
+class SubMain extends Main {
+ public String returnValue() {
+ return "SubMain";
+ }
+}
diff --git a/test/838-override/Android.bp b/test/838-override/Android.bp
new file mode 100644
index 0000000..2ac4e21
--- /dev/null
+++ b/test/838-override/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `838-override`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-838-override",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-838-override-expected-stdout",
+ ":art-run-test-838-override-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-838-override-expected-stdout",
+ out: ["art-run-test-838-override-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-838-override-expected-stderr",
+ out: ["art-run-test-838-override-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/838-override/expected-stderr.txt b/test/838-override/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/838-override/expected-stderr.txt
diff --git a/test/838-override/expected-stdout.txt b/test/838-override/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/838-override/expected-stdout.txt
diff --git a/test/838-override/info.txt b/test/838-override/info.txt
new file mode 100644
index 0000000..a33ddb2
--- /dev/null
+++ b/test/838-override/info.txt
@@ -0,0 +1 @@
+Tests for method overriding in the presence of package private methods.
diff --git a/test/838-override/src/Main.java b/test/838-override/src/Main.java
new file mode 100644
index 0000000..1e6aa49
--- /dev/null
+++ b/test/838-override/src/Main.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+
+ public static void assertEquals(Object expected, Object actual) {
+ if (expected != actual) {
+ throw new Error("Expected " + expected + ", got " + actual);
+ }
+ }
+
+ public static void main(String[] args) {
+ // Tescase 1: a class with:
+ // - a package-private pkg1 'foo'
+ // - a public pkg2 'foo'.
+ {
+ pkg2.PublicFoo obj = new pkg2.PublicFoo();
+ assertEquals(pkg1.Pkg1Foo.class, pkg1.Pkg1Foo.callFoo(obj));
+ assertEquals(pkg2.PublicFoo.class, pkg2.Pkg2Foo.callFoo(obj));
+ assertEquals(pkg2.PublicFoo.class, pkg3.Pkg3Foo.callFoo(obj));
+ assertEquals(pkg2.PublicFoo.class, obj.foo());
+ }
+ // Tescase 2: a class with:
+ // - a package-private pkg1 'foo'
+ // - a public pkg2 'foo'
+ // - a public pkg1 'foo.
+ {
+ pkg1.PublicFoo obj = new pkg1.PublicFoo();
+ assertEquals(pkg1.PublicFoo.class, pkg1.Pkg1Foo.callFoo(obj));
+ assertEquals(pkg1.PublicFoo.class, pkg2.Pkg2Foo.callFoo(obj));
+ assertEquals(pkg1.PublicFoo.class, pkg3.Pkg3Foo.callFoo(obj));
+ assertEquals(pkg1.PublicFoo.class, obj.foo());
+ }
+
+ // Tescase 3: a class with:
+ // - a package-private pkg1 'foo'
+ // - a package-private pkg2 'foo'
+ // - a public pkg3 'foo.
+ {
+ pkg3.PublicFoo obj = new pkg3.PublicFoo();
+ assertEquals(pkg1.Pkg1Foo.class, pkg1.Pkg1Foo.callFoo(obj));
+ assertEquals(pkg2.Pkg2Foo.class, pkg2.Pkg2Foo.callFoo(obj));
+ assertEquals(pkg3.PublicFoo.class, pkg3.Pkg3Foo.callFoo(obj));
+ assertEquals(pkg3.PublicFoo.class, obj.foo());
+ }
+
+ // Tescase 4: a class with:
+ // - a package-private pkg1 'foo'
+ // - a package-private pkg2 'foo'
+ // - a public pkg3 'foo.
+ // - a public pkg2 'foo'
+ {
+ pkg2.PublicFooInheritsPkg3 obj = new pkg2.PublicFooInheritsPkg3();
+ assertEquals(pkg1.Pkg1Foo.class, pkg1.Pkg1Foo.callFoo(obj));
+ assertEquals(pkg2.PublicFooInheritsPkg3.class, pkg2.Pkg2Foo.callFoo(obj));
+ assertEquals(pkg2.PublicFooInheritsPkg3.class, pkg3.Pkg3Foo.callFoo(obj));
+ assertEquals(pkg2.PublicFooInheritsPkg3.class, obj.foo());
+ }
+
+ // Tescase 5: a class with:
+ // - a package-private pkg1 'foo'
+ // - a package-private pkg2 'foo'
+ // - a public pkg3 'foo.
+ // - a public pkg2 'foo'
+ // - a public pkg1 'foo'
+ {
+ pkg1.PublicFooInheritsPkg2 obj = new pkg1.PublicFooInheritsPkg2();
+ assertEquals(pkg1.PublicFooInheritsPkg2.class, pkg1.Pkg1Foo.callFoo(obj));
+ assertEquals(pkg1.PublicFooInheritsPkg2.class, pkg2.Pkg2Foo.callFoo(obj));
+ assertEquals(pkg1.PublicFooInheritsPkg2.class, pkg3.Pkg3Foo.callFoo(obj));
+ assertEquals(pkg1.PublicFooInheritsPkg2.class, obj.foo());
+ }
+
+ // Tescase 6: a class with:
+ // - a package-private pkg1 'foo'
+ // - a package-private pkg2 'foo'
+ // - a public pkg1 'foo.
+ {
+ pkg1.LowerIndexImplementsFoo obj = new pkg1.LowerIndexImplementsFoo();
+ assertEquals(pkg1.LowerIndexPublicFoo.class, pkg1.Pkg1Foo.callFoo(obj));
+ assertEquals(pkg2.Pkg2Foo.class, pkg2.Pkg2Foo.callFoo(obj));
+ assertEquals(pkg2.Pkg2Foo.class, pkg3.Pkg3Foo.callFoo(obj));
+ assertEquals(pkg1.LowerIndexPublicFoo.class, obj.foo());
+ }
+ }
+}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg1/InterfaceFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg1/InterfaceFoo.java
index a16764b..cd9e44b 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg1/InterfaceFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
+package pkg1;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public interface InterfaceFoo {
+ public Class<?> foo();
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg1/LowerIndexImplementsFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg1/LowerIndexImplementsFoo.java
index a16764b..290f07b 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg1/LowerIndexImplementsFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,7 @@
* limitations under the License.
*/
-package android.os;
+package pkg1;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class LowerIndexImplementsFoo extends LowerIndexPublicFoo implements InterfaceFoo {
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg1/LowerIndexPublicFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg1/LowerIndexPublicFoo.java
index a16764b..87c0c72 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg1/LowerIndexPublicFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg1;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class LowerIndexPublicFoo extends pkg2.Pkg2Foo {
+ public Class<?> foo() {
+ return LowerIndexPublicFoo.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg1/Pkg1Foo.java
similarity index 71%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg1/Pkg1Foo.java
index a16764b..0ffbc27 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg1/Pkg1Foo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,15 @@
* limitations under the License.
*/
-package android.os;
+package pkg1;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class Pkg1Foo {
+
+ Class<?> foo() {
+ return Pkg1Foo.class;
+ }
+
+ public static Class<?> callFoo(Pkg1Foo obj) {
+ return obj.foo();
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg1/PublicFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg1/PublicFoo.java
index a16764b..c43b282 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg1/PublicFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg1;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class PublicFoo extends pkg2.PublicFoo {
+ public Class<?> foo() {
+ return PublicFoo.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg1/PublicFooInheritsPkg2.java
similarity index 72%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg1/PublicFooInheritsPkg2.java
index a16764b..a255f36 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg1/PublicFooInheritsPkg2.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg1;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class PublicFooInheritsPkg2 extends pkg2.PublicFooInheritsPkg3 {
+ public Class<?> foo() {
+ return PublicFooInheritsPkg2.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg2/Pkg2Foo.java
similarity index 70%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg2/Pkg2Foo.java
index a16764b..c521914 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg2/Pkg2Foo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package android.os;
+package pkg2;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class Pkg2Foo extends pkg1.Pkg1Foo{
+ Class<?> foo() {
+ return Pkg2Foo.class;
+ }
+
+ public static Class<?> callFoo(Pkg2Foo obj) {
+ return obj.foo();
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg2/PublicFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg2/PublicFoo.java
index a16764b..5434234 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg2/PublicFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg2;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class PublicFoo extends Pkg2Foo {
+ public Class<?> foo() {
+ return PublicFoo.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg2/PublicFooInheritsPkg3.java
similarity index 73%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg2/PublicFooInheritsPkg3.java
index a16764b..1a983a1 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg2/PublicFooInheritsPkg3.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg2;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class PublicFooInheritsPkg3 extends pkg3.PublicFoo {
+ public Class<?> foo() {
+ return PublicFooInheritsPkg3.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg3/Pkg3Foo.java
similarity index 70%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg3/Pkg3Foo.java
index a16764b..72a16aa 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg3/Pkg3Foo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package android.os;
+package pkg3;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class Pkg3Foo extends pkg2.Pkg2Foo {
+ Class<?> foo() {
+ return Pkg3Foo.class;
+ }
+
+ public static Class<?> callFoo(Pkg3Foo obj) {
+ return obj.foo();
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/838-override/src/pkg3/PublicFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/838-override/src/pkg3/PublicFoo.java
index a16764b..9d37137 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/838-override/src/pkg3/PublicFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg3;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class PublicFoo extends Pkg3Foo {
+ public Class<?> foo() {
+ return PublicFoo.class;
+ }
}
diff --git a/test/840-resolution/expected-stderr.txt b/test/840-resolution/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/840-resolution/expected-stderr.txt
diff --git a/test/840-resolution/expected-stdout.txt b/test/840-resolution/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/840-resolution/expected-stdout.txt
diff --git a/test/840-resolution/info.txt b/test/840-resolution/info.txt
new file mode 100644
index 0000000..bd88f7d
--- /dev/null
+++ b/test/840-resolution/info.txt
@@ -0,0 +1,2 @@
+Various tests on interface method linking when not finding a public
+implementation.
diff --git a/test/840-resolution/jasmin/SubClass2.j b/test/840-resolution/jasmin/SubClass2.j
new file mode 100644
index 0000000..8a65e1f
--- /dev/null
+++ b/test/840-resolution/jasmin/SubClass2.j
@@ -0,0 +1,35 @@
+; Copyright (C) 2022 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.
+
+.class public SubClass2
+.super SuperClass
+.implements Interface
+
+.method public <init>()V
+ .limit stack 1
+ .limit locals 1
+ aload_0
+ invokespecial SuperClass/<init>()V
+ return
+.end method
+
+.method foo()Ljava/lang/Class;
+ .limit stack 1
+ .limit locals 1
+ ; jasmin does not support ldc with a class, so just return null for the
+ ; purpose of this test.
+ aconst_null
+ areturn
+.end method
+
diff --git a/test/840-resolution/src/Main.java b/test/840-resolution/src/Main.java
new file mode 100644
index 0000000..23cd31d
--- /dev/null
+++ b/test/840-resolution/src/Main.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+public class Main {
+
+ // Testcase 1: the superclass has a package private version in the same package.
+ static Interface s = new SubClass();
+
+ // Testcase 2: the class has a package private version.
+ static Interface s2;
+
+ // Testcase 3: the superclass has a package private version in a different package.
+ static Interface s3 = new SubClassFromPkg();
+
+ // Testcase 4: there is no implementation in the hierarchy.
+ static Interface s4 = new SubClassNoFoo();
+
+ // Testcase 5: there is a private method in the hierarchy.
+ static Interface s5 = new SubClassPrivateFoo();
+
+ // Testcase 6: there is a static method in the hierarchy.
+ static Interface s6 = new SubClassStaticFoo();
+
+ static {
+ try {
+ s2 = (Interface) Class.forName("SubClass2").newInstance();
+ } catch (Exception e) {
+ throw new Error(e);
+ }
+ }
+
+ public static void assertEquals(Object expected, Object actual) {
+ if (expected != actual) {
+ throw new Error("Expected " + expected + ", got " + actual);
+ }
+ }
+
+ public static void assertTrue(boolean value) {
+ if (!value) {
+ throw new Error("");
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ assertEquals(SuperClass.class, ((SubClass) s).foo());
+ assertEquals(SuperClass.class, ((SuperClass) s).foo());
+
+ try {
+ s.foo();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError ie) {
+ // expected
+ }
+
+ assertEquals(null, ((SuperClass) s2).foo());
+ try {
+ s2.foo();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError ie) {
+ // expected
+ }
+
+ try {
+ ((pkg.PkgSuperClass) s3).foo();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError ie) {
+ // expected
+ }
+
+ try {
+ ((SubClassFromPkg) s3).foo();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError ie) {
+ // expected
+ }
+
+ try {
+ s3.foo();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError ie) {
+ // expected
+ }
+
+ try {
+ ((SuperClassNoFoo) s4).foo();
+ throw new Error("Expected NoSuchMethodError");
+ } catch (NoSuchMethodError e) {
+ // expected
+ }
+
+ try {
+ ((SubClassNoFoo) s4).foo();
+ throw new Error("Expected AbstractMethodError");
+ } catch (AbstractMethodError e) {
+ // expected
+ }
+
+ try {
+ s4.foo();
+ throw new Error("Expected AbstractMethodError");
+ } catch (AbstractMethodError e) {
+ // expected
+ }
+
+ try {
+ ((SuperClassPrivateFoo) s5).foo();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError e) {
+ // expected
+ }
+
+ try {
+ ((SubClassPrivateFoo) s5).foo();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError e) {
+ // expected
+ }
+
+ try {
+ s5.foo();
+ throw new Error("Expected AbstractMethodError on RI, IllegalAccessError on ART");
+ } catch (AbstractMethodError | IllegalAccessError e) {
+ // expected
+ }
+
+ try {
+ ((SuperClassStaticFoo) s6).foo();
+ throw new Error("Expected IncompatibleClassChangeError");
+ } catch (IncompatibleClassChangeError e) {
+ // expected
+ }
+
+ try {
+ ((SubClassStaticFoo) s6).foo();
+ throw new Error("Expected IncompatibleClassChangeError");
+ } catch (IncompatibleClassChangeError e) {
+ // expected
+ }
+
+ try {
+ s6.foo();
+ throw new Error("Expected AbstractMethodError");
+ } catch (AbstractMethodError e) {
+ // expected
+ }
+ }
+}
+
+interface Interface {
+ public Class<?> foo();
+}
+
+class SubClass extends SuperClass implements Interface {
+}
+
+class SubClassFromPkg extends pkg.PkgSuperClass implements Interface {
+}
+
+class SubClassNoFoo extends SuperClassNoFoo implements Interface {
+}
+
+class SubClassPrivateFoo extends SuperClassPrivateFoo implements Interface {
+}
+
+class SubClassStaticFoo extends SuperClassStaticFoo implements Interface {
+}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src/SuperClass.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src/SuperClass.java
index a16764b..ece0188 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src/SuperClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+class SuperClass {
+ public Class<?> foo() {
+ return SuperClass.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src/SuperClassNoFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src/SuperClassNoFoo.java
index a16764b..747aaef 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src/SuperClassNoFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class SuperClassNoFoo {
+ public Class<?> foo() {
+ throw new Error("Unreachable");
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src/SuperClassPrivateFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src/SuperClassPrivateFoo.java
index a16764b..95af4f7 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src/SuperClassPrivateFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+class SuperClassPrivateFoo {
+ public Class<?> foo() {
+ return SuperClass.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src/SuperClassStaticFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src/SuperClassStaticFoo.java
index a16764b..490637f 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src/SuperClassStaticFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class SuperClassStaticFoo {
+ public Class<?> foo() {
+ throw new Error("Unreachable");
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src/pkg/PkgSuperClass.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src/pkg/PkgSuperClass.java
index a16764b..397ae28 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src/pkg/PkgSuperClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class PkgSuperClass {
+ public Class<?> foo() {
+ return PkgSuperClass.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src2/SuperClass.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src2/SuperClass.java
index a16764b..fe40c0a 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src2/SuperClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+class SuperClass {
+ Class<?> foo() {
+ return SuperClass.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src2/SuperClassNoFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src2/SuperClassNoFoo.java
index a16764b..c0e8c44 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src2/SuperClassNoFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,5 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class SuperClassNoFoo {
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src2/SuperClassPrivateFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src2/SuperClassPrivateFoo.java
index a16764b..f0c1c68 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src2/SuperClassPrivateFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+class SuperClassPrivateFoo {
+ private Class<?> foo() {
+ return SuperClass.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src2/SuperClassStaticFoo.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src2/SuperClassStaticFoo.java
index a16764b..ecf8bc1 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src2/SuperClassStaticFoo.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,8 @@
* limitations under the License.
*/
-package android.os;
-
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class SuperClassStaticFoo {
+ public static Class<?> foo() {
+ return SuperClassStaticFoo.class;
+ }
}
diff --git a/artd/binder/android/os/IArtd.aidl b/test/840-resolution/src2/pkg/PkgSuperClass.java
similarity index 74%
copy from artd/binder/android/os/IArtd.aidl
copy to test/840-resolution/src2/pkg/PkgSuperClass.java
index a16764b..2f333ee 100644
--- a/artd/binder/android/os/IArtd.aidl
+++ b/test/840-resolution/src2/pkg/PkgSuperClass.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -14,10 +14,10 @@
* limitations under the License.
*/
-package android.os;
+package pkg;
-/** {@hide} */
-interface IArtd {
- // Test to see if the artd service is available.
- boolean isAlive();
+public class PkgSuperClass {
+ Class<?> foo() {
+ return PkgSuperClass.class;
+ }
}
diff --git a/test/841-defaults/Android.bp b/test/841-defaults/Android.bp
new file mode 100644
index 0000000..2d2fce6
--- /dev/null
+++ b/test/841-defaults/Android.bp
@@ -0,0 +1,40 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `841-defaults`.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "art_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["art_license"],
+}
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-841-defaults",
+ defaults: ["art-run-test-defaults"],
+ test_config_template: ":art-run-test-target-template",
+ srcs: ["src/**/*.java"],
+ data: [
+ ":art-run-test-841-defaults-expected-stdout",
+ ":art-run-test-841-defaults-expected-stderr",
+ ],
+}
+
+// Test's expected standard output.
+genrule {
+ name: "art-run-test-841-defaults-expected-stdout",
+ out: ["art-run-test-841-defaults-expected-stdout.txt"],
+ srcs: ["expected-stdout.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
+
+// Test's expected standard error.
+genrule {
+ name: "art-run-test-841-defaults-expected-stderr",
+ out: ["art-run-test-841-defaults-expected-stderr.txt"],
+ srcs: ["expected-stderr.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/841-defaults/expected-stderr.txt b/test/841-defaults/expected-stderr.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/841-defaults/expected-stderr.txt
diff --git a/test/841-defaults/expected-stdout.txt b/test/841-defaults/expected-stdout.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/841-defaults/expected-stdout.txt
diff --git a/test/841-defaults/info.txt b/test/841-defaults/info.txt
new file mode 100644
index 0000000..85e265f
--- /dev/null
+++ b/test/841-defaults/info.txt
@@ -0,0 +1,2 @@
+Regression test for doing an invokeinterface on a default method whose
+dex method index is greater than the imt size.
diff --git a/test/841-defaults/src/Main.java b/test/841-defaults/src/Main.java
new file mode 100644
index 0000000..c07b516
--- /dev/null
+++ b/test/841-defaults/src/Main.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 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 Itf {
+ default int defaultMethod1() { return 1; }
+ default int defaultMethod2() { return 2; }
+ default int defaultMethod3() { return 3; }
+ default int defaultMethod4() { return 4; }
+ default int defaultMethod5() { return 5; }
+ default int defaultMethod6() { return 6; }
+ default int defaultMethod7() { return 7; }
+ default int defaultMethod8() { return 8; }
+ default int defaultMethod9() { return 9; }
+ default int defaultMethod10() { return 10; }
+ default int defaultMethod11() { return 11; }
+ default int defaultMethod12() { return 12; }
+ default int defaultMethod13() { return 13; }
+ default int defaultMethod14() { return 14; }
+ default int defaultMethod15() { return 15; }
+ default int defaultMethod16() { return 16; }
+ default int defaultMethod17() { return 17; }
+ default int defaultMethod18() { return 18; }
+ default int defaultMethod19() { return 19; }
+ default int defaultMethod20() { return 20; }
+ default int defaultMethod21() { return 21; }
+ default int defaultMethod22() { return 22; }
+ default int defaultMethod23() { return 23; }
+ default int defaultMethod24() { return 24; }
+ default int defaultMethod25() { return 25; }
+ default int defaultMethod26() { return 26; }
+ default int defaultMethod27() { return 27; }
+ default int defaultMethod28() { return 28; }
+ default int defaultMethod29() { return 29; }
+ default int defaultMethod30() { return 30; }
+ default int defaultMethod31() { return 31; }
+ default int defaultMethod32() { return 32; }
+ default int defaultMethod33() { return 33; }
+ default int defaultMethod34() { return 34; }
+ default int defaultMethod35() { return 35; }
+ default int defaultMethod36() { return 36; }
+ default int defaultMethod37() { return 37; }
+ default int defaultMethod38() { return 38; }
+ default int defaultMethod39() { return 39; }
+ default int defaultMethod40() { return 40; }
+ default int defaultMethod41() { return 41; }
+ default int defaultMethod42() { return 42; }
+ default int defaultMethod43() { return 43; }
+ default int defaultMethod44() { return 44; }
+ default int defaultMethod45() { return 45; }
+ default int defaultMethod46() { return 46; }
+ default int defaultMethod47() { return 47; }
+ default int defaultMethod48() { return 48; }
+ default int defaultMethod49() { return 49; }
+ default int defaultMethod50() { return 50; }
+ default int defaultMethod51() { return 51; }
+}
+
+public class Main implements Itf {
+ static Itf itf = new Main();
+ public static void assertEquals(int value, int expected) {
+ if (value != expected) {
+ throw new Error("Expected " + expected + ", got " + value);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ assertEquals(itf.defaultMethod1(), 1);
+ assertEquals(itf.defaultMethod2(), 2);
+ assertEquals(itf.defaultMethod3(), 3);
+ assertEquals(itf.defaultMethod4(), 4);
+ assertEquals(itf.defaultMethod5(), 5);
+ assertEquals(itf.defaultMethod6(), 6);
+ assertEquals(itf.defaultMethod7(), 7);
+ assertEquals(itf.defaultMethod8(), 8);
+ assertEquals(itf.defaultMethod9(), 9);
+ assertEquals(itf.defaultMethod10(), 10);
+ assertEquals(itf.defaultMethod11(), 11);
+ assertEquals(itf.defaultMethod12(), 12);
+ assertEquals(itf.defaultMethod13(), 13);
+ assertEquals(itf.defaultMethod14(), 14);
+ assertEquals(itf.defaultMethod15(), 15);
+ assertEquals(itf.defaultMethod16(), 16);
+ assertEquals(itf.defaultMethod17(), 17);
+ assertEquals(itf.defaultMethod18(), 18);
+ assertEquals(itf.defaultMethod19(), 19);
+ assertEquals(itf.defaultMethod20(), 20);
+ assertEquals(itf.defaultMethod21(), 21);
+ assertEquals(itf.defaultMethod22(), 22);
+ assertEquals(itf.defaultMethod23(), 23);
+ assertEquals(itf.defaultMethod24(), 24);
+ assertEquals(itf.defaultMethod25(), 25);
+ assertEquals(itf.defaultMethod26(), 26);
+ assertEquals(itf.defaultMethod27(), 27);
+ assertEquals(itf.defaultMethod28(), 28);
+ assertEquals(itf.defaultMethod29(), 29);
+ assertEquals(itf.defaultMethod30(), 30);
+ assertEquals(itf.defaultMethod31(), 31);
+ assertEquals(itf.defaultMethod32(), 32);
+ assertEquals(itf.defaultMethod33(), 33);
+ assertEquals(itf.defaultMethod34(), 34);
+ assertEquals(itf.defaultMethod35(), 35);
+ assertEquals(itf.defaultMethod36(), 36);
+ assertEquals(itf.defaultMethod37(), 37);
+ assertEquals(itf.defaultMethod38(), 38);
+ assertEquals(itf.defaultMethod39(), 39);
+ assertEquals(itf.defaultMethod40(), 40);
+ assertEquals(itf.defaultMethod41(), 41);
+ assertEquals(itf.defaultMethod42(), 42);
+ assertEquals(itf.defaultMethod43(), 43);
+ assertEquals(itf.defaultMethod44(), 44);
+ assertEquals(itf.defaultMethod45(), 45);
+ assertEquals(itf.defaultMethod46(), 46);
+ assertEquals(itf.defaultMethod47(), 47);
+ assertEquals(itf.defaultMethod48(), 48);
+ assertEquals(itf.defaultMethod49(), 49);
+ assertEquals(itf.defaultMethod50(), 50);
+ assertEquals(itf.defaultMethod51(), 51);
+ }
+}
diff --git a/test/912-classes/expected-stdout.txt b/test/912-classes/expected-stdout.txt
index 5da9f8c..787efc5 100644
--- a/test/912-classes/expected-stdout.txt
+++ b/test/912-classes/expected-stdout.txt
@@ -23,7 +23,7 @@
[public static final int java.lang.Integer.BYTES, static final byte[] java.lang.Integer.DigitOnes, static final byte[] java.lang.Integer.DigitTens, public static final int java.lang.Integer.MAX_VALUE, public static final int java.lang.Integer.MIN_VALUE, public static final int java.lang.Integer.SIZE, private static final java.lang.String[] java.lang.Integer.SMALL_NEG_VALUES, private static final java.lang.String[] java.lang.Integer.SMALL_NONNEG_VALUES, public static final java.lang.Class java.lang.Integer.TYPE, static final char[] java.lang.Integer.digits, private static final long java.lang.Integer.serialVersionUID, static final int[] java.lang.Integer.sizeTable, private final int java.lang.Integer.value]
[]
[]
-[java.lang.Integer(), public java.lang.Integer(int), public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.bitCount(int), public static int java.lang.Integer.compare(int,int), public static int java.lang.Integer.compareUnsigned(int,int), public static java.lang.Integer java.lang.Integer.decode(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.divideUnsigned(int,int), static void java.lang.Integer.formatUnsignedInt(int,int,byte[],int,int), static void java.lang.Integer.formatUnsignedInt(int,int,char[],int,int), static int java.lang.Integer.getChars(int,int,byte[]), static int java.lang.Integer.getChars(int,int,char[]), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,int), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,java.lang.Integer), public static int java.lang.Integer.hashCode(int), public static int java.lang.Integer.highestOneBit(int), public static int java.lang.Integer.lowestOneBit(int), public static int java.lang.Integer.max(int,int), public static int java.lang.Integer.min(int,int), public static int java.lang.Integer.numberOfLeadingZeros(int), public static int java.lang.Integer.numberOfTrailingZeros(int), public static int java.lang.Integer.parseInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.remainderUnsigned(int,int), public static int java.lang.Integer.reverse(int), public static int java.lang.Integer.reverseBytes(int), public static int java.lang.Integer.rotateLeft(int,int), public static int java.lang.Integer.rotateRight(int,int), public static int java.lang.Integer.signum(int), static int java.lang.Integer.stringSize(int), public static int java.lang.Integer.sum(int,int), public static java.lang.String java.lang.Integer.toBinaryString(int), public static java.lang.String java.lang.Integer.toHexString(int), public static java.lang.String java.lang.Integer.toOctalString(int), public static java.lang.String java.lang.Integer.toString(int), public static java.lang.String java.lang.Integer.toString(int,int), public static long java.lang.Integer.toUnsignedLong(int), public static java.lang.String java.lang.Integer.toUnsignedString(int), public static java.lang.String java.lang.Integer.toUnsignedString(int,int), private static java.lang.String java.lang.Integer.toUnsignedString0(int,int), public static java.lang.Integer java.lang.Integer.valueOf(int), public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String) throws java.lang.NumberFormatException, public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String,int) throws java.lang.NumberFormatException, public byte java.lang.Integer.byteValue(), public int java.lang.Integer.compareTo(java.lang.Integer), public int java.lang.Integer.compareTo(java.lang.Object), public double java.lang.Integer.doubleValue(), public boolean java.lang.Integer.equals(java.lang.Object), public float java.lang.Integer.floatValue(), public int java.lang.Integer.hashCode(), public int java.lang.Integer.intValue(), public long java.lang.Integer.longValue(), public short java.lang.Integer.shortValue(), public java.lang.String java.lang.Integer.toString()]
+[java.lang.Integer(), public java.lang.Integer(int), public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.bitCount(int), public static int java.lang.Integer.compare(int,int), public static int java.lang.Integer.compareUnsigned(int,int), public static java.lang.Integer java.lang.Integer.decode(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.divideUnsigned(int,int), private static void java.lang.Integer.formatUnsignedInt(int,int,byte[],int), static void java.lang.Integer.formatUnsignedInt(int,int,byte[],int,int), static void java.lang.Integer.formatUnsignedInt(int,int,char[],int,int), static int java.lang.Integer.getChars(int,int,byte[]), static int java.lang.Integer.getChars(int,int,char[]), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,int), public static java.lang.Integer java.lang.Integer.getInteger(java.lang.String,java.lang.Integer), public static int java.lang.Integer.hashCode(int), public static int java.lang.Integer.highestOneBit(int), public static int java.lang.Integer.lowestOneBit(int), public static int java.lang.Integer.max(int,int), public static int java.lang.Integer.min(int,int), public static int java.lang.Integer.numberOfLeadingZeros(int), public static int java.lang.Integer.numberOfTrailingZeros(int), public static int java.lang.Integer.parseInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.CharSequence,int,int,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String) throws java.lang.NumberFormatException, public static int java.lang.Integer.parseUnsignedInt(java.lang.String,int) throws java.lang.NumberFormatException, public static int java.lang.Integer.remainderUnsigned(int,int), public static int java.lang.Integer.reverse(int), public static int java.lang.Integer.reverseBytes(int), public static int java.lang.Integer.rotateLeft(int,int), public static int java.lang.Integer.rotateRight(int,int), public static int java.lang.Integer.signum(int), static int java.lang.Integer.stringSize(int), public static int java.lang.Integer.sum(int,int), public static java.lang.String java.lang.Integer.toBinaryString(int), public static java.lang.String java.lang.Integer.toHexString(int), public static java.lang.String java.lang.Integer.toOctalString(int), public static java.lang.String java.lang.Integer.toString(int), public static java.lang.String java.lang.Integer.toString(int,int), public static long java.lang.Integer.toUnsignedLong(int), public static java.lang.String java.lang.Integer.toUnsignedString(int), public static java.lang.String java.lang.Integer.toUnsignedString(int,int), private static java.lang.String java.lang.Integer.toUnsignedString0(int,int), public static java.lang.Integer java.lang.Integer.valueOf(int), public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String) throws java.lang.NumberFormatException, public static java.lang.Integer java.lang.Integer.valueOf(java.lang.String,int) throws java.lang.NumberFormatException, public byte java.lang.Integer.byteValue(), public int java.lang.Integer.compareTo(java.lang.Integer), public int java.lang.Integer.compareTo(java.lang.Object), public double java.lang.Integer.doubleValue(), public boolean java.lang.Integer.equals(java.lang.Object), public float java.lang.Integer.floatValue(), public int java.lang.Integer.hashCode(), public int java.lang.Integer.intValue(), public long java.lang.Integer.longValue(), public short java.lang.Integer.shortValue(), public java.lang.String java.lang.Integer.toString()]
[]
[]
int 100000
diff --git a/test/913-heaps/expected-stdout.txt b/test/913-heaps/expected-stdout.txt
index 9684503..5f882b1 100644
--- a/test/913-heaps/expected-stdout.txt
+++ b/test/913-heaps/expected-stdout.txt
@@ -197,7 +197,6 @@
---- untagged objects
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestNonRoot,vreg=8,location= 31])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 164])--> 1000@0 [size=123456780050, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
root@root --(thread)--> 3000@0 [size=120, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780050, length=-1]
@@ -245,7 +244,6 @@
root@root --(stack-local[id=1,tag=3000,depth=1,method=doFollowReferencesTestImpl,vreg=5,location= 8])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=13,location= 20])--> 1@1000 [size=16, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=2,method=doFollowReferencesTestRoot,vreg=4,location= 20])--> 1@1000 [size=16, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 164])--> 1000@0 [size=123456780055, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
root@root --(thread)--> 3000@0 [size=120, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780055, length=-1]
@@ -288,7 +286,6 @@
---
---- tagged classes
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 181])--> 1000@0 [size=123456780060, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
root@root --(thread)--> 3000@0 [size=120, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780060, length=-1]
@@ -316,7 +313,6 @@
6@1000 --(class)--> 1000@0 [size=123456780060, length=-1]
---
root@root --(jni-local[id=1,tag=3000,depth=0,method=followReferences])--> 3000@0 [size=120, length=-1]
-root@root --(stack-local[id=1,tag=3000,depth=4,method=runFollowReferences,vreg=3,location= 181])--> 1000@0 [size=123456780065, length=-1]
root@root --(stack-local[id=1,tag=3000,depth=5,method=run,vreg=2,location= 0])--> 3000@0 [size=120, length=-1]
root@root --(thread)--> 3000@0 [size=120, length=-1]
1001@0 --(superclass)--> 1000@0 [size=123456780065, length=-1]
diff --git a/test/913-heaps/heaps.cc b/test/913-heaps/heaps.cc
index 28a737d..98ea906 100644
--- a/test/913-heaps/heaps.cc
+++ b/test/913-heaps/heaps.cc
@@ -191,6 +191,12 @@
return 0;
}
+ // Ignore system classes, which may come from the JIT compiling a method
+ // in these classes.
+ if (reference_kind == JVMTI_HEAP_REFERENCE_SYSTEM_CLASS) {
+ return 0;
+ }
+
// Only check tagged objects.
if (tag == 0) {
return JVMTI_VISIT_OBJECTS;
diff --git a/test/948-change-annotations/build b/test/948-change-annotations/build
deleted file mode 100755
index 898e2e5..0000000
--- a/test/948-change-annotations/build
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-./default-build "$@" --experimental agents
diff --git a/test/677-fsi/build b/test/948-change-annotations/build.py
old mode 100755
new mode 100644
similarity index 79%
copy from test/677-fsi/build
copy to test/948-change-annotations/build.py
index b90b408..f0496d7
--- a/test/677-fsi/build
+++ b/test/948-change-annotations/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="agents")
diff --git a/test/677-fsi/build b/test/952-invoke-custom/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/952-invoke-custom/build.py
index b90b408..ce6dd89
--- a/test/677-fsi/build
+++ b/test/952-invoke-custom/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(use_desugar=False, api_level=28)
diff --git a/test/952-invoke-custom/build b/test/952-invoke-custom/generate-sources
similarity index 89%
rename from test/952-invoke-custom/build
rename to test/952-invoke-custom/generate-sources
index e835517..4244f8c 100755
--- a/test/952-invoke-custom/build
+++ b/test/952-invoke-custom/generate-sources
@@ -24,6 +24,3 @@
${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d classes $(find util-src -name '*.java')
${SOONG_ZIP} --jar -o transformer.jar -C classes -D classes
rm -rf classes
-
-# Use API level 28 for invoke-custom bytecode support.
-USE_DESUGAR=false ./default-build "$@" --api-level 28
diff --git a/test/952-invoke-custom/javac_wrapper.sh b/test/952-invoke-custom/javac_wrapper.sh
index 8659030..c8e0716 100755
--- a/test/952-invoke-custom/javac_wrapper.sh
+++ b/test/952-invoke-custom/javac_wrapper.sh
@@ -16,6 +16,8 @@
set -e # Stop on error - the caller script may not have this set.
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
+
# Update arguments to add transformer and ASM to the compiler classpath.
classpath="./transformer.jar:$ASM_JAR"
args=(-cp $classpath)
diff --git a/test/953-invoke-polymorphic-compiler/build b/test/953-invoke-polymorphic-compiler/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/953-invoke-polymorphic-compiler/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/953-invoke-polymorphic-compiler/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/953-invoke-polymorphic-compiler/build.py
index b90b408..e8e08eb
--- a/test/677-fsi/build
+++ b/test/953-invoke-polymorphic-compiler/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="method-handles")
diff --git a/test/954-invoke-polymorphic-verifier/build b/test/954-invoke-polymorphic-verifier/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/954-invoke-polymorphic-verifier/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/954-invoke-polymorphic-verifier/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/954-invoke-polymorphic-verifier/build.py
index b90b408..e8e08eb
--- a/test/677-fsi/build
+++ b/test/954-invoke-polymorphic-verifier/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="method-handles")
diff --git a/test/955-methodhandles-smali/build b/test/955-methodhandles-smali/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/955-methodhandles-smali/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/955-methodhandles-smali/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/955-methodhandles-smali/build.py
index b90b408..e8e08eb
--- a/test/677-fsi/build
+++ b/test/955-methodhandles-smali/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="method-handles")
diff --git a/test/956-methodhandles/build b/test/956-methodhandles/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/956-methodhandles/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/956-methodhandles/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/956-methodhandles/build.py
index b90b408..e8e08eb
--- a/test/677-fsi/build
+++ b/test/956-methodhandles/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="method-handles")
diff --git a/test/957-methodhandle-transforms/build b/test/957-methodhandle-transforms/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/957-methodhandle-transforms/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/957-methodhandle-transforms/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/957-methodhandle-transforms/build.py
index b90b408..e8e08eb
--- a/test/677-fsi/build
+++ b/test/957-methodhandle-transforms/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="method-handles")
diff --git a/test/957-methodhandle-transforms/src/Main.java b/test/957-methodhandle-transforms/src/Main.java
index 2b16485..3ee270d 100644
--- a/test/957-methodhandle-transforms/src/Main.java
+++ b/test/957-methodhandle-transforms/src/Main.java
@@ -1557,7 +1557,7 @@
try {
adapter = MethodHandles.collectArguments(target, 3, filter);
fail();
- } catch (IndexOutOfBoundsException expected) {
+ } catch (IndexOutOfBoundsException | IllegalArgumentException expected) {
}
// Mismatch in filter return type.
diff --git a/test/958-methodhandle-stackframe/build b/test/958-methodhandle-stackframe/build
deleted file mode 100755
index 2b0b2c1..0000000
--- a/test/958-methodhandle-stackframe/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/958-methodhandle-stackframe/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/958-methodhandle-stackframe/build.py
index b90b408..e8e08eb
--- a/test/677-fsi/build
+++ b/test/958-methodhandle-stackframe/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="method-handles")
diff --git a/test/959-invoke-polymorphic-accessors/build b/test/959-invoke-polymorphic-accessors/build
deleted file mode 100644
index 2b0b2c1..0000000
--- a/test/959-invoke-polymorphic-accessors/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2016 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental method-handles
diff --git a/test/677-fsi/build b/test/959-invoke-polymorphic-accessors/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/959-invoke-polymorphic-accessors/build.py
index b90b408..e8e08eb
--- a/test/677-fsi/build
+++ b/test/959-invoke-polymorphic-accessors/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="method-handles")
diff --git a/test/959-invoke-polymorphic-accessors/src/Main.java b/test/959-invoke-polymorphic-accessors/src/Main.java
index 03fd285..e4a6f94 100644
--- a/test/959-invoke-polymorphic-accessors/src/Main.java
+++ b/test/959-invoke-polymorphic-accessors/src/Main.java
@@ -793,6 +793,10 @@
Long z = (Long) h0.invoke(valueHolder);
fail();
} catch (WrongMethodTypeException expected) {}
+ try {
+ int x = (int) h0.invokeExact((ValueHolder) null);
+ fail();
+ } catch (NullPointerException expected) {}
}
/*package*/ static Number getDoubleAsNumber() {
@@ -822,6 +826,10 @@
h0.invoke(valueHolder, (Float) null);
fail();
} catch (NullPointerException expected) {}
+ try {
+ h0.invoke((ValueHolder) null, Float.valueOf(1.0f));
+ fail();
+ } catch (NullPointerException expected) {}
// Test that type conversion checks work on small field types.
short temp = (short) s0.invoke(valueHolder, new Byte((byte) 45));
diff --git a/test/677-fsi/build b/test/960-default-smali/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/960-default-smali/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/960-default-smali/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/960-default-smali/build b/test/960-default-smali/generate-sources
similarity index 93%
rename from test/960-default-smali/build
rename to test/960-default-smali/generate-sources
index 44d6bd2..1fffc71 100755
--- a/test/960-default-smali/build
+++ b/test/960-default-smali/generate-sources
@@ -19,5 +19,3 @@
# Generate the Main.java file or fail
${ANDROID_BUILD_TOP}/art/test/utils/python/generate_java_main.py ./src
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/961-default-iface-resolution-gen/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/961-default-iface-resolution-gen/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/961-default-iface-resolution-gen/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/961-default-iface-resolution-gen/build b/test/961-default-iface-resolution-gen/generate-sources
similarity index 93%
rename from test/961-default-iface-resolution-gen/build
rename to test/961-default-iface-resolution-gen/generate-sources
index b9b36d0..4d12e5f 100755
--- a/test/961-default-iface-resolution-gen/build
+++ b/test/961-default-iface-resolution-gen/generate-sources
@@ -21,5 +21,3 @@
# Generate the smali files and expected-stdout.txt or fail
./util-src/generate_java.py ./src ./expected-stdout.txt
-
-./default-build "$@" --experimental default-methods
diff --git a/test/962-iface-static/build b/test/962-iface-static/build
deleted file mode 100644
index 82f4931..0000000
--- a/test/962-iface-static/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2017 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.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/962-iface-static/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/962-iface-static/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/962-iface-static/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/677-fsi/build b/test/964-default-iface-init-gen/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/964-default-iface-init-gen/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/964-default-iface-init-gen/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/964-default-iface-init-gen/build b/test/964-default-iface-init-gen/generate-sources
similarity index 93%
rename from test/964-default-iface-init-gen/build
rename to test/964-default-iface-init-gen/generate-sources
index b9b36d0..4d12e5f 100755
--- a/test/964-default-iface-init-gen/build
+++ b/test/964-default-iface-init-gen/generate-sources
@@ -21,5 +21,3 @@
# Generate the smali files and expected-stdout.txt or fail
./util-src/generate_java.py ./src ./expected-stdout.txt
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/968-default-partial-compile-gen/build.py
old mode 100755
new mode 100644
similarity index 73%
copy from test/677-fsi/build
copy to test/968-default-partial-compile-gen/build.py
index b90b408..e4416ec
--- a/test/677-fsi/build
+++ b/test/968-default-partial-compile-gen/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+import os
+
+if os.environ["BUILD_MODE"] != "jvm":
+ build_run_test(experimental="default-methods")
diff --git a/test/968-default-partial-compile-gen/build b/test/968-default-partial-compile-gen/generate-sources
similarity index 93%
rename from test/968-default-partial-compile-gen/build
rename to test/968-default-partial-compile-gen/generate-sources
index 04532b0..83aad65 100755
--- a/test/968-default-partial-compile-gen/build
+++ b/test/968-default-partial-compile-gen/generate-sources
@@ -31,6 +31,4 @@
mkdir -p ./smali
# Generate the smali files and expected-stdout.txt or fail
./util-src/generate_smali.py ./smali ./expected-stdout.txt
- # Use the default build script
- ./default-build "$@" --experimental default-methods
fi
diff --git a/test/677-fsi/build b/test/969-iface-super/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/969-iface-super/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/969-iface-super/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/969-iface-super/build b/test/969-iface-super/generate-sources
similarity index 93%
rename from test/969-iface-super/build
rename to test/969-iface-super/generate-sources
index 44d6bd2..1fffc71 100755
--- a/test/969-iface-super/build
+++ b/test/969-iface-super/generate-sources
@@ -19,5 +19,3 @@
# Generate the Main.java file or fail
${ANDROID_BUILD_TOP}/art/test/utils/python/generate_java_main.py ./src
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/970-iface-super-resolution-gen/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/970-iface-super-resolution-gen/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/970-iface-super-resolution-gen/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/970-iface-super-resolution-gen/build b/test/970-iface-super-resolution-gen/generate-sources
similarity index 94%
rename from test/970-iface-super-resolution-gen/build
rename to test/970-iface-super-resolution-gen/generate-sources
index 6eecd71..175497f 100755
--- a/test/970-iface-super-resolution-gen/build
+++ b/test/970-iface-super-resolution-gen/generate-sources
@@ -29,5 +29,3 @@
mkdir -p smali
./util-src/generate_smali.py ./smali ./expected-stdout.txt
fi
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/971-iface-super/build.py
old mode 100755
new mode 100644
similarity index 73%
copy from test/677-fsi/build
copy to test/971-iface-super/build.py
index b90b408..e4416ec
--- a/test/677-fsi/build
+++ b/test/971-iface-super/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+import os
+
+if os.environ["BUILD_MODE"] != "jvm":
+ build_run_test(experimental="default-methods")
diff --git a/test/971-iface-super/build b/test/971-iface-super/generate-sources
similarity index 93%
rename from test/971-iface-super/build
rename to test/971-iface-super/generate-sources
index 04532b0..83aad65 100755
--- a/test/971-iface-super/build
+++ b/test/971-iface-super/generate-sources
@@ -31,6 +31,4 @@
mkdir -p ./smali
# Generate the smali files and expected-stdout.txt or fail
./util-src/generate_smali.py ./smali ./expected-stdout.txt
- # Use the default build script
- ./default-build "$@" --experimental default-methods
fi
diff --git a/test/677-fsi/build b/test/975-iface-private/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/975-iface-private/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/975-iface-private/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/978-virtual-interface/build b/test/978-virtual-interface/build
deleted file mode 100755
index 14230c2..0000000
--- a/test/978-virtual-interface/build
+++ /dev/null
@@ -1,20 +0,0 @@
-#!/bin/bash
-#
-# Copyright 2015 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-# make us exit on a failure
-set -e
-
-./default-build "$@" --experimental default-methods
diff --git a/test/677-fsi/build b/test/978-virtual-interface/build.py
old mode 100755
new mode 100644
similarity index 78%
copy from test/677-fsi/build
copy to test/978-virtual-interface/build.py
index b90b408..48cb485
--- a/test/677-fsi/build
+++ b/test/978-virtual-interface/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(experimental="default-methods")
diff --git a/test/677-fsi/build b/test/979-const-method-handle/build.py
old mode 100755
new mode 100644
similarity index 80%
copy from test/677-fsi/build
copy to test/979-const-method-handle/build.py
index b90b408..193aa91
--- a/test/677-fsi/build
+++ b/test/979-const-method-handle/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+from art_build_rules import build_run_test
+
+build_run_test(api_level=28)
diff --git a/test/979-const-method-handle/build b/test/979-const-method-handle/generate-sources
similarity index 89%
rename from test/979-const-method-handle/build
rename to test/979-const-method-handle/generate-sources
index fa6a0ea..d102d73 100755
--- a/test/979-const-method-handle/build
+++ b/test/979-const-method-handle/generate-sources
@@ -24,6 +24,3 @@
${JAVAC:-javac} ${JAVAC_ARGS} -cp "${ASM_JAR}" -d classes $(find util-src -name '*.java')
${SOONG_ZIP} --jar -o transformer.jar -C classes -D classes
rm -rf classes
-
-# Use API level 28 for DEX file support constant method handles.
-./default-build "$@" --api-level 28
diff --git a/test/979-const-method-handle/javac_wrapper.sh b/test/979-const-method-handle/javac_wrapper.sh
index 77b6bc3..5ed8481 100755
--- a/test/979-const-method-handle/javac_wrapper.sh
+++ b/test/979-const-method-handle/javac_wrapper.sh
@@ -16,6 +16,8 @@
set -e
+export ASM_JAR="${ANDROID_BUILD_TOP}/prebuilts/misc/common/asm/asm-9.2.jar"
+
# Add annotation src files to our compiler inputs.
asrcs=util-src/annotations/*.java
@@ -32,3 +34,6 @@
transformed_class=classes/$(basename ${class})
${JAVA:-java} ${transformer_args} ${class} ${transformed_class}
done
+
+# Remove class which we want missing at runtime.
+rm classes/MissingType.class
diff --git a/test/979-const-method-handle/src/Main.java b/test/979-const-method-handle/src/Main.java
index 5368a22..04d782b 100644
--- a/test/979-const-method-handle/src/Main.java
+++ b/test/979-const-method-handle/src/Main.java
@@ -72,6 +72,14 @@
return null;
}
+ @ConstantMethodType(
+ returnType = void.class,
+ parameterTypes = {MissingType.class})
+ private static MethodType missingType() {
+ unreachable();
+ return null;
+ }
+
private static void repeatConstMethodType0(MethodType expected) {
System.out.print("repeatConstMethodType0(");
System.out.print(expected);
@@ -189,6 +197,16 @@
return null;
}
+ @ConstantMethodHandle(
+ kind = ConstantMethodHandle.STATIC_GET,
+ owner = "PrivateMember",
+ fieldOrMethodName = "privateField",
+ descriptor = "I")
+ private static MethodHandle getPrivateField() {
+ unreachable();
+ return null;
+ }
+
private static void repeatConstMethodHandle() throws Throwable {
System.out.println("repeatConstMethodHandle()");
String[] values = {"A", "B", "C"};
@@ -243,5 +261,37 @@
System.out.println("Stack: capacity was " + stack.capacity());
stackTrim().invokeExact(stack);
System.out.println("Stack: capacity is " + stack.capacity());
+
+ // We used to not report in the compiler that loading a ConstMethodHandle/ConstMethodType
+ // can throw, which meant we were not catching the exception in the situation where we
+ // inline the loading.
+ try {
+ $inline$getPrivateField();
+ throw new Error("Expected IllegalAccessError");
+ } catch (IllegalAccessError e) {
+ // expected
+ }
+
+ try {
+ $inline$missingType();
+ throw new Error("Expected NoClassDefFoundError");
+ } catch (NoClassDefFoundError e) {
+ // expected
+ }
}
+
+ public static void $inline$getPrivateField() {
+ getPrivateField();
+ }
+
+ public static void $inline$missingType() {
+ missingType();
+ }
+}
+
+class PrivateMember {
+ private static int privateField;
+}
+
+class MissingType {
}
diff --git a/test/988-method-trace/expected-stdout.txt b/test/988-method-trace/expected-stdout.txt
index 59077b9..c5ab4ed 100644
--- a/test/988-method-trace/expected-stdout.txt
+++ b/test/988-method-trace/expected-stdout.txt
@@ -13,13 +13,19 @@
..=> public java.lang.Object()
..<= public java.lang.Object() -> <null: null>
.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
+...=> private void java.util.ArrayList.ensureCapacityInternal(int)
+....=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+....<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+...<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
fibonacci(30)=832040
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
=> art.Test988$RecurOp()
.=> public java.lang.Object()
@@ -62,13 +68,19 @@
..=> public java.lang.Object()
..<= public java.lang.Object() -> <null: null>
.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
+...=> private void java.util.ArrayList.ensureCapacityInternal(int)
+....=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+....<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+...<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
fibonacci(5)=5
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
=> art.Test988$NativeOp()
.=> public java.lang.Object()
@@ -83,13 +95,19 @@
..=> public java.lang.Object()
..<= public java.lang.Object() -> <null: null>
.<= public art.Test988$FibResult(java.lang.String,int,int) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
+...=> private void java.util.ArrayList.ensureCapacityInternal(int)
+....=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+....<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+...<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
fibonacci(5)=5
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
=> art.Test988$IterOp()
.=> public java.lang.Object()
@@ -170,10 +188,10 @@
......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
- art.Test988.iter_fibonacci(Test988.java:269)
- art.Test988$IterOp.applyAsInt(Test988.java:264)
- art.Test988.doFibTest(Test988.java:402)
- art.Test988.run(Test988.java:358)
+ art.Test988.iter_fibonacci(Test988.java:280)
+ art.Test988$IterOp.applyAsInt(Test988.java:275)
+ art.Test988.doFibTest(Test988.java:413)
+ art.Test988.run(Test988.java:369)
<additional hidden frames>
>
....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -184,19 +202,25 @@
..=> public java.lang.Object()
..<= public java.lang.Object() -> <null: null>
.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
+...=> private void java.util.ArrayList.ensureCapacityInternal(int)
+....=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+....<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+...<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
- art.Test988.iter_fibonacci(Test988.java:269)
- art.Test988$IterOp.applyAsInt(Test988.java:264)
- art.Test988.doFibTest(Test988.java:402)
- art.Test988.run(Test988.java:358)
+ art.Test988.iter_fibonacci(Test988.java:280)
+ art.Test988$IterOp.applyAsInt(Test988.java:275)
+ art.Test988.doFibTest(Test988.java:413)
+ art.Test988.run(Test988.java:369)
<additional hidden frames>
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
=> art.Test988$RecurOp()
.=> public java.lang.Object()
@@ -277,10 +301,10 @@
......=> private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace()
......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: Bad argument: -19 < 0
- art.Test988.fibonacci(Test988.java:291)
- art.Test988$RecurOp.applyAsInt(Test988.java:286)
- art.Test988.doFibTest(Test988.java:402)
- art.Test988.run(Test988.java:359)
+ art.Test988.fibonacci(Test988.java:302)
+ art.Test988$RecurOp.applyAsInt(Test988.java:297)
+ art.Test988.doFibTest(Test988.java:413)
+ art.Test988.run(Test988.java:370)
<additional hidden frames>
>
....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -291,19 +315,25 @@
..=> public java.lang.Object()
..<= public java.lang.Object() -> <null: null>
.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
+...=> private void java.util.ArrayList.ensureCapacityInternal(int)
+....=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+....<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+...<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
fibonacci(-19) -> java.lang.Error: Bad argument: -19 < 0
- art.Test988.fibonacci(Test988.java:291)
- art.Test988$RecurOp.applyAsInt(Test988.java:286)
- art.Test988.doFibTest(Test988.java:402)
- art.Test988.run(Test988.java:359)
+ art.Test988.fibonacci(Test988.java:302)
+ art.Test988$RecurOp.applyAsInt(Test988.java:297)
+ art.Test988.doFibTest(Test988.java:413)
+ art.Test988.run(Test988.java:370)
<additional hidden frames>
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
=> art.Test988$NativeOp()
.=> public java.lang.Object()
@@ -323,9 +353,9 @@
......<= private static java.lang.Object java.lang.Throwable.nativeFillInStackTrace() -> <class [Ljava.lang.Object;: <non-deterministic>>
.....<= public synchronized java.lang.Throwable java.lang.Throwable.fillInStackTrace() -> <class java.lang.Error: java.lang.Error: bad argument
art.Test988.nativeFibonacci(Native Method)
- art.Test988$NativeOp.applyAsInt(Test988.java:301)
- art.Test988.doFibTest(Test988.java:402)
- art.Test988.run(Test988.java:360)
+ art.Test988$NativeOp.applyAsInt(Test988.java:312)
+ art.Test988.doFibTest(Test988.java:413)
+ art.Test988.run(Test988.java:371)
<additional hidden frames>
>
....<= public java.lang.Throwable(java.lang.String) -> <null: null>
@@ -336,19 +366,25 @@
..=> public java.lang.Object()
..<= public java.lang.Object() -> <null: null>
.<= public art.Test988$FibThrow(java.lang.String,int,java.lang.Throwable) -> <null: null>
-.=> public boolean java.util.ArrayList.add(java.lang.Object)
-..=> private void java.util.ArrayList.ensureCapacityInternal(int)
+.=> static void art.Test988.addToResults(art.Test988$Printable)
+..=> public void java.util.ArrayList.ensureCapacity(int)
...=> private void java.util.ArrayList.ensureExplicitCapacity(int)
...<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
-..<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
+..<= public void java.util.ArrayList.ensureCapacity(int) -> <null: null>
+..=> public boolean java.util.ArrayList.add(java.lang.Object)
+...=> private void java.util.ArrayList.ensureCapacityInternal(int)
+....=> private void java.util.ArrayList.ensureExplicitCapacity(int)
+....<= private void java.util.ArrayList.ensureExplicitCapacity(int) -> <null: null>
+...<= private void java.util.ArrayList.ensureCapacityInternal(int) -> <null: null>
fibonacci(-19) -> java.lang.Error: bad argument
art.Test988.nativeFibonacci(Native Method)
- art.Test988$NativeOp.applyAsInt(Test988.java:301)
- art.Test988.doFibTest(Test988.java:402)
- art.Test988.run(Test988.java:360)
+ art.Test988$NativeOp.applyAsInt(Test988.java:312)
+ art.Test988.doFibTest(Test988.java:413)
+ art.Test988.run(Test988.java:371)
<additional hidden frames>
-.<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+..<= public boolean java.util.ArrayList.add(java.lang.Object) -> <class java.lang.Boolean: true>
+.<= static void art.Test988.addToResults(art.Test988$Printable) -> <null: null>
<= public static void art.Test988.doFibTest(int,java.util.function.IntUnaryOperator) -> <null: null>
=> public final void <non-deterministic-type 0>.run()
.=> private static java.lang.Object java.lang.reflect.Proxy.invoke(java.lang.reflect.Proxy,java.lang.reflect.Method,java.lang.Object[]) throws java.lang.Throwable
diff --git a/test/988-method-trace/src/art/Test988.java b/test/988-method-trace/src/art/Test988.java
index 9c8ce4a..227ce62 100644
--- a/test/988-method-trace/src/art/Test988.java
+++ b/test/988-method-trace/src/art/Test988.java
@@ -253,11 +253,22 @@
}
}
- private static List<Printable> results = new ArrayList<>();
+ private static ArrayList<Printable> results = new ArrayList<>();
+ private static int results_index = 0;
// Starts with => enableMethodTracing
// .=> enableTracing
private static int cnt = 2;
+ static void addToResults(Printable obj) {
+ // Reserve space for the current object. If any other method entry callbacks are called they
+ // will reserve more space. Without this we may get into strange problems where ArrayList::add
+ // cecks there is enough space (which involves a couple of method calls) which then use up the
+ // space and by the time we actually add this record there is no capacity left.
+ results_index++;
+ results.ensureCapacity(results_index + 1);
+ results.add(obj);
+ }
+
// Iterative version
static final class IterOp implements IntUnaryOperator {
public int applyAsInt(int x) {
@@ -319,7 +330,7 @@
if ((cnt - 1) > METHOD_TRACING_IGNORE_DEPTH && sMethodTracingIgnore) {
return;
}
- results.add(new MethodEntry(m, cnt - 1));
+ addToResults(new MethodEntry(m, cnt - 1));
}
public static void notifyMethodExit(Executable m, boolean exception, Object result) {
@@ -330,9 +341,9 @@
}
if (exception) {
- results.add(new MethodThrownThrough(m, cnt));
+ addToResults(new MethodThrownThrough(m, cnt));
} else {
- results.add(new MethodReturn(m, result, cnt));
+ addToResults(new MethodReturn(m, result, cnt));
}
}
@@ -400,9 +411,9 @@
public static void doFibTest(int x, IntUnaryOperator op) {
try {
int y = op.applyAsInt(x);
- results.add(new FibResult("fibonacci(%d)=%d\n", x, y));
+ addToResults(new FibResult("fibonacci(%d)=%d\n", x, y));
} catch (Throwable t) {
- results.add(new FibThrow("fibonacci(%d) -> %s\n", x, t));
+ addToResults(new FibThrow("fibonacci(%d) -> %s\n", x, t));
}
}
diff --git a/test/999-redefine-hiddenapi/build b/test/999-redefine-hiddenapi/build.py
similarity index 80%
rename from test/999-redefine-hiddenapi/build
rename to test/999-redefine-hiddenapi/build.py
index f4b029f..8367ad0 100644
--- a/test/999-redefine-hiddenapi/build
+++ b/test/999-redefine-hiddenapi/build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+from art_build_rules import build_run_test
+
+build_run_test(use_hiddenapi=True)
diff --git a/test/Android.bp b/test/Android.bp
index 8a1ca84..4976c01 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -104,10 +104,10 @@
],
}
-// Variant of art_test_defaults that installs the library in a location which
-// will be added to the default namespace, and hence also the com_android_art
-// namespace. That allows the library to have shared_libs dependencies on all
-// ART internal libraries.
+// Variant of art_test_defaults for test libraries that installs them in a
+// location which will be added to the default namespace, and hence also the
+// com_android_art namespace. That allows them to have shared_libs
+// dependencies on all ART internal libraries.
//
// Currently this only works for run tests where run-test-jar sets
// LD_LIBRARY_PATH and NATIVELOADER_DEFAULT_NAMESPACE_LIBS.
@@ -170,6 +170,21 @@
// eventually.
host_supported: false,
test_config_template: ":art-gtests-target-standalone-template",
+
+ // Support multilib variants (using different suffix per sub-architecture),
+ // which is needed on build targets with secondary architectures, as the
+ // CTS/MTS/etc test suite packaging logic flattens all test artifacts into a
+ // single `testcases` directory. Also, there is CI testing that expects
+ // 64-bit multilib test suites to work for 32-bit devices (b/233550842).
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
}
// Properties common to `art_gtest_defaults` and `art_standalone_gtest_defaults`.
@@ -399,7 +414,6 @@
],
shared_libs: [
"libbase",
- "libbacktrace",
"liblog",
],
target: {
@@ -581,7 +595,6 @@
name: "libartagent-defaults",
defaults: ["art_test_internal_library_defaults"],
shared_libs: [
- "libbacktrace",
"libbase",
"liblog",
"libnativehelper",
@@ -689,6 +702,7 @@
"1932-monitor-events-misc/monitor_misc.cc",
"1934-jvmti-signal-thread/signal_threads.cc",
"1939-proxy-frames/local_instance.cc",
+ "1940-ddms-ext/ddm_ext.cc",
"1941-dispose-stress/dispose_stress.cc",
"1942-suspend-raw-monitor-exit/native_suspend_monitor.cc",
"1943-suspend-raw-monitor-wait/native_suspend_monitor.cc",
@@ -736,7 +750,6 @@
"936-search-onload/search_onload.cc",
"980-redefine-object/redef_object.cc",
"983-source-transform-verify/source_transform_art.cc",
- "1940-ddms-ext/ddm_ext.cc",
// "1952-pop-frame-jit/pop_frame.cc",
"1959-redefine-object-instrument/fake_redef_object.cc",
"1960-obsolete-jit-multithread-native/native_say_hi.cc",
@@ -949,10 +962,10 @@
"common/stack_inspect.cc",
],
shared_libs: [
- "libbacktrace",
"libbase",
"liblog",
"libnativehelper",
+ "libunwindstack",
],
}
@@ -1075,6 +1088,7 @@
"1936-thread-end-events/src/art/Test1936.java",
"1937-transform-soft-fail/src/art/Test1937.java",
"1939-proxy-frames/src/art/Test1939.java",
+ "1940-ddms-ext/src-art/art/Test1940.java",
"1941-dispose-stress/src/art/Test1941.java",
"1942-suspend-raw-monitor-exit/src/art/Test1942.java",
"1943-suspend-raw-monitor-wait/src/art/Test1943.java",
@@ -1121,6 +1135,8 @@
"2007-virtual-structural-finalizable/src-art/art/Test2007.java",
],
sdk_version: "core_platform",
+ // Make sure that this will be added to the sdk snapshot for S.
+ min_sdk_version: "S",
// Some ART run-tests contain constructs which break ErrorProne checks;
// disable `errorprone` builds.
errorprone: {
@@ -1220,6 +1236,7 @@
"1936-thread-end-events/expected-stdout.txt",
"1937-transform-soft-fail/expected-stdout.txt",
"1939-proxy-frames/expected-stdout.txt",
+ "1940-ddms-ext/expected-stdout.txt",
"1941-dispose-stress/expected-stdout.txt",
"1942-suspend-raw-monitor-exit/expected-stdout.txt",
"1943-suspend-raw-monitor-wait/expected-stdout.txt",
@@ -1284,6 +1301,8 @@
"expected_cts_outputs_gen",
],
sdk_version: "core_current",
+ // Make sure that this will be added to the sdk snapshot for S.
+ min_sdk_version: "S",
}
art_cc_test {
@@ -1381,6 +1400,7 @@
srcs: [
":art-gtest-jars-AbstractMethod",
":art-gtest-jars-AllFields",
+ ":art-gtest-jars-ArrayClassWithUnresolvedComponent",
":art-gtest-jars-DefaultMethods",
":art-gtest-jars-ErroneousA",
":art-gtest-jars-ErroneousB",
@@ -1427,6 +1447,7 @@
":art-gtest-jars-MainStripped",
":art-gtest-jars-MainUncompressedAligned",
":art-gtest-jars-MultiDexUncompressedAligned",
+ ":art-gtest-jars-SuperWithAccessChecks",
":art-gtest-jars-VerifierDeps",
":art-gtest-jars-VerifierDepsMulti",
":art-gtest-jars-VerifySoftFailDuringClinit",
@@ -1832,6 +1853,20 @@
}
genrule {
+ name: "art-gtest-jars-ArrayClassWithUnresolvedComponent",
+ defaults: ["art-gtest-jars-smali-defaults"],
+ srcs: ["ArrayClassWithUnresolvedComponent/*.smali"],
+ out: ["art-gtest-jars-ArrayClassWithUnresolvedComponent.dex"],
+}
+
+genrule {
+ name: "art-gtest-jars-SuperWithAccessChecks",
+ defaults: ["art-gtest-jars-smali-defaults"],
+ srcs: ["SuperWithAccessChecks/*.smali"],
+ out: ["art-gtest-jars-SuperWithAccessChecks.dex"],
+}
+
+genrule {
name: "art-gtest-jars-LinkageTest",
defaults: ["art-gtest-jars-smali-defaults"],
srcs: ["LinkageTest/*.smali"],
@@ -1853,11 +1888,13 @@
"art_module_source_build_genrule_defaults",
],
tool_files: [
+ "art_build_rules.py",
"run-test-build.py",
"buildfailures.json",
- "etc/default-build",
- "etc/default-run",
+ "etc/default-build.py",
"etc/default-check",
+ "etc/default-run",
+ "etc/run-test-jar",
":art-run-test-bootclasspath",
],
tools: [
diff --git a/test/677-fsi/build b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali
old mode 100755
new mode 100644
similarity index 77%
copy from test/677-fsi/build
copy to test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali
index b90b408..f68d70c
--- a/test/677-fsi/build
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingInterface.smali
@@ -1,6 +1,4 @@
-#!/bin/bash
-#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+.class public LClassWithMissingInterface;
+
+.super Ljava/lang/Object;
+.implements LMissingInterface;
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali
similarity index 81%
copy from test/2038-hiddenapi-jvmti-ext/build
copy to test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali
index f4b029f..134302c 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithMissingSuper.smali
@@ -1,6 +1,4 @@
-#!/bin/bash
-#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+.class public LClassWithMissingSuper;
+
+.super LMissingClass;
diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali
new file mode 100644
index 0000000..f35e5f0
--- /dev/null
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithStatic.smali
@@ -0,0 +1,28 @@
+# Copyright (C) 2022 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.
+
+.class public LClassWithStatic;
+
+.super Ljava/lang/Object;
+
+.method static constructor <clinit>()V
+ .registers 1
+ const-string v0, "[LClassWithMissingInterface;"
+ invoke-static {v0}, Ljava/lang/Class;->forName(Ljava/lang/String;)Ljava/lang/Class;
+ move-result-object v0
+ sput-object v0, LClassWithStatic;->field:Ljava/lang/Class;
+ return-void
+.end method
+
+.field public static field:Ljava/lang/Class;
diff --git a/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali b/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali
new file mode 100644
index 0000000..68aecfb
--- /dev/null
+++ b/test/ArrayClassWithUnresolvedComponent/ClassWithStaticConst.smali
@@ -0,0 +1,26 @@
+# Copyright (C) 2022 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.
+
+.class public LClassWithStaticConst;
+
+.super Ljava/lang/Object;
+
+.method static constructor <clinit>()V
+ .registers 1
+ const-class v0, [LClassWithMissingInterface;
+ sput-object v0, LClassWithStaticConst;->field:Ljava/lang/Class;
+ return-void
+.end method
+
+.field public static field:Ljava/lang/Class;
diff --git a/test/README.chroot.md b/test/README.chroot.md
index 49b76ba..b97e56c 100644
--- a/test/README.chroot.md
+++ b/test/README.chroot.md
@@ -50,19 +50,61 @@
```
2. Set lunch target and ADB:
* With a minimal `aosp/master-art` tree:
- ```bash
- export SOONG_ALLOW_MISSING_DEPENDENCIES=true
- . ./build/envsetup.sh
- lunch armv8-eng # or arm_krait-eng for 32-bit ARM
- export PATH="$(pwd)/prebuilts/runtime:$PATH"
- export ADB="$ANDROID_BUILD_TOP/prebuilts/runtime/adb"
- ```
+ 1. Initialize the environment:
+ ```bash
+ export SOONG_ALLOW_MISSING_DEPENDENCIES=true
+ . ./build/envsetup.sh
+ ```
+ 2. Select a lunch target corresponding to the architecture you want to
+ build and test:
+ * For (32-bit) Arm:
+ ```bash
+ lunch arm_krait-eng
+ ```
+ * For (64-bit only) Arm64:
+ ```bash
+ lunch armv8-eng
+ ```
+ * For (32- and 64-bit) Arm64:
+ ```bash
+ lunch arm_v7_v8-eng
+ ```
+ * For (32-bit) Intel x86:
+ ```bash
+ lunch silvermont-eng
+ ```
+ 3. Set up the environment to use a pre-built ADB:
+ ```bash
+ export PATH="$(pwd)/prebuilts/runtime:$PATH"
+ export ADB="$ANDROID_BUILD_TOP/prebuilts/runtime/adb"
+ ```
* With a full Android (AOSP) `aosp/master` tree:
- ```bash
- . ./build/envsetup.sh
- lunch aosp_arm64-eng # or aosp_arm-eng for 32-bit ARM
- m adb
- ```
+ 1. Initialize the environment:
+ ```bash
+ . ./build/envsetup.sh
+ ```
+ 2. Select a lunch target corresponding to the architecture you want to
+ build and test:
+ * For (32-bit) Arm:
+ ```bash
+ lunch aosp_arm-eng
+ ```
+ * For (32- and 64-bit) Arm64:
+ ```bash
+ lunch aosp_arm64-eng
+ ```
+ * For (32-bit) Intel x86:
+ ```bash
+ lunch aosp_x86-eng
+ ```
+ * For (32- and 64-bit) Intel x86-64:
+ ```bash
+ lunch aosp_x86_64-eng
+ ```
+ 3. Build ADB:
+ ```bash
+ m adb
+ ```
3. Build ART and required dependencies:
```bash
art/tools/buildbot-build.sh --target
diff --git a/test/README.md b/test/README.md
index da9a1f1..dc9e9b7 100644
--- a/test/README.md
+++ b/test/README.md
@@ -201,6 +201,17 @@
## Running one gtest on the build host
```sh
+$ m test-art-host-gtest-art_runtime_tests
+```
+
+Note: Although this is a build command, it actually builds the test with
+dependencies and runs the test.
+
+If you want to run the test with more options, use the following commands
+instead. Note that you need to run the test with the command above at least once
+before you run the commands below.
+
+```sh
$ find out/host/ -type f -name art_runtime_tests # Find the path of the test.
$ out/host/linux-x86/nativetest/art_runtime_tests/art_runtime_tests
```
diff --git a/test/677-fsi/build b/test/SuperWithAccessChecks/ImplementsClass.smali
old mode 100755
new mode 100644
similarity index 80%
rename from test/677-fsi/build
rename to test/SuperWithAccessChecks/ImplementsClass.smali
index b90b408..e39877f
--- a/test/677-fsi/build
+++ b/test/SuperWithAccessChecks/ImplementsClass.smali
@@ -1,6 +1,4 @@
-#!/bin/bash
-#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+.class public LImplementsClass;
+
+.super Ljava/lang/Object;
+.implements LItf;
diff --git a/test/677-fsi/build b/test/SuperWithAccessChecks/Itf.smali
old mode 100755
new mode 100644
similarity index 69%
copy from test/677-fsi/build
copy to test/SuperWithAccessChecks/Itf.smali
index b90b408..c91686e
--- a/test/677-fsi/build
+++ b/test/SuperWithAccessChecks/Itf.smali
@@ -1,6 +1,4 @@
-#!/bin/bash
-#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +12,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+.class public abstract interface LItf;
+
+.super Ljava/lang/Object;
+
+.method public foo()V
+ .registers 1
+ invoke-static {}, LMissingClass;->forName()V
+ return-void
+.end method
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/SuperWithAccessChecks/SubClass.smali
similarity index 83%
rename from test/2038-hiddenapi-jvmti-ext/build
rename to test/SuperWithAccessChecks/SubClass.smali
index f4b029f..21c8f1c 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/SuperWithAccessChecks/SubClass.smali
@@ -1,6 +1,4 @@
-#!/bin/bash
-#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +12,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+.class public LSubClass;
+
+.super LSuperClass;
diff --git a/test/677-fsi/build b/test/SuperWithAccessChecks/SuperClass.smali
old mode 100755
new mode 100644
similarity index 70%
copy from test/677-fsi/build
copy to test/SuperWithAccessChecks/SuperClass.smali
index b90b408..8e12521
--- a/test/677-fsi/build
+++ b/test/SuperWithAccessChecks/SuperClass.smali
@@ -1,6 +1,4 @@
-#!/bin/bash
-#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +12,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-./default-build "$@" --zip-compression-method store --zip-align 4
+.class public LSuperClass;
+
+.super Ljava/lang/Object;
+
+.method static foo()V
+ .registers 0
+ invoke-static {}, LMissingClass;->forName()V
+ return-void
+.end method
+
diff --git a/test/art-gtests-target-chroot.xml b/test/art-gtests-target-chroot.xml
index 5fd76f8..88c45b7 100644
--- a/test/art-gtests-target-chroot.xml
+++ b/test/art-gtests-target-chroot.xml
@@ -37,7 +37,7 @@
<test class="com.android.tradefed.testtype.ArtGTest" >
<!-- TODO(b/147821328): These tests do not work since they need to write to /system -->
- <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantTest.SystemFrameworkDir" />
+ <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantBaseTest.SystemFrameworkDir" />
<option name="native-test-timeout" value="600000" />
<option name="native-test-device-path" value="/data/local/tmp/art-test-chroot/apex/com.android.art/bin/art" />
</test>
diff --git a/test/art-gtests-target-install-apex.xml b/test/art-gtests-target-install-apex.xml
index 240b441..b030f26 100644
--- a/test/art-gtests-target-install-apex.xml
+++ b/test/art-gtests-target-install-apex.xml
@@ -23,7 +23,7 @@
<test class="com.android.tradefed.testtype.GTest" >
<!-- TODO(b/147821328): These tests do not work since they need to write to /system -->
- <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantTest.SystemFrameworkDir" />
+ <option name="exclude-filter" value="HiddenApiTest.DexDomain_System*:OatFileAssistantBaseTest.SystemFrameworkDir" />
<option name="native-test-timeout" value="600000" /> <!-- 10 min -->
<option name="native-test-device-path" value="/apex/com.android.art/bin/art" />
</test>
diff --git a/test/art_build_rules.py b/test/art_build_rules.py
new file mode 100644
index 0000000..765289d
--- /dev/null
+++ b/test/art_build_rules.py
@@ -0,0 +1,449 @@
+#
+# Copyright (C) 2021 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.
+
+"""This is the default build script for run-tests.
+
+It can be overwrite by specific run-tests if needed.
+It is used from soong build and not intended to be called directly.
+"""
+
+import argparse
+import functools
+import glob
+import os
+from os import path
+import shlex
+import shutil
+import subprocess
+import tempfile
+import zipfile
+from shutil import rmtree
+from os import remove
+from re import match
+
+USE_RBE_FOR_JAVAC = 100 # Percentage of tests that can use RBE (between 0 and 100)
+USE_RBE_FOR_D8 = 100 # Percentage of tests that can use RBE (between 0 and 100)
+
+def rm(*patterns):
+ for pattern in patterns:
+ for path in glob.glob(pattern):
+ if os.path.isdir(path):
+ shutil.rmtree(path)
+ else:
+ os.remove(path)
+
+def build_run_test(
+ use_desugar=True,
+ use_hiddenapi=True,
+ need_dex=None,
+ experimental="no-experiment",
+ zip_compression_method="deflate",
+ zip_align_bytes=None,
+ api_level=None,
+ javac_args=[],
+ d8_flags=[],
+ smali_args=[],
+ has_smali=None,
+ has_jasmin=None,
+ ):
+
+ def parse_bool(text):
+ return {"true": True, "false": False}[text.lower()]
+
+ ANDROID_BUILD_TOP = os.environ["ANDROID_BUILD_TOP"]
+ SBOX_PATH = os.environ["SBOX_PATH"]
+ CWD = os.getcwd()
+ TEST_NAME = os.environ["TEST_NAME"]
+ ART_TEST_RUN_TEST_BOOTCLASSPATH = path.relpath(os.environ["ART_TEST_RUN_TEST_BOOTCLASSPATH"], CWD)
+ NEED_DEX = parse_bool(os.environ["NEED_DEX"]) if need_dex is None else need_dex
+
+ RBE_exec_root = os.environ.get("RBE_exec_root")
+ RBE_rewrapper = path.join(ANDROID_BUILD_TOP, "prebuilts/remoteexecution-client/live/rewrapper")
+
+ # Set default values for directories.
+ HAS_SMALI = path.exists("smali") if has_smali is None else has_smali
+ HAS_JASMIN = path.exists("jasmin") if has_jasmin is None else has_jasmin
+ HAS_SRC = path.exists("src")
+ HAS_SRC_ART = path.exists("src-art")
+ HAS_SRC2 = path.exists("src2")
+ HAS_SRC_MULTIDEX = path.exists("src-multidex")
+ HAS_SMALI_MULTIDEX = path.exists("smali-multidex")
+ HAS_JASMIN_MULTIDEX = path.exists("jasmin-multidex")
+ HAS_SMALI_EX = path.exists("smali-ex")
+ HAS_SRC_EX = path.exists("src-ex")
+ HAS_SRC_EX2 = path.exists("src-ex2")
+ HAS_SRC_AOTEX = path.exists("src-aotex")
+ HAS_SRC_BCPEX = path.exists("src-bcpex")
+ HAS_HIDDENAPI_SPEC = path.exists("hiddenapi-flags.csv")
+
+ JAVAC_ARGS = shlex.split(os.environ.get("JAVAC_ARGS", "")) + javac_args
+ SMALI_ARGS = shlex.split(os.environ.get("SMALI_ARGS", "")) + smali_args
+ D8_FLAGS = shlex.split(os.environ.get("D8_FLAGS", "")) + d8_flags
+
+ BUILD_MODE = os.environ["BUILD_MODE"]
+
+ # Setup experimental API level mappings in a bash associative array.
+ EXPERIMENTAL_API_LEVEL = {}
+ EXPERIMENTAL_API_LEVEL["no-experiment"] = "26"
+ EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
+ EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
+ EXPERIMENTAL_API_LEVEL["agents"] = "26"
+ EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
+ EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
+
+ if BUILD_MODE == "jvm":
+ # No desugaring on jvm because it supports the latest functionality.
+ use_desugar = False
+ # Do not attempt to build src-art directories on jvm,
+ # since it would fail without libcore.
+ HAS_SRC_ART = False
+
+ # Set API level for smali and d8.
+ if not api_level:
+ api_level = EXPERIMENTAL_API_LEVEL[experimental]
+
+ # Add API level arguments to smali and dx
+ SMALI_ARGS.extend(["--api", str(api_level)])
+ D8_FLAGS.extend(["--min-api", str(api_level)])
+
+
+ def run(executable, args):
+ cmd = shlex.split(executable) + args
+ if executable.endswith(".sh"):
+ cmd = ["/bin/bash"] + cmd
+ p = subprocess.run(cmd,
+ encoding=os.sys.stdout.encoding,
+ stderr=subprocess.STDOUT,
+ stdout=subprocess.PIPE)
+ if p.returncode != 0:
+ raise Exception("Command failed with exit code {}\n$ {}\n{}".format(
+ p.returncode, " ".join(cmd), p.stdout))
+ return p
+
+
+ # Helper functions to execute tools.
+ soong_zip = functools.partial(run, os.environ["SOONG_ZIP"])
+ zipalign = functools.partial(run, os.environ["ZIPALIGN"])
+ javac = functools.partial(run, os.environ["JAVAC"])
+ jasmin = functools.partial(run, os.environ["JASMIN"])
+ smali = functools.partial(run, os.environ["SMALI"])
+ d8 = functools.partial(run, os.environ["D8"])
+ hiddenapi = functools.partial(run, os.environ["HIDDENAPI"])
+
+ if "RBE_server_address" in os.environ:
+ version = match(r"Version: (\d*)\.(\d*)\.(\d*)", run(RBE_rewrapper, ["--version"]).stdout)
+ assert version, "Could not parse RBE version"
+ assert tuple(map(int, version.groups())) >= (0, 76, 0), "Please update " + RBE_rewrapper
+
+ def rbe_wrap(args, inputs=set()):
+ with tempfile.NamedTemporaryFile(mode="w+t", dir=RBE_exec_root) as input_list:
+ for arg in args:
+ inputs.update(filter(path.exists, arg.split(":")))
+ input_list.writelines([path.relpath(i, RBE_exec_root)+"\n" for i in inputs])
+ input_list.flush()
+ return run(RBE_rewrapper, [
+ "--platform=" + os.environ["RBE_platform"],
+ "--input_list_paths=" + input_list.name,
+ ] + args)
+
+ if USE_RBE_FOR_JAVAC > (hash(TEST_NAME) % 100): # Use for given percentage of tests.
+ def javac(args):
+ output = path.relpath(path.join(CWD, args[args.index("-d") + 1]), RBE_exec_root)
+ return rbe_wrap([
+ "--output_directories", output,
+ os.path.relpath(os.environ["JAVAC"], CWD),
+ ] + args)
+
+ if USE_RBE_FOR_D8 > (hash(TEST_NAME) % 100): # Use for given percentage of tests.
+ def d8(args):
+ inputs = set([path.join(SBOX_PATH, "tools/out/framework/d8.jar")])
+ output = path.relpath(path.join(CWD, args[args.index("--output") + 1]), RBE_exec_root)
+ return rbe_wrap([
+ "--output_files" if output.endswith(".jar") else "--output_directories", output,
+ "--toolchain_inputs=prebuilts/jdk/jdk11/linux-x86/bin/java",
+ os.path.relpath(os.environ["D8"], CWD)] + args, inputs)
+
+ # If wrapper script exists, use it instead of the default javac.
+ if os.path.exists("javac_wrapper.sh"):
+ javac = functools.partial(run, "javac_wrapper.sh")
+
+ def find(root, name):
+ return sorted(glob.glob(path.join(root, "**", name), recursive=True))
+
+
+ def zip(zip_target, *files):
+ zip_args = ["-o", zip_target]
+ if zip_compression_method == "store":
+ zip_args.extend(["-L", "0"])
+ for f in files:
+ zip_args.extend(["-f", f])
+ soong_zip(zip_args)
+
+ if zip_align_bytes:
+ # zipalign does not operate in-place, so write results to a temp file.
+ with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
+ tmp_file = path.join(tmp_dir, "aligned.zip")
+ zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file])
+ # replace original zip target with our temp file.
+ os.rename(tmp_file, zip_target)
+
+
+ def make_jasmin(out_directory, jasmin_sources):
+ os.makedirs(out_directory, exist_ok=True)
+ jasmin(["-d", out_directory] + sorted(jasmin_sources))
+
+
+ # Like regular javac but may include libcore on the bootclasspath.
+ def javac_with_bootclasspath(args):
+ flags = JAVAC_ARGS + ["-encoding", "utf8"]
+ if BUILD_MODE != "jvm":
+ flags.extend(["-bootclasspath", ART_TEST_RUN_TEST_BOOTCLASSPATH])
+ javac(flags + args)
+
+
+ # Make a "dex" file given a directory of classes. This will be
+ # packaged in a jar file.
+ def make_dex(name):
+ d8_inputs = find(name, "*.class")
+ d8_output = name + ".jar"
+ dex_output = name + ".dex"
+ if use_desugar:
+ flags = ["--lib", ART_TEST_RUN_TEST_BOOTCLASSPATH]
+ else:
+ flags = ["--no-desugaring"]
+ assert d8_inputs
+ d8(D8_FLAGS + flags + ["--output", d8_output] + d8_inputs)
+
+ # D8 outputs to JAR files today rather than DEX files as DX used
+ # to. To compensate, we extract the DEX from d8's output to meet the
+ # expectations of make_dex callers.
+ with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
+ zipfile.ZipFile(d8_output, "r").extractall(tmp_dir)
+ os.rename(path.join(tmp_dir, "classes.dex"), dex_output)
+
+
+ # Merge all the dex files.
+ # Skip non-existing files, but at least 1 file must exist.
+ def make_dexmerge(*dex_files_to_merge):
+ # Dex file that acts as the destination.
+ dst_file = dex_files_to_merge[0]
+
+ # Skip any non-existing files.
+ dex_files_to_merge = list(filter(path.exists, dex_files_to_merge))
+
+ # NB: We merge even if there is just single input.
+ # It is useful to normalize non-deterministic smali output.
+
+ tmp_dir = "dexmerge"
+ os.makedirs(tmp_dir)
+ d8(["--min-api", api_level, "--output", tmp_dir] + dex_files_to_merge)
+ assert not path.exists(path.join(tmp_dir, "classes2.dex"))
+ for input_dex in dex_files_to_merge:
+ os.remove(input_dex)
+ os.rename(path.join(tmp_dir, "classes.dex"), dst_file)
+ os.rmdir(tmp_dir)
+
+
+ def make_hiddenapi(*dex_files):
+ args = ["encode"]
+ for dex_file in dex_files:
+ args.extend(["--input-dex=" + dex_file, "--output-dex=" + dex_file])
+ args.append("--api-flags=hiddenapi-flags.csv")
+ args.append("--no-force-assign-all")
+ hiddenapi(args)
+
+
+ if path.exists("classes.dex"):
+ zip(TEST_NAME + ".jar", "classes.dex")
+ return
+
+
+ def has_multidex():
+ return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
+
+
+ def add_to_cp_args(old_cp_args, path):
+ if len(old_cp_args) == 0:
+ return ["-cp", path]
+ else:
+ return ["-cp", old_cp_args[1] + ":" + path]
+
+
+ src_tmp_all = []
+
+ if HAS_JASMIN:
+ make_jasmin("jasmin_classes", find("jasmin", "*.j"))
+ src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes")
+
+ if HAS_JASMIN_MULTIDEX:
+ make_jasmin("jasmin_classes2", find("jasmin-multidex", "*.j"))
+ src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes2")
+
+ if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
+ HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
+ # To allow circular references, compile src/, src-multidex/, src-aotex/,
+ # src-bcpex/, src-ex/ together and pass the output as class path argument.
+ # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
+ # used by the other src-* sources we compile here but everything needed to
+ # compile the other src-* sources should be present in src/ (and jasmin*/).
+ os.makedirs("classes-tmp-all")
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes-tmp-all"] +
+ find("src", "*.java") +
+ find("src-multidex", "*.java") +
+ find("src-aotex", "*.java") +
+ find("src-bcpex", "*.java") +
+ find("src-ex", "*.java"))
+ src_tmp_all = add_to_cp_args(src_tmp_all, "classes-tmp-all")
+
+ if HAS_SRC_AOTEX:
+ os.makedirs("classes-aotex")
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes-aotex"] +
+ find("src-aotex", "*.java"))
+ if NEED_DEX:
+ make_dex("classes-aotex")
+ # rename it so it shows up as "classes.dex" in the zip file.
+ os.rename("classes-aotex.dex", "classes.dex")
+ zip(TEST_NAME + "-aotex.jar", "classes.dex")
+
+ if HAS_SRC_BCPEX:
+ os.makedirs("classes-bcpex")
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes-bcpex"] +
+ find("src-bcpex", "*.java"))
+ if NEED_DEX:
+ make_dex("classes-bcpex")
+ # rename it so it shows up as "classes.dex" in the zip file.
+ os.rename("classes-bcpex.dex", "classes.dex")
+ zip(TEST_NAME + "-bcpex.jar", "classes.dex")
+
+ if HAS_SRC:
+ os.makedirs("classes", exist_ok=True)
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes"] + find("src", "*.java"))
+
+ if HAS_SRC_ART:
+ os.makedirs("classes", exist_ok=True)
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes"] + find("src-art", "*.java"))
+
+ if HAS_SRC_MULTIDEX:
+ os.makedirs("classes2")
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes2"] +
+ find("src-multidex", "*.java"))
+ if NEED_DEX:
+ make_dex("classes2")
+
+ if HAS_SRC2:
+ os.makedirs("classes", exist_ok=True)
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes"] +
+ find("src2", "*.java"))
+
+ # If the classes directory is not-empty, package classes in a DEX file.
+ # NB: some tests provide classes rather than java files.
+ if find("classes", "*"):
+ if NEED_DEX:
+ make_dex("classes")
+
+ if HAS_JASMIN:
+ # Compile Jasmin classes as if they were part of the classes.dex file.
+ if NEED_DEX:
+ make_dex("jasmin_classes")
+ make_dexmerge("classes.dex", "jasmin_classes.dex")
+ else:
+ # Move jasmin classes into classes directory so that they are picked up
+ # with -cp classes.
+ os.makedirs("classes", exist_ok=True)
+ shutil.copytree("jasmin_classes", "classes", dirs_exist_ok=True)
+
+ if HAS_SMALI and NEED_DEX:
+ # Compile Smali classes
+ smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
+ ["--output", "smali_classes.dex"] + find("smali", "*.smali"))
+ assert path.exists("smali_classes.dex")
+ # Merge smali files into classes.dex,
+ # this takes priority over any jasmin files.
+ make_dexmerge("classes.dex", "smali_classes.dex")
+
+ # Compile Jasmin classes in jasmin-multidex as if they were part of
+ # the classes2.jar
+ if HAS_JASMIN_MULTIDEX:
+ if NEED_DEX:
+ make_dex("jasmin_classes2")
+ make_dexmerge("classes2.dex", "jasmin_classes2.dex")
+ else:
+ # Move jasmin classes into classes2 directory so that
+ # they are picked up with -cp classes2.
+ os.makedirs("classes2", exist_ok=True)
+ shutil.copytree("jasmin_classes2", "classes2", dirs_exist_ok=True)
+ shutil.rmtree("jasmin_classes2")
+
+ if HAS_SMALI_MULTIDEX and NEED_DEX:
+ # Compile Smali classes
+ smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
+ ["--output", "smali_classes2.dex"] + find("smali-multidex", "*.smali"))
+
+ # Merge smali_classes2.dex into classes2.dex
+ make_dexmerge("classes2.dex", "smali_classes2.dex")
+
+ if HAS_SRC_EX:
+ os.makedirs("classes-ex", exist_ok=True)
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes-ex"] + find("src-ex", "*.java"))
+
+ if HAS_SRC_EX2:
+ os.makedirs("classes-ex", exist_ok=True)
+ javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
+ ["-d", "classes-ex"] + find("src-ex2", "*.java"))
+
+ if path.exists("classes-ex") and NEED_DEX:
+ make_dex("classes-ex")
+
+ if HAS_SMALI_EX and NEED_DEX:
+ # Compile Smali classes
+ smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
+ ["--output", "smali_classes-ex.dex"] + find("smali-ex", "*.smali"))
+ assert path.exists("smali_classes-ex.dex")
+ # Merge smali files into classes-ex.dex.
+ make_dexmerge("classes-ex.dex", "smali_classes-ex.dex")
+
+ if path.exists("classes-ex.dex"):
+ # Apply hiddenapi on the dex files if the test has API list file(s).
+ if use_hiddenapi and HAS_HIDDENAPI_SPEC:
+ make_hiddenapi("classes-ex.dex")
+
+ # quick shuffle so that the stored name is "classes.dex"
+ os.rename("classes.dex", "classes-1.dex")
+ os.rename("classes-ex.dex", "classes.dex")
+ zip(TEST_NAME + "-ex.jar", "classes.dex")
+ os.rename("classes.dex", "classes-ex.dex")
+ os.rename("classes-1.dex", "classes.dex")
+
+ # Apply hiddenapi on the dex files if the test has API list file(s).
+ if NEED_DEX and use_hiddenapi and HAS_HIDDENAPI_SPEC:
+ if has_multidex():
+ make_hiddenapi("classes.dex", "classes2.dex")
+ else:
+ make_hiddenapi("classes.dex")
+
+ # Create a single dex jar with two dex files for multidex.
+ if NEED_DEX:
+ if path.exists("classes2.dex"):
+ zip(TEST_NAME + ".jar", "classes.dex", "classes2.dex")
+ else:
+ zip(TEST_NAME + ".jar", "classes.dex")
diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc
index 0b72612..a900714 100644
--- a/test/common/runtime_state.cc
+++ b/test/common/runtime_state.cc
@@ -234,7 +234,7 @@
{
ScopedObjectAccess soa(self);
- if (Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method)) {
+ if (Runtime::Current()->GetInstrumentation()->IsDeoptimized(method)) {
std::string msg(method->PrettyMethod());
msg += ": is not safe to jit!";
ThrowIllegalStateException(msg.c_str());
@@ -276,7 +276,20 @@
// this before checking if we will execute JIT code in case the request
// is for an 'optimized' compilation.
jit->CompileMethod(method, self, kind, /*prejit=*/ false);
- } while (!code_cache->ContainsPc(method->GetEntryPointFromQuickCompiledCode()));
+ const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
+ if (code_cache->ContainsPc(entry_point)) {
+ // If we're running baseline or not requesting optimized, we're good to go.
+ if (jit->GetJitCompiler()->IsBaselineCompiler() || kind != CompilationKind::kOptimized) {
+ break;
+ }
+ // If we're requesting optimized, check that we did get the method
+ // compiled optimized.
+ OatQuickMethodHeader* method_header = OatQuickMethodHeader::FromEntryPoint(entry_point);
+ if (!CodeInfo::IsBaseline(method_header->GetOptimizedCodeInfoPtr())) {
+ break;
+ }
+ }
+ } while (true);
}
extern "C" JNIEXPORT void JNICALL Java_Main_ensureMethodJitCompiled(JNIEnv*, jclass, jobject meth) {
diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc
index 79c7a36..33f31e2 100644
--- a/test/common/stack_inspect.cc
+++ b/test/common/stack_inspect.cc
@@ -195,7 +195,8 @@
if (stack_visitor->GetMethod() == nullptr ||
stack_visitor->GetMethod()->IsNative() ||
(stack_visitor->GetCurrentShadowFrame() == nullptr &&
- !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetCurrentQuickFramePc()))) {
+ !Runtime::Current()->IsAsyncDeoptimizeable(stack_visitor->GetOuterMethod(),
+ stack_visitor->GetCurrentQuickFramePc()))) {
return true;
}
result = soa.AddLocalReference<jobject>(stack_visitor->GetThisObject());
diff --git a/test/dexpreopt/Android.bp b/test/dexpreopt/Android.bp
index b39565b..f5ffd89 100644
--- a/test/dexpreopt/Android.bp
+++ b/test/dexpreopt/Android.bp
@@ -36,5 +36,5 @@
"libgmock",
"libprocinfo",
],
- test_config_template: "//art/test:art-gtests-target-standalone-root-template",
+ test_config_template: "art_standalone_dexpreopt_tests.xml",
}
diff --git a/test/dexpreopt/art_standalone_dexpreopt_tests.xml b/test/dexpreopt/art_standalone_dexpreopt_tests.xml
new file mode 100644
index 0000000..873b6a2
--- /dev/null
+++ b/test/dexpreopt/art_standalone_dexpreopt_tests.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<!-- Note: This test config file for {MODULE} is generated from a template. -->
+<configuration description="Runs {MODULE} as root.">
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="cleanup" value="true" />
+ <option name="push" value="{MODULE}->/data/local/tmp/{MODULE}/{MODULE}" />
+ <option name="append-bitness" value="true" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp/{MODULE}" />
+ <option name="module-name" value="{MODULE}" />
+ <option name="ld-library-path-32" value="/apex/com.android.art/lib" />
+ <option name="ld-library-path-64" value="/apex/com.android.art/lib64" />
+ </test>
+
+ <!-- When this test is run in a Mainline context (e.g. with `mts-tradefed`), only enable it if
+ one of the Mainline modules below is present on the device used for testing. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+ <!-- ART Mainline Module (internal version). -->
+ <option name="mainline-module-package-name" value="com.google.android.art" />
+ <!-- ART Mainline Module (external (AOSP) version). -->
+ <option name="mainline-module-package-name" value="com.android.art" />
+ </object>
+
+ <!-- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+ <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
+</configuration>
diff --git a/test/etc/apex-bootclasspath-utils.sh b/test/etc/apex-bootclasspath-utils.sh
deleted file mode 100755
index 5a0873d..0000000
--- a/test/etc/apex-bootclasspath-utils.sh
+++ /dev/null
@@ -1,86 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2022 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.
-
-# This file contains utils for constructing -Xbootclasspath and -Xbootclasspath-location
-# for dex2oat and dalvikvm from apex modules list.
-#
-# Those utils could be used outside of art/test/ to run ART in chroot setup.
-
-# Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
-# because that's what we use for compiling the boot.art image.
-# It may contain additional modules from TEST_CORE_JARS.
-readonly bpath_modules="core-oj core-libart okhttp bouncycastle apache-xml core-icu4j conscrypt"
-
-# Helper function to construct paths for apex modules (for both -Xbootclasspath and
-# -Xbootclasspath-location).
-#
-# Arguments.
-# ${1}: path prefix.
-get_apex_bootclasspath_impl() {
- local -r bpath_prefix="$1"
- local bpath_separator=""
- local bpath=""
- local bpath_jar=""
- for bpath_module in ${bpath_modules}; do
- local apex_module="com.android.art"
- case "$bpath_module" in
- (conscrypt) apex_module="com.android.conscrypt";;
- (core-icu4j) apex_module="com.android.i18n";;
- (*) apex_module="com.android.art";;
- esac
- bpath_jar="/apex/${apex_module}/javalib/${bpath_module}.jar"
- bpath+="${bpath_separator}${bpath_prefix}${bpath_jar}"
- bpath_separator=":"
- done
- echo "${bpath}"
-}
-
-# Gets a -Xbootclasspath paths with the apex modules.
-#
-# Arguments.
-# ${1}: host (y|n).
-get_apex_bootclasspath() {
- local -r host="${1}"
- local bpath_prefix=""
-
- if [[ "${host}" == "y" ]]; then
- bpath_prefix="${ANDROID_HOST_OUT}"
- fi
-
- get_apex_bootclasspath_impl "${bpath_prefix}"
-}
-
-# Gets a -Xbootclasspath-location paths with the apex modules.
-#
-# Arguments.
-# ${1}: host (y|n).
-get_apex_bootclasspath_locations() {
- local -r host="${1}"
- local bpath_location_prefix=""
-
- if [[ "${host}" == "y" ]]; then
- if [[ "${ANDROID_HOST_OUT:0:${#ANDROID_BUILD_TOP}+1}" == "${ANDROID_BUILD_TOP}/" ]]; then
- bpath_location_prefix="${ANDROID_HOST_OUT:${#ANDROID_BUILD_TOP}+1}"
- else
- error_msg "ANDROID_BUILD_TOP/ is not a prefix of ANDROID_HOST_OUT"\
- "\nANDROID_BUILD_TOP=${ANDROID_BUILD_TOP}"\
- "\nANDROID_HOST_OUT=${ANDROID_HOST_OUT}"
- exit
- fi
- fi
-
- get_apex_bootclasspath_impl "${bpath_location_prefix}"
-}
diff --git a/test/etc/apex_bootclasspath_utils.py b/test/etc/apex_bootclasspath_utils.py
new file mode 100755
index 0000000..f969b14
--- /dev/null
+++ b/test/etc/apex_bootclasspath_utils.py
@@ -0,0 +1,79 @@
+#
+# Copyright (C) 2022 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.
+
+# This file contains utils for constructing -Xbootclasspath and -Xbootclasspath-location
+# for dex2oat and dalvikvm from apex modules list.
+#
+# Those utils could be used outside of art/test/ to run ART in chroot setup.
+
+import os, sys
+
+# Note: This must start with the CORE_IMG_JARS in Android.common_path.mk
+# because that's what we use for compiling the boot.art image.
+# It may contain additional modules from TEST_CORE_JARS.
+bpath_modules="core-oj core-libart okhttp bouncycastle apache-xml core-icu4j conscrypt"
+
+ANDROID_BUILD_TOP=os.environ["ANDROID_BUILD_TOP"]
+ANDROID_HOST_OUT=os.environ["ANDROID_HOST_OUT"]
+
+# Helper function to construct paths for apex modules (for both -Xbootclasspath and
+# -Xbootclasspath-location).
+#
+# Arguments.
+# ${1}: path prefix.
+def get_apex_bootclasspath_impl(bpath_prefix: str):
+ bpath_separator=""
+ bpath=""
+ bpath_jar=""
+ for bpath_module in bpath_modules.split(" "):
+ apex_module="com.android.art"
+ if bpath_module == "conscrypt":
+ apex_module="com.android.conscrypt"
+ if bpath_module == "core-icu4j":
+ apex_module="com.android.i18n"
+ bpath_jar=f"/apex/{apex_module}/javalib/{bpath_module}.jar"
+ bpath+=f"{bpath_separator}{bpath_prefix}{bpath_jar}"
+ bpath_separator=":"
+ return bpath
+
+# Gets a -Xbootclasspath paths with the apex modules.
+#
+# Arguments.
+# ${1}: host (y|n).
+def get_apex_bootclasspath(host: bool):
+ bpath_prefix=""
+
+ if host:
+ bpath_prefix=ANDROID_HOST_OUT
+
+ return get_apex_bootclasspath_impl(bpath_prefix)
+
+# Gets a -Xbootclasspath-location paths with the apex modules.
+#
+# Arguments.
+# ${1}: host (y|n).
+def get_apex_bootclasspath_locations(host: bool):
+ bpath_location_prefix=""
+
+ if host:
+ if ANDROID_HOST_OUT[0:len(ANDROID_BUILD_TOP)+1] == f"{ANDROID_BUILD_TOP}/":
+ bpath_location_prefix=ANDROID_HOST_OUT[len(ANDROID_BUILD_TOP)+1:]
+ else:
+ print(f"ANDROID_BUILD_TOP/ is not a prefix of ANDROID_HOST_OUT"\
+ "\nANDROID_BUILD_TOP={ANDROID_BUILD_TOP}"\
+ "\nANDROID_HOST_OUT={ANDROID_HOST_OUT}")
+ sys.exit(1)
+
+ return get_apex_bootclasspath_impl(bpath_location_prefix)
diff --git a/test/etc/default-build b/test/etc/default-build
deleted file mode 100755
index 26820f2..0000000
--- a/test/etc/default-build
+++ /dev/null
@@ -1,430 +0,0 @@
-#!/usr/bin/env python3
-#
-# Copyright (C) 2021 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.
-
-"""This is the default build script for run-tests.
-
-It can be overwrite by specific run-tests if needed.
-It is used from soong build and not intended to be called directly.
-"""
-
-import argparse
-import functools
-import glob
-import os
-from os import path
-import shlex
-import shutil
-import subprocess
-import tempfile
-import zipfile
-
-if not os.sys.argv:
- print(
- 'Error: default-build should have the parameters from the "build" script forwarded to it'
- )
- print('Error: An example of how do it correctly is ./default-build "$@"')
- os.sys.exit(1)
-
-
-def parse_bool(text):
- return {"true": True, "false": False}[text.lower()]
-
-
-TEST_NAME = os.environ["TEST_NAME"]
-ART_TEST_RUN_TEST_BOOTCLASSPATH = os.environ["ART_TEST_RUN_TEST_BOOTCLASSPATH"]
-NEED_DEX = parse_bool(os.environ["NEED_DEX"])
-
-# Set default values for directories.
-HAS_SMALI = path.exists("smali")
-HAS_JASMIN = path.exists("jasmin")
-HAS_SRC = path.exists("src")
-HAS_SRC_ART = path.exists("src-art")
-HAS_SRC2 = path.exists("src2")
-HAS_SRC_MULTIDEX = path.exists("src-multidex")
-HAS_SMALI_MULTIDEX = path.exists("smali-multidex")
-HAS_JASMIN_MULTIDEX = path.exists("jasmin-multidex")
-HAS_SMALI_EX = path.exists("smali-ex")
-HAS_SRC_EX = path.exists("src-ex")
-HAS_SRC_EX2 = path.exists("src-ex2")
-HAS_SRC_AOTEX = path.exists("src-aotex")
-HAS_SRC_BCPEX = path.exists("src-bcpex")
-HAS_HIDDENAPI_SPEC = path.exists("hiddenapi-flags.csv")
-
-# USE_HIDDENAPI=false run-test... will disable hiddenapi.
-USE_HIDDENAPI = parse_bool(os.environ.get("USE_HIDDENAPI", "true"))
-
-# USE_DESUGAR=false run-test... will disable desugaring.
-USE_DESUGAR = parse_bool(os.environ.get("USE_DESUGAR", "true"))
-
-JAVAC_ARGS = shlex.split(os.environ.get("JAVAC_ARGS", ""))
-SMALI_ARGS = shlex.split(os.environ.get("SMALI_ARGS", ""))
-D8_FLAGS = shlex.split(os.environ.get("D8_FLAGS", ""))
-
-# Allow overriding ZIP_COMPRESSION_METHOD with e.g. 'store'
-ZIP_COMPRESSION_METHOD = "deflate"
-# Align every ZIP file made by calling $ZIPALIGN command?
-ZIP_ALIGN_BYTES = None
-
-DEV_MODE = False
-BUILD_MODE = "target"
-API_LEVEL = None
-DEFAULT_EXPERIMENT = "no-experiment"
-EXPERIMENTAL = DEFAULT_EXPERIMENT
-
-# Setup experimental API level mappings in a bash associative array.
-EXPERIMENTAL_API_LEVEL = {}
-EXPERIMENTAL_API_LEVEL[DEFAULT_EXPERIMENT] = "26"
-EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
-EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
-EXPERIMENTAL_API_LEVEL["agents"] = "26"
-EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
-EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
-
-# Parse command line arguments.
-opt_bool = argparse.BooleanOptionalAction # Bool also accepts the --no- prefix.
-parser = argparse.ArgumentParser(description=__doc__)
-parser.add_argument("--src", dest="HAS_SRC", action=opt_bool)
-parser.add_argument("--src2", dest="HAS_SRC2", action=opt_bool)
-parser.add_argument("--src-multidex", dest="HAS_SRC_MULTIDEX", action=opt_bool)
-parser.add_argument(
- "--smali-multidex", dest="HAS_SMALI_MULTIDEX", action=opt_bool)
-parser.add_argument("--src-ex", dest="HAS_SRC_EX", action=opt_bool)
-parser.add_argument("--src-ex2", dest="HAS_SRC_EX2", action=opt_bool)
-parser.add_argument("--smali", dest="HAS_SMALI", action=opt_bool)
-parser.add_argument("--jasmin", dest="HAS_JASMIN", action=opt_bool)
-parser.add_argument("--api-level", dest="API_LEVEL", type=int)
-parser.add_argument(
- "--experimental", dest="EXPERIMENTAL", type=str)
-parser.add_argument(
- "--zip-compression-method", dest="ZIP_COMPRESSION_METHOD", type=str)
-parser.add_argument("--zip-align", dest="ZIP_ALIGN_BYTES", type=int)
-parser.add_argument(
- "--host", dest="BUILD_MODE", action="store_const", const="host")
-parser.add_argument(
- "--target", dest="BUILD_MODE", action="store_const", const="target")
-parser.add_argument(
- "--jvm", dest="BUILD_MODE", action="store_const", const="jvm")
-parser.add_argument("--dev", dest="DEV_MODE", action=opt_bool)
-# Update variables with command line arguments that were set.
-globals().update(
- {k: v for k, v in parser.parse_args().__dict__.items() if v is not None})
-
-if BUILD_MODE == "jvm":
- # No desugaring on jvm because it supports the latest functionality.
- USE_DESUGAR = False
- # Do not attempt to build src-art directories on jvm,
- # since it would fail without libcore.
- HAS_SRC_ART = False
-
-# Set API level for smali and d8.
-if not API_LEVEL:
- API_LEVEL = EXPERIMENTAL_API_LEVEL[EXPERIMENTAL]
-
-# Add API level arguments to smali and dx
-SMALI_ARGS.extend(["--api", str(API_LEVEL)])
-D8_FLAGS.extend(["--min-api", str(API_LEVEL)])
-
-
-def run(executable, args):
- cmd = shlex.split(executable) + args
- if executable.endswith(".sh"):
- cmd = ["/bin/bash"] + cmd
- if DEV_MODE:
- print("Run:", " ".join(cmd))
- p = subprocess.run(cmd, check=True)
- if p.returncode != 0:
- raise Exception("Failed command: " + " ".join(cmd))
-
-
-# Helper functions to execute tools.
-soong_zip = functools.partial(run, os.environ["SOONG_ZIP"])
-zipalign = functools.partial(run, os.environ["ZIPALIGN"])
-javac = functools.partial(run, os.environ["JAVAC"])
-jasmin = functools.partial(run, os.environ["JASMIN"])
-smali = functools.partial(run, os.environ["SMALI"])
-d8 = functools.partial(run, os.environ["D8"])
-hiddenapi = functools.partial(run, os.environ["HIDDENAPI"])
-
-# If wrapper script exists, use it instead of the default javac.
-if os.path.exists("javac_wrapper.sh"):
- javac = functools.partial(run, "javac_wrapper.sh")
-
-def find(root, name):
- return sorted(glob.glob(path.join(root, "**", name), recursive=True))
-
-
-def zip(zip_target, *files):
- zip_args = ["-o", zip_target]
- if ZIP_COMPRESSION_METHOD == "store":
- zip_args.extend(["-L", "0"])
- for f in files:
- zip_args.extend(["-f", f])
- soong_zip(zip_args)
-
- if ZIP_ALIGN_BYTES:
- # zipalign does not operate in-place, so write results to a temp file.
- with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
- tmp_file = path.join(tmp_dir, "aligned.zip")
- zipalign(["-f", str(ZIP_ALIGN_BYTES), zip_target, tmp_file])
- # replace original zip target with our temp file.
- os.rename(tmp_file, zip_target)
-
-
-def make_jasmin(out_directory, jasmin_sources):
- os.makedirs(out_directory, exist_ok=True)
- jasmin(["-d", out_directory] + sorted(jasmin_sources))
-
-
-# Like regular javac but may include libcore on the bootclasspath.
-def javac_with_bootclasspath(args):
- flags = JAVAC_ARGS + ["-encoding", "utf8"]
- if BUILD_MODE != "jvm":
- flags.extend(["-bootclasspath", ART_TEST_RUN_TEST_BOOTCLASSPATH])
- javac(flags + args)
-
-
-# Make a "dex" file given a directory of classes. This will be
-# packaged in a jar file.
-def make_dex(name):
- d8_inputs = find(name, "*.class")
- d8_output = name + ".jar"
- dex_output = name + ".dex"
- if USE_DESUGAR:
- flags = ["--lib", ART_TEST_RUN_TEST_BOOTCLASSPATH]
- else:
- flags = ["--no-desugaring"]
- assert d8_inputs
- d8(D8_FLAGS + flags + ["--output", d8_output] + d8_inputs)
-
- # D8 outputs to JAR files today rather than DEX files as DX used
- # to. To compensate, we extract the DEX from d8's output to meet the
- # expectations of make_dex callers.
- with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
- zipfile.ZipFile(d8_output, "r").extractall(tmp_dir)
- os.rename(path.join(tmp_dir, "classes.dex"), dex_output)
-
-
-# Merge all the dex files.
-# Skip non-existing files, but at least 1 file must exist.
-def make_dexmerge(*dex_files_to_merge):
- # Dex file that acts as the destination.
- dst_file = dex_files_to_merge[0]
-
- # Skip any non-existing files.
- dex_files_to_merge = list(filter(path.exists, dex_files_to_merge))
-
- # NB: We merge even if there is just single input.
- # It is useful to normalize non-deterministic smali output.
-
- with tempfile.TemporaryDirectory(dir=".") as tmp_dir:
- d8(["--min-api", API_LEVEL, "--output", tmp_dir] + dex_files_to_merge)
- assert not path.exists(path.join(tmp_dir, "classes2.dex"))
- for input_dex in dex_files_to_merge:
- os.remove(input_dex)
- os.rename(path.join(tmp_dir, "classes.dex"), dst_file)
-
-
-def make_hiddenapi(*dex_files):
- args = ["encode"]
- for dex_file in dex_files:
- args.extend(["--input-dex=" + dex_file, "--output-dex=" + dex_file])
- args.append("--api-flags=hiddenapi-flags.csv")
- args.append("--no-force-assign-all")
- hiddenapi(args)
-
-
-if path.exists("classes.dex"):
- zip(TEST_NAME + ".jar", "classes.dex")
- os.sys.exit(0)
-
-
-def has_multidex():
- return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
-
-
-def add_to_cp_args(old_cp_args, path):
- if len(old_cp_args) == 0:
- return ["-cp", path]
- else:
- return ["-cp", old_cp_args[1] + ":" + path]
-
-
-src_tmp_all = []
-
-if HAS_JASMIN:
- make_jasmin("jasmin_classes", find("jasmin", "*.j"))
- src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes")
-
-if HAS_JASMIN_MULTIDEX:
- make_jasmin("jasmin_classes2", find("jasmin-multidex", "*.j"))
- src_tmp_all = add_to_cp_args(src_tmp_all, "jasmin_classes2")
-
-if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
- HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
- # To allow circular references, compile src/, src-multidex/, src-aotex/,
- # src-bcpex/, src-ex/ together and pass the output as class path argument.
- # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
- # used by the other src-* sources we compile here but everything needed to
- # compile the other src-* sources should be present in src/ (and jasmin*/).
- os.makedirs("classes-tmp-all")
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes-tmp-all"] +
- find("src", "*.java") +
- find("src-multidex", "*.java") +
- find("src-aotex", "*.java") +
- find("src-bcpex", "*.java") +
- find("src-ex", "*.java"))
- src_tmp_all = add_to_cp_args(src_tmp_all, "classes-tmp-all")
-
-if HAS_SRC_AOTEX:
- os.makedirs("classes-aotex")
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes-aotex"] +
- find("src-aotex", "*.java"))
- if NEED_DEX:
- make_dex("classes-aotex")
- # rename it so it shows up as "classes.dex" in the zip file.
- os.rename("classes-aotex.dex", "classes.dex")
- zip(TEST_NAME + "-aotex.jar", "classes.dex")
-
-if HAS_SRC_BCPEX:
- os.makedirs("classes-bcpex")
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes-bcpex"] +
- find("src-bcpex", "*.java"))
- if NEED_DEX:
- make_dex("classes-bcpex")
- # rename it so it shows up as "classes.dex" in the zip file.
- os.rename("classes-bcpex.dex", "classes.dex")
- zip(TEST_NAME + "-bcpex.jar", "classes.dex")
-
-if HAS_SRC:
- os.makedirs("classes", exist_ok=True)
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes"] + find("src", "*.java"))
-
-if HAS_SRC_ART:
- os.makedirs("classes", exist_ok=True)
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes"] + find("src-art", "*.java"))
-
-if HAS_SRC_MULTIDEX:
- os.makedirs("classes2")
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes2"] +
- find("src-multidex", "*.java"))
- if NEED_DEX:
- make_dex("classes2")
-
-if HAS_SRC2:
- os.makedirs("classes", exist_ok=True)
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes"] +
- find("src2", "*.java"))
-
-# If the classes directory is not-empty, package classes in a DEX file.
-# NB: some tests provide classes rather than java files.
-if find("classes", "*"):
- if NEED_DEX:
- make_dex("classes")
-
-if HAS_JASMIN:
- # Compile Jasmin classes as if they were part of the classes.dex file.
- if NEED_DEX:
- make_dex("jasmin_classes")
- make_dexmerge("classes.dex", "jasmin_classes.dex")
- else:
- # Move jasmin classes into classes directory so that they are picked up
- # with -cp classes.
- os.makedirs("classes", exist_ok=True)
- shutil.copytree("jasmin_classes", "classes", dirs_exist_ok=True)
-
-if HAS_SMALI and NEED_DEX:
- # Compile Smali classes
- smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
- ["--output", "smali_classes.dex"] + find("smali", "*.smali"))
- assert path.exists("smali_classes.dex")
- # Merge smali files into classes.dex,
- # this takes priority over any jasmin files.
- make_dexmerge("classes.dex", "smali_classes.dex")
-
-# Compile Jasmin classes in jasmin-multidex as if they were part of
-# the classes2.jar
-if HAS_JASMIN_MULTIDEX:
- if NEED_DEX:
- make_dex("jasmin_classes2")
- make_dexmerge("classes2.dex", "jasmin_classes2.dex")
- else:
- # Move jasmin classes into classes2 directory so that
- # they are picked up with -cp classes2.
- os.makedirs("classes2", exist_ok=True)
- shutil.copytree("jasmin_classes2", "classes2", dirs_exist_ok=True)
- shutil.rmtree("jasmin_classes2")
-
-if HAS_SMALI_MULTIDEX and NEED_DEX:
- # Compile Smali classes
- smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
- ["--output", "smali_classes2.dex"] + find("smali-multidex", "*.smali"))
-
- # Merge smali_classes2.dex into classes2.dex
- make_dexmerge("classes2.dex", "smali_classes2.dex")
-
-if HAS_SRC_EX:
- os.makedirs("classes-ex", exist_ok=True)
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes-ex"] + find("src-ex", "*.java"))
-
-if HAS_SRC_EX2:
- os.makedirs("classes-ex", exist_ok=True)
- javac_with_bootclasspath(["-implicit:none"] + src_tmp_all +
- ["-d", "classes-ex"] + find("src-ex2", "*.java"))
-
-if path.exists("classes-ex") and NEED_DEX:
- make_dex("classes-ex")
-
-if HAS_SMALI_EX and NEED_DEX:
- # Compile Smali classes
- smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
- ["--output", "smali_classes-ex.dex"] + find("smali-ex", "*.smali"))
- assert path.exists("smali_classes-ex.dex")
- # Merge smali files into classes-ex.dex.
- make_dexmerge("classes-ex.dex", "smali_classes-ex.dex")
-
-if path.exists("classes-ex.dex"):
- # Apply hiddenapi on the dex files if the test has API list file(s).
- if USE_HIDDENAPI and HAS_HIDDENAPI_SPEC:
- make_hiddenapi("classes-ex.dex")
-
- # quick shuffle so that the stored name is "classes.dex"
- os.rename("classes.dex", "classes-1.dex")
- os.rename("classes-ex.dex", "classes.dex")
- zip(TEST_NAME + "-ex.jar", "classes.dex")
- os.rename("classes.dex", "classes-ex.dex")
- os.rename("classes-1.dex", "classes.dex")
-
-# Apply hiddenapi on the dex files if the test has API list file(s).
-if NEED_DEX and USE_HIDDENAPI and HAS_HIDDENAPI_SPEC:
- if has_multidex():
- make_hiddenapi("classes.dex", "classes2.dex")
- else:
- make_hiddenapi("classes.dex")
-
-# Create a single dex jar with two dex files for multidex.
-if NEED_DEX:
- if path.exists("classes2.dex"):
- zip(TEST_NAME + ".jar", "classes.dex", "classes2.dex")
- else:
- zip(TEST_NAME + ".jar", "classes.dex")
diff --git a/test/2038-hiddenapi-jvmti-ext/build b/test/etc/default-build.py
similarity index 82%
copy from test/2038-hiddenapi-jvmti-ext/build
copy to test/etc/default-build.py
index f4b029f..7a229c7 100644
--- a/test/2038-hiddenapi-jvmti-ext/build
+++ b/test/etc/default-build.py
@@ -1,6 +1,5 @@
-#!/bin/bash
#
-# Copyright 2018 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -14,4 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-USE_HIDDENAPI=true ./default-build "$@"
+from art_build_rules import build_run_test
+
+build_run_test()
diff --git a/test/etc/run-test-jar b/test/etc/run-test-jar
index ef168ca..1f6bc2d 100755
--- a/test/etc/run-test-jar
+++ b/test/etc/run-test-jar
@@ -1,36 +1,111 @@
-#!/bin/bash
+#!/usr/bin/env python3
#
-# Runner for an individual run-test.
+# Copyright (C) 2022 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.
-readonly local_path=$(dirname "$0")
-source "${local_path}/apex-bootclasspath-utils.sh"
+import sys, os, shutil, shlex, re, subprocess, glob
+from apex_bootclasspath_utils import get_apex_bootclasspath, get_apex_bootclasspath_locations
+from os import path
+from os.path import isfile, isdir
+from typing import List
+from subprocess import DEVNULL, PIPE, STDOUT
-# Check how many colors the terminal can display.
-ncolors=$(tput colors 2>/dev/null)
+def export(env: str, value: str) -> None:
+ os.environ[env] = value
+ if env in globals():
+ globals()[env] = value
+
+# Script debugging: Record executed commands into the given directory.
+# This is useful to ensure that changes to the script don't change behaviour.
+# (the commands are appended so the directory needs to be cleared before run)
+ART_TEST_CMD_DIR = os.environ.get("ART_TEST_CMD_DIR")
+
+# Script debugging: Record executed commands, but don't actually run the main test.
+# This makes it possible the extract the test commands without waiting for days.
+# This will make tests fail since there is no stdout. Use with large -j value.
+ART_TEST_DRY_RUN = os.environ.get("ART_TEST_DRY_RUN")
+
+def run(cmdline: str, capture_output=True, check=True, save_cmd=True) -> subprocess.CompletedProcess:
+ if ART_TEST_CMD_DIR and save_cmd and cmdline != "true":
+ tmp = os.environ["DEX_LOCATION"]
+ with open(os.path.join(ART_TEST_CMD_DIR, os.environ["FULL_TEST_NAME"]), "a") as f:
+ env_ignore = ["SHLVL", "_", "ART_TOOLS_BUILD_VAR_CACHE", "PWD", "OLDPWD", "TMUX", "TMUX_PANE"]
+ env = {k: v for k, v in sorted(os.environ.items()) if k not in env_ignore}
+ # Replace DEX_LOCATION (which is randomly generated temporary directory),
+ # with a deterministic placeholder so that we can do a diff from run to run.
+ f.write("\n".join(k + ":" + v.replace(tmp, "<tmp>") for k, v in env.items()) + "\n\n")
+ f.write(re.sub(" +", "\n", cmdline).replace(tmp, "<tmp>") + "\n\n")
+ if ART_TEST_DRY_RUN and ("dalvikvm" in cmdline or "adb shell chroot" in cmdline):
+ cmdline = "true" # We still need to run some command, so run the no-op "true" binary instead.
+ proc = subprocess.run([cmdline],
+ shell=True,
+ encoding="utf8",
+ capture_output=capture_output)
+ if check and proc.returncode != 0:
+ print(proc.stdout or "", file=sys.stdout, flush=True)
+ print("$ " + cmdline + "\n", file=sys.stderr)
+ print(proc.stderr or "", file=sys.stderr, flush=True)
+ raise Exception("Command returned exit code {}".format(proc.returncode))
+ return proc
+
+class Adb():
+ def root(self) -> None:
+ run("adb root")
+ def wait_for_device(self) -> None:
+ run("adb wait-for-device")
+ def shell(self, cmdline: str, **kwargs) -> subprocess.CompletedProcess:
+ return run("adb shell " + cmdline, **kwargs)
+ def push(self, src: str, dst: str, **kwargs) -> None:
+ run(f"adb push {src} {dst}", **kwargs)
+
+adb = Adb()
+
+local_path=os.path.dirname(__file__)
# Check that stdout is connected to a terminal and that we have at least 1 color.
# This ensures that if the stdout is not connected to a terminal and instead
# the stdout will be used for a log, it will not append the color characters.
-if [[ -t 1 && ${ncolors} && ${ncolors} -ge 1 ]]; then
- bold_red="$(tput bold)$(tput setaf 1)"
-fi
+bold_red=""
+if sys.stdout.isatty():
+ if int(run("tput colors", save_cmd=False).stdout) >= 1:
+ bold_red=run("tput bold", save_cmd=False).stdout.strip()
+ bold_red+=run("tput setaf 1", save_cmd=False).stdout.strip()
-readonly bold_red
+def error_msg(msg: str):
+ print(f"{bold_red}ERROR: {msg}")
-error_msg() {
- echo -e "${bold_red}ERROR: $@" 1>&2
-}
+ANDROID_BUILD_TOP = os.environ.get("ANDROID_BUILD_TOP")
+ANDROID_DATA = os.environ.get("ANDROID_DATA")
+ANDROID_HOST_OUT = os.environ["ANDROID_HOST_OUT"]
+ANDROID_LOG_TAGS = os.environ.get("ANDROID_LOG_TAGS", "")
+ART_TIME_OUT_MULTIPLIER = int(os.environ.get("ART_TIME_OUT_MULTIPLIER", 1))
+DEX2OAT = os.environ.get("DEX2OAT", "")
+DEX_LOCATION = os.environ["DEX_LOCATION"]
+JAVA = os.environ.get("JAVA")
+OUT_DIR = os.environ.get("OUT_DIR")
+PATH = os.environ.get("PATH", "")
+SANITIZE_HOST = os.environ.get("SANITIZE_HOST", "")
+TEST_NAME = os.environ["TEST_NAME"]
+USE_EXRACTED_ZIPAPEX = os.environ.get("USE_EXRACTED_ZIPAPEX", "")
-if [[ -z "$ANDROID_BUILD_TOP" ]]; then
- error_msg 'ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?'
- exit 1
-fi
+if not ANDROID_BUILD_TOP:
+ error_msg('ANDROID_BUILD_TOP environment variable is empty; did you forget to run `lunch`?')
+ sys.exit(1)
-msg() {
- if [ "$QUIET" = "n" ]; then
- echo "$@"
- fi
-}
+def msg(msg: str):
+ if not QUIET:
+ print(msg)
ANDROID_ROOT="/system"
ANDROID_ART_ROOT="/apex/com.android.art"
@@ -38,20 +113,20 @@
ANDROID_TZDATA_ROOT="/apex/com.android.tzdata"
ARCHITECTURES_32="(arm|x86|none)"
ARCHITECTURES_64="(arm64|x86_64|none)"
-ARCHITECTURES_PATTERN="${ARCHITECTURES_32}"
+ARCHITECTURES_PATTERN=ARCHITECTURES_32
GET_DEVICE_ISA_BITNESS_FLAG="--32"
BOOT_IMAGE=""
-CHROOT=
+CHROOT=""
COMPILE_FLAGS=""
DALVIKVM="dalvikvm32"
DEBUGGER="n"
-WITH_AGENT=()
+WITH_AGENT=[]
DEBUGGER_AGENT=""
-WRAP_DEBUGGER_AGENT="n"
-DEV_MODE="n"
+WRAP_DEBUGGER_AGENT=False
+DEV_MODE=False
DEX2OAT_NDEBUG_BINARY="dex2oat32"
DEX2OAT_DEBUG_BINARY="dex2oatd32"
-EXPERIMENTAL=""
+EXPERIMENTAL=[]
FALSE_BIN="false"
FLAGS=""
ANDROID_FLAGS=""
@@ -61,831 +136,778 @@
GDB_DEX2OAT_ARGS=""
GDBSERVER_DEVICE="gdbserver"
GDBSERVER_HOST="gdbserver"
-HAVE_IMAGE="y"
-HOST="n"
-BIONIC="n"
-CREATE_ANDROID_ROOT="n"
-USE_ZIPAPEX="n"
+HAVE_IMAGE=True
+HOST=False
+BIONIC=False
+CREATE_ANDROID_ROOT=False
+USE_ZIPAPEX=False
ZIPAPEX_LOC=""
-USE_EXTRACTED_ZIPAPEX="n"
+USE_EXTRACTED_ZIPAPEX=False
EXTRACTED_ZIPAPEX_LOC=""
-INTERPRETER="n"
-JIT="n"
+INTERPRETER=False
+JIT=False
INVOKE_WITH=""
-IS_JVMTI_TEST="n"
-ADD_LIBDIR_ARGUMENTS="n"
+IS_JVMTI_TEST=False
+ADD_LIBDIR_ARGUMENTS=False
SUFFIX64=""
-ISA=x86
+ISA="x86"
LIBRARY_DIRECTORY="lib"
TEST_DIRECTORY="nativetest"
MAIN=""
-OPTIMIZE="y"
-PREBUILD="y"
-QUIET="n"
-RELOCATE="n"
+OPTIMIZE=True
+PREBUILD=True
+QUIET=False
+RELOCATE=False
SECONDARY_DEX=""
TIME_OUT="n" # "n" (disabled), "timeout" (use timeout), "gdb" (use gdb)
-TIMEOUT_DUMPER=signal_dumper
+TIMEOUT_DUMPER="signal_dumper"
# Values in seconds.
TIME_OUT_EXTRA=0
-TIME_OUT_VALUE=
-USE_GDB="n"
-USE_GDBSERVER="n"
+TIME_OUT_VALUE=0
+USE_GDB=False
+USE_GDBSERVER=False
GDBSERVER_PORT=":5039"
-USE_GDB_DEX2OAT="n"
-USE_JVM="n"
-USE_JVMTI="n"
+USE_GDB_DEX2OAT=False
+USE_JVM=False
+USE_JVMTI=False
VERIFY="y" # y=yes,n=no,s=softfail
ZYGOTE=""
DEX_VERIFY=""
INSTRUCTION_SET_FEATURES=""
ARGS=""
VDEX_ARGS=""
-EXTERNAL_LOG_TAGS="n" # if y respect externally set ANDROID_LOG_TAGS.
-DRY_RUN="n" # if y prepare to run the test but don't run it.
-TEST_VDEX="n"
-TEST_DEX2OAT_DM="n"
-TEST_RUNTIME_DM="n"
-TEST_IS_NDEBUG="n"
-APP_IMAGE="y"
-SECONDARY_APP_IMAGE="y"
+EXTERNAL_LOG_TAGS=False # if True respect externally set ANDROID_LOG_TAGS.
+DRY_RUN=False # if True prepare to run the test but don't run it.
+TEST_VDEX=False
+TEST_DEX2OAT_DM=False
+TEST_RUNTIME_DM=False
+TEST_IS_NDEBUG=False
+APP_IMAGE=True
+SECONDARY_APP_IMAGE=True
SECONDARY_CLASS_LOADER_CONTEXT=""
-SECONDARY_COMPILATION="y"
-JVMTI_STRESS="n"
-JVMTI_STEP_STRESS="n"
-JVMTI_FIELD_STRESS="n"
-JVMTI_TRACE_STRESS="n"
-JVMTI_REDEFINE_STRESS="n"
-PROFILE="n"
-RANDOM_PROFILE="n"
+SECONDARY_COMPILATION=True
+JVMTI_STRESS=False
+JVMTI_STEP_STRESS=False
+JVMTI_FIELD_STRESS=False
+JVMTI_TRACE_STRESS=False
+JVMTI_REDEFINE_STRESS=False
+PROFILE=False
+RANDOM_PROFILE=False
# The normal dex2oat timeout.
-DEX2OAT_TIMEOUT="300" # 5 mins
+DEX2OAT_TIMEOUT=300 # 5 mins
# The *hard* timeout where we really start trying to kill the dex2oat.
-DEX2OAT_RT_TIMEOUT="360" # 6 mins
-CREATE_RUNNER="n"
+DEX2OAT_RT_TIMEOUT=360 # 6 mins
+CREATE_RUNNER=False
+INT_OPTS=""
+SIMPLEPERF=False
+DEBUGGER_OPTS=""
+JVM_VERIFY_ARG=""
-# if "y", run 'sync' before dalvikvm to make sure all files from
+# if True, run 'sync' before dalvikvm to make sure all files from
# build step (e.g. dex2oat) were finished writing.
-SYNC_BEFORE_RUN="n"
+SYNC_BEFORE_RUN=False
# When running a debug build, we want to run with all checks.
-ANDROID_FLAGS="${ANDROID_FLAGS} -XX:SlowDebug=true"
+ANDROID_FLAGS+=" -XX:SlowDebug=true"
# The same for dex2oatd, both prebuild and runtime-driven.
-ANDROID_FLAGS="${ANDROID_FLAGS} -Xcompiler-option --runtime-arg -Xcompiler-option -XX:SlowDebug=true"
-COMPILER_FLAGS="${COMPILER_FLAGS} --runtime-arg -XX:SlowDebug=true"
+ANDROID_FLAGS+=" -Xcompiler-option --runtime-arg -Xcompiler-option -XX:SlowDebug=true"
+COMPILER_FLAGS=" --runtime-arg -XX:SlowDebug=true"
# Let the compiler and runtime know that we are running tests.
-COMPILE_FLAGS="${COMPILE_FLAGS} --compile-art-test"
-ANDROID_FLAGS="${ANDROID_FLAGS} -Xcompiler-option --compile-art-test"
+COMPILE_FLAGS+=" --compile-art-test"
+ANDROID_FLAGS+=" -Xcompiler-option --compile-art-test"
-while true; do
- if [ "x$1" = "x--quiet" ]; then
- QUIET="y"
- shift
- elif [ "x$1" = "x--dex2oat-rt-timeout" ]; then
- shift
- if [ "x$1" = "x" ]; then
- error_msg "$0 missing argument to --dex2oat-rt-timeout"
- exit 1
- fi
- DEX2OAT_RT_TIMEOUT="$1"
- shift
- elif [ "x$1" = "x--dex2oat-timeout" ]; then
- shift
- if [ "x$1" = "x" ]; then
- error_msg "$0 missing argument to --dex2oat-timeout"
- exit 1
- fi
- DEX2OAT_TIMEOUT="$1"
- shift
- elif [ "x$1" = "x--jvmti" ]; then
- USE_JVMTI="y"
- IS_JVMTI_TEST="y"
+args = list(sys.argv)
+arg = ""
+def shift():
+ global arg
+ args.pop(0)
+ arg = args[0] if args else None
+shift()
+while arg:
+ if arg == "--quiet":
+ QUIET=True
+ shift()
+ elif arg == "--dex2oat-rt-timeout":
+ shift()
+ if arg == "":
+ error_msg("missing argument to --dex2oat-rt-timeout")
+ sys.exit(1)
+ DEX2OAT_RT_TIMEOUT=int(arg)
+ shift()
+ elif arg == "--dex2oat-timeout":
+ shift()
+ if arg == "":
+ error_msg("missing argument to --dex2oat-timeout")
+ sys.exit(1)
+ DEX2OAT_TIMEOUT=int(arg)
+ shift()
+ elif arg == "--jvmti":
+ USE_JVMTI=True
+ IS_JVMTI_TEST=True
# Secondary images block some tested behavior.
- SECONDARY_APP_IMAGE="n"
- shift
- elif [ "x$1" = "x--add-libdir-argument" ]; then
- ADD_LIBDIR_ARGUMENTS="y"
- shift
- elif [ "x$1" = "x-O" ]; then
- TEST_IS_NDEBUG="y"
- shift
- elif [ "x$1" = "x--lib" ]; then
- shift
- if [ "x$1" = "x" ]; then
- error_msg "$0 missing argument to --lib"
- exit 1
- fi
- LIB="$1"
- shift
- elif [ "x$1" = "x--gc-stress" ]; then
+ SECONDARY_APP_IMAGE=False
+ shift()
+ elif arg == "--add-libdir-argument":
+ ADD_LIBDIR_ARGUMENTS=True
+ shift()
+ elif arg == "-O":
+ TEST_IS_NDEBUG=True
+ shift()
+ elif arg == "--lib":
+ shift()
+ if arg == "":
+ error_msg("missing argument to --lib")
+ sys.exit(1)
+ LIB=arg
+ shift()
+ elif arg == "--gc-stress":
# Give an extra 20 mins if we are gc-stress.
- TIME_OUT_EXTRA=$((${TIME_OUT_EXTRA} + 1200))
- shift
- elif [ "x$1" = "x--testlib" ]; then
- shift
- if [ "x$1" = "x" ]; then
- error_msg "$0 missing argument to --testlib"
- exit 1
- fi
- ARGS="${ARGS} $1"
- shift
- elif [ "x$1" = "x--args" ]; then
- shift
- if [ "x$1" = "x" ]; then
- error_msg "$0 missing argument to --args"
- exit 1
- fi
- ARGS="${ARGS} $1"
- shift
- elif [ "x$1" = "x--compiler-only-option" ]; then
- shift
- option="$1"
- COMPILE_FLAGS="${COMPILE_FLAGS} $option"
- shift
- elif [ "x$1" = "x-Xcompiler-option" ]; then
- shift
- option="$1"
- FLAGS="${FLAGS} -Xcompiler-option $option"
- COMPILE_FLAGS="${COMPILE_FLAGS} $option"
- shift
- elif [ "x$1" = "x--create-runner" ]; then
- CREATE_RUNNER="y"
- shift
- elif [ "x$1" = "x--android-runtime-option" ]; then
- shift
- option="$1"
- ANDROID_FLAGS="${ANDROID_FLAGS} $option"
- shift
- elif [ "x$1" = "x--runtime-option" ]; then
- shift
- option="$1"
- FLAGS="${FLAGS} $option"
- if [ "x$option" = "x-Xmethod-trace" ]; then
+ TIME_OUT_EXTRA+=1200
+ shift()
+ elif arg == "--testlib":
+ shift()
+ if arg == "":
+ error_msg("missing argument to --testlib")
+ sys.exit(1)
+ ARGS+=f" {arg}"
+ shift()
+ elif arg == "--args":
+ shift()
+ if arg == "":
+ error_msg("missing argument to --args")
+ sys.exit(1)
+ ARGS+=f" {arg}"
+ shift()
+ elif arg == "--compiler-only-option":
+ shift()
+ option=arg
+ COMPILE_FLAGS+=f" {option}"
+ shift()
+ elif arg == "-Xcompiler-option":
+ shift()
+ option=arg
+ FLAGS+=f" -Xcompiler-option {option}"
+ COMPILE_FLAGS+=f" {option}"
+ shift()
+ elif arg == "--create-runner":
+ CREATE_RUNNER=True
+ shift()
+ elif arg == "--android-runtime-option":
+ shift()
+ option=arg
+ ANDROID_FLAGS+=f" {option}"
+ shift()
+ elif arg == "--runtime-option":
+ shift()
+ option=arg
+ FLAGS+=f" {option}"
+ if option == "-Xmethod-trace":
# Method tracing can slow some tests down a lot.
- TIME_OUT_EXTRA=$((${TIME_OUT_EXTRA} + 1200))
- fi
- shift
- elif [ "x$1" = "x--boot" ]; then
- shift
- BOOT_IMAGE="$1"
- shift
- elif [ "x$1" = "x--relocate" ]; then
- RELOCATE="y"
- shift
- elif [ "x$1" = "x--no-relocate" ]; then
- RELOCATE="n"
- shift
- elif [ "x$1" = "x--prebuild" ]; then
- PREBUILD="y"
- shift
- elif [ "x$1" = "x--compact-dex-level" ]; then
- shift
- COMPILE_FLAGS="${COMPILE_FLAGS} --compact-dex-level=$1"
- shift
- elif [ "x$1" = "x--jvmti-redefine-stress" ]; then
+ TIME_OUT_EXTRA+=1200
+ shift()
+ elif arg == "--boot":
+ shift()
+ BOOT_IMAGE=arg
+ shift()
+ elif arg == "--relocate":
+ RELOCATE=True
+ shift()
+ elif arg == "--no-relocate":
+ RELOCATE=False
+ shift()
+ elif arg == "--prebuild":
+ PREBUILD=True
+ shift()
+ elif arg == "--compact-dex-level":
+ shift()
+ COMPILE_FLAGS+=f" --compact-dex-level={arg}"
+ shift()
+ elif arg == "--jvmti-redefine-stress":
# APP_IMAGE doesn't really work with jvmti redefine stress
- USE_JVMTI="y"
- APP_IMAGE="n"
- SECONDARY_APP_IMAGE="n"
- JVMTI_STRESS="y"
- JVMTI_REDEFINE_STRESS="y"
- shift
- elif [ "x$1" = "x--jvmti-step-stress" ]; then
- USE_JVMTI="y"
- JVMTI_STRESS="y"
- JVMTI_STEP_STRESS="y"
- shift
- elif [ "x$1" = "x--jvmti-field-stress" ]; then
- USE_JVMTI="y"
- JVMTI_STRESS="y"
- JVMTI_FIELD_STRESS="y"
- shift
- elif [ "x$1" = "x--jvmti-trace-stress" ]; then
- USE_JVMTI="y"
- JVMTI_STRESS="y"
- JVMTI_TRACE_STRESS="y"
- shift
- elif [ "x$1" = "x--no-app-image" ]; then
- APP_IMAGE="n"
- shift
- elif [ "x$1" = "x--no-secondary-app-image" ]; then
- SECONDARY_APP_IMAGE="n"
- shift
- elif [ "x$1" = "x--secondary-class-loader-context" ]; then
- shift
- SECONDARY_CLASS_LOADER_CONTEXT="$1"
- shift
- elif [ "x$1" = "x--no-secondary-compilation" ]; then
- SECONDARY_COMPILATION="n"
- shift
- elif [ "x$1" = "x--host" ]; then
- HOST="y"
- ANDROID_ROOT="${ANDROID_HOST_OUT}"
- ANDROID_ART_ROOT="${ANDROID_HOST_OUT}/com.android.art"
- ANDROID_I18N_ROOT="${ANDROID_HOST_OUT}/com.android.i18n"
- ANDROID_TZDATA_ROOT="${ANDROID_HOST_OUT}/com.android.tzdata"
+ USE_JVMTI=True
+ APP_IMAGE=False
+ SECONDARY_APP_IMAGE=False
+ JVMTI_STRESS=True
+ JVMTI_REDEFINE_STRESS=True
+ shift()
+ elif arg == "--jvmti-step-stress":
+ USE_JVMTI=True
+ JVMTI_STRESS=True
+ JVMTI_STEP_STRESS=True
+ shift()
+ elif arg == "--jvmti-field-stress":
+ USE_JVMTI=True
+ JVMTI_STRESS=True
+ JVMTI_FIELD_STRESS=True
+ shift()
+ elif arg == "--jvmti-trace-stress":
+ USE_JVMTI=True
+ JVMTI_STRESS=True
+ JVMTI_TRACE_STRESS=True
+ shift()
+ elif arg == "--no-app-image":
+ APP_IMAGE=False
+ shift()
+ elif arg == "--no-secondary-app-image":
+ SECONDARY_APP_IMAGE=False
+ shift()
+ elif arg == "--secondary-class-loader-context":
+ shift()
+ SECONDARY_CLASS_LOADER_CONTEXT=arg
+ shift()
+ elif arg == "--no-secondary-compilation":
+ SECONDARY_COMPILATION=False
+ shift()
+ elif arg == "--host":
+ HOST=True
+ ANDROID_ROOT=ANDROID_HOST_OUT
+ ANDROID_ART_ROOT=f"{ANDROID_HOST_OUT}/com.android.art"
+ ANDROID_I18N_ROOT=f"{ANDROID_HOST_OUT}/com.android.i18n"
+ ANDROID_TZDATA_ROOT=f"{ANDROID_HOST_OUT}/com.android.tzdata"
# On host, we default to using the symlink, as the PREFER_32BIT
# configuration is the only configuration building a 32bit version of
# dex2oat.
DEX2OAT_DEBUG_BINARY="dex2oatd"
DEX2OAT_NDEBUG_BINARY="dex2oat"
- shift
- elif [ "x$1" = "x--bionic" ]; then
- BIONIC="y"
+ shift()
+ elif arg == "--bionic":
+ BIONIC=True
# We need to create an ANDROID_ROOT because currently we cannot create
# the frameworks/libcore with linux_bionic so we need to use the normal
# host ones which are in a different location.
- CREATE_ANDROID_ROOT="y"
- shift
- elif [ "x$1" = "x--runtime-extracted-zipapex" ]; then
- shift
- USE_EXTRACTED_ZIPAPEX="y"
- EXTRACTED_ZIPAPEX_LOC="$1"
- shift
- elif [ "x$1" = "x--runtime-zipapex" ]; then
- shift
- USE_ZIPAPEX="y"
- ZIPAPEX_LOC="$1"
+ CREATE_ANDROID_ROOT=True
+ shift()
+ elif arg == "--runtime-extracted-zipapex":
+ shift()
+ USE_EXTRACTED_ZIPAPEX=True
+ EXTRACTED_ZIPAPEX_LOC=arg
+ shift()
+ elif arg == "--runtime-zipapex":
+ shift()
+ USE_ZIPAPEX=True
+ ZIPAPEX_LOC=arg
# TODO (b/119942078): Currently apex does not support
# symlink_preferred_arch so we will not have a dex2oatd to execute and
# need to manually provide
# dex2oatd64.
DEX2OAT_DEBUG_BINARY="dex2oatd64"
- shift
- elif [ "x$1" = "x--no-prebuild" ]; then
- PREBUILD="n"
- shift
- elif [ "x$1" = "x--no-image" ]; then
- HAVE_IMAGE="n"
- shift
- elif [ "x$1" = "x--secondary" ]; then
- SECONDARY_DEX=":$DEX_LOCATION/$TEST_NAME-ex.jar"
+ shift()
+ elif arg == "--no-prebuild":
+ PREBUILD=False
+ shift()
+ elif arg == "--no-image":
+ HAVE_IMAGE=False
+ shift()
+ elif arg == "--secondary":
+ SECONDARY_DEX=f":{DEX_LOCATION}/{TEST_NAME}-ex.jar"
# Enable cfg-append to make sure we get the dump for both dex files.
# (otherwise the runtime compilation of the secondary dex will overwrite
# the dump of the first one).
- FLAGS="${FLAGS} -Xcompiler-option --dump-cfg-append"
- COMPILE_FLAGS="${COMPILE_FLAGS} --dump-cfg-append"
- shift
- elif [ "x$1" = "x--with-agent" ]; then
- shift
- USE_JVMTI="y"
- WITH_AGENT+=("$1")
- shift
- elif [ "x$1" = "x--debug-wrap-agent" ]; then
- WRAP_DEBUGGER_AGENT="y"
- shift
- elif [ "x$1" = "x--debug-agent" ]; then
- shift
+ FLAGS+=" -Xcompiler-option --dump-cfg-append"
+ COMPILE_FLAGS+=" --dump-cfg-append"
+ shift()
+ elif arg == "--with-agent":
+ shift()
+ USE_JVMTI=True
+ WITH_AGENT.append(arg)
+ shift()
+ elif arg == "--debug-wrap-agent":
+ WRAP_DEBUGGER_AGENT=True
+ shift()
+ elif arg == "--debug-agent":
+ shift()
DEBUGGER="agent"
- USE_JVMTI="y"
- DEBUGGER_AGENT="$1"
+ USE_JVMTI=True
+ DEBUGGER_AGENT=arg
TIME_OUT="n"
- shift
- elif [ "x$1" = "x--debug" ]; then
- USE_JVMTI="y"
+ shift()
+ elif arg == "--debug":
+ USE_JVMTI=True
DEBUGGER="y"
TIME_OUT="n"
- shift
- elif [ "x$1" = "x--gdbserver-port" ]; then
- shift
- GDBSERVER_PORT=$1
- shift
- elif [ "x$1" = "x--gdbserver-bin" ]; then
- shift
- GDBSERVER_HOST=$1
- GDBSERVER_DEVICE=$1
- shift
- elif [ "x$1" = "x--gdbserver" ]; then
- USE_GDBSERVER="y"
- DEV_MODE="y"
+ shift()
+ elif arg == "--gdbserver-port":
+ shift()
+ GDBSERVER_PORT=arg
+ shift()
+ elif arg == "--gdbserver-bin":
+ shift()
+ GDBSERVER_HOST=arg
+ GDBSERVER_DEVICE=arg
+ shift()
+ elif arg == "--gdbserver":
+ USE_GDBSERVER=True
+ DEV_MODE=True
TIME_OUT="n"
- shift
- elif [ "x$1" = "x--gdb" ]; then
- USE_GDB="y"
- DEV_MODE="y"
+ shift()
+ elif arg == "--gdb":
+ USE_GDB=True
+ DEV_MODE=True
TIME_OUT="n"
- shift
- elif [ "x$1" = "x--gdb-arg" ]; then
- shift
- gdb_arg="$1"
- GDB_ARGS="${GDB_ARGS} $gdb_arg"
- shift
- elif [ "x$1" = "x--gdb-dex2oat" ]; then
- USE_GDB_DEX2OAT="y"
- DEV_MODE="y"
+ shift()
+ elif arg == "--gdb-arg":
+ shift()
+ gdb_arg=arg
+ GDB_ARGS+=f" {gdb_arg}"
+ shift()
+ elif arg == "--gdb-dex2oat":
+ USE_GDB_DEX2OAT=True
+ DEV_MODE=True
TIME_OUT="n"
- shift
- elif [ "x$1" = "x--gdb-dex2oat-args" ]; then
- shift
- for arg in $(echo $1 | tr ";" " "); do
- GDB_DEX2OAT_ARGS+="$arg "
- done
- shift
- elif [ "x$1" = "x--zygote" ]; then
+ shift()
+ elif arg == "--gdb-dex2oat-args":
+ shift()
+ for arg in arg.split(";"):
+ GDB_DEX2OAT_ARGS+=f"{arg} "
+ shift()
+ elif arg == "--zygote":
ZYGOTE="-Xzygote"
- msg "Spawning from zygote"
- shift
- elif [ "x$1" = "x--dev" ]; then
- DEV_MODE="y"
- shift
- elif [ "x$1" = "x--interpreter" ]; then
- INTERPRETER="y"
- shift
- elif [ "x$1" = "x--jit" ]; then
- JIT="y"
- shift
- elif [ "x$1" = "x--baseline" ]; then
- FLAGS="${FLAGS} -Xcompiler-option --baseline"
- COMPILE_FLAGS="${COMPILE_FLAGS} --baseline"
- shift
- elif [ "x$1" = "x--jvm" ]; then
- USE_JVM="y"
- shift
- elif [ "x$1" = "x--invoke-with" ]; then
- shift
- if [ "x$1" = "x" ]; then
- error_msg "$0 missing argument to --invoke-with"
- exit 1
- fi
- if [ "x$INVOKE_WITH" = "x" ]; then
- INVOKE_WITH="$1"
- else
- INVOKE_WITH="$INVOKE_WITH $1"
- fi
- shift
- elif [ "x$1" = "x--no-verify" ]; then
+ msg("Spawning from zygote")
+ shift()
+ elif arg == "--dev":
+ DEV_MODE=True
+ shift()
+ elif arg == "--interpreter":
+ INTERPRETER=True
+ shift()
+ elif arg == "--jit":
+ JIT=True
+ shift()
+ elif arg == "--baseline":
+ FLAGS+=" -Xcompiler-option --baseline"
+ COMPILE_FLAGS+=" --baseline"
+ shift()
+ elif arg == "--jvm":
+ USE_JVM=True
+ shift()
+ elif arg == "--invoke-with":
+ shift()
+ if arg == "":
+ error_msg("missing argument to --invoke-with")
+ sys.exit(1)
+ if INVOKE_WITH == "":
+ INVOKE_WITH=arg
+ else:
+ INVOKE_WITH+=f" {arg}"
+ shift()
+ elif arg == "--no-verify":
VERIFY="n"
- shift
- elif [ "x$1" = "x--verify-soft-fail" ]; then
+ shift()
+ elif arg == "--verify-soft-fail":
VERIFY="s"
- shift
- elif [ "x$1" = "x--no-optimize" ]; then
- OPTIMIZE="n"
- shift
- elif [ "x$1" = "x--chroot" ]; then
- shift
- CHROOT="$1"
- shift
- elif [ "x$1" = "x--simpleperf" ]; then
- SIMPLEPERF="yes"
- shift
- elif [ "x$1" = "x--android-root" ]; then
- shift
- ANDROID_ROOT="$1"
- shift
- elif [ "x$1" = "x--android-i18n-root" ]; then
- shift
- ANDROID_I18N_ROOT="$1"
- shift
- elif [ "x$1" = "x--android-art-root" ]; then
- shift
- ANDROID_ART_ROOT="$1"
- shift
- elif [ "x$1" = "x--android-tzdata-root" ]; then
- shift
- ANDROID_TZDATA_ROOT="$1"
- shift
- elif [ "x$1" = "x--instruction-set-features" ]; then
- shift
- INSTRUCTION_SET_FEATURES="$1"
- shift
- elif [ "x$1" = "x--timeout" ]; then
- shift
- TIME_OUT_VALUE="$1"
- shift
- elif [ "x$1" = "x--" ]; then
- shift
+ shift()
+ elif arg == "--no-optimize":
+ OPTIMIZE=False
+ shift()
+ elif arg == "--chroot":
+ shift()
+ CHROOT=arg
+ shift()
+ elif arg == "--simpleperf":
+ SIMPLEPERF=True
+ shift()
+ elif arg == "--android-root":
+ shift()
+ ANDROID_ROOT=arg
+ shift()
+ elif arg == "--android-i18n-root":
+ shift()
+ ANDROID_I18N_ROOT=arg
+ shift()
+ elif arg == "--android-art-root":
+ shift()
+ ANDROID_ART_ROOT=arg
+ shift()
+ elif arg == "--android-tzdata-root":
+ shift()
+ ANDROID_TZDATA_ROOT=arg
+ shift()
+ elif arg == "--instruction-set-features":
+ shift()
+ INSTRUCTION_SET_FEATURES=arg
+ shift()
+ elif arg == "--timeout":
+ shift()
+ TIME_OUT_VALUE=int(arg)
+ shift()
+ elif arg == "--":
+ shift()
break
- elif [ "x$1" = "x--64" ]; then
+ elif arg == "--64":
SUFFIX64="64"
ISA="x86_64"
GDBSERVER_DEVICE="gdbserver64"
DALVIKVM="dalvikvm64"
LIBRARY_DIRECTORY="lib64"
TEST_DIRECTORY="nativetest64"
- ARCHITECTURES_PATTERN="${ARCHITECTURES_64}"
+ ARCHITECTURES_PATTERN=ARCHITECTURES_64
GET_DEVICE_ISA_BITNESS_FLAG="--64"
DEX2OAT_NDEBUG_BINARY="dex2oat64"
DEX2OAT_DEBUG_BINARY="dex2oatd64"
- shift
- elif [ "x$1" = "x--experimental" ]; then
- if [ "$#" -lt 2 ]; then
- error_msg "missing --experimental option"
- exit 1
- fi
- EXPERIMENTAL="$EXPERIMENTAL $2"
- shift 2
- elif [ "x$1" = "x--external-log-tags" ]; then
- EXTERNAL_LOG_TAGS="y"
- shift
- elif [ "x$1" = "x--dry-run" ]; then
- DRY_RUN="y"
- shift
- elif [ "x$1" = "x--vdex" ]; then
- TEST_VDEX="y"
- shift
- elif [ "x$1" = "x--dex2oat-dm" ]; then
- TEST_DEX2OAT_DM="y"
- shift
- elif [ "x$1" = "x--runtime-dm" ]; then
- TEST_RUNTIME_DM="y"
- shift
- elif [ "x$1" = "x--vdex-filter" ]; then
- shift
- option="$1"
- VDEX_ARGS="${VDEX_ARGS} --compiler-filter=$option"
- shift
- elif [ "x$1" = "x--vdex-arg" ]; then
- shift
- VDEX_ARGS="${VDEX_ARGS} $1"
- shift
- elif [ "x$1" = "x--sync" ]; then
- SYNC_BEFORE_RUN="y"
- shift
- elif [ "x$1" = "x--profile" ]; then
- PROFILE="y"
- shift
- elif [ "x$1" = "x--random-profile" ]; then
- RANDOM_PROFILE="y"
- shift
- elif expr "x$1" : "x--" >/dev/null 2>&1; then
- error_msg "unknown $0 option: $1"
- exit 1
- else
+ shift()
+ elif arg == "--experimental":
+ if len(args) < 2:
+ error_msg("missing --experimental option")
+ sys.exit(1)
+ shift()
+ EXPERIMENTAL.append(arg)
+ shift()
+ elif arg == "--external-log-tags":
+ EXTERNAL_LOG_TAGS=True
+ shift()
+ elif arg == "--dry-run":
+ DRY_RUN=True
+ shift()
+ elif arg == "--vdex":
+ TEST_VDEX=True
+ shift()
+ elif arg == "--dex2oat-dm":
+ TEST_DEX2OAT_DM=True
+ shift()
+ elif arg == "--runtime-dm":
+ TEST_RUNTIME_DM=True
+ shift()
+ elif arg == "--vdex-filter":
+ shift()
+ option=arg
+ VDEX_ARGS+=f" --compiler-filter={option}"
+ shift()
+ elif arg == "--vdex-arg":
+ shift()
+ VDEX_ARGS+=f" {arg}"
+ shift()
+ elif arg == "--sync":
+ SYNC_BEFORE_RUN=True
+ shift()
+ elif arg == "--profile":
+ PROFILE=True
+ shift()
+ elif arg == "--random-profile":
+ RANDOM_PROFILE=True
+ shift()
+ elif arg.startswith("--"):
+ error_msg(f"unknown option: {arg}")
+ sys.exit(1)
+ else:
break
- fi
-done
# HACK: Force the use of `signal_dumper` on host.
-if [[ "$HOST" = "y" ]]; then
+if HOST:
TIME_OUT="timeout"
-fi
# If you change this, update the timeout in testrunner.py as well.
-if [ -z "$TIME_OUT_VALUE" ] ; then
+if not TIME_OUT_VALUE:
# 10 minutes is the default.
TIME_OUT_VALUE=600
# For sanitized builds use a larger base.
# TODO: Consider sanitized target builds?
- if [ "x$SANITIZE_HOST" != "x" ] ; then
+ if SANITIZE_HOST != "":
TIME_OUT_VALUE=1500 # 25 minutes.
- fi
- TIME_OUT_VALUE=$((${TIME_OUT_VALUE} + ${TIME_OUT_EXTRA}))
-fi
+ TIME_OUT_VALUE+=TIME_OUT_EXTRA
# Escape hatch for slow hosts or devices. Accept an environment variable as a timeout factor.
-if [ ! -z "$ART_TIME_OUT_MULTIPLIER" ] ; then
- TIME_OUT_VALUE=$((${TIME_OUT_VALUE} * ${ART_TIME_OUT_MULTIPLIER}))
-fi
+if ART_TIME_OUT_MULTIPLIER:
+ TIME_OUT_VALUE*=ART_TIME_OUT_MULTIPLIER
# The DEX_LOCATION with the chroot prefix, if any.
-CHROOT_DEX_LOCATION="$CHROOT$DEX_LOCATION"
+CHROOT_DEX_LOCATION=f"{CHROOT}{DEX_LOCATION}"
# If running on device, determine the ISA of the device.
-if [ "$HOST" = "n" -a "$USE_JVM" = "n" ]; then
- ISA=$("$ANDROID_BUILD_TOP/art/test/utils/get-device-isa" "$GET_DEVICE_ISA_BITNESS_FLAG")
-fi
+if not HOST and not USE_JVM:
+ ISA=run(f"{ANDROID_BUILD_TOP}/art/test/utils/get-device-isa {GET_DEVICE_ISA_BITNESS_FLAG}",
+ save_cmd=False).stdout.strip()
-if [ "$USE_JVM" = "n" ]; then
- FLAGS="${FLAGS} ${ANDROID_FLAGS}"
+if not USE_JVM:
+ FLAGS+=f" {ANDROID_FLAGS}"
# we don't want to be trying to get adbconnections since the plugin might
# not have been built.
- FLAGS="${FLAGS} -XjdwpProvider:none"
- for feature in ${EXPERIMENTAL}; do
- FLAGS="${FLAGS} -Xexperimental:${feature} -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:${feature}"
- COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xexperimental:${feature}"
- done
-fi
+ FLAGS+=" -XjdwpProvider:none"
+ for feature in EXPERIMENTAL:
+ FLAGS+=f" -Xexperimental:{feature} -Xcompiler-option --runtime-arg -Xcompiler-option -Xexperimental:{feature}"
+ COMPILE_FLAGS=f"{COMPILE_FLAGS} --runtime-arg -Xexperimental:{feature}"
-if [ "$CREATE_ANDROID_ROOT" = "y" ]; then
- ANDROID_ROOT=$DEX_LOCATION/android-root
-fi
+if CREATE_ANDROID_ROOT:
+ ANDROID_ROOT=f"{DEX_LOCATION}/android-root"
-if [ "x$1" = "x" ] ; then
+if not arg:
MAIN="Main"
-else
- MAIN="$1"
- shift
-fi
+else:
+ MAIN=arg
+ shift()
-if [ "$ZYGOTE" = "" ]; then
- if [ "$OPTIMIZE" = "y" ]; then
- if [ "$VERIFY" = "y" ]; then
+test_args = (" " + " ".join(args)) if args else ""
+
+if ZYGOTE == "":
+ if OPTIMIZE:
+ if VERIFY == "y":
DEX_OPTIMIZE="-Xdexopt:verified"
- else
+ else:
DEX_OPTIMIZE="-Xdexopt:all"
- fi
- msg "Performing optimizations"
- else
+ msg("Performing optimizations")
+ else:
DEX_OPTIMIZE="-Xdexopt:none"
- msg "Skipping optimizations"
- fi
+ msg("Skipping optimizations")
- if [ "$VERIFY" = "y" ]; then
+ if VERIFY == "y":
JVM_VERIFY_ARG="-Xverify:all"
- msg "Performing verification"
- elif [ "$VERIFY" = "s" ]; then
+ msg("Performing verification")
+ elif VERIFY == "s":
JVM_VERIFY_ARG="Xverify:all"
DEX_VERIFY="-Xverify:softfail"
- msg "Forcing verification to be soft fail"
- else # VERIFY = "n"
+ msg("Forcing verification to be soft fail")
+ else: # VERIFY == "n"
DEX_VERIFY="-Xverify:none"
JVM_VERIFY_ARG="-Xverify:none"
- msg "Skipping verification"
- fi
-fi
+ msg("Skipping verification")
-msg "------------------------------"
+msg("------------------------------")
-if [ "$DEBUGGER" = "y" ]; then
+if DEBUGGER == "y":
# Use this instead for ddms and connect by running 'ddms':
# DEBUGGER_OPTS="-XjdwpOptions=server=y,suspend=y -XjdwpProvider:adbconnection"
# TODO: add a separate --ddms option?
PORT=12345
- msg "Waiting for jdb to connect:"
- if [ "$HOST" = "n" ]; then
- msg " adb forward tcp:$PORT tcp:$PORT"
- fi
- msg " jdb -attach localhost:$PORT"
- if [ "$USE_JVM" = "n" ]; then
+ msg("Waiting for jdb to connect:")
+ if not HOST:
+ msg(f" adb forward tcp:{PORT} tcp:{PORT}")
+ msg(f" jdb -attach localhost:{PORT}")
+ if not USE_JVM:
# Use the default libjdwp agent. Use --debug-agent to use a custom one.
- DEBUGGER_OPTS="-agentpath:libjdwp.so=transport=dt_socket,address=$PORT,server=y,suspend=y -XjdwpProvider:internal"
- else
- DEBUGGER_OPTS="-agentlib:jdwp=transport=dt_socket,address=$PORT,server=y,suspend=y"
- fi
-elif [ "$DEBUGGER" = "agent" ]; then
+ DEBUGGER_OPTS=f"-agentpath:libjdwp.so=transport=dt_socket,address={PORT},server=y,suspend=y -XjdwpProvider:internal"
+ else:
+ DEBUGGER_OPTS=f"-agentlib:jdwp=transport=dt_socket,address={PORT},server=y,suspend=y"
+elif DEBUGGER == "agent":
PORT=12345
# TODO Support ddms connection and support target.
- if [ "$HOST" = "n" ]; then
- error_msg "--debug-agent not supported yet for target!"
- exit 1
- fi
- AGENTPATH=${DEBUGGER_AGENT}
- if [ "$WRAP_DEBUGGER_AGENT" = "y" ]; then
- WRAPPROPS="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}/libwrapagentpropertiesd.so"
- if [ "$TEST_IS_NDEBUG" = "y" ]; then
- WRAPPROPS="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}/libwrapagentproperties.so"
- fi
- AGENTPATH="${WRAPPROPS}=${ANDROID_BUILD_TOP}/art/tools/libjdwp-compat.props,${AGENTPATH}"
- fi
- msg "Connect to localhost:$PORT"
- DEBUGGER_OPTS="-agentpath:${AGENTPATH}=transport=dt_socket,address=$PORT,server=y,suspend=y"
-fi
+ if not HOST:
+ error_msg("--debug-agent not supported yet for target!")
+ sys.exit(1)
+ AGENTPATH=DEBUGGER_AGENT
+ if WRAP_DEBUGGER_AGENT:
+ WRAPPROPS=f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}/libwrapagentpropertiesd.so"
+ if TEST_IS_NDEBUG:
+ WRAPPROPS=f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}/libwrapagentproperties.so"
+ AGENTPATH=f"{WRAPPROPS}={ANDROID_BUILD_TOP}/art/tools/libjdwp-compat.props,{AGENTPATH}"
+ msg(f"Connect to localhost:{PORT}")
+ DEBUGGER_OPTS=f"-agentpath:{AGENTPATH}=transport=dt_socket,address={PORT},server=y,suspend=y"
-for agent in "${WITH_AGENT[@]}"; do
- FLAGS="${FLAGS} -agentpath:${agent}"
-done
+for agent in WITH_AGENT:
+ FLAGS+=f" -agentpath:{agent}"
-if [ "$USE_JVMTI" = "y" ]; then
- if [ "$USE_JVM" = "n" ]; then
- plugin=libopenjdkjvmtid.so
- if [[ "$TEST_IS_NDEBUG" = "y" ]]; then
- plugin=libopenjdkjvmti.so
- fi
+if USE_JVMTI:
+ if not USE_JVM:
+ plugin="libopenjdkjvmtid.so"
+ if TEST_IS_NDEBUG:
+ plugin="libopenjdkjvmti.so"
# We used to add flags here that made the runtime debuggable but that is not
# needed anymore since the plugin can do it for us now.
- FLAGS="${FLAGS} -Xplugin:${plugin}"
+ FLAGS+=f" -Xplugin:{plugin}"
# For jvmti tests, set the threshold of compilation to 1, so we jit early to
# provide better test coverage for jvmti + jit. This means we won't run
# the default --jit configuration but it is not too important test scenario for
# jvmti tests. This is art specific flag, so don't use it with jvm.
- FLAGS="${FLAGS} -Xjitthreshold:1"
- fi
-fi
+ FLAGS+=" -Xjitthreshold:1"
# Add the libdir to the argv passed to the main function.
-if [ "$ADD_LIBDIR_ARGUMENTS" = "y" ]; then
- if [[ "$HOST" = "y" ]]; then
- ARGS="${ARGS} ${ANDROID_HOST_OUT}/${TEST_DIRECTORY}/"
- else
- ARGS="${ARGS} /data/${TEST_DIRECTORY}/art/${ISA}/"
- fi
-fi
-if [ "$IS_JVMTI_TEST" = "y" ]; then
- agent=libtiagentd.so
- lib=tiagentd
- if [[ "$TEST_IS_NDEBUG" = "y" ]]; then
- agent=libtiagent.so
- lib=tiagent
- fi
+if ADD_LIBDIR_ARGUMENTS:
+ if HOST:
+ ARGS+=f" {ANDROID_HOST_OUT}/{TEST_DIRECTORY}/"
+ else:
+ ARGS+=f" /data/{TEST_DIRECTORY}/art/{ISA}/"
+if IS_JVMTI_TEST:
+ agent="libtiagentd.so"
+ lib="tiagentd"
+ if TEST_IS_NDEBUG:
+ agent="libtiagent.so"
+ lib="tiagent"
- ARGS="${ARGS} ${lib}"
- if [[ "$USE_JVM" = "y" ]]; then
- FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=${TEST_NAME},jvm"
- else
- FLAGS="${FLAGS} -agentpath:${agent}=${TEST_NAME},art"
- fi
-fi
+ ARGS+=f" {lib}"
+ if USE_JVM:
+ FLAGS+=f" -agentpath:{ANDROID_HOST_OUT}/nativetest64/{agent}={TEST_NAME},jvm"
+ else:
+ FLAGS+=f" -agentpath:{agent}={TEST_NAME},art"
-if [[ "$JVMTI_STRESS" = "y" ]]; then
- agent=libtistressd.so
- if [[ "$TEST_IS_NDEBUG" = "y" ]]; then
- agent=libtistress.so
- fi
+if JVMTI_STRESS:
+ agent="libtistressd.so"
+ if TEST_IS_NDEBUG:
+ agent="libtistress.so"
# Just give it a default start so we can always add ',' to it.
agent_args="jvmti-stress"
- if [[ "$JVMTI_REDEFINE_STRESS" = "y" ]]; then
+ if JVMTI_REDEFINE_STRESS:
# We really cannot do this on RI so don't both passing it in that case.
- if [[ "$USE_JVM" = "n" ]]; then
- agent_args="${agent_args},redefine"
- fi
- fi
- if [[ "$JVMTI_FIELD_STRESS" = "y" ]]; then
- agent_args="${agent_args},field"
- fi
- if [[ "$JVMTI_STEP_STRESS" = "y" ]]; then
- agent_args="${agent_args},step"
- fi
- if [[ "$JVMTI_TRACE_STRESS" = "y" ]]; then
- agent_args="${agent_args},trace"
- fi
+ if not USE_JVM:
+ agent_args=f"{agent_args},redefine"
+ if JVMTI_FIELD_STRESS:
+ agent_args=f"{agent_args},field"
+ if JVMTI_STEP_STRESS:
+ agent_args=f"{agent_args},step"
+ if JVMTI_TRACE_STRESS:
+ agent_args=f"{agent_args},trace"
# In the future add onto this;
- if [[ "$USE_JVM" = "y" ]]; then
- FLAGS="${FLAGS} -agentpath:${ANDROID_HOST_OUT}/nativetest64/${agent}=${agent_args}"
- else
- FLAGS="${FLAGS} -agentpath:${agent}=${agent_args}"
- fi
-fi
+ if USE_JVM:
+ FLAGS+=f" -agentpath:{ANDROID_HOST_OUT}/nativetest64/{agent}={agent_args}"
+ else:
+ FLAGS+=f" -agentpath:{agent}={agent_args}"
-if [ "$USE_JVM" = "y" ]; then
- export LD_LIBRARY_PATH=${ANDROID_HOST_OUT}/lib64
+if USE_JVM:
+ export(f"LD_LIBRARY_PATH", f"{ANDROID_HOST_OUT}/lib64")
# Some jvmti tests are flaky without -Xint on the RI.
- if [ "$IS_JVMTI_TEST" = "y" ]; then
- FLAGS="${FLAGS} -Xint"
- fi
+ if IS_JVMTI_TEST:
+ FLAGS+=" -Xint"
# Xmx is necessary since we don't pass down the ART flags to JVM.
# We pass the classes2 path whether it's used (src-multidex) or not.
- cmdline="${JAVA} ${DEBUGGER_OPTS} ${JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 ${FLAGS} $MAIN $@ ${ARGS}"
- if [ "$DEV_MODE" = "y" ]; then
- echo $cmdline
- fi
- if [ "$CREATE_RUNNER" = "y" ]; then
- echo "#!/bin/bash" > runit.sh
- echo "export LD_LIBRARY_PATH=\"$LD_LIBRARY_PATH\""
- echo $cmdline >> runit.sh
- chmod u+x runit.sh
- echo "Runnable test script written to $PWD/runit.sh"
- else
- $cmdline
- fi
- exit
-fi
+ cmdline=f"{JAVA} {DEBUGGER_OPTS} {JVM_VERIFY_ARG} -Xmx256m -classpath classes:classes2 {FLAGS} {MAIN} {test_args} {ARGS}"
+ if DEV_MODE:
+ print(cmdline)
+ if CREATE_RUNNER:
+ with open("runit.sh", "w") as f:
+ f.write("#!/bin/bash")
+ print(f"export LD_LIBRARY_PATH=\"{LD_LIBRARY_PATH}\"")
+ f.write(cmdline)
+ os.chmod("runit.sh", 0o777)
+ pwd = os.getcwd()
+ print(f"Runnable test script written to {pwd}/runit.sh")
+ sys.exit(0)
+ else:
+ exit_value=run(cmdline, check=False, capture_output=False).returncode
+ sys.exit(exit_value)
-readonly b_path=$(get_apex_bootclasspath ${HOST})
-readonly b_path_locations=$(get_apex_bootclasspath_locations ${HOST})
+b_path=get_apex_bootclasspath(HOST)
+b_path_locations=get_apex_bootclasspath_locations(HOST)
-BCPEX=
-if [ -f "$TEST_NAME-bcpex.jar" ] ; then
- BCPEX=":$DEX_LOCATION/$TEST_NAME-bcpex.jar"
-fi
+BCPEX=""
+if isfile(f"{TEST_NAME}-bcpex.jar"):
+ BCPEX=f":{DEX_LOCATION}/{TEST_NAME}-bcpex.jar"
# Pass down the bootclasspath
-FLAGS="${FLAGS} -Xbootclasspath:${b_path}${BCPEX}"
-FLAGS="${FLAGS} -Xbootclasspath-locations:${b_path_locations}${BCPEX}"
-COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xbootclasspath:${b_path}"
-COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xbootclasspath-locations:${b_path_locations}"
+FLAGS+=f" -Xbootclasspath:{b_path}{BCPEX}"
+FLAGS+=f" -Xbootclasspath-locations:{b_path_locations}{BCPEX}"
+COMPILE_FLAGS+=f" --runtime-arg -Xbootclasspath:{b_path}"
+COMPILE_FLAGS+=f" --runtime-arg -Xbootclasspath-locations:{b_path_locations}"
-if [ "$HAVE_IMAGE" = "n" ]; then
+if not HAVE_IMAGE:
# Disable image dex2oat - this will forbid the runtime to patch or compile an image.
- FLAGS="${FLAGS} -Xnoimage-dex2oat"
+ FLAGS+=" -Xnoimage-dex2oat"
# We'll abuse a second flag here to test different behavior. If --relocate, use the
# existing image - relocation will fail as patching is disallowed. If --no-relocate,
# pass a non-existent image - compilation will fail as dex2oat is disallowed.
- if [ "${RELOCATE}" = "n" ] ; then
+ if not RELOCATE:
BOOT_IMAGE="/system/non-existent/boot.art"
- fi
# App images cannot be generated without a boot image.
- APP_IMAGE="n"
-fi
-DALVIKVM_BOOT_OPT="-Ximage:${BOOT_IMAGE}"
+ APP_IMAGE=False
+DALVIKVM_BOOT_OPT=f"-Ximage:{BOOT_IMAGE}"
-if [ "$USE_GDB_DEX2OAT" = "y" ]; then
- if [ "$HOST" = "n" ]; then
- echo "The --gdb-dex2oat option is not yet implemented for target." >&2
- exit 1
- fi
-fi
+if USE_GDB_DEX2OAT:
+ if not HOST:
+ print("The --gdb-dex2oat option is not yet implemented for target.", file=sys.stderr)
+ sys.exit(1)
-if [ "$USE_GDB" = "y" ]; then
- if [ "$USE_GDBSERVER" = "y" ]; then
- error_msg "Cannot pass both --gdb and --gdbserver at the same time!"
- exit 1
- elif [ "$HOST" = "n" ]; then
+if USE_GDB:
+ if USE_GDBSERVER:
+ error_msg("Cannot pass both --gdb and --gdbserver at the same time!")
+ sys.exit(1)
+ elif not HOST:
# We might not have any hostname resolution if we are using a chroot.
- GDB="$GDBSERVER_DEVICE --no-startup-with-shell 127.0.0.1$GDBSERVER_PORT"
- else
- if [ `uname` = "Darwin" ]; then
- GDB=lldb
- GDB_ARGS="$GDB_ARGS -- $DALVIKVM"
- DALVIKVM=
- else
- GDB=gdb
- GDB_ARGS="$GDB_ARGS --args $DALVIKVM"
+ GDB=f"{GDBSERVER_DEVICE} --no-startup-with-shell 127.0.0.1{GDBSERVER_PORT}"
+ else:
+ if run("uname").stdout.strip() == "Darwin":
+ GDB="lldb"
+ GDB_ARGS+=f" -- {DALVIKVM}"
+ DALVIKVM=""
+ else:
+ GDB="gdb"
+ GDB_ARGS+=f" --args {DALVIKVM}"
# Enable for Emacs "M-x gdb" support. TODO: allow extra gdb arguments on command line.
- # gdbargs="--annotate=3 $gdbargs"
- fi
- fi
-elif [ "$USE_GDBSERVER" = "y" ]; then
- if [ "$HOST" = "n" ]; then
+ # gdbargs=f"--annotate=3 {gdbargs}"
+elif USE_GDBSERVER:
+ if not HOST:
# We might not have any hostname resolution if we are using a chroot.
- GDB="$GDBSERVER_DEVICE --no-startup-with-shell 127.0.0.1$GDBSERVER_PORT"
- else
- GDB="$GDBSERVER_HOST $GDBSERVER_PORT"
- fi
-fi
+ GDB=f"{GDBSERVER_DEVICE} --no-startup-with-shell 127.0.0.1{GDBSERVER_PORT}"
+ else:
+ GDB=f"{GDBSERVER_HOST} {GDBSERVER_PORT}"
-if [ "$INTERPRETER" = "y" ]; then
- INT_OPTS="${INT_OPTS} -Xint"
-fi
+if INTERPRETER:
+ INT_OPTS+=" -Xint"
-if [ "$JIT" = "y" ]; then
- INT_OPTS="${INT_OPTS} -Xusejit:true"
-else
- INT_OPTS="${INT_OPTS} -Xusejit:false"
-fi
+if JIT:
+ INT_OPTS+=" -Xusejit:true"
+else:
+ INT_OPTS+=" -Xusejit:false"
-if [ "$INTERPRETER" = "y" ] || [ "$JIT" = "y" ]; then
- if [ "$VERIFY" = "y" ] ; then
- INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=verify"
- COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=verify"
- elif [ "$VERIFY" = "s" ]; then
- INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=extract"
- COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=extract"
- DEX_VERIFY="${DEX_VERIFY} -Xverify:softfail"
- else # VERIFY = "n"
- INT_OPTS="${INT_OPTS} -Xcompiler-option --compiler-filter=assume-verified"
- COMPILE_FLAGS="${COMPILE_FLAGS} --compiler-filter=assume-verified"
- DEX_VERIFY="${DEX_VERIFY} -Xverify:none"
- fi
-fi
+if INTERPRETER or JIT:
+ if VERIFY == "y":
+ INT_OPTS+=" -Xcompiler-option --compiler-filter=verify"
+ COMPILE_FLAGS+=" --compiler-filter=verify"
+ elif VERIFY == "s":
+ INT_OPTS+=" -Xcompiler-option --compiler-filter=extract"
+ COMPILE_FLAGS+=" --compiler-filter=extract"
+ DEX_VERIFY=f"{DEX_VERIFY} -Xverify:softfail"
+ else: # VERIFY == "n"
+ INT_OPTS+=" -Xcompiler-option --compiler-filter=assume-verified"
+ COMPILE_FLAGS+=" --compiler-filter=assume-verified"
+ DEX_VERIFY=f"{DEX_VERIFY} -Xverify:none"
JNI_OPTS="-Xjnigreflimit:512 -Xcheck:jni"
-COMPILE_FLAGS="${COMPILE_FLAGS} --runtime-arg -Xnorelocate"
-if [ "$RELOCATE" = "y" ]; then
- FLAGS="${FLAGS} -Xrelocate"
-else
- FLAGS="$FLAGS -Xnorelocate"
-fi
+COMPILE_FLAGS+=" --runtime-arg -Xnorelocate"
+if RELOCATE:
+ FLAGS+=" -Xrelocate"
+else:
+ FLAGS+=" -Xnorelocate"
-if [ "$BIONIC" = "y" ]; then
+if BIONIC:
# This is the location that soong drops linux_bionic builds. Despite being
# called linux_bionic-x86 the build is actually amd64 (x86_64) only.
- if [ ! -e "$OUT_DIR/soong/host/linux_bionic-x86" ]; then
- error_msg "linux_bionic-x86 target doesn't seem to have been built!"
- exit 1
- fi
+ if not path.exists(f"{OUT_DIR}/soong/host/linux_bionic-x86"):
+ error_msg("linux_bionic-x86 target doesn't seem to have been built!")
+ sys.exit(1)
# Set TIMEOUT_DUMPER manually so it works even with apex's
- TIMEOUT_DUMPER=$OUT_DIR/soong/host/linux_bionic-x86/bin/signal_dumper
-fi
+ TIMEOUT_DUMPER=f"{OUT_DIR}/soong/host/linux_bionic-x86/bin/signal_dumper"
# Prevent test from silently falling back to interpreter in no-prebuild mode. This happens
# when DEX_LOCATION path is too long, because vdex/odex filename is constructed by taking
# full path to dex, stripping leading '/', appending '@classes.vdex' and changing every
# remaining '/' into '@'.
-if [ "$HOST" = "y" ]; then
- max_filename_size=$(getconf NAME_MAX $DEX_LOCATION)
-else
+if HOST:
+ max_filename_size=int(run(f"getconf NAME_MAX {DEX_LOCATION}", save_cmd=False).stdout)
+else:
# There is no getconf on device, fallback to standard value.
# See NAME_MAX in kernel <linux/limits.h>
max_filename_size=255
-fi
# Compute VDEX_NAME.
-DEX_LOCATION_STRIPPED="${DEX_LOCATION#/}"
-VDEX_NAME="${DEX_LOCATION_STRIPPED//\//@}@$TEST_NAME.jar@classes.vdex"
-if [ ${#VDEX_NAME} -gt $max_filename_size ]; then
- echo "Dex location path too long:"
- error_msg "$VDEX_NAME is ${#VDEX_NAME} character long, and the limit is $max_filename_size."
- exit 1
-fi
+DEX_LOCATION_STRIPPED=DEX_LOCATION.lstrip("/")
+VDEX_NAME=f"{DEX_LOCATION_STRIPPED}@{TEST_NAME}.jar@classes.vdex".replace("/", "@")
+if len(VDEX_NAME) > max_filename_size:
+ print("Dex location path too long:")
+ error_msg(f"{VDEX_NAME} is {len(VDEX_NAME)} character long, and the limit is {max_filename_size}.")
+ sys.exit(1)
-if [ "$HOST" = "y" ]; then
+if HOST:
# On host, run binaries (`dex2oat(d)`, `dalvikvm`, `profman`) from the `bin`
# directory under the "Android Root" (usually `out/host/linux-x86`).
#
# TODO(b/130295968): Adjust this if/when ART host artifacts are installed
# under the ART root (usually `out/host/linux-x86/com.android.art`).
- ANDROID_ART_BIN_DIR=$ANDROID_ROOT/bin
-else
+ ANDROID_ART_BIN_DIR=f"{ANDROID_ROOT}/bin"
+else:
# On target, run binaries (`dex2oat(d)`, `dalvikvm`, `profman`) from the ART
# APEX's `bin` directory. This means the linker will observe the ART APEX
# linker configuration file (`/apex/com.android.art/etc/ld.config.txt`) for
# these binaries.
- ANDROID_ART_BIN_DIR=$ANDROID_ART_ROOT/bin
-fi
+ ANDROID_ART_BIN_DIR=f"{ANDROID_ART_ROOT}/bin"
profman_cmdline="true"
dex2oat_cmdline="true"
vdex_cmdline="true"
dm_cmdline="true"
-mkdir_locations="${DEX_LOCATION}/dalvik-cache/$ISA"
+mkdir_locations=f"{DEX_LOCATION}/dalvik-cache/{ISA}"
strip_cmdline="true"
sync_cmdline="true"
linkroot_cmdline="true"
@@ -894,165 +916,145 @@
installapex_cmdline="true"
installapex_test_cmdline="true"
-linkdirs() {
- find "$1" -maxdepth 1 -mindepth 1 -type d | xargs -i ln -sf '{}' "$2"
+def linkdirs(host_out: str, root: str):
+ dirs = list(filter(os.path.isdir, glob.glob(os.path.join(host_out, "*"))))
# Also create a link for the boot image.
- ln -sf $ANDROID_HOST_OUT/apex/art_boot_images "$2"
-}
+ dirs.append(f"{ANDROID_HOST_OUT}/apex/art_boot_images")
+ return " && ".join(f"ln -sf {dir} {root}" for dir in dirs)
-if [ "$CREATE_ANDROID_ROOT" = "y" ]; then
- mkdir_locations="${mkdir_locations} ${ANDROID_ROOT}"
- linkroot_cmdline="linkdirs ${ANDROID_HOST_OUT} ${ANDROID_ROOT}"
- if [ "${BIONIC}" = "y" ]; then
+if CREATE_ANDROID_ROOT:
+ mkdir_locations+=f" {ANDROID_ROOT}"
+ linkroot_cmdline=linkdirs(ANDROID_HOST_OUT, ANDROID_ROOT)
+ if BIONIC:
# TODO Make this overlay more generic.
- linkroot_overlay_cmdline="linkdirs $OUT_DIR/soong/host/linux_bionic-x86 ${ANDROID_ROOT}"
- fi
+ linkroot_overlay_cmdline=linkdirs(f"{OUT_DIR}/soong/host/linux_bionic-x86", ANDROID_ROOT)
# Replace the boot image to a location expected by the runtime.
- DALVIKVM_BOOT_OPT="-Ximage:${ANDROID_ROOT}/art_boot_images/javalib/boot.art"
-fi
+ DALVIKVM_BOOT_OPT=f"-Ximage:{ANDROID_ROOT}/art_boot_images/javalib/boot.art"
-if [ "$USE_ZIPAPEX" = "y" ]; then
+if USE_ZIPAPEX:
# TODO Currently this only works for linux_bionic zipapexes because those are
# stripped and so small enough that the ulimit doesn't kill us.
- mkdir_locations="${mkdir_locations} $DEX_LOCATION/zipapex"
+ mkdir_locations+=f" {DEX_LOCATION}/zipapex"
zip_options="-qq"
- if [ "$DEV_MODE" = "y" ]; then
+ if DEV_MODE:
zip_options=""
- fi
- setupapex_cmdline="unzip -o -u ${zip_options} ${ZIPAPEX_LOC} apex_payload.zip -d ${DEX_LOCATION}"
- installapex_cmdline="unzip -o -u ${zip_options} ${DEX_LOCATION}/apex_payload.zip -d ${DEX_LOCATION}/zipapex"
- ANDROID_ART_BIN_DIR=$DEX_LOCATION/zipapex/bin
-elif [ "$USE_EXTRACTED_ZIPAPEX" = "y" ]; then
+ setupapex_cmdline=f"unzip -o -u {zip_options} {ZIPAPEX_LOC} apex_payload.zip -d {DEX_LOCATION}"
+ installapex_cmdline=f"unzip -o -u {zip_options} {DEX_LOCATION}/apex_payload.zip -d {DEX_LOCATION}/zipapex"
+ ANDROID_ART_BIN_DIR=f"{DEX_LOCATION}/zipapex/bin"
+elif USE_EXTRACTED_ZIPAPEX:
# Just symlink the zipapex binaries
- ANDROID_ART_BIN_DIR=$DEX_LOCATION/zipapex/bin
+ ANDROID_ART_BIN_DIR=f"{DEX_LOCATION}/zipapex/bin"
# Force since some tests manually run this file twice.
ln_options=""
- if [ "$DEV_MODE" = "y" ]; then
+ if DEV_MODE:
ln_options="--verbose"
- fi
- # If the ${RUN} is executed multiple times we don't need to recreate the link
- installapex_test_cmdline="test -L ${DEX_LOCATION}/zipapex"
- installapex_cmdline="ln -s -f ${ln_options} ${EXTRACTED_ZIPAPEX_LOC} ${DEX_LOCATION}/zipapex"
-fi
+ # If the {RUN} is executed multiple times we don't need to recreate the link
+ installapex_test_cmdline=f"test -L {DEX_LOCATION}/zipapex"
+ installapex_cmdline=f"ln -s -f {ln_options} {EXTRACTED_ZIPAPEX_LOC} {DEX_LOCATION}/zipapex"
# PROFILE takes precedence over RANDOM_PROFILE, since PROFILE tests require a
# specific profile to run properly.
-if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
- profman_cmdline="$ANDROID_ART_BIN_DIR/profman \
- --apk=$DEX_LOCATION/$TEST_NAME.jar \
- --dex-location=$DEX_LOCATION/$TEST_NAME.jar"
- if [ -f "$TEST_NAME-ex.jar" ] && [ "$SECONDARY_COMPILATION" = "y" ] ; then
- profman_cmdline="${profman_cmdline} \
- --apk=$DEX_LOCATION/$TEST_NAME-ex.jar \
- --dex-location=$DEX_LOCATION/$TEST_NAME-ex.jar"
- fi
- COMPILE_FLAGS="${COMPILE_FLAGS} --profile-file=$DEX_LOCATION/$TEST_NAME.prof"
- FLAGS="${FLAGS} -Xcompiler-option --profile-file=$DEX_LOCATION/$TEST_NAME.prof"
- if [ "$PROFILE" = "y" ]; then
- profman_cmdline="${profman_cmdline} --create-profile-from=$DEX_LOCATION/profile \
- --reference-profile-file=$DEX_LOCATION/$TEST_NAME.prof"
- else
- profman_cmdline="${profman_cmdline} --generate-test-profile=$DEX_LOCATION/$TEST_NAME.prof \
+if PROFILE or RANDOM_PROFILE:
+ profman_cmdline=f"{ANDROID_ART_BIN_DIR}/profman \
+ --apk={DEX_LOCATION}/{TEST_NAME}.jar \
+ --dex-location={DEX_LOCATION}/{TEST_NAME}.jar"
+ if isfile(f"{TEST_NAME}-ex.jar") and SECONDARY_COMPILATION:
+ profman_cmdline=f"{profman_cmdline} \
+ --apk={DEX_LOCATION}/{TEST_NAME}-ex.jar \
+ --dex-location={DEX_LOCATION}/{TEST_NAME}-ex.jar"
+ COMPILE_FLAGS=f"{COMPILE_FLAGS} --profile-file={DEX_LOCATION}/{TEST_NAME}.prof"
+ FLAGS=f"{FLAGS} -Xcompiler-option --profile-file={DEX_LOCATION}/{TEST_NAME}.prof"
+ if PROFILE:
+ profman_cmdline=f"{profman_cmdline} --create-profile-from={DEX_LOCATION}/profile \
+ --reference-profile-file={DEX_LOCATION}/{TEST_NAME}.prof"
+ else:
+ profman_cmdline=f"{profman_cmdline} --generate-test-profile={DEX_LOCATION}/{TEST_NAME}.prof \
--generate-test-profile-seed=0"
- fi
-fi
-function get_prebuilt_lldb_path {
- local CLANG_BASE="prebuilts/clang/host"
- local CLANG_VERSION="$("$ANDROID_BUILD_TOP/build/soong/scripts/get_clang_version.py")"
- case "$(uname -s)" in
- Darwin)
- local PREBUILT_NAME="darwin-x86"
- ;;
- Linux)
- local PREBUILT_NAME="linux-x86"
- ;;
- *)
- >&2 echo "Unknown host $(uname -s). Unsupported for debugging dex2oat with LLDB."
+def get_prebuilt_lldb_path():
+ CLANG_BASE="prebuilts/clang/host"
+ CLANG_VERSION=run(f"{ANDROID_BUILD_TOP}/build/soong/scripts/get_clang_version.py").stdout.strip()
+ uname = run("uname -s").stdout.strip()
+ if uname == "Darwin":
+ PREBUILT_NAME="darwin-x86"
+ elif uname == "Linux":
+ PREBUILT_NAME="linux-x86"
+ else:
+ print("Unknown host $(uname -s). Unsupported for debugging dex2oat with LLDB.", file=sys.stderr)
return
- ;;
- esac
- local CLANG_PREBUILT_HOST_PATH="$ANDROID_BUILD_TOP/$CLANG_BASE/$PREBUILT_NAME/$CLANG_VERSION"
+ CLANG_PREBUILT_HOST_PATH=f"{ANDROID_BUILD_TOP}/{CLANG_BASE}/{PREBUILT_NAME}/{CLANG_VERSION}"
# If the clang prebuilt directory exists and the reported clang version
# string does not, then it is likely that the clang version reported by the
# get_clang_version.py script does not match the expected directory name.
- if [ -d "${ANDROID_BUILD_TOP}/${CLANG_BASE}/${PREBUILT_NAME}" ] && \
- [ ! -d "${CLANG_PREBUILT_HOST_PATH}" ]; then
- error_msg "The prebuilt clang directory exists, but the specific clang"\
+ if (isdir(f"{ANDROID_BUILD_TOP}/{CLANG_BASE}/{PREBUILT_NAME}") and
+ not isdir(CLANG_PREBUILT_HOST_PATH)):
+ error_msg("The prebuilt clang directory exists, but the specific clang"\
"\nversion reported by get_clang_version.py does not exist in that path."\
"\nPlease make sure that the reported clang version resides in the"\
- "\nprebuilt clang directory!"
- exit 1
- fi
+ "\nprebuilt clang directory!")
+ sys.exit(1)
# The lldb-server binary is a dependency of lldb.
- export LLDB_DEBUGSERVER_PATH="${CLANG_PREBUILT_HOST_PATH}/runtimes_ndk_cxx/x86_64/lldb-server"
+ export("LLDB_DEBUGSERVER_PATH", f"{CLANG_PREBUILT_HOST_PATH}/runtimes_ndk_cxx/x86_64/lldb-server")
# Set the current terminfo directory to TERMINFO so that LLDB can read the
# termcap database.
- local terminfo_regexp_path='\/.*\/*terminfo\/'
- if [[ $(infocmp) =~ $terminfo_regexp_path ]] ; then
- export TERMINFO="${BASH_REMATCH[0]}"
- fi
+ terminfo = re.search('/.*/terminfo/', run("infocmp", save_cmd=False).stdout)
+ if terminfo:
+ export("TERMINFO", terminfo[0])
- prebuilt_lldb_path="$CLANG_PREBUILT_HOST_PATH/bin/lldb.sh"
-}
+ return f"{CLANG_PREBUILT_HOST_PATH}/bin/lldb.sh"
-function write_dex2oat_cmdlines {
- local name="$1"
+def write_dex2oat_cmdlines(name: str):
+ global dex2oat_cmdline, dm_cmdline, vdex_cmdline
- local class_loader_context=""
- local enable_app_image=false
- if [ "$APP_IMAGE" = "y" ]; then
- enable_app_image=true
- fi
+ class_loader_context=""
+ enable_app_image=False
+ if APP_IMAGE:
+ enable_app_image=True
# If the name ends in -ex then this is a secondary dex file
- if [ "${name:${#name}-3}" = "-ex" ]; then
+ if name.endswith("-ex"):
# Lazily realize the default value in case DEX_LOCATION/TEST_NAME change
- if [ "x$SECONDARY_CLASS_LOADER_CONTEXT" = "x" ]; then
- if [ "x$SECONDARY_DEX" = "x" ]; then
+ global SECONDARY_CLASS_LOADER_CONTEXT
+ if SECONDARY_CLASS_LOADER_CONTEXT == "":
+ if SECONDARY_DEX == "":
# Tests without `--secondary` load the "-ex" jar in a separate PathClassLoader
# that is a child of the main PathClassLoader. If the class loader is constructed
# in any other way, the test needs to specify the secondary CLC explicitly.
- SECONDARY_CLASS_LOADER_CONTEXT="PCL[];PCL[$DEX_LOCATION/$TEST_NAME.jar]"
- else
+ SECONDARY_CLASS_LOADER_CONTEXT=f"PCL[];PCL[{DEX_LOCATION}/{TEST_NAME}.jar]"
+ else:
# Tests with `--secondary` load the `-ex` jar a part of the main PathClassLoader.
- SECONDARY_CLASS_LOADER_CONTEXT="PCL[$DEX_LOCATION/$TEST_NAME.jar]"
- fi
- fi
- class_loader_context="'--class-loader-context=$SECONDARY_CLASS_LOADER_CONTEXT'"
- $enable_app_image && [ "$SECONDARY_APP_IMAGE" = "y" ] || enable_app_image=false
- fi
+ SECONDARY_CLASS_LOADER_CONTEXT=f"PCL[{DEX_LOCATION}/{TEST_NAME}.jar]"
+ class_loader_context=f"'--class-loader-context={SECONDARY_CLASS_LOADER_CONTEXT}'"
+ enable_app_image = enable_app_image and SECONDARY_APP_IMAGE
- local app_image=""
- $enable_app_image && app_image="--app-image-file=$DEX_LOCATION/oat/$ISA/$name.art --resolve-startup-const-strings=true"
+ app_image=""
+ if enable_app_image:
+ app_image=f"--app-image-file={DEX_LOCATION}/oat/{ISA}/{name}.art --resolve-startup-const-strings=true"
- if [ "$USE_GDB_DEX2OAT" = "y" ]; then
- get_prebuilt_lldb_path
- GDB_DEX2OAT="$prebuilt_lldb_path -f"
+ global GDB_DEX2OAT, GDB_DEX2OAT_ARGS
+ if USE_GDB_DEX2OAT:
+ prebuilt_lldb_path=get_prebuilt_lldb_path()
+ GDB_DEX2OAT=f"{prebuilt_lldb_path} -f"
GDB_DEX2OAT_ARGS+=" -- "
- fi
- local dex2oat_binary
- dex2oat_binary=${DEX2OAT_DEBUG_BINARY}
- if [[ "$TEST_IS_NDEBUG" = "y" ]]; then
- dex2oat_binary=${DEX2OAT_NDEBUG_BINARY}
- fi
- dex2oat_cmdline="$INVOKE_WITH $GDB_DEX2OAT \
- $ANDROID_ART_BIN_DIR/$dex2oat_binary \
- $GDB_DEX2OAT_ARGS \
- $COMPILE_FLAGS \
- --boot-image=${BOOT_IMAGE} \
- --dex-file=$DEX_LOCATION/$name.jar \
- --oat-file=$DEX_LOCATION/oat/$ISA/$name.odex \
- "$app_image" \
+ dex2oat_binary=DEX2OAT_DEBUG_BINARY
+ if TEST_IS_NDEBUG:
+ dex2oat_binary=DEX2OAT_NDEBUG_BINARY
+ dex2oat_cmdline=f"{INVOKE_WITH} {GDB_DEX2OAT} \
+ {ANDROID_ART_BIN_DIR}/{dex2oat_binary} \
+ {GDB_DEX2OAT_ARGS} \
+ {COMPILE_FLAGS} \
+ --boot-image={BOOT_IMAGE} \
+ --dex-file={DEX_LOCATION}/{name}.jar \
+ --oat-file={DEX_LOCATION}/oat/{ISA}/{name}.odex \
+ {app_image} \
--generate-mini-debug-info \
- --instruction-set=$ISA \
- $class_loader_context"
- if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then
- dex2oat_cmdline="${dex2oat_cmdline} --instruction-set-features=${INSTRUCTION_SET_FEATURES}"
- fi
+ --instruction-set={ISA} \
+ {class_loader_context}"
+ if INSTRUCTION_SET_FEATURES != "":
+ dex2oat_cmdline+=f" --instruction-set-features={INSTRUCTION_SET_FEATURES}"
# Add in a timeout. This is important for testing the compilation/verification time of
# pathological cases. We do not append a timeout when debugging dex2oat because we
@@ -1061,262 +1063,231 @@
# now. We should try to improve this.
# The current value is rather arbitrary. run-tests should compile quickly.
# Watchdog timeout is in milliseconds so add 3 '0's to the dex2oat timeout.
- if [ "$HOST" != "n" ] && [ "$USE_GDB_DEX2OAT" != "y" ]; then
+ if HOST and not USE_GDB_DEX2OAT:
# Use SIGRTMIN+2 to try to dump threads.
# Use -k 1m to SIGKILL it a minute later if it hasn't ended.
- dex2oat_cmdline="timeout -k ${DEX2OAT_TIMEOUT}s -s SIGRTMIN+2 ${DEX2OAT_RT_TIMEOUT}s ${dex2oat_cmdline} --watchdog-timeout=${DEX2OAT_TIMEOUT}000"
- fi
- if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
- vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --input-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex --output-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex"
- elif [ "$TEST_VDEX" = "y" ]; then
- if [ "$VDEX_ARGS" = "" ]; then
+ dex2oat_cmdline=f"timeout -k {DEX2OAT_TIMEOUT}s -s SIGRTMIN+2 {DEX2OAT_RT_TIMEOUT}s {dex2oat_cmdline} --watchdog-timeout={DEX2OAT_TIMEOUT}000"
+ if PROFILE or RANDOM_PROFILE:
+ vdex_cmdline=f"{dex2oat_cmdline} {VDEX_ARGS} --input-vdex={DEX_LOCATION}/oat/{ISA}/{name}.vdex --output-vdex={DEX_LOCATION}/oat/{ISA}/{name}.vdex"
+ elif TEST_VDEX:
+ if VDEX_ARGS == "":
# If no arguments need to be passed, just delete the odex file so that the runtime only picks up the vdex file.
- vdex_cmdline="rm $DEX_LOCATION/oat/$ISA/$name.odex"
- else
- vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --input-vdex=$DEX_LOCATION/oat/$ISA/$name.vdex"
- fi
- elif [ "$TEST_DEX2OAT_DM" = "y" ]; then
- vdex_cmdline="${dex2oat_cmdline} ${VDEX_ARGS} --dump-timings --dm-file=$DEX_LOCATION/oat/$ISA/$name.dm"
- dex2oat_cmdline="${dex2oat_cmdline} --copy-dex-files=false --output-vdex=$DEX_LOCATION/oat/$ISA/primary.vdex"
- dm_cmdline="zip -qj $DEX_LOCATION/oat/$ISA/$name.dm $DEX_LOCATION/oat/$ISA/primary.vdex"
- elif [ "$TEST_RUNTIME_DM" = "y" ]; then
- dex2oat_cmdline="${dex2oat_cmdline} --copy-dex-files=false --output-vdex=$DEX_LOCATION/oat/$ISA/primary.vdex"
- dm_cmdline="zip -qj $DEX_LOCATION/$name.dm $DEX_LOCATION/oat/$ISA/primary.vdex"
- fi
-}
+ vdex_cmdline=f"rm {DEX_LOCATION}/oat/{ISA}/{name}.odex"
+ else:
+ vdex_cmdline=f"{dex2oat_cmdline} {VDEX_ARGS} --input-vdex={DEX_LOCATION}/oat/{ISA}/{name}.vdex"
+ elif TEST_DEX2OAT_DM:
+ vdex_cmdline=f"{dex2oat_cmdline} {VDEX_ARGS} --dump-timings --dm-file={DEX_LOCATION}/oat/{ISA}/{name}.dm"
+ dex2oat_cmdline=f"{dex2oat_cmdline} --copy-dex-files=false --output-vdex={DEX_LOCATION}/oat/{ISA}/primary.vdex"
+ dm_cmdline=f"zip -qj {DEX_LOCATION}/oat/{ISA}/{name}.dm {DEX_LOCATION}/oat/{ISA}/primary.vdex"
+ elif TEST_RUNTIME_DM:
+ dex2oat_cmdline=f"{dex2oat_cmdline} --copy-dex-files=false --output-vdex={DEX_LOCATION}/oat/{ISA}/primary.vdex"
+ dm_cmdline=f"zip -qj {DEX_LOCATION}/{name}.dm {DEX_LOCATION}/oat/{ISA}/primary.vdex"
# Enable mini-debug-info for JIT (if JIT is used).
-FLAGS="$FLAGS -Xcompiler-option --generate-mini-debug-info"
+FLAGS+=" -Xcompiler-option --generate-mini-debug-info"
-if [ "$PREBUILD" = "y" ]; then
- mkdir_locations="${mkdir_locations} ${DEX_LOCATION}/oat/$ISA"
+if PREBUILD:
+ mkdir_locations+=f" {DEX_LOCATION}/oat/{ISA}"
# "Primary".
- write_dex2oat_cmdlines "$TEST_NAME"
- dex2oat_cmdline=$(echo $dex2oat_cmdline)
- dm_cmdline=$(echo $dm_cmdline)
- vdex_cmdline=$(echo $vdex_cmdline)
+ write_dex2oat_cmdlines(TEST_NAME)
+ dex2oat_cmdline=re.sub(" +", " ", dex2oat_cmdline)
+ dm_cmdline=re.sub(" +", " ", dm_cmdline)
+ vdex_cmdline=re.sub(" +", " ", vdex_cmdline)
# Enable mini-debug-info for JIT (if JIT is used).
- FLAGS="$FLAGS -Xcompiler-option --generate-mini-debug-info"
+ FLAGS+=" -Xcompiler-option --generate-mini-debug-info"
- if [ -f "$TEST_NAME-ex.jar" ] && [ "$SECONDARY_COMPILATION" = "y" ] ; then
+ if isfile(f"{TEST_NAME}-ex.jar") and SECONDARY_COMPILATION:
# "Secondary" for test coverage.
# Store primary values.
- base_dex2oat_cmdline="$dex2oat_cmdline"
- base_dm_cmdline="$dm_cmdline"
- base_vdex_cmdline="$vdex_cmdline"
+ base_dex2oat_cmdline=dex2oat_cmdline
+ base_dm_cmdline=dm_cmdline
+ base_vdex_cmdline=vdex_cmdline
- write_dex2oat_cmdlines "$TEST_NAME-ex"
- dex2oat_cmdline=$(echo $dex2oat_cmdline)
- dm_cmdline=$(echo $dm_cmdline)
- vdex_cmdline=$(echo $vdex_cmdline)
+ write_dex2oat_cmdlines(f"{TEST_NAME}-ex")
+ dex2oat_cmdline=re.sub(" +", " ", dex2oat_cmdline)
+ dm_cmdline=re.sub(" +", " ", dm_cmdline)
+ vdex_cmdline=re.sub(" +", " ", vdex_cmdline)
# Concatenate.
- dex2oat_cmdline="$base_dex2oat_cmdline && $dex2oat_cmdline"
- dm_cmdline="$base_dm_cmdline" # Only use primary dm.
- vdex_cmdline="$base_vdex_cmdline && $vdex_cmdline"
- fi
-fi
+ dex2oat_cmdline=f"{base_dex2oat_cmdline} && {dex2oat_cmdline}"
+ dm_cmdline=base_dm_cmdline # Only use primary dm.
+ vdex_cmdline=f"{base_vdex_cmdline} && {vdex_cmdline}"
-if [ "$SYNC_BEFORE_RUN" = "y" ]; then
+if SYNC_BEFORE_RUN:
sync_cmdline="sync"
-fi
DALVIKVM_ISA_FEATURES_ARGS=""
-if [ "x$INSTRUCTION_SET_FEATURES" != "x" ] ; then
- DALVIKVM_ISA_FEATURES_ARGS="-Xcompiler-option --instruction-set-features=${INSTRUCTION_SET_FEATURES}"
-fi
+if INSTRUCTION_SET_FEATURES != "":
+ DALVIKVM_ISA_FEATURES_ARGS=f"-Xcompiler-option --instruction-set-features={INSTRUCTION_SET_FEATURES}"
# java.io.tmpdir can only be set at launch time.
TMP_DIR_OPTION=""
-if [ "$HOST" = "n" ]; then
+if not HOST:
TMP_DIR_OPTION="-Djava.io.tmpdir=/data/local/tmp"
-fi
# The build servers have an ancient version of bash so we cannot use @Q.
-if [ "$USE_GDBSERVER" == "y" ]; then
- printf -v QUOTED_DALVIKVM_BOOT_OPT "%q" "$DALVIKVM_BOOT_OPT"
-else
- QUOTED_DALVIKVM_BOOT_OPT="$DALVIKVM_BOOT_OPT"
-fi
+QUOTED_DALVIKVM_BOOT_OPT=shlex.quote(DALVIKVM_BOOT_OPT)
-DALVIKVM_CLASSPATH=$DEX_LOCATION/$TEST_NAME.jar
-if [ -f "$TEST_NAME-aotex.jar" ] ; then
- DALVIKVM_CLASSPATH=$DALVIKVM_CLASSPATH:$DEX_LOCATION/$TEST_NAME-aotex.jar
-fi
-DALVIKVM_CLASSPATH=$DALVIKVM_CLASSPATH$SECONDARY_DEX
+DALVIKVM_CLASSPATH=f"{DEX_LOCATION}/{TEST_NAME}.jar"
+if isfile(f"{TEST_NAME}-aotex.jar"):
+ DALVIKVM_CLASSPATH=f"{DALVIKVM_CLASSPATH}:{DEX_LOCATION}/{TEST_NAME}-aotex.jar"
+DALVIKVM_CLASSPATH=f"{DALVIKVM_CLASSPATH}{SECONDARY_DEX}"
# We set DumpNativeStackOnSigQuit to false to avoid stressing libunwind.
# b/27185632
# b/24664297
-dalvikvm_cmdline="$INVOKE_WITH $GDB $ANDROID_ART_BIN_DIR/$DALVIKVM \
- $GDB_ARGS \
- $FLAGS \
- $DEX_VERIFY \
- -XXlib:$LIB \
- $DEX2OAT \
- $DALVIKVM_ISA_FEATURES_ARGS \
- $ZYGOTE \
- $JNI_OPTS \
- $INT_OPTS \
- $DEBUGGER_OPTS \
- ${QUOTED_DALVIKVM_BOOT_OPT} \
- $TMP_DIR_OPTION \
+dalvikvm_cmdline=f"{INVOKE_WITH} {GDB} {ANDROID_ART_BIN_DIR}/{DALVIKVM} \
+ {GDB_ARGS} \
+ {FLAGS} \
+ {DEX_VERIFY} \
+ -XXlib:{LIB} \
+ {DEX2OAT} \
+ {DALVIKVM_ISA_FEATURES_ARGS} \
+ {ZYGOTE} \
+ {JNI_OPTS} \
+ {INT_OPTS} \
+ {DEBUGGER_OPTS} \
+ {QUOTED_DALVIKVM_BOOT_OPT} \
+ {TMP_DIR_OPTION} \
-XX:DumpNativeStackOnSigQuit:false \
- -cp $DALVIKVM_CLASSPATH $MAIN $ARGS"
+ -cp {DALVIKVM_CLASSPATH} {MAIN} {ARGS}"
-if [ "x$SIMPLEPERF" == xyes ]; then
- dalvikvm_cmdline="simpleperf record ${dalvikvm_cmdline} && simpleperf report"
-fi
+if SIMPLEPERF:
+ dalvikvm_cmdline=f"simpleperf record {dalvikvm_cmdline} && simpleperf report"
-sanitize_dex2oat_cmdline() {
- local args=()
- for arg in "$@"; do
- if [ "$arg" = "--class-loader-context=&" ]; then
+def sanitize_dex2oat_cmdline(cmdline: str) -> str:
+ args = []
+ for arg in cmdline.split(" "):
+ if arg == "--class-loader-context=&":
arg="--class-loader-context=\&"
- fi
- args+=("$arg")
- done
- echo -n "${args[@]}"
-}
+ args.append(arg)
+ return " ".join(args)
# Remove whitespace.
-dex2oat_cmdline=$(sanitize_dex2oat_cmdline $(echo $dex2oat_cmdline))
-dalvikvm_cmdline=$(echo $dalvikvm_cmdline)
-dm_cmdline=$(echo $dm_cmdline)
-vdex_cmdline=$(sanitize_dex2oat_cmdline $(echo $vdex_cmdline))
-profman_cmdline=$(echo $profman_cmdline)
+dex2oat_cmdline=sanitize_dex2oat_cmdline(dex2oat_cmdline)
+dalvikvm_cmdline=re.sub(" +", " ", dalvikvm_cmdline)
+dm_cmdline=re.sub(" +", " ", dm_cmdline)
+vdex_cmdline=sanitize_dex2oat_cmdline(vdex_cmdline)
+profman_cmdline=re.sub(" +", " ", profman_cmdline)
# Use an empty ASAN_OPTIONS to enable defaults.
# Note: this is required as envsetup right now exports detect_leaks=0.
RUN_TEST_ASAN_OPTIONS=""
# Multiple shutdown leaks. b/38341789
-if [ "x$RUN_TEST_ASAN_OPTIONS" != "x" ] ; then
- RUN_TEST_ASAN_OPTIONS="${RUN_TEST_ASAN_OPTIONS}:"
-fi
-RUN_TEST_ASAN_OPTIONS="${RUN_TEST_ASAN_OPTIONS}detect_leaks=0"
+if RUN_TEST_ASAN_OPTIONS != "":
+ RUN_TEST_ASAN_OPTIONS=f"{RUN_TEST_ASAN_OPTIONS}:"
+RUN_TEST_ASAN_OPTIONS=f"{RUN_TEST_ASAN_OPTIONS}detect_leaks=0"
# For running, we must turn off logging when dex2oat is missing. Otherwise we use
# the same defaults as for prebuilt: everything when --dev, otherwise errors and above only.
-if [ "$EXTERNAL_LOG_TAGS" = "n" ]; then
- if [ "$DEV_MODE" = "y" ]; then
- export ANDROID_LOG_TAGS='*:d'
- elif [ "$HAVE_IMAGE" = "n" ]; then
+if not EXTERNAL_LOG_TAGS:
+ if DEV_MODE:
+ export("ANDROID_LOG_TAGS", '*:d')
+ elif not HAVE_IMAGE:
# All tests would log the error of missing image. Be silent here and only log fatal
# events.
- export ANDROID_LOG_TAGS='*:s'
- else
+ export("ANDROID_LOG_TAGS", '*:s')
+ else:
# We are interested in LOG(ERROR) output.
- export ANDROID_LOG_TAGS='*:e'
- fi
-fi
+ export("ANDROID_LOG_TAGS", '*:e')
-if [ "$HOST" = "n" ]; then
- adb root > /dev/null
- adb wait-for-device
- if [ "$QUIET" = "n" ]; then
- adb shell rm -rf $CHROOT_DEX_LOCATION
- adb shell mkdir -p $CHROOT_DEX_LOCATION
- adb push $TEST_NAME.jar $CHROOT_DEX_LOCATION
- adb push $TEST_NAME-ex.jar $CHROOT_DEX_LOCATION
- adb push $TEST_NAME-aotex.jar $CHROOT_DEX_LOCATION
- adb push $TEST_NAME-bcpex.jar $CHROOT_DEX_LOCATION
- if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
- adb push profile $CHROOT_DEX_LOCATION
- fi
+if not HOST:
+ adb.root()
+ adb.wait_for_device()
+ if not QUIET:
+ adb.shell(f"rm -rf {CHROOT_DEX_LOCATION}", capture_output=False)
+ adb.shell(f"mkdir -p {CHROOT_DEX_LOCATION}", capture_output=False)
+ adb.push(f"{TEST_NAME}.jar", CHROOT_DEX_LOCATION, capture_output=False)
+ adb.push(f"{TEST_NAME}-ex.jar", CHROOT_DEX_LOCATION, check=False, capture_output=False)
+ adb.push(f"{TEST_NAME}-aotex.jar", CHROOT_DEX_LOCATION, check=False, capture_output=False)
+ adb.push(f"{TEST_NAME}-bcpex.jar", CHROOT_DEX_LOCATION, check=False, capture_output=False)
+ if PROFILE or RANDOM_PROFILE:
+ adb.push("profile", CHROOT_DEX_LOCATION, check=False, capture_output=False)
# Copy resource folder
- if [ -d res ]; then
- adb push res $CHROOT_DEX_LOCATION
- fi
- else
- adb shell rm -rf $CHROOT_DEX_LOCATION >/dev/null 2>&1
- adb shell mkdir -p $CHROOT_DEX_LOCATION >/dev/null 2>&1
- adb push $TEST_NAME.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
- adb push $TEST_NAME-ex.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
- adb push $TEST_NAME-aotex.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
- adb push $TEST_NAME-bcpex.jar $CHROOT_DEX_LOCATION >/dev/null 2>&1
- if [ "$PROFILE" = "y" ] || [ "$RANDOM_PROFILE" = "y" ]; then
- adb push profile $CHROOT_DEX_LOCATION >/dev/null 2>&1
- fi
+ if isdir("res"):
+ adb.push("res", CHROOT_DEX_LOCATION, capture_output=False)
+ else:
+ adb.shell(f"rm -rf {CHROOT_DEX_LOCATION}")
+ adb.shell(f"mkdir -p {CHROOT_DEX_LOCATION}")
+ adb.push(f"{TEST_NAME}.jar", CHROOT_DEX_LOCATION)
+ adb.push(f"{TEST_NAME}-ex.jar", CHROOT_DEX_LOCATION, check=False)
+ adb.push(f"{TEST_NAME}-aotex.jar", CHROOT_DEX_LOCATION, check=False)
+ adb.push(f"{TEST_NAME}-bcpex.jar", CHROOT_DEX_LOCATION, check=False)
+ if PROFILE or RANDOM_PROFILE:
+ adb.push("profile", CHROOT_DEX_LOCATION, check=False)
# Copy resource folder
- if [ -d res ]; then
- adb push res $CHROOT_DEX_LOCATION >/dev/null 2>&1
- fi
- fi
+ if isdir("res"):
+ adb.push("res", CHROOT_DEX_LOCATION)
# Populate LD_LIBRARY_PATH.
- LD_LIBRARY_PATH=
- if [ "$ANDROID_ROOT" != "/system" ]; then
+ LD_LIBRARY_PATH=""
+ if ANDROID_ROOT != "/system":
# Current default installation is dalvikvm 64bits and dex2oat 32bits,
# so we can only use LD_LIBRARY_PATH when testing on a local
# installation.
- LD_LIBRARY_PATH="$ANDROID_ROOT/$LIBRARY_DIRECTORY"
- fi
+ LD_LIBRARY_PATH=f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}"
# This adds libarttest(d).so to the default linker namespace when dalvikvm
# is run from /apex/com.android.art/bin. Since that namespace is essentially
# an alias for the com_android_art namespace, that gives libarttest(d).so
# full access to the internal ART libraries.
- LD_LIBRARY_PATH="/data/$TEST_DIRECTORY/com.android.art/lib${SUFFIX64}:$LD_LIBRARY_PATH"
- if [ "$TEST_IS_NDEBUG" = "y" ]; then dlib=""; else dlib="d"; fi
- art_test_internal_libraries=(
- libartagent${dlib}.so
- libarttest${dlib}.so
- libtiagent${dlib}.so
- libtistress${dlib}.so
- )
- art_test_internal_libraries="${art_test_internal_libraries[*]}"
- NATIVELOADER_DEFAULT_NAMESPACE_LIBS="${art_test_internal_libraries// /:}"
- dlib=
- art_test_internal_libraries=
+ LD_LIBRARY_PATH=f"/data/{TEST_DIRECTORY}/com.android.art/lib{SUFFIX64}:{LD_LIBRARY_PATH}"
+ dlib=("" if TEST_IS_NDEBUG else "d")
+ art_test_internal_libraries=[
+ f"libartagent{dlib}.so",
+ f"libarttest{dlib}.so",
+ f"libtiagent{dlib}.so",
+ f"libtistress{dlib}.so",
+ ]
+ NATIVELOADER_DEFAULT_NAMESPACE_LIBS=":".join(art_test_internal_libraries)
+ dlib=""
+ art_test_internal_libraries=[]
# Needed to access the test's Odex files.
- LD_LIBRARY_PATH="$DEX_LOCATION/oat/$ISA:$LD_LIBRARY_PATH"
+ LD_LIBRARY_PATH=f"{DEX_LOCATION}/oat/{ISA}:{LD_LIBRARY_PATH}"
# Needed to access the test's native libraries (see e.g. 674-hiddenapi,
- # which generates `libhiddenapitest_*.so` libraries in `$DEX_LOCATION`).
- LD_LIBRARY_PATH="$DEX_LOCATION:$LD_LIBRARY_PATH"
+ # which generates `libhiddenapitest_*.so` libraries in `{DEX_LOCATION}`).
+ LD_LIBRARY_PATH=f"{DEX_LOCATION}:{LD_LIBRARY_PATH}"
# Prepend directories to the path on device.
- PREPEND_TARGET_PATH=$ANDROID_ART_BIN_DIR
- if [ "$ANDROID_ROOT" != "/system" ]; then
- PREPEND_TARGET_PATH="$PREPEND_TARGET_PATH:$ANDROID_ROOT/bin"
- fi
+ PREPEND_TARGET_PATH=ANDROID_ART_BIN_DIR
+ if ANDROID_ROOT != "/system":
+ PREPEND_TARGET_PATH=f"{PREPEND_TARGET_PATH}:{ANDROID_ROOT}/bin"
- timeout_dumper_cmd=
+ timeout_dumper_cmd=""
# Check whether signal_dumper is available.
- if [ "$TIMEOUT_DUMPER" = signal_dumper ] ; then
+ if TIMEOUT_DUMPER == "signal_dumper":
# Chroot? Use as prefix for tests.
- TIMEOUT_DUMPER_PATH_PREFIX=
- if [ -n "$CHROOT" ]; then
- TIMEOUT_DUMPER_PATH_PREFIX="$CHROOT/"
- fi
+ TIMEOUT_DUMPER_PATH_PREFIX=""
+ if CHROOT:
+ TIMEOUT_DUMPER_PATH_PREFIX=f"{CHROOT}/"
# Testing APEX?
- if adb shell "test -x ${TIMEOUT_DUMPER_PATH_PREFIX}/apex/com.android.art/bin/signal_dumper" ; then
+ if adb.shell(f"test -x {TIMEOUT_DUMPER_PATH_PREFIX}/apex/com.android.art/bin/signal_dumper",
+ check=False, save_cmd=False).returncode:
TIMEOUT_DUMPER="/apex/com.android.art/bin/signal_dumper"
# Is it in /system/bin?
- elif adb shell "test -x ${TIMEOUT_DUMPER_PATH_PREFIX}/system/bin/signal_dumper" ; then
+ elif adb.shell(f"test -x {TIMEOUT_DUMPER_PATH_PREFIX}/system/bin/signal_dumper",
+ check=False, save_cmd=False).returncode:
TIMEOUT_DUMPER="/system/bin/signal_dumper"
- else
- TIMEOUT_DUMPER=
- fi
- else
- TIMEOUT_DUMPER=
- fi
+ else:
+ TIMEOUT_DUMPER=""
+ else:
+ TIMEOUT_DUMPER=""
- if [ ! -z "$TIMEOUT_DUMPER" ] ; then
+ if TIMEOUT_DUMPER:
# Use "-l" to dump to logcat. That is convenience for the build bot crash symbolization.
# Use exit code 124 for toybox timeout (b/141007616).
- timeout_dumper_cmd="${TIMEOUT_DUMPER} -l -s 15 -e 124"
- fi
+ timeout_dumper_cmd=f"{TIMEOUT_DUMPER} -l -s 15 -e 124"
- timeout_prefix=
- if [ "$TIME_OUT" = "timeout" ]; then
+ timeout_prefix=""
+ if TIME_OUT == "timeout":
# Add timeout command if time out is desired.
#
# Note: We first send SIGTERM (the timeout default, signal 15) to the signal dumper, which
@@ -1324,106 +1295,100 @@
# dumping do not lead to a deadlock, we also use the "-k" option to definitely kill the
# child.
# Note: Using "--foreground" to not propagate the signal to children, i.e., the runtime.
- timeout_prefix="timeout --foreground -k 120s ${TIME_OUT_VALUE}s ${timeout_dumper_cmd} $cmdline"
- fi
+ timeout_prefix=f"timeout --foreground -k 120s {TIME_OUT_VALUE}s {timeout_dumper_cmd} {cmdline}"
# Create a script with the command. The command can get longer than the longest
# allowed adb command and there is no way to get the exit status from a adb shell
# command. Dalvik cache is cleaned before running to make subsequent executions
# of the script follow the same runtime path.
- cmdline="cd $DEX_LOCATION && \
- export ASAN_OPTIONS=$RUN_TEST_ASAN_OPTIONS && \
- export ANDROID_DATA=$DEX_LOCATION && \
- export DEX_LOCATION=$DEX_LOCATION && \
- export ANDROID_ROOT=$ANDROID_ROOT && \
- export ANDROID_I18N_ROOT=$ANDROID_I18N_ROOT && \
- export ANDROID_ART_ROOT=$ANDROID_ART_ROOT && \
- export ANDROID_TZDATA_ROOT=$ANDROID_TZDATA_ROOT && \
- export ANDROID_LOG_TAGS=$ANDROID_LOG_TAGS && \
- rm -rf ${DEX_LOCATION}/dalvik-cache/ && \
- mkdir -p ${mkdir_locations} && \
- export LD_LIBRARY_PATH=$LD_LIBRARY_PATH && \
- export NATIVELOADER_DEFAULT_NAMESPACE_LIBS=$NATIVELOADER_DEFAULT_NAMESPACE_LIBS && \
- export PATH=$PREPEND_TARGET_PATH:\$PATH && \
- $profman_cmdline && \
- $dex2oat_cmdline && \
- $dm_cmdline && \
- $vdex_cmdline && \
- $strip_cmdline && \
- $sync_cmdline && \
- $timeout_prefix $dalvikvm_cmdline"
+ cmdline=f"cd {DEX_LOCATION} && \
+ export ASAN_OPTIONS={RUN_TEST_ASAN_OPTIONS} && \
+ export ANDROID_DATA={DEX_LOCATION} && \
+ export DEX_LOCATION={DEX_LOCATION} && \
+ export ANDROID_ROOT={ANDROID_ROOT} && \
+ export ANDROID_I18N_ROOT={ANDROID_I18N_ROOT} && \
+ export ANDROID_ART_ROOT={ANDROID_ART_ROOT} && \
+ export ANDROID_TZDATA_ROOT={ANDROID_TZDATA_ROOT} && \
+ export ANDROID_LOG_TAGS={ANDROID_LOG_TAGS} && \
+ rm -rf {DEX_LOCATION}/dalvik-cache/ && \
+ mkdir -p {mkdir_locations} && \
+ export LD_LIBRARY_PATH={LD_LIBRARY_PATH} && \
+ export NATIVELOADER_DEFAULT_NAMESPACE_LIBS={NATIVELOADER_DEFAULT_NAMESPACE_LIBS} && \
+ export PATH={PREPEND_TARGET_PATH}:$PATH && \
+ {profman_cmdline} && \
+ {dex2oat_cmdline} && \
+ {dm_cmdline} && \
+ {vdex_cmdline} && \
+ {strip_cmdline} && \
+ {sync_cmdline} && \
+ {timeout_prefix} {dalvikvm_cmdline}"
- cmdfile=$(mktemp cmd-XXXX --suffix "-$TEST_NAME")
- echo "$cmdline" >> $cmdfile
+ cmdfile=run(f'mktemp cmd-XXXX --suffix "-{TEST_NAME}"', save_cmd=False).stdout.strip()
+ with open(cmdfile, "w") as f:
+ f.write(cmdline)
- if [ "$DEV_MODE" = "y" ]; then
- echo $cmdline
- if [ "$USE_GDB" = "y" ] || [ "$USE_GDBSERVER" = "y" ]; then
- echo "Forward ${GDBSERVER_PORT} to local port and connect GDB"
- fi
- fi
+ run('echo cmdline.sh "' + cmdline.replace('"', '\\"') + '"')
- if [ "$QUIET" = "n" ]; then
- adb push $cmdfile $CHROOT_DEX_LOCATION/cmdline.sh
- else
- adb push $cmdfile $CHROOT_DEX_LOCATION/cmdline.sh >/dev/null 2>&1
- fi
+ if DEV_MODE:
+ print(cmdline)
+ if USE_GDB or USE_GDBSERVER:
+ print(f"Forward {GDBSERVER_PORT} to local port and connect GDB")
+
+ if not QUIET:
+ adb.push(cmdfile, f"{CHROOT_DEX_LOCATION}/cmdline.sh", save_cmd=False, capture_output=False)
+ else:
+ adb.push(cmdfile, f"{CHROOT_DEX_LOCATION}/cmdline.sh", save_cmd=False)
exit_status=0
- if [ "$DRY_RUN" != "y" ]; then
- if [ -n "$CHROOT" ]; then
- adb shell chroot "$CHROOT" sh $DEX_LOCATION/cmdline.sh
- else
- adb shell sh $DEX_LOCATION/cmdline.sh
- fi
- exit_status=$?
- fi
+ if not DRY_RUN:
+ if CHROOT:
+ exit_status=adb.shell(f"chroot {CHROOT} sh {DEX_LOCATION}/cmdline.sh",
+ check=False, capture_output=False).returncode
+ else:
+ exit_status=adb.shell(f"sh {DEX_LOCATION}/cmdline.sh",
+ check=False ,capture_output=False).returncode
- rm -f $cmdfile
- exit $exit_status
-else
+ run(f'rm -f {cmdfile}', save_cmd=False)
+ sys.exit(exit_status)
+else:
# Host run.
- export ANDROID_PRINTF_LOG=brief
+ export("ANDROID_PRINTF_LOG", "brief")
- export ANDROID_DATA="$DEX_LOCATION"
- export ANDROID_ROOT="${ANDROID_ROOT}"
- export ANDROID_I18N_ROOT="${ANDROID_I18N_ROOT}"
- export ANDROID_ART_ROOT="${ANDROID_ART_ROOT}"
- export ANDROID_TZDATA_ROOT="${ANDROID_TZDATA_ROOT}"
- if [ "$USE_ZIPAPEX" = "y" ] || [ "$USE_EXRACTED_ZIPAPEX" = "y" ]; then
+ export("ANDROID_DATA", DEX_LOCATION)
+ export("ANDROID_ROOT", ANDROID_ROOT)
+ export("ANDROID_I18N_ROOT", ANDROID_I18N_ROOT)
+ export("ANDROID_ART_ROOT", ANDROID_ART_ROOT)
+ export("ANDROID_TZDATA_ROOT", ANDROID_TZDATA_ROOT)
+ if USE_ZIPAPEX or USE_EXRACTED_ZIPAPEX:
# Put the zipapex files in front of the ld-library-path
- export LD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
- export DYLD_LIBRARY_PATH="${ANDROID_DATA}/zipapex/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
- else
- export LD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
- export DYLD_LIBRARY_PATH="${ANDROID_ROOT}/${LIBRARY_DIRECTORY}:${ANDROID_ROOT}/${TEST_DIRECTORY}"
- fi
- export PATH="$PATH:$ANDROID_ART_BIN_DIR"
+ export("LD_LIBRARY_PATH", f"{ANDROID_DATA}/zipapex/{LIBRARY_DIRECTORY}:{ANDROID_ROOT}/{TEST_DIRECTORY}")
+ export("DYLD_LIBRARY_PATH", f"{ANDROID_DATA}/zipapex/{LIBRARY_DIRECTORY}:{ANDROID_ROOT}/{TEST_DIRECTORY}")
+ else:
+ export("LD_LIBRARY_PATH", f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}:{ANDROID_ROOT}/{TEST_DIRECTORY}")
+ export("DYLD_LIBRARY_PATH", f"{ANDROID_ROOT}/{LIBRARY_DIRECTORY}:{ANDROID_ROOT}/{TEST_DIRECTORY}")
+ export("PATH", f"{PATH}:{ANDROID_ART_BIN_DIR}")
# Temporarily disable address space layout randomization (ASLR).
# This is needed on the host so that the linker loads core.oat at the necessary address.
- export LD_USE_LOAD_BIAS=1
+ export("LD_USE_LOAD_BIAS", "1")
- cmdline="$dalvikvm_cmdline"
+ cmdline=dalvikvm_cmdline
- if [ "$TIME_OUT" = "gdb" ]; then
- if [ `uname` = "Darwin" ]; then
+ if TIME_OUT == "gdb":
+ if run("uname").stdout.strip() == "Darwin":
# Fall back to timeout on Mac.
TIME_OUT="timeout"
- elif [ "$ISA" = "x86" ]; then
+ elif ISA == "x86":
# prctl call may fail in 32-bit on an older (3.2) 64-bit Linux kernel. Fall back to timeout.
TIME_OUT="timeout"
- else
+ else:
# Check if gdb is available.
- gdb --eval-command="quit" > /dev/null 2>&1
- if [ $? != 0 ]; then
+ proc = run('gdb --eval-command="quit"', check=False, save_cmd=False, capture_output=True)
+ if proc.returncode != 0:
# gdb isn't available. Fall back to timeout.
TIME_OUT="timeout"
- fi
- fi
- fi
- if [ "$TIME_OUT" = "timeout" ]; then
+ if TIME_OUT == "timeout":
# Add timeout command if time out is desired.
#
# Note: We first send SIGTERM (the timeout default, signal 15) to the signal dumper, which
@@ -1431,116 +1396,104 @@
# dumping do not lead to a deadlock, we also use the "-k" option to definitely kill the
# child.
# Note: Using "--foreground" to not propagate the signal to children, i.e., the runtime.
- cmdline="timeout --foreground -k 120s ${TIME_OUT_VALUE}s ${TIMEOUT_DUMPER} -s 15 $cmdline"
- fi
+ cmdline=f"timeout --foreground -k 120s {TIME_OUT_VALUE}s {TIMEOUT_DUMPER} -s 15 {cmdline}"
- if [ "$DEV_MODE" = "y" ]; then
- for var in ANDROID_PRINTF_LOG ANDROID_DATA ANDROID_ROOT ANDROID_I18N_ROOT ANDROID_TZDATA_ROOT ANDROID_ART_ROOT LD_LIBRARY_PATH DYLD_LIBRARY_PATH PATH LD_USE_LOAD_BIAS; do
- echo EXPORT $var=${!var}
- done
- echo "$(declare -f linkdirs)"
- echo "mkdir -p ${mkdir_locations} && $setupapex_cmdline && ( $installapex_test_cmdline || $installapex_cmdline ) && $linkroot_cmdline && $linkroot_overlay_cmdline && $profman_cmdline && $dex2oat_cmdline && $dm_cmdline && $vdex_cmdline && $strip_cmdline && $sync_cmdline && $cmdline"
- fi
+ if DEV_MODE:
+ for var in "ANDROID_PRINTF_LOG ANDROID_DATA ANDROID_ROOT ANDROID_I18N_ROOT ANDROID_TZDATA_ROOT ANDROID_ART_ROOT LD_LIBRARY_PATH DYLD_LIBRARY_PATH PATH LD_USE_LOAD_BIAS".split(" "):
+ value = os.environ.get(var, "")
+ print(f"echo EXPORT {var}={value}")
+ print("$(declare -f linkdirs)")
+ print(f"mkdir -p {mkdir_locations} && {setupapex_cmdline} && ( {installapex_test_cmdline} || {installapex_cmdline} ) && {linkroot_cmdline} && {linkroot_overlay_cmdline} && {profman_cmdline} && {dex2oat_cmdline} && {dm_cmdline} && {vdex_cmdline} && {strip_cmdline} && {sync_cmdline} && {cmdline}")
- cd $ANDROID_BUILD_TOP
+ os.chdir(ANDROID_BUILD_TOP)
# Make sure we delete any existing compiler artifacts.
# This enables tests to call the RUN script multiple times in a row
# without worrying about interference.
- rm -rf ${DEX_LOCATION}/oat
- rm -rf ${DEX_LOCATION}/dalvik-cache/
+ shutil.rmtree(f"{DEX_LOCATION}/oat", ignore_errors=True)
+ shutil.rmtree(f"{DEX_LOCATION}/dalvik-cache/", ignore_errors=True)
- export ASAN_OPTIONS=$RUN_TEST_ASAN_OPTIONS
+ export("ASAN_OPTIONS", RUN_TEST_ASAN_OPTIONS)
- mkdir -p ${mkdir_locations} || exit 1
- $setupapex_cmdline || { echo "zipapex extraction failed." >&2 ; exit 2; }
- $installapex_test_cmdline || $installapex_cmdline || { echo "zipapex install failed. cmd was: ${installapex_test_cmdline} || ${installapex_cmdline}." >&2; find ${mkdir_locations} -type f >&2; exit 2; }
- $linkroot_cmdline || { echo "create symlink android-root failed." >&2 ; exit 2; }
- $linkroot_overlay_cmdline || { echo "overlay android-root failed." >&2 ; exit 2; }
- $profman_cmdline || { echo "Profman failed." >&2 ; exit 2; }
- eval "$dex2oat_cmdline" || { echo "Dex2oat failed." >&2 ; exit 2; }
- eval "$dm_cmdline" || { echo "Dex2oat failed." >&2 ; exit 2; }
- eval "$vdex_cmdline" || { echo "Dex2oat failed." >&2 ; exit 2; }
- $strip_cmdline || { echo "Strip failed." >&2 ; exit 3; }
- $sync_cmdline || { echo "Sync failed." >&2 ; exit 4; }
+ run(f"mkdir -p {mkdir_locations}", save_cmd=False)
+ run(setupapex_cmdline, capture_output=False)
+ if run(installapex_test_cmdline, check=False, capture_output=False).returncode != 0:
+ run(installapex_cmdline, capture_output=False)
+ run(linkroot_cmdline, capture_output=False)
+ run(linkroot_overlay_cmdline, capture_output=False)
+ run(profman_cmdline, capture_output=False)
+ run(dex2oat_cmdline, capture_output=False)
+ run(dm_cmdline, capture_output=False)
+ run(vdex_cmdline, capture_output=False)
+ run(strip_cmdline, capture_output=False)
+ run(sync_cmdline, capture_output=False)
- if [ "$CREATE_RUNNER" = "y" ]; then
- echo "#!/bin/bash" > ${DEX_LOCATION}/runit.sh
- for var in ANDROID_PRINTF_LOG ANDROID_DATA ANDROID_ROOT ANDROID_I18N_ROOT ANDROID_TZDATA_ROOT ANDROID_ART_ROOT LD_LIBRARY_PATH DYLD_LIBRARY_PATH PATH LD_USE_LOAD_BIAS; do
- echo export $var="${!var}" >> ${DEX_LOCATION}/runit.sh
- done
- if [ "$DEV_MODE" = "y" ]; then
- echo $cmdline >> ${DEX_LOCATION}/runit.sh
- else
- echo 'STDERR=$(mktemp)' >> ${DEX_LOCATION}/runit.sh
- echo 'STDOUT=$(mktemp)' >> ${DEX_LOCATION}/runit.sh
- echo $cmdline '>${STDOUT} 2>${STDERR}' >> ${DEX_LOCATION}/runit.sh
- echo 'if diff ${STDOUT} $ANDROID_DATA/expected-stdout.txt; then' \
- >> ${DEX_LOCATION}/runit.sh
- echo ' rm -f ${STDOUT} ${STDERR}' >> ${DEX_LOCATION}/runit.sh
- echo ' exit 0' >> ${DEX_LOCATION}/runit.sh
- echo 'elif diff ${STDERR} $ANDROID_DATA/expected-stderr.txt; then' \
- >> ${DEX_LOCATION}/runit.sh
- echo ' rm -f ${STDOUT} ${STDERR}' >> ${DEX_LOCATION}/runit.sh
- echo ' exit 0' >> ${DEX_LOCATION}/runit.sh
- echo 'else' >> ${DEX_LOCATION}/runit.sh
- echo ' echo STDOUT:' >> ${DEX_LOCATION}/runit.sh
- echo ' cat ${STDOUT}' >> ${DEX_LOCATION}/runit.sh
- echo ' echo STDERR:' >> ${DEX_LOCATION}/runit.sh
- echo ' cat ${STDERR}' >> ${DEX_LOCATION}/runit.sh
- echo ' rm -f ${STDOUT} ${STDERR}' >> ${DEX_LOCATION}/runit.sh
- echo ' exit 1' >> ${DEX_LOCATION}/runit.sh
- echo 'fi' >> ${DEX_LOCATION}/runit.sh
- fi
- chmod u+x $DEX_LOCATION/runit.sh
- echo "Runnable test script written to ${DEX_LOCATION}/runit.sh"
- fi
- if [ "$DRY_RUN" = "y" ]; then
- exit 0
- fi
+ if CREATE_RUNNER:
+ with open(f"{DEX_LOCATION}/runit.sh", "w") as f:
+ f.write("#!/bin/bash")
+ for var in "ANDROID_PRINTF_LOG ANDROID_DATA ANDROID_ROOT ANDROID_I18N_ROOT ANDROID_TZDATA_ROOT ANDROID_ART_ROOT LD_LIBRARY_PATH DYLD_LIBRARY_PATH PATH LD_USE_LOAD_BIAS".split(" "):
+ value = os.environ.get(var, "")
+ f.write(f'export {var}="{value}"')
+ if DEV_MODE:
+ f.write(cmdline)
+ else:
+ f.writelines([
+ 'STDERR=$(mktemp)',
+ 'STDOUT=$(mktemp)',
+ cmdline + ' >${STDOUT} 2>${STDERR}',
+ 'if diff ${STDOUT} {ANDROID_DATA}/expected-stdout.txt; then',
+ ' rm -f ${STDOUT} ${STDERR}',
+ ' exit 0',
+ 'elif diff ${STDERR} {ANDROID_DATA}/expected-stderr.txt; then',
+ ' rm -f ${STDOUT} ${STDERR}',
+ ' exit 0',
+ 'else',
+ ' echo STDOUT:',
+ ' cat ${STDOUT}',
+ ' echo STDERR:',
+ ' cat ${STDERR}',
+ ' rm -f ${STDOUT} ${STDERR}',
+ ' exit 1',
+ 'fi',
+ ])
+ os.chmod("{DEX_LOCATION}/runit.sh", 0o777)
+ print(f"Runnable test script written to {DEX_LOCATION}/runit.sh")
+ if DRY_RUN:
+ sys.exit(0)
- if [ "$USE_GDB" = "y" ]; then
+ if USE_GDB:
# When running under gdb, we cannot do piping and grepping...
- $cmdline "$@"
- elif [ "$USE_GDBSERVER" = "y" ]; then
- echo "Connect to $GDBSERVER_PORT"
+ subprocess.run(cmdline + test_args, shell=True)
+ elif USE_GDBSERVER:
+ print("Connect to {GDBSERVER_PORT}")
# When running under gdb, we cannot do piping and grepping...
- $cmdline "$@"
- else
- if [ "$TIME_OUT" != "gdb" ]; then
- trap 'kill -INT -$pid' INT
- $cmdline "$@" & pid=$!
- wait $pid
- exit_value=$?
+ subprocess.run(cmdline + test_args, shell=True)
+ else:
+ if TIME_OUT != "gdb":
+ proc = run(cmdline + test_args, check=False, capture_output=False)
+ exit_value=proc.returncode
# Add extra detail if time out is enabled.
- if [ $exit_value = 124 ] && [ "$TIME_OUT" = "timeout" ]; then
- echo -e "\e[91mTEST TIMED OUT!\e[0m" >&2
- fi
- exit $exit_value
- else
+ if exit_value == 124 and TIME_OUT == "timeout":
+ print("\e[91mTEST TIMED OUT!\e[0m", file=sys.stderr)
+ sys.exit(exit_value)
+ else:
# With a thread dump that uses gdb if a timeout.
- trap 'kill -INT -$pid' INT
- $cmdline "$@" & pid=$!
- # Spawn a watcher process.
- ( sleep $TIME_OUT_VALUE && \
- echo "##### Thread dump using gdb on test timeout" && \
- ( gdb -q -p $pid --eval-command="info thread" --eval-command="thread apply all bt" \
- --eval-command="call exit(124)" --eval-command=quit || \
- kill $pid )) 2> /dev/null & watcher=$!
- wait $pid
- test_exit_status=$?
- pkill -P $watcher 2> /dev/null # kill the sleep which will in turn end the watcher as well
- if [ $test_exit_status = 0 ]; then
+ proc = run(cmdline + test_args, check=False)
+ # TODO: Spawn a watcher process.
+ raise Exception("Not implemented")
+ # ( sleep {TIME_OUT_VALUE} && \
+ # echo "##### Thread dump using gdb on test timeout" && \
+ # ( gdb -q -p {pid} --eval-command="info thread" --eval-command="thread apply all bt" \
+ # --eval-command="call exit(124)" --eval-command=quit || \
+ # kill {pid} )) 2> /dev/null & watcher=$!
+ test_exit_status=proc.returncode
+ # pkill -P {watcher} 2> /dev/null # kill the sleep which will in turn end the watcher as well
+ if test_exit_status == 0:
# The test finished normally.
- exit 0
- else
+ sys.exit(0)
+ else:
# The test failed or timed out.
- if [ $test_exit_status = 124 ]; then
+ if test_exit_status == 124:
# The test timed out.
- echo -e "\e[91mTEST TIMED OUT!\e[0m" >&2
- fi
- exit $test_exit_status
- fi
- fi
- fi
-fi
+ print("\e[91mTEST TIMED OUT!\e[0m", file=sys.stderr)
+ sys.exit(test_exit_status)
diff --git a/test/knownfailures.json b/test/knownfailures.json
index 3e485f6..ac10757 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1097,6 +1097,12 @@
"description": ["Failing on RI. Needs further investigating. Some of these use smali."]
},
{
+ "tests": ["2042-reference-processing",
+ "2043-reference-pauses"],
+ "variant": "jvm",
+ "description": ["Flakey behavior of RI."]
+ },
+ {
"tests": [
"1974-resize-array",
"1975-hello-structural-transformation",
@@ -1168,6 +1174,7 @@
"831-unresolved-field",
"833-background-verification",
"836-32768classes",
+ "837-deopt",
"999-redefine-hiddenapi",
"1000-non-moving-space-stress",
"1001-app-image-regions",
@@ -1261,7 +1268,7 @@
"tests": ["141-class-unload", "071-dexfile"],
"variant": "gcstress",
"bug": "b/111543628",
- "description" : ["Test seems to timeout when run with gcstress due to slower unwinding by libbacktrace"]
+ "description" : ["Test seems to timeout when run with gcstress due to slower unwinding by libunwindstack"]
},
{
"tests": ["708-jit-cache-churn"],
@@ -1273,7 +1280,7 @@
"tests": ["712-varhandle-invocations"],
"variant": "gcstress",
"bug": "b/111630237",
- "description": ["Test timing out under gcstress possibly due to slower unwinding by libbacktrace"]
+ "description": ["Test timing out under gcstress possibly due to slower unwinding by libunwindstack"]
},
{
"tests": ["1336-short-finalizer-timeout"],
@@ -1326,6 +1333,16 @@
"description": ["Test containing Checker assertions expecting Baker read barriers."]
},
{
+ "tests": ["2009-structural-local-ref",
+ "2035-structural-native-method",
+ "2036-structural-subclass-shadow",
+ "2040-huge-native-alloc",
+ "905-object-free"],
+ "env_vars": {"ART_USE_READ_BARRIER": "false"},
+ "bug": "b/242181443",
+ "description": ["Tests temporarily disabled for userfaultfd GC. Remove once native GC-root updation is implemented."]
+ },
+ {
"tests": ["1004-checker-volatile-ref-load"],
"env_vars": {"ART_READ_BARRIER_TYPE": "TABLELOOKUP"},
"bug": "b/140507091",
@@ -1358,11 +1375,23 @@
"description": "Interpreting BigInteger.add() is too slow (timeouts)"
},
{
- "tests": ["2029-contended-monitors"],
+ "tests": ["2029-contended-monitors", "2043-reference-pauses"],
"variant": "interpreter | interp-ac | gcstress | trace",
+ "description": ["Slow tests. Prone to timeouts."]
+ },
+ {
+ "tests": ["2042-reference-processing"],
+ "variant": "interpreter | interp-ac | gcstress | trace | debuggable",
"description": ["Slow test. Prone to timeouts."]
},
{
+ "tests": ["2043-reference-pauses"],
+ "env_vars": {"ART_USE_READ_BARRIER": "false"},
+ "variant": "host",
+ "bug": "b/232459100",
+ "description": ["Fails intermittently for CMS."]
+ },
+ {
"tests": ["096-array-copy-concurrent-gc"],
"variant": "gcstress & debuggable & debug & host",
"bug": "b/149708943",
@@ -1497,5 +1526,27 @@
"variant": "target & ndebug & 64",
"bug": "b/224733324",
"description": ["segfault in VarHandle::GetMethodTypeMatchForAccessMode"]
+ },
+ {
+ "tests": ["2043-reference-pauses"],
+ "env_vars": {"ART_TEST_DEBUG_GC": "true"},
+ "description": ["Test timing out on debug gc."]
+ },
+ {
+ "tests": ["837-deopt"],
+ "description": ["Tests deoptimization and OSR, which never happens when tracing."],
+ "variant": "trace | stream | jit-on-first-use"
+ },
+ {
+ "tests": ["1912-get-set-local-primitive"],
+ "description": ["JVMTI error code used changed in JDK 17."],
+ "bug": "b/243356199",
+ "variant": "jvm"
+ },
+ {
+ "tests": ["715-clinit-implicit-parameter-annotations"],
+ "description": ["Change for Annotation.toString() in JDK 17."],
+ "bug": "b/243500721",
+ "variant": "jvm"
}
]
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 8c2c5b2..60d7642 100644
--- a/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/CompOsTestUtils.java
@@ -16,7 +16,6 @@
package com.android.tests.odsign;
-import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
@@ -36,7 +35,8 @@
"/data/misc/apexdata/com.android.art/compos-pending";
/** Maximum time for a slow VM like cuttlefish to boot and finish odrefresh. */
- private static final int VM_ODREFRESH_MAX_SECONDS = 360;
+ // odrefresh overall timeout is currently 480s; add some generous padding for VM startup.
+ private static final int VM_ODREFRESH_MAX_SECONDS = 480 + 60;
/** Waiting time for the job to be scheduled after staging an APEX */
private static final int JOB_CREATION_MAX_SECONDS = 5;
@@ -74,8 +74,6 @@
// Sort by filename (second column) to make comparison easier.
// Filter out compos.info* (which will be deleted at boot) and cache-info.xml
// compos.info.signature since it's only generated by CompOS.
- // TODO(b/210473615): Remove irrelevant APEXes (i.e. those aren't contributing to the
- // classpaths, thus not in the VM) from cache-info.xml.
return assertCommandSucceeds("cd " + path + "; find -type f -exec sha256sum {} \\;"
+ "| grep -v cache-info.xml | grep -v compos.info"
+ "| sort -k2");
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
index 731ea38..e67822e 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -27,6 +27,7 @@
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -46,10 +47,6 @@
private static final String CACHE_INFO_FILE =
OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
private static final String ODREFRESH_BIN = "odrefresh";
- private static final String ODREFRESH_COMMAND =
- ODREFRESH_BIN + " --partial-compilation --no-refresh --compile";
- private static final String ODREFRESH_MINIMAL_COMMAND =
- ODREFRESH_BIN + " --partial-compilation --no-refresh --minimal --compile";
private static final String TAG = "OdrefreshHostTest";
private static final String ZYGOTE_ARTIFACTS_KEY = TAG + ":ZYGOTE_ARTIFACTS";
@@ -87,11 +84,27 @@
mTestUtils = new OdsignTestUtils(getTestInformation());
}
+ @After
+ public void tearDown() throws Exception {
+ Set<String> artifacts = new HashSet<>();
+ artifacts.addAll(getZygoteArtifacts());
+ artifacts.addAll(getSystemServerArtifacts());
+
+ for (String artifact : artifacts) {
+ if (!getDevice().doesFileExist(artifact)) {
+ // Things went wrong during the test. Run odrefresh to revert to a normal state.
+ mTestUtils.removeCompilationLogToAvoidBackoff();
+ runOdrefresh();
+ break;
+ }
+ }
+ }
+
@Test
public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
simulateArtApexUpgrade();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
@@ -101,7 +114,7 @@
public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
simulateApexUpgrade();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
@@ -111,7 +124,7 @@
public void verifyBootClasspathOtaTriggersCompilation() throws Exception {
simulateBootClasspathOta();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
@@ -121,7 +134,7 @@
public void verifySystemServerOtaTriggersCompilation() throws Exception {
simulateSystemServerOta();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
@@ -137,7 +150,7 @@
mTestUtils.removeCompilationLogToAvoidBackoff();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
assertArtifactsNotModifiedAfter(remainingArtifacts, timeMs);
assertArtifactsModifiedAfter(missingArtifacts, timeMs);
@@ -158,7 +171,7 @@
"device_config put runtime_native_boot enable_uffd_gc false");
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should not be re-compiled.
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -169,7 +182,7 @@
"device_config put runtime_native_boot enable_uffd_gc true");
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should be re-compiled.
assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -177,7 +190,7 @@
// Run odrefresh again with the flag unchanged.
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should not be re-compiled.
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -188,7 +201,7 @@
"device_config put runtime_native_boot enable_uffd_gc false");
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should be re-compiled.
assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -205,7 +218,7 @@
// Change a system property from empty to a value.
getDevice().setProperty("dalvik.vm.foo", "1");
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should be re-compiled.
assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -213,7 +226,7 @@
// Run again with the same value.
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should not be re-compiled.
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -222,7 +235,7 @@
// Change the system property to another value.
getDevice().setProperty("dalvik.vm.foo", "2");
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should be re-compiled.
assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -230,7 +243,7 @@
// Run again with the same value.
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should not be re-compiled.
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -239,7 +252,7 @@
// Change the system property to empty.
getDevice().setProperty("dalvik.vm.foo", "");
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should be re-compiled.
assertArtifactsModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -247,7 +260,7 @@
// Run again with the same value.
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// Artifacts should not be re-compiled.
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -258,7 +271,7 @@
public void verifyNoCompilationWhenCacheIsGood() throws Exception {
mTestUtils.removeCompilationLogToAvoidBackoff();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
assertArtifactsNotModifiedAfter(getSystemServerArtifacts(), timeMs);
@@ -267,8 +280,8 @@
@Test
public void verifyUnexpectedFilesAreCleanedUp() throws Exception {
String unexpected = OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/unexpected";
- getDevice().pushString(/*contents=*/"", unexpected);
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ getDevice().pushString("" /* contents */, unexpected);
+ runOdrefresh();
assertFalse(getDevice().doesFileExist(unexpected));
}
@@ -292,9 +305,7 @@
mTestUtils.removeCompilationLogToAvoidBackoff();
simulateApexUpgrade();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(
- ODREFRESH_BIN + " --no-refresh --partial-compilation"
- + " --compilation-os-mode --compile");
+ runOdrefresh("--compilation-os-mode");
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
assertArtifactsModifiedAfter(getSystemServerArtifacts(), timeMs);
@@ -307,7 +318,7 @@
// Simulate the odrefresh invocation on the next boot.
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
// odrefresh should not re-compile anything.
assertArtifactsNotModifiedAfter(getZygoteArtifacts(), timeMs);
@@ -319,7 +330,7 @@
mTestUtils.removeCompilationLogToAvoidBackoff();
getDevice().executeShellV2Command(
"rm -rf " + OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME);
- getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
+ runOdrefresh("--minimal");
mTestUtils.restartZygote();
@@ -330,21 +341,14 @@
// Running the command again should not overwrite the minimal boot image.
mTestUtils.removeCompilationLogToAvoidBackoff();
long timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_MINIMAL_COMMAND);
-
- assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
-
- // `odrefresh --check` should keep the minimal boot image.
- mTestUtils.removeCompilationLogToAvoidBackoff();
- timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_BIN + " --check");
+ runOdrefresh("--minimal");
assertArtifactsNotModifiedAfter(minimalZygoteArtifacts, timeMs);
// A normal odrefresh invocation should replace the minimal boot image with a full one.
mTestUtils.removeCompilationLogToAvoidBackoff();
timeMs = mTestUtils.getCurrentTimeMs();
- getDevice().executeShellV2Command(ODREFRESH_COMMAND);
+ runOdrefresh();
for (String artifact : minimalZygoteArtifacts) {
assertFalse(
@@ -488,4 +492,14 @@
private Set<String> getSystemServerArtifacts() {
return getColonSeparatedSet(SYSTEM_SERVER_ARTIFACTS_KEY);
}
+
+ private void runOdrefresh() throws Exception {
+ runOdrefresh("" /* extraArgs */);
+ }
+
+ private void runOdrefresh(String extraArgs) throws Exception {
+ getDevice().executeShellV2Command(ODREFRESH_BIN + " --check");
+ getDevice().executeShellV2Command(
+ ODREFRESH_BIN + " --partial-compilation --no-refresh " + extraArgs + " --compile");
+ }
}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
index caf94a7..5951600 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
@@ -314,18 +314,14 @@
// We can't use the "-c '%.3Y'" flag when to get the timestamp because the Toybox's `stat`
// implementation truncates the timestamp to seconds, which is not accurate enough, so we
// use "-c '%%y'" and parse the time ourselves.
- String dateTimeStr = mTestInfo.getDevice()
- .executeShellCommand(String.format("stat -c '%%y' '%s'", filename))
- .trim();
+ String dateTimeStr = assertCommandSucceeds(String.format("stat -c '%%y' '%s'", filename));
return parseFormattedDateTime(dateTimeStr);
}
public long getCurrentTimeMs() throws Exception {
// We can't use getDevice().getDeviceDate() because it truncates the timestamp to seconds,
// which is not accurate enough.
- String dateTimeStr = mTestInfo.getDevice()
- .executeShellCommand("date +'%Y-%m-%d %H:%M:%S.%N %z'")
- .trim();
+ String dateTimeStr = assertCommandSucceeds("date +'%Y-%m-%d %H:%M:%S.%N %z'");
return parseFormattedDateTime(dateTimeStr);
}
diff --git a/test/run-test b/test/run-test
index dccc9f6..a14ad95 100755
--- a/test/run-test
+++ b/test/run-test
@@ -40,16 +40,18 @@
tmp_dir="${TMPDIR}/${test_dir}"
fi
checker="${progdir}/../tools/checker/checker.py"
-export JAVA="java"
-export JAVAC="javac -g -Xlint:-options -source 1.8 -target 1.8"
-export RUN="${progdir}/etc/run-test-jar"
-export DEX_LOCATION=/data/run-test/${test_dir}
# ANDROID_BUILD_TOP is not set in a build environment.
if [ -z "$ANDROID_BUILD_TOP" ]; then
export ANDROID_BUILD_TOP=$oldwd
fi
+export JAVA="java"
+export JAVAC="javac -g -Xlint:-options -source 1.8 -target 1.8"
+export PYTHON3="${ANDROID_BUILD_TOP}/prebuilts/build-tools/path/linux-x86/python3"
+export RUN="${PYTHON3} ${progdir}/etc/run-test-jar"
+export DEX_LOCATION=/data/run-test/${test_dir}
+
# OUT_DIR defaults to out, and may be relative to $ANDROID_BUILD_TOP.
# Convert it to an absolute path, since we cd into the tmp_dir to run the tests.
export OUT_DIR=${OUT_DIR:-out}
diff --git a/test/run-test-build.py b/test/run-test-build.py
index f8eb283..93e9599 100755
--- a/test/run-test-build.py
+++ b/test/run-test-build.py
@@ -19,7 +19,9 @@
It is intended to be used only from soong genrule.
"""
-import argparse, os, tempfile, shutil, subprocess, glob, textwrap, re, json, concurrent.futures
+import argparse, os, shutil, subprocess, glob, re, json, multiprocessing, pathlib
+import art_build_rules
+from importlib.machinery import SourceFileLoader
ZIP = "prebuilts/build-tools/linux-x86/bin/soong_zip"
BUILDFAILURES = json.loads(open(os.path.join("art", "test", "buildfailures.json"), "rt").read())
@@ -41,7 +43,7 @@
shutil.copytree(srcdir, dstdir)
# Copy the default scripts if the test does not have a custom ones.
- for name in ["build", "run", "check"]:
+ for name in ["build.py", "run", "check"]:
src, dst = f"art/test/etc/default-{name}", join(dstdir, name)
if os.path.exists(dst):
shutil.copy2(src, dstdir) # Copy default script next to the custom script.
@@ -51,18 +53,20 @@
return dstdir
-def build_test(args, mode, dstdir):
+def build_test(args, mode, build_top, sbox, dstdir):
"""Run the build script for single run-test"""
join = os.path.join
- build_top = os.getcwd()
java_home = os.environ.get("JAVA_HOME")
tools_dir = os.path.abspath(join(os.path.dirname(__file__), "../../../out/bin"))
- env = {
- "PATH": os.environ.get("PATH"),
+ test_name = os.path.basename(dstdir)
+ env = dict(os.environ)
+ env.update({
+ "BUILD_MODE": mode,
"ANDROID_BUILD_TOP": build_top,
+ "SBOX_PATH": sbox,
"ART_TEST_RUN_TEST_BOOTCLASSPATH": join(build_top, args.bootclasspath),
- "TEST_NAME": os.path.basename(dstdir),
+ "TEST_NAME": test_name,
"SOONG_ZIP": join(build_top, "prebuilts/build-tools/linux-x86/bin/soong_zip"),
"ZIPALIGN": join(build_top, "prebuilts/build-tools/linux-x86/bin/zipalign"),
"JAVA": join(java_home, "bin/java"),
@@ -73,15 +77,23 @@
"JASMIN": join(tools_dir, "jasmin"),
"SMALI": join(tools_dir, "smali"),
"NEED_DEX": {"host": "true", "target": "true", "jvm": "false"}[mode],
- "USE_DESUGAR": "true",
- }
- proc = subprocess.run([join(dstdir, "build"), "--" + mode],
- cwd=dstdir,
- env=env,
- encoding=os.sys.stdout.encoding,
- stderr=subprocess.STDOUT,
- stdout=subprocess.PIPE)
- return proc.stdout, proc.returncode
+ })
+
+ generate_sources = join(dstdir, "generate-sources")
+ if os.path.exists(generate_sources):
+ proc = subprocess.run([generate_sources, "--" + mode],
+ cwd=dstdir,
+ env=env,
+ encoding=os.sys.stdout.encoding,
+ stderr=subprocess.STDOUT,
+ stdout=subprocess.PIPE)
+ if proc.returncode:
+ raise Exception("Failed to generate sources for " + test_name + ":\n" + proc.stdout)
+
+ os.chdir(dstdir)
+ for name, value in env.items():
+ os.environ[name] = str(value)
+ SourceFileLoader("build_" + test_name, join(dstdir, "build.py")).load_module()
def main():
parser = argparse.ArgumentParser(description=__doc__)
@@ -91,19 +103,26 @@
parser.add_argument("--bootclasspath", help="JAR files used for javac compilation")
args = parser.parse_args()
- with tempfile.TemporaryDirectory(prefix=os.path.basename(__file__)) as tmp:
- srcdirs = sorted(glob.glob(os.path.join("art", "test", "*")))
- srcdirs = filter(lambda srcdir: re.match(".*/\d*{}-.*".format(args.shard), srcdir), srcdirs)
- dstdirs = [copy_sources(args, tmp, args.mode, srcdir) for srcdir in srcdirs]
- dstdirs = filter(lambda dstdir: dstdir, dstdirs) # Remove None (skipped tests).
- with concurrent.futures.ThreadPoolExecutor() as pool:
- for stdout, exitcode in pool.map(lambda dstdir: build_test(args, args.mode, dstdir), dstdirs):
- if stdout:
- print(stdout.strip())
- assert(exitcode == 0) # Build failed. Add test to buildfailures.json if this is expected.
+ build_top = os.getcwd()
+ sbox = pathlib.Path(__file__).absolute().parent.parent.parent.parent.parent
+ assert sbox.parent.name == "sbox" and len(sbox.name) == 40
- # Create the final zip file which contains the content of the temporary directory.
- proc = subprocess.run([ZIP, "-o", args.out, "-C", tmp, "-D", tmp], check=True)
+ ziproot = os.path.join(sbox, "zip")
+ srcdirs = sorted(glob.glob(os.path.join("art", "test", "*")))
+ srcdirs = filter(lambda srcdir: re.match(".*/\d*{}-.*".format(args.shard), srcdir), srcdirs)
+ dstdirs = [copy_sources(args, ziproot, args.mode, srcdir) for srcdir in srcdirs]
+ dstdirs = filter(lambda dstdir: dstdir, dstdirs) # Remove None (skipped tests).
+ # Use multiprocess (i.e. forking) since tests modify their current working directory.
+ with multiprocessing.Pool() as pool:
+ jobs = [(d, pool.apply_async(build_test, (args, args.mode, build_top, sbox, d))) for d in dstdirs]
+ for dstdir, job in jobs:
+ try:
+ job.get()
+ except Exception as e:
+ raise Exception("Failed to build " + os.path.basename(dstdir)) from e.__cause__
+
+ # Create the final zip file which contains the content of the temporary directory.
+ proc = subprocess.run([ZIP, "-o", args.out, "-C", ziproot, "-D", ziproot], check=True)
if __name__ == "__main__":
main()
diff --git a/test/testrunner/env.py b/test/testrunner/env.py
index 319e1a7..d5e0543 100644
--- a/test/testrunner/env.py
+++ b/test/testrunner/env.py
@@ -132,8 +132,8 @@
HOST_OUT_EXECUTABLES = os.path.join(ANDROID_BUILD_TOP,
_get_build_var("HOST_OUT_EXECUTABLES"))
-# Set up default values for $DX, $SMALI, etc to the $HOST_OUT_EXECUTABLES/$name path.
-for tool in ['dx', 'smali', 'jasmin', 'd8']:
+# Set up default values for $D8, $SMALI, etc to the $HOST_OUT_EXECUTABLES/$name path.
+for tool in ['smali', 'jasmin', 'd8']:
os.environ.setdefault(tool.upper(), HOST_OUT_EXECUTABLES + '/' + tool)
ANDROID_JAVA_TOOLCHAIN = os.path.join(ANDROID_BUILD_TOP,
diff --git a/test/testrunner/run_build_test_target.py b/test/testrunner/run_build_test_target.py
index 4191771..f3a73bd 100755
--- a/test/testrunner/run_build_test_target.py
+++ b/test/testrunner/run_build_test_target.py
@@ -29,12 +29,18 @@
import argparse
import os
import pathlib
+import re
import subprocess
import sys
from target_config import target_config
import env
+# Check that we are using reasonably recent version of python
+print("Using", sys.executable, sys.version, flush=True)
+version = tuple(map(int, re.match(r"(\d*)\.(\d*)", sys.version).groups()))
+assert (version >= (3, 9)), "Python version is too old"
+
parser = argparse.ArgumentParser()
parser.add_argument('-j', default='1', dest='n_threads')
# either -l/--list OR build-target is required (but not both).
@@ -81,7 +87,7 @@
if 'build' in target:
build_command = target.get('build').format(
ANDROID_BUILD_TOP = env.ANDROID_BUILD_TOP,
- MAKE_OPTIONS='DX= -j{threads}'.format(threads = n_threads))
+ MAKE_OPTIONS='D8= -j{threads}'.format(threads = n_threads))
sys.stdout.write(str(build_command) + '\n')
sys.stdout.flush()
if subprocess.call(build_command.split()):
@@ -90,7 +96,7 @@
# make runs soong/kati to build the target listed in the entry.
if 'make' in target:
build_command = 'build/soong/soong_ui.bash --make-mode'
- build_command += ' DX='
+ build_command += ' D8='
build_command += ' -j' + str(n_threads)
build_command += ' ' + target.get('make')
if env.DIST_DIR:
diff --git a/test/testrunner/testrunner.py b/test/testrunner/testrunner.py
index 935ce0c..9ab4ba7 100755
--- a/test/testrunner/testrunner.py
+++ b/test/testrunner/testrunner.py
@@ -665,9 +665,12 @@
test_start_time = time.monotonic()
if verbose:
print_text("Starting %s at %s\n" % (test_name, test_start_time))
+ env = dict(os.environ)
+ env["FULL_TEST_NAME"] = test_name
if gdb or gdb_dex2oat:
proc = _popen(
args=command.split(),
+ env=env,
stderr=subprocess.STDOUT,
universal_newlines=True,
start_new_session=True
@@ -675,6 +678,7 @@
else:
proc = _popen(
args=command.split(),
+ env=env,
stderr=subprocess.STDOUT,
stdout = subprocess.PIPE,
universal_newlines=True,
@@ -1241,7 +1245,7 @@
if 'jvm' in _user_input_variants['target']:
build_targets += 'test-art-host-run-test-dependencies '
build_command = env.ANDROID_BUILD_TOP + '/build/soong/soong_ui.bash --make-mode'
- build_command += ' DX='
+ build_command += ' D8='
if dist:
build_command += ' dist'
build_command += ' ' + build_targets
diff --git a/test/ti-agent/scoped_utf_chars.h b/test/ti-agent/scoped_utf_chars.h
index 422caaf..ddf1bd5 100644
--- a/test/ti-agent/scoped_utf_chars.h
+++ b/test/ti-agent/scoped_utf_chars.h
@@ -38,7 +38,7 @@
}
}
- ScopedUtfChars(ScopedUtfChars&& rhs) :
+ ScopedUtfChars(ScopedUtfChars&& rhs) noexcept :
env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) {
rhs.env_ = nullptr;
rhs.string_ = nullptr;
@@ -51,7 +51,7 @@
}
}
- ScopedUtfChars& operator=(ScopedUtfChars&& rhs) {
+ ScopedUtfChars& operator=(ScopedUtfChars&& rhs) noexcept {
if (this != &rhs) {
// Delete the currently owned UTF chars.
this->~ScopedUtfChars();
diff --git a/test/update-rollback/Android.bp b/test/update-rollback/Android.bp
index 3713328..ad14988 100644
--- a/test/update-rollback/Android.bp
+++ b/test/update-rollback/Android.bp
@@ -23,5 +23,9 @@
libs: ["tradefed"],
static_libs: ["cts-install-lib-host"],
data: [":test_broken_com.android.art"],
- test_suites: ["general-tests"],
+ // Add this test to `device-tests` rather than `general-tests` to ensure
+ // that the type of ART APEX -- public (`com.android.art`) or internal
+ // (`com.google.android.art`) -- used in the test matches the one bundled
+ // with the Android platform used in the device-under-test.
+ test_suites: ["device-tests"],
}
diff --git a/test/utils/regen-test-files b/test/utils/regen-test-files
index b6c2fb6..bba75a2 100755
--- a/test/utils/regen-test-files
+++ b/test/utils/regen-test-files
@@ -203,11 +203,21 @@
"826-infinite-loop",
# 832-cha-recursive: Dependency on `libarttest`.
"832-cha-recursive",
+ # 837-deopt: Dependency on `libarttest`.
+ "837-deopt",
+])
+
+known_failing_on_hwasan_tests = frozenset([
+ "BootImageProfileTest", # b/232012605
+ "CtsJdwpTestCases", # times out
+ "art_standalone_compiler_tests", # b/230392320
+ "art_standalone_dex2oat_tests", # b/228881278
])
# ART gtests that do not need root access to the device.
art_gtest_user_module_names = [
"art_libnativebridge_cts_tests",
+ "art_standalone_artd_tests",
"art_standalone_cmdline_tests",
"art_standalone_compiler_tests",
# Temporarily disable this test as it is failing with ART module prebuilts (see b/243510263).
@@ -234,6 +244,7 @@
art_gtest_eng_only_module_names = [
"art_standalone_dexoptanalyzer_tests",
"art_standalone_profman_tests",
+ "libnativeloader_e2e_tests",
]
# All supported ART gtests.
@@ -327,13 +338,12 @@
def is_blank(line):
return re.match("^\\s*$", line)
- # Is `line` a `set -e` statement?
- def is_set_e(line):
- return re.match("^\\s*set -e\\s*", line)
+ def is_import(line):
+ return re.match("^\\s*from .* import .*", line)
# Should `line` be kept in the canonized build script?
def keep_line(line):
- return not (is_comment(line) or is_blank(line) or is_set_e(line))
+ return not (is_comment(line) or is_blank(line) or is_import(line))
with open(build_file, "r") as f:
lines = f.readlines()
@@ -343,7 +353,7 @@
def can_ignore_build_script(self, build_file):
build_script = self.canonize_build_script(build_file)
if len(build_script) == 1:
- if build_script[0] == "./default-build \"$@\" --experimental var-handles\n":
+ if build_script[0] == 'build_run_test(experimental="var-handles")\n':
# Soong builds JARs with VarHandle support by default (i.e. by
# using an API level greater or equal to 28), so we can ignore
# build scripts that just request support for this feature.
@@ -357,8 +367,11 @@
# Skip tests with non-default build rules, unless these build
# rules can be safely ignored.
- if os.path.isfile(os.path.join(run_test_path, "build")):
- if not self.can_ignore_build_script(os.path.join(run_test_path, "build")):
+ if (os.path.isfile(os.path.join(run_test_path, "generate-sources")) or
+ os.path.isfile(os.path.join(run_test_path, "javac_wrapper.sh"))):
+ return False
+ if os.path.isfile(os.path.join(run_test_path, "build.py")):
+ if not self.can_ignore_build_script(os.path.join(run_test_path, "build.py")):
return False
# Skip tests with sources outside the `src` directory.
for subdir in ["jasmin",
@@ -535,14 +548,17 @@
# Presubmits.
other_presubmit_tests = [
- "CtsJdwpTestCases",
- "BootImageProfileTest",
"ArtServiceTests",
+ "BootImageProfileTest",
"ComposHostTestCases",
+ "CtsJdwpTestCases",
+ "art-apex-update-rollback",
"art_standalone_dexpreopt_tests",
]
presubmit_tests = other_presubmit_tests + run_test_module_names + art_gtest_module_names
presubmit_tests_dict = [{"name": t} for t in presubmit_tests]
+ hwasan_presubmit_tests_dict = [{"name": t} for t in presubmit_tests
+ if t not in known_failing_on_hwasan_tests]
# Use an `OrderedDict` container to preserve the order in which items are inserted.
# Do not produce an entry for a test group if it is empty.
@@ -552,6 +568,7 @@
in [
("mainline-presubmit", mainline_presubmit_tests_dict),
("presubmit", presubmit_tests_dict),
+ ("hwasan-presubmit", hwasan_presubmit_tests_dict),
]
if test_group_dict
])
@@ -738,18 +755,11 @@
mts_test_shards = []
- # ART test (gtest & run-test) shard(s).
- # TODO: Also handle the case of gtests requiring root access to the device
- # (`art_gtest_eng_only_module_names`).
+ # ART run-tests shard(s).
art_run_test_module_names = [ART_RUN_TEST_MODULE_NAME_PREFIX + t for t in art_run_tests]
art_run_test_shards = split_list(art_run_test_module_names, NUM_MTS_ART_RUN_TEST_SHARDS)
for i in range(len(art_run_test_shards)):
art_tests_shard_i_tests = art_run_test_shards[i]
- # Append ART gtests to the last ART run-test shard for now.
- # If needed, consider moving them to their own shard to increase
- # the parallelization of code coverage runs.
- if i + 1 == len(art_run_test_shards):
- art_tests_shard_i_tests.extend(art_gtest_mts_user_module_names)
art_tests_shard_i = self.create_mts_test_shard(
"ART run-tests", art_tests_shard_i_tests, i, 2020,
["TODO(rpl): Find a way to express this list in a more concise fashion."])
@@ -777,6 +787,15 @@
other_cts_libcore_tests_shard_num, 2021)
mts_test_shards.append(other_cts_libcore_tests_shard)
+ # ART gtests shard.
+ # TODO: Also handle the case of gtests requiring root access to the device
+ # (`art_gtest_eng_only_module_names`).
+ art_gtests_shard_num = len(mts_test_shards)
+ art_gtests_shard_tests = art_gtest_mts_user_module_names
+ art_gtests_shard = self.create_mts_test_shard(
+ "ART gtests", art_gtests_shard_tests, art_gtests_shard_num, 2022)
+ mts_test_shards.append(art_gtests_shard)
+
for s in mts_test_shards:
s.regen_test_plan_file()
s.regen_test_list_file()
diff --git a/tools/Android.bp b/tools/Android.bp
index b7f5c1b..7ffa672 100644
--- a/tools/Android.bp
+++ b/tools/Android.bp
@@ -97,23 +97,3 @@
},
},
}
-
-python_binary_host {
- name: "art-run-test-checker",
- srcs: [
- "checker/**/*.py",
- ],
- main: "checker/checker.py",
- version: {
- py2: {
- enabled: false,
- },
- py3: {
- enabled: true,
- },
- },
- test_suites: [
- "general-tests",
- "mts-art",
- ],
-}
diff --git a/tools/PresubmitJsonLinter.java b/tools/PresubmitJsonLinter.java
new file mode 100644
index 0000000..334d200
--- /dev/null
+++ b/tools/PresubmitJsonLinter.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 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 com.google.gson.stream.JsonReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * Pre upload hook that ensures art-buildbot expectation files (files under //art/tools ending with
+ * "_failures.txt", e.g. //art/tools/libcore_failures.txt) are well-formed json files.
+ *
+ * It makes basic validation of the keys but does not cover all the cases. Parser structure is
+ * based on external/vogar/src/vogar/ExpectationStore.java.
+ *
+ * Hook is set up in //art/PREUPLOAD.cfg See also //tools/repohooks/README.md
+ */
+class PresubmitJsonLinter {
+
+ private static final int FLAGS = Pattern.MULTILINE | Pattern.DOTALL;
+ private static final Set<String> RESULTS = new HashSet<>();
+
+ static {
+ RESULTS.addAll(List.of(
+ "UNSUPPORTED",
+ "COMPILE_FAILED",
+ "EXEC_FAILED",
+ "EXEC_TIMEOUT",
+ "ERROR",
+ "SUCCESS"
+ ));
+ }
+
+ public static void main(String[] args) {
+ for (String arg : args) {
+ info("Checking " + arg);
+ checkExpectationFile(arg);
+ }
+ }
+
+ private static void info(String message) {
+ System.err.println(message);
+ }
+
+ private static void error(String message) {
+ System.err.println(message);
+ System.exit(1);
+ }
+
+ private static void checkExpectationFile(String arg) {
+ JsonReader reader;
+ try {
+ reader = new JsonReader(new FileReader(arg));
+ } catch (FileNotFoundException e) {
+ error("File '" + arg + "' is not found");
+ return;
+ }
+ reader.setLenient(true);
+ try {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ readExpectation(reader);
+ }
+ reader.endArray();
+ } catch (IOException e) {
+ error("Malformed json: " + reader);
+ }
+ }
+
+ private static void readExpectation(JsonReader reader) throws IOException {
+ Set<String> names = new LinkedHashSet<String>();
+ Set<String> tags = new LinkedHashSet<String>();
+ boolean readResult = false;
+ boolean readDescription = false;
+
+ reader.beginObject();
+ while (reader.hasNext()) {
+ String name = reader.nextName();
+ switch (name) {
+ case "result":
+ String result = reader.nextString();
+ if (!RESULTS.contains(result)) {
+ error("Invalid 'result' value: '" + result +
+ "'. Expected one of " + String.join(", ", RESULTS) +
+ ". " + reader);
+ }
+ readResult = true;
+ break;
+ case "substring": {
+ try {
+ Pattern.compile(
+ ".*" + Pattern.quote(reader.nextString()) + ".*", FLAGS);
+ } catch (PatternSyntaxException e) {
+ error("Malformed 'substring' value: " + reader);
+ }
+ }
+ case "pattern": {
+ try {
+ Pattern.compile(reader.nextString(), FLAGS);
+ } catch (PatternSyntaxException e) {
+ error("Malformed 'pattern' value: " + reader);
+ }
+ break;
+ }
+ case "failure":
+ names.add(reader.nextString());
+ break;
+ case "description":
+ reader.nextString();
+ readDescription = true;
+ break;
+ case "name":
+ names.add(reader.nextString());
+ break;
+ case "names":
+ readStrings(reader, names);
+ break;
+ case "tags":
+ readStrings(reader, tags);
+ break;
+ case "bug":
+ reader.nextLong();
+ break;
+ case "modes":
+ readModes(reader);
+ break;
+ case "modes_variants":
+ readModesAndVariants(reader);
+ break;
+ default:
+ error("Unknown key '" + name + "' in expectations file");
+ reader.skipValue();
+ break;
+ }
+ }
+ reader.endObject();
+
+ if (names.isEmpty()) {
+ error("Missing 'name' or 'failure' key in " + reader);
+ }
+ if (!readResult) {
+ error("Missing 'result' key in " + reader);
+ }
+ if (!readDescription) {
+ error("Missing 'description' key in " + reader);
+ }
+ }
+
+ private static void readStrings(JsonReader reader, Set<String> output) throws IOException {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ output.add(reader.nextString());
+ }
+ reader.endArray();
+ }
+
+ private static void readModes(JsonReader reader) throws IOException {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ reader.nextString();
+ }
+ reader.endArray();
+ }
+
+ /**
+ * Expected format: mode_variants: [["host", "X32"], ["host", "X64"]]
+ */
+ private static void readModesAndVariants(JsonReader reader) throws IOException {
+ reader.beginArray();
+ while (reader.hasNext()) {
+ reader.beginArray();
+ reader.nextString();
+ reader.nextString();
+ reader.endArray();
+ }
+ reader.endArray();
+ }
+}
\ No newline at end of file
diff --git a/tools/ahat/Android.bp b/tools/ahat/Android.bp
index affa2e0..5f6ba81 100644
--- a/tools/ahat/Android.bp
+++ b/tools/ahat/Android.bp
@@ -27,7 +27,7 @@
visibility: [
"//libcore/metrictests/memory/host",
],
- wrapper: "ahat",
+ wrapper: "ahat.sh",
srcs: ["src/main/**/*.java"],
manifest: "etc/ahat.mf",
java_resources: ["etc/style.css"],
diff --git a/tools/ahat/ahat b/tools/ahat/ahat.sh
similarity index 100%
rename from tools/ahat/ahat
rename to tools/ahat/ahat.sh
diff --git a/tools/ahat/src/test-dump/Main.java b/tools/ahat/src/test-dump/Main.java
index 2e29076..711d662 100644
--- a/tools/ahat/src/test-dump/Main.java
+++ b/tools/ahat/src/test-dump/Main.java
@@ -43,6 +43,10 @@
// Allocate the instance of DumpedStuff.
stuff = new DumpedStuff(baseline);
+ // Preemptively garbage collect to avoid an inopportune GC triggering
+ // after this.
+ Runtime.getRuntime().gc();
+
// Create a bunch of unreachable objects pointing to basicString for the
// reverseReferencesAreNotUnreachable test
for (int i = 0; i < 100; i++) {
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 376122b..1f29030 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -224,6 +224,7 @@
@Test
public void reachability() throws IOException {
TestDump dump = TestDump.getTestDump();
+ // We were careful to avoid GC before dumping, so nothing here should be null.
AhatInstance strong1 = dump.getDumpedAhatInstance("reachabilityReferenceChain");
AhatInstance soft1 = strong1.getField("referent").asAhatInstance();
AhatInstance strong2 = soft1.getField("referent").asAhatInstance();
diff --git a/tools/build_linux_bionic.sh b/tools/build_linux_bionic.sh
index 8992512..bbe71b6 100755
--- a/tools/build_linux_bionic.sh
+++ b/tools/build_linux_bionic.sh
@@ -16,83 +16,38 @@
# This will build a target using linux_bionic. It can be called with normal make
# flags.
-#
-# TODO This runs a 'm clean' prior to building the targets in order to ensure
-# that obsolete kati files don't mess up the build.
-if [[ -z $ANDROID_BUILD_TOP ]]; then
- pushd .
-else
- pushd $ANDROID_BUILD_TOP
-fi
+set -e
if [ ! -d art ]; then
echo "Script needs to be run at the root of the android tree"
exit 1
fi
+export TARGET_PRODUCT=linux_bionic
+
+# Avoid Soong error about invalid dependencies on disabled libLLVM_android,
+# which we get due to the --soong-only mode. (Another variant is to set
+# SOONG_ALLOW_MISSING_DEPENDENCIES).
+export FORCE_BUILD_LLVM_COMPONENTS=true
+
# TODO(b/194433871): Set MODULE_BUILD_FROM_SOURCE to disable prebuilt modules,
# which Soong otherwise can create duplicate install rules for in --soong-only
# mode.
-soong_args="MODULE_BUILD_FROM_SOURCE=true"
+export MODULE_BUILD_FROM_SOURCE=true
# Switch the build system to unbundled mode in the reduced manifest branch.
if [ ! -d frameworks/base ]; then
- soong_args="$soong_args TARGET_BUILD_UNBUNDLED=true"
+ export TARGET_BUILD_UNBUNDLED=true
fi
-source build/envsetup.sh >&/dev/null # for get_build_var
-# Soong needs a bunch of variables set and will not run if they are missing.
-# The default values of these variables is only contained in make, so use
-# nothing to create the variables then remove all the other artifacts.
-# Lunch since it seems we cannot find the build-number otherwise.
-lunch aosp_x86-eng
-build/soong/soong_ui.bash --make-mode $soong_args nothing
+vars="$(build/soong/soong_ui.bash --dumpvars-mode --vars="OUT_DIR BUILD_NUMBER")"
+# Assign to a variable and eval that, since bash ignores any error status from
+# the command substitution if it's directly on the eval line.
+eval $vars
-if [ $? != 0 ]; then
- exit 1
-fi
+# This file is currently not created in --soong-only mode, but some build
+# targets depend on it.
+printf %s "${BUILD_NUMBER}" > ${OUT_DIR}/soong/build_number.txt
-out_dir=$(get_build_var OUT_DIR)
-host_out=$(get_build_var HOST_OUT)
-
-# TODO(b/31559095) Figure out a better way to do this.
-#
-# There is no good way to force soong to generate host-bionic builds currently
-# so this is a hacky workaround.
-tmp_soong_var=$(mktemp --tmpdir soong.variables.bak.XXXXXX)
-tmp_build_number=$(cat ${out_dir}/soong/build_number.txt)
-
-cat $out_dir/soong/soong.variables > ${tmp_soong_var}
-
-# See comment above about b/123645297 for why we cannot just do m clean. Clear
-# out all files except for intermediates and installed files and dexpreopt.config.
-find $out_dir/ -maxdepth 1 -mindepth 1 \
- -not -name soong \
- -not -name host \
- -not -name target | xargs -I '{}' rm -rf '{}'
-find $out_dir/soong/ -maxdepth 1 -mindepth 1 \
- -not -name .intermediates \
- -not -name host \
- -not -name dexpreopt.config \
- -not -name target | xargs -I '{}' rm -rf '{}'
-
-python3 <<END - ${tmp_soong_var} ${out_dir}/soong/soong.variables
-import json
-import sys
-x = json.load(open(sys.argv[1]))
-x['Allow_missing_dependencies'] = True
-x['HostArch'] = 'x86_64'
-x['CrossHost'] = 'linux_bionic'
-x['CrossHostArch'] = 'x86_64'
-if 'CrossHostSecondaryArch' in x:
- del x['CrossHostSecondaryArch']
-json.dump(x, open(sys.argv[2], mode='w'))
-END
-
-rm $tmp_soong_var
-
-# Write a new build-number
-echo ${tmp_build_number}_SOONG_ONLY_BUILD > ${out_dir}/soong/build_number.txt
-
-build/soong/soong_ui.bash --make-mode --skip-config --soong-only $soong_args $@
+build/soong/soong_ui.bash --make-mode --soong-only "$@"
diff --git a/tools/build_linux_bionic_tests.sh b/tools/build_linux_bionic_tests.sh
index 7379e9a..0470d6d 100755
--- a/tools/build_linux_bionic_tests.sh
+++ b/tools/build_linux_bionic_tests.sh
@@ -14,64 +14,36 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-
-if [[ -z $ANDROID_BUILD_TOP ]]; then
- pushd .
-else
- pushd $ANDROID_BUILD_TOP
-fi
+set -e
if [ ! -d art ]; then
echo "Script needs to be run at the root of the android tree"
exit 1
fi
-soong_args=""
-
# Switch the build system to unbundled mode in the reduced manifest branch.
if [ ! -d frameworks/base ]; then
- soong_args="$soong_args TARGET_BUILD_UNBUNDLED=true"
+ export TARGET_BUILD_UNBUNDLED=true
fi
-source build/envsetup.sh >&/dev/null # for get_build_var
-
-out_dir=$(get_build_var OUT_DIR)
-host_out=$(get_build_var HOST_OUT)
-
-# TODO(b/31559095) Figure out a better way to do this.
-#
-# There is no good way to force soong to generate host-bionic builds currently
-# so this is a hacky workaround.
+vars="$(build/soong/soong_ui.bash --dumpvars-mode --vars="OUT_DIR HOST_OUT")"
+# Assign to a variable and eval that, since bash ignores any error status from
+# the command substitution if it's directly on the eval line.
+eval $vars
# First build all the targets still in .mk files (also build normal glibc host
# targets so we know what's needed to run the tests).
-build/soong/soong_ui.bash --make-mode $soong_args "$@" test-art-host-run-test-dependencies build-art-host-tests
-if [ $? != 0 ]; then
- exit 1
-fi
+build/soong/soong_ui.bash --make-mode "$@" test-art-host-run-test-dependencies build-art-host-tests
-tmp_soong_var=$(mktemp --tmpdir soong.variables.bak.XXXXXX)
+# Next build the Linux host Bionic targets in --soong-only mode.
+export TARGET_PRODUCT=linux_bionic
-echo "Saving soong.variables to " $tmp_soong_var
-cat $out_dir/soong/soong.variables > ${tmp_soong_var}
-python3 <<END - ${tmp_soong_var} ${out_dir}/soong/soong.variables
-import json
-import sys
-x = json.load(open(sys.argv[1]))
-x['Allow_missing_dependencies'] = True
-x['HostArch'] = 'x86_64'
-x['CrossHost'] = 'linux_bionic'
-x['CrossHostArch'] = 'x86_64'
-if 'CrossHostSecondaryArch' in x:
- del x['CrossHostSecondaryArch']
-json.dump(x, open(sys.argv[2], mode='w'))
-END
-if [ $? != 0 ]; then
- mv $tmp_soong_var $out_dir/soong/soong.variables
- exit 2
-fi
+# Avoid Soong error about invalid dependencies on disabled libLLVM_android,
+# which we get due to the --soong-only mode. (Another variant is to set
+# SOONG_ALLOW_MISSING_DEPENDENCIES).
+export FORCE_BUILD_LLVM_COMPONENTS=true
-soong_out=$out_dir/soong/host/linux_bionic-x86
+soong_out=$OUT_DIR/soong/host/linux_bionic-x86
declare -a bionic_targets
# These are the binaries actually used in tests. Since some of the files are
# java targets or 32 bit we cannot just do the same find for the bin files.
@@ -89,24 +61,10 @@
$soong_out/bin/hprof-conv
$soong_out/bin/signal_dumper
$soong_out/lib64/libclang_rt.ubsan_standalone-x86_64-android.so
- $(find $host_out/apex/com.android.art.host.zipapex -type f | sed "s:$host_out:$soong_out:g")
- $(find $host_out/lib64 -type f | sed "s:$host_out:$soong_out:g")
- $(find $host_out/nativetest64 -type f | sed "s:$host_out:$soong_out:g"))
+ $(find $HOST_OUT/apex/com.android.art.host.zipapex -type f | sed "s:$HOST_OUT:$soong_out:g")
+ $(find $HOST_OUT/lib64 -type f | sed "s:$HOST_OUT:$soong_out:g")
+ $(find $HOST_OUT/nativetest64 -type f | sed "s:$HOST_OUT:$soong_out:g"))
echo building ${bionic_targets[*]}
-build/soong/soong_ui.bash --make-mode --skip-config --soong-only $soong_args "$@" ${bionic_targets[*]}
-ret=$?
-
-mv $tmp_soong_var $out_dir/soong/soong.variables
-
-# Having built with host-bionic confuses soong somewhat by making it think the
-# linux_bionic targets are needed for art phony targets like
-# test-art-host-run-test-dependencies. To work around this blow away all
-# ninja files in OUT_DIR. The build system is smart enough to not need to
-# rebuild stuff so this should be fine.
-rm -f $OUT_DIR/*.ninja $OUT_DIR/soong/*.ninja
-
-popd
-
-exit $ret
+build/soong/soong_ui.bash --make-mode --soong-only "$@" ${bionic_targets[*]}
diff --git a/tools/buildbot-build.sh b/tools/buildbot-build.sh
index 48fc004..59164ee 100755
--- a/tools/buildbot-build.sh
+++ b/tools/buildbot-build.sh
@@ -69,6 +69,9 @@
elif [[ "$1" == "--showcommands" ]]; then
showcommands="showcommands"
shift
+ elif [[ "$1" == "--dist" ]]; then
+ common_targets="$common_targets dist"
+ shift
elif [[ "$1" == "" ]]; then
break
else
@@ -120,13 +123,12 @@
# Indirect dependencies in the platform, e.g. through heapprofd_client_api.
# These are built to go into system/lib(64) to be part of the system linker
# namespace.
- make_command+=" libbacktrace libnetd_client-target libprocinfo libtombstoned_client libunwindstack"
+ make_command+=" libnetd_client-target libprocinfo libtombstoned_client libunwindstack"
# Stubs for other APEX SDKs, for use by vogar. Referenced from DEVICE_JARS in
# external/vogar/src/vogar/ModeId.java.
# Note these go into out/target/common/obj/JAVA_LIBRARIES which isn't removed
# by "m installclean".
make_command+=" i18n.module.public.api.stubs conscrypt.module.public.api.stubs"
- make_command+=" ${ANDROID_PRODUCT_OUT#"${ANDROID_BUILD_TOP}/"}/system/etc/public.libraries.txt"
# Targets required to generate a linker configuration for device within the
# chroot environment. The *.libraries.txt targets are required by
# the source linkerconfig but not included in the prebuilt one.
@@ -183,14 +185,19 @@
fi
done
- # Replace stub libraries with implemenation libraries: because we do chroot
+ # Replace stub libraries with implementation libraries: because we do chroot
# testing, we need to install an implementation of the libraries (and cannot
# rely on the one already installed on the device, if the device is post R and
# has it).
implementation_libs=(
"heapprofd_client_api.so"
+ "libandroid_runtime_lazy.so"
"libartpalette-system.so"
- "liblog.so"
+ "libbinder.so"
+ "libbinder_ndk.so"
+ "libcutils.so"
+ "libutils.so"
+ "libvndksupport.so"
)
if [ -d prebuilts/runtime/mainline/platform/impl ]; then
if [[ $TARGET_ARCH = arm* ]]; then
@@ -299,6 +306,11 @@
mkdir -p $linkerconfig_root/system
cp -r $ANDROID_PRODUCT_OUT/system/etc $linkerconfig_root/system
+ # Use our smaller public.libraries.txt that contains only the public libraries
+ # pushed to the chroot directory.
+ cp $ANDROID_BUILD_TOP/art/tools/public.libraries.buildbot.txt \
+ $linkerconfig_root/system/etc/public.libraries.txt
+
# For linkerconfig to pick up the APEXes correctly we need to make them
# available in $linkerconfig_root/apex.
mkdir -p $linkerconfig_root/apex
diff --git a/tools/buildbot-setup-device.sh b/tools/buildbot-setup-device.sh
index ad2c59c..1cad3e3 100755
--- a/tools/buildbot-setup-device.sh
+++ b/tools/buildbot-setup-device.sh
@@ -173,6 +173,8 @@
|| adb shell mount -o bind /dev "$ART_TEST_CHROOT/dev"
adb shell mount | grep -q "^devpts on $ART_TEST_CHROOT/dev/pts type devpts " \
|| adb shell mount -o bind /dev/pts "$ART_TEST_CHROOT/dev/pts"
+ adb shell mount | grep -q " on $ART_TEST_CHROOT/dev/cpuset type cgroup " \
+ || adb shell mount -o bind /dev/cpuset "$ART_TEST_CHROOT/dev/cpuset"
# Create /apex directory in chroot.
adb shell mkdir -p "$ART_TEST_CHROOT/apex"
diff --git a/tools/buildbot-sync.sh b/tools/buildbot-sync.sh
index 28dab0c..5970cc7 100755
--- a/tools/buildbot-sync.sh
+++ b/tools/buildbot-sync.sh
@@ -111,6 +111,26 @@
activate_apex com.android.conscrypt
activate_apex com.android.os.statsd
+# Replace the crash dump binary with the one on the device. This is because
+# the tombstoned server running is the one on the device.
+crash_dump_locations=(
+ # Location for Q+ devices.
+ "/apex/com.android.runtime/bin"
+ # Location on devices prior to Q.
+ "/system/bin/"
+)
+
+for b in 32 64; do
+ for crash_dump_location in ${crash_dump_locations[@]}; do
+ crash_dump_path="$crash_dump_location/crash_dump$b"
+ if adb shell test -x "$crash_dump_path"; then
+ msginfo "Copying $crash_dump_path from device to chroot"
+ adb shell cp "$crash_dump_path" "$ART_TEST_CHROOT/$crash_dump_path"
+ break
+ fi
+ done
+done
+
# Generate primary boot images on device for testing.
for b in {32,64}; do
basename="generate-boot-image$b"
diff --git a/tools/buildbot-teardown-device.sh b/tools/buildbot-teardown-device.sh
index 927e3c5..156b4f1 100755
--- a/tools/buildbot-teardown-device.sh
+++ b/tools/buildbot-teardown-device.sh
@@ -79,7 +79,7 @@
local remove_dir=$3
local dir="$ART_TEST_CHROOT/$dir_in_chroot"
adb shell test -d "$dir" \
- && adb shell mount | grep -q "^$fstype on $dir type $fstype " \
+ && adb shell mount | grep -q " on $dir type $fstype " \
&& if adb shell umount "$dir"; then
$remove_dir && adb shell rmdir "$dir"
else
@@ -95,6 +95,7 @@
adb shell rm -rf "$ART_TEST_CHROOT/apex"
# Remove /dev from chroot.
+ remove_filesystem_from_chroot dev/cpuset cgroup false
remove_filesystem_from_chroot dev/pts devpts false
remove_filesystem_from_chroot dev tmpfs true
diff --git a/tools/check_presubmit_json_expectations.sh b/tools/check_presubmit_json_expectations.sh
new file mode 100755
index 0000000..ecb1e3e
--- /dev/null
+++ b/tools/check_presubmit_json_expectations.sh
@@ -0,0 +1,50 @@
+#!/bin/bash
+#
+# Copyright (C) 2022 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.
+
+set -e
+
+REPO_ROOT="$1"
+
+FILES_TO_CHECK=()
+for i in "${@:2}"; do
+ if [[ $i == *_failures.txt ]]; then
+ FILES_TO_CHECK+=($i)
+ fi
+done
+
+# if no libcore_*_failures.txt files were changed
+if [ ${#FILES_TO_CHECK[@]} -eq 0 ]; then
+ exit 0
+fi
+
+TMP_DIR=`mktemp -d`
+# check if tmp dir was created
+if [[ ! "$TMP_DIR" || ! -d "$TMP_DIR" ]]; then
+ echo "Could not create temp dir"
+ exit 1
+fi
+
+function cleanup {
+ rm -rf "$TMP_DIR"
+}
+
+# register the cleanup function to be called on the EXIT signal
+trap cleanup EXIT
+
+GSON_JAR="${REPO_ROOT}/external/caliper/lib/gson-2.2.2.jar"
+
+javac --class-path "$GSON_JAR" "${REPO_ROOT}/art/tools/PresubmitJsonLinter.java" -d "$TMP_DIR"
+java --class-path "$TMP_DIR:$GSON_JAR" PresubmitJsonLinter "${FILES_TO_CHECK[@]}"
diff --git a/libartservice/tests/Android.bp b/tools/checker/Android.bp
similarity index 66%
rename from libartservice/tests/Android.bp
rename to tools/checker/Android.bp
index dc110a1..4eafe85 100644
--- a/libartservice/tests/Android.bp
+++ b/tools/checker/Android.bp
@@ -1,5 +1,5 @@
//
-// Copyright (C) 2021 The Android Open Source Project
+// Copyright (C) 2022 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.
@@ -14,10 +14,6 @@
// limitations under the License.
//
-//########################################################################
-// Build ArtServiceTests package
-//########################################################################
-
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
@@ -27,18 +23,23 @@
default_applicable_licenses: ["art_license"],
}
-java_test {
- name: "ArtServiceTests",
-
- // Include all test java files.
+python_binary_host {
+ name: "art-run-test-checker",
srcs: [
- "src/**/*.java",
+ "**/*.py",
],
-
- static_libs: [
- "androidx.test.runner",
- "service-art.impl",
+ main: "checker.py",
+ version: {
+ py2: {
+ enabled: false,
+ },
+ py3: {
+ enabled: true,
+ embedded_launcher: true,
+ },
+ },
+ test_suites: [
+ "general-tests",
+ "mts-art",
],
-
- test_suites: ["general-tests"],
}
diff --git a/tools/cpp-define-generator/globals.def b/tools/cpp-define-generator/globals.def
index 2572ea6..459e5a8 100644
--- a/tools/cpp-define-generator/globals.def
+++ b/tools/cpp-define-generator/globals.def
@@ -28,6 +28,7 @@
#include "mirror/object_reference.h"
#include "runtime_globals.h"
#include "stack.h"
+#include "entrypoints/quick/callee_save_frame.h"
#endif
ASM_DEFINE(ACCESS_FLAGS_METHOD_IS_NATIVE,
@@ -82,3 +83,11 @@
std::memory_order_relaxed)
ASM_DEFINE(STACK_OVERFLOW_RESERVED_BYTES,
GetStackOverflowReservedBytes(art::kRuntimeISA))
+ASM_DEFINE(CALLEE_SAVE_EVERYTHING_NUM_CORE_SPILLS,
+ art::POPCOUNT(art::RuntimeCalleeSaveFrame::GetCoreSpills(
+ art::CalleeSaveType::kSaveEverything)))
+ASM_DEFINE(TAGGED_JNI_SP_MASK, art::ManagedStack::kTaggedJniSpMask)
+ASM_DEFINE(TAGGED_JNI_SP_MASK_TOGGLED32,
+ ~static_cast<uint32_t>(art::ManagedStack::kTaggedJniSpMask))
+ASM_DEFINE(TAGGED_JNI_SP_MASK_TOGGLED64,
+ ~static_cast<uint64_t>(art::ManagedStack::kTaggedJniSpMask))
diff --git a/tools/cpp-define-generator/lockword.def b/tools/cpp-define-generator/lockword.def
index a170c15..5494d59 100644
--- a/tools/cpp-define-generator/lockword.def
+++ b/tools/cpp-define-generator/lockword.def
@@ -30,10 +30,8 @@
art::LockWord::kMarkBitStateMaskShifted)
ASM_DEFINE(LOCK_WORD_MARK_BIT_SHIFT,
art::LockWord::kMarkBitStateShift)
-ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_MASK,
+ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_MASK_SHIFTED,
art::LockWord::kReadBarrierStateMaskShifted)
-ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_MASK_TOGGLED,
- art::LockWord::kReadBarrierStateMaskShiftedToggled)
ASM_DEFINE(LOCK_WORD_READ_BARRIER_STATE_SHIFT,
art::LockWord::kReadBarrierStateShift)
ASM_DEFINE(LOCK_WORD_STATE_FORWARDING_ADDRESS,
diff --git a/tools/cpp-define-generator/mirror_class.def b/tools/cpp-define-generator/mirror_class.def
index 8cfd54e..af396bf 100644
--- a/tools/cpp-define-generator/mirror_class.def
+++ b/tools/cpp-define-generator/mirror_class.def
@@ -16,6 +16,7 @@
#if ASM_DEFINE_INCLUDE_DEPENDENCIES
#include "mirror/class.h"
+#include "subtype_check.h"
#endif
ASM_DEFINE(MIRROR_CLASS_ACCESS_FLAGS_OFFSET,
@@ -49,3 +50,14 @@
ASM_DEFINE(MIRROR_CLASS_IS_INTERFACE_FLAG, art::kAccInterface)
ASM_DEFINE(MIRROR_CLASS_IS_INTERFACE_FLAG_BIT,
art::WhichPowerOf2(art::kAccInterface))
+ASM_DEFINE(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_OFFSET,
+ art::mirror::Class::StatusOffset().SizeValue() +
+ (art::SubtypeCheckBits::BitStructSizeOf() / art::kBitsPerByte))
+ASM_DEFINE(MIRROR_CLASS_IS_VISIBLY_INITIALIZED_VALUE,
+ art::enum_cast<uint32_t>(art::ClassStatus::kVisiblyInitialized) <<
+ (art::SubtypeCheckBits::BitStructSizeOf() % art::kBitsPerByte))
+ASM_DEFINE(MIRROR_CLASS_IS_INITIALIZING_VALUE,
+ art::enum_cast<uint32_t>(art::ClassStatus::kInitializing) <<
+ (art::SubtypeCheckBits::BitStructSizeOf() % art::kBitsPerByte))
+ASM_DEFINE(MIRROR_CLASS_CLINIT_THREAD_ID_OFFSET,
+ art::mirror::Class::ClinitThreadIdOffset().Int32Value())
diff --git a/tools/cpp-define-generator/runtime.def b/tools/cpp-define-generator/runtime.def
index 2a2e303..4407e0c 100644
--- a/tools/cpp-define-generator/runtime.def
+++ b/tools/cpp-define-generator/runtime.def
@@ -30,3 +30,9 @@
art::Runtime::GetCalleeSaveMethodOffset(art::CalleeSaveType::kSaveRefsAndArgs))
ASM_DEFINE(RUNTIME_SAVE_REFS_ONLY_METHOD_OFFSET,
art::Runtime::GetCalleeSaveMethodOffset(art::CalleeSaveType::kSaveRefsOnly))
+ASM_DEFINE(RUNTIME_INSTRUMENTATION_OFFSET, art::Runtime::GetInstrumentationOffset().Int32Value())
+ASM_DEFINE(INSTRUMENTATION_STUBS_INSTALLED_OFFSET,
+ art::instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value())
+ASM_DEFINE(INSTRUMENTATION_STUBS_INSTALLED_OFFSET_FROM_RUNTIME_INSTANCE,
+ art::Runtime::GetInstrumentationOffset().Int32Value() +
+ art::instrumentation::Instrumentation::NeedsEntryExitHooksOffset().Int32Value())
diff --git a/tools/cpp-define-generator/thread.def b/tools/cpp-define-generator/thread.def
index bae9200..97033fc 100644
--- a/tools/cpp-define-generator/thread.def
+++ b/tools/cpp-define-generator/thread.def
@@ -37,6 +37,8 @@
(art::WhichPowerOf2(sizeof(art::InterpreterCache::Entry)) - 2))
ASM_DEFINE(THREAD_IS_GC_MARKING_OFFSET,
art::Thread::IsGcMarkingOffset<art::kRuntimePointerSize>().Int32Value())
+ASM_DEFINE(THREAD_DEOPT_CHECK_REQUIRED_OFFSET,
+ art::Thread::DeoptCheckRequiredOffset<art::kRuntimePointerSize>().Int32Value())
ASM_DEFINE(THREAD_LOCAL_ALLOC_STACK_END_OFFSET,
art::Thread::ThreadLocalAllocStackEndOffset<art::kRuntimePointerSize>().Int32Value())
ASM_DEFINE(THREAD_LOCAL_ALLOC_STACK_TOP_OFFSET,
@@ -69,3 +71,5 @@
art::Thread::ReadBarrierMarkEntryPointsOffset<art::kRuntimePointerSize>(0))
ASM_DEFINE(THREAD_SHARED_METHOD_HOTNESS_OFFSET,
art::Thread::SharedMethodHotnessOffset<art::kRuntimePointerSize>().Int32Value())
+ASM_DEFINE(THREAD_TID_OFFSET,
+ art::Thread::TidOffset<art::kRuntimePointerSize>().Int32Value())
diff --git a/tools/dexfuzz/Android.bp b/tools/dexfuzz/Android.bp
index 02bda0e..083ecd7 100644
--- a/tools/dexfuzz/Android.bp
+++ b/tools/dexfuzz/Android.bp
@@ -33,6 +33,6 @@
// --- dexfuzz script ----------------
sh_binary_host {
name: "dexfuzz-script",
- src: "dexfuzz",
- filename_from_src: true,
+ src: "dexfuzz.sh",
+ filename: "dexfuzz",
}
diff --git a/tools/dexfuzz/dexfuzz b/tools/dexfuzz/dexfuzz.sh
similarity index 100%
rename from tools/dexfuzz/dexfuzz
rename to tools/dexfuzz/dexfuzz.sh
diff --git a/tools/dist_linux_bionic.sh b/tools/dist_linux_bionic.sh
index 4c7ba1c..f710310 100755
--- a/tools/dist_linux_bionic.sh
+++ b/tools/dist_linux_bionic.sh
@@ -19,12 +19,6 @@
# Builds the given targets using linux-bionic and moves the output files to the
# DIST_DIR. Takes normal make arguments.
-if [[ -z $ANDROID_BUILD_TOP ]]; then
- pushd .
-else
- pushd $ANDROID_BUILD_TOP
-fi
-
if [[ -z $DIST_DIR ]]; then
echo "DIST_DIR must be set!"
exit 1
@@ -35,10 +29,12 @@
exit 1
fi
-source build/envsetup.sh >&/dev/null # for get_build_var
-out_dir=$(get_build_var OUT_DIR)
+vars="$(build/soong/soong_ui.bash --dumpvars-mode --vars="OUT_DIR")"
+# Assign to a variable and eval that, since bash ignores any error status from
+# the command substitution if it's directly on the eval line.
+eval $vars
-./art/tools/build_linux_bionic.sh $@
+./art/tools/build_linux_bionic.sh "$@"
mkdir -p $DIST_DIR
-cp -R ${out_dir}/soong/host/* $DIST_DIR/
+cp -R ${OUT_DIR}/soong/host/* $DIST_DIR/
diff --git a/tools/dmtracedump/tracedump.cc b/tools/dmtracedump/tracedump.cc
index 3cb7374..ecae6c1 100644
--- a/tools/dmtracedump/tracedump.cc
+++ b/tools/dmtracedump/tracedump.cc
@@ -1490,10 +1490,8 @@
char classBuf[HTML_BUFSIZE], methodBuf[HTML_BUFSIZE];
char signatureBuf[HTML_BUFSIZE];
char anchor_buf[80];
- const char* anchor_close = "";
anchor_buf[0] = 0;
if (gOptions.outputHtml) {
- anchor_close = "</a>";
printf("<a name=\"inclusive\"></a>\n");
printf("<hr>\n");
outputNavigationBar();
diff --git a/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt b/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt
new file mode 100644
index 0000000..7e181a2
--- /dev/null
+++ b/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt
@@ -0,0 +1,18 @@
+/*
+ * This file contains expectations for ART's buildbot. The purpose of this file is
+ * to temporarily list failing tests and not break the bots.
+ *
+ * This file contains the expectations for the 'libjdwp-aot' and 'libjdwp-jit'
+ * test groups on the chromium buildbot running without read-barrier.
+ */
+[
+{
+ description: "Failing with userfaultfd GC",
+ result: EXEC_FAILED,
+ bug: 242181443,
+ names: [ "org.apache.harmony.jpda.tests.jdwp.ObjectReference_DisableCollectionTest#testDisableCollection001",
+ "org.apache.harmony.jpda.tests.jdwp.ObjectReference_IsCollectedTest#testIsCollected001",
+ "org.apache.harmony.jpda.tests.jdwp.ObjectReference_IsCollectedTest#testIsCollected_invalid",
+ "org.apache.harmony.jpda.tests.jdwp.ObjectReference_IsCollectedTest#testIsCollected_null" ]
+}
+]
diff --git a/tools/generate_cmake_lists.py b/tools/generate_cmake_lists.py
index b19c292..3fda003 100755
--- a/tools/generate_cmake_lists.py
+++ b/tools/generate_cmake_lists.py
@@ -32,6 +32,7 @@
(Also, exclude projects that you don't bother about. This will make
the indexing faster).
"""
+from __future__ import print_function
import sys
import os
@@ -47,7 +48,7 @@
path_to_top = os.path.realpath(path_to_top)
if not os.path.exists(os.path.join(path_to_top, 'build/envsetup.sh')):
- print path_to_top
+ print(path_to_top)
raise AssertionError("geneate_cmake_lists.py must be located inside an android source tree")
return path_to_top
diff --git a/tools/hiddenapi/hiddenapi_test.cc b/tools/hiddenapi/hiddenapi_test.cc
index 3a0e625..f408c66 100644
--- a/tools/hiddenapi/hiddenapi_test.cc
+++ b/tools/hiddenapi/hiddenapi_test.cc
@@ -143,7 +143,7 @@
std::map<std::string, std::string> flags;
for (std::string line; std::getline(ifs, line);) {
- std::size_t comma = line.find(",");
+ std::size_t comma = line.find(',');
if (comma == std::string::npos) {
flags.emplace(line, "");
} else {
diff --git a/tools/jvmti-agents/chain-agents/chainagents.cc b/tools/jvmti-agents/chain-agents/chainagents.cc
index 1242409..d272fc1 100644
--- a/tools/jvmti-agents/chain-agents/chainagents.cc
+++ b/tools/jvmti-agents/chain-agents/chainagents.cc
@@ -53,7 +53,7 @@
OnLoad,
};
-static std::pair<std::string, std::string> Split(std::string source, char delim) {
+static std::pair<std::string, std::string> Split(const std::string& source, char delim) {
std::string first(source.substr(0, source.find(delim)));
if (source.find(delim) == std::string::npos) {
return std::pair(first, "");
diff --git a/tools/jvmti-agents/field-counts/fieldcount.cc b/tools/jvmti-agents/field-counts/fieldcount.cc
index c31a973..5a4b00e 100644
--- a/tools/jvmti-agents/field-counts/fieldcount.cc
+++ b/tools/jvmti-agents/field-counts/fieldcount.cc
@@ -182,7 +182,7 @@
<< "\t" << "<ALL_TYPES>"
<< "\t" << obj_len
<< "\t" << total_size;
- for (auto sz : class_sizes) {
+ for (const std::pair<std::string, size_t> sz : class_sizes) {
size_t count = class_counts[sz.first];
LOG(INFO) << "\t" << field_class_name << "." << field_name << ":" << field_sig
<< "\t" << sz.first
diff --git a/tools/jvmti-agents/simple-force-redefine/forceredefine.cc b/tools/jvmti-agents/simple-force-redefine/forceredefine.cc
index 055fb8a..3474238 100644
--- a/tools/jvmti-agents/simple-force-redefine/forceredefine.cc
+++ b/tools/jvmti-agents/simple-force-redefine/forceredefine.cc
@@ -94,7 +94,7 @@
jvmtiEnv* jvmti_;
};
-static void Transform(std::shared_ptr<ir::DexFile> ir) {
+static void Transform(const std::shared_ptr<ir::DexFile>& ir) {
std::unique_ptr<ir::Builder> builder;
for (auto& method : ir->encoded_methods) {
// Do not look into abstract/bridge/native/synthetic methods.
diff --git a/tools/jvmti-agents/simple-profile/simple_profile.cc b/tools/jvmti-agents/simple-profile/simple_profile.cc
index 5ead97e..7161142 100644
--- a/tools/jvmti-agents/simple-profile/simple_profile.cc
+++ b/tools/jvmti-agents/simple-profile/simple_profile.cc
@@ -26,6 +26,7 @@
#include <sstream>
#include <string>
#include <unordered_map>
+#include <utility>
#include <vector>
#include "android-base/unique_fd.h"
@@ -55,7 +56,7 @@
SimpleProfileData(
jvmtiEnv* env, std::string out_fd_name, int fd, bool dump_on_shutdown, bool dump_on_main_stop)
: dump_id_(0),
- out_fd_name_(out_fd_name),
+ out_fd_name_(std::move(out_fd_name)),
out_fd_(fd),
shutdown_(false),
dump_on_shutdown_(dump_on_shutdown || dump_on_main_stop),
@@ -79,7 +80,7 @@
void Shutdown(jvmtiEnv* jvmti, JNIEnv* jni);
private:
- void DoDump(jvmtiEnv* jvmti, JNIEnv* jni, std::unordered_map<jmethodID, uint64_t> copy);
+ void DoDump(jvmtiEnv* jvmti, JNIEnv* jni, const std::unordered_map<jmethodID, uint64_t>& copy);
jlong dump_id_;
jrawMonitorID mon_;
@@ -320,7 +321,7 @@
void SimpleProfileData::DoDump(jvmtiEnv* jvmti,
JNIEnv* jni,
- std::unordered_map<jmethodID, uint64_t> copy) {
+ const std::unordered_map<jmethodID, uint64_t>& copy) {
std::ostringstream oss;
oss << "[";
bool is_first = true;
diff --git a/tools/libcore_failures.txt b/tools/libcore_failures.txt
index 6e6ccb8..dec53a4 100644
--- a/tools/libcore_failures.txt
+++ b/tools/libcore_failures.txt
@@ -330,5 +330,24 @@
bug: 228441328,
names: ["tck.java.time",
"test.java.time"]
+},
+{
+ description: "vogar does not respect '@BeforeMethod' and '@AfterMethod' annotations yet",
+ result: ERROR,
+ bug: 231283845,
+ names: ["test.java.lang.invoke.MethodTypeTest#testGeneric",
+ "test.java.lang.invoke.MethodTypeTest#testMakeGeneric"]
+},
+{
+ description: "Timing out after ojluni tests were enabled",
+ result: ERROR,
+ bug: 231439593,
+ names: ["org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime"]
+},
+{
+ description: "Segfault with poison build on LUCI in multi-threaded MH test",
+ result: ERROR,
+ bug: 241199625,
+ names: ["libcore.java.lang.invoke.MethodHandleCombinersTest#testThrowCatchExceptionMultiThreaded"]
}
]
diff --git a/tools/libcore_fugu_failures.txt b/tools/libcore_fugu_failures.txt
index 0fff814..e02e3ff 100644
--- a/tools/libcore_fugu_failures.txt
+++ b/tools/libcore_fugu_failures.txt
@@ -112,7 +112,6 @@
"org.apache.harmony.crypto.tests.javax.crypto.func.CipherRSATest#test_RSANoPadding",
"org.apache.harmony.crypto.tests.javax.crypto.func.CipherRSATest#test_RSAShortKey",
"org.apache.harmony.crypto.tests.javax.crypto.func.KeyGeneratorFunctionalTest#test_",
- "org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime",
"org.apache.harmony.tests.java.math.BigIntegerTest#test_isProbablePrimeI",
"org.apache.harmony.tests.java.math.OldBigIntegerTest#test_ConstructorIILjava_util_Random",
"org.apache.harmony.tests.java.math.OldBigIntegerTest#test_isProbablePrimeI",
@@ -127,5 +126,109 @@
"org.apache.harmony.tests.javax.security.OldSHA1PRNGSecureRandomTest#testNextBytesbyteArray03",
"org.apache.harmony.tests.javax.security.OldSHA1PRNGSecureRandomTest#testSetSeedbyteArray02"
]
+},
+{
+ description: "Test using the getrandom() syscall, only available from Linux 3.17.",
+ result: ERROR,
+ bug: 141230711,
+ modes: [device],
+ names: [
+ "test.java.awt",
+ "test.java.io.ByteArrayInputStream",
+ "test.java.io.ByteArrayOutputStream",
+ "test.java.io.FileReader",
+ "test.java.io.FileWriter",
+ "test.java.io.InputStream",
+ "test.java.io.OutputStream",
+ "test.java.io.PrintStream",
+ "test.java.io.PrintWriter",
+ "test.java.io.Reader",
+ "test.java.io.Writer",
+ "test.java.lang.Boolean",
+ "test.java.lang.ClassLoader",
+ "test.java.lang.Double",
+ "test.java.lang.Float",
+ "test.java.lang.Integer",
+ "test.java.lang.Long",
+ "test.java.lang.StackWalker#main",
+ "test.java.lang.StrictMath.CubeRootTests",
+ "test.java.lang.StrictMath.ExactArithTests",
+ "test.java.lang.StrictMath.Expm1Tests",
+ "test.java.lang.StrictMath.ExpTests",
+ "test.java.lang.StrictMath.HyperbolicTests",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard1",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard2",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard3",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard4",
+ "test.java.lang.StrictMath.HypotTests#testHypot",
+ "test.java.lang.StrictMath.Log1pTests",
+ "test.java.lang.StrictMath.Log10Tests",
+ "test.java.lang.StrictMath.MultiplicationTests",
+ "test.java.lang.StrictMath.PowTests",
+ "test.java.lang.String",
+ "test.java.lang.Thread",
+ "test.java.lang.invoke",
+ "test.java.lang.ref.SoftReference",
+ "test.java.lang.ref.BasicTest",
+ "test.java.lang.ref.EnqueueNullRefTest",
+ "test.java.lang.ref.EnqueuePollRaceTest",
+ "test.java.lang.ref.ReferenceCloneTest",
+ "test.java.lang.ref.ReferenceEnqueuePendingTest",
+ "test.java.math.BigDecimal",
+ "test.java.math.BigInteger#testArithmetic",
+ "test.java.math.BigInteger#testBitCount",
+ "test.java.math.BigInteger#testBitLength",
+ "test.java.math.BigInteger#testbitOps",
+ "test.java.math.BigInteger#testBitwise",
+ "test.java.math.BigInteger#testByteArrayConv",
+ "test.java.math.BigInteger#testConstructor",
+ "test.java.math.BigInteger#testDivideAndReminder",
+ "test.java.math.BigInteger#testDivideLarge",
+ "test.java.math.BigInteger#testModExp",
+ "test.java.math.BigInteger#testMultiplyLarge",
+ "test.java.math.BigInteger#testNextProbablePrime",
+ "test.java.math.BigInteger#testPow",
+ "test.java.math.BigInteger#testSerialize",
+ "test.java.math.BigInteger#testShift",
+ "test.java.math.BigInteger#testSquare",
+ "test.java.math.BigInteger#testSquareLarge",
+ "test.java.math.BigInteger#testSquareRootAndReminder",
+ "test.java.math.BigInteger#testStringConv_generic",
+ "test.java.math.RoundingMode",
+ "test.java.net.DatagramSocket",
+ "test.java.net.Socket",
+ "test.java.net.SocketOptions",
+ "test.java.net.URLDecoder",
+ "test.java.net.URLEncoder",
+ "test.java.nio.channels.Channels",
+ "test.java.nio.channels.SelectionKey",
+ "test.java.nio.channels.Selector",
+ "test.java.nio.file",
+ "test.java.security.cert",
+ "test.java.security.KeyAgreement.KeyAgreementTest",
+ "test.java.security.KeyAgreement.KeySizeTest#testECDHKeySize",
+ "test.java.security.KeyAgreement.KeySpecTest",
+ "test.java.security.KeyAgreement.MultiThreadTest",
+ "test.java.security.KeyAgreement.NegativeTest",
+ "test.java.security.KeyStore",
+ "test.java.security.Provider",
+ "test.java.util.Arrays",
+ "test.java.util.Collection",
+ "test.java.util.Collections",
+ "test.java.util.Date",
+ "test.java.util.EnumMap",
+ "test.java.util.EnumSet",
+ "test.java.util.GregorianCalendar",
+ "test.java.util.LinkedHashMap",
+ "test.java.util.LinkedHashSet",
+ "test.java.util.List",
+ "test.java.util.Map",
+ "test.java.util.Optional",
+ "test.java.util.TimeZone",
+ "test.java.util.concurrent",
+ "test.java.util.function",
+ "test.java.util.stream",
+ "test.java.util.zip.ZipFile"
+ ]
}
]
diff --git a/tools/libcore_gcstress_debug_failures.txt b/tools/libcore_gcstress_debug_failures.txt
index 2193189..4718947 100644
--- a/tools/libcore_gcstress_debug_failures.txt
+++ b/tools/libcore_gcstress_debug_failures.txt
@@ -50,7 +50,6 @@
"org.apache.harmony.luni.tests.internal.net.www.protocol.https.HttpsURLConnectionTest#testConsequentProxyConnection",
"org.apache.harmony.tests.java.lang.ref.ReferenceQueueTest#test_removeJ",
"org.apache.harmony.tests.java.lang.ProcessManagerTest#testSleep",
- "org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime",
"org.apache.harmony.tests.java.util.TimerTest#testOverdueTaskExecutesImmediately",
"org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext"
]
diff --git a/tools/libcore_gcstress_failures.txt b/tools/libcore_gcstress_failures.txt
index 55bba72..81d6ca0 100644
--- a/tools/libcore_gcstress_failures.txt
+++ b/tools/libcore_gcstress_failures.txt
@@ -36,7 +36,6 @@
"libcore.java.util.stream.CollectorsTest#counting_largeStream",
"org.apache.harmony.tests.java.lang.ref.ReferenceQueueTest#test_remove",
"org.apache.harmony.tests.java.lang.String2Test#test_getBytes",
- "org.apache.harmony.tests.java.math.BigIntegerConstructorsTest#testConstructorPrime",
"org.apache.harmony.tests.java.text.DateFormatTest#test_getAvailableLocales",
"org.apache.harmony.tests.java.util.TimerTest#testOverdueTaskExecutesImmediately",
"org.apache.harmony.tests.java.util.WeakHashMapTest#test_keySet_hasNext"]
diff --git a/tools/luci/config/generated/cr-buildbucket.cfg b/tools/luci/config/generated/cr-buildbucket.cfg
index e4a5923..887b2af 100644
--- a/tools/luci/config/generated/cr-buildbucket.cfg
+++ b/tools/luci/config/generated/cr-buildbucket.cfg
@@ -35,7 +35,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -59,7 +59,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -83,7 +83,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -107,7 +107,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -131,7 +131,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -155,7 +155,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -179,7 +179,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -203,7 +203,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -227,7 +227,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -251,7 +251,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -275,7 +275,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -299,7 +299,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -323,7 +323,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -347,7 +347,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -371,7 +371,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -395,7 +395,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -419,7 +419,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -443,7 +443,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -467,7 +467,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -491,7 +491,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -515,7 +515,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -539,7 +539,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -563,7 +563,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -587,7 +587,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
builders {
@@ -611,7 +611,7 @@
service_account: "art-ci-builder@chops-service-accounts.iam.gserviceaccount.com"
experiments {
key: "luci.recipes.use_python3"
- value: 10
+ value: 100
}
}
}
diff --git a/tools/luci/config/generated/luci-scheduler.cfg b/tools/luci/config/generated/luci-scheduler.cfg
index d6c6ab5..ba7bcee 100644
--- a/tools/luci/config/generated/luci-scheduler.cfg
+++ b/tools/luci/config/generated/luci-scheduler.cfg
@@ -356,6 +356,40 @@
refs: "regexp:refs/heads/master-art"
}
}
+trigger {
+ id: "vogar"
+ realm: "ci"
+ acl_sets: "ci"
+ triggers: "angler-armv7-debug"
+ triggers: "angler-armv7-ndebug"
+ triggers: "angler-armv7-non-gen-cc"
+ triggers: "angler-armv8-debug"
+ triggers: "angler-armv8-ndebug"
+ triggers: "angler-armv8-non-gen-cc"
+ triggers: "bullhead-armv7-gcstress-ndebug"
+ triggers: "bullhead-armv8-gcstress-debug"
+ triggers: "bullhead-armv8-gcstress-ndebug"
+ triggers: "fugu-debug"
+ triggers: "fugu-ndebug"
+ triggers: "host-x86-cms"
+ triggers: "host-x86-debug"
+ triggers: "host-x86-gcstress-debug"
+ triggers: "host-x86-ndebug"
+ triggers: "host-x86-poison-debug"
+ triggers: "host-x86_64-cdex-fast"
+ triggers: "host-x86_64-cms"
+ triggers: "host-x86_64-debug"
+ triggers: "host-x86_64-ndebug"
+ triggers: "host-x86_64-non-gen-cc"
+ triggers: "host-x86_64-poison-debug"
+ triggers: "walleye-armv7-poison-debug"
+ triggers: "walleye-armv8-poison-debug"
+ triggers: "walleye-armv8-poison-ndebug"
+ gitiles {
+ repo: "https://android.googlesource.com/platform/external/vogar"
+ refs: "regexp:refs/heads/master"
+ }
+}
acl_sets {
name: "ci"
acls {
diff --git a/tools/luci/config/generated/project.cfg b/tools/luci/config/generated/project.cfg
index 4844ab4..e066382 100644
--- a/tools/luci/config/generated/project.cfg
+++ b/tools/luci/config/generated/project.cfg
@@ -7,7 +7,7 @@
name: "art"
access: "group:all"
lucicfg {
- version: "1.30.11"
+ version: "1.31.4"
package_dir: ".."
config_dir: "generated"
entry_point: "main.star"
diff --git a/tools/luci/config/main.star b/tools/luci/config/main.star
index f4ad488..5dd2fea 100755
--- a/tools/luci/config/main.star
+++ b/tools/luci/config/main.star
@@ -23,7 +23,7 @@
lucicfg.check_version("1.30.9", "Please update depot_tools")
luci.builder.defaults.experiments.set({
- "luci.recipes.use_python3": 10,
+ "luci.recipes.use_python3": 100,
})
# Use LUCI Scheduler BBv2 names and add Scheduler realms configs.
@@ -141,6 +141,13 @@
)
luci.gitiles_poller(
+ name = "vogar",
+ bucket = "ci",
+ repo = "https://android.googlesource.com/platform/external/vogar",
+ refs = ["refs/heads/master"],
+)
+
+luci.gitiles_poller(
name = "manifest",
bucket = "ci",
repo = "https://android.googlesource.com/platform/manifest",
@@ -185,6 +192,7 @@
"art",
"libcore",
"manifest",
+ "vogar",
],
)
luci.console_view_entry(
diff --git a/tools/prebuilt_libjdwp_art_failures.txt b/tools/prebuilt_libjdwp_art_failures.txt
index ee59315..4ded7d5 100644
--- a/tools/prebuilt_libjdwp_art_failures.txt
+++ b/tools/prebuilt_libjdwp_art_failures.txt
@@ -106,5 +106,5 @@
result: EXEC_FAILED,
bug: 69169846,
name: "org.apache.harmony.jpda.tests.jdwp.DDM_DDMTest#testChunk001"
-},
+}
]
diff --git a/tools/public.libraries.buildbot.txt b/tools/public.libraries.buildbot.txt
index e23cf2c..9b0dc68 100644
--- a/tools/public.libraries.buildbot.txt
+++ b/tools/public.libraries.buildbot.txt
@@ -1,6 +1,6 @@
-libbacktrace.so
libc.so
libc++.so
libdl.so
libm.so
libnativehelper.so
+libunwindstack.so
diff --git a/tools/run-libcore-tests.py b/tools/run-libcore-tests.py
index 1e6070a..de1636d 100755
--- a/tools/run-libcore-tests.py
+++ b/tools/run-libcore-tests.py
@@ -159,21 +159,15 @@
"test.java.math.BigInteger#testDivideAndReminder",
"test.java.math.BigInteger#testDivideLarge",
"test.java.math.BigInteger#testModExp",
- "test.java.math.BigInteger#testModInv",
"test.java.math.BigInteger#testMultiplyLarge",
"test.java.math.BigInteger#testNextProbablePrime",
"test.java.math.BigInteger#testPow",
- "test.java.math.BigInteger#testPrime",
"test.java.math.BigInteger#testSerialize",
"test.java.math.BigInteger#testShift",
"test.java.math.BigInteger#testSquare",
"test.java.math.BigInteger#testSquareLarge",
- "test.java.math.BigInteger#testSquareRoot",
"test.java.math.BigInteger#testSquareRootAndReminder",
"test.java.math.BigInteger#testStringConv_generic",
- "test.java.math.BigInteger#testStringConv_schoenhage_threshold_pow0",
- "test.java.math.BigInteger#testStringConv_schoenhage_threshold_pow1",
- "test.java.math.BigInteger#testStringConv_schoenhage_threshold_pow2",
"test.java.math.RoundingMode",
# test.java.net
"test.java.net.DatagramSocket",
@@ -190,7 +184,6 @@
"test.java.security.cert",
# Sharded test.java.security.KeyAgreement
"test.java.security.KeyAgreement.KeyAgreementTest",
- "test.java.security.KeyAgreement.KeySizeTest#testDHKeySize",
"test.java.security.KeyAgreement.KeySizeTest#testECDHKeySize",
"test.java.security.KeyAgreement.KeySpecTest",
"test.java.security.KeyAgreement.MultiThreadTest",
@@ -242,6 +235,128 @@
CLASSPATH = ["core-tests", "core-ojtests", "jsr166-tests", "mockito-target"]
+SLOW_OJLUNI_TESTS = {
+ "test.java.awt",
+ "test.java.lang.String",
+ "test.java.lang.invoke",
+ "test.java.nio.channels.Selector",
+ "test.java.time",
+ "test.java.util.Arrays",
+ "test.java.util.Map",
+ "test.java.util.concurrent",
+ "test.java.util.stream",
+ "test.java.util.zip.ZipFile",
+ "tck.java.time",
+}
+
+# Disabled to unblock art-buildbot
+# These tests fail with "java.io.IOException: Stream closed", tracked in
+# http://b/235566533 and http://b/208639267
+DISABLED_GCSTRESS_DEBUG_TESTS = {
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard1",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard2",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard3",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard4",
+ "test.java.math.BigDecimal",
+ "test.java.math.BigInteger#testConstructor",
+}
+
+DISABLED_FUGU_TESTS = {
+ "test.java.awt",
+ "test.java.io.ByteArrayInputStream",
+ "test.java.io.ByteArrayOutputStream",
+ "test.java.io.InputStream",
+ "test.java.io.OutputStream",
+ "test.java.io.PrintStream",
+ "test.java.io.PrintWriter",
+ "test.java.io.Reader",
+ "test.java.io.Writer",
+ "test.java.lang.Boolean",
+ "test.java.lang.ClassLoader",
+ "test.java.lang.Double",
+ "test.java.lang.Float",
+ "test.java.lang.Integer",
+ "test.java.lang.Long",
+ "test.java.lang.StrictMath.CubeRootTests",
+ "test.java.lang.StrictMath.Expm1Tests",
+ "test.java.lang.StrictMath.ExpTests",
+ "test.java.lang.StrictMath.HyperbolicTests",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard1",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard2",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard3",
+ "test.java.lang.StrictMath.HypotTests#testAgainstTranslit_shard4",
+ "test.java.lang.StrictMath.HypotTests#testHypot",
+ "test.java.lang.StrictMath.Log1pTests",
+ "test.java.lang.StrictMath.Log10Tests",
+ "test.java.lang.StrictMath.MultiplicationTests",
+ "test.java.lang.StrictMath.PowTests",
+ "test.java.lang.String",
+ "test.java.lang.Thread",
+ "test.java.lang.invoke",
+ "test.java.lang.ref.SoftReference",
+ "test.java.lang.ref.BasicTest",
+ "test.java.lang.ref.EnqueueNullRefTest",
+ "test.java.lang.ref.EnqueuePollRaceTest",
+ "test.java.lang.ref.ReferenceCloneTest",
+ "test.java.lang.ref.ReferenceEnqueuePendingTest",
+ "test.java.math.BigDecimal",
+ "test.java.math.BigInteger#testArithmetic",
+ "test.java.math.BigInteger#testBitCount",
+ "test.java.math.BigInteger#testBitLength",
+ "test.java.math.BigInteger#testbitOps",
+ "test.java.math.BigInteger#testBitwise",
+ "test.java.math.BigInteger#testByteArrayConv",
+ "test.java.math.BigInteger#testConstructor",
+ "test.java.math.BigInteger#testDivideAndReminder",
+ "test.java.math.BigInteger#testDivideLarge",
+ "test.java.math.BigInteger#testModExp",
+ "test.java.math.BigInteger#testMultiplyLarge",
+ "test.java.math.BigInteger#testNextProbablePrime",
+ "test.java.math.BigInteger#testPow",
+ "test.java.math.BigInteger#testSerialize",
+ "test.java.math.BigInteger#testShift",
+ "test.java.math.BigInteger#testSquare",
+ "test.java.math.BigInteger#testSquareLarge",
+ "test.java.math.BigInteger#testSquareRootAndReminder",
+ "test.java.math.BigInteger#testStringConv_generic",
+ "test.java.math.RoundingMode",
+ "test.java.net.DatagramSocket",
+ "test.java.net.Socket",
+ "test.java.net.SocketOptions",
+ "test.java.net.URLDecoder",
+ "test.java.net.URLEncoder",
+ "test.java.nio.channels.Channels",
+ "test.java.nio.channels.SelectionKey",
+ "test.java.nio.channels.Selector",
+ "test.java.nio.file",
+ "test.java.security.cert",
+ "test.java.security.KeyAgreement.KeyAgreementTest",
+ "test.java.security.KeyAgreement.KeySizeTest#testECDHKeySize",
+ "test.java.security.KeyAgreement.KeySpecTest",
+ "test.java.security.KeyAgreement.MultiThreadTest",
+ "test.java.security.KeyAgreement.NegativeTest",
+ "test.java.security.KeyStore",
+ "test.java.security.Provider",
+ "test.java.time",
+ "test.java.util.Arrays",
+ "test.java.util.Collection",
+ "test.java.util.Collections",
+ "test.java.util.Date",
+ "test.java.util.EnumMap",
+ "test.java.util.EnumSet",
+ "test.java.util.GregorianCalendar",
+ "test.java.util.LinkedHashMap",
+ "test.java.util.LinkedHashSet",
+ "test.java.util.List",
+ "test.java.util.Map",
+ "test.java.util.Optional",
+ "test.java.util.TestFormatter",
+ "test.java.util.TimeZone",
+ "test.java.util.function",
+ "test.java.util.stream",
+ "tck.java.time",
+}
+
def get_jar_filename(classpath):
base_path = (ANDROID_PRODUCT_OUT + "/../..") if ANDROID_PRODUCT_OUT else "out/target"
base_path = os.path.normpath(base_path) # Normalize ".." components for readability.
@@ -275,6 +390,11 @@
# See b/78228743 and b/178351808.
if args.gcstress or args.debug or args.mode == "jvm":
test_names = list(t for t in test_names if not t.startswith("libcore.highmemorytest"))
+ test_names = list(filter(lambda x: x not in SLOW_OJLUNI_TESTS, test_names))
+ if args.gcstress and args.debug:
+ test_names = list(filter(lambda x: x not in DISABLED_GCSTRESS_DEBUG_TESTS, test_names))
+ if not args.getrandom:
+ test_names = list(filter(lambda x: x not in DISABLED_FUGU_TESTS, test_names))
return test_names
def get_vogar_command(test_name):
@@ -302,7 +422,7 @@
if ART_TEST_CHROOT:
cmd.append(f"--chroot {ART_TEST_CHROOT} --device-dir=/tmp/vogar/test-{test_name}")
else:
- cmd.append("--device-dir=/data/local/tmp/vogar/test-{test_name}")
+ cmd.append(f"--device-dir=/data/local/tmp/vogar/test-{test_name}")
cmd.append(f"--vm-command={ART_TEST_ANDROID_ROOT}/bin/art")
else:
cmd.append(f"--device-dir=/tmp/vogar/test-{test_name}")
diff --git a/tools/run-libjdwp-tests.sh b/tools/run-libjdwp-tests.sh
index efb2737..06e34f9 100755
--- a/tools/run-libjdwp-tests.sh
+++ b/tools/run-libjdwp-tests.sh
@@ -138,6 +138,10 @@
expectations="$expectations --expectations $PWD/art/tools/external_oj_libjdwp_art_gcstress_debug_failures.txt"
fi
+if [[ "${ART_USE_READ_BARRIER}" = "false" ]]; then
+ expectations="$expectations --expectations $PWD/art/tools/external_oj_libjdwp_art_no_read_barrier_failures.txt"
+fi
+
function verbose_run() {
echo "$@"
env "$@"
diff --git a/tools/signal_dumper/Android.bp b/tools/signal_dumper/Android.bp
index 33450d1..00948b8 100644
--- a/tools/signal_dumper/Android.bp
+++ b/tools/signal_dumper/Android.bp
@@ -48,18 +48,6 @@
},
}
-cc_defaults {
- name: "signal_dumper_libbacktrace_static_deps",
- defaults: [
- "signal_dumper_libbase_static_deps",
- "signal_dumper_libunwindstack_static_deps",
- ],
- static_libs: [
- "libbase",
- "libunwindstack",
- ],
-}
-
art_cc_binary {
name: "signal_dumper",
@@ -73,14 +61,14 @@
defaults: [
"art_defaults",
- "signal_dumper_libbacktrace_static_deps",
"signal_dumper_libbase_static_deps",
+ "signal_dumper_libunwindstack_static_deps",
],
srcs: ["signal_dumper.cc"],
static_libs: [
- "libbacktrace",
"libbase",
+ "libunwindstack",
],
}
diff --git a/tools/signal_dumper/signal_dumper.cc b/tools/signal_dumper/signal_dumper.cc
index e9a589e..e88ac19 100644
--- a/tools/signal_dumper/signal_dumper.cc
+++ b/tools/signal_dumper/signal_dumper.cc
@@ -15,6 +15,7 @@
*/
#include <dirent.h>
+#include <inttypes.h>
#include <poll.h>
#include <sys/prctl.h>
#include <sys/ptrace.h>
@@ -38,8 +39,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
-#include <backtrace/Backtrace.h>
-#include <backtrace/BacktraceMap.h>
+#include <unwindstack/AndroidUnwinder.h>
namespace art {
namespace {
@@ -492,11 +492,10 @@
constexpr bool kIs64Bit = false;
#endif
-void DumpThread(pid_t pid,
+void DumpThread(unwindstack::AndroidRemoteUnwinder& unwinder, pid_t pid,
pid_t tid,
const std::string* addr2line_path,
- const char* prefix,
- BacktraceMap* map) {
+ const char* prefix) {
LOG(ERROR) << std::endl << "=== pid: " << pid << " tid: " << tid << " ===" << std::endl;
constexpr uint32_t kMaxWaitMicros = 1000 * 1000; // 1s.
@@ -504,50 +503,41 @@
LOG(ERROR) << "Failed to wait for sigstop on " << tid;
}
- std::unique_ptr<Backtrace> backtrace(Backtrace::Create(pid, tid, map));
- if (backtrace == nullptr) {
- LOG(ERROR) << prefix << "(failed to create Backtrace for thread " << tid << ")";
- return;
- }
- backtrace->SetSkipFrames(false);
- if (!backtrace->Unwind(0, nullptr)) {
- LOG(ERROR) << prefix << "(backtrace::Unwind failed for thread " << tid
- << ": " << backtrace->GetErrorString(backtrace->GetError()) << ")";
- return;
- }
- if (backtrace->NumFrames() == 0) {
- LOG(ERROR) << prefix << "(no native stack frames for thread " << tid << ")";
+ unwindstack::AndroidUnwinderData data;
+ if (!unwinder.Unwind(tid, data)) {
+ LOG(ERROR) << prefix << "(Unwind failed for thread " << tid << ": "
+ << data.GetErrorString() << ")";
return;
}
std::unique_ptr<addr2line::Addr2linePipe> addr2line_state;
-
- for (Backtrace::const_iterator it = backtrace->begin();
- it != backtrace->end(); ++it) {
+ data.DemangleFunctionNames();
+ for (const unwindstack::FrameData& frame : data.frames) {
std::ostringstream oss;
- oss << prefix << StringPrintf("#%02zu pc ", it->num);
+ oss << prefix << StringPrintf("#%02zu pc ", frame.num);
bool try_addr2line = false;
- if (!BacktraceMap::IsValid(it->map)) {
- oss << StringPrintf(kIs64Bit ? "%016" PRIx64 " ???" : "%08" PRIx64 " ???", it->pc);
+ if (frame.map_info == nullptr) {
+ oss << StringPrintf(kIs64Bit ? "%016" PRIx64 " ???" : "%08" PRIx64 " ???", frame.pc);
} else {
- oss << StringPrintf(kIs64Bit ? "%016" PRIx64 " " : "%08" PRIx64 " ", it->rel_pc);
- if (it->map.name.empty()) {
- oss << StringPrintf("<anonymous:%" PRIx64 ">", it->map.start);
+ oss << StringPrintf(kIs64Bit ? "%016" PRIx64 " " : "%08" PRIx64 " ", frame.rel_pc);
+ if (frame.map_info->name().empty()) {
+ oss << StringPrintf("<anonymous:%" PRIx64 ">", frame.map_info->start());
} else {
- oss << it->map.name;
+ oss << frame.map_info->name().c_str();
}
- if (it->map.offset != 0) {
- oss << StringPrintf(" (offset %" PRIx64 ")", it->map.offset);
+ if (frame.map_info->offset() != 0) {
+ oss << StringPrintf(" (offset %" PRIx64 ")", frame.map_info->offset());
}
oss << " (";
- if (!it->func_name.empty()) {
- oss << it->func_name;
- if (it->func_offset != 0) {
- oss << "+" << it->func_offset;
+ const std::string& function_name = frame.function_name;
+ if (!function_name.empty()) {
+ oss << function_name;
+ if (frame.function_offset != 0) {
+ oss << "+" << frame.function_offset;
}
// Functions found using the gdb jit interface will be in an empty
// map that cannot be found using addr2line.
- if (!it->map.name.empty()) {
+ if (!frame.map_info->name().empty()) {
try_addr2line = true;
}
} else {
@@ -558,8 +548,8 @@
LOG(ERROR) << oss.str() << std::endl;
if (try_addr2line && addr2line_path != nullptr) {
addr2line::Addr2line(*addr2line_path,
- it->map.name,
- it->rel_pc,
+ frame.map_info->name(),
+ frame.rel_pc,
LOG_STREAM(ERROR),
prefix,
&addr2line_state);
@@ -593,14 +583,9 @@
LOG(ERROR) << "Did not receive SIGSTOP for pid " << forked_pid;
}
- std::unique_ptr<BacktraceMap> backtrace_map(BacktraceMap::Create(forked_pid));
- if (backtrace_map == nullptr) {
- LOG(ERROR) << "Could not create BacktraceMap";
- return;
- }
-
+ unwindstack::AndroidRemoteUnwinder unwinder(forked_pid);
for (pid_t tid : tids) {
- DumpThread(forked_pid, tid, addr2line_path.get(), " ", backtrace_map.get());
+ DumpThread(unwinder, forked_pid, tid, addr2line_path.get(), " ");
}
}
diff --git a/tools/tracefast-plugin/tracefast.cc b/tools/tracefast-plugin/tracefast.cc
index 618742d..734dce0 100644
--- a/tools/tracefast-plugin/tracefast.cc
+++ b/tools/tracefast-plugin/tracefast.cc
@@ -60,7 +60,6 @@
override REQUIRES_SHARED(art::Locks::mutator_lock_) { }
void MethodUnwind(art::Thread* thread ATTRIBUTE_UNUSED,
- art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
art::ArtMethod* method ATTRIBUTE_UNUSED,
uint32_t dex_pc ATTRIBUTE_UNUSED)
override REQUIRES_SHARED(art::Locks::mutator_lock_) { }
diff --git a/tools/veridex/Android.mk b/tools/veridex/Android.mk
index f7c8d50..9ea9b3a 100644
--- a/tools/veridex/Android.mk
+++ b/tools/veridex/Android.mk
@@ -23,14 +23,14 @@
system_stub_dex := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/core_dex_intermediates/classes.dex
$(system_stub_dex): PRIVATE_MIN_SDK_VERSION := 1000
-$(system_stub_dex): $(call resolve-prebuilt-sdk-jar-path,system_current) | $(ZIP2ZIP) $(DX)
+$(system_stub_dex): $(call resolve-prebuilt-sdk-jar-path,system_current) | $(ZIP2ZIP) $(D8)
$(transform-classes.jar-to-dex)
$(call declare-1p-target,$(system_stub_dex),art)
oahl_stub_dex := $(TARGET_OUT_COMMON_INTERMEDIATES)/PACKAGING/oahl_dex_intermediates/classes.dex
$(oahl_stub_dex): PRIVATE_MIN_SDK_VERSION := 1000
-$(oahl_stub_dex): $(call get-prebuilt-sdk-dir,current)/org.apache.http.legacy.jar | $(ZIP2ZIP) $(DX)
+$(oahl_stub_dex): $(call get-prebuilt-sdk-dir,current)/org.apache.http.legacy.jar | $(ZIP2ZIP) $(D8)
$(transform-classes.jar-to-dex)
$(call declare-1p-target,$(oahl_stub_dex),art)